!262 发布 v5.6.0-v2.6.0 版本 新年第一版
Merge pull request !262 from 疯狂的狮子Li/dev
This commit is contained in:
@@ -17,6 +17,7 @@
|
|||||||
"MaybeRefOrGetter": true,
|
"MaybeRefOrGetter": true,
|
||||||
"PropType": true,
|
"PropType": true,
|
||||||
"Ref": true,
|
"Ref": true,
|
||||||
|
"ShallowRef": true,
|
||||||
"Slot": true,
|
"Slot": true,
|
||||||
"Slots": true,
|
"Slots": true,
|
||||||
"VNode": true,
|
"VNode": true,
|
||||||
@@ -54,6 +55,7 @@
|
|||||||
"getActivePinia": true,
|
"getActivePinia": true,
|
||||||
"getCurrentInstance": true,
|
"getCurrentInstance": true,
|
||||||
"getCurrentScope": true,
|
"getCurrentScope": true,
|
||||||
|
"getCurrentWatcher": true,
|
||||||
"h": true,
|
"h": true,
|
||||||
"ignorableWatch": true,
|
"ignorableWatch": true,
|
||||||
"inject": true,
|
"inject": true,
|
||||||
@@ -63,6 +65,7 @@
|
|||||||
"isReactive": true,
|
"isReactive": true,
|
||||||
"isReadonly": true,
|
"isReadonly": true,
|
||||||
"isRef": true,
|
"isRef": true,
|
||||||
|
"isShallow": true,
|
||||||
"makeDestructurable": true,
|
"makeDestructurable": true,
|
||||||
"mapActions": true,
|
"mapActions": true,
|
||||||
"mapGetters": true,
|
"mapGetters": true,
|
||||||
@@ -106,11 +109,11 @@
|
|||||||
"refAutoReset": true,
|
"refAutoReset": true,
|
||||||
"refDebounced": true,
|
"refDebounced": true,
|
||||||
"refDefault": true,
|
"refDefault": true,
|
||||||
|
"refManualReset": true,
|
||||||
"refThrottled": true,
|
"refThrottled": true,
|
||||||
"refWithControl": true,
|
"refWithControl": true,
|
||||||
"resolveComponent": true,
|
"resolveComponent": true,
|
||||||
"resolveRef": true,
|
"resolveRef": true,
|
||||||
"resolveUnref": true,
|
|
||||||
"setActivePinia": true,
|
"setActivePinia": true,
|
||||||
"setMapStoreSuffix": true,
|
"setMapStoreSuffix": true,
|
||||||
"shallowReactive": true,
|
"shallowReactive": true,
|
||||||
@@ -168,6 +171,7 @@
|
|||||||
"useCountdown": true,
|
"useCountdown": true,
|
||||||
"useCounter": true,
|
"useCounter": true,
|
||||||
"useCssModule": true,
|
"useCssModule": true,
|
||||||
|
"useCssSupports": true,
|
||||||
"useCssVar": true,
|
"useCssVar": true,
|
||||||
"useCssVars": true,
|
"useCssVars": true,
|
||||||
"useCurrentElement": true,
|
"useCurrentElement": true,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
## 平台简介
|
## 平台简介
|
||||||
|
|
||||||
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
|
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
|
||||||
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)
|
||||||
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
|
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
|
||||||
|
|
||||||
## 配套后端代码仓库地址
|
## 配套后端代码仓库地址
|
||||||
|
|||||||
65
package.json
65
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "ruoyi-vue-plus",
|
"name": "ruoyi-vue-plus",
|
||||||
"version": "5.5.3-2.5.3",
|
"version": "5.6.0-2.6.0",
|
||||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||||
"author": "LionLi",
|
"author": "LionLi",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -23,68 +23,67 @@
|
|||||||
"@element-plus/icons-vue": "2.3.2",
|
"@element-plus/icons-vue": "2.3.2",
|
||||||
"@highlightjs/vue-plugin": "2.1.2",
|
"@highlightjs/vue-plugin": "2.1.2",
|
||||||
"@vueup/vue-quill": "1.2.0",
|
"@vueup/vue-quill": "1.2.0",
|
||||||
"@vueuse/core": "13.9.0",
|
"@vueuse/core": "14.2.1",
|
||||||
"animate.css": "4.1.1",
|
"animate.css": "4.1.1",
|
||||||
"await-to-js": "3.0.0",
|
"await-to-js": "3.0.0",
|
||||||
"axios": "1.13.1",
|
"axios": "1.13.6",
|
||||||
"crypto-js": "4.2.0",
|
"crypto-js": "4.2.0",
|
||||||
"echarts": "5.6.0",
|
"echarts": "6.0.0",
|
||||||
"element-plus": "2.11.7",
|
"element-plus": "2.13.5",
|
||||||
"file-saver": "2.0.5",
|
"file-saver": "2.0.5",
|
||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"image-conversion": "2.1.1",
|
"image-conversion": "2.1.1",
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"jsencrypt": "3.5.4",
|
"jsencrypt": "3.5.4",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"pinia": "3.0.3",
|
"pinia": "3.0.4",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
"vue": "3.5.22",
|
"vue": "3.5.30",
|
||||||
"vue-cropper": "1.1.4",
|
"vue-cropper": "1.1.4",
|
||||||
"vue-i18n": "11.1.12",
|
"vue-i18n": "11.3.0",
|
||||||
"vue-json-pretty": "2.6.0",
|
"vue-json-pretty": "2.6.0",
|
||||||
"vue-router": "4.6.3",
|
"vue-router": "5.0.3",
|
||||||
"vue-types": "6.0.0",
|
"vue-types": "6.0.0",
|
||||||
"vxe-table": "4.17.7"
|
"vxe-table": "4.18.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/json": "^2.2.403",
|
"@iconify/json": "^2.2.448",
|
||||||
"@types/crypto-js": "4.2.2",
|
"@types/crypto-js": "4.2.2",
|
||||||
"@types/file-saver": "2.0.7",
|
"@types/file-saver": "2.0.7",
|
||||||
"@types/js-cookie": "3.0.6",
|
"@types/js-cookie": "3.0.6",
|
||||||
"@types/node": "^22.19.0",
|
"@types/node": "^25.4.0",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@unocss/preset-attributify": "66.5.4",
|
"@unocss/preset-attributify": "66.6.6",
|
||||||
"@unocss/preset-icons": "66.5.4",
|
"@unocss/preset-icons": "66.6.6",
|
||||||
"@unocss/preset-uno": "66.5.4",
|
"@unocss/preset-uno": "66.6.6",
|
||||||
"@vitejs/plugin-vue": "5.2.4",
|
"@vitejs/plugin-vue": "6.0.4",
|
||||||
"@vue/compiler-sfc": "3.5.22",
|
"@vue/compiler-sfc": "3.5.30",
|
||||||
"@vue/eslint-config-prettier": "10.2.0",
|
"@vue/eslint-config-prettier": "10.2.0",
|
||||||
"@vue/eslint-config-typescript": "14.6.0",
|
"@vue/eslint-config-typescript": "14.6.0",
|
||||||
"autoprefixer": "10.4.21",
|
"autoprefixer": "10.4.27",
|
||||||
"eslint": "9.39.1",
|
"eslint": "9.39.1",
|
||||||
"eslint-plugin-prettier": "5.5.4",
|
"eslint-plugin-prettier": "5.5.5",
|
||||||
"eslint-plugin-vue": "9.33.0",
|
"eslint-plugin-vue": "9.33.0",
|
||||||
"globals": "16.5.0",
|
"globals": "17.4.0",
|
||||||
"prettier": "3.6.2",
|
"prettier": "3.8.1",
|
||||||
"sass": "1.93.3",
|
"sass": "1.98.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"unocss": "66.5.4",
|
"unocss": "66.6.6",
|
||||||
"unplugin-auto-import": "19.3.0",
|
"unplugin-auto-import": "21.0.0",
|
||||||
"unplugin-icons": "22.5.0",
|
"unplugin-icons": "23.0.1",
|
||||||
"unplugin-vue-components": "28.8.0",
|
"unplugin-vue-components": "31.0.0",
|
||||||
"unplugin-vue-setup-extend-plus": "1.0.1",
|
"unplugin-vue-setup-extend-plus": "1.0.1",
|
||||||
"vite": "6.4.1",
|
"vite": "7.3.1",
|
||||||
"vite-plugin-compression": "0.5.1",
|
|
||||||
"vite-plugin-svg-icons-ng": "^1.5.2",
|
"vite-plugin-svg-icons-ng": "^1.5.2",
|
||||||
"vite-plugin-vue-devtools": "8.0.3",
|
"vite-plugin-vue-devtools": "8.0.7",
|
||||||
"vitest": "3.2.4",
|
"vitest": "4.0.18",
|
||||||
"vue-tsc": "^2.2.12"
|
"vue-tsc": "^3.2.5"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"quill": "2.0.2"
|
"quill": "1.3.7"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.15.0",
|
"node": ">=20.19.0",
|
||||||
"npm": ">=8.19.0"
|
"npm": ">=8.19.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export interface ButtonList {
|
|||||||
}
|
}
|
||||||
export interface FlowCopyVo {
|
export interface FlowCopyVo {
|
||||||
userId: string | number;
|
userId: string | number;
|
||||||
userName: string;
|
nickName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TaskOperationBo {
|
export interface TaskOperationBo {
|
||||||
@@ -53,6 +53,8 @@ export interface TaskOperationBo {
|
|||||||
userIds?: string[];
|
userIds?: string[];
|
||||||
//任务ID(必填)
|
//任务ID(必填)
|
||||||
taskId: string | number;
|
taskId: string | number;
|
||||||
|
//消息类型
|
||||||
|
messageType?: string[];
|
||||||
//意见或备注信息(可选)
|
//意见或备注信息(可选)
|
||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 14px 36px;
|
padding: 14px 36px;
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: 600ms ease all;
|
transition: 600ms ease all;
|
||||||
@@ -95,5 +95,5 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
border-radius: 4px;
|
border-radius: var(--app-radius-sm);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,6 +74,9 @@
|
|||||||
|
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
margin: 0 auto !important;
|
margin: 0 auto !important;
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
box-shadow: var(--app-shadow-md);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.el-dialog__body {
|
.el-dialog__body {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
@@ -108,6 +111,8 @@
|
|||||||
|
|
||||||
// dropdown
|
// dropdown
|
||||||
.el-dropdown-menu {
|
.el-dropdown-menu {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
a {
|
a {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -151,3 +156,122 @@
|
|||||||
.el-message-box .el-message-box__message {
|
.el-message-box .el-message-box__message {
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-message-box {
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
box-shadow: var(--app-shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message,
|
||||||
|
.el-notification,
|
||||||
|
.el-alert {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modern rounded inputs
|
||||||
|
.el-input__wrapper,
|
||||||
|
.el-textarea__inner,
|
||||||
|
.el-select__wrapper,
|
||||||
|
.el-date-editor,
|
||||||
|
.el-range-editor,
|
||||||
|
.el-input-number,
|
||||||
|
.el-input-number__decrease,
|
||||||
|
.el-input-number__increase {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
transition: box-shadow 0.2s ease, border-color 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper.is-focus,
|
||||||
|
.el-textarea__inner:focus,
|
||||||
|
.el-select__wrapper.is-focus,
|
||||||
|
.el-date-editor.is-focus,
|
||||||
|
.el-range-editor.is-focus {
|
||||||
|
box-shadow: 0 0 0 2px var(--el-color-primary-light-8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buttons
|
||||||
|
.el-button {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button:not(.is-text):not(.is-link):hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags and badges
|
||||||
|
.el-tag {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cards, popovers, drawers
|
||||||
|
.el-popover,
|
||||||
|
.el-tooltip__popper,
|
||||||
|
.el-popper {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-drawer {
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
box-shadow: var(--app-shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-drawer__header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table polish
|
||||||
|
.el-table {
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
border: 1px solid var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__header-wrapper,
|
||||||
|
.el-table__body-wrapper {
|
||||||
|
background: var(--el-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table__row:hover td.el-table__cell {
|
||||||
|
background-color: var(--el-fill-color-light) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabs
|
||||||
|
.el-tabs__header {
|
||||||
|
margin: 0 0 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__item {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
margin: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tabs__nav-wrap::after {
|
||||||
|
background-color: var(--el-border-color-lighter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
.el-pagination .btn-prev,
|
||||||
|
.el-pagination .btn-next,
|
||||||
|
.el-pagination .el-pager li {
|
||||||
|
border-radius: var(--app-radius-sm);
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-pagination .el-pager li.is-active {
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breadcrumb
|
||||||
|
.el-breadcrumb {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,18 +14,20 @@ body {
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
|
background: var(--el-bg-color-page);
|
||||||
font-family:
|
font-family:
|
||||||
Helvetica Neue,
|
'MiSans',
|
||||||
Helvetica,
|
'HarmonyOS Sans SC',
|
||||||
PingFang SC,
|
'PingFang SC',
|
||||||
Hiragino Sans GB,
|
'Source Han Sans SC',
|
||||||
Microsoft YaHei,
|
'Noto Sans SC',
|
||||||
Arial,
|
'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
@@ -116,11 +118,18 @@ aside {
|
|||||||
background: #eef1f6;
|
background: #eef1f6;
|
||||||
padding: 8px 24px;
|
padding: 8px 24px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 2px;
|
border-radius: var(--app-radius-md);
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
font-family:
|
||||||
|
'MiSans',
|
||||||
|
'HarmonyOS Sans SC',
|
||||||
|
'PingFang SC',
|
||||||
|
'Source Han Sans SC',
|
||||||
|
'Noto Sans SC',
|
||||||
|
'Hiragino Sans GB',
|
||||||
|
'Microsoft YaHei',
|
||||||
sans-serif;
|
sans-serif;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
@@ -139,21 +148,27 @@ aside {
|
|||||||
//main-container全局样式
|
//main-container全局样式
|
||||||
.app-container {
|
.app-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
background: var(--app-surface-bg);
|
||||||
|
border: 1px solid var(--app-surface-border);
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// search面板样式
|
// search面板样式
|
||||||
.panel,
|
.panel,
|
||||||
.search {
|
.search {
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: var(--app-radius-lg);
|
||||||
border: 1px solid var(--el-border-color-light);
|
border: 1px solid var(--el-border-color-light);
|
||||||
background-color: var(--el-bg-color-overlay);
|
background-color: var(--el-bg-color-overlay);
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
transition: all ease 0.3s;
|
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: 0 2px 12px #0000001a;
|
box-shadow: var(--app-shadow-sm);
|
||||||
transition: all ease 0.3s;
|
border-color: var(--el-border-color);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ h6 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-form .el-form-item__label {
|
.el-form .el-form-item__label {
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
.el-dialog:not(.is-fullscreen) {
|
.el-dialog:not(.is-fullscreen) {
|
||||||
margin-top: 6vh !important;
|
margin-top: 6vh !important;
|
||||||
@@ -127,7 +127,7 @@ h6 {
|
|||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
border: 1px solid #e5e6e7;
|
border: 1px solid #e5e6e7;
|
||||||
background: #ffffff none;
|
background: #ffffff none;
|
||||||
border-radius: 4px;
|
border-radius: var(--app-radius-md);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,12 +190,27 @@ h6 {
|
|||||||
.el-card__header {
|
.el-card__header {
|
||||||
padding: 14px 15px 7px !important;
|
padding: 14px 15px 7px !important;
|
||||||
min-height: 40px;
|
min-height: 40px;
|
||||||
|
background: var(--el-fill-color-blank);
|
||||||
|
border-bottom: 1px solid var(--el-border-color-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-card__body {
|
.el-card__body {
|
||||||
padding: 15px 20px 20px 20px !important;
|
padding: 15px 20px 20px 20px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-card {
|
||||||
|
border-radius: var(--app-radius-lg);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
border-color: var(--el-border-color-lighter);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-card:hover {
|
||||||
|
box-shadow: var(--app-shadow-md);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.card-box {
|
.card-box {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
@@ -282,3 +297,9 @@ h6 {
|
|||||||
.top-right-btn {
|
.top-right-btn {
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* horizontal el menu */
|
||||||
|
.el-menu--horizontal .el-menu-item .svg-icon + span,
|
||||||
|
.el-menu--horizontal .el-sub-menu__title .svg-icon + span {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
transition: margin-left 0.28s;
|
transition: margin-left 0.28s;
|
||||||
margin-left: $base-sidebar-width;
|
margin-left: $base-sidebar-width;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: var(--el-bg-color-page);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebarHide {
|
.sidebarHide {
|
||||||
@@ -88,7 +89,7 @@
|
|||||||
// menu hover
|
// menu hover
|
||||||
.theme-dark .sub-menu-title-noDropdown,
|
.theme-dark .sub-menu-title-noDropdown,
|
||||||
.theme-dark .el-sub-menu__title {
|
.theme-dark .el-sub-menu__title {
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $base-sub-menu-title-hover !important;
|
background-color: $base-sub-menu-title-hover !important;
|
||||||
@@ -96,7 +97,7 @@
|
|||||||
}
|
}
|
||||||
.sub-menu-title-noDropdown,
|
.sub-menu-title-noDropdown,
|
||||||
.el-sub-menu__title {
|
.el-sub-menu__title {
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||||
@@ -110,7 +111,7 @@
|
|||||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||||
& .el-sub-menu .el-menu-item {
|
& .el-sub-menu .el-menu-item {
|
||||||
min-width: calc($base-sidebar-width - 20px) !important;
|
min-width: calc($base-sidebar-width - 20px) !important;
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
height: 45px;
|
height: 45px;
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
&:not(.is-active):hover {
|
&:not(.is-active):hover {
|
||||||
@@ -121,7 +122,7 @@
|
|||||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||||
& .theme-dark .el-sub-menu .el-menu-item {
|
& .theme-dark .el-sub-menu .el-menu-item {
|
||||||
background-color: $base-sub-menu-background !important;
|
background-color: $base-sub-menu-background !important;
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
height: 45px;
|
height: 45px;
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@
|
|||||||
|
|
||||||
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
& .theme-dark .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||||
& .theme-dark .el-menu-item {
|
& .theme-dark .el-menu-item {
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
height: 45px;
|
height: 45px;
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@
|
|||||||
|
|
||||||
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
& .nest-menu .el-sub-menu > .el-sub-menu__title,
|
||||||
& .el-menu-item {
|
& .el-menu-item {
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
height: 45px;
|
height: 45px;
|
||||||
margin: 1px 5px 1px 5px;
|
margin: 1px 5px 1px 5px;
|
||||||
|
|
||||||
@@ -196,9 +197,9 @@
|
|||||||
|
|
||||||
& .el-sub-menu {
|
& .el-sub-menu {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
.el-sub-menu__title.el-tooltip__trigger {
|
.el-sub-menu__title.el-tooltip__trigger {
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
height: 45px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,11 +287,11 @@
|
|||||||
}
|
}
|
||||||
// 收起菜单后悬浮的菜单样式
|
// 收起菜单后悬浮的菜单样式
|
||||||
.el-popper.is-pure{
|
.el-popper.is-pure{
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
.el-menu--popup{
|
.el-menu--popup{
|
||||||
border-radius: 8px;
|
border-radius: var(--app-radius-md);
|
||||||
}
|
}
|
||||||
.el-menu-item{
|
.el-menu-item{
|
||||||
border-radius: 4px;
|
border-radius: var(--app-radius-sm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,24 @@
|
|||||||
// 添加 tag 相关变量
|
// 添加 tag 相关变量
|
||||||
--tags-view-active-bg: var(--el-color-primary);
|
--tags-view-active-bg: var(--el-color-primary);
|
||||||
--tags-view-active-border-color: var(--el-color-primary);
|
--tags-view-active-border-color: var(--el-color-primary);
|
||||||
|
|
||||||
|
// Modern rounded style + soft shadows
|
||||||
|
--app-radius-sm: 6px;
|
||||||
|
--app-radius-md: 10px;
|
||||||
|
--app-radius-lg: 14px;
|
||||||
|
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
|
||||||
|
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||||
|
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16);
|
||||||
|
--app-surface-bg: #ffffff;
|
||||||
|
--app-surface-border: var(--el-border-color-lighter);
|
||||||
|
|
||||||
|
// Element Plus tokens
|
||||||
|
--el-border-radius-base: var(--app-radius-md);
|
||||||
|
--el-border-radius-small: var(--app-radius-sm);
|
||||||
|
--el-border-radius-round: 999px;
|
||||||
|
--el-box-shadow-light: var(--app-shadow-sm);
|
||||||
|
--el-box-shadow: var(--app-shadow-md);
|
||||||
|
--el-bg-color-page: #f5f7fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.dark {
|
html.dark {
|
||||||
@@ -48,8 +66,103 @@ html.dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.el-button--primary {
|
.el-button--primary {
|
||||||
--el-button-bg-color: var(--el-color-primary-dark-6);
|
--el-button-bg-color: #2b6bd3;
|
||||||
--el-button-border-color: var(--el-color-primary-light-2);
|
--el-button-border-color: #3a7be8;
|
||||||
|
--el-button-text-color: #eef4ff;
|
||||||
|
--el-button-hover-bg-color: #3a7be8;
|
||||||
|
--el-button-hover-border-color: #3a7be8;
|
||||||
|
--el-button-active-bg-color: #255fb8;
|
||||||
|
--el-button-active-border-color: #255fb8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--primary.is-plain {
|
||||||
|
--el-button-bg-color: rgba(43, 107, 211, 0.12);
|
||||||
|
--el-button-border-color: rgba(58, 123, 232, 0.5);
|
||||||
|
--el-button-text-color: #dbe8ff;
|
||||||
|
--el-button-hover-bg-color: rgba(58, 123, 232, 0.2);
|
||||||
|
--el-button-hover-border-color: rgba(58, 123, 232, 0.7);
|
||||||
|
--el-button-active-bg-color: rgba(43, 107, 211, 0.28);
|
||||||
|
--el-button-active-border-color: rgba(43, 107, 211, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--success {
|
||||||
|
--el-button-bg-color: #1f8a5a;
|
||||||
|
--el-button-border-color: #29a46d;
|
||||||
|
--el-button-text-color: #eefaf4;
|
||||||
|
--el-button-hover-bg-color: #29a46d;
|
||||||
|
--el-button-hover-border-color: #29a46d;
|
||||||
|
--el-button-active-bg-color: #1b784f;
|
||||||
|
--el-button-active-border-color: #1b784f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--success.is-plain {
|
||||||
|
--el-button-bg-color: rgba(31, 138, 90, 0.12);
|
||||||
|
--el-button-border-color: rgba(41, 164, 109, 0.5);
|
||||||
|
--el-button-text-color: #dbf6e8;
|
||||||
|
--el-button-hover-bg-color: rgba(41, 164, 109, 0.2);
|
||||||
|
--el-button-hover-border-color: rgba(41, 164, 109, 0.7);
|
||||||
|
--el-button-active-bg-color: rgba(31, 138, 90, 0.28);
|
||||||
|
--el-button-active-border-color: rgba(31, 138, 90, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--warning {
|
||||||
|
--el-button-bg-color: #b87922;
|
||||||
|
--el-button-border-color: #d6953b;
|
||||||
|
--el-button-text-color: #fff7e6;
|
||||||
|
--el-button-hover-bg-color: #d6953b;
|
||||||
|
--el-button-hover-border-color: #d6953b;
|
||||||
|
--el-button-active-bg-color: #a56c1d;
|
||||||
|
--el-button-active-border-color: #a56c1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--warning.is-plain {
|
||||||
|
--el-button-bg-color: rgba(184, 121, 34, 0.12);
|
||||||
|
--el-button-border-color: rgba(214, 149, 59, 0.5);
|
||||||
|
--el-button-text-color: #ffecc8;
|
||||||
|
--el-button-hover-bg-color: rgba(214, 149, 59, 0.2);
|
||||||
|
--el-button-hover-border-color: rgba(214, 149, 59, 0.7);
|
||||||
|
--el-button-active-bg-color: rgba(184, 121, 34, 0.28);
|
||||||
|
--el-button-active-border-color: rgba(184, 121, 34, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--danger {
|
||||||
|
--el-button-bg-color: #b24a4a;
|
||||||
|
--el-button-border-color: #d16060;
|
||||||
|
--el-button-text-color: #ffecec;
|
||||||
|
--el-button-hover-bg-color: #d16060;
|
||||||
|
--el-button-hover-border-color: #d16060;
|
||||||
|
--el-button-active-bg-color: #9c3f3f;
|
||||||
|
--el-button-active-border-color: #9c3f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--danger.is-plain {
|
||||||
|
--el-button-bg-color: rgba(178, 74, 74, 0.12);
|
||||||
|
--el-button-border-color: rgba(209, 96, 96, 0.5);
|
||||||
|
--el-button-text-color: #ffd6d6;
|
||||||
|
--el-button-hover-bg-color: rgba(209, 96, 96, 0.2);
|
||||||
|
--el-button-hover-border-color: rgba(209, 96, 96, 0.7);
|
||||||
|
--el-button-active-bg-color: rgba(178, 74, 74, 0.28);
|
||||||
|
--el-button-active-border-color: rgba(178, 74, 74, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--info {
|
||||||
|
--el-button-bg-color: #4b5563;
|
||||||
|
--el-button-border-color: #667085;
|
||||||
|
--el-button-text-color: #f3f4f6;
|
||||||
|
--el-button-hover-bg-color: #667085;
|
||||||
|
--el-button-hover-border-color: #667085;
|
||||||
|
--el-button-active-bg-color: #3f4753;
|
||||||
|
--el-button-active-border-color: #3f4753;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button--info.is-plain {
|
||||||
|
--el-button-bg-color: rgba(75, 85, 99, 0.16);
|
||||||
|
--el-button-border-color: rgba(102, 112, 133, 0.55);
|
||||||
|
--el-button-text-color: #e5e7eb;
|
||||||
|
--el-button-hover-bg-color: rgba(102, 112, 133, 0.22);
|
||||||
|
--el-button-hover-border-color: rgba(102, 112, 133, 0.75);
|
||||||
|
--el-button-active-bg-color: rgba(75, 85, 99, 0.3);
|
||||||
|
--el-button-active-border-color: rgba(75, 85, 99, 0.85);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-switch {
|
.el-switch {
|
||||||
@@ -62,9 +175,41 @@ html.dark {
|
|||||||
--el-tag-border-color: var(--el-color-primary-light-2);
|
--el-tag-border-color: var(--el-color-primary-light-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-tag--success {
|
||||||
|
--el-tag-bg-color: rgba(31, 138, 90, 0.18);
|
||||||
|
--el-tag-border-color: rgba(41, 164, 109, 0.6);
|
||||||
|
--el-tag-text-color: #c7f2df;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tag--warning {
|
||||||
|
--el-tag-bg-color: rgba(184, 121, 34, 0.18);
|
||||||
|
--el-tag-border-color: rgba(214, 149, 59, 0.6);
|
||||||
|
--el-tag-text-color: #ffe6bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tag--danger {
|
||||||
|
--el-tag-bg-color: rgba(178, 74, 74, 0.18);
|
||||||
|
--el-tag-border-color: rgba(209, 96, 96, 0.6);
|
||||||
|
--el-tag-text-color: #ffd0d0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-tag--info {
|
||||||
|
--el-tag-bg-color: rgba(75, 85, 99, 0.18);
|
||||||
|
--el-tag-border-color: rgba(102, 112, 133, 0.6);
|
||||||
|
--el-tag-text-color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
// 在深色模式下使用更深的颜色
|
// 在深色模式下使用更深的颜色
|
||||||
--tags-view-active-bg: var(--el-color-primary-dark-6);
|
--tags-view-active-bg: var(--el-color-primary-dark-6);
|
||||||
--tags-view-active-border-color: var(--el-color-primary-light-2);
|
--tags-view-active-border-color: var(--el-color-primary-light-2);
|
||||||
|
|
||||||
|
// Modern rounded style + soft shadows (dark)
|
||||||
|
--app-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28), 0 8px 18px rgba(0, 0, 0, 0.25);
|
||||||
|
--app-shadow-md: 0 10px 26px rgba(0, 0, 0, 0.35);
|
||||||
|
--app-shadow-lg: 0 14px 34px rgba(0, 0, 0, 0.4);
|
||||||
|
--app-surface-bg: #151922;
|
||||||
|
--app-surface-border: var(--el-border-color);
|
||||||
|
--el-bg-color-page: #0f1115;
|
||||||
// vxe-table 主题
|
// vxe-table 主题
|
||||||
--vxe-font-color: #98989e;
|
--vxe-font-color: #98989e;
|
||||||
--vxe-primary-color: #2c7ecf;
|
--vxe-primary-color: #2c7ecf;
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-upload
|
<el-upload
|
||||||
v-if="type === 'url'"
|
v-if="type"
|
||||||
:action="upload.url"
|
action=""
|
||||||
:before-upload="handleBeforeUpload"
|
:before-upload="handleBeforeUpload"
|
||||||
:on-success="handleUploadSuccess"
|
:http-request="handleUploadRequest"
|
||||||
:on-error="handleUploadError"
|
|
||||||
class="editor-img-uploader"
|
class="editor-img-uploader"
|
||||||
name="file"
|
name="file"
|
||||||
:show-file-list="false"
|
:show-file-list="false"
|
||||||
:headers="upload.headers"
|
|
||||||
>
|
>
|
||||||
<i ref="uploadRef"></i>
|
<i ref="uploadRef"></i>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
@@ -31,7 +29,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
|||||||
|
|
||||||
import { QuillEditor, Quill } from '@vueup/vue-quill';
|
import { QuillEditor, Quill } from '@vueup/vue-quill';
|
||||||
import { propTypes } from '@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { globalHeaders } from '@/utils/request';
|
import type { UploadRequestHandler, UploadRequestOptions } from 'element-plus';
|
||||||
|
|
||||||
defineEmits(['update:modelValue']);
|
defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
@@ -47,15 +45,11 @@ const props = defineProps({
|
|||||||
/* 上传文件大小限制(MB) */
|
/* 上传文件大小限制(MB) */
|
||||||
fileSize: propTypes.number.def(5),
|
fileSize: propTypes.number.def(5),
|
||||||
/* 类型(base64格式、url格式) */
|
/* 类型(base64格式、url格式) */
|
||||||
type: propTypes.string.def('url')
|
type: propTypes.string.def('base64')
|
||||||
});
|
});
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
const upload = reactive<UploadOption>({
|
|
||||||
headers: globalHeaders(),
|
|
||||||
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
|
|
||||||
});
|
|
||||||
const quillEditorRef = ref();
|
const quillEditorRef = ref();
|
||||||
const uploadRef = ref<HTMLDivElement>();
|
const uploadRef = ref<HTMLDivElement>();
|
||||||
|
|
||||||
@@ -116,28 +110,9 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
// 图片上传成功返回图片地址
|
|
||||||
const handleUploadSuccess = (res: any) => {
|
|
||||||
// 如果上传成功
|
|
||||||
if (res.code === 200) {
|
|
||||||
// 获取富文本实例
|
|
||||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
|
||||||
// 获取光标位置
|
|
||||||
const length = quill.selection.savedRange.index;
|
|
||||||
// 插入图片,res为服务器返回的图片链接地址
|
|
||||||
quill.insertEmbed(length, 'image', res.data.url);
|
|
||||||
// 调整光标到最后
|
|
||||||
quill.setSelection(length + 1);
|
|
||||||
proxy?.$modal.closeLoading();
|
|
||||||
} else {
|
|
||||||
proxy?.$modal.msgError('图片插入失败');
|
|
||||||
proxy?.$modal.closeLoading();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 图片上传前拦截
|
// 图片上传前拦截
|
||||||
const handleBeforeUpload = (file: any) => {
|
const handleBeforeUpload = (file: any) => {
|
||||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
|
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||||
const isJPG = type.includes(file.type);
|
const isJPG = type.includes(file.type);
|
||||||
//检验文件格式
|
//检验文件格式
|
||||||
if (!isJPG) {
|
if (!isJPG) {
|
||||||
@@ -156,9 +131,41 @@ const handleBeforeUpload = (file: any) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 图片失败拦截
|
// base64 模式插入图片
|
||||||
const handleUploadError = (err: any) => {
|
const handleUploadRequest: UploadRequestHandler = (options: UploadRequestOptions) => {
|
||||||
proxy?.$modal.msgError('上传文件失败');
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
const file = options.file as File;
|
||||||
|
const quill = toRaw(quillEditorRef.value)?.getQuill();
|
||||||
|
if (!quill) {
|
||||||
|
proxy?.$modal.msgError('编辑器未就绪');
|
||||||
|
proxy?.$modal.closeLoading();
|
||||||
|
reject(new Error('editor not ready'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
const base64 = reader.result as string;
|
||||||
|
const range = quill.selection?.savedRange;
|
||||||
|
const length = range ? range.index : quill.getLength();
|
||||||
|
quill.insertEmbed(length, 'image', base64);
|
||||||
|
quill.setSelection(length + 1);
|
||||||
|
proxy?.$modal.closeLoading();
|
||||||
|
options.onSuccess?.({ url: base64 });
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
proxy?.$modal.msgError('图片插入失败');
|
||||||
|
proxy?.$modal.closeLoading();
|
||||||
|
const err = Object.assign(new Error('read image failed'), {
|
||||||
|
status: 0,
|
||||||
|
method: 'POST',
|
||||||
|
url: options.action || ''
|
||||||
|
});
|
||||||
|
options.onError?.(err as any);
|
||||||
|
reject(err);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ const handleTransferTask = async (data) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userId: data[0].userId,
|
userId: data[0].userId,
|
||||||
taskId: task.value.id,
|
taskId: task.value.id,
|
||||||
message: ''
|
message: '',
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await proxy?.$modal.confirm('是否确认提交?');
|
await proxy?.$modal.confirm('是否确认提交?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -139,7 +140,8 @@ const addMultiInstanceUser = async (data) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userIds: data.map((e) => e.userId),
|
userIds: data.map((e) => e.userId),
|
||||||
taskId: task.value.id,
|
taskId: task.value.id,
|
||||||
message: ''
|
message: '',
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await proxy?.$modal.confirm('是否确认提交?');
|
await proxy?.$modal.confirm('是否确认提交?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -163,7 +165,8 @@ const deleteMultiInstanceUser = async (row) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userIds: [row.userId],
|
userIds: [row.userId],
|
||||||
taskId: task.value.id,
|
taskId: task.value.id,
|
||||||
message: ''
|
message: '',
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<el-form-item label="抄送" v-if="buttonObj.copy">
|
<el-form-item label="抄送" v-if="buttonObj.copy">
|
||||||
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
|
<el-button type="primary" icon="Plus" circle @click="openUserSelectCopy" />
|
||||||
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
|
<el-tag v-for="user in selectCopyUserList" :key="user.userId" closable style="margin: 2px" @close="handleCopyCloseTag(user)">
|
||||||
{{ user.userName }}
|
{{ user.nickName }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap">
|
<el-form-item v-if="buttonObj.pop && nestNodeList && nestNodeList.length > 0" label="下一步审批人" prop="assigneeMap">
|
||||||
@@ -80,7 +80,13 @@
|
|||||||
<!-- 加签组件 -->
|
<!-- 加签组件 -->
|
||||||
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
|
<UserSelect ref="multiInstanceUserRef" :multiple="true" @confirm-call-back="addMultiInstanceUser"></UserSelect>
|
||||||
<!-- 弹窗选人 -->
|
<!-- 弹窗选人 -->
|
||||||
<UserSelect ref="porUserRef" :data="form.assigneeMap[nodeCode]" :multiple="true" :userIds="popUserIds" @confirm-call-back="handlePopUser"></UserSelect>
|
<UserSelect
|
||||||
|
ref="porUserRef"
|
||||||
|
:data="form.assigneeMap[nodeCode]"
|
||||||
|
:multiple="true"
|
||||||
|
:userIds="popUserIds"
|
||||||
|
@confirm-call-back="handlePopUser"
|
||||||
|
></UserSelect>
|
||||||
|
|
||||||
<!-- 驳回开始 -->
|
<!-- 驳回开始 -->
|
||||||
<el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
|
<el-dialog v-model="backVisible" draggable title="驳回" width="40%" :close-on-click-modal="false">
|
||||||
@@ -265,7 +271,7 @@ const openDialog = async (id?: string) => {
|
|||||||
selectCopyUserList.value = task.value.copyList;
|
selectCopyUserList.value = task.value.copyList;
|
||||||
selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
|
selectCopyUserIds.value = task.value.copyList.map((e) => e.userId).join(',');
|
||||||
varNodeList.value = task.value.varList;
|
varNodeList.value = task.value.varList;
|
||||||
console.log('varNodeList', varNodeList.value)
|
console.log('varNodeList', varNodeList.value);
|
||||||
buttonDisabled.value = false;
|
buttonDisabled.value = false;
|
||||||
try {
|
try {
|
||||||
const data = {
|
const data = {
|
||||||
@@ -310,7 +316,7 @@ const handleCompleteTask = async () => {
|
|||||||
selectCopyUserList.value.forEach((e) => {
|
selectCopyUserList.value.forEach((e) => {
|
||||||
const copyUser = {
|
const copyUser = {
|
||||||
userId: e.userId,
|
userId: e.userId,
|
||||||
userName: e.userName
|
nickName: e.nickName
|
||||||
};
|
};
|
||||||
flowCopyList.push(copyUser);
|
flowCopyList.push(copyUser);
|
||||||
});
|
});
|
||||||
@@ -397,7 +403,8 @@ const addMultiInstanceUser = async (data) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userIds: data.map((e) => e.userId),
|
userIds: data.map((e) => e.userId),
|
||||||
taskId: taskId.value,
|
taskId: taskId.value,
|
||||||
message: form.value.message
|
message: form.value.message,
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await proxy?.$modal.confirm('是否确认提交?');
|
await proxy?.$modal.confirm('是否确认提交?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -421,7 +428,8 @@ const deleteMultiInstanceUser = async (row) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userIds: [row.userId],
|
userIds: [row.userId],
|
||||||
taskId: taskId.value,
|
taskId: taskId.value,
|
||||||
message: form.value.message
|
message: form.value.message,
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
await taskOperation(taskOperationBo, 'reductionSignature').finally(() => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
@@ -441,7 +449,8 @@ const handleTransferTask = async (data) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userId: data[0].userId,
|
userId: data[0].userId,
|
||||||
taskId: taskId.value,
|
taskId: taskId.value,
|
||||||
message: form.value.message
|
message: form.value.message,
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await proxy?.$modal.confirm('是否确认提交?');
|
await proxy?.$modal.confirm('是否确认提交?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
@@ -468,7 +477,8 @@ const handleDelegateTask = async (data) => {
|
|||||||
const taskOperationBo = reactive<TaskOperationBo>({
|
const taskOperationBo = reactive<TaskOperationBo>({
|
||||||
userId: data[0].userId,
|
userId: data[0].userId,
|
||||||
taskId: taskId.value,
|
taskId: taskId.value,
|
||||||
message: form.value.message
|
message: form.value.message,
|
||||||
|
messageType: ['1']
|
||||||
});
|
});
|
||||||
await proxy?.$modal.confirm('是否确认提交?');
|
await proxy?.$modal.confirm('是否确认提交?');
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
|
|||||||
17
src/enums/NavTypeEnum.ts
Normal file
17
src/enums/NavTypeEnum.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 导航栏布局枚举
|
||||||
|
*/
|
||||||
|
export enum NavTypeEnum {
|
||||||
|
/**
|
||||||
|
* 左侧导航
|
||||||
|
*/
|
||||||
|
LEFT = 'left',
|
||||||
|
/**
|
||||||
|
* 顶部导航
|
||||||
|
*/
|
||||||
|
TOP = 'top',
|
||||||
|
/**
|
||||||
|
* 混合导航
|
||||||
|
*/
|
||||||
|
MIX = 'mix'
|
||||||
|
}
|
||||||
@@ -89,11 +89,11 @@ function addIframe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
::-webkit-scrollbar-track {
|
||||||
background-color: #f1f1f1;
|
background-color: var(--el-fill-color-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background-color: #c0c0c0;
|
background-color: var(--el-text-color-placeholder);
|
||||||
border-radius: 3px;
|
border-radius: 999px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="navbar">
|
<div class="navbar" :class="'nav' + navType">
|
||||||
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
|
<hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggle-click="toggleSideBar" />
|
||||||
<breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container" />
|
|
||||||
<top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container" />
|
|
||||||
|
|
||||||
|
<breadcrumb v-if="navType == NavTypeEnum.LEFT" id="breadcrumb-container" class="breadcrumb-container" />
|
||||||
|
<top-nav v-if="navType == NavTypeEnum.MIX" id="topmenu-container" class="topmenu-container" />
|
||||||
|
|
||||||
|
<template v-if="navType == NavTypeEnum.TOP">
|
||||||
|
<logo v-show="showLogo" :collapse="false"></logo>
|
||||||
|
<top-bar id="topbar-container" class="topbar-container" />
|
||||||
|
</template>
|
||||||
<div class="right-menu flex align-center">
|
<div class="right-menu flex align-center">
|
||||||
<template v-if="appStore.device !== 'mobile'">
|
<template v-if="appStore.device !== 'mobile'">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -99,6 +104,9 @@ import { TenantVO } from '@/api/types';
|
|||||||
import notice from './notice/index.vue';
|
import notice from './notice/index.vue';
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
|
import { ElMessageBoxOptions } from 'element-plus/es/components/message-box/src/message-box.type';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
|
import Logo from "@/layout/components/Sidebar/Logo.vue";
|
||||||
|
import TopBar from './TopBar'
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -109,6 +117,9 @@ const newNotice = ref(<number>0);
|
|||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
const userId = ref(userStore.userId);
|
const userId = ref(userStore.userId);
|
||||||
|
const navType = computed(() => settingsStore.navType);
|
||||||
|
const showLogo = computed(() => settingsStore.sidebarLogo);
|
||||||
|
|
||||||
const companyName = ref(undefined);
|
const companyName = ref(undefined);
|
||||||
const tenantList = ref<TenantVO[]>([]);
|
const tenantList = ref<TenantVO[]>([]);
|
||||||
// 是否切换了租户
|
// 是否切换了租户
|
||||||
@@ -201,6 +212,12 @@ watch(
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.navbar.navtop {
|
||||||
|
.hamburger-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-select .el-input__wrapper) {
|
:deep(.el-select .el-input__wrapper) {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
@@ -221,24 +238,34 @@ watch(
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
//background: #fff;
|
background: var(--el-bg-color);
|
||||||
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
|
box-shadow: none;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// padding: 0 8px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
.hamburger-container {
|
.hamburger-container {
|
||||||
line-height: 46px;
|
line-height: 46px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
float: left;
|
//float: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s;
|
transition: background 0.3s;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.025);
|
background: var(--el-fill-color-lighter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-container {
|
.breadcrumb-container {
|
||||||
float: left;
|
//float: left;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topmenu-container {
|
.topmenu-container {
|
||||||
@@ -246,16 +273,28 @@ watch(
|
|||||||
left: 50px;
|
left: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.topbar-container {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.errLog-container {
|
.errLog-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-menu {
|
.right-menu {
|
||||||
float: right;
|
//float: right;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
@@ -266,7 +305,7 @@ watch(
|
|||||||
padding: 0 8px;
|
padding: 0 8px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: #5a5e66;
|
color: var(--el-text-color-regular);
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
|
|
||||||
&.hover-effect {
|
&.hover-effect {
|
||||||
@@ -274,7 +313,7 @@ watch(
|
|||||||
transition: background 0.3s;
|
transition: background 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.025);
|
background: var(--el-fill-color-lighter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +329,7 @@ watch(
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
border-radius: 10px;
|
border-radius: var(--app-radius-md);
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
|
<el-drawer v-model="showSettings" :with-header="false" direction="rtl" size="300px" close-on-click-modal>
|
||||||
|
<h3 class="drawer-title">菜单导航设置</h3>
|
||||||
|
<div class="nav-wrap">
|
||||||
|
<el-tooltip content="左侧菜单" placement="bottom">
|
||||||
|
<div
|
||||||
|
class="item left"
|
||||||
|
@click="handleNavType(NavTypeEnum.LEFT)"
|
||||||
|
:style="{ '--theme': theme }"
|
||||||
|
:class="{ activeItem: navType == NavTypeEnum.LEFT }"
|
||||||
|
>
|
||||||
|
<b></b><b></b>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<el-tooltip content="混合菜单" placement="bottom">
|
||||||
|
<div
|
||||||
|
class="item mix"
|
||||||
|
@click="handleNavType(NavTypeEnum.MIX)"
|
||||||
|
:style="{ '--theme': theme }"
|
||||||
|
:class="{ activeItem: navType == NavTypeEnum.MIX }"
|
||||||
|
>
|
||||||
|
<b></b><b></b>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="顶部菜单" placement="bottom">
|
||||||
|
<div
|
||||||
|
class="item top"
|
||||||
|
@click="handleNavType(NavTypeEnum.TOP)"
|
||||||
|
:style="{ '--theme': theme }"
|
||||||
|
:class="{ activeItem: navType == NavTypeEnum.TOP }"
|
||||||
|
>
|
||||||
|
<b></b><b></b>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3 class="drawer-title">主题风格设置</h3>
|
<h3 class="drawer-title">主题风格设置</h3>
|
||||||
|
|
||||||
<div class="setting-drawer-block-checbox">
|
<div class="setting-drawer-block-checbox">
|
||||||
@@ -45,13 +80,6 @@
|
|||||||
|
|
||||||
<h3 class="drawer-title">系统布局配置</h3>
|
<h3 class="drawer-title">系统布局配置</h3>
|
||||||
|
|
||||||
<div class="drawer-item">
|
|
||||||
<span>开启 TopNav</span>
|
|
||||||
<span class="comp-style">
|
|
||||||
<el-switch v-model="settingsStore.topNav" class="drawer-switch" @change="topNavChange" />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-item">
|
<div class="drawer-item">
|
||||||
<span>开启 Tags-Views</span>
|
<span>开启 Tags-Views</span>
|
||||||
<span class="comp-style">
|
<span class="comp-style">
|
||||||
@@ -101,6 +129,7 @@ import { useSettingsStore } from '@/store/modules/settings';
|
|||||||
import { usePermissionStore } from '@/store/modules/permission';
|
import { usePermissionStore } from '@/store/modules/permission';
|
||||||
import { handleThemeStyle } from '@/utils/theme';
|
import { handleThemeStyle } from '@/utils/theme';
|
||||||
import { SideThemeEnum } from '@/enums/SideThemeEnum';
|
import { SideThemeEnum } from '@/enums/SideThemeEnum';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
import defaultSettings from '@/settings';
|
import defaultSettings from '@/settings';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
@@ -113,7 +142,7 @@ const theme = ref(settingsStore.theme);
|
|||||||
const sideTheme = ref(settingsStore.sideTheme);
|
const sideTheme = ref(settingsStore.sideTheme);
|
||||||
const storeSettings = computed(() => settingsStore);
|
const storeSettings = computed(() => settingsStore);
|
||||||
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
|
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
|
||||||
|
const navType = ref(settingsStore.navType);
|
||||||
// 是否暗黑模式
|
// 是否暗黑模式
|
||||||
const isDark = useDark({
|
const isDark = useDark({
|
||||||
storageKey: 'useDarkKey',
|
storageKey: 'useDarkKey',
|
||||||
@@ -130,11 +159,26 @@ watch(isDark, () => {
|
|||||||
});
|
});
|
||||||
const toggleDark = () => useToggle(isDark);
|
const toggleDark = () => useToggle(isDark);
|
||||||
|
|
||||||
const topNavChange = (val: any) => {
|
/** 菜单导航设置 */
|
||||||
if (!val) {
|
watch(
|
||||||
appStore.toggleSideBarHide(false);
|
() => navType,
|
||||||
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
(val: string) => {
|
||||||
}
|
if (val.value === NavTypeEnum.TOP) {
|
||||||
|
appStore.toggleSideBarHide(true);
|
||||||
|
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
||||||
|
} else if (val.value === NavTypeEnum.LEFT) {
|
||||||
|
appStore.toggleSideBarHide(false);
|
||||||
|
permissionStore.setSidebarRouters(permissionStore.defaultRoutes as any);
|
||||||
|
} else if (val.value === NavTypeEnum.MIX) {
|
||||||
|
appStore.toggleSideBarHide(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleNavType = (val: NavTypeEnum) => {
|
||||||
|
settingsStore.navType = val;
|
||||||
|
navType.value = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
const dynamicTitleChange = () => {
|
const dynamicTitleChange = () => {
|
||||||
@@ -158,7 +202,6 @@ const handleTheme = (val: string) => {
|
|||||||
const saveSetting = () => {
|
const saveSetting = () => {
|
||||||
proxy?.$modal.loading('正在保存到本地,请稍候...');
|
proxy?.$modal.loading('正在保存到本地,请稍候...');
|
||||||
const settings = useStorage<LayoutSetting>('layout-setting', defaultSettings);
|
const settings = useStorage<LayoutSetting>('layout-setting', defaultSettings);
|
||||||
settings.value.topNav = storeSettings.value.topNav;
|
|
||||||
settings.value.tagsView = storeSettings.value.tagsView;
|
settings.value.tagsView = storeSettings.value.tagsView;
|
||||||
settings.value.tagsIcon = storeSettings.value.tagsIcon;
|
settings.value.tagsIcon = storeSettings.value.tagsIcon;
|
||||||
settings.value.fixedHeader = storeSettings.value.fixedHeader;
|
settings.value.fixedHeader = storeSettings.value.fixedHeader;
|
||||||
@@ -166,6 +209,7 @@ const saveSetting = () => {
|
|||||||
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
|
settings.value.dynamicTitle = storeSettings.value.dynamicTitle;
|
||||||
settings.value.sideTheme = storeSettings.value.sideTheme;
|
settings.value.sideTheme = storeSettings.value.sideTheme;
|
||||||
settings.value.theme = storeSettings.value.theme;
|
settings.value.theme = storeSettings.value.theme;
|
||||||
|
settings.value.navType = storeSettings.value.navType;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
proxy?.$modal.closeLoading();
|
proxy?.$modal.closeLoading();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -243,4 +287,67 @@ defineExpose({
|
|||||||
margin: -3px 8px 0px 0px;
|
margin: -3px 8px 0px 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 导航模式
|
||||||
|
.nav-wrap {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
.activeItem {
|
||||||
|
border: 2px solid #{'var(--theme)'} !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
margin-right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 56px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
b:first-child {
|
||||||
|
display: block;
|
||||||
|
height: 30%;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
b:last-child {
|
||||||
|
width: 30%;
|
||||||
|
background: #1b2a47;
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
border-radius: 4px 0 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mix {
|
||||||
|
b:first-child {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
display: block;
|
||||||
|
height: 30%;
|
||||||
|
background: #1b2a47;
|
||||||
|
}
|
||||||
|
b:last-child {
|
||||||
|
width: 30%;
|
||||||
|
background: #1b2a47;
|
||||||
|
position: absolute;
|
||||||
|
height: 70%;
|
||||||
|
border-radius: 0 0 0 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.top {
|
||||||
|
b:first-child {
|
||||||
|
display: block;
|
||||||
|
height: 30%;
|
||||||
|
background: #1b2a47;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,18 +2,17 @@
|
|||||||
<div
|
<div
|
||||||
class="sidebar-logo-container"
|
class="sidebar-logo-container"
|
||||||
:class="{ collapse: collapse }"
|
:class="{ collapse: collapse }"
|
||||||
:style="{ backgroundColor: sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground }"
|
|
||||||
>
|
>
|
||||||
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
|
<transition :enter-active-class="proxy?.animate.logoAnimate.enter" mode="out-in">
|
||||||
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
<router-link v-if="collapse" key="collapse" class="sidebar-logo-link" to="/">
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||||
<h1 v-else class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
|
<h1 v-else class="sidebar-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
<router-link v-else key="expand" class="sidebar-logo-link" to="/">
|
||||||
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
<img v-if="logo" :src="logo" class="sidebar-logo" />
|
||||||
<h1 class="sidebar-title" :style="{ color: sideTheme === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor }">
|
<h1 class="sidebar-title">
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h1>
|
</h1>
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -26,6 +25,7 @@ import variables from '@/assets/styles/variables.module.scss';
|
|||||||
import logo from '@/assets/logo/logo.png';
|
import logo from '@/assets/logo/logo.png';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
collapse: {
|
collapse: {
|
||||||
@@ -37,6 +37,28 @@ defineProps({
|
|||||||
const title = import.meta.env.VITE_APP_LOGO_TITLE;
|
const title = import.meta.env.VITE_APP_LOGO_TITLE;
|
||||||
const settingsStore = useSettingsStore();
|
const settingsStore = useSettingsStore();
|
||||||
const sideTheme = computed(() => settingsStore.sideTheme);
|
const sideTheme = computed(() => settingsStore.sideTheme);
|
||||||
|
|
||||||
|
// 获取Logo背景色
|
||||||
|
const getLogoBackground = computed(() => {
|
||||||
|
if (settingsStore.isDark) {
|
||||||
|
return 'var(--sidebar-bg)'
|
||||||
|
}
|
||||||
|
if (settingsStore.navType == NavTypeEnum.TOP) {
|
||||||
|
return variables.menuLightBackground
|
||||||
|
}
|
||||||
|
return sideTheme.value === 'theme-dark' ? variables.menuBg : variables.menuLightBackground
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取Logo文字颜色
|
||||||
|
const getLogoTextColor = computed(() => {
|
||||||
|
if (settingsStore.isDark) {
|
||||||
|
return 'var(--sidebar-text)'
|
||||||
|
}
|
||||||
|
if (settingsStore.navType == NavTypeEnum.TOP) {
|
||||||
|
return variables.logoLightTitleColor
|
||||||
|
}
|
||||||
|
return sideTheme.value === 'theme-dark' ? variables.logoTitleColor : variables.logoLightTitleColor
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -51,10 +73,9 @@ const sideTheme = computed(() => settingsStore.sideTheme);
|
|||||||
|
|
||||||
.sidebar-logo-container {
|
.sidebar-logo-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
|
||||||
height: 50px;
|
height: 50px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
background: #2b2f3a;
|
background: v-bind(getLogoBackground);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
@@ -67,21 +88,17 @@ const sideTheme = computed(() => settingsStore.sideTheme);
|
|||||||
height: 32px;
|
height: 32px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
|
margin-left: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .sidebar-title {
|
& .sidebar-title {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #fff;
|
color: v-bind(getLogoTextColor);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-family:
|
font-family: Avenir, Helvetica Neue, Arial, Helvetica, sans-serif;
|
||||||
Avenir,
|
|
||||||
Helvetica Neue,
|
|
||||||
Arial,
|
|
||||||
Helvetica,
|
|
||||||
sans-serif;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,9 +244,8 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--el-bg-color);
|
background-color: var(--el-bg-color);
|
||||||
border: 1px solid var(--el-border-color-light);
|
border: 1px solid var(--el-border-color-light);
|
||||||
box-shadow:
|
border-radius: var(--app-radius-md);
|
||||||
0 1px 3px 0 rgba(0, 0, 0, 0.12),
|
box-shadow: var(--app-shadow-sm);
|
||||||
0 0 3px 0 rgba(0, 0, 0, 0.04);
|
|
||||||
.tags-view-wrapper {
|
.tags-view-wrapper {
|
||||||
.tags-view-item {
|
.tags-view-item {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -261,9 +260,13 @@ onMounted(() => {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
border-radius: 4px;
|
border-radius: var(--app-radius-md);
|
||||||
|
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary);
|
color: var(--el-color-primary);
|
||||||
|
border-color: var(--el-color-primary-light-5);
|
||||||
|
box-shadow: var(--app-shadow-sm);
|
||||||
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
@@ -302,16 +305,16 @@ onMounted(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding: 5px 0;
|
padding: 5px 0;
|
||||||
border-radius: 4px;
|
border-radius: var(--app-radius-md);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3);
|
box-shadow: var(--app-shadow-md);
|
||||||
li {
|
li {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 7px 16px;
|
padding: 7px 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #eee;
|
background: var(--el-fill-color-light);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
103
src/layout/components/TopBar/index.vue
Normal file
103
src/layout/components/TopBar/index.vue
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<template>
|
||||||
|
<el-menu class="topbar-menu" :ellipsis="false" :default-active="activeMenu" :active-text-color="theme" mode="horizontal">
|
||||||
|
<sidebar-item :key="route.path + index" v-for="(route, index) in topMenus" :item="route" :base-path="route.path" />
|
||||||
|
|
||||||
|
<el-sub-menu index="more" class="el-sub-menu__hide-arrow" v-if="moreRoutes.length > 0">
|
||||||
|
<template #title>
|
||||||
|
<span>更多菜单</span>
|
||||||
|
</template>
|
||||||
|
<sidebar-item :key="route.path + index" v-for="(route, index) in moreRoutes" :item="route" :base-path="route.path" />
|
||||||
|
</el-sub-menu>
|
||||||
|
</el-menu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import SidebarItem from '../Sidebar/SidebarItem'
|
||||||
|
import {useAppStore} from '@/store/modules/app'
|
||||||
|
import {useSettingsStore} from '@/store/modules/settings'
|
||||||
|
import {usePermissionStore} from '@/store/modules/permission'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const settingsStore = useSettingsStore()
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
const sidebarRouters = computed(() => permissionStore.sidebarRouters)
|
||||||
|
const theme = computed(() => settingsStore.theme)
|
||||||
|
const device = computed(() => appStore.device)
|
||||||
|
const activeMenu = computed(() => {
|
||||||
|
const { meta, path } = route
|
||||||
|
if (meta.activeMenu) {
|
||||||
|
return meta.activeMenu
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
})
|
||||||
|
|
||||||
|
const visibleNumber = ref(5)
|
||||||
|
const topMenus = computed(() => {
|
||||||
|
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(0, visibleNumber.value)
|
||||||
|
})
|
||||||
|
const moreRoutes = computed(() => {
|
||||||
|
return permissionStore.sidebarRouters.filter((f) => !f.hidden).slice(visibleNumber.value, sidebarRouters.value.length - visibleNumber.value)
|
||||||
|
})
|
||||||
|
function setVisibleNumber() {
|
||||||
|
let width = document.body.getBoundingClientRect().width
|
||||||
|
if (width >= 1000) {
|
||||||
|
width -= 500
|
||||||
|
}
|
||||||
|
visibleNumber.value = parseInt(width / 3 / 85)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
window.addEventListener('resize', setVisibleNumber)
|
||||||
|
})
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener('resize', setVisibleNumber)
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setVisibleNumber()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
/* menu item */
|
||||||
|
#app .topbar-menu.el-menu--horizontal .el-sub-menu__title, #app .topbar-menu.el-menu--horizontal .el-menu-item {
|
||||||
|
padding: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-menu.el-menu--horizontal > .el-menu-item {
|
||||||
|
float: left;
|
||||||
|
height: 50px !important;
|
||||||
|
line-height: 50px !important;
|
||||||
|
color: #303133 !important;
|
||||||
|
padding: 0 5px !important;
|
||||||
|
margin: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-sub-menu.is-active .svg-icon, .el-menu-item.is-active .svg-icon + span, .el-sub-menu.is-active .svg-icon + span, .el-sub-menu.is-active .el-sub-menu__title span {
|
||||||
|
color: v-bind(theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sub-menu item */
|
||||||
|
.topbar-menu.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
|
||||||
|
float: left;
|
||||||
|
line-height: 50px !important;
|
||||||
|
color: #303133 !important;
|
||||||
|
margin: 0 15px -3px!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* topbar more arrow */
|
||||||
|
.topbar-menu .el-sub-menu .el-sub-menu__icon-arrow {
|
||||||
|
position: static;
|
||||||
|
margin-left: 8px;
|
||||||
|
margin-top: 0px;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* menu__title el-menu-item */
|
||||||
|
.topbar-menu.el-menu--horizontal .el-sub-menu__title, .topbar-menu.el-menu--horizontal .el-menu-item {
|
||||||
|
height: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,6 +26,7 @@ import SideBar from './components/Sidebar/index.vue';
|
|||||||
import { AppMain, Navbar, Settings, TagsView } from './components';
|
import { AppMain, Navbar, Settings, TagsView } from './components';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
import { useSettingsStore } from '@/store/modules/settings';
|
import { useSettingsStore } from '@/store/modules/settings';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
import { initWebSocket } from '@/utils/websocket';
|
import { initWebSocket } from '@/utils/websocket';
|
||||||
import { initSSE } from '@/utils/sse';
|
import { initSSE } from '@/utils/sse';
|
||||||
|
|
||||||
@@ -35,6 +36,13 @@ const sidebar = computed(() => useAppStore().sidebar);
|
|||||||
const device = computed(() => useAppStore().device);
|
const device = computed(() => useAppStore().device);
|
||||||
const needTagsView = computed(() => settingsStore.tagsView);
|
const needTagsView = computed(() => settingsStore.tagsView);
|
||||||
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
const fixedHeader = computed(() => settingsStore.fixedHeader);
|
||||||
|
const layout = computed(() => settingsStore.navType);
|
||||||
|
|
||||||
|
// 根据布局模式判断是否显示侧边栏
|
||||||
|
const showSidebar = computed(() => {
|
||||||
|
if (sidebar.value.hide) return false;
|
||||||
|
return layout.value === NavTypeEnum.LEFT || layout.value === NavTypeEnum.MIX;
|
||||||
|
});
|
||||||
|
|
||||||
const classObj = computed(() => ({
|
const classObj = computed(() => ({
|
||||||
hideSidebar: !sidebar.value.opened,
|
hideSidebar: !sidebar.value.opened,
|
||||||
|
|||||||
@@ -28,9 +28,6 @@ import ElementIcons from '@/plugins/svgicon';
|
|||||||
// permission control
|
// permission control
|
||||||
import './permission';
|
import './permission';
|
||||||
|
|
||||||
// 开发者工具保护
|
|
||||||
import { initDevToolsProtection } from '@/utils/devtools-protection';
|
|
||||||
|
|
||||||
// 国际化
|
// 国际化
|
||||||
import i18n from '@/lang/index';
|
import i18n from '@/lang/index';
|
||||||
|
|
||||||
@@ -58,6 +55,3 @@ app.use(plugins);
|
|||||||
directive(app);
|
directive(app);
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
// 初始化开发者工具保护(仅生产环境)
|
|
||||||
initDevToolsProtection();
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { LanguageEnum } from '@/enums/LanguageEnum';
|
import { LanguageEnum } from '@/enums/LanguageEnum';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
|
|
||||||
const setting: DefaultSettings = {
|
const setting: DefaultSettings = {
|
||||||
/**
|
/**
|
||||||
@@ -18,9 +19,9 @@ const setting: DefaultSettings = {
|
|||||||
showSettings: true,
|
showSettings: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示顶部导航
|
* 默认布局
|
||||||
*/
|
*/
|
||||||
topNav: false,
|
navType: NavTypeEnum.LEFT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示 tagsView
|
* 是否显示 tagsView
|
||||||
@@ -35,7 +36,7 @@ const setting: DefaultSettings = {
|
|||||||
/**
|
/**
|
||||||
* 是否固定头部
|
* 是否固定头部
|
||||||
*/
|
*/
|
||||||
fixedHeader: false,
|
fixedHeader: true,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示logo
|
* 是否显示logo
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import defaultSettings from '@/settings';
|
|||||||
import { useDynamicTitle } from '@/utils/dynamicTitle';
|
import { useDynamicTitle } from '@/utils/dynamicTitle';
|
||||||
import { useStorage } from '@vueuse/core';
|
import { useStorage } from '@vueuse/core';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
|
|
||||||
export const useSettingsStore = defineStore('setting', () => {
|
export const useSettingsStore = defineStore('setting', () => {
|
||||||
const storageSetting = useStorage<LayoutSetting>('layout-setting', {
|
const storageSetting = useStorage<LayoutSetting>('layout-setting', {
|
||||||
@@ -13,13 +14,13 @@ export const useSettingsStore = defineStore('setting', () => {
|
|||||||
sidebarLogo: defaultSettings.sidebarLogo,
|
sidebarLogo: defaultSettings.sidebarLogo,
|
||||||
dynamicTitle: defaultSettings.dynamicTitle,
|
dynamicTitle: defaultSettings.dynamicTitle,
|
||||||
sideTheme: defaultSettings.sideTheme,
|
sideTheme: defaultSettings.sideTheme,
|
||||||
theme: defaultSettings.theme
|
theme: defaultSettings.theme,
|
||||||
|
navType: defaultSettings.navType
|
||||||
});
|
});
|
||||||
const title = ref<string>(defaultSettings.title);
|
const title = ref<string>(defaultSettings.title);
|
||||||
const theme = ref<string>(storageSetting.value.theme);
|
const theme = ref<string>(storageSetting.value.theme);
|
||||||
const sideTheme = ref<string>(storageSetting.value.sideTheme);
|
const sideTheme = ref<string>(storageSetting.value.sideTheme);
|
||||||
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
const showSettings = ref<boolean>(defaultSettings.showSettings);
|
||||||
const topNav = ref<boolean>(storageSetting.value.topNav);
|
|
||||||
const tagsView = ref<boolean>(storageSetting.value.tagsView);
|
const tagsView = ref<boolean>(storageSetting.value.tagsView);
|
||||||
const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
|
const tagsIcon = ref<boolean>(storageSetting.value.tagsIcon);
|
||||||
const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
|
const fixedHeader = ref<boolean>(storageSetting.value.fixedHeader);
|
||||||
@@ -27,6 +28,7 @@ export const useSettingsStore = defineStore('setting', () => {
|
|||||||
const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
|
const dynamicTitle = ref<boolean>(storageSetting.value.dynamicTitle);
|
||||||
const animationEnable = ref<boolean>(defaultSettings.animationEnable);
|
const animationEnable = ref<boolean>(defaultSettings.animationEnable);
|
||||||
const dark = ref<boolean>(defaultSettings.dark);
|
const dark = ref<boolean>(defaultSettings.dark);
|
||||||
|
const navType = ref<NavTypeEnum>(storageSetting.value.navType || NavTypeEnum.LEFT);
|
||||||
|
|
||||||
const setTitle = (value: string) => {
|
const setTitle = (value: string) => {
|
||||||
title.value = value;
|
title.value = value;
|
||||||
@@ -37,7 +39,6 @@ export const useSettingsStore = defineStore('setting', () => {
|
|||||||
theme,
|
theme,
|
||||||
sideTheme,
|
sideTheme,
|
||||||
showSettings,
|
showSettings,
|
||||||
topNav,
|
|
||||||
tagsView,
|
tagsView,
|
||||||
tagsIcon,
|
tagsIcon,
|
||||||
fixedHeader,
|
fixedHeader,
|
||||||
@@ -45,6 +46,7 @@ export const useSettingsStore = defineStore('setting', () => {
|
|||||||
dynamicTitle,
|
dynamicTitle,
|
||||||
animationEnable,
|
animationEnable,
|
||||||
dark,
|
dark,
|
||||||
|
navType,
|
||||||
setTitle
|
setTitle
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
5
src/types/global.d.ts
vendored
5
src/types/global.d.ts
vendored
@@ -1,5 +1,6 @@
|
|||||||
import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
|
import type { PropType as VuePropType, ComponentInternalInstance as ComponentInstance } from 'vue';
|
||||||
import { LanguageEnum } from '@/enums/LanguageEnum';
|
import { LanguageEnum } from '@/enums/LanguageEnum';
|
||||||
|
import { NavTypeEnum } from '@/enums/NavTypeEnum';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
/** vue Instance */
|
/** vue Instance */
|
||||||
@@ -90,9 +91,9 @@ declare global {
|
|||||||
}
|
}
|
||||||
declare interface LayoutSetting {
|
declare interface LayoutSetting {
|
||||||
/**
|
/**
|
||||||
* 是否显示顶部导航
|
* 默认布局
|
||||||
*/
|
*/
|
||||||
topNav: boolean;
|
navType: NavTypeEnum;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否显示多标签导航
|
* 是否显示多标签导航
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
/**
|
|
||||||
* 开发者工具保护
|
|
||||||
* 检测开发者工具是否打开,如果打开则循环执行 debugger 阻止调试
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 检测开发者工具是否打开
|
|
||||||
function detectDevTools(): boolean {
|
|
||||||
try {
|
|
||||||
// 方法1: 检测窗口尺寸差异(最可靠的方法)
|
|
||||||
const widthThreshold = 160; // 开发者工具最小宽度
|
|
||||||
const heightThreshold = 160; // 开发者工具最小高度
|
|
||||||
|
|
||||||
const widthDiff = window.outerWidth - window.innerWidth;
|
|
||||||
const heightDiff = window.outerHeight - window.innerHeight;
|
|
||||||
|
|
||||||
if (widthDiff > widthThreshold || heightDiff > heightThreshold) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法2: 检测 debugger 执行时间(最准确的方法)
|
|
||||||
const start = performance.now();
|
|
||||||
debugger; // 这个 debugger 用于检测,不会被移除
|
|
||||||
const end = performance.now();
|
|
||||||
const timeDiff = end - start;
|
|
||||||
|
|
||||||
// 如果 debugger 被跳过(开发者工具关闭),时间差会很小(通常 < 1ms)
|
|
||||||
// 如果 debugger 暂停(开发者工具打开),时间差会很大(通常 > 100ms)
|
|
||||||
// 降低阈值以提高检测灵敏度
|
|
||||||
if (timeDiff > 10) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法3: 检测控制台对象
|
|
||||||
let devtoolsDetected = false;
|
|
||||||
const element = document.createElement('div');
|
|
||||||
Object.defineProperty(element, 'id', {
|
|
||||||
get: function () {
|
|
||||||
devtoolsDetected = true;
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 使用 console 来触发 getter(仅在开发者工具打开时)
|
|
||||||
try {
|
|
||||||
console.log(element);
|
|
||||||
console.clear();
|
|
||||||
} catch (e) {
|
|
||||||
// 忽略错误
|
|
||||||
}
|
|
||||||
|
|
||||||
if (devtoolsDetected) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 方法4: 检测控制台是否被重写(开发者工具打开时)
|
|
||||||
const devtoolsRegex = /./;
|
|
||||||
// @ts-expect-error - 动态添加属性
|
|
||||||
devtoolsRegex.toString = function () {
|
|
||||||
// @ts-expect-error - 动态添加属性
|
|
||||||
this.opened = true;
|
|
||||||
};
|
|
||||||
console.log('%c', devtoolsRegex);
|
|
||||||
// @ts-expect-error - 检查动态添加的属性
|
|
||||||
if (devtoolsRegex.opened) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// 如果检测过程中出错,默认返回 false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开发者工具保护主函数
|
|
||||||
export function initDevToolsProtection(): void {
|
|
||||||
// 可以通过环境变量控制是否启用
|
|
||||||
// 生产环境默认启用,开发环境可以通过 VITE_ENABLE_ANTI_DEBUG=true 来启用测试
|
|
||||||
const isProduction = import.meta.env.MODE === 'production';
|
|
||||||
const enableAntiDebug = import.meta.env.VITE_ENABLE_ANTI_DEBUG === 'true' || isProduction;
|
|
||||||
|
|
||||||
if (!enableAntiDebug) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let devToolsOpen = false;
|
|
||||||
let debuggerInterval: number | null = null;
|
|
||||||
|
|
||||||
// 立即执行一次检测
|
|
||||||
const initialCheck = detectDevTools();
|
|
||||||
if (initialCheck) {
|
|
||||||
devToolsOpen = true;
|
|
||||||
debuggerInterval = window.setInterval(() => {
|
|
||||||
debugger; // 循环执行 debugger
|
|
||||||
}, 50); // 更频繁的 debugger,每 50ms 一次
|
|
||||||
}
|
|
||||||
|
|
||||||
// 循环检测开发者工具(更频繁的检测)
|
|
||||||
const checkInterval = setInterval(() => {
|
|
||||||
const isOpen = detectDevTools();
|
|
||||||
|
|
||||||
if (isOpen && !devToolsOpen) {
|
|
||||||
// 开发者工具刚打开
|
|
||||||
devToolsOpen = true;
|
|
||||||
|
|
||||||
// 开始循环执行 debugger(更频繁)
|
|
||||||
if (debuggerInterval === null) {
|
|
||||||
debuggerInterval = window.setInterval(() => {
|
|
||||||
debugger; // 循环执行 debugger
|
|
||||||
}, 50); // 每 50ms 执行一次,更激进
|
|
||||||
}
|
|
||||||
} else if (!isOpen && devToolsOpen) {
|
|
||||||
// 开发者工具关闭了
|
|
||||||
devToolsOpen = false;
|
|
||||||
|
|
||||||
// 停止循环 debugger
|
|
||||||
if (debuggerInterval !== null) {
|
|
||||||
clearInterval(debuggerInterval);
|
|
||||||
debuggerInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500); // 每 500ms 检测一次,更频繁
|
|
||||||
|
|
||||||
// 页面卸载时清理
|
|
||||||
window.addEventListener('beforeunload', () => {
|
|
||||||
clearInterval(checkInterval);
|
|
||||||
if (debuggerInterval !== null) {
|
|
||||||
clearInterval(debuggerInterval);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 额外的检测:监听窗口大小变化
|
|
||||||
let lastWidth = window.innerWidth;
|
|
||||||
let lastHeight = window.innerHeight;
|
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
|
||||||
const currentWidth = window.innerWidth;
|
|
||||||
const currentHeight = window.innerHeight;
|
|
||||||
|
|
||||||
// 如果窗口尺寸变化很大,可能是开发者工具打开/关闭
|
|
||||||
if (Math.abs(currentWidth - lastWidth) > 200 || Math.abs(currentHeight - lastHeight) > 200) {
|
|
||||||
// 重新检测
|
|
||||||
const isOpen = detectDevTools();
|
|
||||||
if (isOpen && !devToolsOpen) {
|
|
||||||
devToolsOpen = true;
|
|
||||||
if (debuggerInterval === null) {
|
|
||||||
debuggerInterval = window.setInterval(() => {
|
|
||||||
debugger; // 循环执行 debugger
|
|
||||||
}, 50); // 更频繁的 debugger
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastWidth = currentWidth;
|
|
||||||
lastHeight = currentHeight;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
* 部署方式 Docker 容器编排 一键部署业务集群<br />
|
* 部署方式 Docker 容器编排 一键部署业务集群<br />
|
||||||
* 国际化 SpringMessage Spring标准国际化方案<br />
|
* 国际化 SpringMessage Spring标准国际化方案<br />
|
||||||
</p>
|
</p>
|
||||||
<p><b>当前版本:</b> <span>v5.5.3</span></p>
|
<p><b>当前版本:</b> <span>v5.6.0</span></p>
|
||||||
<p>
|
<p>
|
||||||
<el-tag type="danger">¥免费开源</el-tag>
|
<el-tag type="danger">¥免费开源</el-tag>
|
||||||
</p>
|
</p>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
* 分布式监控 Prometheus、Grafana 全方位性能监控<br />
|
* 分布式监控 Prometheus、Grafana 全方位性能监控<br />
|
||||||
* 其余与 Vue 版本一致<br />
|
* 其余与 Vue 版本一致<br />
|
||||||
</p>
|
</p>
|
||||||
<p><b>当前版本:</b> <span>v2.5.3</span></p>
|
<p><b>当前版本:</b> <span>v2.6.0</span></p>
|
||||||
<p>
|
<p>
|
||||||
<el-tag type="danger">¥免费开源</el-tag>
|
<el-tag type="danger">¥免费开源</el-tag>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -240,29 +240,38 @@ onMounted(() => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background-image: url('../assets/images/login-background.jpg');
|
background-image: url('../assets/images/login-background.jpg');
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-box {
|
.title-box {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin: 0px auto 30px auto;
|
margin: 0px auto 26px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #707070;
|
color: var(--el-text-color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.lang-select--style) {
|
:deep(.lang-select--style) {
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
color: #7483a3;
|
color: var(--el-text-color-secondary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
border-radius: 6px;
|
border-radius: var(--app-radius-lg);
|
||||||
background: #ffffff;
|
background: rgba(255, 255, 255, 0.94);
|
||||||
width: 400px;
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
padding: 25px 25px 5px 25px;
|
width: min(420px, 90vw);
|
||||||
|
padding: 32px 30px 12px 30px;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
box-shadow: var(--app-shadow-lg);
|
||||||
|
backdrop-filter: blur(6px);
|
||||||
|
-webkit-backdrop-filter: blur(6px);
|
||||||
.el-input {
|
.el-input {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
input {
|
input {
|
||||||
@@ -283,14 +292,48 @@ onMounted(() => {
|
|||||||
color: #bfbfbf;
|
color: #bfbfbf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-input__wrapper) {
|
||||||
|
background-color: rgba(255, 255, 255, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-input__wrapper.is-focus) {
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-button--primary) {
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-button.is-circle) {
|
||||||
|
background: rgba(15, 23, 42, 0.04);
|
||||||
|
border: 1px solid rgba(15, 23, 42, 0.08);
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-button.is-circle:hover) {
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
border-color: rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.login-code {
|
.login-code {
|
||||||
width: 33%;
|
width: calc(37% - 10px);
|
||||||
height: 40px;
|
height: 40px;
|
||||||
float: right;
|
float: right;
|
||||||
|
margin-left: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: var(--app-radius-sm);
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
border: 1px solid var(--el-border-color-light);
|
||||||
|
|
||||||
img {
|
img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 40px;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +344,7 @@ onMounted(() => {
|
|||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.75);
|
||||||
font-family: Arial, serif;
|
font-family: Arial, serif;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 1px;
|
letter-spacing: 1px;
|
||||||
@@ -309,6 +352,27 @@ onMounted(() => {
|
|||||||
|
|
||||||
.login-code-img {
|
.login-code-img {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
padding-left: 12px;
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) {
|
||||||
|
.login-form {
|
||||||
|
background: rgba(17, 24, 39, 0.9);
|
||||||
|
border-color: rgba(148, 163, 184, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-input__wrapper) {
|
||||||
|
background-color: rgba(17, 24, 39, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form :deep(.el-button.is-circle) {
|
||||||
|
background: rgba(148, 163, 184, 0.12);
|
||||||
|
border-color: rgba(148, 163, 184, 0.25);
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-login-footer {
|
||||||
|
color: rgba(226, 232, 240, 0.65);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,309 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="p-2">
|
|
||||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
||||||
<div v-show="showSearch" class="mb-[10px]">
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
||||||
<el-form-item label="字典名称" prop="dictType">
|
|
||||||
<el-select v-model="queryParams.dictType">
|
|
||||||
<el-option v-for="item in typeOptions" :key="item.dictId" :label="item.dictName" :value="item.dictType" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="字典标签" prop="dictLabel">
|
|
||||||
<el-input v-model="queryParams.dictLabel" placeholder="请输入字典标签" clearable @keyup.enter="handleQuery" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
|
||||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
||||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
</el-card>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
<el-card shadow="hover">
|
|
||||||
<template #header>
|
|
||||||
<el-row :gutter="10" class="mb8">
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button>
|
|
||||||
</el-col>
|
|
||||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table v-loading="loading" border :data="dataList" @selection-change="handleSelectionChange">
|
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
|
||||||
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
|
|
||||||
<el-table-column label="字典标签" align="center" prop="dictLabel">
|
|
||||||
<template #default="scope">
|
|
||||||
<span
|
|
||||||
v-if="(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)"
|
|
||||||
>{{ scope.row.dictLabel }}</span
|
|
||||||
>
|
|
||||||
<el-tag
|
|
||||||
v-else
|
|
||||||
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
|
|
||||||
:class="scope.row.cssClass"
|
|
||||||
>{{ scope.row.dictLabel }}</el-tag
|
|
||||||
>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="字典键值" align="center" prop="dictValue" />
|
|
||||||
<el-table-column label="字典排序" align="center" prop="dictSort" />
|
|
||||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
|
||||||
<template #default="scope">
|
|
||||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-tooltip content="修改" placement="top">
|
|
||||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
<el-tooltip content="删除" placement="top">
|
|
||||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
|
||||||
</el-tooltip>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
|
||||||
</el-card>
|
|
||||||
<!-- 添加或修改参数配置对话框 -->
|
|
||||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
|
||||||
<el-form ref="dataFormRef" :model="form" :rules="rules" label-width="80px">
|
|
||||||
<el-form-item label="字典类型">
|
|
||||||
<el-input v-model="form.dictType" :disabled="true" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="数据标签" prop="dictLabel">
|
|
||||||
<el-input v-model="form.dictLabel" placeholder="请输入数据标签" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="数据键值" prop="dictValue">
|
|
||||||
<el-input v-model="form.dictValue" placeholder="请输入数据键值" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="样式属性" prop="cssClass">
|
|
||||||
<el-input v-model="form.cssClass" placeholder="请输入样式属性" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="显示排序" prop="dictSort">
|
|
||||||
<el-input-number v-model="form.dictSort" controls-position="right" :min="0" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="回显样式" prop="listClass">
|
|
||||||
<el-select v-model="form.listClass">
|
|
||||||
<el-option
|
|
||||||
v-for="item in listClassOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label + '(' + item.value + ')'"
|
|
||||||
:value="item.value"
|
|
||||||
></el-option>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
|
||||||
<el-button @click="cancel">取 消</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup name="Data" lang="ts">
|
|
||||||
import { useDictStore } from '@/store/modules/dict';
|
|
||||||
import { optionselect as getDictOptionselect, getType } from '@/api/system/dict/type';
|
|
||||||
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
|
|
||||||
import { DictTypeVO } from '@/api/system/dict/type/types';
|
|
||||||
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
|
|
||||||
import { RouteLocationNormalized } from 'vue-router';
|
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const dataList = ref<DictDataVO[]>([]);
|
|
||||||
const loading = ref(true);
|
|
||||||
const showSearch = ref(true);
|
|
||||||
const ids = ref<Array<string | number>>([]);
|
|
||||||
const single = ref(true);
|
|
||||||
const multiple = ref(true);
|
|
||||||
const total = ref(0);
|
|
||||||
const defaultDictType = ref('');
|
|
||||||
const typeOptions = ref<DictTypeVO[]>([]);
|
|
||||||
|
|
||||||
const dataFormRef = ref<ElFormInstance>();
|
|
||||||
const queryFormRef = ref<ElFormInstance>();
|
|
||||||
|
|
||||||
const dialog = reactive<DialogOption>({
|
|
||||||
visible: false,
|
|
||||||
title: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// 数据标签回显样式
|
|
||||||
const listClassOptions = ref<Array<{ value: string; label: string }>>([
|
|
||||||
{ value: 'default', label: '默认' },
|
|
||||||
{ value: 'primary', label: '主要' },
|
|
||||||
{ value: 'success', label: '成功' },
|
|
||||||
{ value: 'info', label: '信息' },
|
|
||||||
{ value: 'warning', label: '警告' },
|
|
||||||
{ value: 'danger', label: '危险' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
const initFormData: DictDataForm = {
|
|
||||||
dictCode: undefined,
|
|
||||||
dictLabel: '',
|
|
||||||
dictValue: '',
|
|
||||||
cssClass: '',
|
|
||||||
listClass: 'primary',
|
|
||||||
dictSort: 0,
|
|
||||||
remark: ''
|
|
||||||
};
|
|
||||||
const data = reactive<PageData<DictDataForm, DictDataQuery>>({
|
|
||||||
form: { ...initFormData },
|
|
||||||
queryParams: {
|
|
||||||
pageNum: 1,
|
|
||||||
pageSize: 10,
|
|
||||||
dictName: '',
|
|
||||||
dictType: '',
|
|
||||||
dictLabel: ''
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
|
||||||
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
|
||||||
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
|
||||||
|
|
||||||
/** 查询字典类型详细 */
|
|
||||||
const getTypes = async (dictId: string | number) => {
|
|
||||||
const { data } = await getType(dictId);
|
|
||||||
queryParams.value.dictType = data.dictType;
|
|
||||||
defaultDictType.value = data.dictType;
|
|
||||||
getList();
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 查询字典类型列表 */
|
|
||||||
const getTypeList = async () => {
|
|
||||||
const res = await getDictOptionselect();
|
|
||||||
typeOptions.value = res.data;
|
|
||||||
};
|
|
||||||
/** 查询字典数据列表 */
|
|
||||||
const getList = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
const res = await listData(queryParams.value);
|
|
||||||
dataList.value = res.rows;
|
|
||||||
total.value = res.total;
|
|
||||||
loading.value = false;
|
|
||||||
};
|
|
||||||
/** 取消按钮 */
|
|
||||||
const cancel = () => {
|
|
||||||
dialog.visible = false;
|
|
||||||
reset();
|
|
||||||
};
|
|
||||||
/** 表单重置 */
|
|
||||||
const reset = () => {
|
|
||||||
form.value = { ...initFormData };
|
|
||||||
dataFormRef.value?.resetFields();
|
|
||||||
};
|
|
||||||
/** 搜索按钮操作 */
|
|
||||||
const handleQuery = () => {
|
|
||||||
queryParams.value.pageNum = 1;
|
|
||||||
getList();
|
|
||||||
};
|
|
||||||
/** 返回按钮操作 */
|
|
||||||
const handleClose = () => {
|
|
||||||
const obj: RouteLocationNormalized = {
|
|
||||||
fullPath: '',
|
|
||||||
hash: '',
|
|
||||||
matched: [],
|
|
||||||
meta: undefined,
|
|
||||||
name: undefined,
|
|
||||||
params: undefined,
|
|
||||||
query: undefined,
|
|
||||||
redirectedFrom: undefined,
|
|
||||||
path: '/system/dict'
|
|
||||||
};
|
|
||||||
proxy?.$tab.closeOpenPage(obj);
|
|
||||||
};
|
|
||||||
/** 重置按钮操作 */
|
|
||||||
const resetQuery = () => {
|
|
||||||
queryFormRef.value?.resetFields();
|
|
||||||
queryParams.value.dictType = defaultDictType.value;
|
|
||||||
handleQuery();
|
|
||||||
};
|
|
||||||
/** 新增按钮操作 */
|
|
||||||
const handleAdd = () => {
|
|
||||||
reset();
|
|
||||||
form.value.dictType = queryParams.value.dictType;
|
|
||||||
dialog.visible = true;
|
|
||||||
dialog.title = '添加字典数据';
|
|
||||||
};
|
|
||||||
/** 多选框选中数据 */
|
|
||||||
const handleSelectionChange = (selection: DictDataVO[]) => {
|
|
||||||
ids.value = selection.map((item) => item.dictCode);
|
|
||||||
single.value = selection.length != 1;
|
|
||||||
multiple.value = !selection.length;
|
|
||||||
};
|
|
||||||
/** 修改按钮操作 */
|
|
||||||
const handleUpdate = async (row?: DictDataVO) => {
|
|
||||||
reset();
|
|
||||||
const dictCode = row?.dictCode || ids.value[0];
|
|
||||||
const res = await getData(dictCode);
|
|
||||||
Object.assign(form.value, res.data);
|
|
||||||
dialog.visible = true;
|
|
||||||
dialog.title = '修改字典数据';
|
|
||||||
};
|
|
||||||
/** 提交按钮 */
|
|
||||||
const submitForm = () => {
|
|
||||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
|
||||||
if (valid) {
|
|
||||||
form.value.dictCode ? await updateData(form.value) : await addData(form.value);
|
|
||||||
useDictStore().removeDict(queryParams.value.dictType);
|
|
||||||
proxy?.$modal.msgSuccess('操作成功');
|
|
||||||
dialog.visible = false;
|
|
||||||
await getList();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/** 删除按钮操作 */
|
|
||||||
const handleDelete = async (row?: DictDataVO) => {
|
|
||||||
const dictCodes = row?.dictCode || ids.value;
|
|
||||||
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
|
|
||||||
await delData(dictCodes);
|
|
||||||
await getList();
|
|
||||||
proxy?.$modal.msgSuccess('删除成功');
|
|
||||||
useDictStore().removeDict(queryParams.value.dictType);
|
|
||||||
};
|
|
||||||
/** 导出按钮操作 */
|
|
||||||
const handleExport = () => {
|
|
||||||
proxy?.download(
|
|
||||||
'system/dict/data/export',
|
|
||||||
{
|
|
||||||
...queryParams.value
|
|
||||||
},
|
|
||||||
`dict_data_${new Date().getTime()}.xlsx`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
getTypes(route.params && (route.params.dictId as string));
|
|
||||||
getTypeList();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,106 +1,274 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-2">
|
<div class="p-2 dict-page">
|
||||||
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
<el-row :gutter="16" class="dict-grid">
|
||||||
<div v-show="showSearch" class="mb-[10px]">
|
<!-- 字典类型 -->
|
||||||
<el-card shadow="hover">
|
<el-col :xs="24" :lg="12">
|
||||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
<el-card shadow="hover" class="dict-card">
|
||||||
<el-form-item label="字典名称" prop="dictName">
|
<template #header>
|
||||||
<el-input v-model="queryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleQuery" />
|
<div class="dict-card__header">
|
||||||
</el-form-item>
|
<div class="dict-card__title">字典管理</div>
|
||||||
<el-form-item label="字典类型" prop="dictType">
|
<right-toolbar v-model:show-search="showTypeSearch" @query-table="getTypeList" />
|
||||||
<el-input v-model="queryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleQuery" />
|
</div>
|
||||||
</el-form-item>
|
</template>
|
||||||
<el-form-item label="创建时间" style="width: 308px">
|
|
||||||
<el-date-picker
|
<div v-show="showTypeSearch" class="dict-form-scroll">
|
||||||
v-model="dateRange"
|
<el-form ref="typeQueryFormRef" :model="typeQueryParams" :inline="true">
|
||||||
value-format="YYYY-MM-DD HH:mm:ss"
|
<el-form-item label="字典名称" prop="dictName">
|
||||||
type="daterange"
|
<el-input v-model="typeQueryParams.dictName" placeholder="请输入字典名称" clearable @keyup.enter="handleTypeQuery" />
|
||||||
range-separator="-"
|
</el-form-item>
|
||||||
start-placeholder="开始日期"
|
<el-form-item label="字典类型" prop="dictType">
|
||||||
end-placeholder="结束日期"
|
<el-input v-model="typeQueryParams.dictType" placeholder="请输入字典类型" clearable @keyup.enter="handleTypeQuery" />
|
||||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
</el-form-item>
|
||||||
></el-date-picker>
|
<el-form-item label="创建时间" style="width: 308px">
|
||||||
</el-form-item>
|
<el-date-picker
|
||||||
<el-form-item>
|
v-model="dateRange"
|
||||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
type="daterange"
|
||||||
</el-form-item>
|
range-separator="-"
|
||||||
</el-form>
|
start-placeholder="开始日期"
|
||||||
</el-card>
|
end-placeholder="结束日期"
|
||||||
</div>
|
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||||
</transition>
|
></el-date-picker>
|
||||||
<el-card shadow="hover">
|
</el-form-item>
|
||||||
<template #header>
|
<el-form-item>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-button type="primary" icon="Search" @click="handleTypeQuery">搜索</el-button>
|
||||||
<el-col :span="1.5">
|
<el-button icon="Refresh" @click="handleTypeResetQuery">重置</el-button>
|
||||||
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
|
</el-form-item>
|
||||||
</el-col>
|
</el-form>
|
||||||
<el-col :span="1.5">
|
</div>
|
||||||
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
|
||||||
</el-col>
|
<div class="dict-actions">
|
||||||
<el-col :span="1.5">
|
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" @click="handleTypeAdd">新增</el-button>
|
||||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()">
|
<el-button v-hasPermi="['system:dict:edit']" type="success" plain icon="Edit" :disabled="typeSingle" @click="handleTypeUpdate()"
|
||||||
删除
|
>修改</el-button
|
||||||
</el-button>
|
>
|
||||||
</el-col>
|
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Delete" :disabled="typeMultiple" @click="handleTypeDelete()"
|
||||||
<el-col :span="1.5">
|
>删除</el-button
|
||||||
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleExport">导出</el-button>
|
>
|
||||||
</el-col>
|
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" @click="handleTypeExport">导出</el-button>
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
|
<el-button v-hasPermi="['system:dict:remove']" type="danger" plain icon="Refresh" @click="handleRefreshCache">刷新缓存</el-button>
|
||||||
</el-col>
|
</div>
|
||||||
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
|
|
||||||
</el-row>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-table v-loading="loading" border :data="typeList" @selection-change="handleSelectionChange">
|
<div class="dict-table-wrap">
|
||||||
<el-table-column type="selection" width="55" align="center" />
|
<el-table
|
||||||
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
|
ref="typeTableRef"
|
||||||
<el-table-column label="字典名称" align="center" prop="dictName" :show-overflow-tooltip="true" />
|
v-loading="typeLoading"
|
||||||
<el-table-column label="字典类型" align="center" :show-overflow-tooltip="true">
|
border
|
||||||
<template #default="scope">
|
:data="typeList"
|
||||||
<router-link :to="'/system/dict-data/index/' + scope.row.dictId" class="link-type">
|
highlight-current-row
|
||||||
<span>{{ scope.row.dictType }}</span>
|
@row-click="handleTypeRowClick"
|
||||||
</router-link>
|
@selection-change="handleTypeSelectionChange"
|
||||||
</template>
|
>
|
||||||
</el-table-column>
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
<el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true" />
|
<el-table-column v-if="false" label="字典编号" align="center" prop="dictId" />
|
||||||
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
<el-table-column label="字典名称" align="center" prop="dictName" width="120" />
|
||||||
<template #default="scope">
|
<el-table-column label="字典类型" align="center" prop="dictType" width="160">
|
||||||
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
<template #default="scope">
|
||||||
</template>
|
<span class="link-type" @click.stop="handleTypeRowClick(scope.row)">{{ scope.row.dictType }}</span>
|
||||||
</el-table-column>
|
</template>
|
||||||
<el-table-column label="操作" align="center" width="160" class-name="small-padding fixed-width">
|
</el-table-column>
|
||||||
<template #default="scope">
|
<el-table-column label="备注" align="center" prop="remark" width="160"/>
|
||||||
<el-tooltip content="修改" placement="top">
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
|
<template #default="scope">
|
||||||
</el-tooltip>
|
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||||
<el-tooltip content="删除" placement="top">
|
</template>
|
||||||
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button>
|
</el-table-column>
|
||||||
</el-tooltip>
|
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
|
||||||
</template>
|
<template #default="scope">
|
||||||
</el-table-column>
|
<el-tooltip content="修改" placement="top">
|
||||||
</el-table>
|
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleTypeUpdate(scope.row)"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="删除" placement="top">
|
||||||
|
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleTypeDelete(scope.row)"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" />
|
<pagination
|
||||||
</el-card>
|
v-show="typeTotal > 0"
|
||||||
<!-- 添加或修改参数配置对话框 -->
|
v-model:page="typeQueryParams.pageNum"
|
||||||
<el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body>
|
v-model:limit="typeQueryParams.pageSize"
|
||||||
<el-form ref="dictFormRef" :model="form" :rules="rules" label-width="80px">
|
:total="typeTotal"
|
||||||
|
@pagination="getTypeList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
|
||||||
|
<!-- 字典数据 -->
|
||||||
|
<el-col :xs="24" :lg="12">
|
||||||
|
<el-card shadow="hover" class="dict-card">
|
||||||
|
<template #header>
|
||||||
|
<div class="dict-card__header">
|
||||||
|
<div class="dict-card__title">
|
||||||
|
字典数据
|
||||||
|
<span class="dict-card__subtitle">{{ currentDictLabel }}</span>
|
||||||
|
</div>
|
||||||
|
<right-toolbar v-model:show-search="showDataSearch" @query-table="getDataList" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-show="showDataSearch" class="dict-form-scroll">
|
||||||
|
<el-form ref="dataQueryFormRef" :model="dataQueryParams" :inline="true">
|
||||||
|
<el-form-item label="字典标签" prop="dictLabel">
|
||||||
|
<el-input
|
||||||
|
v-model="dataQueryParams.dictLabel"
|
||||||
|
placeholder="请输入字典标签"
|
||||||
|
clearable
|
||||||
|
:disabled="!hasCurrentDict"
|
||||||
|
@keyup.enter="handleDataQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" icon="Search" :disabled="!hasCurrentDict" @click="handleDataQuery">搜索</el-button>
|
||||||
|
<el-button icon="Refresh" :disabled="!hasCurrentDict" @click="handleDataResetQuery">重置</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dict-actions">
|
||||||
|
<el-button v-hasPermi="['system:dict:add']" type="primary" plain icon="Plus" :disabled="!hasCurrentDict" @click="handleDataAdd"
|
||||||
|
>新增</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:dict:edit']"
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
icon="Edit"
|
||||||
|
:disabled="dataSingle || !hasCurrentDict"
|
||||||
|
@click="handleDataUpdate()"
|
||||||
|
>修改</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-hasPermi="['system:dict:remove']"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
icon="Delete"
|
||||||
|
:disabled="dataMultiple || !hasCurrentDict"
|
||||||
|
@click="handleDataDelete()"
|
||||||
|
>删除</el-button
|
||||||
|
>
|
||||||
|
<el-button v-hasPermi="['system:dict:export']" type="warning" plain icon="Download" :disabled="!hasCurrentDict" @click="handleDataExport"
|
||||||
|
>导出</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dict-table-wrap">
|
||||||
|
<el-table v-loading="dataLoading" border :data="dataList" @selection-change="handleDataSelectionChange">
|
||||||
|
<el-table-column type="selection" width="55" align="center" />
|
||||||
|
<el-table-column v-if="false" label="字典编码" align="center" prop="dictCode" />
|
||||||
|
<el-table-column label="字典标签" align="center" prop="dictLabel" width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<span
|
||||||
|
v-if="
|
||||||
|
(scope.row.listClass === '' || scope.row.listClass === 'default') && (scope.row.cssClass === '' || scope.row.cssClass == null)
|
||||||
|
"
|
||||||
|
>{{ scope.row.dictLabel }}</span
|
||||||
|
>
|
||||||
|
<el-tag
|
||||||
|
v-else
|
||||||
|
:type="scope.row.listClass === 'primary' || scope.row.listClass === 'default' ? 'primary' : scope.row.listClass"
|
||||||
|
:class="scope.row.cssClass"
|
||||||
|
>{{ scope.row.dictLabel }}</el-tag
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="字典键值" align="center" prop="dictValue" width="80" />
|
||||||
|
<el-table-column label="字典排序" align="center" prop="dictSort" width="80" />
|
||||||
|
<el-table-column label="备注" align="center" prop="remark" width="100" />
|
||||||
|
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
|
||||||
|
<template #default="scope">
|
||||||
|
<span>{{ proxy.parseTime(scope.row.createTime) }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" fixed="right" align="center" width="120" class-name="small-padding fixed-width">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip content="修改" placement="top">
|
||||||
|
<el-button v-hasPermi="['system:dict:edit']" link type="primary" icon="Edit" @click="handleDataUpdate(scope.row)"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="删除" placement="top">
|
||||||
|
<el-button v-hasPermi="['system:dict:remove']" link type="primary" icon="Delete" @click="handleDataDelete(scope.row)"></el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="dataTotal > 0"
|
||||||
|
v-model:page="dataQueryParams.pageNum"
|
||||||
|
v-model:limit="dataQueryParams.pageSize"
|
||||||
|
:total="dataTotal"
|
||||||
|
@pagination="getDataList"
|
||||||
|
/>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 字典类型对话框 -->
|
||||||
|
<el-dialog v-model="typeDialog.visible" :title="typeDialog.title" width="500px" append-to-body>
|
||||||
|
<el-form ref="typeFormRef" :model="typeForm" :rules="typeRules" label-width="100px">
|
||||||
<el-form-item label="字典名称" prop="dictName">
|
<el-form-item label="字典名称" prop="dictName">
|
||||||
<el-input v-model="form.dictName" placeholder="请输入字典名称" />
|
<el-input v-model="typeForm.dictName" placeholder="请输入字典名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="字典类型" prop="dictType">
|
<el-form-item prop="dictType">
|
||||||
<el-input v-model="form.dictType" placeholder="请输入字典类型" />
|
<el-input v-model="typeForm.dictType" placeholder="请输入字典类型" maxlength="100" />
|
||||||
|
<span slot="label">
|
||||||
|
<el-tooltip content="数据存储中的Key值,如:sys_user_sex" placement="top">
|
||||||
|
<i class="el-icon-question"></i>
|
||||||
|
</el-tooltip>
|
||||||
|
字典类型
|
||||||
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
|
<el-input v-model="typeForm.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
<el-button type="primary" @click="submitTypeForm">确 定</el-button>
|
||||||
<el-button @click="cancel">取 消</el-button>
|
<el-button @click="cancelType">取 消</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 字典数据对话框 -->
|
||||||
|
<el-dialog v-model="dataDialog.visible" :title="dataDialog.title" width="500px" append-to-body>
|
||||||
|
<el-form ref="dataFormRef" :model="dataForm" :rules="dataRules" label-width="80px">
|
||||||
|
<el-form-item label="字典类型">
|
||||||
|
<el-input v-model="dataForm.dictType" :disabled="true" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="数据标签" prop="dictLabel">
|
||||||
|
<el-input v-model="dataForm.dictLabel" placeholder="请输入数据标签" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="数据键值" prop="dictValue">
|
||||||
|
<el-input v-model="dataForm.dictValue" placeholder="请输入数据键值" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="样式属性" prop="cssClass">
|
||||||
|
<el-input v-model="dataForm.cssClass" placeholder="请输入样式属性" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="显示排序" prop="dictSort">
|
||||||
|
<el-input-number v-model="dataForm.dictSort" controls-position="right" :min="0" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="回显样式" prop="listClass">
|
||||||
|
<el-select v-model="dataForm.listClass">
|
||||||
|
<el-option
|
||||||
|
v-for="item in listClassOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label + '(' + item.value + ')'"
|
||||||
|
:value="item.value"
|
||||||
|
></el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="dataForm.remark" type="textarea" placeholder="请输入内容"></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button type="primary" @click="submitDataForm">确 定</el-button>
|
||||||
|
<el-button @click="cancelData">取 消</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@@ -110,35 +278,39 @@
|
|||||||
<script setup name="Dict" lang="ts">
|
<script setup name="Dict" lang="ts">
|
||||||
import { useDictStore } from '@/store/modules/dict';
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
|
import { listType, getType, delType, addType, updateType, refreshCache } from '@/api/system/dict/type';
|
||||||
|
import { listData, getData, delData, addData, updateData } from '@/api/system/dict/data';
|
||||||
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
|
import { DictTypeForm, DictTypeQuery, DictTypeVO } from '@/api/system/dict/type/types';
|
||||||
|
import { DictDataForm, DictDataQuery, DictDataVO } from '@/api/system/dict/data/types';
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||||
|
|
||||||
const typeList = ref<DictTypeVO[]>([]);
|
const typeList = ref<DictTypeVO[]>([]);
|
||||||
const loading = ref(true);
|
const typeLoading = ref(true);
|
||||||
const showSearch = ref(true);
|
const showTypeSearch = ref(true);
|
||||||
const ids = ref<Array<number | string>>([]);
|
const typeIds = ref<Array<number | string>>([]);
|
||||||
const single = ref(true);
|
const typeSingle = ref(true);
|
||||||
const multiple = ref(true);
|
const typeMultiple = ref(true);
|
||||||
const total = ref(0);
|
const typeTotal = ref(0);
|
||||||
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
|
const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
|
||||||
|
|
||||||
const dictFormRef = ref<ElFormInstance>();
|
const typeFormRef = ref<ElFormInstance>();
|
||||||
const queryFormRef = ref<ElFormInstance>();
|
const typeQueryFormRef = ref<ElFormInstance>();
|
||||||
|
const typeTableRef = ref<ElTableInstance>();
|
||||||
|
|
||||||
const dialog = reactive<DialogOption>({
|
const typeDialog = reactive<DialogOption>({
|
||||||
visible: false,
|
visible: false,
|
||||||
title: ''
|
title: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const initFormData: DictTypeForm = {
|
const typeInitFormData: DictTypeForm = {
|
||||||
dictId: undefined,
|
dictId: undefined,
|
||||||
dictName: '',
|
dictName: '',
|
||||||
dictType: '',
|
dictType: '',
|
||||||
remark: ''
|
remark: ''
|
||||||
};
|
};
|
||||||
const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
|
||||||
form: { ...initFormData },
|
const typeState = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
||||||
|
form: { ...typeInitFormData },
|
||||||
queryParams: {
|
queryParams: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -151,96 +323,353 @@ const data = reactive<PageData<DictTypeForm, DictTypeQuery>>({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { queryParams, form, rules } = toRefs(data);
|
const { queryParams: typeQueryParams, form: typeForm, rules: typeRules } = toRefs(typeState);
|
||||||
|
|
||||||
/** 查询字典类型列表 */
|
const currentDict = ref<DictTypeVO | null>(null);
|
||||||
const getList = () => {
|
const hasCurrentDict = computed(() => !!currentDict.value);
|
||||||
loading.value = true;
|
const currentDictLabel = computed(() => {
|
||||||
listType(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => {
|
if (!currentDict.value) return '请先选择字典';
|
||||||
|
return `${currentDict.value.dictName} / ${currentDict.value.dictType}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataList = ref<DictDataVO[]>([]);
|
||||||
|
const dataLoading = ref(false);
|
||||||
|
const showDataSearch = ref(true);
|
||||||
|
const dataIds = ref<Array<string | number>>([]);
|
||||||
|
const dataSingle = ref(true);
|
||||||
|
const dataMultiple = ref(true);
|
||||||
|
const dataTotal = ref(0);
|
||||||
|
|
||||||
|
const dataFormRef = ref<ElFormInstance>();
|
||||||
|
const dataQueryFormRef = ref<ElFormInstance>();
|
||||||
|
|
||||||
|
const dataDialog = reactive<DialogOption>({
|
||||||
|
visible: false,
|
||||||
|
title: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const listClassOptions = ref<Array<{ value: string; label: string }>>([
|
||||||
|
{ value: 'default', label: '默认' },
|
||||||
|
{ value: 'primary', label: '主要' },
|
||||||
|
{ value: 'success', label: '成功' },
|
||||||
|
{ value: 'info', label: '信息' },
|
||||||
|
{ value: 'warning', label: '警告' },
|
||||||
|
{ value: 'danger', label: '危险' }
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dataInitFormData: DictDataForm = {
|
||||||
|
dictCode: undefined,
|
||||||
|
dictLabel: '',
|
||||||
|
dictValue: '',
|
||||||
|
cssClass: '',
|
||||||
|
listClass: 'primary',
|
||||||
|
dictSort: 0,
|
||||||
|
remark: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataState = reactive<PageData<DictDataForm, DictDataQuery>>({
|
||||||
|
form: { ...dataInitFormData },
|
||||||
|
queryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
dictName: '',
|
||||||
|
dictType: '',
|
||||||
|
dictLabel: ''
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
dictLabel: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],
|
||||||
|
dictValue: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],
|
||||||
|
dictSort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { queryParams: dataQueryParams, form: dataForm, rules: dataRules } = toRefs(dataState);
|
||||||
|
|
||||||
|
const getTypeList = () => {
|
||||||
|
typeLoading.value = true;
|
||||||
|
listType(proxy?.addDateRange(typeQueryParams.value, dateRange.value)).then((res) => {
|
||||||
typeList.value = res.rows;
|
typeList.value = res.rows;
|
||||||
total.value = res.total;
|
typeTotal.value = res.total;
|
||||||
loading.value = false;
|
typeLoading.value = false;
|
||||||
|
ensureCurrentType();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/** 取消按钮 */
|
|
||||||
const cancel = () => {
|
const ensureCurrentType = () => {
|
||||||
reset();
|
if (!typeList.value.length) {
|
||||||
dialog.visible = false;
|
currentDict.value = null;
|
||||||
|
dataQueryParams.value.dictType = '';
|
||||||
|
dataList.value = [];
|
||||||
|
dataTotal.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = currentDict.value && typeList.value.find((item) => item.dictId === currentDict.value?.dictId);
|
||||||
|
const nextRow = current || typeList.value[0];
|
||||||
|
setCurrentType(nextRow);
|
||||||
};
|
};
|
||||||
/** 表单重置 */
|
|
||||||
const reset = () => {
|
const setCurrentType = (row: DictTypeVO) => {
|
||||||
form.value = { ...initFormData };
|
currentDict.value = row;
|
||||||
dictFormRef.value?.resetFields();
|
dataQueryParams.value.dictType = row.dictType;
|
||||||
|
dataQueryParams.value.pageNum = 1;
|
||||||
|
dataQueryParams.value.dictLabel = '';
|
||||||
|
getDataList();
|
||||||
|
nextTick(() => typeTableRef.value?.setCurrentRow(row));
|
||||||
};
|
};
|
||||||
/** 搜索按钮操作 */
|
|
||||||
const handleQuery = () => {
|
const handleTypeRowClick = (row: DictTypeVO) => {
|
||||||
queryParams.value.pageNum = 1;
|
setCurrentType(row);
|
||||||
getList();
|
|
||||||
};
|
};
|
||||||
/** 重置按钮操作 */
|
|
||||||
const resetQuery = () => {
|
const cancelType = () => {
|
||||||
|
resetTypeForm();
|
||||||
|
typeDialog.visible = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetTypeForm = () => {
|
||||||
|
typeForm.value = { ...typeInitFormData };
|
||||||
|
typeFormRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTypeQuery = () => {
|
||||||
|
typeQueryParams.value.pageNum = 1;
|
||||||
|
getTypeList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTypeResetQuery = () => {
|
||||||
dateRange.value = ['', ''];
|
dateRange.value = ['', ''];
|
||||||
queryFormRef.value?.resetFields();
|
typeQueryFormRef.value?.resetFields();
|
||||||
handleQuery();
|
handleTypeQuery();
|
||||||
};
|
};
|
||||||
/** 新增按钮操作 */
|
|
||||||
const handleAdd = () => {
|
const handleTypeAdd = () => {
|
||||||
reset();
|
resetTypeForm();
|
||||||
dialog.visible = true;
|
typeDialog.visible = true;
|
||||||
dialog.title = '添加字典类型';
|
typeDialog.title = '添加字典类型';
|
||||||
};
|
};
|
||||||
/** 多选框选中数据 */
|
|
||||||
const handleSelectionChange = (selection: DictTypeVO[]) => {
|
const handleTypeSelectionChange = (selection: DictTypeVO[]) => {
|
||||||
ids.value = selection.map((item) => item.dictId);
|
typeIds.value = selection.map((item) => item.dictId);
|
||||||
single.value = selection.length != 1;
|
typeSingle.value = selection.length != 1;
|
||||||
multiple.value = !selection.length;
|
typeMultiple.value = !selection.length;
|
||||||
};
|
};
|
||||||
/** 修改按钮操作 */
|
|
||||||
const handleUpdate = async (row?: DictTypeVO) => {
|
const handleTypeUpdate = async (row?: DictTypeVO) => {
|
||||||
reset();
|
resetTypeForm();
|
||||||
const dictId = row?.dictId || ids.value[0];
|
const dictId = row?.dictId || typeIds.value[0];
|
||||||
const res = await getType(dictId);
|
const res = await getType(dictId);
|
||||||
Object.assign(form.value, res.data);
|
Object.assign(typeForm.value, res.data);
|
||||||
dialog.visible = true;
|
typeDialog.visible = true;
|
||||||
dialog.title = '修改字典类型';
|
typeDialog.title = '修改字典类型';
|
||||||
};
|
};
|
||||||
/** 提交按钮 */
|
|
||||||
const submitForm = () => {
|
const submitTypeForm = () => {
|
||||||
dictFormRef.value?.validate(async (valid: boolean) => {
|
typeFormRef.value?.validate(async (valid: boolean) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
form.value.dictId ? await updateType(form.value) : await addType(form.value);
|
typeForm.value.dictId ? await updateType(typeForm.value) : await addType(typeForm.value);
|
||||||
proxy?.$modal.msgSuccess('操作成功');
|
proxy?.$modal.msgSuccess('操作成功');
|
||||||
dialog.visible = false;
|
typeDialog.visible = false;
|
||||||
getList();
|
getTypeList();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
/** 删除按钮操作 */
|
|
||||||
const handleDelete = async (row?: DictTypeVO) => {
|
const handleTypeDelete = async (row?: DictTypeVO) => {
|
||||||
const dictIds = row?.dictId || ids.value;
|
const dictIds = row?.dictId || typeIds.value;
|
||||||
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
|
await proxy?.$modal.confirm('是否确认删除字典编号为"' + dictIds + '"的数据项?');
|
||||||
await delType(dictIds);
|
await delType(dictIds);
|
||||||
getList();
|
getTypeList();
|
||||||
proxy?.$modal.msgSuccess('删除成功');
|
proxy?.$modal.msgSuccess('删除成功');
|
||||||
};
|
};
|
||||||
/** 导出按钮操作 */
|
|
||||||
const handleExport = () => {
|
const handleTypeExport = () => {
|
||||||
proxy?.download(
|
proxy?.download(
|
||||||
'system/dict/type/export',
|
'system/dict/type/export',
|
||||||
{
|
{
|
||||||
...queryParams.value
|
...typeQueryParams.value
|
||||||
},
|
},
|
||||||
`dict_${new Date().getTime()}.xlsx`
|
`dict_${new Date().getTime()}.xlsx`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
/** 刷新缓存按钮操作 */
|
|
||||||
const handleRefreshCache = async () => {
|
const handleRefreshCache = async () => {
|
||||||
await refreshCache();
|
await refreshCache();
|
||||||
proxy?.$modal.msgSuccess('刷新成功');
|
proxy?.$modal.msgSuccess('刷新成功');
|
||||||
useDictStore().cleanDict();
|
useDictStore().cleanDict();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDataList = async () => {
|
||||||
|
if (!currentDict.value) {
|
||||||
|
dataList.value = [];
|
||||||
|
dataTotal.value = 0;
|
||||||
|
dataLoading.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dataLoading.value = true;
|
||||||
|
const res = await listData(dataQueryParams.value);
|
||||||
|
dataList.value = res.rows;
|
||||||
|
dataTotal.value = res.total;
|
||||||
|
dataLoading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelData = () => {
|
||||||
|
dataDialog.visible = false;
|
||||||
|
resetDataForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetDataForm = () => {
|
||||||
|
dataForm.value = { ...dataInitFormData };
|
||||||
|
dataFormRef.value?.resetFields();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataQuery = () => {
|
||||||
|
if (!currentDict.value) return;
|
||||||
|
dataQueryParams.value.pageNum = 1;
|
||||||
|
getDataList();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataResetQuery = () => {
|
||||||
|
dataQueryFormRef.value?.resetFields();
|
||||||
|
dataQueryParams.value.dictLabel = '';
|
||||||
|
handleDataQuery();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataAdd = () => {
|
||||||
|
if (!currentDict.value) {
|
||||||
|
proxy?.$modal.msgWarning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resetDataForm();
|
||||||
|
dataForm.value.dictType = currentDict.value.dictType;
|
||||||
|
dataDialog.visible = true;
|
||||||
|
dataDialog.title = '添加字典数据';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataSelectionChange = (selection: DictDataVO[]) => {
|
||||||
|
dataIds.value = selection.map((item) => item.dictCode);
|
||||||
|
dataSingle.value = selection.length != 1;
|
||||||
|
dataMultiple.value = !selection.length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataUpdate = async (row?: DictDataVO) => {
|
||||||
|
if (!currentDict.value) {
|
||||||
|
proxy?.$modal.msgWarning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resetDataForm();
|
||||||
|
const dictCode = row?.dictCode || dataIds.value[0];
|
||||||
|
const res = await getData(dictCode);
|
||||||
|
Object.assign(dataForm.value, res.data);
|
||||||
|
dataDialog.visible = true;
|
||||||
|
dataDialog.title = '修改字典数据';
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitDataForm = () => {
|
||||||
|
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||||
|
if (valid) {
|
||||||
|
dataForm.value.dictCode ? await updateData(dataForm.value) : await addData(dataForm.value);
|
||||||
|
useDictStore().removeDict(dataQueryParams.value.dictType);
|
||||||
|
proxy?.$modal.msgSuccess('操作成功');
|
||||||
|
dataDialog.visible = false;
|
||||||
|
await getDataList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataDelete = async (row?: DictDataVO) => {
|
||||||
|
if (!currentDict.value) {
|
||||||
|
proxy?.$modal.msgWarning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dictCodes = row?.dictCode || dataIds.value;
|
||||||
|
await proxy?.$modal.confirm('是否确认删除字典编码为"' + dictCodes + '"的数据项?');
|
||||||
|
await delData(dictCodes);
|
||||||
|
await getDataList();
|
||||||
|
proxy?.$modal.msgSuccess('删除成功');
|
||||||
|
useDictStore().removeDict(dataQueryParams.value.dictType);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDataExport = () => {
|
||||||
|
if (!currentDict.value) {
|
||||||
|
proxy?.$modal.msgWarning('请先选择字典');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
proxy?.download(
|
||||||
|
'system/dict/data/export',
|
||||||
|
{
|
||||||
|
...dataQueryParams.value
|
||||||
|
},
|
||||||
|
`dict_data_${new Date().getTime()}.xlsx`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList();
|
getTypeList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.dict-grid {
|
||||||
|
row-gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-card__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-card__title {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-card__subtitle {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-form-scroll {
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-right: 6px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-form-scroll :deep(.el-form) {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: 12px;
|
||||||
|
row-gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-form-scroll :deep(.el-form-item) {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-actions :deep(.el-button) {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-actions :deep(.el-button + .el-button) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dict-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -42,19 +42,19 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
|
<el-button v-hasPermi="['workflow:definition:add']" type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
<el-button v-hasPermi="['workflow:definition:edit']" type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
|
<el-button v-hasPermi="['workflow:definition:remove']" type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
|
<el-button v-hasPermi="['workflow:definition:import']" type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
|
<el-button v-hasPermi="['workflow:definition:export']" type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
<el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
|
<el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-switch
|
<el-switch
|
||||||
|
v-hasPermi="['workflow:definition:active']"
|
||||||
v-model="scope.row.activityStatus"
|
v-model="scope.row.activityStatus"
|
||||||
:active-value="1"
|
:active-value="1"
|
||||||
:inactive-value="0"
|
:inactive-value="0"
|
||||||
@@ -92,21 +93,21 @@
|
|||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
|
<el-button v-hasPermi="['workflow:definition:remove']" link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
|
<el-button v-hasPermi="['workflow:definition:copy']" link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
|
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
|
||||||
>流程设计</el-button
|
>流程设计</el-button
|
||||||
>
|
>
|
||||||
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
|
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
|
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
|
||||||
<el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
|
<el-button v-hasPermi="['workflow:definition:publish']" link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<el-table-column align="center" prop="version" label="版本号" width="90">
|
<el-table-column align="center" prop="version" label="版本号" width="90">
|
||||||
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
|
<el-table-column v-if="tab === 'running'" v-hasPermi="['workflow:instance:active']" align="center" prop="isSuspended" label="状态" min-width="70">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
|
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
|
||||||
<el-tag v-else type="danger">挂起</el-tag>
|
<el-tag v-else type="danger">挂起</el-tag>
|
||||||
@@ -104,15 +104,15 @@
|
|||||||
</el-popover>
|
</el-popover>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
|
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
|
<el-button v-hasPermi="['workflow:instance:query']" type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
|
<el-button v-hasPermi="['workflow:instance:variableQuery']" type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<el-table-column align="center" prop="version" label="版本号" width="90">
|
<el-table-column align="center" prop="version" label="版本号" width="90">
|
||||||
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column align="center" prop="suspensionState" label="状态" min-width="70">
|
<el-table-column v-hasPermi="['workflow:instance:active']" align="center" prop="suspensionState" label="状态" min-width="70">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
|
<el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
|
||||||
<el-tag v-else type="danger">挂起</el-tag>
|
<el-tag v-else type="danger">挂起</el-tag>
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
<el-input v-model="form.value" placeholder="请输入变量值" />
|
<el-input v-model="form.value" placeholder="请输入变量值" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
|
<el-button v-hasPermi="['workflow:instance:variable']" type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|||||||
@@ -27,8 +27,12 @@
|
|||||||
<template #header>
|
<template #header>
|
||||||
<el-row :gutter="10" class="mb8">
|
<el-row :gutter="10" class="mb8">
|
||||||
<el-col :span="1.5" v-if="tab === 'waiting'">
|
<el-col :span="1.5" v-if="tab === 'waiting'">
|
||||||
<el-button type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()">修改办理人 </el-button>
|
<el-button class="todo-action-btn todo-action-btn--primary" type="primary" plain icon="Edit" :disabled="multiple" @click="handleUserOpen()"
|
||||||
<el-button type="primary" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()">催办 </el-button>
|
>修改办理人
|
||||||
|
</el-button>
|
||||||
|
<el-button class="todo-action-btn todo-action-btn--warning" type="warning" plain icon="Bell" :disabled="multiple" @click="handleUrgeTaskOpen()"
|
||||||
|
>催办
|
||||||
|
</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -277,3 +281,59 @@ onMounted(() => {
|
|||||||
getWaitingList();
|
getWaitingList();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.todo-action-btn {
|
||||||
|
height: 32px;
|
||||||
|
padding: 0 14px;
|
||||||
|
border-radius: var(--app-radius-md);
|
||||||
|
font-weight: 600;
|
||||||
|
--el-button-bg-color: var(--el-fill-color-lighter);
|
||||||
|
--el-button-border-color: var(--el-border-color-light);
|
||||||
|
--el-button-text-color: var(--el-text-color-regular);
|
||||||
|
--el-button-hover-bg-color: var(--el-fill-color-light);
|
||||||
|
--el-button-hover-border-color: var(--el-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-action-btn--primary {
|
||||||
|
--el-button-text-color: var(--el-color-primary);
|
||||||
|
--el-button-border-color: var(--el-color-primary-light-5);
|
||||||
|
--el-button-bg-color: var(--el-color-primary-light-9);
|
||||||
|
--el-button-hover-bg-color: var(--el-color-primary-light-8);
|
||||||
|
--el-button-hover-border-color: var(--el-color-primary-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-action-btn--warning {
|
||||||
|
--el-button-text-color: var(--el-color-warning);
|
||||||
|
--el-button-border-color: var(--el-color-warning-light-5);
|
||||||
|
--el-button-bg-color: var(--el-color-warning-light-9);
|
||||||
|
--el-button-hover-bg-color: var(--el-color-warning-light-8);
|
||||||
|
--el-button-hover-border-color: var(--el-color-warning-light-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(html.dark) {
|
||||||
|
.todo-action-btn {
|
||||||
|
--el-button-bg-color: rgba(148, 163, 184, 0.12);
|
||||||
|
--el-button-border-color: rgba(148, 163, 184, 0.3);
|
||||||
|
--el-button-text-color: var(--el-text-color-regular);
|
||||||
|
--el-button-hover-bg-color: rgba(148, 163, 184, 0.2);
|
||||||
|
--el-button-hover-border-color: rgba(148, 163, 184, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-action-btn--primary {
|
||||||
|
--el-button-bg-color: rgba(99, 113, 150, 0.14);
|
||||||
|
--el-button-border-color: rgba(99, 113, 150, 0.45);
|
||||||
|
--el-button-text-color: #c4cfdd;
|
||||||
|
--el-button-hover-bg-color: rgba(99, 113, 150, 0.22);
|
||||||
|
--el-button-hover-border-color: rgba(99, 113, 150, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-action-btn--warning {
|
||||||
|
--el-button-bg-color: rgba(214, 149, 59, 0.16);
|
||||||
|
--el-button-border-color: rgba(214, 149, 59, 0.55);
|
||||||
|
--el-button-text-color: #ffe6bb;
|
||||||
|
--el-button-hover-bg-color: rgba(214, 149, 59, 0.24);
|
||||||
|
--el-button-hover-border-color: rgba(214, 149, 59, 0.75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ export default defineConfig(({ mode, command }) => {
|
|||||||
scss: {
|
scss: {
|
||||||
// additionalData: '@use "@/assets/styles/variables.module.scss as *";'
|
// additionalData: '@use "@/assets/styles/variables.module.scss as *";'
|
||||||
// javascriptEnabled: true
|
// javascriptEnabled: true
|
||||||
api: 'modern-compiler'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
postcss: {
|
postcss: {
|
||||||
|
|||||||
@@ -1,28 +1,110 @@
|
|||||||
import compression from 'vite-plugin-compression';
|
import { promises as fs } from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import zlib from 'zlib';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import type { Plugin, ResolvedConfig } from 'vite';
|
||||||
|
|
||||||
export default (env: any) => {
|
const gzip = promisify(zlib.gzip);
|
||||||
const { VITE_BUILD_COMPRESS } = env;
|
const brotliCompress = promisify(zlib.brotliCompress);
|
||||||
const plugin: any[] = [];
|
const compressibleFileRE = /\.(js|mjs|json|css|html)$/i;
|
||||||
if (VITE_BUILD_COMPRESS) {
|
const defaultThreshold = 1025;
|
||||||
const compressList = VITE_BUILD_COMPRESS.split(',');
|
|
||||||
if (compressList.includes('gzip')) {
|
type CompressionKind = 'gzip' | 'brotli';
|
||||||
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
|
|
||||||
plugin.push(
|
const compressionHandlers: Record<CompressionKind, { ext: string; compress: (content: Buffer) => Promise<Buffer> }> = {
|
||||||
compression({
|
gzip: {
|
||||||
ext: '.gz',
|
ext: '.gz',
|
||||||
deleteOriginFile: false
|
compress: (content) => gzip(content, { level: zlib.constants.Z_BEST_COMPRESSION })
|
||||||
})
|
},
|
||||||
);
|
brotli: {
|
||||||
}
|
ext: '.br',
|
||||||
if (compressList.includes('brotli')) {
|
compress: (content) =>
|
||||||
plugin.push(
|
brotliCompress(content, {
|
||||||
compression({
|
params: {
|
||||||
ext: '.br',
|
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
|
||||||
algorithm: 'brotliCompress',
|
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT
|
||||||
deleteOriginFile: false
|
}
|
||||||
})
|
})
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return plugin;
|
};
|
||||||
|
|
||||||
|
async function collectFiles(rootDir: string): Promise<string[]> {
|
||||||
|
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
||||||
|
const files = await Promise.all(
|
||||||
|
entries.map(async (entry) => {
|
||||||
|
const fullPath = path.join(rootDir, entry.name);
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
return collectFiles(fullPath);
|
||||||
|
}
|
||||||
|
return compressibleFileRE.test(entry.name) ? [fullPath] : [];
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return files.flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCompressionPlugin(kind: CompressionKind): Plugin {
|
||||||
|
const handler = compressionHandlers[kind];
|
||||||
|
let config: ResolvedConfig | undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: `local:compression:${kind}`,
|
||||||
|
apply: 'build',
|
||||||
|
enforce: 'post',
|
||||||
|
configResolved(resolvedConfig) {
|
||||||
|
config = resolvedConfig;
|
||||||
|
},
|
||||||
|
async closeBundle() {
|
||||||
|
const outputDir = path.resolve(process.cwd(), config?.build.outDir ?? 'dist');
|
||||||
|
const files = await collectFiles(outputDir);
|
||||||
|
const compressedEntries: Array<{ file: string; originalKb: string; compressedKb: string }> = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
files.map(async (filePath) => {
|
||||||
|
const stat = await fs.stat(filePath);
|
||||||
|
if (stat.size < defaultThreshold) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = await fs.readFile(filePath);
|
||||||
|
const compressed = await handler.compress(content);
|
||||||
|
const outputFile = `${filePath}${handler.ext}`;
|
||||||
|
|
||||||
|
await fs.writeFile(outputFile, compressed);
|
||||||
|
compressedEntries.push({
|
||||||
|
file: path.relative(outputDir, outputFile).replaceAll('\\', '/'),
|
||||||
|
originalKb: (stat.size / 1024).toFixed(2),
|
||||||
|
compressedKb: (compressed.byteLength / 1024).toFixed(2)
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!compressedEntries.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
compressedEntries.sort((a, b) => a.file.localeCompare(b.file));
|
||||||
|
config?.logger.info(`\n[compression:${kind}] generated ${compressedEntries.length} files`);
|
||||||
|
for (const entry of compressedEntries) {
|
||||||
|
config?.logger.info(`${path.basename(outputDir)}/${entry.file} ${entry.originalKb}kb -> ${entry.compressedKb}kb`);
|
||||||
|
}
|
||||||
|
config?.logger.info('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (env: Record<string, string>) => {
|
||||||
|
const { VITE_BUILD_COMPRESS } = env;
|
||||||
|
const plugins: Plugin[] = [];
|
||||||
|
if (!VITE_BUILD_COMPRESS) {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
const compressionList = VITE_BUILD_COMPRESS.split(',').map((item) => item.trim()) as CompressionKind[];
|
||||||
|
if (compressionList.includes('gzip')) {
|
||||||
|
plugins.push(createCompressionPlugin('gzip'));
|
||||||
|
}
|
||||||
|
if (compressionList.includes('brotli')) {
|
||||||
|
plugins.push(createCompressionPlugin('brotli'));
|
||||||
|
}
|
||||||
|
return plugins;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user