diff --git a/.env b/.env new file mode 100644 index 0000000..5e6d56e --- /dev/null +++ b/.env @@ -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 + diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..f2df6ee --- /dev/null +++ b/.env.development @@ -0,0 +1,8 @@ +# 开发环境配置 +VITE_APP_NODE_ENV = 'development' + +# 运行模式 +VITE_APP_MODE = 'testing' + +# 是否开启DEBUG模式 +VITE_APP_DEBUG = 'on' diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..00e039c --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +# 生产环境配置 +VITE_APP_NODE_ENV = 'production' diff --git a/.env.staging b/.env.staging new file mode 100644 index 0000000..1494414 --- /dev/null +++ b/.env.staging @@ -0,0 +1,2 @@ +# 测试环境配置 +VITE_APP_NODE_ENV = 'production' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c4087f --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.kit/kit.json b/.kit/kit.json new file mode 100644 index 0000000..d8624b6 --- /dev/null +++ b/.kit/kit.json @@ -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>${')\n .replace(/\\#\\{/g, '<#noparse>#{')\n}\n\n// login.vue内容替换\nif (filename === 'login.vue') {\n content = content\n .replace(/

.*<\\/h2>/, '

${loginTitle}

')\n .replace(/

.*<\\/h3>/, '

${loginDescription}

')\n}\n\n// index.html内容替换\nif (filename === 'index.html') {\n content = content\n .replace(/.*<\\/title>/, '<title>${loginTitle}')\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": "
\n \n

伊娃CMS后台前端

\n
\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![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/1.png)\n\n**文章管理**\n![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/2.png)\n\n**栏目管理**\n![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/3.png)\n\n**资源管理**\n![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/4.png)\n![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/5.png)\n\n**模板管理**\n![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/6.png)\n" +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6bbdece --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md index 6cce41f..64a93fa 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ -# nankai-cms-admin +
+ +

伊娃CMS后台前端

+
+## 在线演示 & 技术文档 + +- 演示地址/政企门户主题:[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等。 + +## 项目预览 + +**登录页** +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/1.png) + +**文章管理** +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/2.png) + +**栏目管理** +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/3.png) + +**资源管理** +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/4.png) +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/5.png) + +**模板管理** +![输入图片说明](https://adjustrd-public.oss-cn-shenzhen.aliyuncs.com/eva-cms/6.png) diff --git a/index.html b/index.html new file mode 100644 index 0000000..d291270 --- /dev/null +++ b/index.html @@ -0,0 +1,15 @@ + + + + + + + + + 伊娃CMS-轻量级CMS系统 + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..8056315 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/public/avatar/man.png b/public/avatar/man.png new file mode 100644 index 0000000..a84beb9 Binary files /dev/null and b/public/avatar/man.png differ diff --git a/public/avatar/woman.png b/public/avatar/woman.png new file mode 100644 index 0000000..eef8695 Binary files /dev/null and b/public/avatar/woman.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..c6ff5db Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/icons/cms/demo.css b/public/icons/cms/demo.css new file mode 100644 index 0000000..a67054a --- /dev/null +++ b/public/icons/cms/demo.css @@ -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; +} diff --git a/public/icons/cms/demo_index.html b/public/icons/cms/demo_index.html new file mode 100644 index 0000000..bddab79 --- /dev/null +++ b/public/icons/cms/demo_index.html @@ -0,0 +1,1361 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
+

+ + +

+ +
+
+
    + +
  • + +
    首页
    +
    &#xe637;
    +
  • + +
  • + +
    机构
    +
    &#xe6ae;
    +
  • + +
  • + +
    首页
    +
    &#xe627;
    +
  • + +
  • + +
    首页
    +
    &#xe638;
    +
  • + +
  • + +
    合作
    +
    &#xe65a;
    +
  • + +
  • + +
    组织机构
    +
    &#xe602;
    +
  • + +
  • + +
    专题
    +
    &#xe87a;
    +
  • + +
  • + +
    组织机构
    +
    &#xe620;
    +
  • + +
  • + +
    首页
    +
    &#xe610;
    +
  • + +
  • + +
    合作/招商
    +
    &#xe606;
    +
  • + +
  • + +
    组织机构
    +
    &#xe7ca;
    +
  • + +
  • + +
    专题
    +
    &#xe671;
    +
  • + +
  • + +
    公开
    +
    &#xe63f;
    +
  • + +
  • + +
    动态
    +
    &#xe612;
    +
  • + +
  • + +
    合作
    +
    &#xe777;
    +
  • + +
  • + +
    动态
    +
    &#xe672;
    +
  • + +
  • + +
    首页
    +
    &#xe628;
    +
  • + +
  • + +
    专题
    +
    &#xe607;
    +
  • + +
  • + +
    合作
    +
    &#xe61a;
    +
  • + +
  • + +
    专题
    +
    &#xe61e;
    +
  • + +
  • + +
    首页
    +
    &#xe669;
    +
  • + +
  • + +
    政策
    +
    &#xe61d;
    +
  • + +
  • + +
    政策
    +
    &#xe65c;
    +
  • + +
  • + +
    首页
    +
    &#xe601;
    +
  • + +
  • + +
    政策
    +
    &#xe667;
    +
  • + +
  • + +
    政策
    +
    &#xe609;
    +
  • + +
  • + +
    政策
    +
    &#xe60a;
    +
  • + +
  • + +
    首页
    +
    &#xe617;
    +
  • + +
  • + +
    政府
    +
    &#xe753;
    +
  • + +
  • + +
    合作
    +
    &#xebc9;
    +
  • + +
  • + +
    所有栏目列表
    +
    &#xe625;
    +
  • + +
  • + +
    +
    &#xe6a4;
    +
  • + +
  • + +
    新建下级栏目
    +
    &#xe6a7;
    +
  • + +
  • + +
    栏目管理
    +
    &#xe63b;
    +
  • + +
  • + +
    服务
    +
    &#xe6d8;
    +
  • + +
  • + +
    文章
    +
    &#xe624;
    +
  • + +
  • + +
    商品
    +
    &#xe67b;
    +
  • + +
  • + +
    模板
    +
    &#xe60f;
    +
  • + +
  • + +
    素材
    +
    &#xe66d;
    +
  • + +
  • + +
    模板
    +
    &#xe666;
    +
  • + +
  • + +
    内容
    +
    &#xe890;
    +
  • + +
  • + +
    内容字段管理
    +
    &#xe6b2;
    +
  • + +
  • + +
    内容
    +
    &#xe681;
    +
  • + +
  • + +
    我的文章
    +
    &#xe7d3;
    +
  • + +
  • + +
    素材
    +
    &#xe665;
    +
  • + +
  • + +
    文章
    +
    &#xe615;
    +
  • + +
  • + +
    内容
    +
    &#xe616;
    +
  • + +
  • + +
    服务
    +
    &#xe694;
    +
  • + +
  • + +
    素材资源
    +
    &#xe7ef;
    +
  • + +
  • + +
    服务
    +
    &#xe636;
    +
  • + +
  • + +
    商品
    +
    &#xe61f;
    +
  • + +
+
+

Unicode 引用

+
+ +

Unicode 是字体在网页端最原始的应用方式,特点是:

+
    +
  • 支持按字体的方式去动态调整图标大小,颜色等等。
  • +
  • 默认情况下不支持多色,直接添加多色图标会自动去色。
  • +
+
+

注意:新版 iconfont 支持两种方式引用多色图标:SVG symbol 引用方式和彩色字体图标模式。(使用彩色字体图标需要在「编辑项目」中开启「彩色」选项后并重新生成。)

+
+

Unicode 使用步骤如下:

+

第一步:拷贝项目下面生成的 @font-face

+
@font-face {
+  font-family: 'cmsfont';
+  src: url('iconfont.woff2?t=1731151023913') format('woff2'),
+       url('iconfont.woff?t=1731151023913') format('woff'),
+       url('iconfont.ttf?t=1731151023913') format('truetype');
+}
+
+

第二步:定义使用 iconfont 的样式

+
.cmsfont {
+  font-family: "cmsfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+

第三步:挑选相应图标并获取字体编码,应用于页面

+
+<span class="cmsfont">&#x33;</span>
+
+
+

"cmsfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    + 首页 +
    +
    .cms-home1 +
    +
  • + +
  • + +
    + 机构 +
    +
    .cms-org1 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home2 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home3 +
    +
  • + +
  • + +
    + 合作 +
    +
    .cms-cooperation1 +
    +
  • + +
  • + +
    + 组织机构 +
    +
    .cms-org2 +
    +
  • + +
  • + +
    + 专题 +
    +
    .cms-special-subject1 +
    +
  • + +
  • + +
    + 组织机构 +
    +
    .cms-org3 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home4 +
    +
  • + +
  • + +
    + 合作/招商 +
    +
    .cms-cooperation3 +
    +
  • + +
  • + +
    + 组织机构 +
    +
    .cms-org4 +
    +
  • + +
  • + +
    + 专题 +
    +
    .cms-special-subject2 +
    +
  • + +
  • + +
    + 公开 +
    +
    .cms-public1 +
    +
  • + +
  • + +
    + 动态 +
    +
    .cms-dynamic2 +
    +
  • + +
  • + +
    + 合作 +
    +
    .cms-cooperation4 +
    +
  • + +
  • + +
    + 动态 +
    +
    .cms-dynamic3 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home5 +
    +
  • + +
  • + +
    + 专题 +
    +
    .cms-special-subject3 +
    +
  • + +
  • + +
    + 合作 +
    +
    .cms-cooperation5 +
    +
  • + +
  • + +
    + 专题 +
    +
    .cms-special-subject4 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home6 +
    +
  • + +
  • + +
    + 政策 +
    +
    .cms-policy1 +
    +
  • + +
  • + +
    + 政策 +
    +
    .cms-policy2 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home7 +
    +
  • + +
  • + +
    + 政策 +
    +
    .cms-policy3 +
    +
  • + +
  • + +
    + 政策 +
    +
    .cms-policy4 +
    +
  • + +
  • + +
    + 政策 +
    +
    .cms-policy5 +
    +
  • + +
  • + +
    + 首页 +
    +
    .cms-home8 +
    +
  • + +
  • + +
    + 政府 +
    +
    .cms-gov1 +
    +
  • + +
  • + +
    + 合作 +
    +
    .cms-cooperation2-copy +
    +
  • + +
  • + +
    + 所有栏目列表 +
    +
    .cms-category +
    +
  • + +
  • + +
    + 树 +
    +
    .cms-category1 +
    +
  • + +
  • + +
    + 新建下级栏目 +
    +
    .cms-category2 +
    +
  • + +
  • + +
    + 栏目管理 +
    +
    .cms-category3 +
    +
  • + +
  • + +
    + 服务 +
    +
    .cms-service +
    +
  • + +
  • + +
    + 文章 +
    +
    .cms-article +
    +
  • + +
  • + +
    + 商品 +
    +
    .cms-goods +
    +
  • + +
  • + +
    + 模板 +
    +
    .cms-template +
    +
  • + +
  • + +
    + 素材 +
    +
    .cms-material +
    +
  • + +
  • + +
    + 模板 +
    +
    .cms-template2 +
    +
  • + +
  • + +
    + 内容 +
    +
    .cms-content +
    +
  • + +
  • + +
    + 内容字段管理 +
    +
    .cms-content2 +
    +
  • + +
  • + +
    + 内容 +
    +
    .cms-content3 +
    +
  • + +
  • + +
    + 我的文章 +
    +
    .cms-article2 +
    +
  • + +
  • + +
    + 素材 +
    +
    .cms-material2 +
    +
  • + +
  • + +
    + 文章 +
    +
    .cms-article3 +
    +
  • + +
  • + +
    + 内容 +
    +
    .cms-content4 +
    +
  • + +
  • + +
    + 服务 +
    +
    .cms-service2 +
    +
  • + +
  • + +
    + 素材资源 +
    +
    .cms-material3 +
    +
  • + +
  • + +
    + 服务 +
    +
    .cms-service3 +
    +
  • + +
  • + +
    + 商品 +
    +
    .cms-goods2 +
    +
  • + +
+
+

font-class 引用

+
+ +

font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。

+

与 Unicode 使用方式相比,具有如下特点:

+
    +
  • 相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。
  • +
  • 因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 fontclass 代码:

+
<link rel="stylesheet" href="./iconfont.css">
+
+

第二步:挑选相应图标并获取类名,应用于页面:

+
<span class="cmsfont cms-xxx"></span>
+
+
+

" + cmsfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。

+
+
+
+
+
    + +
  • + +
    首页
    +
    #cms-home1
    +
  • + +
  • + +
    机构
    +
    #cms-org1
    +
  • + +
  • + +
    首页
    +
    #cms-home2
    +
  • + +
  • + +
    首页
    +
    #cms-home3
    +
  • + +
  • + +
    合作
    +
    #cms-cooperation1
    +
  • + +
  • + +
    组织机构
    +
    #cms-org2
    +
  • + +
  • + +
    专题
    +
    #cms-special-subject1
    +
  • + +
  • + +
    组织机构
    +
    #cms-org3
    +
  • + +
  • + +
    首页
    +
    #cms-home4
    +
  • + +
  • + +
    合作/招商
    +
    #cms-cooperation3
    +
  • + +
  • + +
    组织机构
    +
    #cms-org4
    +
  • + +
  • + +
    专题
    +
    #cms-special-subject2
    +
  • + +
  • + +
    公开
    +
    #cms-public1
    +
  • + +
  • + +
    动态
    +
    #cms-dynamic2
    +
  • + +
  • + +
    合作
    +
    #cms-cooperation4
    +
  • + +
  • + +
    动态
    +
    #cms-dynamic3
    +
  • + +
  • + +
    首页
    +
    #cms-home5
    +
  • + +
  • + +
    专题
    +
    #cms-special-subject3
    +
  • + +
  • + +
    合作
    +
    #cms-cooperation5
    +
  • + +
  • + +
    专题
    +
    #cms-special-subject4
    +
  • + +
  • + +
    首页
    +
    #cms-home6
    +
  • + +
  • + +
    政策
    +
    #cms-policy1
    +
  • + +
  • + +
    政策
    +
    #cms-policy2
    +
  • + +
  • + +
    首页
    +
    #cms-home7
    +
  • + +
  • + +
    政策
    +
    #cms-policy3
    +
  • + +
  • + +
    政策
    +
    #cms-policy4
    +
  • + +
  • + +
    政策
    +
    #cms-policy5
    +
  • + +
  • + +
    首页
    +
    #cms-home8
    +
  • + +
  • + +
    政府
    +
    #cms-gov1
    +
  • + +
  • + +
    合作
    +
    #cms-cooperation2-copy
    +
  • + +
  • + +
    所有栏目列表
    +
    #cms-category
    +
  • + +
  • + +
    +
    #cms-category1
    +
  • + +
  • + +
    新建下级栏目
    +
    #cms-category2
    +
  • + +
  • + +
    栏目管理
    +
    #cms-category3
    +
  • + +
  • + +
    服务
    +
    #cms-service
    +
  • + +
  • + +
    文章
    +
    #cms-article
    +
  • + +
  • + +
    商品
    +
    #cms-goods
    +
  • + +
  • + +
    模板
    +
    #cms-template
    +
  • + +
  • + +
    素材
    +
    #cms-material
    +
  • + +
  • + +
    模板
    +
    #cms-template2
    +
  • + +
  • + +
    内容
    +
    #cms-content
    +
  • + +
  • + +
    内容字段管理
    +
    #cms-content2
    +
  • + +
  • + +
    内容
    +
    #cms-content3
    +
  • + +
  • + +
    我的文章
    +
    #cms-article2
    +
  • + +
  • + +
    素材
    +
    #cms-material2
    +
  • + +
  • + +
    文章
    +
    #cms-article3
    +
  • + +
  • + +
    内容
    +
    #cms-content4
    +
  • + +
  • + +
    服务
    +
    #cms-service2
    +
  • + +
  • + +
    素材资源
    +
    #cms-material3
    +
  • + +
  • + +
    服务
    +
    #cms-service3
    +
  • + +
  • + +
    商品
    +
    #cms-goods2
    +
  • + +
+
+

Symbol 引用

+
+ +

这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇文章 + 这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:

+
    +
  • 支持多色图标了,不再受单色限制。
  • +
  • 通过一些技巧,支持像字体那样,通过 font-size, color 来调整样式。
  • +
  • 兼容性较差,支持 IE9+,及现代浏览器。
  • +
  • 浏览器渲染 SVG 的性能一般,还不如 png。
  • +
+

使用步骤如下:

+

第一步:引入项目下面生成的 symbol 代码:

+
<script src="./iconfont.js"></script>
+
+

第二步:加入通用 CSS 代码(引入一次就行):

+
<style>
+.icon {
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.15em;
+  fill: currentColor;
+  overflow: hidden;
+}
+</style>
+
+

第三步:挑选相应图标并获取类名,应用于页面:

+
<svg class="icon" aria-hidden="true">
+  <use xlink:href="#icon-xxx"></use>
+</svg>
+
+
+
+ +
+
+ + + diff --git a/public/icons/cms/iconfont.css b/public/icons/cms/iconfont.css new file mode 100644 index 0000000..5a9f9cb --- /dev/null +++ b/public/icons/cms/iconfont.css @@ -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"; +} + diff --git a/public/icons/cms/iconfont.js b/public/icons/cms/iconfont.js new file mode 100644 index 0000000..c975383 --- /dev/null +++ b/public/icons/cms/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4718675='',(h=>{var c=(l=(l=document.getElementsByTagName("script"))[l.length-1]).getAttribute("data-injectcss"),l=l.getAttribute("data-disable-injectsvg");if(!l){var a,t,m,s,z,v=function(c,l){l.parentNode.insertBefore(c,l)};if(c&&!h.__iconfont__svg__cssinject__){h.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}a=function(){var c,l=document.createElement("div");l.innerHTML=h._iconfont_svg_string_4718675,(l=l.getElementsByTagName("svg")[0])&&(l.setAttribute("aria-hidden","true"),l.style.position="absolute",l.style.width=0,l.style.height=0,l.style.overflow="hidden",l=l,(c=document.body).firstChild?v(l,c.firstChild):c.appendChild(l))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(a,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),a()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(m=a,s=h.document,z=!1,i(),s.onreadystatechange=function(){"complete"==s.readyState&&(s.onreadystatechange=null,o())})}function o(){z||(z=!0,m())}function i(){try{s.documentElement.doScroll("left")}catch(c){return void setTimeout(i,50)}o()}})(window); \ No newline at end of file diff --git a/public/icons/cms/iconfont.json b/public/icons/cms/iconfont.json new file mode 100644 index 0000000..0137aaa --- /dev/null +++ b/public/icons/cms/iconfont.json @@ -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 + } + ] +} diff --git a/public/icons/cms/iconfont.ttf b/public/icons/cms/iconfont.ttf new file mode 100644 index 0000000..13a35ac Binary files /dev/null and b/public/icons/cms/iconfont.ttf differ diff --git a/public/icons/cms/iconfont.woff b/public/icons/cms/iconfont.woff new file mode 100644 index 0000000..aa7cf6b Binary files /dev/null and b/public/icons/cms/iconfont.woff differ diff --git a/public/icons/cms/iconfont.woff2 b/public/icons/cms/iconfont.woff2 new file mode 100644 index 0000000..5bd76ec Binary files /dev/null and b/public/icons/cms/iconfont.woff2 differ diff --git a/public/icons/system/iconfont.css b/public/icons/system/iconfont.css new file mode 100644 index 0000000..9b3aa4f --- /dev/null +++ b/public/icons/system/iconfont.css @@ -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"; +} diff --git a/public/icons/system/iconfont.ttf b/public/icons/system/iconfont.ttf new file mode 100644 index 0000000..cf83da8 Binary files /dev/null and b/public/icons/system/iconfont.ttf differ diff --git a/public/icons/system/iconfont.woff b/public/icons/system/iconfont.woff new file mode 100644 index 0000000..57d3754 Binary files /dev/null and b/public/icons/system/iconfont.woff differ diff --git a/public/icons/system/iconfont.woff2 b/public/icons/system/iconfont.woff2 new file mode 100644 index 0000000..ea1d3a6 Binary files /dev/null and b/public/icons/system/iconfont.woff2 differ diff --git a/public/images/login.jpg b/public/images/login.jpg new file mode 100644 index 0000000..ab83114 Binary files /dev/null and b/public/images/login.jpg differ diff --git a/public/images/system/404.png b/public/images/system/404.png new file mode 100644 index 0000000..a386e02 Binary files /dev/null and b/public/images/system/404.png differ diff --git a/public/images/system/error.png b/public/images/system/error.png new file mode 100644 index 0000000..c581a35 Binary files /dev/null and b/public/images/system/error.png differ diff --git a/public/images/system/not-allowed.png b/public/images/system/not-allowed.png new file mode 100644 index 0000000..57cb287 Binary files /dev/null and b/public/images/system/not-allowed.png differ diff --git a/public/images/system/suggestion.png b/public/images/system/suggestion.png new file mode 100644 index 0000000..5182634 Binary files /dev/null and b/public/images/system/suggestion.png differ diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..dce5053 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,234 @@ + + + diff --git a/src/api/cms/article.js b/src/api/cms/article.js new file mode 100644 index 0000000..2a3b32a --- /dev/null +++ b/src/api/cms/article.js @@ -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) +} diff --git a/src/api/cms/article.tag.js b/src/api/cms/article.tag.js new file mode 100644 index 0000000..d406e39 --- /dev/null +++ b/src/api/cms/article.tag.js @@ -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) +} diff --git a/src/api/cms/category.js b/src/api/cms/category.js new file mode 100644 index 0000000..4f6f8b7 --- /dev/null +++ b/src/api/cms/category.js @@ -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) +} diff --git a/src/api/cms/resource.group.js b/src/api/cms/resource.group.js new file mode 100644 index 0000000..24ff707 --- /dev/null +++ b/src/api/cms/resource.group.js @@ -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) +} diff --git a/src/api/cms/resource.js b/src/api/cms/resource.js new file mode 100644 index 0000000..8fc7960 --- /dev/null +++ b/src/api/cms/resource.js @@ -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) +} diff --git a/src/api/cms/template.js b/src/api/cms/template.js new file mode 100644 index 0000000..83060db --- /dev/null +++ b/src/api/cms/template.js @@ -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) +} diff --git a/src/api/system/config.js b/src/api/system/config.js new file mode 100644 index 0000000..5ae0527 --- /dev/null +++ b/src/api/system/config.js @@ -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) +} diff --git a/src/api/system/dict.data.js b/src/api/system/dict.data.js new file mode 100644 index 0000000..a7e653d --- /dev/null +++ b/src/api/system/dict.data.js @@ -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) +} diff --git a/src/api/system/dict.js b/src/api/system/dict.js new file mode 100644 index 0000000..a896797 --- /dev/null +++ b/src/api/system/dict.js @@ -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) +} diff --git a/src/api/system/icon.js b/src/api/system/icon.js new file mode 100644 index 0000000..b7b249b --- /dev/null +++ b/src/api/system/icon.js @@ -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) +} diff --git a/src/api/system/index.js b/src/api/system/index.js new file mode 100644 index 0000000..2b206fc --- /dev/null +++ b/src/api/system/index.js @@ -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) +} diff --git a/src/api/system/login.log.js b/src/api/system/login.log.js new file mode 100644 index 0000000..820c4c2 --- /dev/null +++ b/src/api/system/login.log.js @@ -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 + }) +} diff --git a/src/api/system/menu.func.js b/src/api/system/menu.func.js new file mode 100644 index 0000000..90d8b0f --- /dev/null +++ b/src/api/system/menu.func.js @@ -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) +} diff --git a/src/api/system/menu.js b/src/api/system/menu.js new file mode 100644 index 0000000..099ddc0 --- /dev/null +++ b/src/api/system/menu.js @@ -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) +} diff --git a/src/api/system/permission.js b/src/api/system/permission.js new file mode 100644 index 0000000..ea3d0d5 --- /dev/null +++ b/src/api/system/permission.js @@ -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) +} diff --git a/src/api/system/role.js b/src/api/system/role.js new file mode 100644 index 0000000..d71c91a --- /dev/null +++ b/src/api/system/role.js @@ -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') +} diff --git a/src/api/system/trace.log.js b/src/api/system/trace.log.js new file mode 100644 index 0000000..f3a1f4f --- /dev/null +++ b/src/api/system/trace.log.js @@ -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 + }) +} diff --git a/src/api/system/user.js b/src/api/system/user.js new file mode 100644 index 0000000..abab18e --- /dev/null +++ b/src/api/system/user.js @@ -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) +} diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..83ebb6b Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/assets/style/element/index.scss b/src/assets/style/element/index.scss new file mode 100644 index 0000000..9eab949 --- /dev/null +++ b/src/assets/style/element/index.scss @@ -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 *; diff --git a/src/assets/style/index.scss b/src/assets/style/index.scss new file mode 100644 index 0000000..8319777 --- /dev/null +++ b/src/assets/style/index.scss @@ -0,0 +1,2 @@ +@use 'theme.scss'; +@use 'style.scss'; diff --git a/src/assets/style/style.scss b/src/assets/style/style.scss new file mode 100644 index 0000000..ad00280 --- /dev/null +++ b/src/assets/style/style.scss @@ -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; +} diff --git a/src/assets/style/theme.scss b/src/assets/style/theme.scss new file mode 100644 index 0000000..f173b74 --- /dev/null +++ b/src/assets/style/theme.scss @@ -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}; +} diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..ca8129c --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + diff --git a/src/components/BaseComponent.vue b/src/components/BaseComponent.vue new file mode 100644 index 0000000..e52b81e --- /dev/null +++ b/src/components/BaseComponent.vue @@ -0,0 +1,14 @@ + diff --git a/src/components/base/BaseDict.vue b/src/components/base/BaseDict.vue new file mode 100644 index 0000000..8c82976 --- /dev/null +++ b/src/components/base/BaseDict.vue @@ -0,0 +1,35 @@ + diff --git a/src/components/base/BaseOpera.vue b/src/components/base/BaseOpera.vue new file mode 100644 index 0000000..7ab6c89 --- /dev/null +++ b/src/components/base/BaseOpera.vue @@ -0,0 +1,140 @@ + diff --git a/src/components/base/BaseTable.vue b/src/components/base/BaseTable.vue new file mode 100644 index 0000000..146096f --- /dev/null +++ b/src/components/base/BaseTable.vue @@ -0,0 +1,314 @@ + diff --git a/src/components/cms/article/ArticleAttachmentUploader.vue b/src/components/cms/article/ArticleAttachmentUploader.vue new file mode 100644 index 0000000..e020e2a --- /dev/null +++ b/src/components/cms/article/ArticleAttachmentUploader.vue @@ -0,0 +1,58 @@ + + + + + diff --git a/src/components/cms/article/ArticleAttachments.vue b/src/components/cms/article/ArticleAttachments.vue new file mode 100644 index 0000000..49c6cf0 --- /dev/null +++ b/src/components/cms/article/ArticleAttachments.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src/components/cms/article/ArticleCoverSelect.vue b/src/components/cms/article/ArticleCoverSelect.vue new file mode 100644 index 0000000..221a657 --- /dev/null +++ b/src/components/cms/article/ArticleCoverSelect.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/components/cms/article/ArticleSelect.vue b/src/components/cms/article/ArticleSelect.vue new file mode 100644 index 0000000..08be7fa --- /dev/null +++ b/src/components/cms/article/ArticleSelect.vue @@ -0,0 +1,104 @@ + + + + diff --git a/src/components/cms/article/ArticleTagManageWindow.vue b/src/components/cms/article/ArticleTagManageWindow.vue new file mode 100644 index 0000000..7355de3 --- /dev/null +++ b/src/components/cms/article/ArticleTagManageWindow.vue @@ -0,0 +1,135 @@ + + + diff --git a/src/components/cms/article/ArticleTagSelect.vue b/src/components/cms/article/ArticleTagSelect.vue new file mode 100644 index 0000000..a985be4 --- /dev/null +++ b/src/components/cms/article/ArticleTagSelect.vue @@ -0,0 +1,84 @@ + + + + diff --git a/src/components/cms/article/OperaArticleTagWindow.vue b/src/components/cms/article/OperaArticleTagWindow.vue new file mode 100644 index 0000000..e4eb489 --- /dev/null +++ b/src/components/cms/article/OperaArticleTagWindow.vue @@ -0,0 +1,55 @@ + + + + diff --git a/src/components/cms/article/editor/ArticleEditorWindow.vue b/src/components/cms/article/editor/ArticleEditorWindow.vue new file mode 100644 index 0000000..4ca9f1c --- /dev/null +++ b/src/components/cms/article/editor/ArticleEditorWindow.vue @@ -0,0 +1,255 @@ + + + + + diff --git a/src/components/cms/article/editor/AttachmentsMixin.vue b/src/components/cms/article/editor/AttachmentsMixin.vue new file mode 100644 index 0000000..ef1e066 --- /dev/null +++ b/src/components/cms/article/editor/AttachmentsMixin.vue @@ -0,0 +1,34 @@ + diff --git a/src/components/cms/article/editor/BaseMixin.vue b/src/components/cms/article/editor/BaseMixin.vue new file mode 100644 index 0000000..f447b81 --- /dev/null +++ b/src/components/cms/article/editor/BaseMixin.vue @@ -0,0 +1,145 @@ + diff --git a/src/components/cms/article/editor/editor.scss b/src/components/cms/article/editor/editor.scss new file mode 100644 index 0000000..ded5bd1 --- /dev/null +++ b/src/components/cms/article/editor/editor.scss @@ -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; + } + } +} diff --git a/src/components/cms/category/CategoryIcon.vue b/src/components/cms/category/CategoryIcon.vue new file mode 100644 index 0000000..76214b2 --- /dev/null +++ b/src/components/cms/category/CategoryIcon.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/cms/category/CategorySelect.vue b/src/components/cms/category/CategorySelect.vue new file mode 100644 index 0000000..9c5dec4 --- /dev/null +++ b/src/components/cms/category/CategorySelect.vue @@ -0,0 +1,105 @@ + + + diff --git a/src/components/cms/category/OperaCategoryWindow.vue b/src/components/cms/category/OperaCategoryWindow.vue new file mode 100644 index 0000000..9595fc9 --- /dev/null +++ b/src/components/cms/category/OperaCategoryWindow.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/src/components/cms/resource/OperaResourceGroupWindow.vue b/src/components/cms/resource/OperaResourceGroupWindow.vue new file mode 100644 index 0000000..37a7e1a --- /dev/null +++ b/src/components/cms/resource/OperaResourceGroupWindow.vue @@ -0,0 +1,150 @@ + + + + diff --git a/src/components/cms/resource/OperaResourceWindow.vue b/src/components/cms/resource/OperaResourceWindow.vue new file mode 100644 index 0000000..e61e416 --- /dev/null +++ b/src/components/cms/resource/OperaResourceWindow.vue @@ -0,0 +1,213 @@ + + + + + diff --git a/src/components/cms/resource/ResourceGroupSelect.vue b/src/components/cms/resource/ResourceGroupSelect.vue new file mode 100644 index 0000000..7f34d8f --- /dev/null +++ b/src/components/cms/resource/ResourceGroupSelect.vue @@ -0,0 +1,80 @@ + + + + diff --git a/src/components/cms/resource/ResourceManageWindow.vue b/src/components/cms/resource/ResourceManageWindow.vue new file mode 100644 index 0000000..061251c --- /dev/null +++ b/src/components/cms/resource/ResourceManageWindow.vue @@ -0,0 +1,216 @@ + + + diff --git a/src/components/cms/resource/input/ImageInput.vue b/src/components/cms/resource/input/ImageInput.vue new file mode 100644 index 0000000..d205aca --- /dev/null +++ b/src/components/cms/resource/input/ImageInput.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/components/cms/resource/input/ImageLinkInput.vue b/src/components/cms/resource/input/ImageLinkInput.vue new file mode 100644 index 0000000..ba47b03 --- /dev/null +++ b/src/components/cms/resource/input/ImageLinkInput.vue @@ -0,0 +1,84 @@ + + + + + diff --git a/src/components/cms/resource/input/LinkInput.vue b/src/components/cms/resource/input/LinkInput.vue new file mode 100644 index 0000000..83b6170 --- /dev/null +++ b/src/components/cms/resource/input/LinkInput.vue @@ -0,0 +1,9 @@ + + + diff --git a/src/components/cms/resource/input/ResourceInput.vue b/src/components/cms/resource/input/ResourceInput.vue new file mode 100644 index 0000000..4202fd7 --- /dev/null +++ b/src/components/cms/resource/input/ResourceInput.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/components/cms/resource/preview/ImageLinkPreview.vue b/src/components/cms/resource/preview/ImageLinkPreview.vue new file mode 100644 index 0000000..4550fbf --- /dev/null +++ b/src/components/cms/resource/preview/ImageLinkPreview.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/components/cms/resource/preview/ImagePreview.vue b/src/components/cms/resource/preview/ImagePreview.vue new file mode 100644 index 0000000..ccfc479 --- /dev/null +++ b/src/components/cms/resource/preview/ImagePreview.vue @@ -0,0 +1,32 @@ + + + + diff --git a/src/components/cms/resource/preview/LinkPreview.vue b/src/components/cms/resource/preview/LinkPreview.vue new file mode 100644 index 0000000..01c86d7 --- /dev/null +++ b/src/components/cms/resource/preview/LinkPreview.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/components/cms/resource/preview/ResourcePreview.vue b/src/components/cms/resource/preview/ResourcePreview.vue new file mode 100644 index 0000000..ff3a121 --- /dev/null +++ b/src/components/cms/resource/preview/ResourcePreview.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/cms/template/OperaCmsTemplateWindow.vue b/src/components/cms/template/OperaCmsTemplateWindow.vue new file mode 100644 index 0000000..a7101c4 --- /dev/null +++ b/src/components/cms/template/OperaCmsTemplateWindow.vue @@ -0,0 +1,150 @@ + + + + diff --git a/src/components/cms/template/TemplateSelect.vue b/src/components/cms/template/TemplateSelect.vue new file mode 100644 index 0000000..eb07f84 --- /dev/null +++ b/src/components/cms/template/TemplateSelect.vue @@ -0,0 +1,251 @@ + + + + diff --git a/src/components/cms/template/parameter-input/ArticleInput.vue b/src/components/cms/template/parameter-input/ArticleInput.vue new file mode 100644 index 0000000..eb63dbb --- /dev/null +++ b/src/components/cms/template/parameter-input/ArticleInput.vue @@ -0,0 +1,14 @@ + + + diff --git a/src/components/cms/template/parameter-input/CategoryInput.vue b/src/components/cms/template/parameter-input/CategoryInput.vue new file mode 100644 index 0000000..48954c4 --- /dev/null +++ b/src/components/cms/template/parameter-input/CategoryInput.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/cms/template/parameter-input/MultipleCategoryInput.vue b/src/components/cms/template/parameter-input/MultipleCategoryInput.vue new file mode 100644 index 0000000..6c358ec --- /dev/null +++ b/src/components/cms/template/parameter-input/MultipleCategoryInput.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/cms/template/parameter-input/MultipleResourceGroupInput.vue b/src/components/cms/template/parameter-input/MultipleResourceGroupInput.vue new file mode 100644 index 0000000..6440082 --- /dev/null +++ b/src/components/cms/template/parameter-input/MultipleResourceGroupInput.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/components/cms/template/parameter-input/NumberInput.vue b/src/components/cms/template/parameter-input/NumberInput.vue new file mode 100644 index 0000000..76ecbfe --- /dev/null +++ b/src/components/cms/template/parameter-input/NumberInput.vue @@ -0,0 +1,14 @@ + + + + + diff --git a/src/components/cms/template/parameter-input/TextInput.vue b/src/components/cms/template/parameter-input/TextInput.vue new file mode 100644 index 0000000..46a660f --- /dev/null +++ b/src/components/cms/template/parameter-input/TextInput.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/components/cms/template/parameter-input/index.vue b/src/components/cms/template/parameter-input/index.vue new file mode 100644 index 0000000..79d5efe --- /dev/null +++ b/src/components/cms/template/parameter-input/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/common/AttachUploader.vue b/src/components/common/AttachUploader.vue new file mode 100644 index 0000000..e829b98 --- /dev/null +++ b/src/components/common/AttachUploader.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/common/AvatarUploader.vue b/src/components/common/AvatarUploader.vue new file mode 100644 index 0000000..8272f2f --- /dev/null +++ b/src/components/common/AvatarUploader.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/src/components/common/ColorsMarking.vue b/src/components/common/ColorsMarking.vue new file mode 100644 index 0000000..60e9d24 --- /dev/null +++ b/src/components/common/ColorsMarking.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/src/components/common/DataLoading.vue b/src/components/common/DataLoading.vue new file mode 100644 index 0000000..40cb06a --- /dev/null +++ b/src/components/common/DataLoading.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/src/components/common/DictCheckboxGroup.vue b/src/components/common/DictCheckboxGroup.vue new file mode 100644 index 0000000..7ff820b --- /dev/null +++ b/src/components/common/DictCheckboxGroup.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/components/common/DictRadioGroup.vue b/src/components/common/DictRadioGroup.vue new file mode 100644 index 0000000..c341898 --- /dev/null +++ b/src/components/common/DictRadioGroup.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/components/common/DictSelect.vue b/src/components/common/DictSelect.vue new file mode 100644 index 0000000..468fe9d --- /dev/null +++ b/src/components/common/DictSelect.vue @@ -0,0 +1,70 @@ + + + + diff --git a/src/components/common/EmptyTip.vue b/src/components/common/EmptyTip.vue new file mode 100644 index 0000000..b74f6ff --- /dev/null +++ b/src/components/common/EmptyTip.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/src/components/common/FormItemTip.vue b/src/components/common/FormItemTip.vue new file mode 100644 index 0000000..2821183 --- /dev/null +++ b/src/components/common/FormItemTip.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/common/GlobalWindow.vue b/src/components/common/GlobalWindow.vue new file mode 100644 index 0000000..1098395 --- /dev/null +++ b/src/components/common/GlobalWindow.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/src/components/common/Icon.vue b/src/components/common/Icon.vue new file mode 100644 index 0000000..a68601e --- /dev/null +++ b/src/components/common/Icon.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/src/components/common/ImageUploader.vue b/src/components/common/ImageUploader.vue new file mode 100644 index 0000000..3860233 --- /dev/null +++ b/src/components/common/ImageUploader.vue @@ -0,0 +1,195 @@ + + + + + diff --git a/src/components/common/ImportButton.vue b/src/components/common/ImportButton.vue new file mode 100644 index 0000000..6f6bd03 --- /dev/null +++ b/src/components/common/ImportButton.vue @@ -0,0 +1,41 @@ + + + diff --git a/src/components/common/ImportWindow.vue b/src/components/common/ImportWindow.vue new file mode 100644 index 0000000..2831cfa --- /dev/null +++ b/src/components/common/ImportWindow.vue @@ -0,0 +1,214 @@ + + + + + diff --git a/src/components/common/Light.vue b/src/components/common/Light.vue new file mode 100644 index 0000000..4b64877 --- /dev/null +++ b/src/components/common/Light.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/common/Pagination.vue b/src/components/common/Pagination.vue new file mode 100644 index 0000000..a9df279 --- /dev/null +++ b/src/components/common/Pagination.vue @@ -0,0 +1,56 @@ + + + diff --git a/src/components/common/PasswordInput.vue b/src/components/common/PasswordInput.vue new file mode 100644 index 0000000..47623d3 --- /dev/null +++ b/src/components/common/PasswordInput.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/src/components/common/PopoverCellValue.vue b/src/components/common/PopoverCellValue.vue new file mode 100644 index 0000000..3d5f449 --- /dev/null +++ b/src/components/common/PopoverCellValue.vue @@ -0,0 +1,160 @@ + + + + + + diff --git a/src/components/common/RichEditor.vue b/src/components/common/RichEditor.vue new file mode 100644 index 0000000..1a752ba --- /dev/null +++ b/src/components/common/RichEditor.vue @@ -0,0 +1,189 @@ + + + + + + diff --git a/src/components/common/Scrollbar.vue b/src/components/common/Scrollbar.vue new file mode 100644 index 0000000..0baf590 --- /dev/null +++ b/src/components/common/Scrollbar.vue @@ -0,0 +1,23 @@ + + + + + diff --git a/src/components/common/SearchForm.vue b/src/components/common/SearchForm.vue new file mode 100644 index 0000000..628df7e --- /dev/null +++ b/src/components/common/SearchForm.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/src/components/common/SearchTable.vue b/src/components/common/SearchTable.vue new file mode 100644 index 0000000..641cbeb --- /dev/null +++ b/src/components/common/SearchTable.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/src/components/common/TagCellValue.vue b/src/components/common/TagCellValue.vue new file mode 100644 index 0000000..cbc51af --- /dev/null +++ b/src/components/common/TagCellValue.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/components/common/TreeSelect.vue b/src/components/common/TreeSelect.vue new file mode 100644 index 0000000..f168dac --- /dev/null +++ b/src/components/common/TreeSelect.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/components/common/TwoFAWindow.vue b/src/components/common/TwoFAWindow.vue new file mode 100644 index 0000000..73c85e0 --- /dev/null +++ b/src/components/common/TwoFAWindow.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/src/components/common/Value.vue b/src/components/common/Value.vue new file mode 100644 index 0000000..fea240d --- /dev/null +++ b/src/components/common/Value.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..8631b37 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,36 @@ +import TableLayout from '@/layouts/TableLayout' +import DictCheckboxGroup from '@/components/common/DictCheckboxGroup' +import DictRadioGroup from '@/components/common/DictRadioGroup' +import DictSelect from '@/components/common/DictSelect' +import EmptyTip from '@/components/common/EmptyTip' +import FormItemTip from '@/components/common/FormItemTip' +import GlobalWindow from '@/components/common/GlobalWindow' +import Icon from '@/components/common/Icon' +import ImportButton from '@/components/common/ImportButton' +import DataLoading from '@/components/common/DataLoading' +import Pagination from '@/components/common/Pagination' +import PopoverCellValue from '@/components/common/PopoverCellValue' +import TagCellValue from '@/components/common/TagCellValue' +import SearchForm from '@/components/common/SearchForm' +import SearchTable from '@/components/common/SearchTable' +export default { + install (Vue) { + // 布局组件 + Vue.component('TableLayout', TableLayout) + // 常用组件 + Vue.component('DictCheckboxGroup', DictCheckboxGroup) + Vue.component('DictRadioGroup', DictRadioGroup) + Vue.component('DictSelect', DictSelect) + Vue.component('EmptyTip', EmptyTip) + Vue.component('FormItemTip', FormItemTip) + Vue.component('GlobalWindow', GlobalWindow) + Vue.component('Icon', Icon) + Vue.component('ImportButton', ImportButton) + Vue.component('DataLoading', DataLoading) + Vue.component('Pagination', Pagination) + Vue.component('PopoverCellValue', PopoverCellValue) + Vue.component('TagCellValue', TagCellValue) + Vue.component('SearchForm', SearchForm) + Vue.component('SearchTable', SearchTable) + } +} diff --git a/src/components/layout/AppHeader.vue b/src/components/layout/AppHeader.vue new file mode 100644 index 0000000..857fe32 --- /dev/null +++ b/src/components/layout/AppHeader.vue @@ -0,0 +1,257 @@ + + + + + diff --git a/src/components/layout/AppMenu.vue b/src/components/layout/AppMenu.vue new file mode 100644 index 0000000..8061119 --- /dev/null +++ b/src/components/layout/AppMenu.vue @@ -0,0 +1,314 @@ + + + + + + diff --git a/src/components/layout/MenuItems.vue b/src/components/layout/MenuItems.vue new file mode 100644 index 0000000..bd9b1ea --- /dev/null +++ b/src/components/layout/MenuItems.vue @@ -0,0 +1,38 @@ + + + diff --git a/src/components/layout/NotAllowed.vue b/src/components/layout/NotAllowed.vue new file mode 100644 index 0000000..920b539 --- /dev/null +++ b/src/components/layout/NotAllowed.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/src/components/layout/Profile.vue b/src/components/layout/Profile.vue new file mode 100644 index 0000000..fff9fce --- /dev/null +++ b/src/components/layout/Profile.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/components/system/LoginForm.vue b/src/components/system/LoginForm.vue new file mode 100644 index 0000000..99c67d8 --- /dev/null +++ b/src/components/system/LoginForm.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/src/components/system/LoginFormWindow.vue b/src/components/system/LoginFormWindow.vue new file mode 100644 index 0000000..6640cd9 --- /dev/null +++ b/src/components/system/LoginFormWindow.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/system/config/ConfigInput.vue b/src/components/system/config/ConfigInput.vue new file mode 100644 index 0000000..6b4b446 --- /dev/null +++ b/src/components/system/config/ConfigInput.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/system/config/OperaConfigWindow.vue b/src/components/system/config/OperaConfigWindow.vue new file mode 100644 index 0000000..11ca10c --- /dev/null +++ b/src/components/system/config/OperaConfigWindow.vue @@ -0,0 +1,158 @@ + + + + diff --git a/src/components/system/dict/DictDataManagerWindow.vue b/src/components/system/dict/DictDataManagerWindow.vue new file mode 100644 index 0000000..23c6d39 --- /dev/null +++ b/src/components/system/dict/DictDataManagerWindow.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/components/system/dict/OperaDictDataWindow.vue b/src/components/system/dict/OperaDictDataWindow.vue new file mode 100644 index 0000000..cee24c2 --- /dev/null +++ b/src/components/system/dict/OperaDictDataWindow.vue @@ -0,0 +1,110 @@ + + + + + diff --git a/src/components/system/dict/OperaDictWindow.vue b/src/components/system/dict/OperaDictWindow.vue new file mode 100644 index 0000000..29e93ea --- /dev/null +++ b/src/components/system/dict/OperaDictWindow.vue @@ -0,0 +1,123 @@ + + + diff --git a/src/components/system/dict/OperaTemplateDataWindow.vue b/src/components/system/dict/OperaTemplateDataWindow.vue new file mode 100644 index 0000000..39bf107 --- /dev/null +++ b/src/components/system/dict/OperaTemplateDataWindow.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/src/components/system/dict/TemplateDataManagerWindow.vue b/src/components/system/dict/TemplateDataManagerWindow.vue new file mode 100644 index 0000000..5ea75a4 --- /dev/null +++ b/src/components/system/dict/TemplateDataManagerWindow.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/components/system/icon/IconSelect.vue b/src/components/system/icon/IconSelect.vue new file mode 100644 index 0000000..09f7d92 --- /dev/null +++ b/src/components/system/icon/IconSelect.vue @@ -0,0 +1,278 @@ + + + + + + + diff --git a/src/components/system/icon/OperaIconWindow.vue b/src/components/system/icon/OperaIconWindow.vue new file mode 100644 index 0000000..8ea7d9b --- /dev/null +++ b/src/components/system/icon/OperaIconWindow.vue @@ -0,0 +1,83 @@ + + + + + + diff --git a/src/components/system/menu/MenuFuncManagerWindow.vue b/src/components/system/menu/MenuFuncManagerWindow.vue new file mode 100644 index 0000000..1136ad3 --- /dev/null +++ b/src/components/system/menu/MenuFuncManagerWindow.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/src/components/system/menu/MenuSelect.vue b/src/components/system/menu/MenuSelect.vue new file mode 100644 index 0000000..e9217f8 --- /dev/null +++ b/src/components/system/menu/MenuSelect.vue @@ -0,0 +1,94 @@ + + + diff --git a/src/components/system/menu/OperaMenuFuncWindow.vue b/src/components/system/menu/OperaMenuFuncWindow.vue new file mode 100644 index 0000000..bf03d21 --- /dev/null +++ b/src/components/system/menu/OperaMenuFuncWindow.vue @@ -0,0 +1,103 @@ + + + + diff --git a/src/components/system/menu/OperaMenuWindow.vue b/src/components/system/menu/OperaMenuWindow.vue new file mode 100644 index 0000000..4ef9d8c --- /dev/null +++ b/src/components/system/menu/OperaMenuWindow.vue @@ -0,0 +1,136 @@ + + + + + diff --git a/src/components/system/role/OperaRoleWindow.vue b/src/components/system/role/OperaRoleWindow.vue new file mode 100644 index 0000000..db4b442 --- /dev/null +++ b/src/components/system/role/OperaRoleWindow.vue @@ -0,0 +1,103 @@ + + + diff --git a/src/components/system/role/PermissionConfigWindow.vue b/src/components/system/role/PermissionConfigWindow.vue new file mode 100644 index 0000000..914fbae --- /dev/null +++ b/src/components/system/role/PermissionConfigWindow.vue @@ -0,0 +1,454 @@ + + + + + diff --git a/src/components/system/role/RoleSelect.vue b/src/components/system/role/RoleSelect.vue new file mode 100644 index 0000000..d968a9f --- /dev/null +++ b/src/components/system/role/RoleSelect.vue @@ -0,0 +1,51 @@ + + + + diff --git a/src/components/system/user/OperaUserWindow.vue b/src/components/system/user/OperaUserWindow.vue new file mode 100644 index 0000000..990f43b --- /dev/null +++ b/src/components/system/user/OperaUserWindow.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/src/components/system/user/ResetPwdWindow.vue b/src/components/system/user/ResetPwdWindow.vue new file mode 100644 index 0000000..a6269d6 --- /dev/null +++ b/src/components/system/user/ResetPwdWindow.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/components/system/user/RoleConfigWindow.vue b/src/components/system/user/RoleConfigWindow.vue new file mode 100644 index 0000000..904da7f --- /dev/null +++ b/src/components/system/user/RoleConfigWindow.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/core/authorize/index.js b/src/core/authorize/index.js new file mode 100644 index 0000000..b70f4e3 --- /dev/null +++ b/src/core/authorize/index.js @@ -0,0 +1,135 @@ +import { useDefaultStore } from '../store' + +class Authorizer { + // 判断是否为超级管理员 + isSuperAdmin = () => { + const userInfo = this.#getStore().userInfo + if (userInfo == null) { + return false + } + return userInfo.isSuperAdmin + } + + // 判断是否包含所有角色 + hasRoles = (roles) => { + roles = this.#handleParameter(roles) + const result = this.#checkRoles(roles) + if (result !== undefined) { + return result + } + const userInfo = this.#getStore().userInfo + // 如果是超管,则验证通过 + if (this.isSuperAdmin()) { + return true + } + for (const role of roles) { + if (userInfo.roles.findIndex(r => r === role) === -1) { + return false + } + } + return true + } + + // 判断是否包含任意指定角色 + hasAnyRoles = (roles) => { + roles = this.#handleParameter(roles) + const result = this.#checkRoles(roles) + if (result !== undefined) { + return result + } + const userInfo = this.#getStore().userInfo + // 如果是超管,则验证通过 + if (this.isSuperAdmin()) { + return true + } + for (const role of roles) { + if (userInfo.roles.findIndex(r => r === role) > -1) { + return true + } + } + return false + } + + // 判断是否包含所有权限 + hasPermissions = (permissions) => { + permissions = this.#handleParameter(permissions) + const result = this.#checkPermissions(permissions) + if (result !== undefined) { + return result + } + const userInfo = this.#getStore().userInfo + // 如果是超管 && 权限为空,则验证通过 + if (this.isSuperAdmin() && userInfo.permissions.length === 0) { + return true + } + for (const permission of permissions) { + if (userInfo.permissions.findIndex(p => p === permission) === -1) { + return false + } + } + return true + } + + // 判断是否包含任意指定权限 + hasAnyPermissions = (permissions) => { + permissions = this.#handleParameter(permissions) + const result = this.#checkPermissions(permissions) + if (result !== undefined) { + return result + } + const userInfo = this.#getStore().userInfo + // 如果是超管 && 权限为空,则验证通过 + if (this.isSuperAdmin() && userInfo.permissions.length === 0) { + return true + } + for (const permission of permissions) { + if (userInfo.permissions.findIndex(p => p === permission) > -1) { + return true + } + } + return false + } + + // 验证角色参数 + #checkRoles = roles => { + if (roles.length === 0) { + return true + } + const userInfo = this.#getStore().userInfo + if (userInfo == null) { + return false + } + } + + // 验证权限参数 + #checkPermissions = permissions => { + if (permissions.length === 0) { + return true + } + const userInfo = this.#getStore().userInfo + if (userInfo == null) { + return false + } + } + + // 处理参数 + #handleParameter = params => { + if (params == null) { + return [] + } + if (typeof params !== 'string' && !(params instanceof Array)) { + throw new Error('v-roles和v-permissions参数必须是一个字符串或数组') + } + if (typeof params === 'string') { + return [params] + } + return params + } + + // 获取Store + #getStore () { + return useDefaultStore() + } +} + +export default new Authorizer() diff --git a/src/core/directives/index.js b/src/core/directives/index.js new file mode 100644 index 0000000..2fe1663 --- /dev/null +++ b/src/core/directives/index.js @@ -0,0 +1,20 @@ +import vRoles from './v-roles' +import vAnyRoles from './v-any-roles' +import vPermissions from './v-permissions' +import vAnyPermissions from './v-any-permissions' +import vTrim from './v-trim' +import vClipboard from './v-clipboard' +export default { + install (app) { + // 角色控制指令 + app.directive('roles', vRoles) + app.directive('any-roles', vAnyRoles) + // 权限控制指令 + app.directive('permissions', vPermissions) + app.directive('any-permissions', vAnyPermissions) + // 自动去空指令 + app.directive('trim', vTrim) + // 文本复制 + app.directive('clipboard', vClipboard) + } +} diff --git a/src/core/directives/v-any-permissions.js b/src/core/directives/v-any-permissions.js new file mode 100644 index 0000000..0250c76 --- /dev/null +++ b/src/core/directives/v-any-permissions.js @@ -0,0 +1,9 @@ +import authorizer from '@/core/authorize' + +export default { + mounted (el, binding) { + if (!authorizer.hasAnyPermissions(binding.value)) { + el.parentNode && el.parentNode.removeChild(el) + } + } +} diff --git a/src/core/directives/v-any-roles.js b/src/core/directives/v-any-roles.js new file mode 100644 index 0000000..edc7780 --- /dev/null +++ b/src/core/directives/v-any-roles.js @@ -0,0 +1,9 @@ +import authorizer from '@/core/authorize' + +export default { + mounted (el, binding) { + if (!authorizer.hasAnyRoles(binding.value)) { + el.parentNode && el.parentNode.removeChild(el) + } + } +} diff --git a/src/core/directives/v-clipboard.js b/src/core/directives/v-clipboard.js new file mode 100644 index 0000000..5a40999 --- /dev/null +++ b/src/core/directives/v-clipboard.js @@ -0,0 +1,65 @@ +import Clipboard from 'vue-clipboard3' +const { toClipboard } = Clipboard() + +export default { + mounted (el, binding) { + // binding.arg 为动态指令参数 + // 将copy的值 成功回调 失败回调 及 click事件都绑定到el上 这样在更新和卸载时方便操作 + switch (binding.arg) { + case 'copy': + // copy值 + el.clipValue = binding.value + // click事件 + el.clipCopy = function () { + toClipboard(el.clipValue) + .then(result => { + el.clipSuccess && el.clipSuccess(result) + }) + .catch(err => { + el.clipError && el.clipError(err) + }) + } + // 绑定click事件 + el.addEventListener('click', el.clipCopy) + break; + case 'success': + // 成功回调 + el.clipSuccess = binding.value + break; + case 'error': + // 失败回调 + el.clipError = binding.value + break + } + }, + // 相应修改 重置相应的值即可 + updated (el, binding) { + switch (binding.arg) { + case 'copy': + el.clipValue = binding.value + break + case 'success': + el.clipSuccess = binding.value + break + case 'error': + el.clipError = binding.value + break + } + }, + // 卸载 删除click事件 删除对应的自定义属性 + unmounted (el, binding) { + switch (binding.arg) { + case 'copy': + el.removeEventListener('click', el.clipCopy) + delete el.clipValue + delete el.clipCopy + break + case 'success': + delete el.clipSuccess + break + case 'error': + delete el.clipError + break + } + } +} diff --git a/src/core/directives/v-permissions.js b/src/core/directives/v-permissions.js new file mode 100644 index 0000000..9b48cc1 --- /dev/null +++ b/src/core/directives/v-permissions.js @@ -0,0 +1,9 @@ +import authorizer from '@/core/authorize' + +export default { + mounted (el, binding) { + if (!authorizer.hasPermissions(binding.value)) { + el.parentNode && el.parentNode.removeChild(el) + } + } +} diff --git a/src/core/directives/v-roles.js b/src/core/directives/v-roles.js new file mode 100644 index 0000000..b8175b1 --- /dev/null +++ b/src/core/directives/v-roles.js @@ -0,0 +1,9 @@ +import authorizer from '@/core/authorize' + +export default { + mounted (el, binding) { + if (!authorizer.hasRoles(binding.value)) { + el.parentNode && el.parentNode.removeChild(el) + } + } +} diff --git a/src/core/directives/v-trim.js b/src/core/directives/v-trim.js new file mode 100644 index 0000000..756de85 --- /dev/null +++ b/src/core/directives/v-trim.js @@ -0,0 +1,34 @@ +export default { + mounted (el) { + let input = el + let classes = input.getAttribute('class') || '' + if (classes != null) { + classes = classes.split(' ') + } + if (classes == null) { + classes = '' + } + // 输入框: + if (classes.indexOf('el-input') > -1) { + input = input.querySelector('input') + } + // 多行输入框: + if (classes.indexOf('el-textarea') > -1) { + input = input.querySelector('textarea') + } + // 失去焦点时去掉两侧空格 + input.addEventListener('blur', (e) => { + e.target.value = e.target.value.trim() + input.dispatchEvent(new Event('input')) + }) + // 回车时去掉两侧空格(仅输入框) + if (classes.indexOf('el-input') > -1) { + input.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + e.target.value = e.target.value.trim() + input.dispatchEvent(new Event('input')) + } + }) + } + } +} diff --git a/src/core/filters/index.js b/src/core/filters/index.js new file mode 100644 index 0000000..bee4802 --- /dev/null +++ b/src/core/filters/index.js @@ -0,0 +1,24 @@ +export default { + // 性别 + sex (value) { + if (value === '1') { + return '男' + } + if (value === '0') { + return '女' + } + return '未知' + }, + // 是否文案(Yes Or No) + yONText(value) { + return value ? '是' : '否' + }, + // 是否启用文案 + enableText (value) { + return value ? '启用' : '禁用' + }, + // 是否禁用文案 + disabledText (value) { + return value ? '禁用' : '启用' + } +} diff --git a/src/core/plugins/bus.js b/src/core/plugins/bus.js new file mode 100644 index 0000000..8d4a417 --- /dev/null +++ b/src/core/plugins/bus.js @@ -0,0 +1,73 @@ +/** + * 事件总线,用于进行全局的事件监听和触发 + * 使用方法: + * this.$bus + * .on('confirm', () => {}) + * .on('cancel', () => {}) + */ +class EventBus { + map = {} + + /** + * 调用事件 + * + * @param arguments[0] 事件名称 + * @param arguments[1,N] 事件参数 + */ + emit () { + const args = [...arguments] + const eventName = args.shift() + const eventCallback = this.map[eventName] + if (eventCallback == null || typeof eventCallback !== 'function') { + throw new Error(`无法触发${eventName}事件!`) + } + eventCallback.apply(null, args) + return this + } + + /** + * 监听事件 + * + * @param name 事件名称 + * @param callback 事件回调函数 + * @param override 如果已存在事件,是否覆盖 + */ + on (name, callback, override = false) { + if (typeof callback !== 'function') { + throw new Error(`${name}事件回调必须为函数!`) + } + if (this.map[name] != null) { + if (!override) { + throw new Error(`事件总线中已存在${name}事件!`) + } + } + this.map[name] = callback + return this + } + + /** + * 删除事件 + * + * @param args 事件名称 | 事件名称数组 | 获取事件名称函数 + */ + delete (args) { + if (args == null) { + throw new Error('事件名称不可为空') + } + let names = [args] + if (typeof args === 'function') { + names = args() + } else if (args instanceof Array) { + names = args + } else if (typeof args !== 'string') { + throw new Error('事件名称必须为字符串、数组或函数!') + } + names.forEach(name => { + if (this.map[name] != null) { + delete this.map[name] + } + }) + return this + } +} +export default new EventBus() diff --git a/src/core/plugins/cache.js b/src/core/plugins/cache.js new file mode 100644 index 0000000..88f7083 --- /dev/null +++ b/src/core/plugins/cache.js @@ -0,0 +1,241 @@ +import constants from './consts' +const isDebug = import.meta.env.VITE_APP_DEBUG === 'on' + +/** + * 缓存值包,缓存存储的最小单位 + * 目的:实现数据的准确存储和超时清理 + */ +class ValuePackage { + // 值类型 + type + // 值 + value + // 写入缓存的时间 + birthtime + // 超时时间 + expiredTime + constructor ({ type, value, birthtime, expiredTime }) { + this.type = type + this.value = value + this.birthtime = birthtime + this.expiredTime = expiredTime + } + + /** + * 获取值 + */ + getValue () { + // 日期 + if (this.type === 'date') { + return new Date(this.value) + } + // 对象 + if (this.type === 'object') { + return JSON.parse(this.value) + } + return this.value + } + + /** + * 判断值包是否已过期 + */ + expired () { + if (this.expiredTime == null) { + return false + } + return new Date().getTime() > this.expiredTime + } + + /** + * 验证是否为正确的值包 + * + * @returns {boolean} + */ + isCorrectPackage () { + return this.type != null && this.value != null && this.birthtime != null + } +} + +/** + * 缓存 + */ +class Cache { + // 缓存类型 + #cacheType + // 缓存策略实例 + #strategyInstance + // 缓存类型和实例的映射 + static #strategyMap = { + [constants.CACHE_TYPE.LOCAL]: window.localStorage, + [constants.CACHE_TYPE.SESSION]: window.sessionStorage, + [constants.CACHE_TYPE.MEMORY]: {} + } + + /** + * 缓存构造器 + * + * @param cacheType 缓存类型,从plugins/consts.js/CACHE_TYPE中获取 + */ + constructor (cacheType) { + this.#cacheType = cacheType + this.#strategyInstance = Cache.#strategyMap[cacheType] + if (this.#strategyInstance == null) { + throw new Error(`构建缓存对象失败,未找到缓存类型${cacheType}`) + } + } + + /** + * 根据key从缓存中获取值 + * + * @param key 缓存键 + * @param defaultValue 默认值,如果没有值则返回该值 + * @returns {*} + */ + get (key, defaultValue = null) { + if (key == null) { + return defaultValue + } + this.#log('get') + this.#trace(`key=${key}`) + this.#trace('default value=', defaultValue) + // 获取值包 + let valuePackage + if (this.#cacheType === constants.CACHE_TYPE.MEMORY) { + valuePackage = this.#strategyInstance[key] + } else { + valuePackage = this.#strategyInstance.getItem(key) + } + if (valuePackage == null) { + this.#trace('result=', defaultValue) + return defaultValue + } + // 从storage中获取的值为字符串,需要转换为JSON对象 + if (typeof valuePackage === 'string') { + try { + valuePackage = new ValuePackage(JSON.parse(valuePackage)) + // 不是正确的值包(可能是通过缓存对象写入的),直接返回 + if (!valuePackage.isCorrectPackage()) { + this.#trace('result=', valuePackage) + return valuePackage + } + } catch (e) { + // 不是JSON字符串(可能是通过缓存对象写入的),直接返回 + this.#trace('result=', valuePackage) + return valuePackage + } + } + this.#trace('value package=', valuePackage) + // 验证缓存是否过期 + if (valuePackage.expired()) { + this.#trace('value expired!', `birth time=${valuePackage.birthtime}`, `expired time=${valuePackage.expiredTime}`) + this.remove(key) + this.#trace('result=', defaultValue) + return defaultValue + } + this.#trace('result=', valuePackage.getValue()) + return valuePackage.getValue() + } + + /** + * 添加至缓存 + * + * @param key 缓存键 + * @param value 缓存值 + * @param timeout 超时时间 + * @returns {Promise|void|void} + */ + set (key, value, timeout = -1) { + if (key == null || key === '') { + return + } + const valuePackage = this.#getValuePackage(value, timeout) + this.#log('set') + this.#trace(`key=${key}`) + this.#trace('value=', value) + this.#trace(`timeout=${timeout}ms`) + if (this.#cacheType === constants.CACHE_TYPE.MEMORY) { + this.#strategyInstance[key] = valuePackage + return + } + this.#strategyInstance.setItem(key, JSON.stringify(valuePackage)) + } + + /** + * 根据key从缓存中删除值 + * + * @param key + * @returns {*|void} + */ + remove (key) { + this.#log('remove', `key=${key}`) + this.#trace(`key=${key}`) + if (this.#cacheType === constants.CACHE_TYPE.MEMORY) { + delete this.#strategyInstance[key] + return + } + this.#strategyInstance.removeItem(key) + } + + /** + * 获取值包对象 + * + * @param value 值 + * @param timeout 超时时间 + * @returns {*|{birthtime: number, type: *, value: number | string, expiredTime: (null|number)}} + */ + #getValuePackage (value, timeout) { + let actualValue = value + let type = typeof value + // 日期,转换为时间戳 + if (value instanceof Date) { + type = 'date' + actualValue = value.getTime() + } + // 对象,转换为json字符串 + if (type === 'object') { + actualValue = JSON.stringify(value) + } + // 获取值创建时间 + const birthtime = new Date().getTime() + const expiredTime = timeout === -1 ? null : (birthtime + timeout) + // 构建值包对象并返回 + return new ValuePackage({ + type, + value: actualValue, + birthtime, + expiredTime + }) + } + + /** + * 日志输出 + */ + // eslint-disable-next-line no-dupe-class-members + #log () { + if (!isDebug) { + return + } + const args = [...arguments] + const directive = args.shift() + console.log(new Date().toLocaleString(), `[CACHE:${this.#cacheType}:${directive}]`) + } + + /** + * 跟踪输出 + */ + // eslint-disable-next-line no-dupe-class-members + #trace () { + if (!isDebug) { + return + } + console.log.apply(null, [' ', ...arguments]) + } +} +const cachePackage = new Cache(constants.CACHE_TYPE.LOCAL) +// 构建基于Vuex的缓存对象 +cachePackage[constants.CACHE_TYPE.MEMORY] = new Cache(constants.CACHE_TYPE.MEMORY) +// 构建基于SessionStorage的缓存对象 +cachePackage[constants.CACHE_TYPE.SESSION] = new Cache(constants.CACHE_TYPE.SESSION) +// 构建基于LocalStorage的缓存对象 +cachePackage[constants.CACHE_TYPE.LOCAL] = new Cache(constants.CACHE_TYPE.LOCAL) +export default cachePackage diff --git a/src/core/plugins/consts.js b/src/core/plugins/consts.js new file mode 100644 index 0000000..da697f0 --- /dev/null +++ b/src/core/plugins/consts.js @@ -0,0 +1,77 @@ +/** + * 常量定义 + */ +export default { + // 超级管理员编码 + ROLE_ADMIN: 'admin', + // 请求头:操作平台 + HEADER_PLATFORM: 'eva-platform', + // 请求头:登录令牌 + HEADER_TOKEN: 'eva-auth-token', + // 请求头:操作类型 + HEADER_OPERA_TYPE: 'eva-opera-type', + // 请求头:下载文件名 + HEADER_DOWNLOAD_FILENAME: 'eva-download-filename', + // 默认错误提示 + DEFAULT_ERROR_MESSAGE: '操作失败,请稍后重试或联系系统管理员', + // 栏目类型 + CATEGORY_TYPE: { + DEFAULT: 'DEFAULT', + OUT_LINK: 'OUT_LINK', + IN_LINK: 'IN_LINK' + }, + // 文章状态 + ARTICLE_STATUS: { + DRAFT: 'DRAFT', + OFFLINE: 'OFFLINE', + ONLINE: 'ONLINE' + }, + // 模板参数 + TEMPLATE_PARAMETERS: { + categoryUid: { + label: '目标栏目', + component: 'CategoryInput' + }, + categoryUids: { + label: '目标栏目集', + component: 'MultipleCategoryInput' + }, + articleUid: { + label: '目标文章', + component: 'ArticleInput' + }, + resourceGroupUids: { + label: '目标资源组集', + component: 'MultipleResourceGroupInput' + }, + keyword: { + label: '搜索关键字', + component: 'KeywordInput' + } + }, + // 系统菜单 + SYSTEM_MENU: { + // 目录 + TYPE_DIR: 'DIR', + // 外部链接 + TYPE_EXTERNAL: 'EXTERNAL', + // IFrame嵌套 + TYPE_IFRAME: 'IFRAME' + }, + // 排序方式 + ORDER_BY: { + // 升序 + ASC: 'ASC', + // 降序 + DESC: 'DESC' + }, + // 缓存类型 + CACHE_TYPE: { + // 内存缓存 + MEMORY: 'memory', + // localStorage缓存 + LOCAL: 'local', + // sessionStorage缓存 + SESSION: 'session' + } +} diff --git a/src/core/plugins/download.js b/src/core/plugins/download.js new file mode 100644 index 0000000..4cab2b2 --- /dev/null +++ b/src/core/plugins/download.js @@ -0,0 +1,24 @@ +import fileDownload from 'js-file-download' +import constants from '@/core/plugins/consts' +import message from './message' + +/** + * 下载文件 + * + * @param response 文件流 + * @param decode 是否解码文件名称 + * @param mime mime类型 + * @param filename 文件名称 + */ +export default function (response, decode = true, mime = 'application/octet-stream', filename) { + if (response.headers['content-length'] === '0') { + message.error('无法下载文件,可能因为数据处理错误导致文件大小为0B') + return + } + if (filename == null) { + // 下载接口在响应头eva-download-filename中存放文件名称,接口返回的文件名称需采用url encode的方式进行编码 + filename = decode ? decodeURI(response.headers[constants.HEADER_DOWNLOAD_FILENAME]) + : response.headers[constants.HEADER_DOWNLOAD_FILENAME] + } + fileDownload(response.data, filename, mime) +} diff --git a/src/core/plugins/index.js b/src/core/plugins/index.js new file mode 100644 index 0000000..f627f93 --- /dev/null +++ b/src/core/plugins/index.js @@ -0,0 +1,66 @@ +import constants from './consts' +import message from './message' +import messagebox from './messagebox' +import cache from './cache' +import download from './download' +import authorizer from '../authorize' +import Dict from './system.dict' +import getConfigValue from './system.config' +import dayjs from 'dayjs' +import filters from '../filters' +import bus from './bus' +import { useDefaultStore } from '@/core/store' + +export default { + install (app) { + // 提醒对象 + app.config.globalProperties.$tip = message + // 提示框对象 + app.config.globalProperties.$dialog = messagebox + // 缓存对象 + app.config.globalProperties.$cache = cache + // 常量 + app.config.globalProperties.$const = constants + // 下载文件 + app.config.globalProperties.$download = download + // 获取配置值方法 + app.config.globalProperties.$c = getConfigValue + // 获取字典标签方法 + app.config.globalProperties.$d = Dict.getDictLabel + // 获取字典配置方法 + app.config.globalProperties.$dc = Dict.getDictConfig + // dayjs对象 + app.config.globalProperties.$dayjs = dayjs + // 判断是否为测试模式 + app.config.globalProperties.$isTesting = import.meta.env.VITE_APP_MODE === 'testing' + // 判断是否为DEBUG模式 + app.config.globalProperties.$isDebugging = import.meta.env.VITE_APP_DEBUG === 'on' + // 获取图片访问路径 + app.config.globalProperties.$getImageURL = (fileKey) => { + if (fileKey.startsWith('http://') || fileKey.startsWith('https://')) { + return fileKey + } + return import.meta.env.VITE_APP_COMMON_IMAGE_PREFIX + fileKey + } + // 获取附件下载路径 + app.config.globalProperties.$getAttachURL = (fileKey, filename) => { + let url = import.meta.env.VITE_APP_COMMON_ATTACH_PREFIX + fileKey + if (filename != null && filename.trim().length !== 0) { + url += `&fn=${filename}` + } + return url + } + // 增加全局权限方法 + app.config.globalProperties.$hasRoles = authorizer.hasRoles + app.config.globalProperties.$hasAnyRoles = authorizer.hasAnyRoles + app.config.globalProperties.$hasPermissions = authorizer.hasPermissions + app.config.globalProperties.$hasAnyPermissions = authorizer.hasAnyPermissions + app.config.globalProperties.$isSuperAdmin = authorizer.isSuperAdmin + // 全局过滤方法 + app.config.globalProperties.$filters = filters + // 自定义事件 + app.config.globalProperties.$bus = bus + // 默认store + app.config.globalProperties.$defaultStore = useDefaultStore() + } +} diff --git a/src/core/plugins/message.js b/src/core/plugins/message.js new file mode 100644 index 0000000..a102dbd --- /dev/null +++ b/src/core/plugins/message.js @@ -0,0 +1,44 @@ +import { ElMessage } from 'element-plus' +const isDebug = import.meta.env.VITE_APP_DEBUG === 'on' + +export default { + ...ElMessage, + /** + * 接口调用成功 + * + * @param message 提示消息 + */ + apiSuccess (message) { + ElMessage.success(message) + }, + /** + * 接口调用失败 + * + * @param err 错误对象 + */ + apiFailed (err) { + try { + if (isDebug) { + console.error(new Date().toLocaleString(), '[REQUEST FAILED]', err) + } + // 内容为取消时,不做任何提示 + if (err === 'cancel') { + return + } + // 下载接口返回的是Blob,此时需要解析为JSON并提示错误消息。(下载接口出现业务失败的情况,例如无权限等) + if (err instanceof Blob) { + const fileReader = new FileReader() + fileReader.readAsText(err, 'utf-8') + fileReader.onload = function () { + ElMessage.error(JSON.parse(fileReader.result).message) + } + return + } + if (!err.message.startsWith('#ignore#')) { + ElMessage.error(err.message) + } + } catch (e) { + console.error('apiFailed error', e) + } + } +} diff --git a/src/core/plugins/messagebox.js b/src/core/plugins/messagebox.js new file mode 100644 index 0000000..45ebf2c --- /dev/null +++ b/src/core/plugins/messagebox.js @@ -0,0 +1,111 @@ +import { ElMessageBox } from 'element-plus' + +export default { + ...ElMessageBox, + /** + * 成功提示 + * + * @param message 消息 + * @param title 标题 + * @param delay 延迟时间(毫秒) + * @param extConfig 扩展配置 + */ + success (message, title = '操作成功', delay = 300, extConfig = {}) { + return new Promise((resolve, reject) => { + setTimeout(() => { + ElMessageBox.alert(message, title, { + customClass: 'success-message-box', + type: 'success', + ...extConfig + }) + .then(() => { + resolve() + }) + .catch(e => { + reject(e) + }) + }, delay) + }) + }, + /** + * 删除二次确认 + * + * @param message 消息内容 + * @param extConfig 扩展配置 + * @returns {Promise} + */ + deleteConfirm (message, extConfig = {}) { + return ElMessageBox.confirm(message, '删除提醒', { + customClass: 'delete-message-box', + confirmButtonText: '确认删除', + cancelButtonText: '取消', + confirmButtonClass: 'el-button--danger', + type: 'warning', + ...extConfig + }) + }, + /** + * 禁用二次确认 + * + * @param message 消息内容 + * @param extConfig 扩展配置 + * @returns {Promise} + */ + disableConfirm (message, extConfig = {}) { + return ElMessageBox.confirm(message, '禁用提醒', { + confirmButtonText: '确认禁用', + cancelButtonText: '取消', + confirmButtonClass: 'el-button--danger', + type: 'warning', + ...extConfig + }) + }, + /** + * 启用二次确认 + * + * @param message 消息内容 + * @param extConfig 扩展配置 + * @returns {Promise} + */ + enableConfirm (message, extConfig = {}) { + return ElMessageBox.confirm(message, '启用提醒', { + confirmButtonText: '确认启用', + cancelButtonText: '取消', + type: 'warning', + ...extConfig + }) + }, + /** + * 导出二次确认 + * + * @param message 消息内容 + * @param extConfig 扩展配置 + * @returns {Promise} + */ + exportConfirm (message, extConfig = {}) { + return ElMessageBox.confirm(message, '导出提醒', { + confirmButtonText: '确认导出', + cancelButtonText: '取消', + type: 'warning', + ...extConfig + }) + }, + /** + * 重要提醒 + * + * @param message 消息内容 + * @param title 提醒标题 + * @param extConfig 扩展配置 + * @returns {Promise} + */ + attentionConfirm (message, title = '重要提醒', extConfig = {}) { + return ElMessageBox.confirm(message, title, { + showCancelButton: false, + showClose: false, + closeOnClickModal: false, + closeOnPressEscape: false, + type: 'warning', + ...extConfig + }) + } +} diff --git a/src/core/plugins/system.config.js b/src/core/plugins/system.config.js new file mode 100644 index 0000000..c682a89 --- /dev/null +++ b/src/core/plugins/system.config.js @@ -0,0 +1,26 @@ +import { useDefaultStore } from '../store' + +// 获取Store +function getStore () { + return useDefaultStore() +} + +/** + * 根据配置编码获取配置值 + * + * @param code 配置编码 + */ +function getConfigValue (code) { + const clientConfig = getStore().clientConfig + if (clientConfig == null) { + return null + } + const configMap = clientConfig.configMap + const data = configMap[code] + if (data == null) { + return null + } + return data.value +} + +export default getConfigValue diff --git a/src/core/plugins/system.dict.js b/src/core/plugins/system.dict.js new file mode 100644 index 0000000..ae31944 --- /dev/null +++ b/src/core/plugins/system.dict.js @@ -0,0 +1,72 @@ +import { useDefaultStore } from '../store' + +// 获取Store +function getStore () { + return useDefaultStore() +} + +/** + * 根据编码表达式获取字典或数据标签 + * + * @param codeExpress 编码表达式 + * 语法1:“字典编码.数据编码”,如GENDER.MALE + * 语法2:“字典编码”,如GENDER + */ +function getDictLabel (codeExpress) { + const clientConfig = getStore().clientConfig + if (clientConfig == null) { + return '' + } + const codes = codeExpress.split('.') + const dictMap = clientConfig.dictMap + const dictCode = codes[0] + const dataValue = codes[1] + const dict = dictMap[dictCode] + if (dict == null) { + return codeExpress + } + // 如果不存在数据编码,则直接返回字典名称 + if (dataValue == null) { + return dict.name + } + const data = dict.dataList.find(d => d.value === dataValue) + if (data == null) { + return codeExpress + } + return data.label +} + +/** + * 根据编码表达式获取字典数据配置 + * + * @param codeExpress 编码表达式 + * 语法:“字典编码.数据编码”,如GENDER.MALE + */ +function getDictConfig (codeExpress) { + const clientConfig = getStore().clientConfig + if (clientConfig == null) { + return null + } + const codes = codeExpress.split('.') + const dictMap = clientConfig.dictMap + const dictCode = codes[0] + const dataValue = codes[1] + const dict = dictMap[dictCode] + if (dict == null) { + return null + } + // 如果不存在数据编码,则直接返回null + if (dataValue == null) { + return null + } + const data = dict.dataList.find(d => d.value === dataValue) + if (data == null) { + return null + } + return data.config +} + +export default { + getDictLabel, + getDictConfig +} diff --git a/src/core/store/index.js b/src/core/store/index.js new file mode 100644 index 0000000..64f2e28 --- /dev/null +++ b/src/core/store/index.js @@ -0,0 +1,64 @@ +import { defineStore } from 'pinia' +import { fetchConfig, getUserInfo } from '@/api/system' + +export const useDefaultStore = defineStore('default', { + state: () => ({ + // 登录用户信息 + userInfo: null, + // 首页 + homePage: null, + // 客户端配置 + clientConfig: null, + // 菜单 + menuData: { + // 菜单列表 + list: [], + // 是否收起 + collapse: false + }, + // 是否展示登录窗口 + visibleLoginWindow: false, + // 缓存内容 + cache: {} + }), + actions: { + // 切换菜单收缩 + switchCollapseMenu (value) { + if (value != null) { + this.menuData.collapse = value + } else { + this.menuData.collapse = !this.menuData.collapse + } + window.localStorage.setItem('MENU_STATUS', this.menuData.collapse) + }, + /** + * 刷新用户信息 + */ + refreshUserInfo () { + return new Promise((resolve, reject) => { + getUserInfo() + .then(userInfo => { + // 初始化客户端配置,避免客户端配置还未获取到导致页面渲染不能获取到配置信息 + fetchConfig() + .then(config => { + // 存储登录用户信息 + this.userInfo = userInfo + // 存储客户端配置 + this.clientConfig = config + resolve({ userInfo, config }) + }) + .catch(e => { + reject({ + // 指定错误类型,用于路由至错误页 + type: 'config-load-failed', + message: e.message + }) + }) + }) + .catch(e => { + reject(e) + }) + }) + } + } +}) diff --git a/src/core/utils/aes.js b/src/core/utils/aes.js new file mode 100644 index 0000000..975e06d --- /dev/null +++ b/src/core/utils/aes.js @@ -0,0 +1,46 @@ +import CryptoJS from 'crypto-js' + +const KEY = import.meta.env.VITE_APP_ENCRYPT_REQUEST_KEY +const IV = import.meta.env.VITE_APP_ENCRYPT_REQUEST_IV + +export default { + /** + * 加密 + * + * @param plaintext 明文 + * @returns {string} + */ + encrypt (plaintext) { + const key = CryptoJS.enc.Utf8.parse(KEY) + const secretData = CryptoJS.enc.Utf8.parse(plaintext) + const encrypted = CryptoJS.AES.encrypt( + secretData, + key, + { + iv: CryptoJS.enc.Utf8.parse(IV), + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + } + ) + return encrypted.toString() + }, + /** + * 解密 + * + * @param cipherText 密文 + * @returns {*} + */ + decrypt (cipherText) { + const key = CryptoJS.enc.Utf8.parse(KEY) + const decrypt = CryptoJS.AES.decrypt( + cipherText, + key, + { + iv: CryptoJS.enc.Utf8.parse(IV), + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + } + ) + return CryptoJS.enc.Utf8.stringify(decrypt).toString() + } +} diff --git a/src/core/utils/form.js b/src/core/utils/form.js new file mode 100644 index 0000000..6d792a8 --- /dev/null +++ b/src/core/utils/form.js @@ -0,0 +1,94 @@ +/** + * 验证身份证号码 + * + * @param rule 规则 + * @param value 值 + * @param callback 回调 + */ +export function checkIdNumber (rule, value, callback) { + if (value == null || value.trim() === '') { + callback() + return + } + if (!/^\d{16}(\d{2})?$/.test(value)) { + callback(new Error('身份证号码格式不正确')) + return + } + callback() +} + +/** + * 验证手机号码 + * + * @param rule 规则 + * @param value 值 + * @param callback 回调 + */ +export function checkMobile (rule, value, callback) { + if (value == null || value.trim() === '') { + callback() + return + } + if (!/^1[3456789]\d{9}$/.test(value)) { + callback(new Error('手机号码格式不正确')) + return + } + callback() +} + +/** + * 验证邮箱 + * + * @param rule 规则 + * @param value 值 + * @param callback 回调 + */ +export function checkEmail (rule, value, callback) { + if (value == null || value.trim() === '') { + callback() + return + } + if (!/^\S+@\S+\.\S+$/.test(value)) { + callback(new Error('邮箱格式不正确')) + return + } + callback() +} + +/** + * 验证网络路径 + * + * @param rule 规则 + * @param value 值 + * @param callback 回调 + */ +export function checkUrl (rule, value, callback) { + if (value == null || value.trim() === '') { + callback() + return + } + if (!/^(http|https):\/\/.+$/.test(value)) { + callback(new Error('网络路径需以"http://"或"ttps://"开头')) + return + } + callback() +} + +/** + * 验证相对路径 + * + * @param rule 规则 + * @param value 值 + * @param callback 回调 + */ +export function checkRelativeUrl (rule, value, callback) { + if (value == null || value.trim() === '') { + callback() + return + } + if (!/^\/.*$/.test(value)) { + callback(new Error('内部链接需以"/"开头')) + return + } + callback() +} diff --git a/src/core/utils/request/index.js b/src/core/utils/request/index.js new file mode 100644 index 0000000..a952251 --- /dev/null +++ b/src/core/utils/request/index.js @@ -0,0 +1,167 @@ +import axios from 'axios' +import Cookies from 'js-cookie' +import pkg from '../../../../package.json' +import cache from './request.cache' +import twoFA from './request.2fa' +import secure from './request.secure' +import constants from '@/core/plugins/consts' +import AES from '../aes' +import { trim } from '@/core/utils/util' +import { useDefaultStore } from '@/core/store/index' + +const isDebug = import.meta.env.VITE_APP_DEBUG === 'on' +// 输出日志 +const log = function () { + if (!isDebug) { + return + } + // eslint-disable-next-line no-useless-call + console.log.apply(console, [new Date().toLocaleString(), '[REQUEST]', ...arguments]) +} + +// 默认配置 +axios.defaults.headers.common['Content-Type'] = 'application/json;charset=UTF-8' +const axiosInstance = axios.create({ + baseURL: import.meta.env.VITE_APP_API_PREFIX, + // 请求超时时间 + timeout: 60000 +}) + +// 新建请求拦截器 +axiosInstance.interceptors.request.use(config => { + // 参数去空格 + if (config.trim === true) { + if (config.data != null) { + config.data = trim(config.data) + } + if (config.params != null) { + config.params = trim(config.params) + } + } + // 参数加密 + if (config.secure === true) { + if (config.data != null) { + config.data = { + _p: AES.encrypt(JSON.stringify(config.data)) + } + } + if (config.params != null) { + config.params = { + _p: AES.encrypt(JSON.stringify(config.params)) + } + } + } + // 导出处理 + if (config.download === true) { + config.responseType = 'blob' + } + // 设置操作平台 + config.headers[constants.HEADER_PLATFORM] = `pc-${pkg.version}` + // 设置认证头 + const authToken = Cookies.get(constants.HEADER_TOKEN) + if (authToken != null) { + config.headers[constants.HEADER_TOKEN] = authToken + } + return config +}, function (error) { + return Promise.reject(error) +}) +// 新建响应拦截器 +axiosInstance.interceptors.response.use((response) => { + // 请求失败 + if (response.status !== 200) { + return Promise.reject(new Error(constants.DEFAULT_ERROR_MESSAGE)) + } + // 下载接口处理 + if (response.headers[constants.HEADER_OPERA_TYPE] === 'download') { + if (response.config.responseType !== 'blob') { + return Promise.reject(new Error('下载接口返回类型错误,请检查接口定义是否缺少download标识!')) + } + // Blob类型数据,导出下载文件时,如果接口未正确执行,返回类型为Blob + return new Promise((resolve, reject) => { + if (response.data.type !== 'application/json') { + resolve(response) + return + } + const blob = new Blob([response.data]) + const fileReader = new FileReader() + // 读取Blob内容 + fileReader.readAsText(blob, 'utf-8') + fileReader.onload = function () { + const result = JSON.parse(fileReader.result) + // 未登录 + if (result.code === 401) { + if (response.config.autoLogin !== false) { + openLoginFormWindow() + } + reject(new Error('#ignore#')) + return + } + // 业务失败 + if (!result.success) { + reject(result) + return + } + reject(result) + } + }) + } + // 参数解密 + if (response.config.secure === true) { + if (typeof response.data === 'string') { + response.data = JSON.parse(AES.decrypt(response.data)) + } + } + log( + response.config.method.toUpperCase(), + response.config.secure === true ? '[SECURE]' : '', + response.config.url, + response.data + ) + // 纯字符串 + if (typeof response.data === 'string') { + return Promise.reject(new Error(`接口${response.config.url}响应格式不正确`)) + } + // 未登录 + if (response.data.code === 401) { + if (response.config.autoLogin !== false) { + openLoginFormWindow() + } + return Promise.reject(new Error('#ignore#')) + } + // 业务失败 + if (!response.data.success) { + return Promise.reject(response.data) + } + return Promise.resolve(response.data.data) +}, function (error) { + // 客户端错误 + if (error.response.status >= 400 && error.response.status < 500) { + return Promise.reject(new Error(`客户端错误,请检查参数,状态码:${error.response.status}`)) + } + // 服务端错误 + if (error.response.status >= 500 && error.response.status < 600) { + return Promise.reject(new Error(constants.DEFAULT_ERROR_MESSAGE)) + } + // 请求超时 + if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) { + return Promise.reject(new Error('服务器响应超时,请稍后再试')) + } + return Promise.reject(error) +}) + +// 添加缓存方法,实现数据自动读取缓存和写入缓存 +axiosInstance.cache = cache +// 添加2FA认证方法,实现自动二次认证 +axiosInstance.twoFA = twoFA +// 添加安全方法,实现参数加密和响应的自动解密 +axiosInstance.secure = secure + +/** + * 打开登录窗口 + */ +export const openLoginFormWindow = function () { + useDefaultStore().visibleLoginWindow = true +} + +export default axiosInstance diff --git a/src/core/utils/request/request.2fa.js b/src/core/utils/request/request.2fa.js new file mode 100644 index 0000000..f52e973 --- /dev/null +++ b/src/core/utils/request/request.2fa.js @@ -0,0 +1,143 @@ +import TwoFAWindow from '@/components/common/TwoFAWindow' +import Cache from '@/core/plugins/cache' +import secure from './request.secure' +import cache from './request.cache' +import AES from '@/core/utils/aes' +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import zhCn from 'element-plus/dist/locale/zh-cn' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import directives from '@/core/directives/index' +import plugins from '@/core/plugins' +import components from '@/components' + +const requestMethods = ['get', 'post', 'delete', 'put', 'head', 'options', 'patch', 'request'] + +/** + * 处理请求头,添加2fa密码头 + * + * @param method 请求方法 + * @param password 密码 + * @param argsArray 请求方法参数数组 + */ +function handleHeaders (method, password, argsArray) { + let config + if (method === 'post' || method === 'put' || method === 'patch') { + config = argsArray[2] + if (config == null) { + config = {} + argsArray[2] = config + } + } else { + config = argsArray[1] + if (config == null) { + config = {} + argsArray[1] = config + } + } + const headers = config.headers || {} + headers['X-2fa-Password'] = AES.encrypt(password) + config.headers = headers +} + +/** + * 创建2FA窗口 + * @param props 参数 + */ +function createTwoFAWindow (props) { + const twoFAWindowApp = createApp(TwoFAWindow, { + ...props + }) + // - 注入Pinia + twoFAWindowApp.use(createPinia()) + // - 注入组件库 + twoFAWindowApp.use(ElementPlus, { locale: zhCn }) + // - 注入自定义指令 + twoFAWindowApp.use(directives) + // - 注入插件 + twoFAWindowApp.use(plugins) + // - 注入自定义全局组件 + twoFAWindowApp.use(components) + // - 注入图标 + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + twoFAWindowApp.component(key, component) + } + return twoFAWindowApp.mount(document.createElement('div')) +} + +/** + * 开启请求缓存 + * 实现逻辑:调用twoFA后,产生axios实例的代理对象,覆盖requestMethods变量中定义的方法(如get方法) + * 实现先打开2FA认证窗口,在点击认证窗口的【确认】按钮后获取到2FA认证密码,并将密码写入到请求头中后,再发起接口请求。 + * + * @param props TwoFAWindow组件参数 + * @usage + * request + * // 开启2FA认证 + * .twoFA() + * .[post|get|delete|put|head|options|patch|request]() + * @returns {{isExtendsAxiosInstance: boolean}} + */ +export default function (props = {}) { + const axiosProxy = {} + // 覆盖请求方法,实现2FA自动认证 + for (const method of requestMethods) { + // 获取到axios原本的请求方法 + const originalMethod = this[method] + // 给代理对象添加请求方法 + axiosProxy[method] = function () { + return new Promise((resolve, reject) => { + // 从缓存中获取2FA密码 + const password = Cache.memory.get('2fa-password') + // 记住我的情况,直接调用业务接口 + if (password != null) { + const argsArray = [...arguments] + handleHeaders(method, password, argsArray) + // 执行业务接口请求 + originalMethod.apply(this, argsArray) + .then(data => { + resolve(data) + }) + .catch(error => { + reject(error) + }) + return + } + // 未记住,或密码存储已超时 + // 构建2FA认证窗口 + const $twoFaWindow = createTwoFAWindow(props) + $twoFaWindow.$bus + // 绑定2FA确认事件 + .on('confirm', (form) => { + $twoFaWindow.isWorking = true + // 补充2fa密码请求头 + const argsArray = [...arguments] + handleHeaders(method, form.password, argsArray) + // 执行业务接口请求 + originalMethod.apply(this, argsArray) + .then(data => { + $twoFaWindow.close() + resolve(data) + }) + .catch(error => { + $twoFaWindow.close() + reject(error) + }) + .finally(() => { + $twoFaWindow.isWorking = false + }) + }) + // 绑定2FA取消事件 + .on('cancel', () => { + // 标记为#ignore#不会进行全局提示 + reject(new Error('#ignore#: cancel 2fa')) + }) + }) + } + } + // 支持连续调用 + axiosProxy.secure = secure + axiosProxy.cache = cache + return axiosProxy +} diff --git a/src/core/utils/request/request.cache.js b/src/core/utils/request/request.cache.js new file mode 100644 index 0000000..faa1dbc --- /dev/null +++ b/src/core/utils/request/request.cache.js @@ -0,0 +1,65 @@ +import cache from '@/core/plugins/cache' +import constants from '@/core/plugins/consts' +import secure from './request.secure' +import twoFA from './request.2fa' +const requestMethods = ['get', 'post', 'delete', 'put', 'head', 'options', 'patch', 'request'] + +/** + * 开启请求缓存 + * 实现逻辑:调用cache后,产生axios实例的代理对象,覆盖requestMethods变量中定义的方法(如get方法) + * 实现先从缓存中获取数据,如果未能获取到数据,则使用axios实例发起请求,并将请求结果缓存起来。 + * + * @param key 缓存的key + * @param config 缓存配置 + * @usage + * request + * // 开启缓存 + * .cache( + * // 缓存的key + * 'key', + * { + * // 缓存类型 + * type: constants.CACHE_TYPE.SESSION, + * // 超时时间,单位毫秒 + * timeout: 3000 + * } + * ) + * .[post|get|delete|put|head|options|patch|request]() + * @returns {{isExtendsAxiosInstance: boolean}} + */ +export default function (key, config = { type: constants.CACHE_TYPE.SESSION, timeout: -1 }) { + if (config.type == null) { + throw new Error('缺少缓存类型') + } + const axiosProxy = {} + // 覆盖请求方法,实现先从缓存中获取数据 + for (const method of requestMethods) { + // 获取到axios原本的请求方法 + const originalMethod = this[method] + // 给代理对象添加请求方法 + axiosProxy[method] = function () { + // 获取缓存数据 + const data = cache[config.type].get(key) + // 如果数据不为null,返回resolve promise对象 + if (data != null) { + return Promise.resolve(data) + } + // 如果数据为null,则调用原有请求方法 + return originalMethod.apply(this, arguments) + .then(data => { + if (data != null) { + // 缓存数据 + cache[config.type].set(key, data, config.timeout) + } + return Promise.resolve(data) + }) + .catch(error => { + return Promise.reject(error) + }) + } + } + // 支持连续调用 + axiosProxy.secure = secure + axiosProxy.twoFA = twoFA + return axiosProxy +} diff --git a/src/core/utils/request/request.secure.js b/src/core/utils/request/request.secure.js new file mode 100644 index 0000000..efe8fa9 --- /dev/null +++ b/src/core/utils/request/request.secure.js @@ -0,0 +1,68 @@ +import cache from './request.cache' +import twoFA from './request.2fa' +const requestMethods = ['get', 'post', 'delete', 'put', 'head', 'options', 'patch', 'request'] + +/** + * 处理请求配置,添加安全请求配置项 + * + * @param method 请求方法 + * @param argsArray 请求方法参数数组 + */ +function handleConfig (method, argsArray) { + let config + if (method === 'post' || method === 'put' || method === 'patch') { + config = argsArray[2] + if (config == null) { + config = {} + argsArray[2] = config + } + } else { + config = argsArray[1] + if (config == null) { + config = {} + argsArray[1] = config + } + } + config.secure = true +} + +/** + * 开启请求加密 + * 实现逻辑:调用secure后,产生axios实例的代理对象,覆盖requestMethods变量中定义的方法(如get方法) + * 实现修改config参数,为其添加secure安全标识配置。 + * + * @usage + * request + * // 开启安全请求 + * .secure() + * .[post|get|delete|put|head|options|patch|request]() + * @returns {{isExtendsAxiosInstance: boolean}} + */ +export default function () { + const axiosProxy = {} + // 覆盖请求方法,实现增加加密标识 + for (const method of requestMethods) { + // 获取到axios原本的请求方法 + const originalMethod = this[method] + // 给代理对象添加请求方法 + axiosProxy[method] = function () { + return new Promise((resolve, reject) => { + const argsArray = [...arguments] + // 增加加密标识 + handleConfig(method, argsArray) + // 执行业务接口请求 + originalMethod.apply(this, argsArray) + .then(data => { + resolve(data) + }) + .catch(error => { + reject(error) + }) + }) + } + } + // 支持连续调用 + axiosProxy.cache = cache + axiosProxy.twoFA = twoFA + return axiosProxy +} diff --git a/src/core/utils/util.js b/src/core/utils/util.js new file mode 100644 index 0000000..6bbba8e --- /dev/null +++ b/src/core/utils/util.js @@ -0,0 +1,46 @@ +import { Base64 } from 'js-base64' +/** + * 为对象、数组、字符串等数据去空 + * + * @param data 数据 + * @returns {string|null|*} + */ +export function trim (data) { + if (data == null) { + return null + } + if (typeof data === 'string') { + return data.trim() + } + if (data instanceof Array) { + for (const item of data) { + trim(item) + } + } + if (typeof data === 'object') { + for (const key in data) { + data[key] = trim(data[key]) + } + } + return data +} + +/** + * 转为base64 + * + * @param string + * @returns {string} + */ +export function encodeToBase64 (string) { + return Base64.encode(string) +} + +/** + * 解码Base64 + * + * @param base64 + * @returns {string} + */ +export function decodeFromBase64 (base64) { + return Base64.decode(base64) +} diff --git a/src/layouts/AppLayout.vue b/src/layouts/AppLayout.vue new file mode 100644 index 0000000..6170d3a --- /dev/null +++ b/src/layouts/AppLayout.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/layouts/TableLayout.vue b/src/layouts/TableLayout.vue new file mode 100644 index 0000000..94e0882 --- /dev/null +++ b/src/layouts/TableLayout.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..78612ca --- /dev/null +++ b/src/main.js @@ -0,0 +1,28 @@ +// import '@/assets/style/element/index.scss' +import '@/assets/style/index.scss' +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import ElementPlus from 'element-plus' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import router from './router/index' +import directives from './core/directives/index' +import components from './components/index' +import plugins from './core/plugins/index' + +const app = createApp(App) +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} +app.use(createPinia()) + .use(router) + .use(plugins) + .use(directives) + .use(components) + .use(ElementPlus, { + locale: zhCn + }) + .mount('#app') + +export default app diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..b1d058d --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,88 @@ +import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router' +import AppLayout from '@/layouts/AppLayout' +import { useDefaultStore } from '@/core/store' +import Message from '@/core/plugins/message' +const Login = () => import('@/views/login') +const Error = () => import('@/views/error') +// 路由模式 +let history = createWebHistory(import.meta.env.VITE_APP_CONTEXT_PATH) +if (import.meta.env.VITE_APP_ROUTER_MODE === 'hash') { + history = createWebHashHistory(import.meta.env.VITE_APP_CONTEXT_PATH) +} +const router = new createRouter({ + history, + routes: [ + // 登录 + { + name: 'login', + path: '/login', + component: Login + }, + // 错误页 + { + name: 'error', + path: '/error/:type', + component: Error + }, + // 内容页(动态加载) + { + name: 'layout', + path: '', + component: AppLayout, + children: [ + // iframe嵌入页 + { + path: 'iframe', + name: 'IFrame', + component: () => import('@/views/iframe') + } + ] + } + ] +}) +router.beforeEach((to, from, next) => { + // 无权访问&404页面可直接访问 + if (to.name === 'login' || to.name === 'error') { + next() + return + } + const defaultStore = useDefaultStore() + // 验证用户是否登录 + const userInfo = defaultStore.userInfo + if (userInfo != null) { + // 如果访问的是登录页面,则直接跳转至首页 + if (to.name === 'login') { + next({ name: 'index' }) + return + } + // 如果访问的是layout(回退时可能存在该情况),直接调整至首页 + if (to.name === 'layout') { + next({ name: 'index' }) + return + } + next() + return + } + defaultStore.refreshUserInfo() + .then(() => { + next() + }) + .catch(e => { + // 提示错误 + Message.apiFailed(e) + // 指定了错误类型,跳转到错误页面,错误页面将根据错误类型作出提示 + if (e.type !== undefined) { + next({ + name: 'error', + params: { + type: e.type + } + }) + return + } + // 其它情况,跳转到登录页 + next({ name: 'login' }) + }) +}) + +export default router diff --git a/src/views/cms/article.vue b/src/views/cms/article.vue new file mode 100644 index 0000000..a8b7d77 --- /dev/null +++ b/src/views/cms/article.vue @@ -0,0 +1,454 @@ + + + + diff --git a/src/views/cms/category.vue b/src/views/cms/category.vue new file mode 100644 index 0000000..60b6081 --- /dev/null +++ b/src/views/cms/category.vue @@ -0,0 +1,234 @@ + + + + diff --git a/src/views/cms/resource/group.vue b/src/views/cms/resource/group.vue new file mode 100644 index 0000000..e90012d --- /dev/null +++ b/src/views/cms/resource/group.vue @@ -0,0 +1,168 @@ + + + diff --git a/src/views/cms/template.vue b/src/views/cms/template.vue new file mode 100644 index 0000000..fbbae4c --- /dev/null +++ b/src/views/cms/template.vue @@ -0,0 +1,166 @@ + + + diff --git a/src/views/components.vue b/src/views/components.vue new file mode 100644 index 0000000..f7d8817 --- /dev/null +++ b/src/views/components.vue @@ -0,0 +1,809 @@ + + + + + diff --git a/src/views/error.vue b/src/views/error.vue new file mode 100644 index 0000000..d140a60 --- /dev/null +++ b/src/views/error.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/views/iframe.vue b/src/views/iframe.vue new file mode 100644 index 0000000..42490c4 --- /dev/null +++ b/src/views/iframe.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/views/index.vue b/src/views/index.vue new file mode 100644 index 0000000..be91b10 --- /dev/null +++ b/src/views/index.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/src/views/login.vue b/src/views/login.vue new file mode 100644 index 0000000..1b99364 --- /dev/null +++ b/src/views/login.vue @@ -0,0 +1,184 @@ + + + + + diff --git a/src/views/system/config.vue b/src/views/system/config.vue new file mode 100644 index 0000000..430b676 --- /dev/null +++ b/src/views/system/config.vue @@ -0,0 +1,161 @@ + + + diff --git a/src/views/system/dict.vue b/src/views/system/dict.vue new file mode 100644 index 0000000..ab52c5d --- /dev/null +++ b/src/views/system/dict.vue @@ -0,0 +1,212 @@ + + + diff --git a/src/views/system/icon.vue b/src/views/system/icon.vue new file mode 100644 index 0000000..788b8b4 --- /dev/null +++ b/src/views/system/icon.vue @@ -0,0 +1,131 @@ + + + + diff --git a/src/views/system/login-log.vue b/src/views/system/login-log.vue new file mode 100644 index 0000000..24c6e6b --- /dev/null +++ b/src/views/system/login-log.vue @@ -0,0 +1,148 @@ + + + diff --git a/src/views/system/menu.vue b/src/views/system/menu.vue new file mode 100644 index 0000000..8aa7086 --- /dev/null +++ b/src/views/system/menu.vue @@ -0,0 +1,322 @@ + + + + + diff --git a/src/views/system/notice.vue b/src/views/system/notice.vue new file mode 100644 index 0000000..657ffbc --- /dev/null +++ b/src/views/system/notice.vue @@ -0,0 +1,207 @@ + + + diff --git a/src/views/system/role.vue b/src/views/system/role.vue new file mode 100644 index 0000000..deefd5b --- /dev/null +++ b/src/views/system/role.vue @@ -0,0 +1,202 @@ + + + diff --git a/src/views/system/trace-log.vue b/src/views/system/trace-log.vue new file mode 100644 index 0000000..6fffde6 --- /dev/null +++ b/src/views/system/trace-log.vue @@ -0,0 +1,256 @@ + + + + + diff --git a/src/views/system/user.vue b/src/views/system/user.vue new file mode 100644 index 0000000..6f37b49 --- /dev/null +++ b/src/views/system/user.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..58865ab --- /dev/null +++ b/vite.config.js @@ -0,0 +1,42 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig, loadEnv } from 'vite' +import vue from '@vitejs/plugin-vue' +import eslint from 'vite-plugin-eslint' + +// https://vitejs.dev/config/ +export default ({mode}) => { + const apiPrefix = loadEnv(mode, process.cwd()).VITE_APP_API_PREFIX + const apiUrl = loadEnv(mode, process.cwd()).VITE_APP_API_URL + return defineConfig({ + plugins: [ + vue(), + eslint({ + include: ['src/**/*.js', 'src/**/*.vue'], + exclude: ['node_modules'] + }) + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + extensions: ['.vue', '.js'] + }, + server: { + host: '0.0.0.0', + port: 10086, + proxy: { + // 接口代理 + [apiPrefix]: { + target: apiUrl, + changeOrigin: true, + rewrite: (path) => path.replace(new RegExp(`^${apiPrefix}`), "") + }, + // 静态资源代理 + '/resource': { + target: apiUrl, + changeOrigin: true + } + } + } + }) +}