初始化仓库
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>
|