初始化仓库
32
.env
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# 运行模式,EVA基础工程中并没有使用,但在实际开发中可以用于做环境级别的特殊处理,取值如下
|
||||||
|
# testing=测试模式
|
||||||
|
# production=生产模式
|
||||||
|
VITE_APP_MODE = 'production'
|
||||||
|
|
||||||
|
# 是否开启DEBUG模式,on开启,off关闭,开启后将详细的输出日志(如接口请求,缓存读取日志等)
|
||||||
|
VITE_APP_DEBUG = 'off'
|
||||||
|
|
||||||
|
# 路由方式
|
||||||
|
VITE_APP_ROUTER_MODE = 'history'
|
||||||
|
|
||||||
|
# 项目上下文路径
|
||||||
|
VITE_APP_CONTEXT_PATH = '/'
|
||||||
|
|
||||||
|
# 接口前缀
|
||||||
|
VITE_APP_API_PREFIX = '/api'
|
||||||
|
|
||||||
|
# 接口地址
|
||||||
|
VITE_APP_API_URL = 'http://localhost:10010'
|
||||||
|
|
||||||
|
# OSS通用图片访问前缀
|
||||||
|
VITE_APP_COMMON_IMAGE_PREFIX = '/resource/oss/image?f='
|
||||||
|
|
||||||
|
# OSS通用文件访问前缀
|
||||||
|
VITE_APP_COMMON_ATTACH_PREFIX = '/resource/oss/attach?f='
|
||||||
|
|
||||||
|
# 加密请求配置
|
||||||
|
# 密钥
|
||||||
|
VITE_APP_ENCRYPT_REQUEST_KEY = 2B7E151628AED2A6
|
||||||
|
# 向量
|
||||||
|
VITE_APP_ENCRYPT_REQUEST_IV = 3D8A9F0BAC4E7D61
|
||||||
|
|
8
.env.development
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 开发环境配置
|
||||||
|
VITE_APP_NODE_ENV = 'development'
|
||||||
|
|
||||||
|
# 运行模式
|
||||||
|
VITE_APP_MODE = 'testing'
|
||||||
|
|
||||||
|
# 是否开启DEBUG模式
|
||||||
|
VITE_APP_DEBUG = 'on'
|
2
.env.production
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# 生产环境配置
|
||||||
|
VITE_APP_NODE_ENV = 'production'
|
2
.env.staging
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# 测试环境配置
|
||||||
|
VITE_APP_NODE_ENV = 'production'
|
28
.gitignore
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
dist.zip
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
# GoldPanKit
|
||||||
|
.kit/translated
|
101
.kit/kit.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"name": "eva-cms-admin-front",
|
||||||
|
"label": "eva-cms-admin-front",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": false,
|
||||||
|
"receivable": false,
|
||||||
|
"compiler": "freemarker",
|
||||||
|
"repository": "",
|
||||||
|
"branch": "",
|
||||||
|
"supportedDatabases": [],
|
||||||
|
"presetPlugins": [],
|
||||||
|
"prices": [
|
||||||
|
{
|
||||||
|
"type": "FREE",
|
||||||
|
"value": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"name": "安装依赖",
|
||||||
|
"type": "Markdown",
|
||||||
|
"content": "在项目目录下执行以下命令,安装项目依赖包\n```shell\nnpm install --registry https://registry.npmmirror.com\n```",
|
||||||
|
"contentType": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "启动项目",
|
||||||
|
"type": "Markdown",
|
||||||
|
"content": "待项目依赖包正确安装完成后,执行以下命令即可启动项目\n```shell\nnpm run dev\n```",
|
||||||
|
"contentType": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unbuilds": [],
|
||||||
|
"variables": [
|
||||||
|
{
|
||||||
|
"id": "syjljese1mow",
|
||||||
|
"type": "variable",
|
||||||
|
"name": "loginTitle",
|
||||||
|
"label": "项目标题",
|
||||||
|
"inputType": "input",
|
||||||
|
"required": true,
|
||||||
|
"hidden": false,
|
||||||
|
"defaultValue": "伊娃CMS-轻量级CMS系统",
|
||||||
|
"compiler": "static",
|
||||||
|
"remark": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1ncl776trtwgg",
|
||||||
|
"type": "variable",
|
||||||
|
"name": "loginDescription",
|
||||||
|
"label": "登录页项目说明",
|
||||||
|
"inputType": "input",
|
||||||
|
"required": false,
|
||||||
|
"hidden": false,
|
||||||
|
"defaultValue": "金镐开源组织研发,免费、开源、轻量、高规范!",
|
||||||
|
"compiler": "static",
|
||||||
|
"remark": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1qm75h7t43dw0",
|
||||||
|
"type": "variable",
|
||||||
|
"name": "menuTitle",
|
||||||
|
"label": "菜单顶部项目名称",
|
||||||
|
"inputType": "input",
|
||||||
|
"required": true,
|
||||||
|
"hidden": false,
|
||||||
|
"defaultValue": "伊娃CMS",
|
||||||
|
"compiler": "static",
|
||||||
|
"remark": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"translator": {
|
||||||
|
"output": ".kit/translated",
|
||||||
|
"filepath": "",
|
||||||
|
"content": "// freemarker语法修复\nif (setting.compiler === 'freemarker') {\n content = content\n .replace(/\\$\\{/g, '<#noparse>${</#noparse>')\n .replace(/\\#\\{/g, '<#noparse>#{</#noparse>')\n}\n\n// login.vue内容替换\nif (filename === 'login.vue') {\n content = content\n .replace(/<h2>.*<\\/h2>/, '<h2>${loginTitle}</h2>')\n .replace(/<h3>.*<\\/h3>/, '<h3>${loginDescription}</h3>')\n}\n\n// index.html内容替换\nif (filename === 'index.html') {\n content = content\n .replace(/<title>.*<\\/title>/, '<title>${loginTitle}</title>')\n}\n\n// AppMenu替换菜单顶部标题\nif (filename === 'AppMenu.vue') {\n content = content\n .replace('伊娃CMS', '${menuTitle}')\n}\n\nreturn content"
|
||||||
|
},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"path": "src/views/login.vue",
|
||||||
|
"compiler": "freemarker",
|
||||||
|
"withoutIfNotExists": false,
|
||||||
|
"enableExpress": "",
|
||||||
|
"variables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "index.html",
|
||||||
|
"compiler": "freemarker",
|
||||||
|
"withoutIfNotExists": false,
|
||||||
|
"enableExpress": "",
|
||||||
|
"variables": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "src/components/layout/AppMenu.vue",
|
||||||
|
"compiler": "freemarker",
|
||||||
|
"withoutIfNotExists": false,
|
||||||
|
"enableExpress": "",
|
||||||
|
"variables": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"introduce": "🚩 eva-cms管理后台的前端工程。",
|
||||||
|
"readme": "<div align=\"center\">\n <img src=\"https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/logo.png\" width=\"120px\" style=\"width:80px;height:80px;\" />\n <h1>伊娃CMS后台前端</h1>\n</div>\n\n## 在线演示 & 技术文档\n\n- 演示地址/政企门户主题:[http://gov.eva-cms.goldpankit.com/](http://gov.eva-cms.goldpankit.com/)\n- 演示地址/后台管理:[http://admin.eva-cms.goldpankit.com/](http://admin.eva-cms.goldpankit.com/)\n- 官方文档:[https://www.yuque.com/u21334242/eva-cms](https://www.yuque.com/u21334242/eva-cms)\n\n## 金镐开源组织生态项目\n\n| 版本 | 说明 | 开源地址 | 构建地址 |\n|---------------------|-------------------|--------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|\n| eva-cms | 伊娃CMS后端 | [https://gitee.com/goldpankit/eva-cms](https://gitee.com/goldpankit/eva-cms) | [http://goldpankit.com/space/Eva/eva-cms](http://goldpankit.com/space/Eva/eva-cms) |\n| eva-cms-admin-front | 伊娃CMS后台管理前端 | [https://gitee.com/goldpankit/eva-cms-admin-front](https://gitee.com/goldpankit/eva-cms-admin-front) | [http://goldpankit.com/space/Eva/eva-cms-admin-front](http://goldpankit.com/space/Eva/eva-cms-admin-front) |\n| eva-cms-website-gov | 伊娃CMS政企门户主题站点前端 | [https://gitee.com/goldpankit/eva-cms-website-gov](https://gitee.com/goldpankit/eva-cms-website-gov) | [http://goldpankit.com/space/Eva/eva-cms-website-gov](http://goldpankit.com/space/Eva/eva-cms-website-gov) |\n| eva-server | 伊娃权限系统单工程版本 | [https://gitee.com/goldpankit/eva-server](https://gitee.com/goldpankit/eva-server) | [http://goldpankit.com/space/Eva/eva-server](http://goldpankit.com/space/Eva/eva-server) |\n| eva-server-modules | 伊娃权限系统Maven多模块版本 | [https://gitee.com/goldpankit/eva-server-modules](https://gitee.com/goldpankit/eva-server-modules) | [http://www.goldpankit.com/space/Eva/eva-server-modules](http://www.goldpankit.com/space/Eva/eva-server-modules) |\n| eva-vue2 | 伊娃权限系统前端vue2版本 | [https://gitee.com/goldpankit/eva-vue/tree/vue2/](https://gitee.com/goldpankit/eva-vue/tree/vue2/) | [http://goldpankit.com/space/Eva/eva-vue2](http://goldpankit.com/space/Eva/eva-vue2) |\n| eva-vue3-options | 伊娃权限系统前端vue3选项式版本 | [https://gitee.com/goldpankit/eva-vue/tree/vue3-options/](https://gitee.com/goldpankit/eva-vue/tree/vue3-options/) | [http://www.goldpankit.com/space/Eva/eva-vue3-options](http://www.goldpankit.com/space/Eva/eva-vue3-options) |\n\n\n## 项目特点\n\n1. 可扩展的功能模块,默认情况下提供了文章管理、栏目管理、资源管理、模板管理等CMS系统的基础模块,以及用户管理、角色管理、菜单管理等权限系统基础模块,使用GoldPanKit可进一步进行源码级功能模块的扩展。\n2. 不用担心存在BUG,如果存在BUG,使用GoldPanKit可实现一键升级。\n3. 不用担心存在安全漏洞,如果存在安全漏洞,GoldPanKit会进行提醒并支持一键升级。\n4. 规范化代码 + 详细的代码注释。\n5. 基于Eva 4权限系统进行研发,安全稳定!\n6. 丰富的插件市场,可使用GoldPanKit进行单表、多表的页面生成。\n\n## 绝对优势\n\n结合GoldPanKit可实现代码直接生成到项目中,安装更多的功能模块,一键修复BUG等。\n\n## 项目预览\n\n**登录页**\n\n\n**文章管理**\n\n\n**栏目管理**\n\n\n**资源管理**\n\n\n\n**模板管理**\n\n"
|
||||||
|
}
|
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 金镐开源组织
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
54
README.md
@ -1,2 +1,54 @@
|
|||||||
# nankai-cms-admin
|
<div align="center">
|
||||||
|
<img src="https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/logo.png" width="120px" style="width:80px;height:80px;" />
|
||||||
|
<h1>伊娃CMS后台前端</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
## 在线演示 & 技术文档
|
||||||
|
|
||||||
|
- 演示地址/政企门户主题:[http://gov.eva-cms.goldpankit.com/](http://gov.eva-cms.goldpankit.com/)
|
||||||
|
- 演示地址/后台管理:[http://admin.eva-cms.goldpankit.com/](http://admin.eva-cms.goldpankit.com/)
|
||||||
|
- 官方文档:[https://www.yuque.com/u21334242/eva-cms](https://www.yuque.com/u21334242/eva-cms)
|
||||||
|
|
||||||
|
## 金镐开源组织生态项目
|
||||||
|
|
||||||
|
| 版本 | 说明 | 开源地址 | 构建地址 |
|
||||||
|
|---------------------|-------------------|--------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| eva-cms | 伊娃CMS后端 | [https://gitee.com/goldpankit/eva-cms](https://gitee.com/goldpankit/eva-cms) | [http://goldpankit.com/space/Eva/eva-cms](http://goldpankit.com/space/Eva/eva-cms) |
|
||||||
|
| eva-cms-admin-front | 伊娃CMS后台管理前端 | [https://gitee.com/goldpankit/eva-cms-admin-front](https://gitee.com/goldpankit/eva-cms-admin-front) | [http://goldpankit.com/space/Eva/eva-cms-admin-front](http://goldpankit.com/space/Eva/eva-cms-admin-front) |
|
||||||
|
| eva-cms-website-gov | 伊娃CMS政企门户主题站点前端 | [https://gitee.com/goldpankit/eva-cms-website-gov](https://gitee.com/goldpankit/eva-cms-website-gov) | [http://goldpankit.com/space/Eva/eva-cms-website-gov](http://goldpankit.com/space/Eva/eva-cms-website-gov) |
|
||||||
|
| eva-server | 伊娃权限系统单工程版本 | [https://gitee.com/goldpankit/eva-server](https://gitee.com/goldpankit/eva-server) | [http://goldpankit.com/space/Eva/eva-server](http://goldpankit.com/space/Eva/eva-server) |
|
||||||
|
| eva-server-modules | 伊娃权限系统Maven多模块版本 | [https://gitee.com/goldpankit/eva-server-modules](https://gitee.com/goldpankit/eva-server-modules) | [http://www.goldpankit.com/space/Eva/eva-server-modules](http://www.goldpankit.com/space/Eva/eva-server-modules) |
|
||||||
|
| eva-vue2 | 伊娃权限系统前端vue2版本 | [https://gitee.com/goldpankit/eva-vue/tree/vue2/](https://gitee.com/goldpankit/eva-vue/tree/vue2/) | [http://goldpankit.com/space/Eva/eva-vue2](http://goldpankit.com/space/Eva/eva-vue2) |
|
||||||
|
| eva-vue3-options | 伊娃权限系统前端vue3选项式版本 | [https://gitee.com/goldpankit/eva-vue/tree/vue3-options/](https://gitee.com/goldpankit/eva-vue/tree/vue3-options/) | [http://www.goldpankit.com/space/Eva/eva-vue3-options](http://www.goldpankit.com/space/Eva/eva-vue3-options) |
|
||||||
|
|
||||||
|
|
||||||
|
## 项目特点
|
||||||
|
|
||||||
|
1. 可扩展的功能模块,默认情况下提供了文章管理、栏目管理、资源管理、模板管理等CMS系统的基础模块,以及用户管理、角色管理、菜单管理等权限系统基础模块,使用GoldPanKit可进一步进行源码级功能模块的扩展。
|
||||||
|
2. 不用担心存在BUG,如果存在BUG,使用GoldPanKit可实现一键升级。
|
||||||
|
3. 不用担心存在安全漏洞,如果存在安全漏洞,GoldPanKit会进行提醒并支持一键升级。
|
||||||
|
4. 规范化代码 + 详细的代码注释。
|
||||||
|
5. 基于Eva 4权限系统进行研发,安全稳定!
|
||||||
|
6. 丰富的插件市场,可使用GoldPanKit进行单表、多表的页面生成。
|
||||||
|
|
||||||
|
## 绝对优势
|
||||||
|
|
||||||
|
结合GoldPanKit可实现代码直接生成到项目中,安装更多的功能模块,一键修复BUG等。
|
||||||
|
|
||||||
|
## 项目预览
|
||||||
|
|
||||||
|
**登录页**
|
||||||
|

|
||||||
|
|
||||||
|
**文章管理**
|
||||||
|

|
||||||
|
|
||||||
|
**栏目管理**
|
||||||
|

|
||||||
|
|
||||||
|
**资源管理**
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
**模板管理**
|
||||||
|

|
||||||
|
15
index.html
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" data-href="/favicon.ico">
|
||||||
|
<link rel="stylesheet" data-href="/icons/system/iconfont.css">
|
||||||
|
<link rel="stylesheet" data-href="/icons/cms/iconfont.css">
|
||||||
|
<title>伊娃CMS-轻量级CMS系统</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
71
package.json
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "eva-vue3-options",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"staging": "vite --mode staging",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:staging": "vite build --mode staging",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"fix": "eslint src --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
|
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"element-plus": "^2.6.2",
|
||||||
|
"js-base64": "^3.7.7",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"js-file-download": "^0.4.12",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"pinyin-pro": "^3.25.0",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-clipboard3": "^2.0.0",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
|
"eslint-plugin-vue": "^9.24.0",
|
||||||
|
"sass": "^1.72.0",
|
||||||
|
"vite": "^5.2.0",
|
||||||
|
"vite-plugin-eslint": "^1.8.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rollup/rollup-win32-x64-msvc": "^4.22.1"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"indent": [
|
||||||
|
"error",
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"vue/multi-word-component-names": "off",
|
||||||
|
"vue/no-reserved-component-names": "off",
|
||||||
|
"generator-star-spacing": "off",
|
||||||
|
"no-debugger": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"volta": {
|
||||||
|
"node": "18.15.0",
|
||||||
|
"npm": "8.19.4",
|
||||||
|
"yarn": "1.22.21"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/avatar/man.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
public/avatar/woman.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
public/favicon.ico
Normal file
After Width: | Height: | Size: 4.2 KiB |
539
public/icons/cms/demo.css
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
/* Logo 字体 */
|
||||||
|
@font-face {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||||
|
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||||
|
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
font-family: "iconfont logo";
|
||||||
|
font-size: 160px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* tabs */
|
||||||
|
.nav-tabs {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-tabs .nav-more {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 42px;
|
||||||
|
line-height: 42px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
#tabs li {
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
margin-bottom: -1px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#tabs .active {
|
||||||
|
border-bottom-color: #f00;
|
||||||
|
color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-container .content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 页面布局 */
|
||||||
|
.main {
|
||||||
|
padding: 30px 100px;
|
||||||
|
width: 960px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo {
|
||||||
|
color: #333;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
line-height: 1;
|
||||||
|
height: 110px;
|
||||||
|
margin-top: -50px;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main .logo a {
|
||||||
|
font-size: 160px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.helps pre {
|
||||||
|
padding: 20px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: solid 1px #e7e1cd;
|
||||||
|
background-color: #fffdef;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists {
|
||||||
|
width: 100% !important;
|
||||||
|
overflow: hidden;
|
||||||
|
*zoom: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li {
|
||||||
|
width: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-right: 20px;
|
||||||
|
text-align: center;
|
||||||
|
list-style: none !important;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon {
|
||||||
|
display: block;
|
||||||
|
height: 100px;
|
||||||
|
line-height: 100px;
|
||||||
|
font-size: 42px;
|
||||||
|
margin: 10px auto;
|
||||||
|
color: #333;
|
||||||
|
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
transition: font-size 0.25s linear, width 0.25s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .icon:hover {
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists .svg-icon {
|
||||||
|
/* 通过设置 font-size 来改变图标大小 */
|
||||||
|
width: 1em;
|
||||||
|
/* 图标和文字相邻时,垂直对齐 */
|
||||||
|
vertical-align: -0.15em;
|
||||||
|
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||||
|
fill: currentColor;
|
||||||
|
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||||
|
normalize.css 中也包含这行 */
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon_lists li .name,
|
||||||
|
.icon_lists li .code-name {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* markdown 样式 */
|
||||||
|
.markdown {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown img {
|
||||||
|
vertical-align: middle;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
color: #404040;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 40px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5,
|
||||||
|
.markdown h6 {
|
||||||
|
color: #404040;
|
||||||
|
margin: 1.6em 0 0.6em 0;
|
||||||
|
font-weight: 500;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h2 {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h3 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h4 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h5 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h6 {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown hr {
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
background: #e9e9e9;
|
||||||
|
margin: 16px 0;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown p {
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>p,
|
||||||
|
.markdown>blockquote,
|
||||||
|
.markdown>.highlight,
|
||||||
|
.markdown>ol,
|
||||||
|
.markdown>ul {
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ul>li {
|
||||||
|
list-style: circle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li,
|
||||||
|
.markdown blockquote ul>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ul li p,
|
||||||
|
.markdown>ol li p {
|
||||||
|
margin: 0.6em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown ol>li {
|
||||||
|
list-style: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>ol li,
|
||||||
|
.markdown blockquote ol>li {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown code {
|
||||||
|
margin: 0 3px;
|
||||||
|
padding: 0 5px;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown strong,
|
||||||
|
.markdown b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0px;
|
||||||
|
empty-cells: show;
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
width: 95%;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th,
|
||||||
|
.markdown>table td {
|
||||||
|
border: 1px solid #e9e9e9;
|
||||||
|
padding: 8px 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>table th {
|
||||||
|
background: #F7F7F7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote {
|
||||||
|
font-size: 90%;
|
||||||
|
color: #999;
|
||||||
|
border-left: 4px solid #e9e9e9;
|
||||||
|
padding-left: 0.8em;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown blockquote p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .anchor {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown .waiting {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown h1:hover .anchor,
|
||||||
|
.markdown h2:hover .anchor,
|
||||||
|
.markdown h3:hover .anchor,
|
||||||
|
.markdown h4:hover .anchor,
|
||||||
|
.markdown h5:hover .anchor,
|
||||||
|
.markdown h6:hover .anchor {
|
||||||
|
opacity: 1;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown>br,
|
||||||
|
.markdown>p>br {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
display: block;
|
||||||
|
background: white;
|
||||||
|
padding: 0.5em;
|
||||||
|
color: #333333;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-comment,
|
||||||
|
.hljs-meta {
|
||||||
|
color: #969896;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-string,
|
||||||
|
.hljs-variable,
|
||||||
|
.hljs-template-variable,
|
||||||
|
.hljs-strong,
|
||||||
|
.hljs-emphasis,
|
||||||
|
.hljs-quote {
|
||||||
|
color: #df5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-keyword,
|
||||||
|
.hljs-selector-tag,
|
||||||
|
.hljs-type {
|
||||||
|
color: #a71d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-literal,
|
||||||
|
.hljs-symbol,
|
||||||
|
.hljs-bullet,
|
||||||
|
.hljs-attribute {
|
||||||
|
color: #0086b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-section,
|
||||||
|
.hljs-name {
|
||||||
|
color: #63a35c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-tag {
|
||||||
|
color: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-title,
|
||||||
|
.hljs-attr,
|
||||||
|
.hljs-selector-id,
|
||||||
|
.hljs-selector-class,
|
||||||
|
.hljs-selector-attr,
|
||||||
|
.hljs-selector-pseudo {
|
||||||
|
color: #795da3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-addition {
|
||||||
|
color: #55a532;
|
||||||
|
background-color: #eaffea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-deletion {
|
||||||
|
color: #bd2c00;
|
||||||
|
background-color: #ffecec;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs-link {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码高亮 */
|
||||||
|
/* PrismJS 1.15.0
|
||||||
|
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||||
|
/**
|
||||||
|
* prism.js default theme for JavaScript, CSS and HTML
|
||||||
|
* Based on dabblet (http://dabblet.com)
|
||||||
|
* @author Lea Verou
|
||||||
|
*/
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
text-shadow: 0 1px white;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::-moz-selection,
|
||||||
|
pre[class*="language-"] ::-moz-selection,
|
||||||
|
code[class*="language-"]::-moz-selection,
|
||||||
|
code[class*="language-"] ::-moz-selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]::selection,
|
||||||
|
pre[class*="language-"] ::selection,
|
||||||
|
code[class*="language-"]::selection,
|
||||||
|
code[class*="language-"] ::selection {
|
||||||
|
text-shadow: none;
|
||||||
|
background: #b3d4fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
padding: 1em;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre)>code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background: #f5f2f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre)>code[class*="language-"] {
|
||||||
|
padding: .1em;
|
||||||
|
border-radius: .3em;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: slategray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #905;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #690;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #9a6e3a;
|
||||||
|
background: hsla(0, 0%, 100%, .5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword {
|
||||||
|
color: #07a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.function,
|
||||||
|
.token.class-name {
|
||||||
|
color: #DD4A68;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important,
|
||||||
|
.token.variable {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important,
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
1361
public/icons/cms/demo_index.html
Normal file
219
public/icons/cms/iconfont.css
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "cmsfont"; /* Project id 4718675 */
|
||||||
|
src: url('iconfont.woff2?t=1731151023913') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1731151023913') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1731151023913') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmsfont {
|
||||||
|
font-family: "cmsfont" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home1:before {
|
||||||
|
content: "\e637";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-org1:before {
|
||||||
|
content: "\e6ae";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home2:before {
|
||||||
|
content: "\e627";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home3:before {
|
||||||
|
content: "\e638";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-cooperation1:before {
|
||||||
|
content: "\e65a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-org2:before {
|
||||||
|
content: "\e602";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-special-subject1:before {
|
||||||
|
content: "\e87a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-org3:before {
|
||||||
|
content: "\e620";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home4:before {
|
||||||
|
content: "\e610";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-cooperation3:before {
|
||||||
|
content: "\e606";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-org4:before {
|
||||||
|
content: "\e7ca";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-special-subject2:before {
|
||||||
|
content: "\e671";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-public1:before {
|
||||||
|
content: "\e63f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-dynamic2:before {
|
||||||
|
content: "\e612";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-cooperation4:before {
|
||||||
|
content: "\e777";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-dynamic3:before {
|
||||||
|
content: "\e672";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home5:before {
|
||||||
|
content: "\e628";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-special-subject3:before {
|
||||||
|
content: "\e607";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-cooperation5:before {
|
||||||
|
content: "\e61a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-special-subject4:before {
|
||||||
|
content: "\e61e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home6:before {
|
||||||
|
content: "\e669";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-policy1:before {
|
||||||
|
content: "\e61d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-policy2:before {
|
||||||
|
content: "\e65c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home7:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-policy3:before {
|
||||||
|
content: "\e667";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-policy4:before {
|
||||||
|
content: "\e609";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-policy5:before {
|
||||||
|
content: "\e60a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-home8:before {
|
||||||
|
content: "\e617";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-gov1:before {
|
||||||
|
content: "\e753";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-cooperation2-copy:before {
|
||||||
|
content: "\ebc9";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-category:before {
|
||||||
|
content: "\e625";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-category1:before {
|
||||||
|
content: "\e6a4";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-category2:before {
|
||||||
|
content: "\e6a7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-category3:before {
|
||||||
|
content: "\e63b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-service:before {
|
||||||
|
content: "\e6d8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-article:before {
|
||||||
|
content: "\e624";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-goods:before {
|
||||||
|
content: "\e67b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-template:before {
|
||||||
|
content: "\e60f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-material:before {
|
||||||
|
content: "\e66d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-template2:before {
|
||||||
|
content: "\e666";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-content:before {
|
||||||
|
content: "\e890";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-content2:before {
|
||||||
|
content: "\e6b2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-content3:before {
|
||||||
|
content: "\e681";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-article2:before {
|
||||||
|
content: "\e7d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-material2:before {
|
||||||
|
content: "\e665";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-article3:before {
|
||||||
|
content: "\e615";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-content4:before {
|
||||||
|
content: "\e616";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-service2:before {
|
||||||
|
content: "\e694";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-material3:before {
|
||||||
|
content: "\e7ef";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-service3:before {
|
||||||
|
content: "\e636";
|
||||||
|
}
|
||||||
|
|
||||||
|
.cms-goods2:before {
|
||||||
|
content: "\e61f";
|
||||||
|
}
|
||||||
|
|
1
public/icons/cms/iconfont.js
Normal file
366
public/icons/cms/iconfont.json
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
{
|
||||||
|
"id": "4718675",
|
||||||
|
"name": "eva-cms",
|
||||||
|
"font_family": "cmsfont",
|
||||||
|
"css_prefix_text": "cms-",
|
||||||
|
"description": "",
|
||||||
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "166640",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home1",
|
||||||
|
"unicode": "e637",
|
||||||
|
"unicode_decimal": 58935
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "974813",
|
||||||
|
"name": "机构",
|
||||||
|
"font_class": "org1",
|
||||||
|
"unicode": "e6ae",
|
||||||
|
"unicode_decimal": 59054
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1129603",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home2",
|
||||||
|
"unicode": "e627",
|
||||||
|
"unicode_decimal": 58919
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1244100",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home3",
|
||||||
|
"unicode": "e638",
|
||||||
|
"unicode_decimal": 58936
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2570131",
|
||||||
|
"name": "合作",
|
||||||
|
"font_class": "cooperation1",
|
||||||
|
"unicode": "e65a",
|
||||||
|
"unicode_decimal": 58970
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5744737",
|
||||||
|
"name": "组织机构",
|
||||||
|
"font_class": "org2",
|
||||||
|
"unicode": "e602",
|
||||||
|
"unicode_decimal": 58882
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5838824",
|
||||||
|
"name": "专题",
|
||||||
|
"font_class": "special-subject1",
|
||||||
|
"unicode": "e87a",
|
||||||
|
"unicode_decimal": 59514
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "5873042",
|
||||||
|
"name": "组织机构",
|
||||||
|
"font_class": "org3",
|
||||||
|
"unicode": "e620",
|
||||||
|
"unicode_decimal": 58912
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "6865351",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home4",
|
||||||
|
"unicode": "e610",
|
||||||
|
"unicode_decimal": 58896
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7814499",
|
||||||
|
"name": "合作/招商",
|
||||||
|
"font_class": "cooperation3",
|
||||||
|
"unicode": "e606",
|
||||||
|
"unicode_decimal": 58886
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "7876369",
|
||||||
|
"name": "组织机构",
|
||||||
|
"font_class": "org4",
|
||||||
|
"unicode": "e7ca",
|
||||||
|
"unicode_decimal": 59338
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "9466859",
|
||||||
|
"name": "专题",
|
||||||
|
"font_class": "special-subject2",
|
||||||
|
"unicode": "e671",
|
||||||
|
"unicode_decimal": 58993
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10158782",
|
||||||
|
"name": "公开",
|
||||||
|
"font_class": "public1",
|
||||||
|
"unicode": "e63f",
|
||||||
|
"unicode_decimal": 58943
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11032202",
|
||||||
|
"name": "动态",
|
||||||
|
"font_class": "dynamic2",
|
||||||
|
"unicode": "e612",
|
||||||
|
"unicode_decimal": 58898
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11521307",
|
||||||
|
"name": "合作",
|
||||||
|
"font_class": "cooperation4",
|
||||||
|
"unicode": "e777",
|
||||||
|
"unicode_decimal": 59255
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "12129940",
|
||||||
|
"name": "动态",
|
||||||
|
"font_class": "dynamic3",
|
||||||
|
"unicode": "e672",
|
||||||
|
"unicode_decimal": 58994
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "16290378",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home5",
|
||||||
|
"unicode": "e628",
|
||||||
|
"unicode_decimal": 58920
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "16994568",
|
||||||
|
"name": "专题",
|
||||||
|
"font_class": "special-subject3",
|
||||||
|
"unicode": "e607",
|
||||||
|
"unicode_decimal": 58887
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "18028238",
|
||||||
|
"name": "合作",
|
||||||
|
"font_class": "cooperation5",
|
||||||
|
"unicode": "e61a",
|
||||||
|
"unicode_decimal": 58906
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "18272239",
|
||||||
|
"name": "专题",
|
||||||
|
"font_class": "special-subject4",
|
||||||
|
"unicode": "e61e",
|
||||||
|
"unicode_decimal": 58910
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "18758196",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home6",
|
||||||
|
"unicode": "e669",
|
||||||
|
"unicode_decimal": 58985
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "21800726",
|
||||||
|
"name": "政策",
|
||||||
|
"font_class": "policy1",
|
||||||
|
"unicode": "e61d",
|
||||||
|
"unicode_decimal": 58909
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "22236044",
|
||||||
|
"name": "政策",
|
||||||
|
"font_class": "policy2",
|
||||||
|
"unicode": "e65c",
|
||||||
|
"unicode_decimal": 58972
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "22771475",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home7",
|
||||||
|
"unicode": "e601",
|
||||||
|
"unicode_decimal": 58881
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "22843424",
|
||||||
|
"name": "政策",
|
||||||
|
"font_class": "policy3",
|
||||||
|
"unicode": "e667",
|
||||||
|
"unicode_decimal": 58983
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "34547473",
|
||||||
|
"name": "政策",
|
||||||
|
"font_class": "policy4",
|
||||||
|
"unicode": "e609",
|
||||||
|
"unicode_decimal": 58889
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "34548382",
|
||||||
|
"name": "政策",
|
||||||
|
"font_class": "policy5",
|
||||||
|
"unicode": "e60a",
|
||||||
|
"unicode_decimal": 58890
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "41012044",
|
||||||
|
"name": "首页",
|
||||||
|
"font_class": "home8",
|
||||||
|
"unicode": "e617",
|
||||||
|
"unicode_decimal": 58903
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42192435",
|
||||||
|
"name": "政府",
|
||||||
|
"font_class": "gov1",
|
||||||
|
"unicode": "e753",
|
||||||
|
"unicode_decimal": 59219
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "42408386",
|
||||||
|
"name": "合作",
|
||||||
|
"font_class": "cooperation2-copy",
|
||||||
|
"unicode": "ebc9",
|
||||||
|
"unicode_decimal": 60361
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1757454",
|
||||||
|
"name": "所有栏目列表",
|
||||||
|
"font_class": "category",
|
||||||
|
"unicode": "e625",
|
||||||
|
"unicode_decimal": 58917
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20605189",
|
||||||
|
"name": "树",
|
||||||
|
"font_class": "category1",
|
||||||
|
"unicode": "e6a4",
|
||||||
|
"unicode_decimal": 59044
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "20683172",
|
||||||
|
"name": "新建下级栏目",
|
||||||
|
"font_class": "category2",
|
||||||
|
"unicode": "e6a7",
|
||||||
|
"unicode_decimal": 59047
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "39691573",
|
||||||
|
"name": "栏目管理",
|
||||||
|
"font_class": "category3",
|
||||||
|
"unicode": "e63b",
|
||||||
|
"unicode_decimal": 58939
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "680102",
|
||||||
|
"name": "服务",
|
||||||
|
"font_class": "service",
|
||||||
|
"unicode": "e6d8",
|
||||||
|
"unicode_decimal": 59096
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "924719",
|
||||||
|
"name": "文章",
|
||||||
|
"font_class": "article",
|
||||||
|
"unicode": "e624",
|
||||||
|
"unicode_decimal": 58916
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1137786",
|
||||||
|
"name": "商品",
|
||||||
|
"font_class": "goods",
|
||||||
|
"unicode": "e67b",
|
||||||
|
"unicode_decimal": 59003
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1367324",
|
||||||
|
"name": "模板",
|
||||||
|
"font_class": "template",
|
||||||
|
"unicode": "e60f",
|
||||||
|
"unicode_decimal": 58895
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1509751",
|
||||||
|
"name": "素材",
|
||||||
|
"font_class": "material",
|
||||||
|
"unicode": "e66d",
|
||||||
|
"unicode_decimal": 58989
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "1680696",
|
||||||
|
"name": "模板",
|
||||||
|
"font_class": "template2",
|
||||||
|
"unicode": "e666",
|
||||||
|
"unicode_decimal": 58982
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2076263",
|
||||||
|
"name": "内容",
|
||||||
|
"font_class": "content",
|
||||||
|
"unicode": "e890",
|
||||||
|
"unicode_decimal": 59536
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "2786517",
|
||||||
|
"name": "内容字段管理",
|
||||||
|
"font_class": "content2",
|
||||||
|
"unicode": "e6b2",
|
||||||
|
"unicode_decimal": 59058
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "3005918",
|
||||||
|
"name": "内容",
|
||||||
|
"font_class": "content3",
|
||||||
|
"unicode": "e681",
|
||||||
|
"unicode_decimal": 59009
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "8798005",
|
||||||
|
"name": "我的文章",
|
||||||
|
"font_class": "article2",
|
||||||
|
"unicode": "e7d3",
|
||||||
|
"unicode_decimal": 59347
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "10940098",
|
||||||
|
"name": "素材",
|
||||||
|
"font_class": "material2",
|
||||||
|
"unicode": "e665",
|
||||||
|
"unicode_decimal": 58981
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "11124968",
|
||||||
|
"name": "文章",
|
||||||
|
"font_class": "article3",
|
||||||
|
"unicode": "e615",
|
||||||
|
"unicode_decimal": 58901
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "14559362",
|
||||||
|
"name": "内容",
|
||||||
|
"font_class": "content4",
|
||||||
|
"unicode": "e616",
|
||||||
|
"unicode_decimal": 58902
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "15765349",
|
||||||
|
"name": "服务",
|
||||||
|
"font_class": "service2",
|
||||||
|
"unicode": "e694",
|
||||||
|
"unicode_decimal": 59028
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "18494060",
|
||||||
|
"name": "素材资源",
|
||||||
|
"font_class": "material3",
|
||||||
|
"unicode": "e7ef",
|
||||||
|
"unicode_decimal": 59375
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "19449190",
|
||||||
|
"name": "服务",
|
||||||
|
"font_class": "service3",
|
||||||
|
"unicode": "e636",
|
||||||
|
"unicode_decimal": 58934
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon_id": "31313020",
|
||||||
|
"name": "商品",
|
||||||
|
"font_class": "goods2",
|
||||||
|
"unicode": "e61f",
|
||||||
|
"unicode_decimal": 58911
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
BIN
public/icons/cms/iconfont.ttf
Normal file
BIN
public/icons/cms/iconfont.woff
Normal file
BIN
public/icons/cms/iconfont.woff2
Normal file
394
public/icons/system/iconfont.css
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "iconfont"; /* Project id 4356808 */
|
||||||
|
src: url('iconfont.woff2?t=1707808486506') format('woff2'),
|
||||||
|
url('iconfont.woff?t=1707808486506') format('woff'),
|
||||||
|
url('iconfont.ttf?t=1707808486506') format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-family: "iconfont" !important;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-file:before {
|
||||||
|
content: "\e63d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-file1:before {
|
||||||
|
content: "\e63e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-word:before {
|
||||||
|
content: "\e603";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pdf:before {
|
||||||
|
content: "\e604";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-word1:before {
|
||||||
|
content: "\e607";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-excel:before {
|
||||||
|
content: "\e665";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pdf1:before {
|
||||||
|
content: "\e605";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-excel1:before {
|
||||||
|
content: "\ea46";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-exit-fullscreen2:before {
|
||||||
|
content: "\e768";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-exit-fullscreen1:before {
|
||||||
|
content: "\e671";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-exit-fullscreen:before {
|
||||||
|
content: "\eb98";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-menu1:before {
|
||||||
|
content: "\e61a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-menu:before {
|
||||||
|
content: "\e634";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-member:before {
|
||||||
|
content: "\e628";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-member1:before {
|
||||||
|
content: "\e77d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user:before {
|
||||||
|
content: "\e629";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user1:before {
|
||||||
|
content: "\e6ab";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-role1:before {
|
||||||
|
content: "\e618";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-log2:before {
|
||||||
|
content: "\e68d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-log1:before {
|
||||||
|
content: "\e663";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-icon:before {
|
||||||
|
content: "\eb80";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-maintenance:before {
|
||||||
|
content: "\e615";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-maintenance1:before {
|
||||||
|
content: "\e6af";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-maintenance2:before {
|
||||||
|
content: "\e616";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home5:before {
|
||||||
|
content: "\e601";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home4:before {
|
||||||
|
content: "\e610";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home3:before {
|
||||||
|
content: "\e602";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home:before {
|
||||||
|
content: "\e6cb";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home1:before {
|
||||||
|
content: "\e62e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-home2:before {
|
||||||
|
content: "\e8c6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-permission:before {
|
||||||
|
content: "\e8a3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-settings1:before {
|
||||||
|
content: "\e8b7";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-monitor:before {
|
||||||
|
content: "\e613";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-interface:before {
|
||||||
|
content: "\e74a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-emoji:before {
|
||||||
|
content: "\ec80";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-interface2:before {
|
||||||
|
content: "\e60b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-maintenance4:before {
|
||||||
|
content: "\e6a5";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-task:before {
|
||||||
|
content: "\e612";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-teacher:before {
|
||||||
|
content: "\e633";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-yuyue1:before {
|
||||||
|
content: "\e65f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-class:before {
|
||||||
|
content: "\e60d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-department:before {
|
||||||
|
content: "\e621";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-contract:before {
|
||||||
|
content: "\e6c8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-customer1:before {
|
||||||
|
content: "\e648";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-message:before {
|
||||||
|
content: "\e8bd";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goods:before {
|
||||||
|
content: "\e683";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-score:before {
|
||||||
|
content: "\e61b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-course1:before {
|
||||||
|
content: "\e61f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-course2:before {
|
||||||
|
content: "\e630";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-store:before {
|
||||||
|
content: "\e6f6";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-address3:before {
|
||||||
|
content: "\e814";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-contract1:before {
|
||||||
|
content: "\e614";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-contract2:before {
|
||||||
|
content: "\e619";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-role:before {
|
||||||
|
content: "\e60c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-teacher1:before {
|
||||||
|
content: "\e81f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-store2:before {
|
||||||
|
content: "\e60a";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-position1:before {
|
||||||
|
content: "\e716";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-login-log:before {
|
||||||
|
content: "\ea45";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-position:before {
|
||||||
|
content: "\e611";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-user-group:before {
|
||||||
|
content: "\e70b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-student2:before {
|
||||||
|
content: "\e73f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-student1:before {
|
||||||
|
content: "\e740";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goods1:before {
|
||||||
|
content: "\e600";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-address2:before {
|
||||||
|
content: "\e652";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-course3:before {
|
||||||
|
content: "\e6be";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-contract4:before {
|
||||||
|
content: "\e632";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-order:before {
|
||||||
|
content: "\e65c";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-store1:before {
|
||||||
|
content: "\e606";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dict:before {
|
||||||
|
content: "\e666";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-settings:before {
|
||||||
|
content: "\e8b8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-student:before {
|
||||||
|
content: "\e7c0";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-notice:before {
|
||||||
|
content: "\e62f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-address1:before {
|
||||||
|
content: "\e63f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-log:before {
|
||||||
|
content: "\e644";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-score2:before {
|
||||||
|
content: "\e67e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-service:before {
|
||||||
|
content: "\e6d8";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-service2:before {
|
||||||
|
content: "\e704";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-article:before {
|
||||||
|
content: "\e624";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goods:before {
|
||||||
|
content: "\e67b";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-template:before {
|
||||||
|
content: "\e60f";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-material:before {
|
||||||
|
content: "\e66d";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-template2:before {
|
||||||
|
content: "\e666";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-content:before {
|
||||||
|
content: "\e890";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-article2:before {
|
||||||
|
content: "\e61e";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-content2:before {
|
||||||
|
content: "\e6b2";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-content3:before {
|
||||||
|
content: "\e681";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-template3:before {
|
||||||
|
content: "\e605";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-article3:before {
|
||||||
|
content: "\e7d3";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-material2:before {
|
||||||
|
content: "\e665";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-article4:before {
|
||||||
|
content: "\e615";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-content4:before {
|
||||||
|
content: "\e616";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-service3:before {
|
||||||
|
content: "\e694";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sucaiziyuan:before {
|
||||||
|
content: "\e7ef";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-service4:before {
|
||||||
|
content: "\e636";
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goods2:before {
|
||||||
|
content: "\e61f";
|
||||||
|
}
|
BIN
public/icons/system/iconfont.ttf
Normal file
BIN
public/icons/system/iconfont.woff
Normal file
BIN
public/icons/system/iconfont.woff2
Normal file
BIN
public/images/login.jpg
Normal file
After Width: | Height: | Size: 2.4 MiB |
BIN
public/images/system/404.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
public/images/system/error.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/system/not-allowed.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
public/images/system/suggestion.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
234
src/App.vue
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<template>
|
||||||
|
<router-view/>
|
||||||
|
<LoginFormWindow/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import pkg from '../package.json'
|
||||||
|
import constants from '@/core/plugins/consts'
|
||||||
|
import router from '@/router'
|
||||||
|
import { useDefaultStore} from '@/core/store'
|
||||||
|
import { mapState, mapActions } from 'pinia'
|
||||||
|
import { fetchUserMenus } from '@/api/system/index'
|
||||||
|
import LoginFormWindow from '@/components/system/LoginFormWindow'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {LoginFormWindow},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(useDefaultStore, ['userInfo', 'menuData', 'homePage'])
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
// 用户信息发生变化时,重新初始化路由
|
||||||
|
async userInfo () {
|
||||||
|
if (this.userInfo == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await this.initRoutes()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions(useDefaultStore, ['switchCollapseMenu']),
|
||||||
|
/**
|
||||||
|
* 为静态引入添加版本号
|
||||||
|
*
|
||||||
|
* @param tag 标签
|
||||||
|
* @param prop url属性名称
|
||||||
|
* @param dataProp 预置的url属性名称
|
||||||
|
*/
|
||||||
|
handleVersion (tag, prop, dataProp) {
|
||||||
|
const tags = document.querySelectorAll(tag)
|
||||||
|
for (const tag of tags) {
|
||||||
|
const uri = tag.getAttribute(dataProp)
|
||||||
|
if (uri == null || uri === '') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const prefix = import.meta.env.BASE_URL === '/' ? '' : import.meta.env.BASE_URL
|
||||||
|
tag[prop] = `${prefix}${uri}?v=${pkg.version}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化本地配置
|
||||||
|
*/
|
||||||
|
initLocalConfig () {
|
||||||
|
// 菜单状态配置
|
||||||
|
const menuStatus = window.localStorage.getItem('MENU_STATUS')
|
||||||
|
if (menuStatus != null) {
|
||||||
|
this.switchCollapseMenu(menuStatus === 'true')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化路由
|
||||||
|
*
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async initRoutes () {
|
||||||
|
if (this.loading || this.userInfo == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
// 重置菜单
|
||||||
|
this.$defaultStore.menuData.list = []
|
||||||
|
// 获取菜单
|
||||||
|
const storeMenus = this.menuData.list
|
||||||
|
await fetchUserMenus()
|
||||||
|
.then(menus => {
|
||||||
|
// 没有菜单,跳转到无权访问提示页
|
||||||
|
if (menus.length === 0) {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'error',
|
||||||
|
params: {
|
||||||
|
type: 'not-allowed'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 设置首页
|
||||||
|
const homePage = this.__getHomePage(menus)
|
||||||
|
// - 没有找到合适的首页(非外部链接、IFrame嵌入的菜单),则跳转到无权访问提示页
|
||||||
|
if (homePage == null) {
|
||||||
|
this.$router.push({
|
||||||
|
name: 'error',
|
||||||
|
params: {
|
||||||
|
type: 'not-allowed'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$defaultStore.homePage = this.__getHomePage(menus)
|
||||||
|
// 添加菜单
|
||||||
|
storeMenus.push.apply(storeMenus, menus)
|
||||||
|
// 添加路由
|
||||||
|
const viewsComponents = import.meta.glob('@/views/**/**.vue')
|
||||||
|
this.__addRouters(storeMenus, [], viewsComponents)
|
||||||
|
// 404捕获,需要在路由添加完成后,将其添加到最后,避免刷新时路由还未加载直接出现404
|
||||||
|
router.addRoute({
|
||||||
|
path: '/:catchAll(.*)',
|
||||||
|
redirect: '/error/not-found'
|
||||||
|
})
|
||||||
|
// 首页
|
||||||
|
router.addRoute({
|
||||||
|
name: 'index',
|
||||||
|
path: '/',
|
||||||
|
redirect: this.__getHomeUri()
|
||||||
|
})
|
||||||
|
// 路由加载完成后,重新使路由生效
|
||||||
|
// - 直接访问域名,跳转至首页
|
||||||
|
if (this.$route.path === '/') {
|
||||||
|
this.$router.push(this.__getHomeUri())
|
||||||
|
}
|
||||||
|
// - 否则重新push到路由中,否则路由不会生效
|
||||||
|
else {
|
||||||
|
this.$router.push({
|
||||||
|
path: this.$route.path,
|
||||||
|
query: this.$route.query,
|
||||||
|
params: this.$route.params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
throw e
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 新建路由
|
||||||
|
*
|
||||||
|
* @param routes 需添加的路由
|
||||||
|
* @param parents 需添加到的目标列表
|
||||||
|
* @param viewComponents 页面组件
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__addRouters (routes, parents = [], viewComponents) {
|
||||||
|
if (routes == null || routes.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const rs = router.getRoutes()
|
||||||
|
for (const route of routes) {
|
||||||
|
const parentsDump = JSON.parse(JSON.stringify(parents))
|
||||||
|
parentsDump.push(route)
|
||||||
|
if (route.type === 'DIR') {
|
||||||
|
this.__addRouters(route.children, parentsDump, viewComponents)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 外链和嵌入链,不添加路由
|
||||||
|
if (route.type === 'EXTERNAL' || route.type === 'IFRAME') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (rs.findIndex(r => r.path === route.path) > -1) {
|
||||||
|
this.__addRouters(route.children, parentsDump, viewComponents)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (this.homePage == null) {
|
||||||
|
this.defaultStore.homePage = route
|
||||||
|
}
|
||||||
|
const viewComponent = viewComponents[`/src/views${route.uri}.vue`]
|
||||||
|
if (viewComponent != null) {
|
||||||
|
router.addRoute('layout', {
|
||||||
|
path: route.uri,
|
||||||
|
name: route.name,
|
||||||
|
meta: {
|
||||||
|
title: route.name,
|
||||||
|
paths: [...parents.map(p => p.name), route.name]
|
||||||
|
},
|
||||||
|
component: viewComponent
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 标记为不存在
|
||||||
|
route.__exists = false
|
||||||
|
console.error(`未找到路由组件:@/views${route.uri}.vue`)
|
||||||
|
}
|
||||||
|
this.__addRouters(route.children, parentsDump, viewComponents)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取首页
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__getHomePage (menus) {
|
||||||
|
for (const menu of menus) {
|
||||||
|
// 目录,继续查找子菜单
|
||||||
|
if (menu.type === constants.SYSTEM_MENU.TYPE_DIR) {
|
||||||
|
const homePage = this.__getHomePage(menu.children)
|
||||||
|
// 在当前目录下未找到合适的菜单作为首页,则继续查找下一级菜单
|
||||||
|
if (homePage == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return homePage
|
||||||
|
}
|
||||||
|
// 外部链接,不能作为首页
|
||||||
|
if (menu.type === constants.SYSTEM_MENU.TYPE_EXTERNAL) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return menu
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
},
|
||||||
|
// 获取首页uri
|
||||||
|
__getHomeUri () {
|
||||||
|
if (this.$defaultStore.homePage.type === 'IFRAME') {
|
||||||
|
return `/iframe?url=${this.$defaultStore.homePage.uri}`
|
||||||
|
}
|
||||||
|
return this.$defaultStore.homePage.uri
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.handleVersion('link', 'href', 'data-href')
|
||||||
|
this.handleVersion('script', 'src', 'data-src')
|
||||||
|
// 已登录,则初始化路由
|
||||||
|
if (this.userInfo != null) {
|
||||||
|
await this.initRoutes()
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.initLocalConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
63
src/api/cms/article.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 保存草稿/更新文章
|
||||||
|
export function save (data) {
|
||||||
|
return request.post('/cms/article/save', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布
|
||||||
|
export function publish (id) {
|
||||||
|
return request.get(`/cms/article/publish/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上线
|
||||||
|
export function online (id) {
|
||||||
|
return request.get(`/cms/article/online/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量上线
|
||||||
|
export function onlineInBatch (ids) {
|
||||||
|
return request.get('/cms/article/online/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下线
|
||||||
|
export function offline (id) {
|
||||||
|
return request.get(`/cms/article/offline/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量下线
|
||||||
|
export function offlineInBatch (ids) {
|
||||||
|
return request.get('/cms/article/offline/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/article/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/article/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/cms/article/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 为文章下拉选择查询概要信息列表
|
||||||
|
export function fetchProfileList4Select (data) {
|
||||||
|
return request.post('/cms/article/select/list', data)
|
||||||
|
}
|
35
src/api/cms/article.tag.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/cms/article/tag/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/article/tag/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/article/tag/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/cms/article/tag/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/cms/article/tag/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.get('/cms/article/tag/all', data)
|
||||||
|
}
|
35
src/api/cms/category.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/cms/category/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/category/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/category/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/cms/category/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改状态
|
||||||
|
export function updateStatus (data) {
|
||||||
|
return request.post('/cms/category/updateStatus', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.get('/cms/category/tree', data)
|
||||||
|
}
|
35
src/api/cms/resource.group.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/cms/res/group/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/res/group/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/res/group/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/cms/res/group/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/cms/res/group/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.get('/cms/res/group/all', data)
|
||||||
|
}
|
35
src/api/cms/resource.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/cms/res/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/res/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/res/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/cms/res/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改状态
|
||||||
|
export function updateStatus (data) {
|
||||||
|
return request.post('/cms/res/updateStatus', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/cms/res/page', data)
|
||||||
|
}
|
35
src/api/cms/template.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/cms/template/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/cms/template/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/cms/template/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/cms/template/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/cms/template/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.get('/cms/template/all', data)
|
||||||
|
}
|
35
src/api/system/config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/config/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/config/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/config/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/config/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/config/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新缓存
|
||||||
|
export function refreshCache (data) {
|
||||||
|
return request.get('/system/config/cache/refresh', data)
|
||||||
|
}
|
35
src/api/system/dict.data.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/dictData/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/dictData/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/dictData/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/dictData/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
export function sort (data) {
|
||||||
|
return request.post('/system/dictData/sort', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/dictData/page', data)
|
||||||
|
}
|
35
src/api/system/dict.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/dict/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/dict/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/dict/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/dict/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/dict/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新缓存
|
||||||
|
export function refreshCache (data) {
|
||||||
|
return request.get('/system/dict/cache/refresh', data)
|
||||||
|
}
|
35
src/api/system/icon.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/icon/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/icon/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/icon/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据ID修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/icon/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/icon/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有图标
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.get('/system/icon/all', data)
|
||||||
|
}
|
54
src/api/system/index.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 获取图片验证码
|
||||||
|
export function getCaptcha () {
|
||||||
|
return request.get('/common/captcha/image')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载本地文件
|
||||||
|
export function downloadLocalFile (params) {
|
||||||
|
return request.get('/resource/local/download', {
|
||||||
|
params,
|
||||||
|
download: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载OSS文件
|
||||||
|
export function downloadOSSFile (params) {
|
||||||
|
return request.get('/resource/oss/attach', {
|
||||||
|
params,
|
||||||
|
download: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据密码登录
|
||||||
|
export function loginByPassword (data) {
|
||||||
|
return request.secure().post('/system/login', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登出
|
||||||
|
export function logout (data) {
|
||||||
|
return request.post('/system/logout', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
export function updatePwd (data) {
|
||||||
|
return request.post('/system/updatePwd', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取已登录的用户信息
|
||||||
|
export function getUserInfo () {
|
||||||
|
return request.get('/system/getUserInfo', {
|
||||||
|
autoLogin: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户菜单
|
||||||
|
export function fetchUserMenus () {
|
||||||
|
return request.get('/system/menus')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询客户端配置
|
||||||
|
export function fetchConfig (data) {
|
||||||
|
return request.get('/system/client/config', data)
|
||||||
|
}
|
16
src/api/system/login.log.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import request from '../../core/utils/request'
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/loginLog/page', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出Excel
|
||||||
|
export function exportExcel (data) {
|
||||||
|
return request.post('/system/loginLog/exportExcel', data, {
|
||||||
|
download: true,
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
30
src/api/system/menu.func.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/menu/func/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/menu/func/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/menu/func/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/menu/func/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/menu/func/page', data)
|
||||||
|
}
|
40
src/api/system/menu.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/menu/create', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/menu/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/menu/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/menu/updateById', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改状态
|
||||||
|
export function updateStatus (data) {
|
||||||
|
return request.post('/system/menu/updateStatus', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
export function sort (data) {
|
||||||
|
return request.post('/system/menu/updateSort', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchAll (data) {
|
||||||
|
return request.post('/system/menu/all', data)
|
||||||
|
}
|
11
src/api/system/permission.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 获取权限数据
|
||||||
|
export function fetchPermissions () {
|
||||||
|
return request.get('/system/permission/data')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置权限
|
||||||
|
export function configPermissions (data) {
|
||||||
|
return request.post('/system/permission/config', data)
|
||||||
|
}
|
39
src/api/system/role.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/role/create', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/role/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/role/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/role/updateById', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/role/page', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询所有
|
||||||
|
export function fetchAll () {
|
||||||
|
return request.get('/system/role/all')
|
||||||
|
}
|
16
src/api/system/trace.log.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import request from '../../core/utils/request'
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/traceLog/page', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出Excel
|
||||||
|
export function exportExcel (data) {
|
||||||
|
return request.post('/system/traceLog/exportExcel', data, {
|
||||||
|
download: true,
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
44
src/api/system/user.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import request from '@/core/utils/request'
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
export function create (data) {
|
||||||
|
return request.post('/system/user/create', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
export function deleteById (id) {
|
||||||
|
return request.get(`/system/user/delete/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量删除
|
||||||
|
export function deleteByIdInBatch (ids) {
|
||||||
|
return request.get('/system/user/delete/batch', {
|
||||||
|
params: {
|
||||||
|
ids
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
export function updateById (data) {
|
||||||
|
return request.post('/system/user/updateById', data, {
|
||||||
|
trim: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置用户角色
|
||||||
|
export function createUserRole (data) {
|
||||||
|
return request.post('/system/user/createUserRole', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置密码
|
||||||
|
export function resetPwd (data) {
|
||||||
|
return request.post('/system/user/resetPwd', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询
|
||||||
|
export function fetchPage (data) {
|
||||||
|
return request.post('/system/user/page', data)
|
||||||
|
}
|
BIN
src/assets/logo.png
Normal file
After Width: | Height: | Size: 53 KiB |
20
src/assets/style/element/index.scss
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @link https://github.com/element-plus/element-plus/blob/dev/packages/theme-chalk/src/common/var.scss
|
||||||
|
*/
|
||||||
|
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||||
|
$colors: (
|
||||||
|
// - 主色调
|
||||||
|
'primary': (
|
||||||
|
'base': #2d8cf0,
|
||||||
|
),
|
||||||
|
// - 危险色
|
||||||
|
'danger': (
|
||||||
|
'base': #F56C6C,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
$button-font-size: (
|
||||||
|
// - 小型按钮字体大小
|
||||||
|
'small': 14px,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
@use "element-plus/theme-chalk/src/index.scss" as *;
|
2
src/assets/style/index.scss
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@use 'theme.scss';
|
||||||
|
@use 'style.scss';
|
101
src/assets/style/style.scss
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// 样式重置
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--font-color);
|
||||||
|
font-size: var(--font-size);
|
||||||
|
font-family: var(--font-family);
|
||||||
|
}
|
||||||
|
h1,h2,h3,h4,h5,h6,ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
#app {
|
||||||
|
height: 100%;
|
||||||
|
min-width: var(--page-min-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 穿梭框的按钮
|
||||||
|
.el-transfer__buttons {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dialog
|
||||||
|
.eva-dialog {
|
||||||
|
.el-form-item {
|
||||||
|
display: flex !important;
|
||||||
|
.el-form-item__content {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除按钮
|
||||||
|
.el-button--delete {
|
||||||
|
background: transparent !important;
|
||||||
|
border: 0 !important;
|
||||||
|
color: var(--color-danger) !important;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-danger-hover) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除窗口
|
||||||
|
.delete-message-box {
|
||||||
|
.el-message-box__message p {
|
||||||
|
word-break: break-all !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按钮图标
|
||||||
|
.el-button.iconfont {
|
||||||
|
display: inline-block;
|
||||||
|
line-height: 1;
|
||||||
|
vertical-align: baseline;
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// el-alert
|
||||||
|
.el-alert--info {
|
||||||
|
background: var(--alert-background-color) !important;
|
||||||
|
border: 1px solid var(--alert-border-color) !important;
|
||||||
|
color: var(--alert-text-color) !important;
|
||||||
|
padding: 5px 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要文字
|
||||||
|
.text-primary {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 危险文字
|
||||||
|
.text-danger {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉表单自动填充的背景色
|
||||||
|
input:-webkit-autofill,
|
||||||
|
input:-webkit-autofill:hover,
|
||||||
|
input:-webkit-autofill:focus,
|
||||||
|
input:-webkit-autofill:active {
|
||||||
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
|
-webkit-text-fill-color: black;
|
||||||
|
background-color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改表格树的前方占位宽度,原值为20px会导致树节点未对齐
|
||||||
|
.el-table__placeholder {
|
||||||
|
width: 12px;
|
||||||
|
}
|
93
src/assets/style/theme.scss
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// - 主色调,如需调整,同时也许要调整assets/style/element/index.scss中的主题色
|
||||||
|
$--color-primary: #2d8cf0;
|
||||||
|
// - 危险色,如需调整,同时也许要调整assets/style/element/index.scss中的危险色
|
||||||
|
$--color-danger: #F56C6C;
|
||||||
|
// - 字体路径
|
||||||
|
$--font-path: '~element-ui/lib/theme-chalk/fonts';
|
||||||
|
@use 'element/index' as *;
|
||||||
|
|
||||||
|
// 自定义主题
|
||||||
|
// 主色调
|
||||||
|
$--color-primary-light: mix($color-white, $--color-primary, 20%);
|
||||||
|
$--color-primary-dark: mix($color-black, $--color-primary, 10%);
|
||||||
|
// 头部高度
|
||||||
|
$--header-height: 60px;
|
||||||
|
// 菜单宽度
|
||||||
|
$--menu-width: 255px;
|
||||||
|
// 页面最小宽度
|
||||||
|
$--page-min-width: 1000px;
|
||||||
|
// 字体
|
||||||
|
$--font-color: #282828;
|
||||||
|
$--font-color-gray: #999;
|
||||||
|
$--font-color-gray-deep: #555;
|
||||||
|
$--font-size: 13px;
|
||||||
|
$--font-family: 'Avenir', Helvetica, Arial, sans-serif;
|
||||||
|
// 背景色
|
||||||
|
$--background-color: #f7f7f7;
|
||||||
|
// 登录按钮背景色
|
||||||
|
$--login-button-background-color: linear-gradient(130deg, var(--color-primary-light) 0%, var(--color-primary-dark) 100%);
|
||||||
|
// 菜单色调
|
||||||
|
$--menu-background-color: #2f3044;
|
||||||
|
$--menu-hover-background-color: mix($color-white, $--menu-background-color, 20%);
|
||||||
|
$--menu-text-color: #9f9f9f;
|
||||||
|
$--menu-text-hover-color: #f7f7f7;
|
||||||
|
$--menu-active-background-color: $--menu-text-hover-color;
|
||||||
|
$--menu-active-text-color: #333;
|
||||||
|
$--menu-active-text-hover-color: $--menu-active-text-color;
|
||||||
|
$--submenu-background-color: mix($color-black, $--menu-background-color, 20%);
|
||||||
|
// 菜单选中后的圆弧大小
|
||||||
|
$--menu-active-radius-size: 10px;
|
||||||
|
// 危险色
|
||||||
|
$--color-danger-hover: mix($color-white, $--color-danger, 20%);
|
||||||
|
// alert提示色
|
||||||
|
$--alert-background-color: #f0faff;
|
||||||
|
$--alert-text-color: #555;
|
||||||
|
$--alert-border-color: #abdcff;
|
||||||
|
// 标签
|
||||||
|
$--tag-background-color: #eef5fd;
|
||||||
|
$--tag-text-color: #555;
|
||||||
|
$--tag-border-color: #abdcff;
|
||||||
|
:root {
|
||||||
|
// 头部高度
|
||||||
|
--header-height: #{$--header-height};
|
||||||
|
// 菜单宽度
|
||||||
|
--menu-width: #{$--menu-width};
|
||||||
|
// 页面最小宽度
|
||||||
|
--page-min-width: #{$--page-min-width};
|
||||||
|
// 字体
|
||||||
|
--font-color: #{$--font-color};
|
||||||
|
--font-color-gray: #{$--font-color-gray};
|
||||||
|
--font-color-gray-deep: #{$--font-color-gray-deep};
|
||||||
|
--font-size: #{$--font-size};
|
||||||
|
--font-family: #{$--font-family};
|
||||||
|
// 主色调(注意:受到Element-UI的实现影响,修改该变量并不会影响Element-UI的主题色,如需调整组件主色调,需调整$--color-primary变量)
|
||||||
|
--color-primary: #{$--color-primary};
|
||||||
|
--color-primary-light: #{$--color-primary-light};
|
||||||
|
--color-primary-dark: #{$--color-primary-dark};
|
||||||
|
// 背景色
|
||||||
|
--background-color: #{$--background-color};
|
||||||
|
// 登录按钮背景
|
||||||
|
--login-button-background-color: #{$--login-button-background-color};
|
||||||
|
// 菜单色调
|
||||||
|
--menu-background-color: #{$--menu-background-color};
|
||||||
|
--menu-hover-background-color: #{$--menu-hover-background-color};
|
||||||
|
--menu-text-color: #{$--menu-text-color};
|
||||||
|
--menu-text-hover-color: #{$--menu-text-hover-color};
|
||||||
|
--menu-active-background-color: #{$--menu-active-background-color};
|
||||||
|
--menu-active-text-color: #{$--menu-active-text-color};
|
||||||
|
--menu-active-text-hover-color: #{$--menu-active-text-hover-color};
|
||||||
|
--submenu-background-color: #{$--submenu-background-color};
|
||||||
|
// 菜单圆弧大小
|
||||||
|
--menu-active-radius-size: #{$--menu-active-radius-size};
|
||||||
|
// 危险色
|
||||||
|
--color-danger: #{$--color-danger};
|
||||||
|
--color-danger-hover: #{$--color-danger-hover};
|
||||||
|
// alert提示色
|
||||||
|
--alert-background-color: #{$--alert-background-color};
|
||||||
|
--alert-text-color: #{$--alert-text-color};
|
||||||
|
--alert-border-color: #{$--alert-border-color};
|
||||||
|
// 标签
|
||||||
|
--tag-background-color: #{$--tag-background-color};
|
||||||
|
--tag-text-color: #{$--tag-text-color};
|
||||||
|
--tag-border-color: #{$--tag-border-color};
|
||||||
|
}
|
1
src/assets/vue.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
After Width: | Height: | Size: 497 B |
14
src/components/BaseComponent.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'BaseComponent',
|
||||||
|
methods: {
|
||||||
|
// 计算属性
|
||||||
|
computeProp (propName) {
|
||||||
|
if (typeof this[propName] === 'function') {
|
||||||
|
return this[propName]()
|
||||||
|
}
|
||||||
|
return this[propName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
35
src/components/base/BaseDict.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
// 值
|
||||||
|
value: {},
|
||||||
|
// 字典编码
|
||||||
|
code: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 是否禁用
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 字典数据列表,从客户端配置中读取字典列表并根据code找到对应字典和字典数据
|
||||||
|
dataList () {
|
||||||
|
const clientConfig = this.$defaultStore.clientConfig
|
||||||
|
if (
|
||||||
|
clientConfig == null ||
|
||||||
|
clientConfig.dictMap == null
|
||||||
|
) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const dict = clientConfig.dictMap[this.code]
|
||||||
|
if (dict == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return dict.dataList.filter(data => !data.disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
140
src/components/base/BaseOpera.vue
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'BaseOpera',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
isWorking: false,
|
||||||
|
// 接口
|
||||||
|
api: null,
|
||||||
|
// 配置数据
|
||||||
|
configData: {
|
||||||
|
'field.id': 'id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 配置
|
||||||
|
*
|
||||||
|
* @param extParams 配置参数
|
||||||
|
*/
|
||||||
|
config (extParams = {}) {
|
||||||
|
if (extParams == null) {
|
||||||
|
throw new Error('Parameter can not be null of method \'config\' .')
|
||||||
|
}
|
||||||
|
if (extParams.api == null) {
|
||||||
|
throw new Error('Missing config option \'api\'.')
|
||||||
|
}
|
||||||
|
this.api = extParams.api
|
||||||
|
extParams['field.id'] && (this.configData['field.id'] = extParams['field.id'])
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param target 行对象(仅编辑需该参数)
|
||||||
|
*/
|
||||||
|
open (title, target) {
|
||||||
|
this.title = title
|
||||||
|
this.visible = true
|
||||||
|
// 新建
|
||||||
|
if (target == null) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.form[this.configData['field.id']] = null
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
this.$nextTick(() => {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认(点击确认按钮后触发)
|
||||||
|
*/
|
||||||
|
confirm () {
|
||||||
|
if (this.form[this.configData['field.id']] == null || this.form[this.configData['field.id']] === '') {
|
||||||
|
this.__confirmCreate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.__confirmEdit()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认新建
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__confirmCreate () {
|
||||||
|
this.__validateForm((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 调用新建接口
|
||||||
|
this.isWorking = true
|
||||||
|
this.api.create(this.__getForm())
|
||||||
|
.then(() => {
|
||||||
|
this.visible = false
|
||||||
|
this.$tip.apiSuccess('新建成功')
|
||||||
|
this.$emit('success')
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认修改
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__confirmEdit () {
|
||||||
|
this.__validateForm((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 调用更新接口
|
||||||
|
this.isWorking = true
|
||||||
|
this.api.updateById(this.__getForm())
|
||||||
|
.then(() => {
|
||||||
|
this.visible = false
|
||||||
|
this.$tip.apiSuccess('修改成功')
|
||||||
|
this.$emit('success')
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证表单
|
||||||
|
*
|
||||||
|
* @param callback 验证回调
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__validateForm (callback) {
|
||||||
|
this.$refs.form.validate((valid) => {
|
||||||
|
callback && callback(valid)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取新增编辑表单对象
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__getForm () {
|
||||||
|
return this.form
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
314
src/components/base/BaseTable.vue
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'BaseTable',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 接口
|
||||||
|
api: null,
|
||||||
|
// 模块名称
|
||||||
|
module: '数据',
|
||||||
|
// 配置数据
|
||||||
|
configData: {
|
||||||
|
// id字段
|
||||||
|
'field.id': 'id',
|
||||||
|
// 主字段
|
||||||
|
'field.main': null
|
||||||
|
},
|
||||||
|
// 是否全屏
|
||||||
|
fullscreen: false,
|
||||||
|
// 是否正在执行
|
||||||
|
isWorking: {
|
||||||
|
// 搜索中
|
||||||
|
search: false,
|
||||||
|
// 删除中
|
||||||
|
delete: false,
|
||||||
|
// 导出中
|
||||||
|
export: false
|
||||||
|
},
|
||||||
|
// 表格数据
|
||||||
|
tableData: {
|
||||||
|
// 已选中的数据
|
||||||
|
selectedRows: [],
|
||||||
|
// 排序的字段
|
||||||
|
sorts: [],
|
||||||
|
// 当前页数据
|
||||||
|
list: [],
|
||||||
|
// 分页
|
||||||
|
pagination: {
|
||||||
|
page: 1,
|
||||||
|
capacity: 10,
|
||||||
|
total: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 配置
|
||||||
|
*
|
||||||
|
* @param extParams 配置参数
|
||||||
|
*/
|
||||||
|
config (extParams) {
|
||||||
|
if (extParams == null) {
|
||||||
|
throw new Error('Parameter can not be null of method \'config\' .')
|
||||||
|
}
|
||||||
|
if (extParams.api == null) {
|
||||||
|
throw new Error('Missing config option \'api\'.')
|
||||||
|
}
|
||||||
|
this.api = extParams.api
|
||||||
|
extParams.module && (this.module = extParams.module)
|
||||||
|
extParams['field.id'] && (this.configData['field.id'] = extParams['field.id'])
|
||||||
|
extParams['field.main'] && (this.configData['field.main'] = extParams['field.main'])
|
||||||
|
this.tableData.sorts = extParams.sorts
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 搜索(点击搜索按钮时触发)
|
||||||
|
*/
|
||||||
|
search () {
|
||||||
|
this.handlePageChange(1)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 导出Excel(点击导出按钮时触发)
|
||||||
|
*/
|
||||||
|
exportExcel () {
|
||||||
|
this.__checkApi()
|
||||||
|
this.$dialog.exportConfirm('确认导出吗?')
|
||||||
|
.then(() => {
|
||||||
|
this.isWorking.export = true
|
||||||
|
return this.api.exportExcel({
|
||||||
|
page: this.tableData.pagination.page,
|
||||||
|
capacity: 1000000,
|
||||||
|
model: this.__getSearchForm(),
|
||||||
|
sorts: this.tableData.sorts
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
this.$download(response)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.export = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 重置搜索条件(点击重置按钮时触发)
|
||||||
|
*/
|
||||||
|
reset () {
|
||||||
|
this.$refs.searchForm.resetFields()
|
||||||
|
this.search()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 页容量变更处理(切换页容量时触发)
|
||||||
|
*
|
||||||
|
* @param capacity 页容量
|
||||||
|
*/
|
||||||
|
handleSizeChange (capacity) {
|
||||||
|
this.tableData.pagination.capacity = capacity
|
||||||
|
this.search()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 行选中处理(点击选中列时触发)
|
||||||
|
*
|
||||||
|
* @param selectedRows 已选中的行数组
|
||||||
|
*/
|
||||||
|
handleSelectionChange (selectedRows) {
|
||||||
|
this.tableData.selectedRows = selectedRows
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 排序(点击列头排序时触发)
|
||||||
|
*
|
||||||
|
* @param sortData 排序参数
|
||||||
|
*/
|
||||||
|
handleSortChange (sortData) {
|
||||||
|
this.tableData.sorts = this.tableData.sorts.filter(item => item.fixed === true)
|
||||||
|
if (sortData.order != null) {
|
||||||
|
this.tableData.sorts.push({
|
||||||
|
property: sortData.column.sortBy,
|
||||||
|
direction: sortData.order === 'descending' ? 'DESC' : 'ASC'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.handlePageChange()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 页码变更处理(分页时触发)
|
||||||
|
*
|
||||||
|
* @param page 新页码
|
||||||
|
*/
|
||||||
|
handlePageChange (page) {
|
||||||
|
this.__checkApi()
|
||||||
|
this.tableData.pagination.page = page || this.tableData.pagination.page
|
||||||
|
this.isWorking.search = true
|
||||||
|
// 获取接口,优先使用“分页查询”接口,如果没有,则使用“查询所有”接口
|
||||||
|
const api = this.api.fetchPage || this.api.fetchAll
|
||||||
|
if (api == null) {
|
||||||
|
throw new Error('未找到fetchPage或fetchAll的接口定义')
|
||||||
|
}
|
||||||
|
let params = this.__getSearchForm()
|
||||||
|
// 分页查询,携带分页参数
|
||||||
|
if (api === this.api.fetchPage) {
|
||||||
|
params = {
|
||||||
|
page: this.tableData.pagination.page,
|
||||||
|
capacity: this.tableData.pagination.capacity,
|
||||||
|
model: this.__getSearchForm(),
|
||||||
|
sorts: this.tableData.sorts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 调用接口
|
||||||
|
api(params)
|
||||||
|
.then(data => {
|
||||||
|
// 分页接口,填充列表数据和分页数据
|
||||||
|
if (api === this.api.fetchPage) {
|
||||||
|
this.tableData.list = this.__handleTableData(data.records)
|
||||||
|
this.tableData.pagination.total = data.total
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 查询所有接口,填充列表数据
|
||||||
|
this.tableData.list = this.__handleTableData(data)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.search = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 刷新
|
||||||
|
*/
|
||||||
|
refresh () {
|
||||||
|
this.handlePageChange(this.tableData.pagination.page)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除(点击行操作/删除时触发)
|
||||||
|
*
|
||||||
|
* @param row 行对象
|
||||||
|
* @param childConfirm 删除子节点时是否进行二次确认
|
||||||
|
*/
|
||||||
|
deleteById (row, childConfirm = true) {
|
||||||
|
this.__checkApi()
|
||||||
|
let message = `确认删除${this.module}【${row[this.configData['field.main']]}】吗?`
|
||||||
|
if (row[this.configData['field.main']] == null) {
|
||||||
|
message = `确认删除该${this.module}吗?`
|
||||||
|
}
|
||||||
|
if (childConfirm && row.children != null && row.children.length > 0) {
|
||||||
|
message = `确认删除${this.module}【${row[this.configData['field.main']]}】及其子${this.module}吗?`
|
||||||
|
if (row[this.configData['field.main']] == null) {
|
||||||
|
message = `确认删除该${this.module}及其子${this.module}吗?`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$dialog.deleteConfirm(message)
|
||||||
|
.then(() => {
|
||||||
|
this.isWorking.delete = true
|
||||||
|
return this.api.deleteById(row[this.configData['field.id']])
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.__afterDelete()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.delete = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 批量删除(点击批量删除时触发)
|
||||||
|
*
|
||||||
|
* @param childConfirm 删除子节点时是否进行二次确认
|
||||||
|
*/
|
||||||
|
deleteByIdInBatch (childConfirm = true) {
|
||||||
|
this.__checkApi()
|
||||||
|
if (this.tableData.selectedRows.length === 0) {
|
||||||
|
this.$tip.warning('请至少选择一条数据')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let message = `确认删除已选中的 ${this.tableData.selectedRows.length} 条${this.module}记录吗?`
|
||||||
|
if (childConfirm) {
|
||||||
|
const containChildrenRows = []
|
||||||
|
for (const row of this.tableData.selectedRows) {
|
||||||
|
if (row.children != null && row.children.length > 0) {
|
||||||
|
containChildrenRows.push(row[this.configData['field.main']])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (containChildrenRows.length > 0) {
|
||||||
|
message = `本次将删除${this.module}【${containChildrenRows.join('、')}】及其子${this.module}记录,确认删除吗?`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$dialog.deleteConfirm(message)
|
||||||
|
.then(() => {
|
||||||
|
this.isWorking.delete = true
|
||||||
|
return this.api.deleteByIdInBatch(this.tableData.selectedRows.map(row => row.id).join(','))
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.__afterDelete(this.tableData.selectedRows.length)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.delete = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取资源类型显示标签
|
||||||
|
*
|
||||||
|
* @param dictValues 字典值字符串,如 ,MALE,FEMALE,
|
||||||
|
* @param dictCode 字典编码
|
||||||
|
* @returns {(string|*)[]|*[]}
|
||||||
|
*/
|
||||||
|
getDictLabels (dictValues, dictCode) {
|
||||||
|
if (dictValues == null || dictValues.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return dictValues.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
.map(item => this.$d(`${dictCode}.${item}`))
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取搜索表单对象
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__getSearchForm () {
|
||||||
|
return this.searchForm
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 删除后处理,在单行删除或多行删除后调用
|
||||||
|
*
|
||||||
|
* @param deleteCount 删除数量
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__afterDelete (deleteCount = 1) {
|
||||||
|
this.$tip.apiSuccess('删除成功')
|
||||||
|
// 删除当前页最后一条记录时查询上一页数据
|
||||||
|
if (this.tableData.list.length - deleteCount === 0) {
|
||||||
|
this.handlePageChange(this.tableData.pagination.page - 1 === 0 ? 1 : this.tableData.pagination.page - 1)
|
||||||
|
} else {
|
||||||
|
this.handlePageChange(this.tableData.pagination.page)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 检查接口是否配置,在调用接口时调用
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__checkApi () {
|
||||||
|
if (this.api == null) {
|
||||||
|
throw new Error('页面没有使用config方法进行初始化,无法使用BaseTable提供的属性和方法。')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 处理列表数据
|
||||||
|
*
|
||||||
|
* @param data 列表数据
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__handleTableData (data) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
58
src/components/cms/article/ArticleAttachmentUploader.vue
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<template>
|
||||||
|
<el-upload
|
||||||
|
class="attach-uploader"
|
||||||
|
action="/api/cms/article/upload/attach"
|
||||||
|
ref="upload"
|
||||||
|
:multiple="true"
|
||||||
|
:show-file-list="false"
|
||||||
|
:before-upload="handleBeforeUpload"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
:on-error="handleUploadError"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</el-upload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleAttachmentUploader',
|
||||||
|
emits: ['add-file', 'success', 'error'],
|
||||||
|
methods: {
|
||||||
|
handleBeforeUpload (file) {
|
||||||
|
this.$emit('add-file', file)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
// 处理文件上传成功
|
||||||
|
handleUploadSuccess (res, file) {
|
||||||
|
if (!res.success) {
|
||||||
|
this.$emit('error', {
|
||||||
|
error: res.message,
|
||||||
|
file
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('success', {
|
||||||
|
file,
|
||||||
|
data: res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 处理文件上传失败
|
||||||
|
handleUploadError (error, file) {
|
||||||
|
this.$emit('error', {
|
||||||
|
error: error.message,
|
||||||
|
file
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attach-uploader {
|
||||||
|
display: flex;
|
||||||
|
:deep(.el-upload-list) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
98
src/components/cms/article/ArticleAttachments.vue
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<div class="article-attachments">
|
||||||
|
<ul v-if="modelValue.length > 0">
|
||||||
|
<li v-for="(attach, index) of modelValue" :key="attach.uid">
|
||||||
|
<label>{{ attach.name }}</label>
|
||||||
|
<div>
|
||||||
|
<span v-if="attach.__uploading">
|
||||||
|
<el-icon class="is-loading" size="16">
|
||||||
|
<Loading/>
|
||||||
|
</el-icon>
|
||||||
|
<i>上传中...</i>
|
||||||
|
</span>
|
||||||
|
<Icon v-else value="Close" @click="deleteFile(index)"/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<EmptyTip v-else description="无附件"/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'ArticleAttachments',
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
deleteFile (index) {
|
||||||
|
this.$dialog.deleteConfirm(`确认删除 ${this.modelValue[index].name} 附件吗?`)
|
||||||
|
.then(() => {
|
||||||
|
const newModelValue = [...this.modelValue]
|
||||||
|
newModelValue.splice(index, 1)
|
||||||
|
this.$emit('update:modelValue', newModelValue)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.article-attachments {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 5px;
|
||||||
|
ul li {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 12px;
|
||||||
|
border-bottom: 1px dashed #eee;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
& > label {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
& > div {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 12px;
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
:deep(.el-icon) {
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--font-color-gray);
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.el-empty {
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
110
src/components/cms/article/ArticleCoverSelect.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
title="选取文章封面"
|
||||||
|
v-model="visible"
|
||||||
|
:append-to-body="true"
|
||||||
|
class="article-cover-select-dialog"
|
||||||
|
>
|
||||||
|
<ul v-if="images.length > 0">
|
||||||
|
<li
|
||||||
|
v-for="url in images"
|
||||||
|
:key="url"
|
||||||
|
:class="{ selected: selected === url }"
|
||||||
|
@click="selected = url"
|
||||||
|
>
|
||||||
|
<img :src="url" alt="文章封面选取"/>
|
||||||
|
</li>
|
||||||
|
<!-- 补充差缺项,实现规整的3列 -->
|
||||||
|
<li v-for="i in images.length % 3 === 0 ? 0 : (3 - images.length % 3)" :key="i"></li>
|
||||||
|
</ul>
|
||||||
|
<EmptyTip v-else description="文中暂无图片可选择"/>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="visible = false">关闭</el-button>
|
||||||
|
<el-button type="primary" :disabled="selected == null" @click="confirm">确认选择</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleCoverSelect',
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
images: [],
|
||||||
|
selected: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param images 图片URL集
|
||||||
|
*/
|
||||||
|
open (images) {
|
||||||
|
this.images = images
|
||||||
|
this.selected = null
|
||||||
|
this.visible = true
|
||||||
|
},
|
||||||
|
// 确认
|
||||||
|
confirm () {
|
||||||
|
if (this.selected == null) {
|
||||||
|
this.$tip.warning('请先选择封面!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.visible = false
|
||||||
|
this.$emit('confirm-select', this.selected.split('=')[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.article-cover-select-dialog {
|
||||||
|
width: 980px !important;
|
||||||
|
ul {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
li {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 285px;
|
||||||
|
height: 200px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: all ease .15s;
|
||||||
|
border-radius: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
transition: all ease .3s;
|
||||||
|
}
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
border-color: #eee;
|
||||||
|
img {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:empty {
|
||||||
|
cursor: default;
|
||||||
|
background-color: transparent;
|
||||||
|
&:hover {
|
||||||
|
border-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
104
src/components/cms/article/ArticleSelect.vue
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<el-select
|
||||||
|
class="article-tag-select"
|
||||||
|
:class="{select__block: !inline}"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:clearable="clearable"
|
||||||
|
:disabled="disabled"
|
||||||
|
:multiple="multiple"
|
||||||
|
:model-value="modelValue"
|
||||||
|
filterable
|
||||||
|
remote
|
||||||
|
:remote-method="fetchList"
|
||||||
|
@update:modelValue="$emit('update:modelValue', $event)"
|
||||||
|
@change="$emit('change', $event)"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="data in computedList"
|
||||||
|
:key="data.id"
|
||||||
|
:value="data[valueKey]"
|
||||||
|
:label="data.title"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fetchProfileList4Select } from '@/api/cms/article'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleSelect',
|
||||||
|
props: {
|
||||||
|
modelValue: {},
|
||||||
|
valueKey: {
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: '请选择文章'
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
// 顶部补充的选项列表
|
||||||
|
firstOptions: {
|
||||||
|
type: Array,
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedList () {
|
||||||
|
return [
|
||||||
|
...this.firstOptions,
|
||||||
|
...this.list
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 查询数据
|
||||||
|
fetchList (kwd = '') {
|
||||||
|
if (this.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
fetchProfileList4Select({
|
||||||
|
uids: this.modelValue == null ? [] : [this.modelValue],
|
||||||
|
kwd
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.list = data
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.select__block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
135
src/components/cms/article/ArticleTagManageWindow.vue
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
title="文章标签管理"
|
||||||
|
width="1000px"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:with-footer="false"
|
||||||
|
:wrapper-closable="true"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
>
|
||||||
|
<TableLayout
|
||||||
|
:authorized="$hasPermissions([`cms:article:tag:query`])"
|
||||||
|
:fullscreen="fullscreen"
|
||||||
|
>
|
||||||
|
<!-- 搜索表单 -->
|
||||||
|
<template #search-form>
|
||||||
|
<SearchForm ref="searchForm" :model="searchForm" :collapse="false">
|
||||||
|
<el-form-item label="标签名" prop="name">
|
||||||
|
<el-input v-model="searchForm.name" v-trim placeholder="请输入标签名" @keypress.enter="search"/>
|
||||||
|
</el-form-item>
|
||||||
|
<template #buttons>
|
||||||
|
<el-button type="primary" @click="search">搜索</el-button>
|
||||||
|
<el-button @click="reset">重置</el-button>
|
||||||
|
</template>
|
||||||
|
</SearchForm>
|
||||||
|
</template>
|
||||||
|
<!-- 表格和分页 -->
|
||||||
|
<template #table-wrap>
|
||||||
|
<SearchTable
|
||||||
|
v-loading="isWorking.search"
|
||||||
|
:data="tableData.list"
|
||||||
|
:with-fullscreen="false"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@refresh="refresh"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="$hasAnyPermissions([
|
||||||
|
'cms:article:tag:create',
|
||||||
|
'cms:article:tag:delete'
|
||||||
|
])"
|
||||||
|
#toolbar
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:article:tag:create']"
|
||||||
|
type="primary"
|
||||||
|
icon="Plus"
|
||||||
|
@click="$refs.operaArticleTagWindow.open('新建标签')"
|
||||||
|
>新建</el-button>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:article:tag:delete']"
|
||||||
|
icon="Delete"
|
||||||
|
@click="deleteByIdInBatch"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column prop="name" label="标签名" min-width="100px"/>
|
||||||
|
<el-table-column prop="creatorRealName" label="创建人" min-width="100px"/>
|
||||||
|
<el-table-column prop="createdAt" label="创建时间" min-width="185px"/>
|
||||||
|
<el-table-column prop="updaterRealName" label="更新人" min-width="100px"/>
|
||||||
|
<el-table-column prop="updatedAt" label="更新时间" min-width="185px"/>
|
||||||
|
<template
|
||||||
|
v-if="$hasAnyPermissions([
|
||||||
|
'cms:article:tag:update',
|
||||||
|
'cms:article:tag:delete'
|
||||||
|
])"
|
||||||
|
#buttons="{ row }"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:article:tag:update']"
|
||||||
|
type="primary"
|
||||||
|
icon="EditPen"
|
||||||
|
link
|
||||||
|
@click="$refs.operaArticleTagWindow.open(`编辑${row.name}`, row)"
|
||||||
|
>编辑</el-button>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:article:tag:delete']"
|
||||||
|
type="danger"
|
||||||
|
icon="Delete"
|
||||||
|
link
|
||||||
|
@click="deleteById(row)"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</SearchTable>
|
||||||
|
<pagination
|
||||||
|
:pagination="tableData.pagination"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- 新建编辑窗口 -->
|
||||||
|
<OperaArticleTagWindow
|
||||||
|
ref="operaArticleTagWindow"
|
||||||
|
@success="refresh"
|
||||||
|
/>
|
||||||
|
</TableLayout>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseTable from '@/components/base/BaseTable'
|
||||||
|
import OperaArticleTagWindow from '@/components/cms/article/OperaArticleTagWindow'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleTagManageWindow',
|
||||||
|
extends: BaseTable,
|
||||||
|
components: { OperaArticleTagWindow },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
// 搜索
|
||||||
|
searchForm: {
|
||||||
|
name: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param dictId 字典ID
|
||||||
|
* @param dictName 字典名称
|
||||||
|
*/
|
||||||
|
open (dictId, dictName) {
|
||||||
|
this.searchForm.dictId = dictId
|
||||||
|
this.dictName = dictName
|
||||||
|
this.visible = true
|
||||||
|
this.search()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
module: '标签',
|
||||||
|
api: await import('@/api/cms/article.tag')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
84
src/components/cms/article/ArticleTagSelect.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<el-select
|
||||||
|
class="article-tag-select"
|
||||||
|
:class="{select__block: !inline}"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:clearable="clearable"
|
||||||
|
:disabled="disabled"
|
||||||
|
:multiple="multiple"
|
||||||
|
:filterable="true"
|
||||||
|
:filter-method="filterMethod"
|
||||||
|
@change="$emit('change', $event)"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="data in computedList"
|
||||||
|
:key="data.id"
|
||||||
|
:value="data.id"
|
||||||
|
:label="data.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fetchAll } from '@/api/cms/article.tag'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleTagSelect',
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: '请选择文章标签'
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
filterWord: '',
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedList () {
|
||||||
|
if (this.filterWord.trim() === '') {
|
||||||
|
return this.list
|
||||||
|
}
|
||||||
|
return this.list.filter(item => item.name.indexOf(this.filterWord) !== -1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 过滤
|
||||||
|
filterMethod (kwd) {
|
||||||
|
this.filterWord = kwd
|
||||||
|
},
|
||||||
|
// 查询数据
|
||||||
|
fetchList () {
|
||||||
|
fetchAll()
|
||||||
|
.then(data => {
|
||||||
|
this.list = data
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.select__block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
55
src/components/cms/article/OperaArticleTagWindow.vue
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
:title="title"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
width="750px"
|
||||||
|
@confirm="confirm"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="form" :rules="rules" @submit.prevent>
|
||||||
|
<el-form-item label="标签名" prop="name" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.name"
|
||||||
|
placeholder="请输入标签名"
|
||||||
|
v-trim
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import GlobalWindow from '@/components/common/GlobalWindow'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OperaArticleTagWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
components: {
|
||||||
|
GlobalWindow
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
name: ''
|
||||||
|
},
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入标签名' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
api: await import('@/api/cms/article.tag'),
|
||||||
|
'field.id': 'id'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
255
src/components/cms/article/editor/ArticleEditorWindow.vue
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
ref="dialog"
|
||||||
|
:title="title"
|
||||||
|
v-model="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
:fullscreen="true"
|
||||||
|
:append-to-body="true"
|
||||||
|
:close-on-press-escape="false"
|
||||||
|
:show-close="false"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
class="article-dialog"
|
||||||
|
@close="$emit('exit')"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<h3>{{ title }}</h3>
|
||||||
|
<div class="opera">
|
||||||
|
<el-button
|
||||||
|
v-if="form.status === $const.ARTICLE_STATUS.ONLINE"
|
||||||
|
type="primary"
|
||||||
|
:disabled="unsavable"
|
||||||
|
:loading="isWorking.save"
|
||||||
|
@click="save"
|
||||||
|
>更新</el-button>
|
||||||
|
<template v-else>
|
||||||
|
<el-button
|
||||||
|
:disabled="unsavable"
|
||||||
|
:loading="isWorking.save"
|
||||||
|
@click="save"
|
||||||
|
>保存</el-button>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:disabled="isWorking.publish"
|
||||||
|
:loading="isWorking.publish"
|
||||||
|
@click="publish"
|
||||||
|
>发布</el-button>
|
||||||
|
</template>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
@click="exit"
|
||||||
|
>退出</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="content-wrap">
|
||||||
|
<div class="settings-wrap">
|
||||||
|
<el-form :model="form" ref="form" :rules="rules">
|
||||||
|
<el-form-item label="所属栏目" prop="categoryIds" required>
|
||||||
|
<CategorySelect v-model="form.categoryIds"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文章摘要" prop="contentDigest" class="form-item-with-opera" required>
|
||||||
|
<template #label>
|
||||||
|
<span>文章摘要</span>
|
||||||
|
<el-button size="small" @click="extractDigest">自动提取</el-button>
|
||||||
|
</template>
|
||||||
|
<el-input
|
||||||
|
v-model="form.contentDigest"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
placeholder="请输入文章摘要"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="关键字" prop="keywords">
|
||||||
|
<el-input
|
||||||
|
v-model="form.keywords"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
placeholder="请输入文章关键字"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
<FormItemTip>文章关键字用于SEO优化</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文章标签" prop="tagIds">
|
||||||
|
<ArticleTagSelect v-model="form.tagIds"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文章封面图" prop="cover" class="form-item-with-opera">
|
||||||
|
<template #label>
|
||||||
|
<span>文章封面图</span>
|
||||||
|
<el-button size="small" @click="openCoverSelect">从正文中选取</el-button>
|
||||||
|
</template>
|
||||||
|
<ImageUploader v-model="form.cover"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="附件上传" prop="attachments" class="form-item-with-opera form-item-attachments">
|
||||||
|
<template #label>
|
||||||
|
<span>附件上传</span>
|
||||||
|
<ArticleAttachmentUploader
|
||||||
|
@add-file="handleAddAttachment"
|
||||||
|
@success="handleAttachmentUploadSuccess"
|
||||||
|
@error="handleAttachmentUploadError"
|
||||||
|
>
|
||||||
|
<el-button size="small" type="primary" icon="Upload">上传附件</el-button>
|
||||||
|
</ArticleAttachmentUploader>
|
||||||
|
</template>
|
||||||
|
<!-- 附件列表展示 -->
|
||||||
|
<ArticleAttachments v-model="form.attachments"/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="editor-wrap">
|
||||||
|
<el-input
|
||||||
|
v-model="form.title"
|
||||||
|
placeholder="请输入文章标题"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
<RichEditor
|
||||||
|
ref="richEditor"
|
||||||
|
v-model="form.content"
|
||||||
|
placeholder="请输入文章内容"
|
||||||
|
@created="handleEditorCreated"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 正文图片选择 -->
|
||||||
|
<ArticleCoverSelect ref="articleCoverSelect" @confirm-select="form.cover = $event"/>
|
||||||
|
<!-- 退出窗口 -->
|
||||||
|
<el-dialog
|
||||||
|
class="article-exit-dialog"
|
||||||
|
v-model="exitDialog.visible"
|
||||||
|
align-center
|
||||||
|
width="420px"
|
||||||
|
title="确认退出"
|
||||||
|
>
|
||||||
|
<Icon value="WarningFilled" size="24px"/>{{ exitDialog.message }}
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="exitDialog.visible = false">取消</el-button>
|
||||||
|
<el-button type="danger" @click="forceClose">直接退出</el-button>
|
||||||
|
<el-button type="primary" @click="saveAndClose">{{ exitDialog.confirmButtonText }}</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import RichEditor from '@/components/common/RichEditor'
|
||||||
|
import ImageUploader from '@/components/common/ImageUploader'
|
||||||
|
import CategorySelect from '@/components/cms/category/CategorySelect'
|
||||||
|
import ArticleTagSelect from '@/components/cms/article/ArticleTagSelect'
|
||||||
|
import ArticleCoverSelect from '@/components/cms/article/ArticleCoverSelect'
|
||||||
|
import ArticleAttachments from '@/components/cms/article/ArticleAttachments'
|
||||||
|
import ArticleAttachmentUploader from '@/components/cms/article/ArticleAttachmentUploader'
|
||||||
|
import BaseMixin from '@/components/cms/article/editor/BaseMixin'
|
||||||
|
import AttachmentsMixin from '@/components/cms/article/editor/AttachmentsMixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ArticleEditorWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
mixins: [BaseMixin, AttachmentsMixin],
|
||||||
|
components: {
|
||||||
|
ArticleAttachmentUploader,
|
||||||
|
ArticleAttachments,
|
||||||
|
ArticleCoverSelect,
|
||||||
|
ArticleTagSelect,
|
||||||
|
CategorySelect,
|
||||||
|
ImageUploader,
|
||||||
|
RichEditor
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
isWorking: {
|
||||||
|
save: false,
|
||||||
|
publish: false
|
||||||
|
},
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
uid: '',
|
||||||
|
title: '',
|
||||||
|
status: '',
|
||||||
|
categoryIds: [],
|
||||||
|
tagIds: [],
|
||||||
|
cover: '',
|
||||||
|
attachments: [],
|
||||||
|
content: '',
|
||||||
|
contentDigest: '',
|
||||||
|
keywords: ''
|
||||||
|
},
|
||||||
|
// 原始表单数据,打开窗口和保存时进行赋值
|
||||||
|
savedForm: null,
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
categoryIds: [
|
||||||
|
{ required: true, message: '请选择所属栏目' }
|
||||||
|
],
|
||||||
|
contentDigest: [
|
||||||
|
{ required: true, message: '请输入文章摘要' }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
// 退出窗口
|
||||||
|
exitDialog: {
|
||||||
|
visible: false,
|
||||||
|
message: '',
|
||||||
|
confirmButtonText: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// 不可保存
|
||||||
|
unsavable () {
|
||||||
|
return this.isWorking.save || JSON.stringify(this.form) === JSON.stringify(this.savedForm)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 标题
|
||||||
|
* @param target 文章对象
|
||||||
|
*/
|
||||||
|
open (title, target) {
|
||||||
|
this.visible = true
|
||||||
|
this.title = title
|
||||||
|
this.$nextTick(() => {
|
||||||
|
// 重置表单
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
// - 以下字段不在表单元素中,需手动清理
|
||||||
|
this.form.id = null
|
||||||
|
this.form.uid = ''
|
||||||
|
this.form.title = ''
|
||||||
|
this.form.content = ''
|
||||||
|
this.form.status = ''
|
||||||
|
// 填充文章信息
|
||||||
|
if (target != null) {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
// 补充栏目
|
||||||
|
this.form.categoryIds = target.categoryIds == null || target.categoryIds.length === 0 ?
|
||||||
|
[] : target.categoryIds.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
.map(item => Number(item))
|
||||||
|
// 补充标签
|
||||||
|
this.form.tagIds = target.tagIds == null || target.tagIds.length === 0 ?
|
||||||
|
[] : target.tagIds.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
.map(item => Number(item))
|
||||||
|
// 初始化附件
|
||||||
|
this.form.attachments = target.attachments == null ?
|
||||||
|
[] : JSON.parse(target.attachments)
|
||||||
|
}
|
||||||
|
// 拷贝一份作为已保存的数据,用于保存检测
|
||||||
|
this.savedForm = JSON.parse(JSON.stringify(this.form))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
@import "./editor";
|
||||||
|
</style>
|
34
src/components/cms/article/editor/AttachmentsMixin.vue
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'AttachmentsMixin',
|
||||||
|
methods: {
|
||||||
|
// 添加附件
|
||||||
|
handleAddAttachment (file) {
|
||||||
|
this.form.attachments.push({
|
||||||
|
uid: file.uid,
|
||||||
|
name: file.name,
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
size: file.size,
|
||||||
|
// 文件是否正在上传
|
||||||
|
__uploading: true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 附件上传成功
|
||||||
|
handleAttachmentUploadSuccess ({ data, file }) {
|
||||||
|
const targetFile = this.form.attachments.find(item => item.uid === file.uid)
|
||||||
|
if (targetFile != null) {
|
||||||
|
targetFile.fileKey = data.fileKey
|
||||||
|
delete targetFile.__uploading
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 附件上传失败
|
||||||
|
handleAttachmentUploadError ({ error, file }) {
|
||||||
|
const targetFileIndex = this.form.attachments.findIndex(item => item.uid === file.uid)
|
||||||
|
if (targetFileIndex !== -1) {
|
||||||
|
this.form.attachments.splice(targetFileIndex, 1)
|
||||||
|
this.$tip.apiFailed(`附件 ${file.name} 上传失败,原因:${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
145
src/components/cms/article/editor/BaseMixin.vue
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<script>
|
||||||
|
import { publish, save } from '@/api/cms/article'
|
||||||
|
export default {
|
||||||
|
name: 'BaseMixin',
|
||||||
|
methods: {
|
||||||
|
// 打开封面选择
|
||||||
|
openCoverSelect () {
|
||||||
|
this.$refs.articleCoverSelect.open(this.$refs.richEditor.getImages())
|
||||||
|
},
|
||||||
|
// 保存草稿/更新文章
|
||||||
|
save () {
|
||||||
|
if (this.isWorking.save) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
if (this.form.title.trim() === '') {
|
||||||
|
this.$tip.warning('请填写文章标题!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const attach of this.form.attachments) {
|
||||||
|
if (attach.__uploading) {
|
||||||
|
this.$tip.warning('存在正在上传中的附件,请等待上传完成!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.isWorking.save = true
|
||||||
|
save ({
|
||||||
|
...this.form,
|
||||||
|
// 在前后方添加“,“,方便模糊查询
|
||||||
|
categoryIds: ',' + this.form.categoryIds.join(',') + ',',
|
||||||
|
// 在前后方添加“,",方便模糊查询
|
||||||
|
tagIds: ',' + this.form.tagIds.join(',') + ',',
|
||||||
|
// 图片集
|
||||||
|
images: JSON.stringify(this.$refs.richEditor.getImages()),
|
||||||
|
// 附件集
|
||||||
|
attachments: JSON.stringify(this.form.attachments)
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
this.form.id = data.id
|
||||||
|
// 拷贝一份作为已保存的数据,用于保存检测
|
||||||
|
this.savedForm = JSON.parse(JSON.stringify(this.form))
|
||||||
|
resolve(data)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
reject(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.save = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 保存并关闭
|
||||||
|
saveAndClose () {
|
||||||
|
this.save()
|
||||||
|
.then(() => {
|
||||||
|
this.forceClose()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 强制关闭
|
||||||
|
forceClose () {
|
||||||
|
this.visible = false
|
||||||
|
this.exitDialog.visible = false
|
||||||
|
},
|
||||||
|
// 退出
|
||||||
|
exit () {
|
||||||
|
// 已保存或未发生任何改动,直接关闭
|
||||||
|
// - 内容会受到编辑器初始化值影响,编辑器初始化空值为<p><br></p>,需要做进一步空处理
|
||||||
|
const copyForm = JSON.parse(JSON.stringify(this.form))
|
||||||
|
copyForm.content = copyForm.content === '<p><br></p>' ? '' : copyForm.content
|
||||||
|
if (JSON.stringify(copyForm) === JSON.stringify(this.savedForm)) {
|
||||||
|
this.visible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 未保存,做弹窗提示
|
||||||
|
let operaName = '保存'
|
||||||
|
if (this.form.status === this.$const.ARTICLE_STATUS.ONLINE) {
|
||||||
|
operaName = '更新'
|
||||||
|
}
|
||||||
|
this.exitDialog.message = `当前存在数据还未${operaName},是否继续退出?`
|
||||||
|
this.exitDialog.confirmButtonText = `${operaName}并退出`
|
||||||
|
this.exitDialog.visible = true
|
||||||
|
},
|
||||||
|
// 发布
|
||||||
|
publish () {
|
||||||
|
if (this.isWorking.publish) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.form.title.trim() === '') {
|
||||||
|
this.$tip.warning('请填写文章标题!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.$refs.richEditor.getText().trim() === '' &&
|
||||||
|
this.$refs.richEditor.getImages().length === 0 &&
|
||||||
|
this.form.attachments.length === 0) {
|
||||||
|
this.$tip.warning('请输入文章内容!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const attach of this.form.attachments) {
|
||||||
|
if (attach.__uploading) {
|
||||||
|
this.$tip.warning('存在正在上传中的附件,请等待上传完成!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$refs.form.validate(valid => {
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.isWorking.publish = true
|
||||||
|
// 执行保存
|
||||||
|
this.save()
|
||||||
|
.then(() => {
|
||||||
|
// 执行发布
|
||||||
|
publish(this.form.id)
|
||||||
|
.then(() => {
|
||||||
|
// 发布成功后关闭窗口
|
||||||
|
this.visible = false
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isWorking.publish = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.isWorking.publish = false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 提取摘要
|
||||||
|
extractDigest () {
|
||||||
|
this.form.contentDigest = this.$refs.richEditor.getDigest()
|
||||||
|
},
|
||||||
|
// 编辑器创建完成
|
||||||
|
handleEditorCreated () {
|
||||||
|
// 启用图片上传功能
|
||||||
|
this.$refs.richEditor.enableImageUpload({
|
||||||
|
url: '/cms/article/upload/image',
|
||||||
|
maxFileSize: 5
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
105
src/components/cms/article/editor/editor.scss
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
.article-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
& > .el-dialog__header {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
// 关闭按钮
|
||||||
|
.el-dialog__headerbtn {
|
||||||
|
top: 3px;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .el-dialog__body {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.content-wrap {
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
& > * {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
// 设置
|
||||||
|
.settings-wrap {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 15%;
|
||||||
|
min-width: 255px;
|
||||||
|
max-width: 350px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
border-right: 1px solid #eee;
|
||||||
|
// 表单项
|
||||||
|
.el-form-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
// 标签
|
||||||
|
.el-form-item__label {
|
||||||
|
float: none;
|
||||||
|
}
|
||||||
|
// 元素宽度为100%
|
||||||
|
.el-form-item__content{
|
||||||
|
& > * {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 带操作的 form-item
|
||||||
|
.form-item-with-opera {
|
||||||
|
.el-form-item__label {
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding-right: 0;
|
||||||
|
position: relative;
|
||||||
|
.el-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 编辑器
|
||||||
|
.editor-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
& > .el-input {
|
||||||
|
height: 60px;
|
||||||
|
font-size: 25px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
.el-input__wrapper {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& > .rich-editor {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 退出
|
||||||
|
.article-exit-dialog {
|
||||||
|
.el-dialog__body {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
.icon {
|
||||||
|
color: var(--el-color-warning);
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
src/components/cms/category/CategoryIcon.vue
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 网络图标 -->
|
||||||
|
<img
|
||||||
|
v-if="isNetworkIcon"
|
||||||
|
class="category-icon image-icon"
|
||||||
|
:src="value"
|
||||||
|
alt="栏目图标"
|
||||||
|
:style="{ width: sizeWidth }"
|
||||||
|
/>
|
||||||
|
<!-- class图标 -->
|
||||||
|
<i
|
||||||
|
v-else-if="isClassIcon"
|
||||||
|
class="category-icon"
|
||||||
|
:class="value"
|
||||||
|
:style="{ 'font-size': size }"
|
||||||
|
></i>
|
||||||
|
<!-- 不正确的图标 -->
|
||||||
|
<span class="category-icon holder" v-else-if="withHolder"><em>x</em></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CategoryIcon',
|
||||||
|
props: {
|
||||||
|
// 图标值
|
||||||
|
value: {},
|
||||||
|
// 尺寸
|
||||||
|
size: {
|
||||||
|
default: 'inherit'
|
||||||
|
},
|
||||||
|
// 如果图标不存在,是否显示占位符
|
||||||
|
withHolder: {
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// size对应的width值
|
||||||
|
sizeWidth () {
|
||||||
|
if (this.size === 'inherit') {
|
||||||
|
return '16px'
|
||||||
|
}
|
||||||
|
return this.size
|
||||||
|
},
|
||||||
|
// 判断是否为class图标
|
||||||
|
isClassIcon () {
|
||||||
|
if (this.value == null || this.value === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !this.value.startsWith('/') &&
|
||||||
|
!this.value.startsWith('http://') && !this.value.startsWith('https://')
|
||||||
|
},
|
||||||
|
// 判断是否为网络图标
|
||||||
|
isNetworkIcon () {
|
||||||
|
if (this.value == null || this.value === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return this.value.startsWith('/') ||
|
||||||
|
this.value.startsWith('http://') || this.value.startsWith('https://')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.holder {
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: red !important;
|
||||||
|
background: #e0e0e0;
|
||||||
|
em {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
105
src/components/cms/category/CategorySelect.vue
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<template>
|
||||||
|
<TreeSelect
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:data="data"
|
||||||
|
:append-to-body="appendToBody"
|
||||||
|
:clearable="clearable"
|
||||||
|
:inline="inline"
|
||||||
|
node-key="value"
|
||||||
|
:multiple="multiple"
|
||||||
|
:default-expand-all="true"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TreeSelect from '@/components/common/TreeSelect'
|
||||||
|
import { fetchAll } from '@/api/cms/category'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CategorySelect',
|
||||||
|
components: { TreeSelect },
|
||||||
|
props: {
|
||||||
|
valueKey: {
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: '请选择栏目'
|
||||||
|
},
|
||||||
|
// 是否可清空
|
||||||
|
clearable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
appendToBody: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
// 需被排除的栏目ID
|
||||||
|
excludeValue: {},
|
||||||
|
// 顶部补充的选项
|
||||||
|
firstOptions: {
|
||||||
|
default () {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
data: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
excludeValue () {
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 获取所有菜单
|
||||||
|
*/
|
||||||
|
fetchData () {
|
||||||
|
fetchAll()
|
||||||
|
.then(records => {
|
||||||
|
this.data = [
|
||||||
|
...this.firstOptions
|
||||||
|
]
|
||||||
|
this.__fillData(this.data, records)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 填充栏目树
|
||||||
|
__fillData (list, pool) {
|
||||||
|
for (const category of pool) {
|
||||||
|
// 已排除
|
||||||
|
if (category[this.valueKey] === this.excludeValue) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const categoryNode = {
|
||||||
|
value: category[this.valueKey],
|
||||||
|
label: category.title,
|
||||||
|
// 禁用非常规栏目
|
||||||
|
disabled: category.type !== this.$const.CATEGORY_TYPE.DEFAULT
|
||||||
|
}
|
||||||
|
list.push(categoryNode)
|
||||||
|
if (category.children != null && category.children.length > 0) {
|
||||||
|
categoryNode.children = []
|
||||||
|
this.__fillData(categoryNode.children, category.children)
|
||||||
|
if (categoryNode.children.length === 0) {
|
||||||
|
delete categoryNode.children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
310
src/components/cms/category/OperaCategoryWindow.vue
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
:title="title"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
width="750px"
|
||||||
|
@confirm="confirm"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="form" :rules="rules">
|
||||||
|
<el-form-item label="上级栏目" prop="parentId">
|
||||||
|
<CategorySelect v-model="form.parentId" :multiple="false" :clearable="true"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="栏目标题" prop="title" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.title"
|
||||||
|
placeholder="请输入栏目标题"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
@input="paddingUid"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="栏目图标" prop="icon">
|
||||||
|
<IconSelect v-if="visible" v-model="form.icon"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="唯一标识" prop="uid" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.uid"
|
||||||
|
placeholder="请输入唯一标识"
|
||||||
|
v-trim
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="类型" prop="type" required>
|
||||||
|
<DictRadioGroup code="CATEGORY_TYPES" v-model="form.type" @change="handleTypeChange"/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="form.type === $const.CATEGORY_TYPE.IN_LINK || form.type === $const.CATEGORY_TYPE.OUT_LINK"
|
||||||
|
label="链接地址"
|
||||||
|
prop="uri"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="form.uri"
|
||||||
|
placeholder="请输入链接地址"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item
|
||||||
|
v-if="visible && form.type === $const.CATEGORY_TYPE.DEFAULT"
|
||||||
|
label="模板"
|
||||||
|
prop="templateId"
|
||||||
|
>
|
||||||
|
<TemplateSelect
|
||||||
|
ref="templateSelect"
|
||||||
|
v-model="form.templateId"
|
||||||
|
v-model:parameters="form.templateParameters"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="hidden" required>
|
||||||
|
<el-switch
|
||||||
|
v-model="form.hidden"
|
||||||
|
active-text="显示"
|
||||||
|
:active-value="false"
|
||||||
|
inactive-text="隐藏"
|
||||||
|
:inactive-value="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort" required>
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.sort"
|
||||||
|
placeholder="请输入排序"
|
||||||
|
:controls="false"
|
||||||
|
:min="1"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.type === $const.CATEGORY_TYPE.DEFAULT" label="权限码" prop="permission">
|
||||||
|
<el-input
|
||||||
|
v-model="form.permission"
|
||||||
|
placeholder="请输入权限码"
|
||||||
|
v-trim
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="栏目配置" prop="config">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:rows="10"
|
||||||
|
v-model="form.config"
|
||||||
|
placeholder="请输入栏目配置"
|
||||||
|
v-trim
|
||||||
|
show-word-limit
|
||||||
|
maxlength="2000"
|
||||||
|
/>
|
||||||
|
<FormItemTip>栏目配置用于对栏目做进一步自定义设置,无需配置可不填写。</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import GlobalWindow from '@/components/common/GlobalWindow'
|
||||||
|
import CategorySelect from '@/components/cms/category/CategorySelect'
|
||||||
|
import TemplateSelect from '@/components/cms/template/TemplateSelect'
|
||||||
|
import IconSelect from '@/components/system/icon/IconSelect'
|
||||||
|
import { pinyin } from 'pinyin-pro'
|
||||||
|
import { checkUrl, checkRelativeUrl } from '@/core/utils/form'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OperaCategoryWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
components: {
|
||||||
|
IconSelect,
|
||||||
|
TemplateSelect,
|
||||||
|
CategorySelect,
|
||||||
|
GlobalWindow
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
const _this = this
|
||||||
|
return {
|
||||||
|
// 父栏目
|
||||||
|
parent: null,
|
||||||
|
// 原唯一标识
|
||||||
|
originUid: null,
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
parentId: null,
|
||||||
|
uid: '',
|
||||||
|
title: '',
|
||||||
|
type: 'DEFAULT',
|
||||||
|
uri: '',
|
||||||
|
icon: '',
|
||||||
|
permission: '',
|
||||||
|
templateId: null,
|
||||||
|
templateParameters: null,
|
||||||
|
hidden: false,
|
||||||
|
sort: 1,
|
||||||
|
config: ''
|
||||||
|
},
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
title: [
|
||||||
|
{ required: true, message: '请输入栏目标题' }
|
||||||
|
],
|
||||||
|
uid: [
|
||||||
|
{ required: true, message: '请输入唯一标识' }
|
||||||
|
],
|
||||||
|
type: [
|
||||||
|
{ required: true, message: '请输入类型' }
|
||||||
|
],
|
||||||
|
uri: [
|
||||||
|
{ required: true, message: '请输入链接地址' },
|
||||||
|
{ validator: _this.checkLink }
|
||||||
|
],
|
||||||
|
sort: [
|
||||||
|
{ required: true, message: '请输入排序' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param target 行对象(仅编辑需该参数)
|
||||||
|
* @param parent 父栏目
|
||||||
|
*/
|
||||||
|
open (title, target, parent) {
|
||||||
|
this.title = title
|
||||||
|
this.parent = parent
|
||||||
|
this.visible = true
|
||||||
|
// 新建
|
||||||
|
if (target == null) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.form[this.configData['field.id']] = null
|
||||||
|
this.form.parentId = parent == null ? null : parent.id
|
||||||
|
this.form.templateId = null
|
||||||
|
this.form.templateParameters = null
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
this.$nextTick(() => {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
// 拷贝原始唯一标识
|
||||||
|
this.originUid = this.form.uid
|
||||||
|
// 初始化数据参数
|
||||||
|
this.form.templateParameters = target.templateParameters == null || target.templateParameters === '' ?
|
||||||
|
[] : JSON.parse(this.form.templateParameters)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认(点击确认按钮后触发)
|
||||||
|
*/
|
||||||
|
confirm () {
|
||||||
|
if (this.form[this.configData['field.id']] == null || this.form[this.configData['field.id']] === '') {
|
||||||
|
this.__confirmCreate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.form.uid === this.originUid) {
|
||||||
|
this.__confirmEdit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 修改了唯一标识
|
||||||
|
this.$dialog.confirm('唯一标识可能会在源码中使用,修改后需及时修改源码,确认修改吗?如不确定,请咨询超级管理员!', '提示', {
|
||||||
|
confirmButtonText: '确认修改',
|
||||||
|
confirmButtonClass: 'el-button--danger',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.__confirmEdit()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
// 切换类型
|
||||||
|
handleTypeChange () {
|
||||||
|
if (this.form.type === this.$const.CATEGORY_TYPE.DEFAULT) {
|
||||||
|
this.form.uri = ''
|
||||||
|
}
|
||||||
|
if (this.form.type === this.$const.CATEGORY_TYPE.IN_LINK || this.form.type === this.$const.CATEGORY_TYPE.OUT_LINK) {
|
||||||
|
this.form.templateId = null
|
||||||
|
this.form.templateParameters = null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 补充uid
|
||||||
|
*
|
||||||
|
* @param name 栏目名称
|
||||||
|
*/
|
||||||
|
paddingUid (name) {
|
||||||
|
// 编辑时不补充
|
||||||
|
if (this.form.id != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.form.uid = pinyin(name, {
|
||||||
|
toneType: 'none',
|
||||||
|
type: 'array'
|
||||||
|
}).map(item => item.substring(0,1)).join('')
|
||||||
|
},
|
||||||
|
// 验证链接地址
|
||||||
|
checkLink (rule, value, callback) {
|
||||||
|
if (this.form.type === this.$const.CATEGORY_TYPE.IN_LINK) {
|
||||||
|
checkRelativeUrl(rule, value, callback)
|
||||||
|
} else {
|
||||||
|
checkUrl(rule, value, callback)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取新增编辑表单对象
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__getForm () {
|
||||||
|
const form = JSON.parse(JSON.stringify(this.form))
|
||||||
|
// 将模板参数转为JSON字符串
|
||||||
|
if (form.templateParameters != null) {
|
||||||
|
form.templateParameters = JSON.stringify(form.templateParameters)
|
||||||
|
}
|
||||||
|
return form
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证表单
|
||||||
|
*
|
||||||
|
* @param callback 验证回调
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__validateForm (callback) {
|
||||||
|
this.$refs.form.validate((valid) => {
|
||||||
|
if (!valid) {
|
||||||
|
callback(valid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.$refs.templateSelect) {
|
||||||
|
callback(valid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$refs.templateSelect.validateForm()
|
||||||
|
.then(() => {
|
||||||
|
callback(true)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
callback(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
api: await import('@/api/cms/category'),
|
||||||
|
'field.id': 'id'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
.el-input__inner {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
150
src/components/cms/resource/OperaResourceGroupWindow.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
:title="title"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
width="750px"
|
||||||
|
@confirm="confirm"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="form" :rules="rules">
|
||||||
|
<el-form-item label="唯一标识" prop="uid" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.uid"
|
||||||
|
placeholder="请输入唯一标识"
|
||||||
|
v-trim
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="组名" prop="name" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.name"
|
||||||
|
placeholder="请输入组名"
|
||||||
|
v-trim
|
||||||
|
maxlength="50"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="支持的资源类型" prop="supportedTypes">
|
||||||
|
<DictSelect
|
||||||
|
v-model="form.supportedTypes"
|
||||||
|
code="RESOURCE_TYPES"
|
||||||
|
placeholder="请选择支持的资源类型"
|
||||||
|
:multiple="true"
|
||||||
|
/>
|
||||||
|
<FormItemTip>选择支持的资源类型,可避免创建资源时指定不支持的类型!</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="form.remark"
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
show-word-limit
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import GlobalWindow from '@/components/common/GlobalWindow'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OperaResourceGroupWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
components: {
|
||||||
|
GlobalWindow
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 原始唯一标识
|
||||||
|
originUid: null,
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
uid: '',
|
||||||
|
name: '',
|
||||||
|
supportedTypes: [],
|
||||||
|
remark: ''
|
||||||
|
},
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
uid: [
|
||||||
|
{ required: true, message: '请输入唯一标识' }
|
||||||
|
],
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入组名' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param target 行对象(仅编辑需该参数)
|
||||||
|
*/
|
||||||
|
open (title, target) {
|
||||||
|
this.title = title
|
||||||
|
this.visible = true
|
||||||
|
// 新建
|
||||||
|
if (target == null) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.form[this.configData['field.id']] = null
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
this.$nextTick(() => {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
// 拷贝原始唯一标识
|
||||||
|
this.originUid = this.form.uid
|
||||||
|
// - 赋值支持的资源类型
|
||||||
|
this.form.supportedTypes = target.supportedTypes == null ? [] : target.supportedTypes.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 确认(点击确认按钮后触发)
|
||||||
|
*/
|
||||||
|
confirm () {
|
||||||
|
if (this.form[this.configData['field.id']] == null || this.form[this.configData['field.id']] === '') {
|
||||||
|
this.__confirmCreate()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.form.uid === this.originUid) {
|
||||||
|
this.__confirmEdit()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 修改了唯一标识
|
||||||
|
this.$dialog.confirm('唯一标识可能会在源码中使用,修改后需及时修改源码,确认修改吗?如不确定,请咨询超级管理员!', '提示', {
|
||||||
|
confirmButtonText: '确认修改',
|
||||||
|
confirmButtonClass: 'el-button--danger',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.__confirmEdit()
|
||||||
|
}).catch(() => {})
|
||||||
|
},
|
||||||
|
// 获取新增编辑表单对象
|
||||||
|
__getForm () {
|
||||||
|
const form = JSON.parse(JSON.stringify(this.form))
|
||||||
|
form.supportedTypes = form.supportedTypes.join(',')
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
api: await import('@/api/cms/resource.group'),
|
||||||
|
'field.id': 'id'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
213
src/components/cms/resource/OperaResourceWindow.vue
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
:title="title"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
width="750px"
|
||||||
|
@confirm="confirm"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="form" :rules="rules">
|
||||||
|
<el-form-item label="资源标题" prop="title" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.title"
|
||||||
|
placeholder="请输入资源标题"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源副标题" prop="subTitle">
|
||||||
|
<el-input
|
||||||
|
v-model="form.subTitle"
|
||||||
|
placeholder="请输入资源副标题"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源值类型" prop="valueType" required>
|
||||||
|
<DictSelect
|
||||||
|
v-model="form.valueType"
|
||||||
|
code="RESOURCE_TYPES"
|
||||||
|
placeholder="请选择资源值类型"
|
||||||
|
:only-values="supportedTypes"
|
||||||
|
@change="form.value = ''"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="form.valueType != null && form.valueType !== ''" label="资源值" prop="value" required>
|
||||||
|
<ResourceInput
|
||||||
|
ref="resourceInput"
|
||||||
|
:type="form.valueType"
|
||||||
|
v-model="form.value"
|
||||||
|
:image-tip="imageTip"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="disabled" required class="form-item-status">
|
||||||
|
<el-switch
|
||||||
|
v-model="form.disabled"
|
||||||
|
:active-value="false"
|
||||||
|
active-text="启用"
|
||||||
|
:inactive-value="true"
|
||||||
|
inactive-text="禁用"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort" required>
|
||||||
|
<el-input-number
|
||||||
|
v-model="form.sort"
|
||||||
|
placeholder="请输入排序"
|
||||||
|
:controls="false"
|
||||||
|
:min="1"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源描述" prop="description">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
v-model="form.description"
|
||||||
|
placeholder="请输入资源描述"
|
||||||
|
v-trim
|
||||||
|
show-word-limit
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源配置" prop="config">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
v-model="form.config"
|
||||||
|
placeholder="请输入资源配置"
|
||||||
|
v-trim
|
||||||
|
show-word-limit
|
||||||
|
maxlength="2000"
|
||||||
|
/>
|
||||||
|
<FormItemTip>资源配置用于通过JSON或其它形式的内容进一步对资源做设定!</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import GlobalWindow from '@/components/common/GlobalWindow'
|
||||||
|
import ResourceInput from '@/components/cms/resource/input/ResourceInput'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OperaResourceWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
components: {
|
||||||
|
ResourceInput,
|
||||||
|
GlobalWindow
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 资源组
|
||||||
|
group: null,
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
groupId: '',
|
||||||
|
title: '',
|
||||||
|
subTitle: '',
|
||||||
|
sort: 1,
|
||||||
|
description: '',
|
||||||
|
value: '',
|
||||||
|
valueType: null,
|
||||||
|
config: '',
|
||||||
|
disabled: false
|
||||||
|
},
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
title: [
|
||||||
|
{ required: true, message: '请输入资源标题' }
|
||||||
|
],
|
||||||
|
value: [
|
||||||
|
{ required: true, message: '请补充资源值' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
supportedTypes () {
|
||||||
|
if (this.group == null) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
if (this.group.supportedTypes == null || this.group.supportedTypes.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return this.group.supportedTypes.split(',').filter(type => type.trim() !== '')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param group 所属资源组
|
||||||
|
* @param target 行对象(仅编辑需该参数)
|
||||||
|
*/
|
||||||
|
open (title, group, target) {
|
||||||
|
this.group = group
|
||||||
|
this.title = title
|
||||||
|
this.visible = true
|
||||||
|
// 新建
|
||||||
|
if (target == null) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.form.id = null
|
||||||
|
this.form.groupId = group.id
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
this.$nextTick(() => {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证表单
|
||||||
|
*
|
||||||
|
* @param callback 验证回调
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__validateForm (callback) {
|
||||||
|
Promise.all([this.$refs.form.validate(), this.$refs.resourceInput.validate()])
|
||||||
|
.then(result => {
|
||||||
|
callback(result[0], result[1])
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
callback(false)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
api: await import('@/api/cms/resource'),
|
||||||
|
'field.id': 'id'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
:deep(.el-input-number) {
|
||||||
|
.el-input__inner {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.form-item-status) {
|
||||||
|
.el-form-item__content{
|
||||||
|
display: flex;
|
||||||
|
& > * {
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.status-text {
|
||||||
|
color: #999;
|
||||||
|
margin-left: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
80
src/components/cms/resource/ResourceGroupSelect.vue
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<el-select
|
||||||
|
class="resource-select"
|
||||||
|
:class="{select__block: !inline}"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:clearable="clearable"
|
||||||
|
:disabled="disabled"
|
||||||
|
:multiple="multiple"
|
||||||
|
@change="$emit('change', $event)"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="data in list"
|
||||||
|
:key="data.id"
|
||||||
|
:value="data[valueKey]"
|
||||||
|
:label="data.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { fetchAll } from '@/api/cms/resource.group'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ResourceGroupSelect',
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
valueKey: {
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: '请选择资源组'
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
multiple: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
list: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 查询数据
|
||||||
|
fetchList () {
|
||||||
|
if (this.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.loading = true
|
||||||
|
fetchAll()
|
||||||
|
.then(data => {
|
||||||
|
this.list = data
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.select__block {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
216
src/components/cms/resource/ResourceManageWindow.vue
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
v-if="group != null"
|
||||||
|
:title="group.name + '资源管理'"
|
||||||
|
width="1200px"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:with-footer="false"
|
||||||
|
:wrapper-closable="true"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
>
|
||||||
|
<TableLayout
|
||||||
|
:authorized="$hasPermissions([`cms:resource:query`])"
|
||||||
|
:fullscreen="fullscreen"
|
||||||
|
>
|
||||||
|
<!-- 表格和分页 -->
|
||||||
|
<template #table-wrap>
|
||||||
|
<SearchTable
|
||||||
|
v-loading="isWorking.search"
|
||||||
|
:data="tableData.list"
|
||||||
|
:with-fullscreen="false"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@refresh="refresh"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-if="$hasAnyPermissions([
|
||||||
|
'cms:resource:create',
|
||||||
|
'cms:resource:delete'
|
||||||
|
])"
|
||||||
|
#toolbar
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:resource:create']"
|
||||||
|
type="primary"
|
||||||
|
icon="Plus"
|
||||||
|
@click="$refs.operaResourceWindow.open('新建资源', group)"
|
||||||
|
>新建</el-button>
|
||||||
|
<el-button
|
||||||
|
v-permissions="['cms:resource:delete']"
|
||||||
|
type="danger"
|
||||||
|
icon="Delete"
|
||||||
|
@click="deleteByIdInBatch"
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table-column type="selection" fixed="left" width="55"/>
|
||||||
|
<el-table-column prop="title" label="资源标题" min-width="150px"/>
|
||||||
|
<el-table-column prop="sort" label="排序" min-width="80px"/>
|
||||||
|
<el-table-column prop="subTitle" label="资源副标题" min-width="150px"/>
|
||||||
|
<el-table-column prop="description" label="资源描述" min-width="150px">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<PopoverCellValue :content="row.description"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="value" label="资源值" min-width="200px">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<ResourcePreview :value="row.value" :type="row.valueType" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="valueType" label="资源值类型" min-width="100px">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ $d(`RESOURCE_TYPES.${row.valueType}`) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-if="$hasPermissions(['cms:resource:update'])"
|
||||||
|
prop="disabled"
|
||||||
|
label="是否启用"
|
||||||
|
min-width="80px"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-switch
|
||||||
|
v-model="row.disabled"
|
||||||
|
:active-value="false"
|
||||||
|
:inactive-value="true"
|
||||||
|
@change="switchDisabled(row)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
v-else
|
||||||
|
prop="disabled"
|
||||||
|
label="状态"
|
||||||
|
min-width="80px"
|
||||||
|
>
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{$filters.disabledText(row.disabled)}}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="config" label="资源配置" min-width="200px">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<PopoverCellValue :content="row.description"/>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="creatorRealName" label="创建人" min-width="100px"/>
|
||||||
|
<el-table-column prop="createdAt" label="创建时间" min-width="185px"/>
|
||||||
|
<el-table-column prop="updaterRealName" label="更新人" min-width="100px"/>
|
||||||
|
<el-table-column prop="updatedAt" label="更新时间" min-width="185px"/>
|
||||||
|
<template
|
||||||
|
v-if="$hasAnyPermissions([
|
||||||
|
'cms:resource:update',
|
||||||
|
'cms:resource:delete'
|
||||||
|
])"
|
||||||
|
#buttons="{ row }"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
icon="EditPen"
|
||||||
|
v-permissions="['cms:resource:update']"
|
||||||
|
@click="$refs.operaResourceWindow.open(`编辑${row.title}`, group, row)"
|
||||||
|
link
|
||||||
|
>编辑</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
icon="Delete"
|
||||||
|
v-permissions="['cms:resource:delete']"
|
||||||
|
@click="deleteById(row)"
|
||||||
|
link
|
||||||
|
>删除</el-button>
|
||||||
|
</template>
|
||||||
|
</SearchTable>
|
||||||
|
<pagination
|
||||||
|
:pagination="tableData.pagination"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<!-- 新建编辑窗口 -->
|
||||||
|
<OperaResourceWindow
|
||||||
|
ref="operaResourceWindow"
|
||||||
|
@success="refresh"
|
||||||
|
/>
|
||||||
|
</TableLayout>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseTable from '@/components/base/BaseTable'
|
||||||
|
import OperaResourceWindow from '@/components/cms/resource/OperaResourceWindow'
|
||||||
|
import ResourcePreview from '@/components/cms/resource/preview/ResourcePreview'
|
||||||
|
import { updateStatus } from '@/api/cms/resource'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ResourceManageWindow',
|
||||||
|
extends: BaseTable,
|
||||||
|
components: { ResourcePreview, OperaResourceWindow },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
visible: false,
|
||||||
|
group: null,
|
||||||
|
// 搜索
|
||||||
|
searchForm: {
|
||||||
|
groupId: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param group 资源组对象
|
||||||
|
*/
|
||||||
|
open (group) {
|
||||||
|
this.group = group
|
||||||
|
this.searchForm.groupId = group.id
|
||||||
|
this.visible = true
|
||||||
|
this.search()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 启用/禁用
|
||||||
|
*
|
||||||
|
* @param row 行对象
|
||||||
|
*/
|
||||||
|
switchDisabled (row) {
|
||||||
|
const newValue = row.disabled
|
||||||
|
row.disabled = !row.disabled
|
||||||
|
// 开启
|
||||||
|
if (!newValue) {
|
||||||
|
this.__updateStatus(row, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 禁用
|
||||||
|
this.$dialog.disableConfirm(`确认禁用 ${row.title} 资源吗?`)
|
||||||
|
.then(() => {
|
||||||
|
this.__updateStatus(row, newValue)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 修改状态
|
||||||
|
*
|
||||||
|
* @param row 行对象
|
||||||
|
* @param newValue 新值
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__updateStatus (row, newValue) {
|
||||||
|
updateStatus({
|
||||||
|
id: row.id,
|
||||||
|
disabled: newValue
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
row.disabled = newValue
|
||||||
|
this.$tip.apiSuccess('修改成功')
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
module: '资源',
|
||||||
|
api: await import('@/api/cms/resource')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
15
src/components/cms/resource/input/ImageInput.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<ImageUploader preview-height="450px"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import ImageUploader from '@/components/common/ImageUploader'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ImageInput',
|
||||||
|
components: {
|
||||||
|
ImageUploader
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
84
src/components/cms/resource/input/ImageLinkInput.vue
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<template>
|
||||||
|
<el-form ref="form" class="image-link-input-form" :model="modelValueObj" :rules="rules">
|
||||||
|
<el-form-item label="资源链接" prop="link" required>
|
||||||
|
<LinkInput v-model="modelValueObj.link" @change="handleChange"/>
|
||||||
|
<FormItemTip>资源链接可为相对路径,也可以为绝对路径!</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="资源图片" prop="image" required>
|
||||||
|
<ImageInput v-model="modelValueObj.image" @change="handleChange"/>
|
||||||
|
<FormItemTip v-if="imageTip != null && imageTip !== ''">{{ imageTip }}</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import LinkInput from './LinkInput'
|
||||||
|
import ImageInput from './ImageInput'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ImageLinkInput',
|
||||||
|
components: { ImageInput, LinkInput },
|
||||||
|
props: {
|
||||||
|
// 值,格式为{ image: '', link: '' }
|
||||||
|
modelValue: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
// 图片提示
|
||||||
|
imageTip: {
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
modelValueObj: {
|
||||||
|
image: '',
|
||||||
|
link: ''
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
image: [
|
||||||
|
{ required: true, message: '请上传图片', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
link: [
|
||||||
|
{ required: true, message: '请输入链接地址', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
modelValue: {
|
||||||
|
immediate: true,
|
||||||
|
handler () {
|
||||||
|
this.initModelValueObj()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 初始化值对象
|
||||||
|
initModelValueObj () {
|
||||||
|
this.modelValueObj.image = null
|
||||||
|
this.modelValueObj.link = null
|
||||||
|
if (this.modelValue != null && this.modelValue.length > 0) {
|
||||||
|
const modelValueObj = JSON.parse(this.modelValue)
|
||||||
|
this.modelValueObj.image = modelValueObj.image
|
||||||
|
this.modelValueObj.link = modelValueObj.link
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChange () {
|
||||||
|
this.$emit('update:modelValue', JSON.stringify(this.modelValueObj))
|
||||||
|
},
|
||||||
|
validate () {
|
||||||
|
return this.$refs.form.validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.image-link-input-form {
|
||||||
|
padding: 0 20px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
9
src/components/cms/resource/input/LinkInput.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<el-input placeholder="请输入资源链接"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'LinkInput'
|
||||||
|
}
|
||||||
|
</script>
|
46
src/components/cms/resource/input/ResourceInput.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template>
|
||||||
|
<component ref="component" :is="component" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const map = {
|
||||||
|
IMAGE: 'ImageInput',
|
||||||
|
LINK: 'LinkInput',
|
||||||
|
IMAGE_LINK: 'ImageLinkInput'
|
||||||
|
}
|
||||||
|
import ImageInput from './ImageInput'
|
||||||
|
import LinkInput from './LinkInput'
|
||||||
|
import ImageLinkInput from './ImageLinkInput'
|
||||||
|
export default {
|
||||||
|
name: 'ResourceInput',
|
||||||
|
components: {
|
||||||
|
ImageInput,
|
||||||
|
LinkInput,
|
||||||
|
ImageLinkInput
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 资源类型
|
||||||
|
type: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
component () {
|
||||||
|
let targetComponent = this.$options.components[map[this.type]]
|
||||||
|
if (targetComponent == null) {
|
||||||
|
throw new Error(`未能匹配资源类型 ${this.type}`)
|
||||||
|
}
|
||||||
|
return targetComponent
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 输入验证
|
||||||
|
validate () {
|
||||||
|
if (this.$refs.component.validate) {
|
||||||
|
return this.$refs.component.validate()
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
59
src/components/cms/resource/preview/ImageLinkPreview.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="image-link-preview">
|
||||||
|
<el-image
|
||||||
|
:src="$getImageURL(valueObj.image)"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:max-scale="3"
|
||||||
|
:min-scale="0.5"
|
||||||
|
:preview-src-list="[$getImageURL(valueObj.image)]"
|
||||||
|
:preview-teleported="true"
|
||||||
|
:hide-on-click-modal="true"
|
||||||
|
:initial-index="0"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
<p>{{ valueObj.link }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ImageLinkPreview',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
valueObj () {
|
||||||
|
return JSON.parse(this.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.image-link-preview {
|
||||||
|
width: 150px;
|
||||||
|
height: 120px;
|
||||||
|
position: relative;
|
||||||
|
.el-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
color: #fff;
|
||||||
|
padding: 3px 5px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
32
src/components/cms/resource/preview/ImagePreview.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<template>
|
||||||
|
<el-image
|
||||||
|
class="resource-image-preview"
|
||||||
|
:src="$getImageURL(value)"
|
||||||
|
:zoom-rate="1.2"
|
||||||
|
:max-scale="3"
|
||||||
|
:min-scale="0.5"
|
||||||
|
:preview-src-list="[$getImageURL(value)]"
|
||||||
|
:preview-teleported="true"
|
||||||
|
:hide-on-click-modal="true"
|
||||||
|
:initial-index="0"
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ImagePreview',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.resource-image-preview {
|
||||||
|
width: 150px;
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
</style>
|
19
src/components/cms/resource/preview/LinkPreview.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<a :href="value" target="_blank">{{ value }}</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'LinkPreview',
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
38
src/components/cms/resource/preview/ResourcePreview.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<template v-if="component == null">错误类型数据</template>
|
||||||
|
<component v-else ref="component" :is="component" :value="value"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const map = {
|
||||||
|
IMAGE: 'ImagePreview',
|
||||||
|
LINK: 'LinkPreview',
|
||||||
|
IMAGE_LINK: 'ImageLinkPreview'
|
||||||
|
}
|
||||||
|
import ImagePreview from './ImagePreview'
|
||||||
|
import LinkPreview from './LinkPreview'
|
||||||
|
import ImageLinkPreview from './ImageLinkPreview'
|
||||||
|
export default {
|
||||||
|
name: 'ResourcePreview',
|
||||||
|
components: {
|
||||||
|
ImagePreview,
|
||||||
|
LinkPreview,
|
||||||
|
ImageLinkPreview
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 资源类型
|
||||||
|
type: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
// 资源值
|
||||||
|
value: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
component () {
|
||||||
|
return this.$options.components[map[this.type]]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
150
src/components/cms/template/OperaCmsTemplateWindow.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<!-- 获取验证字段 -->
|
||||||
|
<template>
|
||||||
|
<GlobalWindow
|
||||||
|
:title="title"
|
||||||
|
v-model:visible="visible"
|
||||||
|
:confirm-working="isWorking"
|
||||||
|
width="750px"
|
||||||
|
@confirm="confirm"
|
||||||
|
>
|
||||||
|
<el-form :model="form" ref="form" :rules="rules">
|
||||||
|
<el-form-item label="模板名称" prop="name" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.name"
|
||||||
|
placeholder="请输入模板名称"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="模板文件路径" prop="path" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.path"
|
||||||
|
placeholder="请输入模板路径"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
<FormItemTip>
|
||||||
|
模板文件路径也是模板的唯一标识,用于查询模板的相关配置。
|
||||||
|
<a href="https://www.yuque.com/u21334242/eva-cms/wz7s8t6cmd9t4pg1" target="_blank">查看更详细的说明</a>
|
||||||
|
</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="访问路径" prop="accessUri" required>
|
||||||
|
<el-input
|
||||||
|
v-model="form.accessUri"
|
||||||
|
placeholder="如:/article/${articleUid}"
|
||||||
|
v-trim
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
<FormItemTip>
|
||||||
|
访问路径将使用freemarker解析,作为挂载该模板数据的访问地址。
|
||||||
|
<a href="https://www.yuque.com/u21334242/eva-cms/wz7s8t6cmd9t4pg1" target="_blank">查看更详细的说明</a>
|
||||||
|
</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="visible" label="模板数据" prop="presetDataCodes">
|
||||||
|
<DictSelect
|
||||||
|
code="TEMPLATE_DATA_LIST"
|
||||||
|
v-model="form.presetDataCodes"
|
||||||
|
placeholder="请选择模板数据"
|
||||||
|
:multiple="true"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
type="textarea"
|
||||||
|
:rows="3"
|
||||||
|
v-model="form.remark"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
v-trim
|
||||||
|
show-word-limit
|
||||||
|
maxlength="200"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</GlobalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseOpera from '@/components/base/BaseOpera'
|
||||||
|
import GlobalWindow from '@/components/common/GlobalWindow'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OperaCmsTemplateWindow',
|
||||||
|
extends: BaseOpera,
|
||||||
|
components: {
|
||||||
|
GlobalWindow
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// 表单数据
|
||||||
|
form: {
|
||||||
|
id: null,
|
||||||
|
name: '',
|
||||||
|
path: '',
|
||||||
|
accessUri: '',
|
||||||
|
presetDataCodes: [],
|
||||||
|
remark: ''
|
||||||
|
},
|
||||||
|
// 验证规则
|
||||||
|
rules: {
|
||||||
|
name: [
|
||||||
|
{ required: true, message: '请输入模板名称' }
|
||||||
|
],
|
||||||
|
path: [
|
||||||
|
{ required: true, message: '请输入模板文件路径' }
|
||||||
|
],
|
||||||
|
accessUri: [
|
||||||
|
{ required: true, message: '请输入访问路径' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* 打开窗口
|
||||||
|
*
|
||||||
|
* @param title 窗口标题
|
||||||
|
* @param target 行对象(仅编辑需该参数)
|
||||||
|
*/
|
||||||
|
open (title, target) {
|
||||||
|
this.title = title
|
||||||
|
this.visible = true
|
||||||
|
// 新建
|
||||||
|
if (target == null) {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.form.resetFields()
|
||||||
|
this.form[this.configData['field.id']] = null
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
this.$nextTick(() => {
|
||||||
|
for (const key in this.form) {
|
||||||
|
this.form[key] = target[key]
|
||||||
|
}
|
||||||
|
// 初始化数据编码
|
||||||
|
this.form.presetDataCodes = target.presetDataCodes == null || target.presetDataCodes === '' ?
|
||||||
|
[] : target.presetDataCodes.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取新增编辑表单对象
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
__getForm () {
|
||||||
|
const form = JSON.parse(JSON.stringify(this.form))
|
||||||
|
// 将预设数据的编码集转为字符串,并在前后增加“,”,便于模糊搜索
|
||||||
|
form.presetDataCodes = ',' + form.presetDataCodes.join(',') + ','
|
||||||
|
return form
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
this.config({
|
||||||
|
api: await import('@/api/cms/template'),
|
||||||
|
'field.id': 'id'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
251
src/components/cms/template/TemplateSelect.vue
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<div class="template-select">
|
||||||
|
<el-select
|
||||||
|
:class="{select__block: !inline}"
|
||||||
|
:model-value="modelValue"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:clearable="clearable"
|
||||||
|
:disabled="disabled"
|
||||||
|
:filterable="true"
|
||||||
|
:filter-method="filterMethod"
|
||||||
|
@change="handleChange"
|
||||||
|
@update:modelValue="$emit('update:modelValue', $event)"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="data in computedList"
|
||||||
|
:key="data.id"
|
||||||
|
:value="data.id"
|
||||||
|
:label="data.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
<div v-if="settingTableData.length > 0" class="setting-wrap">
|
||||||
|
<h3>设置模板参数</h3>
|
||||||
|
<el-table :data="settingTableData" row-key="name">
|
||||||
|
<el-table-column label="数据项" prop="label" width="100px"/>
|
||||||
|
<el-table-column label="数据所需参数设置">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-form :ref="`${row.name}ParameterForm`" :model="row.form" :rules="row.rules">
|
||||||
|
<el-form-item
|
||||||
|
v-for="parameter in row.parameters"
|
||||||
|
:key="parameter.name"
|
||||||
|
:label="parameter.label"
|
||||||
|
:prop="parameter.name"
|
||||||
|
:required="parameter.required"
|
||||||
|
>
|
||||||
|
<TemplateParameterInput
|
||||||
|
:type="parameter.component"
|
||||||
|
v-model="row.form[parameter.name]"
|
||||||
|
@change="emitUpdateParameters"
|
||||||
|
/>
|
||||||
|
<FormItemTip v-if="!parameter.required">如不填写,需在获取模板数据时传入该参数!</FormItemTip>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import TemplateParameterInput from '@/components/cms/template/parameter-input'
|
||||||
|
import { fetchAll } from '@/api/cms/template'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TemplateSelect',
|
||||||
|
components: { TemplateParameterInput },
|
||||||
|
props: {
|
||||||
|
// 模板ID
|
||||||
|
modelValue: {},
|
||||||
|
// 模板参数
|
||||||
|
parameters: {},
|
||||||
|
disabled: {
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
default: '请选择模板'
|
||||||
|
},
|
||||||
|
inline: {
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
clearable: {
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
filterWord: '',
|
||||||
|
list: [],
|
||||||
|
// 模板数据设置
|
||||||
|
settingTableData: [],
|
||||||
|
// 设置表单
|
||||||
|
settingForm: {},
|
||||||
|
// 设置表单规则
|
||||||
|
settingRules: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedList () {
|
||||||
|
if (this.filterWord.trim() === '') {
|
||||||
|
return this.list
|
||||||
|
}
|
||||||
|
return this.list.filter(item => item.name.indexOf(this.filterWord) !== -1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 过滤
|
||||||
|
filterMethod (kwd) {
|
||||||
|
this.filterWord = kwd
|
||||||
|
},
|
||||||
|
// 查询数据
|
||||||
|
fetchList () {
|
||||||
|
fetchAll()
|
||||||
|
.then(data => {
|
||||||
|
this.list = data
|
||||||
|
// 构建数据项配置参数表格
|
||||||
|
this.buildSettingTable(this.modelValue)
|
||||||
|
// 初始化数据项配置参数值
|
||||||
|
this.initSettingValues(this.parameters)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.$tip.apiFailed(e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 处理change事件
|
||||||
|
handleChange (evt) {
|
||||||
|
this.buildSettingTable(evt)
|
||||||
|
this.$emit('change', evt)
|
||||||
|
this.emitUpdateParameters()
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 构建参数设置表
|
||||||
|
* 1. 获取模板所需的数据项编码集
|
||||||
|
* 2. 循环数据项编码集,根据编码从字典中取出各个数据项的参数配置
|
||||||
|
* 3. 将编码和参数配置构建成对象写入settingTableData中
|
||||||
|
*
|
||||||
|
* @param templateId 模板唯一标识
|
||||||
|
* @returns {null}
|
||||||
|
*/
|
||||||
|
buildSettingTable (templateId) {
|
||||||
|
if (templateId == null) {
|
||||||
|
this.settingTableData = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const template = this.list.find(item => item.id === templateId)
|
||||||
|
// 1. 获取模板所需的数据项编码集
|
||||||
|
const dataCodes = template.presetDataCodes == null || template.presetDataCodes.trim() === '' ?
|
||||||
|
[] : template.presetDataCodes
|
||||||
|
.split(',')
|
||||||
|
.filter(item => item.trim() !== '')
|
||||||
|
// - 模板未配置任何数据项,无需设置参数
|
||||||
|
if (dataCodes == null || dataCodes.length === 0) {
|
||||||
|
this.settingTableData = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 循环数据项编码集,根据编码从字典中取出各个数据项的参数配置
|
||||||
|
this.settingTableData = dataCodes
|
||||||
|
.map(dataCode => {
|
||||||
|
// 2.1 从字典中获取到当前数据项的参数列表
|
||||||
|
let dictDataConfig = this.$dc(`TEMPLATE_DATA_LIST.${dataCode}`)
|
||||||
|
if (dictDataConfig == null || dictDataConfig === '') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
dictDataConfig = JSON.parse(dictDataConfig)
|
||||||
|
const dataItemParameters = dictDataConfig.parameters
|
||||||
|
// 2.2 根据数据项所需参数,构建参数列表和参数值表单
|
||||||
|
const parameters = []
|
||||||
|
const form = {}
|
||||||
|
const rules = {}
|
||||||
|
// - dataItemParameter: { name: 参数名, label: 参数显示名称, component: 参数输入组件名称, required: 是否必填 }
|
||||||
|
for (const dataItemParameter of dataItemParameters) {
|
||||||
|
parameters.push(dataItemParameter)
|
||||||
|
// 配置值设置为空
|
||||||
|
form[dataItemParameter.name] = null
|
||||||
|
// 必填项
|
||||||
|
if (dataItemParameter.required) {
|
||||||
|
rules[dataItemParameter.name] = [
|
||||||
|
{ required: true, message: `${dataItemParameter.label}不能为空` }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parameters.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// 2.3 组装数据项的参数配置列表
|
||||||
|
return {
|
||||||
|
name: dataCode,
|
||||||
|
label: this.$d(`TEMPLATE_DATA_LIST.${dataCode}`),
|
||||||
|
parameters,
|
||||||
|
form,
|
||||||
|
rules
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(item => item != null)
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化设置值
|
||||||
|
*
|
||||||
|
* @param parameters 预设数据的参数
|
||||||
|
*/
|
||||||
|
initSettingValues (parameters) {
|
||||||
|
for (const dataItem in parameters) {
|
||||||
|
const targetSettingRow = this.settingTableData.find(item => item.name === dataItem)
|
||||||
|
if (targetSettingRow == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const dataItemParameters = parameters[dataItem]
|
||||||
|
for (const p of dataItemParameters) {
|
||||||
|
targetSettingRow.form[p.name] = p.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 触发参数更新
|
||||||
|
emitUpdateParameters () {
|
||||||
|
if (this.settingTableData.length === 0) {
|
||||||
|
this.$emit('update:parameters', null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const parameters = {}
|
||||||
|
for (const dataItem of this.settingTableData) {
|
||||||
|
parameters[dataItem.name] = dataItem.parameters.map(parameter => {
|
||||||
|
return {
|
||||||
|
name: parameter.name,
|
||||||
|
value: dataItem.form[parameter.name]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.$emit('update:parameters', parameters)
|
||||||
|
},
|
||||||
|
// 验证表单
|
||||||
|
validateForm () {
|
||||||
|
const validatePromises = []
|
||||||
|
for (const dateItem of this.settingTableData) {
|
||||||
|
validatePromises.push(this.$refs[`${dateItem.name}ParameterForm`].validate())
|
||||||
|
}
|
||||||
|
return Promise.all(validatePromises)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fetchList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.template-select {
|
||||||
|
& > .el-select {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.setting-wrap {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: var(--background-color);
|
||||||
|
h3 {
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: var(--font-size);
|
||||||
|
}
|
||||||
|
.el-form-item {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
14
src/components/cms/template/parameter-input/ArticleInput.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<ArticleSelect
|
||||||
|
:multiple="false"
|
||||||
|
value-key="uid"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ArticleSelect from '@/components/cms/article/ArticleSelect'
|
||||||
|
export default {
|
||||||
|
name: 'ArticleInput',
|
||||||
|
components: { ArticleSelect }
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<CategorySelect
|
||||||
|
:multiple="false"
|
||||||
|
value-key="uid"
|
||||||
|
:clearable="true"
|
||||||
|
:check-on-click-node="false"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CategorySelect from '@/components/cms/category/CategorySelect'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'CategoryInput',
|
||||||
|
components: {CategorySelect}
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,17 @@
|
|||||||
|
<template>
|
||||||
|
<CategorySelect
|
||||||
|
:multiple="true"
|
||||||
|
:clearable="true"
|
||||||
|
:check-on-click-node="false"
|
||||||
|
value-key="uid"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import CategorySelect from '@/components/cms/category/CategorySelect'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MultipleCategoryInput',
|
||||||
|
components: { CategorySelect }
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,12 @@
|
|||||||
|
<template>
|
||||||
|
<ResourceGroupSelect value-key="uid" :multiple="true"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ResourceGroupSelect from '@/components/cms/resource/ResourceGroupSelect'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'MultipleResourceGroupInput',
|
||||||
|
components: {ResourceGroupSelect}
|
||||||
|
}
|
||||||
|
</script>
|
14
src/components/cms/template/parameter-input/NumberInput.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<template>
|
||||||
|
<el-input-number placeholder="请输入值" :step="1" :controls="false"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'NumberInput'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
10
src/components/cms/template/parameter-input/TextInput.vue
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<template>
|
||||||
|
<el-input placeholder="请输入值"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TextInput'
|
||||||
|
}
|
||||||
|
</script>
|
38
src/components/cms/template/parameter-input/index.vue
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<component ref="component" :is="component"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ArticleInput from './ArticleInput'
|
||||||
|
import MultipleResourceGroupInput from './MultipleResourceGroupInput'
|
||||||
|
import CategoryInput from './CategoryInput'
|
||||||
|
import MultipleCategoryInput from './MultipleCategoryInput'
|
||||||
|
import TextInput from './TextInput'
|
||||||
|
import NumberInput from './NumberInput'
|
||||||
|
export default {
|
||||||
|
name: 'TemplateParameterInput',
|
||||||
|
components: {
|
||||||
|
ArticleInput,
|
||||||
|
MultipleResourceGroupInput,
|
||||||
|
CategoryInput,
|
||||||
|
MultipleCategoryInput,
|
||||||
|
TextInput,
|
||||||
|
NumberInput,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
// 资源类型
|
||||||
|
type: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
component () {
|
||||||
|
let targetComponent = this.$options.components[this.type]
|
||||||
|
if (targetComponent == null) {
|
||||||
|
throw new Error(`未能匹配参数输入类型 ${this.type}`)
|
||||||
|
}
|
||||||
|
return targetComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
100
src/components/common/AttachUploader.vue
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<template>
|
||||||
|
<el-upload
|
||||||
|
class="attach-uploader"
|
||||||
|
action="/api/oss/upload/attach"
|
||||||
|
:accept="accept"
|
||||||
|
:show-file-list="false"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
>
|
||||||
|
<div v-if="modelValue" class="upload-info">
|
||||||
|
<Icon :value="iconClass"/>
|
||||||
|
<p>{{filename}}</p>
|
||||||
|
</div>
|
||||||
|
<el-icon v-else class="attach-uploader-icon">
|
||||||
|
<Plus/>
|
||||||
|
</el-icon>
|
||||||
|
</el-upload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AttachUploader',
|
||||||
|
props: {
|
||||||
|
// 接受上传的文件类型,同el-uploader accept
|
||||||
|
accept: {},
|
||||||
|
// 图标
|
||||||
|
iconClass: {
|
||||||
|
default: 'iconfont icon-file1'
|
||||||
|
},
|
||||||
|
// 文件的fileKey
|
||||||
|
modelValue: {},
|
||||||
|
// 文件名称
|
||||||
|
filename: {
|
||||||
|
default: '文件已上传'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// 处理文件上传成功
|
||||||
|
handleUploadSuccess (res) {
|
||||||
|
if (!res.success) {
|
||||||
|
this.$tip.apiFailed(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', res.data.fileKey)
|
||||||
|
this.$emit('success', res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.attach-uploader {
|
||||||
|
background-color: var(--background-color);
|
||||||
|
:deep(.el-upload) {
|
||||||
|
border: 1px dashed #eee;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
transition: all ease .15s;
|
||||||
|
&:hover {
|
||||||
|
border-color: #409EFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.attach-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
line-height: 178px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.upload-info {
|
||||||
|
height: 178px;
|
||||||
|
width: 178px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
.icon {
|
||||||
|
font-size: 45px !important;
|
||||||
|
color: var(--font-color-gray-deep);
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
color: var(--font-color-gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.attach {
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
67
src/components/common/AvatarUploader.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<el-upload
|
||||||
|
class="avatar-uploader"
|
||||||
|
action="/api/oss/upload/image"
|
||||||
|
accept=".jpg,.jpeg,.png"
|
||||||
|
:show-file-list="false"
|
||||||
|
:on-success="handleUploadSuccess"
|
||||||
|
>
|
||||||
|
<img v-if="modelValue" :src="$getImageURL(modelValue)" class="avatar" alt="头像">
|
||||||
|
<el-icon v-else class="avatar-uploader-icon">
|
||||||
|
<Plus/>
|
||||||
|
</el-icon>
|
||||||
|
</el-upload>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AvatarUploader',
|
||||||
|
props: {
|
||||||
|
// 头像fileKey
|
||||||
|
modelValue: {}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
// 处理头像上传成功
|
||||||
|
handleUploadSuccess (res) {
|
||||||
|
if (!res.success) {
|
||||||
|
this.$tip.apiFailed(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('update:modelValue', res.data.fileKey)
|
||||||
|
this.$emit('success', res.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.avatar-uploader {
|
||||||
|
:deep(.el-upload) {
|
||||||
|
border: 1px dashed #d9d9d9;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
&:hover {
|
||||||
|
border-color: #409EFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.avatar-uploader-icon {
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8c939d;
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
line-height: 178px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 178px;
|
||||||
|
height: 178px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
52
src/components/common/ColorsMarking.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="colors-marking">
|
||||||
|
<li
|
||||||
|
v-for="item in computedData"
|
||||||
|
:key="item.label"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:style="`border: 1px solid ${item.borderColor}; background-color: ${item.backgroundColor}`"
|
||||||
|
></span>
|
||||||
|
<label>{{ item.label }}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseComponent from '@/components/BaseComponent'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ColorsMarking',
|
||||||
|
extends: BaseComponent,
|
||||||
|
props: {
|
||||||
|
data: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
computedData () {
|
||||||
|
return this.computeProp('data')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.colors-marking {
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
& > li {
|
||||||
|
font-size: 13px;
|
||||||
|
margin-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
& > span {
|
||||||
|
display: inline-block;
|
||||||
|
content: '';
|
||||||
|
width: 36px;
|
||||||
|
height: 14px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
35
src/components/common/DataLoading.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<div class="loading">
|
||||||
|
<el-icon class="is-loading" :size="iconSize">
|
||||||
|
<Loading/>
|
||||||
|
</el-icon>
|
||||||
|
<div class="loading-text__wrap"><slot></slot></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DataLoading',
|
||||||
|
props: {
|
||||||
|
// 图标大小
|
||||||
|
iconSize: {
|
||||||
|
default: '20px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.loading {
|
||||||
|
padding: 40px 0;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
.loading-text__wrap {
|
||||||
|
color: #999;
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
31
src/components/common/DictCheckboxGroup.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<el-checkbox-group
|
||||||
|
:model-value="value"
|
||||||
|
:disabled="disabled"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<el-checkbox
|
||||||
|
v-for="data in dataList"
|
||||||
|
:key="data.value"
|
||||||
|
:value="data.value"
|
||||||
|
>{{ data.label }}</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseDict from '@/components/base/BaseDict'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DictCheckboxGroup',
|
||||||
|
extends: BaseDict,
|
||||||
|
props: {
|
||||||
|
value: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|
27
src/components/common/DictRadioGroup.vue
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<el-radio-group
|
||||||
|
class="dict-radio-group"
|
||||||
|
:value="value"
|
||||||
|
:disabled="disabled"
|
||||||
|
@input="$emit('input', $event)"
|
||||||
|
>
|
||||||
|
<el-radio
|
||||||
|
v-for="data of dataList"
|
||||||
|
:key="data.value"
|
||||||
|
:value="data.value"
|
||||||
|
>{{ data.label }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseDict from '@/components/base/BaseDict'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DictRadioGroup',
|
||||||
|
extends: BaseDict,
|
||||||
|
props: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
</style>
|