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>${#noparse>')\n .replace(/\\#\\{/g, '<#noparse>#{#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>/, '${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\n\n**文章管理**\n\n\n**栏目管理**\n\n\n**资源管理**\n\n\n\n**模板管理**\n\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等。
+
+## 项目预览
+
+**登录页**
+
+
+**文章管理**
+
+
+**栏目管理**
+
+
+**资源管理**
+
+
+
+**模板管理**
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Unicode
+ Font class
+ Symbol
+
+
+
查看项目
+
+
+
+
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 机构
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 合作
+ 
+
+
+
+
+ 组织机构
+ 
+
+
+
+
+ 专题
+ 
+
+
+
+
+ 组织机构
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 合作/招商
+ 
+
+
+
+
+ 组织机构
+ 
+
+
+
+
+ 专题
+ 
+
+
+
+
+ 公开
+ 
+
+
+
+
+ 动态
+ 
+
+
+
+
+ 合作
+ 
+
+
+
+
+ 动态
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 专题
+ 
+
+
+
+
+ 合作
+ 
+
+
+
+
+ 专题
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 政策
+ 
+
+
+
+
+ 政策
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 政策
+ 
+
+
+
+
+ 政策
+ 
+
+
+
+
+ 政策
+ 
+
+
+
+
+ 首页
+ 
+
+
+
+
+ 政府
+ 
+
+
+
+
+ 合作
+ 
+
+
+
+
+ 所有栏目列表
+ 
+
+
+
+
+ 树
+ 
+
+
+
+
+ 新建下级栏目
+ 
+
+
+
+
+ 栏目管理
+ 
+
+
+
+
+ 服务
+ 
+
+
+
+
+ 文章
+ 
+
+
+
+
+ 商品
+ 
+
+
+
+
+ 模板
+ 
+
+
+
+
+ 素材
+ 
+
+
+
+
+ 模板
+ 
+
+
+
+
+ 内容
+ 
+
+
+
+
+ 内容字段管理
+ 
+
+
+
+
+ 内容
+ 
+
+
+
+
+ 我的文章
+ 
+
+
+
+
+ 素材
+ 
+
+
+
+
+ 文章
+ 
+
+
+
+
+ 内容
+ 
+
+
+
+
+ 服务
+ 
+
+
+
+
+ 素材资源
+ 
+
+
+
+
+ 服务
+ 
+
+
+
+
+ 商品
+ 
+
+
+
+
+
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">3</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 @@
+
+
+
+
+ {{ attach.name }}
+
+
+
+
+
+ 上传中...
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ {{ title }}
+
+ 更新
+
+ 保存
+ 发布
+
+ 退出
+
+
+
+
+
+
+
+
+
+
+ 文章摘要
+ 自动提取
+
+
+
+
+
+ 文章关键字用于SEO优化
+
+
+
+
+
+
+ 文章封面图
+ 从正文中选取
+
+
+
+
+
+ 附件上传
+
+ 上传附件
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ exitDialog.message }}
+
+ 取消
+ 直接退出
+ {{ exitDialog.confirmButtonText }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ x
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 资源配置用于通过JSON或其它形式的内容进一步对资源做设定!
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 新建
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $d(`RESOURCE_TYPES.${row.valueType}`) }}
+
+
+
+
+
+
+
+
+
+ {{$filters.disabledText(row.disabled)}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ 资源链接可为相对路径,也可以为绝对路径!
+
+
+
+ {{ imageTip }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
{{ valueObj.link }}
+
+
+
+
+
+
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 @@
+
+ {{ value }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ 模板文件路径也是模板的唯一标识,用于查询模板的相关配置。
+ 查看更详细的说明
+
+
+
+
+
+ 访问路径将使用freemarker解析,作为挂载该模板数据的访问地址。
+ 查看更详细的说明
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ {{ item.label }}
+
+
+
+
+
+
+
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 @@
+
+
+ {{ data.label }}
+
+
+
+
+
+
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 @@
+
+
+ {{ data.label }}
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+ x
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 将文件拖到此处,或点击上传
+
+
+
+
+
+ {{form.file.name}}
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
{{formattedContent}}
+
+
+ 复制
+
+
+
+
+ {{content}}
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+ {{getLabelProxy(item)}}
+
+
+
+
+
+
+
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 @@
+
+
+ {{message}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ {{ prefix }}
+ {{ getValue() }}
+ {{ suffix }}
+
+
+
+
+
+
+
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 @@
+
+
+
+
+ {{menu.name}}
+
+
+
+
+
+
+
+
+ {{menu.name}}
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 新建
+ 删除
+ 上移
+ 下移
+
+
+
+
+
+
+
+
+
+
+
+ {{$filters.disabledText(row.disabled)}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+ 数据配置用于针对数据值进行更具体的配置,以方便直接根据配置做业务处理!
+
+
+
+
+
+
+ {{$filters.disabledText(form.disabled)}}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+ {{$filters.disabledText(form.disabled)}}
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+ 新建
+ 删除
+ 上移
+ 下移
+
+
+
+
+
+
+
+
+
+
+
+ {{$filters.disabledText(row.disabled)}}
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 图标值可以为class、Element-Plus图标或路径图标
+
+
+
+
+
+
+
+
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 @@
+
+
+ 为 {{parentName}} 新建子菜单
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 若该菜单需要进行权限控制,请填写权限标识符!注意:标识符为全局唯一的字符串。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
为角色 {{role.name}} 配置权限
+
+
+
+
+
+
+ 全部展开/折叠
+
+
+ 全选
+
+
+ 父子联动
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+ 为用户 {{user.realName}} 重置密码
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+ 为用户 {{user.realName}} 配置角色
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+ 新建
+ 删除
+ 批量上线
+ 批量下线
+ 文章标签管理
+
+
+
+
+
+ 无封面
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $d(`ARTICLE_STATUS.${row.status}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 上线
+ 下线
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ 新建
+ 删除
+
+
+
+
+
+ {{ row.title }}
+
+
+
+
+
+ {{ $d(`CATEGORY_TYPES.${row.type}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建子栏目
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+ SearchForm/搜索表单
+
+
+
+
+
+ 默认
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+ 带展开/收起
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+
+
+
+ SearchTable/管理表格
+
+
+
+
+
+ 默认
+
+
+
+
+
+
+
+
+
+
+
+
+ 增加操作按钮
+
+
+
+
+ 新建
+ 批量删除
+
+
+
+
+
+
+
+
+
+
+ 去掉字体控制、刷新和全屏
+
+
+
+
+ 新建
+ 批量删除
+
+
+
+
+
+
+
+
+
+ 增加数据提示
+
+
+
+
+
+
+
+
+
+
+
+ 增加警告提示
+
+
+
+
+
+
+
+
+
+
+
+ 增加错误提示
+
+
+
+
+
+
+
+
+
+
+
+ 使用tooltip展示超长文本展示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 使用PopoverCellValue展示超长文本展示
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FormItemTip/表单项提示
+
+
+
+
+
+ 基础
+
+
+
+ 填写时尽量填写客户真实的姓名!
+
+
+
+
+
+
+
+ 如已知客户性别,但客户希望保密,请选择“未知”!
+
+
+
+
+
+
+
+
+
+
+ Value/值异步加载
+
+
+
+
值异步加载可用于实现统计数据在数据还未加载完成时展示loading效果。
+
+
+
+
+ 默认
+
+
+
+ 带后缀
+
+
+
+ 带前缀
+
+
+
+ 改变图标尺寸
+
+
+
+ 自定义
+
+
+ 新增
+ {{ value }}
+ 人,
+ 提升
+ {{ value }}
+ %
+
+
+
+
+
+
+
+
+
+
+ DataLoading/数据加载中
+
+
+
+
DataLoading用于处理数据加载中效果,适用于对异步加载的数据做一个加载中提示。
+
+
+
+
+ 默认
+
+
+
+ 添加文字
+ 数据加载中
+
+
+ 改变图标大小
+ 数据加载中
+
+
+
+
+
+
+
+
+ EmptyTip/空提示
+
+
+
+
+
+ 默认
+
+
+
+ 改变图片大小
+
+
+
+ 改变文字
+
+
+
+ 自定义
+
+
+ 暂无数据,点击添加
+
+
+
+
+
+
+
+
+
+
+ GlobalWindow/全局窗口
+
+
+
+
+
+ {{ demo.title }}
+ 点击打开
+
+
+
+
+
+
+
+
+ ImportButton/导入按钮
+
+
+
+
+
+
+ Light/状态灯
+
+
+
+
+
+ 默认
+
+
+
+ 警告灯
+
+
+
+ 危险灯
+
+
+
+ 改变尺寸
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DictSelect & DictRadioGroup & DictCheckboxGroup.vue/字典选择器
+
+
+
+
+
+ 下拉选择
+
+ 已选:{{ dictData.value1 }}
+
+
+ 下拉选择(多选)
+
+ 已选:{{ dictData.value4.join(',') }}
+
+
+ Radio选择
+
+ 已选:{{ dictData.value2 }}
+
+
+ Checkbox多选
+
+ 已选:{{ dictData.value3.join(',') }}
+
+
+
+
+
+
+
+
+ ColorsMarking/色块标记
+
+
+
+
ColorsMarking适用于在表格顶部做色块标记,快速区分出不同颜色的数据行表达的意图。
+
+
+
+
+
+
+
+ RichText/富文本
+
+
+
+
+
+
+
+
+
+
+ 确认操作
+ 重置
+ 巴拉巴拉
+ 巴拉巴拉
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
无权访问
+
您的账号尚未配置访问菜单或访问权限,请联系系统管理员!
+
退出系统
+
+
+
+
+
+
+
找不到网页
+
+ 返回
+ 前往首页
+
+
+
+
你可以尝试以下操作
+
1. 如果是手动更改的网页地址,请检查网页地址是否正确
+
2. 点击【返回】按钮刷新页面后重试
+
3. 联系系统管理员
+
+
+
+
+
+
+
系统配置加载失败,请联系系统管理员或稍后重试!
+
+
+
+
+
+
+
系统出现未知错误,请联系系统管理员!
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
欢迎使用伊娃权限系统基础工程
+
全职团队长期维护,完全开源免费,如有BUG支持一键免费升级,请放心使用!
+
基础工程本身不携带代码生成服务,可以使用我们研发的GoldPanKit(也是免费的)来安装自定义功能和生成增删改查接口及页面,可大大缩短研发成本和周期!
+
+
+ npm install goldpankit -g
+
+ kit
+
+
+
+
你挠头,我掉发
+
进入氛围很哇塞的技术交流群或关注作者抖音,技术永远学不完,但请相信群众的力量!
+
+
+
+
+
+ 添加作者微信,加入交流群
+
+
+
+
+
+ 关注抖音不迷路
+
+
+
+
+
GoldPanKit发展历史
+
Eva作为GoldPanKit中的服务,专注后台权限系统解决方案,伴随着KIT共同发展!
+
+
+
+ 想法诞生
+ 在此之前,我在研发一款基于浏览器的操作系统,想用它来实现未来跨硬件且高隐私的数据操作和读取。系统研发完了,但应用过多,没法快速实现,所以想实现一个极速开发平台。
+
+
+
+
+ KIT前身发布
+ 2019年3月16日正式发布懒猴子CG ,而后更名为CodeRd。
+
+
+
+
+ 辞职
+ 2021年1月28日辞了工作,回到湖南老家创办公司,把精力全身心投入到产品中。
+
+
+
+
+ 成立公司
+ 2021年3月8日成立了桂阳阿茄思特网络技术有限公司,阿茄思特为Adjust的谐音,寓意公司为“调整”而生,简称“AD科技”。注册了公司域名adjustrd.com,后面加了“路”的缩写“rd”,寓意我们愿意不断的去调整目前世界所有不够完善的一切。本想全身心投入,但生活所迫不得不做一些外包项目和带一些学生来养活自己。
+
+
+
+
+ 获用户投资
+ 资金陷入困境,我个人的温饱成了问题。我尝试在交流群中售卖CodeRd股份,获得了崔易、朱晨辉、赵鹏、林郴、常洋、赵浩、曹宝红、侯泽为、李寻飞9人累计4万元的投资款,非常荣幸也非常感谢能得到他们的帮助。将来无论是工作还是继续创业,只要我能赚到钱,我会一一表达感谢和加倍偿还。
+
+
+
+
+ 获天使轮投资
+ 经过2年的摸爬滚打,通过了「清华海峡院」的项目评估,2023年5月20日获得了「清华海峡院北京办公室副主任彭一波」先生10万元资金投入和100万元的资源投入,并纳入「清华海峡院高端装备技术研究中心」专利项目。
+
+
+
+
+ 正式发布
+ 经过3个月的研发,CodeRd进行了全面的重构,采用了新的架构和设计,解决了CodeRd的瓶颈问题,2023年8月17日首次发布KIT v1.1.0。
+
+
+
+
+ 用户里程碑
+ 虽然KIT刚上线不久,但CodeRd依然在持续运营,2024年1月,CodeRd累计注册用户数超过1万。
+
+
+
+
+ 扩大团队
+ 拿着手头上的积蓄和投资款开始招募团队,组建了一个5人团队,准备做大做强!筹划KIT的商业模式,从全面开放通过售卖服务和插件的方式,到尝试只做付费用户的方式。
+
+
+
+
+ 缩减团队
+ 在尝试两次商业模式的变化后,手头上资金不多了,准备干老本行——接外包!公司团队缩减至了2个人,加上外部优秀的合作人员,差不多团队还有10人。
+
+
+
+
+ 全面免费
+ KIT进入了全面免费模式,不再收费。目标只有一个,让KIT真正的好用和被接受!
+
+
+
+
+ 未来
+ 未来就回到初衷,不管投资人怎么想。我只踏踏实实基于KIT提供的服务做好外包项目,没有外包项目时就升级KIT,开发更多的服务和插件。
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
伊娃CMS-轻量级CMS系统
+ 金镐开源组织研发,免费、开源、轻量、高规范!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 导出
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{statusText(row.success)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+ 新建
+ 删除
+ 上移
+ 下移
+
+
+
+
+
+ {{ row.name }}
+
+
+
+
+ {{ $d(`MENU_TYPES.${row.type}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建子菜单
+ 功能管理
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+ 新建
+ 删除
+
+
+
+
+
+ {{ $d(`DATA_SCOPES.${row.scope}`) }}
+
+
+
+
+ {{ $d(`SYSTEM_NOTICE_TYPES.${row.type}`) }}
+
+
+
+
+ {{ row.startTime }} - {{ row.endTime }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $filters.disabledText(row.disabled) }}
+
+
+
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 导出
+ 重置
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{$d(`TRACE_LOG_STATUSES.${row.status}`)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{$d(`TRACE_LOG_EXCEPTION_LEVEL.${row.exceptionLevel}`)}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+ 重置
+
+
+
+
+
+
+
+ 新建
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $d(`GENDERS.${row.gender}`) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 编辑
+ 配置角色
+ 重置密码
+
+ 删除
+
+
+
+
+ 编辑
+
+ 配置角色
+
+ 重置密码
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+ }
+ }
+ }
+ })
+}