Pre Merge pull request !838 from wuxingyu123456/report
commit
9d454497ac
8
.env
8
.env
|
|
@ -1,5 +1,5 @@
|
|||
# 标题
|
||||
VITE_APP_TITLE=芋道管理系统
|
||||
VITE_APP_TITLE=捷汇链管理系统
|
||||
|
||||
# 项目本地运行端口号
|
||||
VITE_PORT=80
|
||||
|
|
@ -20,9 +20,9 @@ VITE_APP_DOCALERT_ENABLE=true
|
|||
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
||||
|
||||
# 默认账户密码
|
||||
VITE_APP_DEFAULT_LOGIN_TENANT = 芋道源码
|
||||
VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
||||
VITE_APP_DEFAULT_LOGIN_PASSWORD = admin123
|
||||
VITE_APP_DEFAULT_LOGIN_TENANT = 捷汇链
|
||||
VITE_APP_DEFAULT_LOGIN_USERNAME =
|
||||
VITE_APP_DEFAULT_LOGIN_PASSWORD =
|
||||
|
||||
# API 加解密
|
||||
VITE_APP_API_ENCRYPT_ENABLE = true
|
||||
|
|
|
|||
2
.env.dev
2
.env.dev
|
|
@ -22,7 +22,7 @@ VITE_DROP_CONSOLE=false
|
|||
VITE_SOURCEMAP=true
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
VITE_BASE_PATH='/boxd-web/'
|
||||
|
||||
# 输出路径
|
||||
VITE_OUT_DIR=dist
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ VITE_DROP_CONSOLE=false
|
|||
VITE_SOURCEMAP=false
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
VITE_BASE_PATH='/boxd-web/'
|
||||
|
||||
# 商城H5会员端域名
|
||||
VITE_MALL_H5_DOMAIN='http://localhost:3000'
|
||||
|
|
|
|||
11
.env.prod
11
.env.prod
|
|
@ -1,10 +1,10 @@
|
|||
# 生产环境:只在打包时使用
|
||||
NODE_ENV=production
|
||||
|
||||
VITE_DEV=false
|
||||
VITE_DEV=true
|
||||
|
||||
# 请求路径
|
||||
VITE_BASE_URL='http://localhost:48080'
|
||||
VITE_BASE_URL='https://boxd.ljwjd.com'
|
||||
|
||||
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
|
||||
VITE_UPLOAD_TYPE=server
|
||||
|
|
@ -22,13 +22,16 @@ VITE_DROP_CONSOLE=true
|
|||
VITE_SOURCEMAP=false
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
VITE_BASE_PATH='/boxd-web/'
|
||||
|
||||
# 输出路径
|
||||
VITE_OUT_DIR=dist-prod
|
||||
VITE_OUT_DIR=dist-boxd
|
||||
|
||||
# 商城H5会员端域名
|
||||
VITE_MALL_H5_DOMAIN='http://mall.yudao.iocoder.cn'
|
||||
|
||||
# 验证码的开关
|
||||
VITE_APP_CAPTCHA_ENABLE=false
|
||||
|
||||
# GoView域名
|
||||
VITE_GOVIEW_URL='http://127.0.0.1:3000'
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
## 🐯 平台简介
|
||||
|
||||
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||
**捷汇链**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
|
||||
|
||||
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现
|
||||
* 改换 saas,自动引入等功能
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ const include = [
|
|||
'echarts/components',
|
||||
'echarts/renderers',
|
||||
'echarts-wordcloud',
|
||||
'@wangeditor/editor',
|
||||
'@wangeditor/editor-for-vue',
|
||||
'@wangeditor-next/editor',
|
||||
'@wangeditor-next/editor-for-vue',
|
||||
'@microsoft/fetch-event-source',
|
||||
'markdown-it',
|
||||
'markmap-view',
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="keywords"
|
||||
content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
|
||||
content="捷汇链管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="芋道管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
|
||||
content="捷汇链管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统!"
|
||||
/>
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
{
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "2.5.0-snapshot",
|
||||
"version": "2025.10-snapshot",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "2.5.0-snapshot",
|
||||
"version": "2025.10-snapshot",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.1.0",
|
||||
"@element-plus/icons-vue": "2.3.2",
|
||||
"@form-create/designer": "^3.2.6",
|
||||
"@form-create/element-ui": "^3.2.11",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@wangeditor-next/editor": "^5.6.46",
|
||||
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
||||
"@wangeditor-next/plugin-mention": "^1.0.16",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.6.8",
|
||||
"axios": "1.9.0",
|
||||
"benz-amr-recorder": "^1.1.5",
|
||||
"bpmn-js-token-simulation": "^0.36.0",
|
||||
"camunda-bpmn-moddle": "^7.0.1",
|
||||
|
|
@ -31,10 +32,11 @@
|
|||
"driver.js": "^1.3.1",
|
||||
"echarts": "^5.5.0",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "2.9.1",
|
||||
"element-plus": "2.11.1",
|
||||
"fast-xml-parser": "^4.3.2",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"jsoneditor": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"markdown-it": "^14.1.0",
|
||||
"markmap-common": "^0.16.0",
|
||||
|
|
@ -48,16 +50,17 @@
|
|||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.12.0",
|
||||
"snabbdom": "^3.6.2",
|
||||
"sortablejs": "^1.15.3",
|
||||
"steady-xml": "^0.1.0",
|
||||
"url": "^0.11.3",
|
||||
"v3-jsoneditor": "^0.0.6",
|
||||
"video.js": "^7.21.5",
|
||||
"vue": "3.5.12",
|
||||
"vue-dompurify-html": "^4.1.4",
|
||||
"vue-i18n": "9.10.2",
|
||||
"vue-router": "4.4.5",
|
||||
"vue-types": "^5.1.1",
|
||||
"vue3-print-nb": "^0.1.4",
|
||||
"vue3-signature": "^0.2.4",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"web-storage-cache": "^1.1.1",
|
||||
|
|
@ -69,6 +72,7 @@
|
|||
"@iconify/json": "^2.2.187",
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/jsoneditor": "^9.9.5",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.21",
|
||||
"@types/nprogress": "^0.2.3",
|
||||
|
|
@ -2310,9 +2314,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@element-plus/icons-vue": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
||||
"integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
|
||||
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.2.0"
|
||||
|
|
@ -4492,6 +4496,13 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ace": {
|
||||
"version": "0.0.52",
|
||||
"resolved": "https://registry.npmmirror.com/@types/ace/-/ace-0.0.52.tgz",
|
||||
"integrity": "sha512-YPF9S7fzpuyrxru+sG/rrTpZkC6gpHBPF14W3x70kqVOD+ks6jkYLapk4yceh36xej7K4HYxcyz9ZDQ2lTvwgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/conventional-commits-parser": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz",
|
||||
|
|
@ -4792,6 +4803,41 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/jsoneditor": {
|
||||
"version": "9.9.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/jsoneditor/-/jsoneditor-9.9.6.tgz",
|
||||
"integrity": "sha512-SJ29nWBIhnhtU5n72wxhPiuUVd8cnDHd7ZYMqVkzWtdRxTUdS8+oy1pg66yhmM1kcuanX3xmAAKfcyhhBnHEjQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ace": "*",
|
||||
"ajv": "^6.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsoneditor/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/jsoneditor/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/lodash": {
|
||||
"version": "4.17.16",
|
||||
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz",
|
||||
|
|
@ -6991,163 +7037,275 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/basic-modules": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/basic-modules/-/basic-modules-1.1.7.tgz",
|
||||
"integrity": "sha512-cY9CPkLJaqF05STqfpZKWG4LpxTMeGSIIF1fHvfm/mz+JXatCagjdkbxdikOuKYlxDdeqvOeBmsUBItufDLXZg==",
|
||||
"node_modules/@wangeditor-next/editor": {
|
||||
"version": "5.6.47",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/editor/-/editor-5.6.47.tgz",
|
||||
"integrity": "sha512-oItJUalHmd67QkGonp9qn3EprdheamlQh0hzxcWiUsElB/3JNTN2Djz8Lq/9QfgCOpr6313He0EDYFv59azLmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor-next/basic-modules": "~1.5.46",
|
||||
"@wangeditor-next/code-highlight": "~1.3.43",
|
||||
"@wangeditor-next/core": "~1.7.45",
|
||||
"@wangeditor-next/list-module": "~1.1.51",
|
||||
"@wangeditor-next/table-module": "~1.6.59",
|
||||
"@wangeditor-next/upload-image-module": "~1.1.49",
|
||||
"@wangeditor-next/video-module": "~1.3.51",
|
||||
"dom7": "^4.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^5.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor-for-vue": {
|
||||
"version": "5.1.14",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/editor-for-vue/-/editor-for-vue-5.1.14.tgz",
|
||||
"integrity": "sha512-Xkrdo590AhLHvzyR+U246t6T89nIWHz1weAgMuo8jEA2HS5RiUnsA4U6+iUGaQ2E5c8mYQaeNqzHQXUp9Okbiw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor-next/editor": ">=5.1.0",
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/basic-modules": {
|
||||
"version": "1.5.46",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/basic-modules/-/basic-modules-1.5.46.tgz",
|
||||
"integrity": "sha512-q8/al99P7+koPTJ+X79WjrEP5pDXs8+vFYo0pIbs59Oi1cM1b9CCB0384Mdcg0DQU7eZrN7dl2bHKO5Z5r7E8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-url": "^1.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"nanoid": "^5.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/code-highlight": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/code-highlight/-/code-highlight-1.0.3.tgz",
|
||||
"integrity": "sha512-iazHwO14XpCuIWJNTQTikqUhGKyqj+dUNWJ9288Oym9M2xMVHvnsOmDU2sgUDWVy+pOLojReMPgXCsvvNlOOhw==",
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/code-highlight": {
|
||||
"version": "1.3.43",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/code-highlight/-/code-highlight-1.3.43.tgz",
|
||||
"integrity": "sha512-22eHjYDmtTxZqZOma2ls9zWA6gsgSkWq3XtmLylA15kegVBKAy7YxYbRrdS7D4Y/igqOerSbc5oMsOdeYjRfnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prismjs": "^1.23.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/core": {
|
||||
"version": "1.1.19",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/core/-/core-1.1.19.tgz",
|
||||
"integrity": "sha512-KevkB47+7GhVszyYF2pKGKtCSj/YzmClsD03C3zTt+9SR2XWT5T0e3yQqg8baZpcMvkjs1D8Dv4fk8ok/UaS2Q==",
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/core": {
|
||||
"version": "1.7.45",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/core/-/core-1.7.45.tgz",
|
||||
"integrity": "sha512-5Pt8JCmdzJWk4q18zUZse+zM+mBW6jYt3npXVkLswYysx01krC3bBQq1J9JeZe4Ci+rQAs0tQj3t1imjpsmRgg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/event-emitter": "^0.3.3",
|
||||
"event-emitter": "^0.3.5",
|
||||
"html-void-elements": "^2.0.0",
|
||||
"i18next": "^20.4.0",
|
||||
"scroll-into-view-if-needed": "^2.2.28",
|
||||
"slate-history": "^0.66.0"
|
||||
"html-void-elements": "^3.0.0",
|
||||
"i18next": "^23.0.0",
|
||||
"scroll-into-view-if-needed": "^3.0.0",
|
||||
"slate-history": "^0.109.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"dom7": "^3.0.0",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"nanoid": "^5.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor": {
|
||||
"version": "5.1.23",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor/-/editor-5.1.23.tgz",
|
||||
"integrity": "sha512-0RxfeVTuK1tktUaPROnCoFfaHVJpRAIE2zdS0mpP+vq1axVQpLjM8+fCvKzqYIkH0Pg+C+44hJpe3VVroSkEuQ==",
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/list-module": {
|
||||
"version": "1.1.51",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/list-module/-/list-module-1.1.51.tgz",
|
||||
"integrity": "sha512-gsHmXAO0rXCd2vXhxJHbRCClEjzokzpS2ozdt1tZHZv/fZUIdTnwwCCT2OThF8SxnlBS5bmZxVa9zYnDxXGjaQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@uppy/core": "^2.1.1",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "^1.1.7",
|
||||
"@wangeditor/code-highlight": "^1.0.3",
|
||||
"@wangeditor/core": "^1.1.19",
|
||||
"@wangeditor/list-module": "^1.0.5",
|
||||
"@wangeditor/table-module": "^1.1.4",
|
||||
"@wangeditor/upload-image-module": "^1.0.2",
|
||||
"@wangeditor/video-module": "^1.1.4",
|
||||
"dom7": "^3.0.0",
|
||||
"is-hotkey": "^0.2.0",
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"peerDependencies": {
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/table-module": {
|
||||
"version": "1.6.59",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/table-module/-/table-module-1.6.59.tgz",
|
||||
"integrity": "sha512-CO24wGDlGNlTzpfR4wEGDoUiWkjH9NRK6pH0jH63Ty+asGPuF3w4UsITqS0z5QSxlxJPo9y3HQbc5WS+Pvs4Ww==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"lodash.toarray": "^4.4.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"nanoid": "^5.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/editor-for-vue": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/editor-for-vue/-/editor-for-vue-5.1.12.tgz",
|
||||
"integrity": "sha512-0Ds3D8I+xnpNWezAeO7HmPRgTfUxHLMd9JKcIw+QzvSmhC5xUHbpCcLU+KLmeBKTR/zffnS5GQo6qi3GhTMJWQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/editor": ">=5.1.0",
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/list-module": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/list-module/-/list-module-1.0.5.tgz",
|
||||
"integrity": "sha512-uDuYTP6DVhcYf7mF1pTlmNn5jOb4QtcVhYwSSAkyg09zqxI1qBqsfUnveeDeDqIuptSJhkh81cyxi+MF8sEPOQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/table-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/table-module/-/table-module-1.1.4.tgz",
|
||||
"integrity": "sha512-5saanU9xuEocxaemGdNi9t8MCDSucnykEC6jtuiT72kt+/Hhh4nERYx1J20OPsTCCdVr7hIyQenFD1iSRkIQ6w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/upload-image-module": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/upload-image-module/-/upload-image-module-1.0.2.tgz",
|
||||
"integrity": "sha512-z81lk/v71OwPDYeQDxj6cVr81aDP90aFuywb8nPD6eQeECtOymrqRODjpO6VGvCVxVck8nUxBHtbxKtjgcwyiA==",
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/upload-image-module": {
|
||||
"version": "1.1.49",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/upload-image-module/-/upload-image-module-1.1.49.tgz",
|
||||
"integrity": "sha512-nT7w5kSYdlZj+ImvjDObGhoW9fGU2OK9Tw3+gGdOx1MidWzoAuOqZK++2GWI5zhQRIhTmfDRrFTmLNDC8qb7YA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.0.3",
|
||||
"@uppy/xhr-upload": "^2.0.3",
|
||||
"@wangeditor/basic-modules": "1.x",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"@wangeditor-next/basic-modules": "1.5.46",
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor/video-module": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor/video-module/-/video-module-1.1.4.tgz",
|
||||
"integrity": "sha512-ZdodDPqKQrgx3IwWu4ZiQmXI8EXZ3hm2/fM6E3t5dB8tCaIGWQZhmqd6P5knfkRAd3z2+YRSRbxOGfoRSp/rLg==",
|
||||
"node_modules/@wangeditor-next/editor/node_modules/@wangeditor-next/video-module": {
|
||||
"version": "1.3.51",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/video-module/-/video-module-1.3.51.tgz",
|
||||
"integrity": "sha512-67ecZCGIY+MUsqFtmwR9QKWlzGeIXVyXHmzPuevYwEqRwg50oR2xCSuoQLhfs5CKjXDZKsZhOnD/CGgt82TU+A==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@uppy/core": "^2.1.4",
|
||||
"@uppy/xhr-upload": "^2.0.7",
|
||||
"@wangeditor/core": "1.x",
|
||||
"dom7": "^3.0.0",
|
||||
"nanoid": "^3.2.0",
|
||||
"slate": "^0.72.0",
|
||||
"snabbdom": "^3.1.0"
|
||||
"@wangeditor-next/core": "1.7.45",
|
||||
"dom7": "^3.0.0 || ^4.0.0",
|
||||
"nanoid": "^5.0.0",
|
||||
"slate": "^0.82.0",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/compute-scroll-into-view": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/dom7": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-4.0.6.tgz",
|
||||
"integrity": "sha512-emjdpPLhpNubapLFdjNL9tP06Sr+GZkrIHEXLWvOGsytACUrkbeIdjO5g77m00BrHTznnlcNqgmn7pCN192TBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ssr-window": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/html-void-elements": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz",
|
||||
"integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/i18next": {
|
||||
"version": "23.16.8",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-23.16.8.tgz",
|
||||
"integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://locize.com/i18next.html"
|
||||
},
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/nanoid": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz",
|
||||
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/scroll-into-view-if-needed": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
|
||||
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/slate": {
|
||||
"version": "0.82.1",
|
||||
"resolved": "https://registry.npmmirror.com/slate/-/slate-0.82.1.tgz",
|
||||
"integrity": "sha512-3mdRdq7U3jSEoyFrGvbeb28hgrvrr4NdFCtJX+IjaNvSFozY0VZd/CGHF0zf/JDx7aEov864xd5uj0HQxxEWTQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/slate-history": {
|
||||
"version": "0.109.0",
|
||||
"resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.109.0.tgz",
|
||||
"integrity": "sha512-DHavPwrTTAEAV66eAocB3iQHEj65N6IVtbRK98ZuqGT0S44T3zXlhzY+5SZ7EPxRcoOYVt1dioRxXYM/+PmCiQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@wangeditor-next/editor/node_modules/ssr-window": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-4.0.2.tgz",
|
||||
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wangeditor-next/plugin-mention": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmmirror.com/@wangeditor-next/plugin-mention/-/plugin-mention-1.0.17.tgz",
|
||||
"integrity": "sha512-mRz6xKDFfZcFWNTmu/SNzHIXtC9hmgYfyfSZntmui+vdG6pU9vggx5dIfZA71LwFB7ozYXqemesPPfkX9wZI0Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@wangeditor-next/editor": "5.6.47",
|
||||
"snabbdom": "^3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
|
|
@ -8231,12 +8389,6 @@
|
|||
"integrity": "sha512-wGA++isMqiDq1jPYeyv2as/Bt/u+3iLW0rEa+8NQ82jAv3TgqMiCM+B2SaBdn2DfLilLjjq736YcezihRYhfxw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/compute-scroll-into-view": {
|
||||
"version": "1.0.20",
|
||||
"resolved": "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
|
||||
"integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/computeds": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/computeds/-/computeds-0.0.1.tgz",
|
||||
|
|
@ -9234,15 +9386,6 @@
|
|||
"resolved": "https://registry.npmmirror.com/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w=="
|
||||
},
|
||||
"node_modules/dom7": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-3.0.0.tgz",
|
||||
"integrity": "sha512-oNlcUdHsC4zb7Msx7JN3K0Nro1dzJ48knvBOnDPKJ2GV9wl1i5vydJZUSyOfrkKFDZEud/jBsTk92S/VGSAe/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ssr-window": "^3.0.0-alpha.1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
|
|
@ -9424,9 +9567,9 @@
|
|||
"license": "ISC"
|
||||
},
|
||||
"node_modules/element-plus": {
|
||||
"version": "2.9.1",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.1.tgz",
|
||||
"integrity": "sha512-9Agqf/jt4Ugk7EZ6C5LME71sgkvauPCsnvJN12Xid2XVobjufxMGpRE4L7pS4luJMOmFAH3J0NgYEGZT5r+NDg==",
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.1.tgz",
|
||||
"integrity": "sha512-weYFIniyNXTAe9vJZnmZpYzurh4TDbdKhBsJwhbzuo0SDZ8PLwHVll0qycJUxc6SLtH+7A9F7dvdDh5CnqeIVA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^3.4.1",
|
||||
|
|
@ -11232,16 +11375,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/html-void-elements": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz",
|
||||
"integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
|
|
@ -11271,15 +11404,6 @@
|
|||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "20.6.1",
|
||||
"resolved": "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz",
|
||||
"integrity": "sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
|
|
@ -11866,20 +11990,19 @@
|
|||
}
|
||||
},
|
||||
"node_modules/jsoneditor": {
|
||||
"version": "9.10.5",
|
||||
"resolved": "https://registry.npmmirror.com/jsoneditor/-/jsoneditor-9.10.5.tgz",
|
||||
"integrity": "sha512-fVZ0NMt+zm4rqTKBv2x7zPdLeaRyKo1EjJkaR1QjK4gEM1rMwICILYSW1OPxSc1qqyAoDaA/eeNrluKoxOocCA==",
|
||||
"version": "10.4.2",
|
||||
"resolved": "https://registry.npmmirror.com/jsoneditor/-/jsoneditor-10.4.2.tgz",
|
||||
"integrity": "sha512-SQPCXlanU4PqdVsYuj2X7yfbLiiJYjklbksGfMKPsuwLhAIPxDlG43jYfXieGXvxpuq1fkw08YoRbkKXKabcLA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ace-builds": "^1.31.1",
|
||||
"ace-builds": "^1.36.2",
|
||||
"ajv": "^6.12.6",
|
||||
"javascript-natural-sort": "^0.7.1",
|
||||
"jmespath": "^0.16.0",
|
||||
"json-source-map": "^0.6.1",
|
||||
"jsonrepair": "3.1.0",
|
||||
"mobius1-selectr": "^2.4.13",
|
||||
"jsonrepair": "^3.8.1",
|
||||
"picomodal": "^3.0.0",
|
||||
"vanilla-picker": "^2.12.2"
|
||||
"vanilla-picker": "^2.12.3"
|
||||
}
|
||||
},
|
||||
"node_modules/jsoneditor/node_modules/ajv": {
|
||||
|
|
@ -11928,9 +12051,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/jsonrepair": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/jsonrepair/-/jsonrepair-3.1.0.tgz",
|
||||
"integrity": "sha512-idqReg23J0PVRAADmZMc5xQM3xeOX5bTB6OTyMnzq33IXJXmn9iJuWIEvGmrN80rQf4d7uLTMEDwpzujNcI0Rg==",
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmmirror.com/jsonrepair/-/jsonrepair-3.13.1.tgz",
|
||||
"integrity": "sha512-WJeiE0jGfxYmtLwBTEk8+y/mYcaleyLXWaqp5bJu0/ZTSeG0KQq/wWQ8pmnkKenEdN6pdnn6QtcoSUkbqDHWNw==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"jsonrepair": "bin/cli.js"
|
||||
|
|
@ -12342,13 +12465,6 @@
|
|||
"integrity": "sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
|
|
@ -13025,12 +13141,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mobius1-selectr": {
|
||||
"version": "2.4.13",
|
||||
"resolved": "https://registry.npmmirror.com/mobius1-selectr/-/mobius1-selectr-2.4.13.tgz",
|
||||
"integrity": "sha512-Mk9qDrvU44UUL0EBhbAA1phfQZ7aMZPjwtL7wkpiBzGh8dETGqfsh50mWoX9EkjDlkONlErWXArHCKfoxVg0Bw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/moddle": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmmirror.com/moddle/-/moddle-6.2.3.tgz",
|
||||
|
|
@ -14824,15 +14934,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/scroll-into-view-if-needed": {
|
||||
"version": "2.2.31",
|
||||
"resolved": "https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
|
||||
"integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-scroll-into-view": "^1.0.20"
|
||||
}
|
||||
},
|
||||
"node_modules/scule": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
|
||||
|
|
@ -14994,29 +15095,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/slate": {
|
||||
"version": "0.72.8",
|
||||
"resolved": "https://registry.npmmirror.com/slate/-/slate-0.72.8.tgz",
|
||||
"integrity": "sha512-/nJwTswQgnRurpK+bGJFH1oM7naD5qDmHd89JyiKNT2oOKD8marW0QSBtuFnwEbL5aGCS8AmrhXQgNOsn4osAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.6",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"tiny-warning": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slate-history": {
|
||||
"version": "0.66.0",
|
||||
"resolved": "https://registry.npmmirror.com/slate-history/-/slate-history-0.66.0.tgz",
|
||||
"integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-plain-object": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"slate": ">=0.65.3"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-5.0.0.tgz",
|
||||
|
|
@ -15108,12 +15186,6 @@
|
|||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ssr-window": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/ssr-window/-/ssr-window-3.0.0.tgz",
|
||||
"integrity": "sha512-q+8UfWDg9Itrg0yWK7oe5p/XRCJpJF9OBtXfOPgSJl+u3Xd5KI328RUEvUqSMVM9CiQUEf1QdBzJMkYGErj9QA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/steady-xml": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/steady-xml/-/steady-xml-0.1.0.tgz",
|
||||
|
|
@ -16489,19 +16561,6 @@
|
|||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/v3-jsoneditor": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/v3-jsoneditor/-/v3-jsoneditor-0.0.6.tgz",
|
||||
"integrity": "sha512-9G0sXWXUn67SBkn46ycWfwPwjuJu/lcsQaNzMtXAR2/95hMV21WfcRNsqJ+vVVrSHQehohB/9fVLwYEXz0u/KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"jsoneditor": "^9.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/pratik227"
|
||||
}
|
||||
},
|
||||
"node_modules/vanilla-picker": {
|
||||
"version": "2.12.3",
|
||||
"resolved": "https://registry.npmmirror.com/vanilla-picker/-/vanilla-picker-2.12.3.tgz",
|
||||
|
|
@ -17064,6 +17123,14 @@
|
|||
"integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vue3-print-nb": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue3-print-nb/-/vue3-print-nb-0.1.4.tgz",
|
||||
"integrity": "sha512-LExI7viEzplR6ZKQ2b+V4U0cwGYbVD4fut/XHvk3UPGlT5CcvIGs6VlwGp107aKgk6P8Pgx4rco3Rehv2lti3A==",
|
||||
"dependencies": {
|
||||
"vue": "^3.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/vue3-signature": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/vue3-signature/-/vue3-signature-0.2.4.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "yudao-ui-admin-vue3",
|
||||
"version": "2025.09-snapshot",
|
||||
"version": "2025.10-snapshot",
|
||||
"description": "基于vue3、vite4、element-plus、typesScript",
|
||||
"author": "xingyu",
|
||||
"private": false,
|
||||
|
|
@ -32,9 +32,9 @@
|
|||
"@microsoft/fetch-event-source": "^2.0.1",
|
||||
"@videojs-player/vue": "^1.0.0",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@wangeditor/plugin-mention": "^1.0.0",
|
||||
"@wangeditor-next/editor": "^5.6.46",
|
||||
"@wangeditor-next/editor-for-vue": "^5.1.14",
|
||||
"@wangeditor-next/plugin-mention": "^1.0.16",
|
||||
"@zxcvbn-ts/core": "^3.0.4",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "1.9.0",
|
||||
|
|
|
|||
2534
pnpm-lock.yaml
2534
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
24
src/App.vue
24
src/App.vue
|
|
@ -1,6 +1,9 @@
|
|||
<script lang="ts" setup>
|
||||
import { isDark } from '@/utils/is'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import { useUserListStore } from '@/store/modules/userList'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import routerSearch from '@/components/RouterSearch/index.vue'
|
||||
|
|
@ -10,10 +13,31 @@ defineOptions({ name: 'APP' })
|
|||
const { getPrefixCls } = useDesign()
|
||||
const prefixCls = getPrefixCls('app')
|
||||
const appStore = useAppStore()
|
||||
const deptStore = useDeptStore()
|
||||
const currentSize = computed(() => appStore.getCurrentSize)
|
||||
const greyMode = computed(() => appStore.getGreyMode)
|
||||
const { wsCache } = useCache()
|
||||
|
||||
// 初始化部门信息
|
||||
const initDeptInfo = async () => {
|
||||
await deptStore.setDeptInfoAction()
|
||||
}
|
||||
initDeptInfo()
|
||||
|
||||
// 初始化用户列表信息
|
||||
const userListStore = useUserListStore()
|
||||
const initUserListInfo = async () => {
|
||||
await userListStore.setUserListAction()
|
||||
}
|
||||
initUserListInfo()
|
||||
|
||||
// 初始化奖惩原因信息
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
const initRewardsPunishReasonInfo = async () => {
|
||||
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||
}
|
||||
initRewardsPunishReasonInfo()
|
||||
|
||||
// 根据浏览器当前主题设置系统主题色
|
||||
const setDefaultTheme = () => {
|
||||
let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
/**
|
||||
* 项目通用API(详情和评论)
|
||||
*/
|
||||
|
||||
// 查询上报详情
|
||||
export const getMyReportDetail = (id: string) => {
|
||||
return request.get({
|
||||
url: `/submit/myReporting/get`,
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 保存评论
|
||||
export const saveComment = (reportingId: string, commentVO: any) => {
|
||||
return request.post({
|
||||
url: `/submit/comment/${reportingId}`,
|
||||
data: commentVO
|
||||
})
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
export const deleteComment = (commentId: string) => {
|
||||
return request.delete({
|
||||
url: `/submit/comment/${commentId}`
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
// api/submit/companyReport/index.ts
|
||||
import { getMyReportPage, exportMyReport } from '../myReporting'
|
||||
import { getMyReportDetail } from '../common/common'
|
||||
import type { QueryParams } from '../myReporting/types'
|
||||
|
||||
// 查询公司通报列表(复用我的上报列表接口,通过参数区分)
|
||||
export const getCompanyReportPage = (params: QueryParams) => {
|
||||
// 设置参数以获取公司通报数据
|
||||
return getMyReportPage({
|
||||
...params,
|
||||
isOwnReport: false,
|
||||
isOwnApprove: false
|
||||
})
|
||||
}
|
||||
|
||||
// 查询公司通报详情(复用我的上报详情接口)
|
||||
export const getCompanyReportDetail = getMyReportDetail
|
||||
|
||||
// 导出公司通报(复用我的上报导出接口)
|
||||
export const exportCompanyReport = (params: QueryParams) => {
|
||||
// 设置参数以导出公司通报数据
|
||||
return exportMyReport({
|
||||
...params,
|
||||
isOwnReport: false,
|
||||
isOwnApprove: false
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
// api/submit/myApprove/index.ts
|
||||
import request from '@/config/axios'
|
||||
import type { QueryParams } from '../myReporting/types'
|
||||
import type { ApproveParams } from './types'
|
||||
import { getMyReportPage, getRuleScore, exportMyReport } from '../myReporting'
|
||||
|
||||
// 查询审批列表 - 复用myReporting的查询接口
|
||||
export const getApproveList = (params: QueryParams) => {
|
||||
return getMyReportPage(params)
|
||||
}
|
||||
|
||||
// 审批上报
|
||||
export const approveMyReport = (data: ApproveParams) => {
|
||||
return request.post({
|
||||
url: '/submit/myApprove/approve',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取规则分值 - 复用myReporting的规则分值接口
|
||||
export { getRuleScore, exportMyReport }
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// api/submit/myApprove/types.ts
|
||||
|
||||
// 用户积分记录
|
||||
export interface UserIntegralRecord {
|
||||
userId: string;
|
||||
userName: string;
|
||||
score: number | null;
|
||||
}
|
||||
|
||||
// 审批请求参数
|
||||
export interface ApproveParams {
|
||||
id: string;
|
||||
action: string; // APPROVED 或 REFUSE
|
||||
comment?: string;
|
||||
userIntegralRecords?: UserIntegralRecord[];
|
||||
}
|
||||
|
||||
// 审批列表查询参数
|
||||
export interface ApproveListParams {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
title?: string;
|
||||
deptId?: string;
|
||||
personName?: string;
|
||||
status?: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
dateRange?: string[];
|
||||
}
|
||||
|
||||
// 审批记录详情
|
||||
export interface ApproveRecord {
|
||||
id: string;
|
||||
title: string;
|
||||
deptId: string;
|
||||
deptName: string;
|
||||
reportType: string;
|
||||
reportTypeName: string;
|
||||
personName: string;
|
||||
personId?: string;
|
||||
remark?: string;
|
||||
status: number | string;
|
||||
approverId?: string;
|
||||
approverName?: string;
|
||||
approveTime?: string;
|
||||
approveComment?: string;
|
||||
createBy?: string;
|
||||
createTime?: string;
|
||||
updateBy?: string;
|
||||
updateTime?: string;
|
||||
userIntegralRecords?: UserIntegralRecord[];
|
||||
fillingTime?: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// api/submit/myReporting/index.ts
|
||||
import request from '@/config/axios'
|
||||
import type { QueryParams, SaveParams, RuleScoreResponse } from './types'
|
||||
|
||||
// 查询我的上报列表
|
||||
export const getMyReportPage = (params: QueryParams) => {
|
||||
return request.get({
|
||||
url: '/submit/myReporting/page',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 创建我的上报
|
||||
export const createMyReport = (data: SaveParams) => {
|
||||
return request.post({
|
||||
url: '/submit/myReporting/create',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 更新我的上报
|
||||
export const updateMyReport = (data: SaveParams) => {
|
||||
return request.put({
|
||||
url: '/submit/myReporting/update',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除我的上报
|
||||
export const deleteMyReport = (id: string) => {
|
||||
return request.delete({
|
||||
url: `/submit/myReporting/delete`,
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 导出我的上报
|
||||
export const exportMyReport = (params: QueryParams) => {
|
||||
return request.download({
|
||||
url: '/submit/myReporting/export-excel',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 提交上报
|
||||
export const commitMyReport = (id: string) => {
|
||||
return request.post({
|
||||
url: `/submit/myReporting/commit`,
|
||||
params: { id }
|
||||
})
|
||||
}
|
||||
|
||||
// 获取规则分值
|
||||
export const getRuleScore = (reasonId: string, personId: string) => {
|
||||
return request.get<RuleScoreResponse>({
|
||||
url: `/submit/ruleManage/score/${reasonId}/${personId}`
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// api/submit/myReporting/types.ts
|
||||
export interface UserIntegralRecord {
|
||||
userId: string;
|
||||
userName: string;
|
||||
score: number | null;
|
||||
}
|
||||
|
||||
export interface Reason {
|
||||
id: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface RuleScoreResponse {
|
||||
type: string;
|
||||
scores: number[];
|
||||
}
|
||||
|
||||
export interface ReportingCommentDTO {
|
||||
comment: string;
|
||||
createUserName: string;
|
||||
createTime: string;
|
||||
}
|
||||
|
||||
export interface MyReportDTO {
|
||||
id: string;
|
||||
title: string;
|
||||
deptId: string;
|
||||
deptName: string;
|
||||
reportType: string;
|
||||
reportTypeName: string;
|
||||
personName: string;
|
||||
personId?: string;
|
||||
remark?: string;
|
||||
imageUrls?: string[];
|
||||
status: number | string;
|
||||
approverId?: string;
|
||||
approverName?: string;
|
||||
approveTime?: string;
|
||||
approveComment?: string;
|
||||
createBy?: string;
|
||||
createTime?: string;
|
||||
updateBy?: string;
|
||||
updateTime?: string;
|
||||
reason?: Reason;
|
||||
userIntegralRecords?: UserIntegralRecord[];
|
||||
comments?: ReportingCommentDTO[];
|
||||
fillingTime?: string;
|
||||
imageIds?: string;
|
||||
}
|
||||
|
||||
export interface QueryParams {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
title?: string;
|
||||
deptId?: string;
|
||||
personName?: string;
|
||||
reasonId?: string;
|
||||
status?: string;
|
||||
isOwnReport: boolean;
|
||||
isOwnApprove:boolean;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
dateRange?: string[];
|
||||
}
|
||||
|
||||
export interface SaveParams {
|
||||
id?: string;
|
||||
title: string;
|
||||
deptId: string;
|
||||
deptName?: string;
|
||||
reasonId?: string;
|
||||
personName: string;
|
||||
personId?: string;
|
||||
remark?: string;
|
||||
content?: string;
|
||||
imageUrls?: string[];
|
||||
imageIds?: string;
|
||||
reason?: Reason;
|
||||
userIntegralRecords?: UserIntegralRecord[];
|
||||
comments?: Array<{ comment: string; createUserName: string }>;
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import request from '@/config/axios'
|
||||
import type { PaySlipDTO, QueryParams, PageResponse } from './types'
|
||||
|
||||
// 下载工资条导入模板
|
||||
export const downloadImportTemplate = () => {
|
||||
return request.download({ url: '/submit/paySlip/download/importTemplate' })
|
||||
}
|
||||
|
||||
// 导入工资条
|
||||
export const importPaySlip = (data: FormData) => {
|
||||
return request.post({ url: '/submit/paySlip/import', data, headers: { 'Content-Type': 'multipart/form-data' } })
|
||||
}
|
||||
|
||||
// 查询工资条列表
|
||||
export const getPaySlipPage = (params: QueryParams) => {
|
||||
return request.get<PageResponse<PaySlipDTO>>({ url: '/submit/paySlip/page', params })
|
||||
}
|
||||
|
||||
// 查询工资条详情
|
||||
export const getPaySlipDetail = (id: string) => {
|
||||
return request.get<PaySlipDTO>({ url: `/submit/paySlip/detail/${id}` })
|
||||
}
|
||||
|
||||
// 删除工资条
|
||||
export const deletePaySlip = (id: string) => {
|
||||
return request.delete({ url: `/submit/paySlip/${id}` })
|
||||
}
|
||||
|
||||
// 签名工资条
|
||||
export const signPaySlip = (id: string) => {
|
||||
return request.post({ url: `/submit/paySlip/sign/${id}` })
|
||||
}
|
||||
|
||||
// 发送短信
|
||||
export const sendMsm = (id: string) => {
|
||||
return request.get({ url: `/submit/paySlip/sendMsm/${id}` })
|
||||
}
|
||||
|
||||
// 批量发送短信
|
||||
export const batchSendMsm = (ids: string[]) => {
|
||||
// 直接将 ids 数组作为请求体发送
|
||||
return request.post({ url: '/submit/paySlip/batch/sendMsm', data: ids });
|
||||
};
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// 工资条相关类型定义
|
||||
|
||||
export enum YesOrNotEnum {
|
||||
YES = 'YES', // 是
|
||||
NO = 'NO' // 否
|
||||
}
|
||||
|
||||
// 工资条DTO
|
||||
export interface PaySlipDTO {
|
||||
id: string; // 主键
|
||||
month?: string; // 月份
|
||||
year?: string; // 年份
|
||||
fullYearMonth?: string; // 完整月份,格式:年-月
|
||||
employeeId?: string; // 工号
|
||||
mobileNo?: string; // 手机号
|
||||
employeeName?: string; // 真实姓名
|
||||
salary?: number; // 工资
|
||||
attendanceDays?: number; // 出勤天数
|
||||
attendanceSalary?: number; // 出勤工资
|
||||
communicationFees?: number; // 通讯费
|
||||
overtimePay?: number; // 加班费
|
||||
jobSubsidies?: number; // 岗位补贴
|
||||
fullAttendanceBonus?: number; // 全勤奖
|
||||
mealExpenses?: number; // 扣餐费
|
||||
housingSupplement?: number; // 房补
|
||||
supplementFees?: number; // 补发
|
||||
insurancePremiumExpenses?: number; // 扣保险金
|
||||
penaltyDeductionExpenses?: number; // 扣罚
|
||||
marginExpenses?: number; // 扣保证金
|
||||
refundOfDeposit?: number; // 退回保证金
|
||||
salariesPayable?: number; // 应发工资
|
||||
msmSend?: string; // 是否已发送短信 (Y/N)
|
||||
msmSendTime?: string; // 短信发送时间
|
||||
signed?: string; // 是否已签名 (Y/N)
|
||||
signedImageIds?: string; // 签名图片IDs
|
||||
signedTime?: string; // 签名时间
|
||||
arrangeSigningDate?: string; // 安排签署日期
|
||||
performance?: number; // 绩效
|
||||
seniorityAward?: number; // 工龄奖
|
||||
workingClothes?: number; // 工服费
|
||||
}
|
||||
|
||||
// 工资条查询参数
|
||||
export interface QueryParams {
|
||||
pageNo: number; // 页码
|
||||
pageSize: number; // 每页条数
|
||||
month?: string; // 月份
|
||||
year?: string; // 年份
|
||||
yearMonth?: string; // 完整月份,格式:年-月
|
||||
employeeId?: string; // 工号
|
||||
mobileNo?: string; // 手机号
|
||||
employeeName?: string; // 真实姓名
|
||||
signed?: string; // 是否已签名 (Y/N)
|
||||
msmSend?: string; // 是否已发送短信 (Y/N)
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface PageResponse<T> {
|
||||
list: T[];
|
||||
total: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import request from '@/config/axios'
|
||||
import type { ReportManageDTO, QueryParams, SaveParams, PageResponse } from './types'
|
||||
|
||||
// 查询报表管理列表
|
||||
export const getReportManagePage = (params: QueryParams) => {
|
||||
return request.get<PageResponse<ReportManageDTO>>({ url: '/submit/reportManage/page', params })
|
||||
}
|
||||
|
||||
// 查询报表管理详情
|
||||
export const getReportManageDetail = (id: string) => {
|
||||
return request.get<ReportManageDTO>({ url: `/submit/reportManage/${id}` })
|
||||
}
|
||||
|
||||
// 创建报表管理
|
||||
export const createReportManage = (data: SaveParams) => {
|
||||
return request.post({ url: '/submit/reportManage/create', data })
|
||||
}
|
||||
|
||||
// 更新报表管理
|
||||
export const updateReportManage = (data: SaveParams) => {
|
||||
return request.put({ url: '/submit/reportManage/update', data })
|
||||
}
|
||||
|
||||
// 删除报表管理
|
||||
export const deleteReportManage = (id: string) => {
|
||||
return request.delete({ url: `/submit/reportManage/delete/${id}` })
|
||||
}
|
||||
|
||||
// 根据类型查询报表管理列表
|
||||
export const getReportManageListByType = (type: string) => {
|
||||
return request.get<ReportManageDTO[]>({ url: '/submit/reportManage/list-by-type', params: { type } })
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
// 报表管理相关类型定义
|
||||
|
||||
// 报表管理DTO
|
||||
export interface ReportManageDTO {
|
||||
id: string; // 主键
|
||||
code: string; // 编码
|
||||
name: string; // 名称
|
||||
orderNum: number; // 排序序号
|
||||
enable: number; // 状态(1启用 0禁用)
|
||||
remark?: string; // 描述
|
||||
createTime?: string; // 创建时间
|
||||
updateTime?: string; // 更新时间
|
||||
}
|
||||
|
||||
// 报表管理查询参数
|
||||
export interface QueryParams {
|
||||
pageNo: number; // 页码
|
||||
pageSize: number; // 每页条数
|
||||
code?: string; // 编码
|
||||
name?: string; // 名称
|
||||
enable?: string; // 状态
|
||||
}
|
||||
|
||||
// 报表管理保存参数
|
||||
export interface SaveParams {
|
||||
id?: string; // 主键
|
||||
code: string; // 编码
|
||||
name: string; // 名称
|
||||
orderNum: number; // 排序序号
|
||||
enable: number; // 状态(1启用 0禁用)
|
||||
remark?: string; // 描述
|
||||
type: 'REWARDS_PUNISH_REASON';
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface PageResponse<T> {
|
||||
list: T[];
|
||||
total: number;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import request from '@/config/axios'
|
||||
import type { IntegralRule, QueryParams, SaveParams, PageResponse } from './types'
|
||||
|
||||
// 查询积分规则列表
|
||||
export const getIntegralRulePage = (params: QueryParams) => {
|
||||
return request.get<PageResponse<IntegralRule>>({ url: '/submit/ruleManage/page', params })
|
||||
}
|
||||
|
||||
// 查询积分规则详情
|
||||
export const getIntegralRuleDetail = (id: string) => {
|
||||
return request.get<IntegralRule>({ url: `/submit/ruleManage/get/${id}` })
|
||||
}
|
||||
|
||||
// 根据事项ID和用户ID获取规则分值
|
||||
export const getRuleScore = (reasonId: string, userId: string) => {
|
||||
return request.get({ url: `/submit/ruleManage/getRuleScore/${reasonId}/${userId}` })
|
||||
}
|
||||
|
||||
// 创建积分规则
|
||||
export const createIntegralRule = (data: SaveParams) => {
|
||||
return request.post({ url: '/submit/ruleManage/create', data })
|
||||
}
|
||||
|
||||
// 更新积分规则
|
||||
export const updateIntegralRule = (data: SaveParams) => {
|
||||
return request.put({ url: '/submit/ruleManage/update', data })
|
||||
}
|
||||
|
||||
// 删除积分规则
|
||||
export const deleteIntegralRule = (id: string) => {
|
||||
return request.delete({ url: `/submit/ruleManage/delete/${id}` })
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// 积分规则类型定义
|
||||
|
||||
export interface IntegralRule {
|
||||
id: string
|
||||
title: string
|
||||
reason: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
type: string
|
||||
score: number
|
||||
scoreRange: string[]
|
||||
formula: string
|
||||
}
|
||||
|
||||
// 查询参数
|
||||
export interface QueryParams {
|
||||
title?: string
|
||||
reasonId?: string
|
||||
type?: string
|
||||
pageNo?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
// 保存参数
|
||||
export interface SaveParams {
|
||||
id?: string
|
||||
title: string
|
||||
reasonId: string
|
||||
type: string
|
||||
score?: number
|
||||
scoreRange?: string[]
|
||||
formula?: string
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface PageResponse<T> {
|
||||
list: T[]
|
||||
total: number
|
||||
totalPages: number
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import request from '@/config/axios'
|
||||
|
||||
// 用户积分API接口
|
||||
export const userScoreApi = {
|
||||
// 查询用户积分排行
|
||||
getUserScoreStatic: (params: any) => {
|
||||
return request.get({ url: '/submit/userScore/static', params })
|
||||
},
|
||||
// 同步用户积分数据
|
||||
syncUserScore: () => {
|
||||
return request.get({ url: '/submit/userScore/init' })
|
||||
},
|
||||
// 导出用户积分排行
|
||||
exportUserScoreStatic: (params: any) => {
|
||||
return request.download({ url: '/submit/userScore/static/export', params })
|
||||
},
|
||||
// 查询用户积分明细
|
||||
getUserScoreList: (params: any) => {
|
||||
return request.get({ url: '/submit/userScore/list', params })
|
||||
},
|
||||
// 导出用户积分明细
|
||||
exportUserScoreList: (params: any) => {
|
||||
return request.download({ url: '/submit/userScore/export', params })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// 用户积分列表请求参数
|
||||
export interface UserScoreListReqVO {
|
||||
// 部门名称
|
||||
deptName?: string;
|
||||
// 部门Id
|
||||
orgId?: number;
|
||||
// 用户名称
|
||||
userName?: string;
|
||||
// 用户Id
|
||||
userId?: string;
|
||||
// 开始月份,格式:yyyy-MM
|
||||
beginMonth?: string;
|
||||
// 结束月份,格式:yyyy-MM
|
||||
endMonth?: string;
|
||||
// 排名前N位
|
||||
top?: number;
|
||||
// 页码
|
||||
pageNo?: number;
|
||||
// 每页条数
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
// 用户积分列表响应数据
|
||||
export interface UserScoreListRespVO {
|
||||
// 排名
|
||||
ranking: number;
|
||||
// 部门名称
|
||||
deptName: string;
|
||||
// 用户名称
|
||||
userName: string;
|
||||
// 积分
|
||||
score: number;
|
||||
// 用户ID
|
||||
userId: string;
|
||||
}
|
||||
|
||||
// 用户积分详情请求参数
|
||||
export interface UserScoreDetailReqVO {
|
||||
// 用户ID
|
||||
userId: string;
|
||||
// 开始月份,格式:yyyy-MM
|
||||
beginMonth: string;
|
||||
// 结束月份,格式:yyyy-MM
|
||||
endMonth: string;
|
||||
}
|
||||
|
||||
// 用户积分详情响应数据
|
||||
export interface UserScoreDetailRespVO {
|
||||
// 用户ID
|
||||
userId: string;
|
||||
// 排名
|
||||
ranking: number;
|
||||
// 总积分
|
||||
totalScore: number;
|
||||
// 用户名称
|
||||
userName: string;
|
||||
// 部门名称
|
||||
deptName: string;
|
||||
// 开始月份,格式:yyyy-MM
|
||||
beginMonth: string;
|
||||
// 结束月份,格式:yyyy-MM
|
||||
endMonth: string;
|
||||
// 积分明细列表
|
||||
scoreDetails: ScoreDetail[];
|
||||
}
|
||||
|
||||
// 积分明细
|
||||
export interface ScoreDetail {
|
||||
// 积分
|
||||
score: number;
|
||||
// 发生日期
|
||||
happenedDate: string;
|
||||
// 积分类型
|
||||
type: string;
|
||||
// 积分原因
|
||||
reason: string;
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
<el-avatar :size="60">
|
||||
<Icon icon="ep:avatar" :size="60" />
|
||||
</el-avatar>
|
||||
<span class="text-18px font-bold">芋道源码</span>
|
||||
<span class="text-18px font-bold">捷汇链源码</span>
|
||||
</div>
|
||||
<Icon icon="tdesign:qrcode" :size="20" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Editor from './src/Editor.vue'
|
||||
import { IDomEditor } from '@wangeditor/editor'
|
||||
import { IDomEditor } from '@wangeditor-next/editor'
|
||||
|
||||
export interface EditorExpose {
|
||||
getEditorRef: () => Promise<IDomEditor>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue'
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor/editor'
|
||||
import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'
|
||||
import { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor-next/editor'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { isNumber } from '@/utils/is'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
|
@ -20,7 +20,7 @@ const currentLocale = computed(() => localeStore.getCurrentLocale)
|
|||
i18nChangeLanguage(unref(currentLocale).lang)
|
||||
|
||||
const props = defineProps({
|
||||
editorId: propTypes.string.def('wangeEditor-1'),
|
||||
editorId: propTypes.string.def('wangEditor-1'),
|
||||
height: propTypes.oneOfType([Number, String]).def('500px'),
|
||||
editorConfig: {
|
||||
type: Object as PropType<Partial<IEditorConfig>>,
|
||||
|
|
@ -58,6 +58,16 @@ watch(
|
|||
emit('update:modelValue', val)
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.readonly,
|
||||
(val) => {
|
||||
if (val) {
|
||||
editorRef.value?.disable()
|
||||
} else {
|
||||
editorRef.value?.enable()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const handleCreated = (editor: IDomEditor) => {
|
||||
editorRef.value = editor
|
||||
|
|
@ -90,6 +100,12 @@ const editorConfig = computed((): IEditorConfig => {
|
|||
},
|
||||
autoFocus: false,
|
||||
scroll: true,
|
||||
EXTEND_CONF: {
|
||||
mentionConfig: {
|
||||
showModal: () => {},
|
||||
hideModal: () => {}
|
||||
}
|
||||
},
|
||||
MENU_CONF: {
|
||||
['uploadImage']: {
|
||||
server: getUploadUrl(),
|
||||
|
|
@ -243,4 +259,4 @@ defineExpose({
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<style src="@wangeditor/editor/dist/css/style.css"></style>
|
||||
<style src="@wangeditor-next/editor/dist/css/style.css"></style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { Rule } from '@form-create/element-ui' //左侧拖拽按钮
|
||||
|
||||
// 左侧拖拽按钮
|
||||
export interface MenuItem {
|
||||
label: string
|
||||
|
|
@ -14,24 +12,6 @@ export interface Menu {
|
|||
list: MenuItem[]
|
||||
}
|
||||
|
||||
export interface MenuList extends Array<Menu> {}
|
||||
|
||||
// 拖拽组件的规则
|
||||
export interface DragRule {
|
||||
icon: string
|
||||
name: string
|
||||
label: string
|
||||
children?: string
|
||||
inside?: true
|
||||
drag?: true | String
|
||||
dragBtn?: false
|
||||
mask?: false
|
||||
|
||||
rule(): Rule
|
||||
|
||||
props(v: any, v1: any): Rule[]
|
||||
}
|
||||
|
||||
// 通用下拉组件 Props 类型
|
||||
export interface ApiSelectProps {
|
||||
name: string // 组件名称
|
||||
|
|
@ -46,6 +26,6 @@ export interface SelectRuleOption {
|
|||
label: string // label 名称
|
||||
name: string // 组件名称
|
||||
icon: string // 组件图标
|
||||
props?: any[], // 组件规则
|
||||
props?: any[] // 组件规则
|
||||
event?: any[] // 事件配置
|
||||
}
|
||||
|
|
|
|||
|
|
@ -642,7 +642,7 @@ const previewProcessJson = () => {
|
|||
})
|
||||
}
|
||||
|
||||
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
|
||||
/* ------------------------------------------------ 捷汇链源码 methods ------------------------------------------------------ */
|
||||
onMounted(() => {
|
||||
initBpmnModeler()
|
||||
createNewDiagram(props.value)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,12 @@ export const CACHE_KEY = {
|
|||
ROLE_ROUTERS: 'roleRouters',
|
||||
USER: 'user',
|
||||
VisitTenantId: 'visitTenantId',
|
||||
// 部门相关(新增)
|
||||
DEPT: 'dept',
|
||||
// 用户列表相关(新增)
|
||||
USER_LIST: 'userList',
|
||||
// 奖惩原因相关(新增)
|
||||
REWARDS_PUNISH_REASON: 'rewardsPunishReason',
|
||||
// 系统设置
|
||||
IS_DARK: 'isDark',
|
||||
LANG: 'lang',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { getSimpleDeptList } from '@/api/system/dept'
|
||||
import { handleTree } from '@/utils/tree'
|
||||
import type { DeptVO } from '@/api/system/dept'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
interface DeptState {
|
||||
deptList: DeptVO[]
|
||||
deptTree: DeptVO[]
|
||||
isSetDept: boolean
|
||||
}
|
||||
|
||||
export const useDeptStore = defineStore('admin-dept', {
|
||||
state: (): DeptState => ({
|
||||
deptList: [],
|
||||
deptTree: [],
|
||||
isSetDept: false
|
||||
}),
|
||||
getters: {
|
||||
getDeptList(): DeptVO[] {
|
||||
return this.deptList
|
||||
},
|
||||
getDeptTree(): DeptVO[] {
|
||||
return this.deptTree
|
||||
},
|
||||
getIsSetDept(): boolean {
|
||||
return this.isSetDept
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 设置部门信息
|
||||
*/
|
||||
async setDeptInfoAction() {
|
||||
let deptInfo = wsCache.get(CACHE_KEY.DEPT)
|
||||
if (!deptInfo) {
|
||||
const deptList = await getSimpleDeptList()
|
||||
const deptTree = handleTree(deptList)
|
||||
deptInfo = {
|
||||
deptList,
|
||||
deptTree
|
||||
}
|
||||
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||
} else {
|
||||
// 有缓存时,尝试更新部门信息
|
||||
try {
|
||||
const deptList = await getSimpleDeptList()
|
||||
const deptTree = handleTree(deptList)
|
||||
deptInfo = { deptList, deptTree }
|
||||
} catch (error) {
|
||||
// 更新失败时使用缓存数据
|
||||
}
|
||||
}
|
||||
this.deptList = deptInfo.deptList
|
||||
this.deptTree = deptInfo.deptTree
|
||||
this.isSetDept = true
|
||||
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||
},
|
||||
/**
|
||||
* 更新部门信息
|
||||
*/
|
||||
async updateDeptInfoAction() {
|
||||
// 强制更新部门信息,不使用缓存
|
||||
const deptList = await getSimpleDeptList()
|
||||
const deptTree = handleTree(deptList)
|
||||
this.deptList = deptList
|
||||
this.deptTree = deptTree
|
||||
this.isSetDept = true
|
||||
const deptInfo = { deptList, deptTree }
|
||||
wsCache.set(CACHE_KEY.DEPT, deptInfo)
|
||||
},
|
||||
/**
|
||||
* 重置部门状态
|
||||
*/
|
||||
resetDeptState() {
|
||||
this.deptList = []
|
||||
this.deptTree = []
|
||||
this.isSetDept = false
|
||||
wsCache.delete(CACHE_KEY.DEPT)
|
||||
}
|
||||
},
|
||||
persist: false
|
||||
})
|
||||
|
||||
/**
|
||||
* 在组件外部使用部门 store
|
||||
*/
|
||||
export const useDeptStoreWithOut = () => {
|
||||
return useDeptStore(store)
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { getReportManageListByType } from '@/api/submit/systemConfig/reportManage'
|
||||
import type { ReportManageDTO } from '@/api/submit/systemConfig/reportManage/types'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
interface RewardsPunishReasonState {
|
||||
// 存储奖惩原因列表
|
||||
rewardsPunishReasonList: Array<{
|
||||
id: string
|
||||
name: string
|
||||
}>
|
||||
// 是否已设置奖惩原因
|
||||
isSetRewardsPunishReason: boolean
|
||||
}
|
||||
|
||||
export const useRewardsPunishReasonStore = defineStore('admin-rewardsPunishReason', {
|
||||
state: (): RewardsPunishReasonState => ({
|
||||
rewardsPunishReasonList: [],
|
||||
isSetRewardsPunishReason: false
|
||||
}),
|
||||
getters: {
|
||||
/**
|
||||
* 获取奖惩原因列表
|
||||
*/
|
||||
getRewardsPunishReasonList(): Array<{ id: string; name: string }> {
|
||||
return this.rewardsPunishReasonList
|
||||
},
|
||||
/**
|
||||
* 是否已设置奖惩原因
|
||||
*/
|
||||
getIsSetRewardsPunishReason(): boolean {
|
||||
return this.isSetRewardsPunishReason
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 设置奖惩原因列表信息
|
||||
*/
|
||||
async setRewardsPunishReasonAction() {
|
||||
let rewardsPunishReasonInfo = wsCache.get(CACHE_KEY.REWARDS_PUNISH_REASON)
|
||||
if (!rewardsPunishReasonInfo) {
|
||||
// 无缓存时,从接口获取
|
||||
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||
const reasons = this.extractRewardsPunishReason(response)
|
||||
rewardsPunishReasonInfo = {
|
||||
rewardsPunishReasonList: reasons
|
||||
}
|
||||
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||
} else {
|
||||
// 有缓存时,尝试更新数据
|
||||
try {
|
||||
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||
const reasons = this.extractRewardsPunishReason(response)
|
||||
rewardsPunishReasonInfo = {
|
||||
rewardsPunishReasonList: reasons
|
||||
}
|
||||
} catch (error) {
|
||||
// 更新失败时使用缓存数据
|
||||
}
|
||||
}
|
||||
this.rewardsPunishReasonList = rewardsPunishReasonInfo.rewardsPunishReasonList
|
||||
this.isSetRewardsPunishReason = true
|
||||
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||
},
|
||||
|
||||
/**
|
||||
* 从报表管理列表中提取奖惩原因
|
||||
*/
|
||||
extractRewardsPunishReason(reportList: ReportManageDTO[]): Array<{ id: string; name: string }> {
|
||||
// 使用Map去重
|
||||
const reasonMap = new Map<string, string>()
|
||||
|
||||
reportList.forEach(report => {
|
||||
reasonMap.set(report.id, report.name)
|
||||
})
|
||||
|
||||
// 转换为数组格式
|
||||
return Array.from(reasonMap.entries()).map(([id, name]) => ({
|
||||
id,
|
||||
name
|
||||
}))
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新奖惩原因列表信息
|
||||
*/
|
||||
async updateRewardsPunishReasonAction() {
|
||||
// 强制更新数据,不使用缓存
|
||||
const response = await getReportManageListByType('REWARDS_PUNISH_REASON')
|
||||
const reasons = this.extractRewardsPunishReason(response)
|
||||
this.rewardsPunishReasonList = reasons
|
||||
this.isSetRewardsPunishReason = true
|
||||
const rewardsPunishReasonInfo = {
|
||||
rewardsPunishReasonList: reasons
|
||||
}
|
||||
wsCache.set(CACHE_KEY.REWARDS_PUNISH_REASON, rewardsPunishReasonInfo)
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置奖惩原因状态
|
||||
*/
|
||||
resetRewardsPunishReasonState() {
|
||||
this.rewardsPunishReasonList = []
|
||||
this.isSetRewardsPunishReason = false
|
||||
wsCache.delete(CACHE_KEY.REWARDS_PUNISH_REASON)
|
||||
}
|
||||
},
|
||||
persist: false
|
||||
})
|
||||
|
||||
/**
|
||||
* 在组件外部使用奖惩原因 store
|
||||
*/
|
||||
export const useRewardsPunishReasonStoreWithOut = () => {
|
||||
return useRewardsPunishReasonStore(store)
|
||||
}
|
||||
|
|
@ -93,6 +93,11 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
delCachedView() {
|
||||
const route = router.currentRoute.value
|
||||
const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)
|
||||
for (const v of this.visitedViews) {
|
||||
if (v.name === route.name) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if (index > -1) {
|
||||
this.cachedViews.delete(this.getCachedViews[index])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import { store } from '@/store'
|
||||
import { defineStore } from 'pinia'
|
||||
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
|
||||
import { getSimpleUserList } from '@/api/system/user'
|
||||
import type { UserVO } from '@/api/system/user'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
interface UserListState {
|
||||
userList: UserVO[]
|
||||
isSetUserList: boolean
|
||||
}
|
||||
|
||||
export const useUserListStore = defineStore('admin-userList', {
|
||||
state: (): UserListState => ({
|
||||
userList: [],
|
||||
isSetUserList: false
|
||||
}),
|
||||
getters: {
|
||||
getUserList(): UserVO[] {
|
||||
return this.userList
|
||||
},
|
||||
getIsSetUserList(): boolean {
|
||||
return this.isSetUserList
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
/**
|
||||
* 设置用户列表信息
|
||||
*/
|
||||
async setUserListAction() {
|
||||
let userListInfo = wsCache.get(CACHE_KEY.USER_LIST)
|
||||
if (!userListInfo) {
|
||||
const userList = await getSimpleUserList()
|
||||
userListInfo = {
|
||||
userList
|
||||
}
|
||||
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||
} else {
|
||||
// 有缓存时,尝试更新用户列表信息
|
||||
try {
|
||||
const userList = await getSimpleUserList()
|
||||
userListInfo = { userList }
|
||||
} catch (error) {
|
||||
// 更新失败时使用缓存数据
|
||||
}
|
||||
}
|
||||
this.userList = userListInfo.userList
|
||||
this.isSetUserList = true
|
||||
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||
},
|
||||
/**
|
||||
* 更新用户列表信息
|
||||
*/
|
||||
async updateUserListAction() {
|
||||
// 强制更新用户列表信息,不使用缓存
|
||||
const userList = await getSimpleUserList()
|
||||
this.userList = userList
|
||||
this.isSetUserList = true
|
||||
const userListInfo = { userList }
|
||||
wsCache.set(CACHE_KEY.USER_LIST, userListInfo)
|
||||
},
|
||||
/**
|
||||
* 重置用户列表状态
|
||||
*/
|
||||
resetUserListState() {
|
||||
this.userList = []
|
||||
this.isSetUserList = false
|
||||
wsCache.delete(CACHE_KEY.USER_LIST)
|
||||
}
|
||||
},
|
||||
persist: false
|
||||
})
|
||||
|
||||
/**
|
||||
* 在组件外部使用用户列表 store
|
||||
*/
|
||||
export const useUserListStoreWithOut = () => {
|
||||
return useUserListStore(store)
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
export const enumsType = {
|
||||
reportStatus:[
|
||||
{value:'STAGING',text:'暂存'},
|
||||
{value:'AUDIT',text:'待审核'},
|
||||
{value:'APPROVED',text:'审核通过'},
|
||||
{value:'REVIEW_FAILED',text:'审核不通过'},
|
||||
],
|
||||
approveActions:[
|
||||
{value:'PASS',text:'通过'},
|
||||
{value:'REFUSE',text:'拒绝'},
|
||||
],
|
||||
trueOrFalse:[
|
||||
{value:true,text:'是'},
|
||||
{value:false,text:'否'},
|
||||
],
|
||||
integralRuleTypeEnum:[
|
||||
{value:'FIXED_VALUE',text:'固定分值'},
|
||||
{value:'MANUAL_VALUE',text:'手工分值'},
|
||||
{value:'CALC_VALUE',text:'动态计算分值'},
|
||||
{value:'CHOOSE_VALUE',text:'选择分值'},
|
||||
],
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Created by 芋道源码
|
||||
* Created by 捷汇链源码
|
||||
*
|
||||
* 枚举类
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<Verify
|
||||
v-if="loginData.captchaEnable === 'true'"
|
||||
v-if="loginData.captchaEnable === true"
|
||||
ref="verify"
|
||||
:captchaType="captchaType"
|
||||
:imgSize="{ width: '400px', height: '200px' }"
|
||||
|
|
@ -158,6 +158,7 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
|
||||
import * as LoginApi from '@/api/login'
|
||||
import * as authUtil from '@/utils/auth'
|
||||
|
|
@ -199,7 +200,7 @@ const loginData = reactive({
|
|||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE !== 'false',
|
||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE !== 'false',
|
||||
loginForm: {
|
||||
tenantName: '芋道源码',
|
||||
tenantName: '捷汇链源码',
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
captchaVerification: '',
|
||||
|
|
@ -256,6 +257,8 @@ const tryLogin = async () => {
|
|||
|
||||
const res = await LoginApi.socialLogin(type, code, state)
|
||||
authUtil.setToken(res)
|
||||
const dictStore = useDictStore()
|
||||
await dictStore.setDictMap()
|
||||
|
||||
router.push({ path: redirect || '/' })
|
||||
} catch (err) {}
|
||||
|
|
@ -302,6 +305,8 @@ const handleLogin = async (params) => {
|
|||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
const dictStore = useDictStore()
|
||||
await dictStore.setDictMap()
|
||||
if (!redirect) {
|
||||
redirect = '/'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@
|
|||
mode="pop"
|
||||
@success="handleLogin"
|
||||
/>
|
||||
<el-col :span="24" class="px-10px">
|
||||
<!-- <el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<el-row :gutter="5" justify="space-between" style="width: 100%">
|
||||
<el-col :span="8">
|
||||
|
|
@ -112,8 +112,8 @@
|
|||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||
</el-col> -->
|
||||
<!-- <el-divider content-position="center">{{ t('login.otherLogin') }}</el-divider>
|
||||
<el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-between">
|
||||
|
|
@ -128,9 +128,9 @@
|
|||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-divider content-position="center">萌新必读</el-divider>
|
||||
<el-col :span="24" class="px-10px">
|
||||
</el-col> -->
|
||||
<!-- <el-divider content-position="center">萌新必读</el-divider> -->
|
||||
<!-- <el-col :span="24" class="px-10px">
|
||||
<el-form-item>
|
||||
<div class="w-full flex justify-between">
|
||||
<el-link href="https://doc.iocoder.cn/" target="_blank">📚开发指南</el-link>
|
||||
|
|
@ -143,7 +143,7 @@
|
|||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-col> -->
|
||||
</el-row>
|
||||
</el-form>
|
||||
</template>
|
||||
|
|
@ -156,6 +156,7 @@ import { useIcon } from '@/hooks/web/useIcon'
|
|||
|
||||
import * as authUtil from '@/utils/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
import * as LoginApi from '@/api/login'
|
||||
import { LoginStateEnum, useFormValid, useLoginState } from './useLogin'
|
||||
|
||||
|
|
@ -272,6 +273,8 @@ const handleLogin = async (params: any) => {
|
|||
authUtil.removeLoginForm()
|
||||
}
|
||||
authUtil.setToken(res)
|
||||
const dictStore = useDictStore()
|
||||
await dictStore.setDictMap()
|
||||
if (!redirect.value) {
|
||||
redirect.value = '/'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ const loginData = reactive({
|
|||
},
|
||||
loginForm: {
|
||||
uuid: '',
|
||||
tenantName: '芋道源码',
|
||||
tenantName: '捷汇链源码',
|
||||
mobileNumber: '',
|
||||
code: ''
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,11 +44,11 @@ const equalToPassword = (_rule, value, callback) => {
|
|||
const rules = reactive<FormRules>({
|
||||
oldPassword: [
|
||||
{ required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
{ min: 4, max: 16, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },
|
||||
{ min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
{ min: 4, max: 16, message: t('profile.password.pwdRules'), trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ required: true, message: t('profile.password.cfPwdMsg'), trigger: 'blur' },
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<div class="relative flex flex-row justify-center w-full h-full">
|
||||
<!-- title -->
|
||||
<div class="flex flex-col justify-center">
|
||||
<div class="text-28px font-bold text-center">芋道 AI</div>
|
||||
<div class="text-28px font-bold text-center">捷汇链 AI</div>
|
||||
<div class="flex flex-row flex-wrap items-center justify-center w-460px mt-20px">
|
||||
<div
|
||||
class="flex justify-center w-180px leading-50px border border-solid border-[#e4e4e4] rounded-10px m-10px cursor-pointer hover:bg-[rgba(243,243,243,0.73)]"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Created by 芋道源码
|
||||
* Created by 捷汇链源码
|
||||
*
|
||||
* AI 枚举类
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
* Created by 芋道源码
|
||||
* Created by 捷汇链源码
|
||||
*
|
||||
* AI 枚举类
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="创建页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<el-form ref="formRef" :model="modelData" :rules="rules" label-width="120px" class="mt-20px">
|
||||
<el-form-item label="流程标识" prop="key" class="mb-20px">
|
||||
<div class="flex items-center">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
|
||||
import { IDomEditor } from '@wangeditor/editor'
|
||||
import { Editor, Toolbar } from '@wangeditor-next/editor-for-vue'
|
||||
import { IDomEditor } from '@wangeditor-next/editor'
|
||||
import MentionModal from './MentionModal.vue'
|
||||
|
||||
const emit = defineEmits(['confirm'])
|
||||
|
|
@ -43,7 +43,7 @@ const handleConfirm = () => {
|
|||
|
||||
// Editor 相关
|
||||
const editorRef = shallowRef<IDomEditor>()
|
||||
const editorId = ref('wangeEditor-1')
|
||||
const editorId = ref('wangEditor-1')
|
||||
const toolbarConfig = {
|
||||
excludeKeys: ['group-video'],
|
||||
insertKeys: {
|
||||
|
|
@ -86,7 +86,7 @@ onBeforeUnmount(() => {
|
|||
/>
|
||||
</div>
|
||||
<!-- TODO @unocss 简化 style -->
|
||||
<div style="border: 1px solid #ccc; margin: 10px">
|
||||
<div style=" margin: 10px;border: 1px solid #ccc">
|
||||
<Toolbar
|
||||
style="border-bottom: 1px solid #ccc"
|
||||
:editor="editorRef"
|
||||
|
|
@ -106,11 +106,11 @@ onBeforeUnmount(() => {
|
|||
@insert-mention="insertMention"
|
||||
/>
|
||||
</div>
|
||||
<div style="margin-right: 10px; float: right">
|
||||
<div style=" float: right;margin-right: 10px">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style src="@wangeditor/editor/dist/css/style.css"></style>
|
||||
<style src="@wangeditor-next/editor/dist/css/style.css"></style>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Boot } from '@wangeditor/editor'
|
||||
import { Boot } from '@wangeditor-next/editor'
|
||||
import processRecordModule from './module'
|
||||
import mentionModule from '@wangeditor/plugin-mention'
|
||||
import mentionModule from '@wangeditor-next/plugin-mention'
|
||||
|
||||
// 注册:要在创建编辑器之前注册,且只能注册一次,不可重复注册
|
||||
export const setupWangEditorPlugin = () => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { SlateElement } from '@wangeditor/editor'
|
||||
import { SlateElement } from '@wangeditor-next/editor'
|
||||
|
||||
function processRecordToHtml(_elem: SlateElement, _childrenHtml: string): string {
|
||||
return `<span data-w-e-type="process-record" data-w-e-is-void data-w-e-is-inline>流程记录</span>`
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IModuleConf } from '@wangeditor/editor'
|
||||
import { IModuleConf } from '@wangeditor-next/editor'
|
||||
import withProcessRecord from './plugin'
|
||||
import renderElemConf from './render-elem'
|
||||
import elemToHtmlConf from './elem-to-html'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IButtonMenu, IDomEditor } from '@wangeditor/editor'
|
||||
import { IButtonMenu, IDomEditor } from '@wangeditor-next/editor'
|
||||
|
||||
class ProcessRecordMenu implements IButtonMenu {
|
||||
readonly tag: string
|
||||
|
|
|
|||
|
|
@ -1,16 +1,28 @@
|
|||
import { DOMElement } from './utils/dom'
|
||||
import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor/editor'
|
||||
import { IDomEditor, SlateDescendant, SlateElement } from '@wangeditor-next/editor'
|
||||
|
||||
/**
|
||||
* 解析 HTML 字符串,生成“附件”元素
|
||||
* @param domElem HTML 对应的 DOM Element
|
||||
* @param children 子节点
|
||||
* @param editor editor 实例
|
||||
* @returns “附件”元素,如上文的 myResume
|
||||
*/
|
||||
function parseHtml(
|
||||
_elem: DOMElement,
|
||||
_domElem: DOMElement,
|
||||
_children: SlateDescendant[],
|
||||
_editor: IDomEditor
|
||||
): SlateElement {
|
||||
return {
|
||||
// TODO @lesan:这里有个红色告警,可以去掉哇?
|
||||
// TS 语法
|
||||
|
||||
|
||||
// 生成“流程记录”元素(按照此前约定的数据结构)
|
||||
const processRecord = {
|
||||
type: 'process-record',
|
||||
children: [{ text: '' }]
|
||||
children: [{ text: '' }], // void node 必须有 children ,其中有一个空字符串,重要!!!
|
||||
}
|
||||
|
||||
return processRecord
|
||||
}
|
||||
|
||||
const parseHtmlConf = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { DomEditor, IDomEditor } from '@wangeditor/editor'
|
||||
import { DomEditor, IDomEditor } from '@wangeditor-next/editor'
|
||||
|
||||
function withProcessRecord<T extends IDomEditor>(editor: T) {
|
||||
const { isInline, isVoid } = editor
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { h, VNode } from 'snabbdom'
|
||||
import { DomEditor, IDomEditor, SlateElement } from '@wangeditor/editor'
|
||||
import { DomEditor, IDomEditor, SlateElement } from '@wangeditor-next/editor'
|
||||
|
||||
function renderProcessRecord(
|
||||
elem: SlateElement,
|
||||
|
|
|
|||
|
|
@ -242,7 +242,6 @@ const handleProcessDetail = (row) => {
|
|||
})
|
||||
}
|
||||
|
||||
// fix: 列表不刷新的问题。
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="流程创建页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="发起流程页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<!-- 第一步,通过流程定义的列表,选择对应的流程 -->
|
||||
<template v-if="!selectProcessDefinition">
|
||||
<el-input
|
||||
|
|
|
|||
|
|
@ -496,7 +496,7 @@
|
|||
</div>
|
||||
</el-popover>
|
||||
<!-- 【再次提交】 按钮-->
|
||||
<div
|
||||
<!-- <div
|
||||
@click="handleReCreate()"
|
||||
class="hover-bg-gray-100 rounded-xl p-6px"
|
||||
v-if="
|
||||
|
|
@ -506,7 +506,7 @@
|
|||
"
|
||||
>
|
||||
<Icon :size="14" icon="ep:refresh" /> 再次提交
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 签名弹窗 -->
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="流程详情页面" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
<ContentWrap :bodyStyle="{ padding: '10px 20px 0' }" class="position-relative">
|
||||
<div class="processInstance-wrap-main">
|
||||
<el-scrollbar>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="流程发起、取消、重新发起" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
<doc-alert title="我的流程页面" url="https://doc.iocoder.cn/bpm/process-instance/" />
|
||||
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
|
|
@ -201,9 +201,9 @@
|
|||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button link type="primary" v-else @click="handleCreate(scope.row)">
|
||||
<!-- <el-button link type="primary" v-else @click="handleCreate(scope.row)">
|
||||
重新发起
|
||||
</el-button>
|
||||
</el-button> -->
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
|
||||
<doc-alert title="已办页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
|
|
@ -52,13 +45,13 @@
|
|||
<el-form-item label="" prop="status" :style="{ position: 'absolute', right: '130px' }">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择流程状态"
|
||||
placeholder="请选择审批状态"
|
||||
clearable
|
||||
class="!w-155px"
|
||||
@change="handleQuery"
|
||||
>
|
||||
<el-option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS)"
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.BPM_TASK_STATUS)"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
:value="dict.value"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
<template>
|
||||
<doc-alert title="审批通过、不通过、驳回" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
<doc-alert
|
||||
title="审批转办、委派、抄送"
|
||||
url="https://doc.iocoder.cn/bpm/task-delegation-and-cc/"
|
||||
/>
|
||||
<doc-alert title="审批加签、减签" url="https://doc.iocoder.cn/bpm/sign/" />
|
||||
|
||||
<doc-alert title="待办页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
|
||||
<ContentWrap>
|
||||
<!-- 搜索工作栏 -->
|
||||
<el-form
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@
|
|||
</el-table>
|
||||
</el-form>
|
||||
<el-row justify="center" class="mt-3" v-if="!disabled">
|
||||
<el-button @click="handleAdd" round>+ 添加采购产品</el-button>
|
||||
<el-button @click="handleAdd" round>+ 添加销售产品</el-button>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
|
|||
|
|
@ -384,6 +384,7 @@ defineOptions({ name: 'IoTDevice' })
|
|||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const { t } = useI18n() // 国际化
|
||||
const route = useRoute()
|
||||
|
||||
const loading = ref(true) // 列表加载中
|
||||
const list = ref<DeviceVO[]>([]) // 列表的数据
|
||||
|
|
@ -392,7 +393,7 @@ const queryParams = reactive({
|
|||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
deviceName: undefined,
|
||||
productId: undefined,
|
||||
productId: undefined as number | undefined,
|
||||
deviceType: undefined,
|
||||
nickname: undefined,
|
||||
status: undefined,
|
||||
|
|
@ -513,7 +514,12 @@ const handleImport = () => {
|
|||
|
||||
/** 初始化 **/
|
||||
onMounted(async () => {
|
||||
getList()
|
||||
// 处理 productId 参数
|
||||
const { productId } = route.query
|
||||
if (productId) {
|
||||
queryParams.productId = Number(productId)
|
||||
}
|
||||
await getList()
|
||||
|
||||
// 获取产品列表
|
||||
products.value = await ProductApi.getSimpleProductList()
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ const copyToClipboard = async (text: string) => {
|
|||
/** 路由跳转到设备管理 */
|
||||
const { push } = useRouter()
|
||||
const goToDeviceList = (productId: number) => {
|
||||
push({ name: 'IoTDevice', params: { productId } })
|
||||
push({ name: 'IoTDevice', query: { productId } })
|
||||
}
|
||||
|
||||
/** 修改操作 */
|
||||
|
|
|
|||
|
|
@ -69,7 +69,6 @@ const getOrderData = async () => {
|
|||
|
||||
/** 查询商品数据 */
|
||||
const getProductData = async () => {
|
||||
// TODO: @芋艿:这个接口的返回值,是不是用命名字段更好些?
|
||||
const productCount = await ProductSpuApi.getTabsCount()
|
||||
data.productForSale.value = productCount['0']
|
||||
data.productInWarehouse.value = productCount['1']
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ const getOrderCountTrendComparison = async (
|
|||
}
|
||||
eChartOptions.xAxis!['data'] = dates
|
||||
eChartOptions.series = series
|
||||
// legend在4个切换到2个的时候,还是显示成4个,需要手动配置一下
|
||||
// legend 在 4 个切换到 2 个的时候,还是显示成 4 个,需要手动配置一下
|
||||
eChartOptions.legend['data'] = series.map((item) => item.name)
|
||||
loading.value = false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,11 +166,15 @@ const handleEditorReset = () => storePageIndex()
|
|||
//#region 无感刷新
|
||||
// 记录标识
|
||||
const DIY_PAGE_INDEX_KEY = 'diy_page_index'
|
||||
|
||||
// 1. 记录
|
||||
const storePageIndex = () =>
|
||||
sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`)
|
||||
function storePageIndex() {
|
||||
debugger
|
||||
return sessionStorage.setItem(DIY_PAGE_INDEX_KEY, `${selectedTemplateItem.value}`)
|
||||
}
|
||||
// 2. 恢复
|
||||
const recoverPageIndex = () => {
|
||||
debugger
|
||||
// 恢复重置前的页面,默认是第一个页面
|
||||
const pageIndex = toNumber(sessionStorage.getItem(DIY_PAGE_INDEX_KEY)) || 0
|
||||
// 移除标记
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@
|
|||
min-width="100"
|
||||
prop="marketPrice"
|
||||
/>
|
||||
<el-table-column label="原价" min-width="100" prop="marketPrice" />
|
||||
<el-table-column align="center" label="活动状态" min-width="100" prop="status">
|
||||
<template #default="scope">
|
||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const crudSchemas = reactive<CrudSchema[]>([
|
|||
}
|
||||
},
|
||||
{
|
||||
label: '积分商城活动商品',
|
||||
label: '活动商品',
|
||||
field: 'spuId',
|
||||
isTable: true,
|
||||
isSearch: false,
|
||||
|
|
|
|||
|
|
@ -89,9 +89,7 @@ const initGiveCouponList = async () => {
|
|||
if (isEmpty(rewardRule.value) || isEmpty(rewardRule.value.giveCouponTemplateCounts)) {
|
||||
return
|
||||
}
|
||||
const tempLateIds = Object.keys(rewardRule.value.giveCouponTemplateCounts!).map((item) =>
|
||||
parseInt(item)
|
||||
)
|
||||
const tempLateIds = Object.keys(rewardRule.value.giveCouponTemplateCounts!)
|
||||
const data = await CouponTemplateApi.getCouponTemplateList(tempLateIds)
|
||||
if (!data) {
|
||||
return
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ const terminalChartOptions = reactive<EChartsOption>({
|
|||
orient: 'vertical',
|
||||
left: 'right'
|
||||
},
|
||||
roseType: 'area',
|
||||
series: [
|
||||
{
|
||||
name: '会员终端',
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@
|
|||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="自提门店" prop="pickUpStoreId">
|
||||
<el-form-item label="自提门店" prop="pickUpStoreIds">
|
||||
<el-select
|
||||
v-model="queryParams.pickUpStoreId"
|
||||
v-model="queryParams.pickUpStoreIds"
|
||||
class="!w-280px"
|
||||
placeholder="全部"
|
||||
@change="handleQuery"
|
||||
|
|
@ -251,7 +251,7 @@ const INIT_QUERY_PARAMS = {
|
|||
// 配送方式
|
||||
deliveryType: DeliveryTypeEnum.PICK_UP.type,
|
||||
// 自提门店
|
||||
pickUpStoreId: -1
|
||||
pickUpStoreIds: -1
|
||||
} // 初始表单参数
|
||||
|
||||
const queryParams = ref({ ...INIT_QUERY_PARAMS }) // 表单搜索
|
||||
|
|
@ -264,7 +264,7 @@ const isUse = ref(true) // 是否可核销
|
|||
// 订单聚合搜索 select 类型配置(动态搜索)
|
||||
const dynamicSearchList = ref([
|
||||
{ value: 'no', label: '订单号' },
|
||||
{ value: 'userId', label: '用户UID' },
|
||||
{ value: 'userId', label: '用户 UID' },
|
||||
{ value: 'userNickname', label: '用户昵称' },
|
||||
{ value: 'userMobile', label: '用户电话' }
|
||||
])
|
||||
|
|
@ -309,7 +309,7 @@ const resetQuery = () => {
|
|||
queryFormRef.value?.resetFields()
|
||||
queryParams.value = { ...INIT_QUERY_PARAMS }
|
||||
if (pickUpStoreList.value.length > 0) {
|
||||
queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
|
||||
queryParams.value.pickUpStoreIds = pickUpStoreList.value[0].id
|
||||
}
|
||||
handleQuery()
|
||||
}
|
||||
|
|
@ -418,7 +418,7 @@ onMounted(async () => {
|
|||
}
|
||||
|
||||
// 查询
|
||||
queryParams.value.pickUpStoreId = pickUpStoreList.value[0].id
|
||||
queryParams.value.pickUpStoreIds = pickUpStoreList.value[0].id
|
||||
isUse.value = false
|
||||
await getList()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① 移除 avue 组件,使用 ElementUI 原生组件
|
||||
-->
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① 移除暂时用不到的 websocket
|
||||
② 代码优化,补充注释,提升阅读性
|
||||
-->
|
||||
|
|
@ -121,7 +121,7 @@ const loadMore = () => {
|
|||
|
||||
const getPage = async (page: any, params: any = null) => {
|
||||
loading.value = true
|
||||
let dataTemp = await getMessagePage(
|
||||
const dataTemp = await getMessagePage(
|
||||
Object.assign(
|
||||
{
|
||||
pageNo: page.pageNo,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
【微信消息 - 图文】
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① 代码优化,补充注释,提升阅读性
|
||||
-->
|
||||
<template>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<div class="news-main">
|
||||
<div class="news-content">
|
||||
<el-image
|
||||
:src="article.picUrl||article.thumbUrl"
|
||||
:src="article.picUrl || article.thumbUrl"
|
||||
class="material-img"
|
||||
style="width: 100%; height: 120px"
|
||||
/>
|
||||
|
|
@ -29,7 +29,7 @@
|
|||
<div class="news-content-item">
|
||||
<div class="news-content-item-title">{{ article.title }}</div>
|
||||
<div class="news-content-item-img">
|
||||
<img :src="article.picUrl||article.thumbUrl" class="material-img" height="100%"/>
|
||||
<img :src="article.picUrl || article.thumbUrl" class="material-img" height="100%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<!--
|
||||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① 移除多余的 rep 为前缀的变量,让 message 消息更简单
|
||||
② 代码优化,补充注释,提升阅读性
|
||||
③ 优化消息的临时缓存策略,发送消息时,只清理被发送消息的 tab,不会强制切回到 text 输入
|
||||
|
|
@ -106,7 +106,7 @@ watch(
|
|||
if (temp) {
|
||||
reply.value = temp
|
||||
} else {
|
||||
let newData = createEmptyReply(reply)
|
||||
const newData = createEmptyReply(reply)
|
||||
newData.type = newTab
|
||||
reply.value = newData
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
【微信消息 - 视频】
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① bug 修复:
|
||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
- Copyright (C) 2018-2019
|
||||
- All rights reserved, Designed By www.joolun.com
|
||||
【微信消息 - 语音】
|
||||
芋道源码:
|
||||
捷汇链源码:
|
||||
① bug 修复:
|
||||
1)joolun 的做法:使用 mediaId 从微信公众号,下载对应的 mp4 素材,从而播放内容;
|
||||
存在的问题:mediaId 有效期是 3 天,超过时间后无法播放
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="waterfall" v-loading="props.loading">
|
||||
<template v-for="item in props.list" :key="item.articleId">
|
||||
<template v-for="(item, index) in props.list" :key="index">
|
||||
<div class="waterfall-item" v-if="item.content && item.content.newsItem">
|
||||
<WxNews :articles="item.content.newsItem" />
|
||||
<!-- 操作按钮 -->
|
||||
|
|
|
|||
|
|
@ -186,7 +186,9 @@ const removeNews = async (index: number) => {
|
|||
if (activeNewsIndex.value === index) {
|
||||
activeNewsIndex.value = 0
|
||||
}
|
||||
} catch {}
|
||||
} catch {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个图文
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { IEditorConfig } from '@wangeditor/editor'
|
||||
import { IEditorConfig } from '@wangeditor-next/editor'
|
||||
import { getAccessToken, getTenantId } from '@/utils/auth'
|
||||
|
||||
const message = useMessage()
|
||||
|
|
|
|||
|
|
@ -110,7 +110,9 @@ const onBeforeDialogClose = async (onDone: () => {}) => {
|
|||
try {
|
||||
await message.confirm('修改内容可能还未保存,确定关闭吗?')
|
||||
onDone()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 列表查询 ========================
|
||||
|
|
@ -179,7 +181,9 @@ const onPublish = async (item: Article) => {
|
|||
await MpFreePublishApi.submitFreePublish(accountId.value, mediaId)
|
||||
message.notifySuccess('发布成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除按钮操作 */
|
||||
|
|
@ -190,7 +194,9 @@ const onDelete = async (item: Article) => {
|
|||
await MpDraftApi.deleteDraft(accountId.value, mediaId)
|
||||
message.notifySuccess('删除成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -97,7 +97,9 @@ const handleDelete = async (item: any) => {
|
|||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ const emit = defineEmits<{
|
|||
column-count: 5;
|
||||
margin-top: 10px;
|
||||
|
||||
/* 芋道源码:增加 10px,避免顶着上面 */
|
||||
/* 捷汇链源码:增加 10px,避免顶着上面 */
|
||||
}
|
||||
|
||||
.waterfall-item {
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ const uploadVideoRef = ref<UploadInstance | null>(null)
|
|||
const submitVideo = () => {
|
||||
uploadFormRef.value?.validate((valid) => {
|
||||
if (!valid) {
|
||||
return false
|
||||
return
|
||||
}
|
||||
uploadVideoRef.value?.submit()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ const onParentDragEnd = ({ oldIndex, newIndex }) => {
|
|||
}
|
||||
|
||||
// 使用一个辅助数组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent`
|
||||
let positions = new Array<boolean>(menuList.value.length).fill(false)
|
||||
const positions = new Array<boolean>(menuList.value.length).fill(false)
|
||||
positions[props.parentIndex] = true
|
||||
const [out] = positions.splice(oldIndex, 1) // 移出菜单,保存到变量out
|
||||
positions.splice(newIndex, 0, out) // 把out变量插入被移出的菜单
|
||||
|
|
|
|||
|
|
@ -217,7 +217,9 @@ const onDeleteMenu = async () => {
|
|||
activeMenu.value = {}
|
||||
showRightPanel.value = false
|
||||
activeIndex.value = MENU_NOT_SELECTED
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
// ======================== 菜单编辑 ========================
|
||||
|
|
@ -267,7 +269,7 @@ const menuListToBackend = () => {
|
|||
// 将前端的 menu,转换成后端接收的 menu
|
||||
// TODO: @芋艿,需要根据后台API删除不需要的字段
|
||||
const menuToBackend = (menu: any) => {
|
||||
let result = {
|
||||
const result = {
|
||||
...menu,
|
||||
children: undefined, // 不处理子节点
|
||||
reply: undefined // 稍后复制
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ const userSummaryOption = reactive({
|
|||
series: [
|
||||
{
|
||||
name: '新增用户',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -108,7 +108,7 @@ const userSummaryOption = reactive({
|
|||
},
|
||||
{
|
||||
name: '取消关注的用户',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -122,7 +122,7 @@ const userCumulateOption = reactive({
|
|||
data: ['累计用户量']
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
type: 'category' as const,
|
||||
data: [] as any[]
|
||||
},
|
||||
yAxis: {
|
||||
|
|
@ -132,7 +132,7 @@ const userCumulateOption = reactive({
|
|||
{
|
||||
name: '累计用户量',
|
||||
data: [] as any[], // 累计用户量的数据
|
||||
type: 'line',
|
||||
type: 'line' as const,
|
||||
smooth: true,
|
||||
label: {
|
||||
show: true
|
||||
|
|
@ -156,7 +156,7 @@ const upstreamMessageOption = reactive({
|
|||
series: [
|
||||
{
|
||||
name: '用户发送人数',
|
||||
type: 'line',
|
||||
type: 'line' as const,
|
||||
smooth: true,
|
||||
label: {
|
||||
show: true
|
||||
|
|
@ -165,7 +165,7 @@ const upstreamMessageOption = reactive({
|
|||
},
|
||||
{
|
||||
name: '用户发送条数',
|
||||
type: 'line',
|
||||
type: 'line' as const,
|
||||
smooth: true,
|
||||
label: {
|
||||
show: true
|
||||
|
|
@ -188,7 +188,7 @@ const interfaceSummaryOption = reactive({
|
|||
series: [
|
||||
{
|
||||
name: '被动回复用户消息的次数',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -197,7 +197,7 @@ const interfaceSummaryOption = reactive({
|
|||
},
|
||||
{
|
||||
name: '失败次数',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -205,7 +205,7 @@ const interfaceSummaryOption = reactive({
|
|||
},
|
||||
{
|
||||
name: '最大耗时',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -213,7 +213,7 @@ const interfaceSummaryOption = reactive({
|
|||
},
|
||||
{
|
||||
name: '总耗时',
|
||||
type: 'bar',
|
||||
type: 'bar' as const,
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
|
|
@ -282,7 +282,9 @@ const initUserSummaryChart = async () => {
|
|||
userSummaryOption.series[1].data[index] = item.cancelUser
|
||||
})
|
||||
})
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 累计用户数据 */
|
||||
|
|
@ -300,7 +302,9 @@ const initUserCumulateChart = async () => {
|
|||
data.forEach((item, index) => {
|
||||
userCumulateOption.series[0].data[index] = item.cumulateUser
|
||||
})
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 消息概况数据 */
|
||||
|
|
@ -320,7 +324,9 @@ const initUpstreamMessageChart = async () => {
|
|||
upstreamMessageOption.series[0].data[index] = item.messageUser
|
||||
upstreamMessageOption.series[1].data[index] = item.messageCount
|
||||
})
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 接口分析数据 */
|
||||
|
|
@ -344,6 +350,8 @@ const interfaceSummaryChart = async () => {
|
|||
interfaceSummaryOption.series[2].data[index] = item.maxTimeCost
|
||||
interfaceSummaryOption.series[3].data[index] = item.totalTimeCost
|
||||
})
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ const handleDelete = async (id: number) => {
|
|||
message.success(t('common.delSuccess'))
|
||||
// 刷新列表
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 同步操作 */
|
||||
|
|
@ -149,6 +151,8 @@ const handleSync = async () => {
|
|||
await MpTagApi.syncTag(queryParams.accountId as number)
|
||||
message.success('同步标签成功')
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
<el-table-column label="用户标识" align="center" prop="openid" width="260" />
|
||||
<el-table-column label="用户头像" min-width="80px" prop="headImageUrl">
|
||||
<template #default="scope">
|
||||
<el-avatar :src="scope.row.headImageUrl"/>
|
||||
<el-avatar :src="scope.row.headImageUrl" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="昵称" align="center" prop="nickname" />
|
||||
|
|
@ -176,7 +176,9 @@ const handleSync = async () => {
|
|||
await MpUserApi.syncUser(queryParams.accountId)
|
||||
message.success('开始从微信公众号同步粉丝信息,同步需要一段时间,建议稍后再查询')
|
||||
await getList()
|
||||
} catch {}
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
/** 初始化 */
|
||||
|
|
|
|||
|
|
@ -147,7 +147,11 @@
|
|||
前往微信商户平台查看证书序列号
|
||||
</a>
|
||||
</el-form-item>
|
||||
<el-form-item label="public_key.pem 证书" label-width="180px" prop="config.publicKeyContent">
|
||||
<el-form-item
|
||||
label="public_key.pem 证书"
|
||||
label-width="180px"
|
||||
prop="config.publicKeyContent"
|
||||
>
|
||||
<el-input
|
||||
v-model="formData.config.publicKeyContent"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
|
|
@ -174,17 +178,10 @@
|
|||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="公钥 ID" label-width="180px" prop="config.publicKeyId">
|
||||
<el-input
|
||||
v-model="formData.config.publicKeyId"
|
||||
clearable
|
||||
placeholder="请输入公钥 ID"
|
||||
/>
|
||||
<el-input v-model="formData.config.publicKeyId" clearable placeholder="请输入公钥 ID" />
|
||||
</el-form-item>
|
||||
<el-form-item label-width="180px">
|
||||
<a
|
||||
href="https://pay.weixin.qq.com/doc/v3/merchant/4012153196"
|
||||
target="_blank"
|
||||
>
|
||||
<a href="https://pay.weixin.qq.com/doc/v3/merchant/4012153196" target="_blank">
|
||||
微信支付公钥产品简介及使用说明
|
||||
</a>
|
||||
</el-form-item>
|
||||
|
|
@ -246,7 +243,6 @@ const formRules = {
|
|||
{ required: true, message: '请上传 apiclient_key.pem 证书', trigger: 'blur' }
|
||||
],
|
||||
'config.certSerialNo': [{ required: true, message: '请输入证书序列号', trigger: 'blur' }],
|
||||
'config.publicKeyContent': [{ required: true, message: '请上传 public_key.pem 证书', trigger: 'blur' }],
|
||||
'config.publicKeyId': [{ required: true, message: '请输入公钥 ID', trigger: 'blur' }],
|
||||
'config.apiV3Key': [{ required: true, message: '请上传 api V3 密钥值', trigger: 'blur' }]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ const getDetail = async () => {
|
|||
return
|
||||
}
|
||||
const data = await PayOrderApi.getOrder(id.value, true)
|
||||
payOrder.value = data
|
||||
// 1.2 无法查询到支付信息
|
||||
if (!data) {
|
||||
message.error('支付订单不存在,请检查!')
|
||||
|
|
@ -255,6 +254,8 @@ const getDetail = async () => {
|
|||
goReturnUrl('close')
|
||||
return
|
||||
}
|
||||
// 2. 正常展示支付信息
|
||||
payOrder.value = data
|
||||
}
|
||||
|
||||
/** 提交支付 */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,481 @@
|
|||
<template>
|
||||
<el-drawer
|
||||
v-model="dialogVisible"
|
||||
title="数据详情"
|
||||
size="38%"
|
||||
direction="rtl"
|
||||
:close-on-click-modal="true"
|
||||
:close-on-press-escape="true"
|
||||
:show-close="true"
|
||||
>
|
||||
<!-- 移除自定义 header,使用默认header -->
|
||||
<div class="main-container">
|
||||
<!-- 基础信息 -->
|
||||
<div class="group-warp">
|
||||
<el-divider content-position="left">
|
||||
<span class="group-name">基础信息</span>
|
||||
</el-divider>
|
||||
<div class="group-container">
|
||||
<el-descriptions :column="2" size="default" border>
|
||||
<el-descriptions-item label="标题" :span="2">
|
||||
{{ detailData.title || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="部门">
|
||||
{{ detailData.deptName || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="事项">
|
||||
{{ detailData.reason?.name || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="涉事人">
|
||||
{{ detailData.personName || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行人">
|
||||
{{ detailData.executorName || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="填报时间">
|
||||
{{ formatDateTime(detailData.fillingTime) || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人">
|
||||
{{ detailData.approveUserName || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">
|
||||
{{ formatDateTime(detailData.approveTime) || "-" }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="getStatusTagType(detailData.status)">
|
||||
{{ getStatusText(detailData.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2">
|
||||
{{ detailData.remark || "-" }}
|
||||
</el-descriptions-item>
|
||||
<template v-if="detailData.userIntegralRecords && detailData.userIntegralRecords.length > 0">
|
||||
<el-descriptions-item
|
||||
v-for="(item, index) in detailData.userIntegralRecords"
|
||||
:key="index"
|
||||
:label="'积分'"
|
||||
>
|
||||
<div class="user-score-detail">
|
||||
<span>{{ item.userName || '-' }}</span>
|
||||
<span v-if="item.happenedDate">
|
||||
{{ ' 于 ' + item.happenedDate.slice(0, 10) }}
|
||||
</span>
|
||||
<span class="user-score-detail-value">
|
||||
<el-tag size="small" :type="item.score > 0 ? 'success' : 'danger'">
|
||||
{{ item.score >= 0 ? ('+' + item.score) : item.score }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</template>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 附件区域 -->
|
||||
<div class="group-warp" v-if="detailData.imageUrls && detailData.imageUrls.length > 0">
|
||||
<el-divider content-position="left">
|
||||
<span class="group-name">附件</span>
|
||||
</el-divider>
|
||||
<div class="group-container">
|
||||
<div class="image-container">
|
||||
<div class="image-container-item" v-for="(url, index) in detailData.imageUrls" :key="index">
|
||||
<el-image
|
||||
:src="getImageSrc(url)"
|
||||
fit="contain"
|
||||
@click="showImage(getImageSrc(url))"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评论区域 -->
|
||||
<div class="group-warp">
|
||||
<el-divider content-position="left">
|
||||
<span class="group-name">评论信息</span>
|
||||
</el-divider>
|
||||
<div class="group-container comment-container">
|
||||
<div class="b-white" v-if="showCommentInput === false">
|
||||
<el-button type="primary" size="small" @click="showCommentInput = true">
|
||||
<el-icon>
|
||||
<Plus />
|
||||
</el-icon>
|
||||
我要评论
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="add-comment-form" v-if="showCommentInput">
|
||||
<el-input
|
||||
v-model="commentText"
|
||||
type="textarea"
|
||||
maxlength="1000"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
placeholder="请输入评论内容"
|
||||
resize="none"
|
||||
/>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="showCommentInput = false" size="small">
|
||||
<el-icon>
|
||||
<CloseBold />
|
||||
</el-icon>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="addComment" :loading="commentLoading" size="small">
|
||||
<el-icon>
|
||||
<Select />
|
||||
</el-icon>
|
||||
提交
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-list">
|
||||
<template v-if="detailData.comments && detailData.comments.length > 0">
|
||||
<el-timeline>
|
||||
<el-timeline-item v-for="(comment, index) in detailData.comments" :key="index" :timestamp="formatDateTime(comment.createTime)" placement="top">
|
||||
<el-card>
|
||||
<div class="card-header" v-if="comment.createUserName === userId">
|
||||
<el-icon class="comment-delete" color="#eb333d" @click="handleDeleteComment(index)">
|
||||
<Delete />
|
||||
</el-icon>
|
||||
</div>
|
||||
<h4 class="comment-content">{{ comment.comment }}</h4>
|
||||
<p class="comment-user-info">{{ comment.createUserName + ' 提交于 ' + formatDateTime(comment.createTime) }}</p>
|
||||
</el-card>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-empty description="暂无评论" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片预览 -->
|
||||
<el-image-viewer
|
||||
v-if="imageViewerVisible"
|
||||
:url-list="[imageViewerUrl]"
|
||||
@close="imageViewerVisible = false"
|
||||
/>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { getMyReportDetail, saveComment, deleteComment } from '@/api/submit/common/common'
|
||||
import { useUserStore } from '@/store/modules/user'
|
||||
import { Picture, Plus, CloseBold, Select, Delete } from '@element-plus/icons-vue'
|
||||
import { enumsType } from '@/store/static'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
reportId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:visible': [value: boolean]
|
||||
'refresh': []
|
||||
}>()
|
||||
|
||||
// 响应式数据
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const detailData = ref({
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
reasonId: '',
|
||||
reason: { name: '' },
|
||||
personName: '',
|
||||
executorName: '',
|
||||
comment: '',
|
||||
remark: '',
|
||||
imageUrls: [] as string[],
|
||||
status: '',
|
||||
approveUserId: '',
|
||||
approveUserName: '',
|
||||
approveTime: '',
|
||||
fillingTime: '',
|
||||
userIntegralRecords: [] as any[],
|
||||
comments: [] as any[],
|
||||
depId: '',
|
||||
type: '',
|
||||
happenedDate: '',
|
||||
approverId: '',
|
||||
approverName: '',
|
||||
approverTime: '',
|
||||
createTime: '',
|
||||
createBy: '',
|
||||
createUserName: ''
|
||||
})
|
||||
|
||||
const commentText = ref('')
|
||||
const showCommentInput = ref(false)
|
||||
const commentLoading = ref(false)
|
||||
const imageViewerVisible = ref(false)
|
||||
const imageViewerUrl = ref('')
|
||||
const userStore = useUserStore()
|
||||
const userId = computed(() => userStore.getUserInfo?.id)
|
||||
|
||||
// 监听reportId变化,加载详情数据
|
||||
watch(() => props.reportId, (newId) => {
|
||||
if (newId && props.visible) {
|
||||
loadDetailData(newId)
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 监听visible变化,确保抽屉显示时重新加载数据
|
||||
watch(() => props.visible, (newVisible) => {
|
||||
if (newVisible && props.reportId) {
|
||||
loadDetailData(props.reportId)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载详情数据
|
||||
const loadDetailData = async (id: string) => {
|
||||
try {
|
||||
console.log('开始加载详情数据,reportId:', id)
|
||||
const response = await getMyReportDetail(id)
|
||||
console.log('接口返回数据:', response)
|
||||
if (response) {
|
||||
response.comments = response.comments || []
|
||||
response.userIntegralRecords = response.userIntegralRecords || []
|
||||
response.imageUrls = response.imageUrls || []
|
||||
detailData.value = response
|
||||
console.log('赋值给detailData的数据:', detailData.value)
|
||||
} else {
|
||||
console.error('接口返回数据格式错误:', response)
|
||||
detailData.value = {
|
||||
comments: [],
|
||||
userIntegralRecords: [],
|
||||
imageUrls: []
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
ElMessage.error('获取详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片URL
|
||||
const getImageSrc = (url: string) => {
|
||||
if (!url) return ''
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url
|
||||
}
|
||||
if (url.startsWith('/')) {
|
||||
return window.location.origin + url
|
||||
}
|
||||
return window.location.origin + '/' + url
|
||||
}
|
||||
|
||||
// 预览图片
|
||||
const showImage = (url: string) => {
|
||||
imageViewerUrl.value = getImageSrc(url)
|
||||
imageViewerVisible.value = true
|
||||
}
|
||||
|
||||
// 格式化日期时间
|
||||
const formatDateTime = (dateTime: any) => {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取状态标签类型
|
||||
const getStatusTagType = (status: any) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'STAGING': 'warning',
|
||||
'AUDIT': 'primary',
|
||||
'APPROVED': 'success',
|
||||
'REVIEW_FAILED': 'danger'
|
||||
}
|
||||
return statusMap[status] || 'info'
|
||||
}
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: any) => {
|
||||
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||
return statusItem ? statusItem.text : '-'
|
||||
}
|
||||
|
||||
// 添加评论
|
||||
const addComment = async () => {
|
||||
if (!commentText.value.trim()) {
|
||||
ElMessage.warning('请输入评论内容')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
commentLoading.value = true
|
||||
await saveComment(props.reportId, {
|
||||
comment: commentText.value.trim()
|
||||
})
|
||||
ElMessage.success('评论成功')
|
||||
commentText.value = ''
|
||||
showCommentInput.value = false
|
||||
await loadDetailData(props.reportId)
|
||||
} catch (error) {
|
||||
console.error('评论失败:', error)
|
||||
ElMessage.error('评论失败')
|
||||
} finally {
|
||||
commentLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
const handleDeleteComment = async (index: number) => {
|
||||
try {
|
||||
const comment = detailData.value.comments[index]
|
||||
if (!comment) return
|
||||
|
||||
await ElMessageBox.confirm('确定删除该评论吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await deleteComment(comment.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadDetailData(props.reportId)
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.main-container {
|
||||
padding: 0px 40px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
color: #A8ABB2
|
||||
}
|
||||
|
||||
.group-warp {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.group-warp:last-child {
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
.group-container {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.group-container .user-score-detail-value {
|
||||
display: inline-block;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.group-container .image-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-container .image-container-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
margin-right: 15px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group-container .image-container-item .image-slot {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
color: var(--el-text-color-secondary);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.group-container .image-container-item .image-slot .el-icon {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.comment-container .b-white {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.comment-container .comment-list {
|
||||
margin-top: 10px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.comment-container .comment-list .card-header {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.comment-container .comment-list .comment-delete {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.comment-container .comment-list .comment-content {
|
||||
line-height: 21px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.comment-container .comment-list .comment-user-info {
|
||||
margin-top: 10px;
|
||||
color: #A8ABB2;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.comment-container .comment-list .el-card {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.comment-container .add-comment-form .dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label){
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__content){
|
||||
min-width: 200px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,512 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="filter-container">
|
||||
<el-card shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="标题">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入标题"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-select
|
||||
v-model="queryParams.deptId"
|
||||
placeholder="请选择部门"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in deptOptions"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="dept.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="涉事人">
|
||||
<el-input
|
||||
v-model="queryParams.personName"
|
||||
placeholder="请输入涉事人"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="事项">
|
||||
<el-select
|
||||
v-model="queryParams.reasonId"
|
||||
placeholder="请选择事项"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reportTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="填报时间">
|
||||
<el-date-picker
|
||||
v-model="createTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-container">
|
||||
<el-card shadow="never">
|
||||
<!-- 表格操作区域 -->
|
||||
<div class="table-header">
|
||||
<el-button
|
||||
@click="handleExport"
|
||||
:loading="exportLoading"
|
||||
type="success"
|
||||
plain
|
||||
>
|
||||
<el-icon class="mr5"><Download /></el-icon>
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 表格 -->
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="reportList"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="标题"
|
||||
prop="title"
|
||||
min-width="200"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
prop="deptName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="涉事人"
|
||||
prop="personName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="事项" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" min-width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.userIntegralRecords?.[0]?.score || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
prop="remark"
|
||||
min-width="250"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="填报时间" min-width="160" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="120"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span class="table-opera-item" @click="handleView(scope.row)" :title="'查看'">
|
||||
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<template v-if="!loading && reportList.length === 0">
|
||||
<el-empty description="暂无公司通报数据" :image-size="200" />
|
||||
</template>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情组件 -->
|
||||
<DetailComponent
|
||||
v-model:visible="detailDialogVisible"
|
||||
:report-id="selectedReportId"
|
||||
@refresh="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { View, Download } from '@element-plus/icons-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import DetailComponent from '../common/detail.vue'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import { enumsType } from '@/store/static'
|
||||
import { getMyReportPage, exportMyReport } from '@/api/submit/myReporting'
|
||||
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||
|
||||
// Store
|
||||
const deptStore = useDeptStore()
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
|
||||
// Router
|
||||
const route = useRoute()
|
||||
|
||||
// 计算属性
|
||||
const deptOptions = computed(() => deptStore.getDeptList)
|
||||
const reportTypeOptions = computed(() => rewardsPunishReasonStore.getRewardsPunishReasonList)
|
||||
|
||||
// 响应式数据
|
||||
const reportList = ref<MyReportDTO[]>([])
|
||||
const total = ref<number>(0)
|
||||
const loading = ref<boolean>(true)
|
||||
const exportLoading = ref<boolean>(false)
|
||||
|
||||
const createTimeRange = ref<string[] | null>(null)
|
||||
const detailDialogVisible = ref<boolean>(false)
|
||||
const selectedReportId = ref<string>('')
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<QueryParams>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '',
|
||||
status: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
isOwnReport: false,
|
||||
isOwnApprove: false
|
||||
})
|
||||
|
||||
// 工具函数
|
||||
const getStatusTagType = (status: string | number) => {
|
||||
const typeMap: Record<string | number, string> = {
|
||||
'STAGING': 'warning', 1: 'warning',
|
||||
'AUDIT': 'primary', 2: 'primary',
|
||||
'APPROVED': 'success', 3: 'success',
|
||||
'REVIEW_FAILED': 'danger', 4: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string | number) => {
|
||||
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||
return statusItem ? statusItem.text : '未知状态'
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTime: string | undefined) => {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const formatDisplayTime = (time: string | undefined) => {
|
||||
return time ? formatDateTime(time) : '-'
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await initData()
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 并行加载数据
|
||||
await Promise.all([
|
||||
!deptStore.getIsSetDept ? deptStore.setDeptInfoAction() : Promise.resolve(),
|
||||
!rewardsPunishReasonStore.getIsSetRewardsPunishReason ? rewardsPunishReasonStore.setRewardsPunishReasonAction() : Promise.resolve()
|
||||
])
|
||||
await getList()
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
ElMessage.error('初始化数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
queryParams.status = 'APPROVED'
|
||||
try {
|
||||
const response = await getMyReportPage(queryParams)
|
||||
reportList.value = response.list || []
|
||||
total.value = response.total || 0
|
||||
} catch (error) {
|
||||
console.error('获取公司通报列表失败:', error)
|
||||
ElMessage.error('获取公司通报列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
|
||||
// 处理填报时间范围
|
||||
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||
queryParams.startTime = createTimeRange.value[0] + 'T00:00:00'
|
||||
queryParams.endTime = createTimeRange.value[1] + 'T23:59:59'
|
||||
} else {
|
||||
queryParams.startTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '',
|
||||
status: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
isOwnReport: false,
|
||||
isOwnApprove: false
|
||||
})
|
||||
|
||||
createTimeRange.value = null
|
||||
getList()
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row: MyReportDTO) => {
|
||||
selectedReportId.value = row.id
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||
watch(
|
||||
[() => route.path, () => route.fullPath],
|
||||
() => {
|
||||
detailDialogVisible.value = false
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await exportMyReport(queryParams)
|
||||
|
||||
// 处理文件下载
|
||||
if (response instanceof Blob) {
|
||||
// 创建Blob URL
|
||||
const blob = new Blob([response], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||
})
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
|
||||
// 从响应头获取文件名,或者使用默认文件名
|
||||
const filename = getFilenameFromResponse(response) || `公司通报数据.xlsx`
|
||||
link.download = filename
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// 清理
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
} else {
|
||||
ElMessage.success('导出请求已发送')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 从响应中获取文件名
|
||||
const getFilenameFromResponse = (response: any) => {
|
||||
try {
|
||||
// 如果是Response对象,从headers中获取
|
||||
if (response.headers) {
|
||||
const contentDisposition = response.headers.get('content-disposition')
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?(.+?)\"?/i)
|
||||
if (filenameMatch && filenameMatch[1]) {
|
||||
return decodeURIComponent(filenameMatch[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取文件名失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.el-card) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
/* 操作列样式 */
|
||||
.table-opera-item {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
transition: color 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.table-opera-item:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.table-opera-icon {
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mr5 {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: #eb333d;
|
||||
}
|
||||
|
||||
.delete-icon:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,759 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="optType === 'approve' ? '审批' : 'Dialog'"
|
||||
:model-value="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="600px"
|
||||
@update:model-value="handleDialogUpdate"
|
||||
>
|
||||
<el-form size="default" @submit.prevent :model="entity" ref="formRef" :rules="rules" class="member" label-position="top">
|
||||
<!-- 基础信息区域 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="标题" class="labelTop">
|
||||
<el-input
|
||||
size="default"
|
||||
class="fw"
|
||||
v-model="entity.title"
|
||||
disabled
|
||||
placeholder="-"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部门" class="labelTop">
|
||||
<el-input
|
||||
v-model="entity.deptName"
|
||||
disabled
|
||||
placeholder="-"
|
||||
:suffix-icon="Check"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="涉事人" class="labelTop">
|
||||
<el-input
|
||||
v-model="entity.personName"
|
||||
disabled
|
||||
placeholder="-"
|
||||
:suffix-icon="Check"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="事项" class="labelTop">
|
||||
<el-input
|
||||
v-model="entity.reason.name"
|
||||
disabled
|
||||
placeholder="-"
|
||||
:suffix-icon="Check"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" class="labelTop">
|
||||
<el-input
|
||||
type="textarea"
|
||||
maxlength="1000"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
class="fw"
|
||||
v-model="entity.remark"
|
||||
disabled
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 分值设置区域 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="分值" class="labelTop new-label" required>
|
||||
<div class="score-instruction">
|
||||
<el-text size="small" type="info">请为相关人员进行分值设置(-10到10分,支持0.5分间隔)</el-text>
|
||||
</div>
|
||||
<div v-for="(item, index) in entity.userIntegralRecords" :key="index" class="res-item">
|
||||
<el-select
|
||||
v-model="item.userName"
|
||||
filterable
|
||||
placeholder="请选择用户"
|
||||
clearable
|
||||
:disabled="index === 0"
|
||||
class="user-select"
|
||||
@change="handleUserChange(index, $event)"
|
||||
>
|
||||
<el-option
|
||||
v-for="user in persons"
|
||||
:key="user.id"
|
||||
:label="user.name"
|
||||
:value="user.id"
|
||||
/>
|
||||
</el-select>
|
||||
|
||||
<!-- 带加减按钮的分值输入 -->
|
||||
<div class="score-control">
|
||||
<el-button
|
||||
@click="decreaseScore(index)"
|
||||
:disabled="item.score <= -10"
|
||||
class="score-btn decrease-btn"
|
||||
size="small"
|
||||
>
|
||||
-
|
||||
</el-button>
|
||||
<el-input
|
||||
size="default"
|
||||
class="score-input"
|
||||
v-model="item.score"
|
||||
type="number"
|
||||
:min="-10"
|
||||
:max="10"
|
||||
:step="0.5"
|
||||
@change="handleScoreChange(item, index)"
|
||||
@blur="handleScoreBlur(item, index, true)"
|
||||
/>
|
||||
<el-button
|
||||
@click="increaseScore(index)"
|
||||
:disabled="item.score >= 10"
|
||||
class="score-btn increase-btn"
|
||||
size="small"
|
||||
>
|
||||
+
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="action-icons">
|
||||
<el-icon
|
||||
v-if="index === 0"
|
||||
@click="addUserScore"
|
||||
class="action-icon add-icon"
|
||||
title="添加人员"
|
||||
>
|
||||
<CirclePlus />
|
||||
</el-icon>
|
||||
<el-icon
|
||||
v-else
|
||||
@click="removeUserScore(index)"
|
||||
class="action-icon remove-icon"
|
||||
title="移除"
|
||||
>
|
||||
<Remove />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 审批结论 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="审批结论" class="labelTop" prop="action">
|
||||
<el-select
|
||||
v-model="entity.action"
|
||||
filterable
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
class="action-select"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in approveActions"
|
||||
:key="item.code"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 审批意见 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="审批意见" class="labelTop">
|
||||
<el-input
|
||||
type="textarea"
|
||||
maxlength="500"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
class="fw"
|
||||
v-model="entity.comment"
|
||||
placeholder="请输入审批意见"
|
||||
clearable
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="cancel">取消</el-button>
|
||||
<el-button type="primary" @click="submit" :loading="submitting">
|
||||
确定
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { CirclePlus, Remove, Check } from '@element-plus/icons-vue'
|
||||
import { useUserListStore } from '@/store/modules/userList'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { MyReportDTO } from '@/api/submit/myReporting/types'
|
||||
import { approveMyReport } from '@/api/submit/myApprove'
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
dialogVisible: boolean
|
||||
optType: 'approve' | 'view'
|
||||
formData: MyReportDTO
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:dialogVisible': [value: boolean]
|
||||
success: []
|
||||
}>()
|
||||
|
||||
// Store
|
||||
const userListStore = useUserListStore()
|
||||
const deptStore = useDeptStore()
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
|
||||
// Ref
|
||||
const formRef = ref<FormInstance>()
|
||||
const submitting = ref(false)
|
||||
|
||||
// 表单数据
|
||||
const entity = reactive({
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
personId: '',
|
||||
personName: '',
|
||||
reason: {
|
||||
id: '',
|
||||
name: ''
|
||||
},
|
||||
userIntegralRecords: [{
|
||||
userId: '',
|
||||
userName: '',
|
||||
score: 0
|
||||
}],
|
||||
remark: '',
|
||||
comment: '',
|
||||
action: '',
|
||||
status: 'AUDIT'
|
||||
})
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<FormRules>({
|
||||
action: [
|
||||
{ required: true, message: '请选择审批结论', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
// 计算属性
|
||||
const persons = computed(() => {
|
||||
return userListStore.getUserList?.map(user => ({
|
||||
id: user.id,
|
||||
name: user.nickname || user.realName || user.username
|
||||
})) || []
|
||||
})
|
||||
|
||||
const deptOptions = computed(() => {
|
||||
return deptStore.getDeptList || []
|
||||
})
|
||||
|
||||
const reasonOptions = computed(() => {
|
||||
return rewardsPunishReasonStore.getRewardsPunishReasonList || []
|
||||
})
|
||||
|
||||
const approveActions = computed(() => [
|
||||
{ code: 'PASS', name: '通过' },
|
||||
{ code: 'REFUSE', name: '驳回' }
|
||||
])
|
||||
|
||||
// 增加分数
|
||||
const increaseScore = (index: number) => {
|
||||
const currentScore = entity.userIntegralRecords[index].score
|
||||
const newScore = Math.min(currentScore + 0.5, 10)
|
||||
entity.userIntegralRecords[index].score = newScore
|
||||
validateScore(entity.userIntegralRecords[index], index, true)
|
||||
}
|
||||
|
||||
// 减少分数
|
||||
const decreaseScore = (index: number) => {
|
||||
const currentScore = entity.userIntegralRecords[index].score
|
||||
const newScore = Math.max(currentScore - 0.5, -10)
|
||||
entity.userIntegralRecords[index].score = newScore
|
||||
validateScore(entity.userIntegralRecords[index], index, true)
|
||||
}
|
||||
|
||||
// 验证分值
|
||||
const validateScore = (item: any, index: number, showMessage: boolean = false) => {
|
||||
let score = item.score
|
||||
|
||||
// 确保是数字
|
||||
if (typeof score !== 'number') {
|
||||
score = parseFloat(score)
|
||||
if (isNaN(score)) score = 0
|
||||
item.score = score
|
||||
}
|
||||
|
||||
// 检查范围
|
||||
if (score < -10) {
|
||||
item.score = -10
|
||||
if (showMessage) {
|
||||
ElMessage.warning('分值不能小于-10')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (score > 10) {
|
||||
item.score = 10
|
||||
if (showMessage) {
|
||||
ElMessage.warning('分值不能大于10')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否为 0.5 的倍数
|
||||
if (score !== 0) {
|
||||
const remainder = (score * 10) % 5
|
||||
if (Math.abs(remainder) > 0.01 && Math.abs(remainder) < 4.99) {
|
||||
// 自动修正为最接近的 0.5 倍数
|
||||
const correctedScore = Math.round(score * 2) / 2
|
||||
item.score = correctedScore
|
||||
|
||||
if (showMessage) {
|
||||
ElMessage.warning({
|
||||
message: `分值必须是0.5的倍数,已自动调整为 ${correctedScore}`,
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理对话框显示状态更新
|
||||
const handleDialogUpdate = (visible: boolean) => {
|
||||
emit('update:dialogVisible', visible)
|
||||
}
|
||||
|
||||
// 添加用户分值行
|
||||
const addUserScore = () => {
|
||||
entity.userIntegralRecords.push({
|
||||
userId: '',
|
||||
userName: '',
|
||||
score: 0
|
||||
})
|
||||
}
|
||||
|
||||
// 删除用户分值行
|
||||
const removeUserScore = (index: number) => {
|
||||
if (entity.userIntegralRecords.length > 1) {
|
||||
entity.userIntegralRecords.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户选择变化处理
|
||||
const handleUserChange = (index: number, userId: string) => {
|
||||
if (userId) {
|
||||
const user = persons.value.find(u => u.id === userId)
|
||||
if (user) {
|
||||
entity.userIntegralRecords[index].userName = user.name
|
||||
entity.userIntegralRecords[index].userId = userId
|
||||
}
|
||||
} else {
|
||||
entity.userIntegralRecords[index].userName = ''
|
||||
entity.userIntegralRecords[index].userId = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 分值变化处理
|
||||
const handleScoreChange = (item: any, index: number) => {
|
||||
validateScore(item, index, false)
|
||||
}
|
||||
|
||||
// 分值失去焦点处理
|
||||
const handleScoreBlur = (item: any, index: number, showMessage: boolean = false) => {
|
||||
validateScore(item, index, showMessage)
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const cancel = () => {
|
||||
emit('update:dialogVisible', false)
|
||||
}
|
||||
|
||||
// 提交审批
|
||||
const submit = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
// 验证分值项数据
|
||||
const invalidRecords = entity.userIntegralRecords.filter(item => !item.userId)
|
||||
if (invalidRecords.length > 0) {
|
||||
ElMessage.error('请为所有分值项选择用户')
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否有重复用户
|
||||
const userIds = entity.userIntegralRecords.map(item => item.userId)
|
||||
const uniqueUserIds = [...new Set(userIds)]
|
||||
if (userIds.length !== uniqueUserIds.length) {
|
||||
ElMessage.error('请勿选择重复的用户')
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 检查分值是否是0.5的倍数
|
||||
const invalidScores = entity.userIntegralRecords.filter((item: any) => {
|
||||
if (item.score === 0 || item.score === 0.0) return false
|
||||
const remainder = Math.abs((item.score * 10) % 5)
|
||||
return remainder > 0.01 && remainder < 4.99
|
||||
})
|
||||
|
||||
if (invalidScores.length > 0) {
|
||||
ElMessage.error('分值必须是0.5的倍数')
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建请求参数
|
||||
const params = {
|
||||
action: entity.action,
|
||||
comment: entity.comment || '',
|
||||
userIntegralRecords: entity.userIntegralRecords.map(item => ({
|
||||
userId: item.userId,
|
||||
userName: item.userName,
|
||||
score: item.score
|
||||
}))
|
||||
}
|
||||
|
||||
// 调用审批接口
|
||||
await approveMyReport({ reportId: entity.id, ...params })
|
||||
|
||||
ElMessage.success('审批成功')
|
||||
emit('success')
|
||||
emit('update:dialogVisible', false)
|
||||
} catch (error: any) {
|
||||
console.error('审批失败:', error)
|
||||
ElMessage.error(error.msg || '审批失败,请重试')
|
||||
}
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听props变化
|
||||
watch(
|
||||
() => props.formData,
|
||||
(newVal) => {
|
||||
if (newVal && newVal.id) {
|
||||
// 检查部门字段
|
||||
const deptName = newVal.deptName || (newVal as any).depthName || ''
|
||||
const deptId = newVal.deptId || (newVal as any).depthId || ''
|
||||
|
||||
// 1. 设置基础字段
|
||||
Object.assign(entity, {
|
||||
id: newVal.id || '',
|
||||
title: newVal.title || '',
|
||||
deptId: deptId,
|
||||
deptName: deptName,
|
||||
personId: newVal.personId || '',
|
||||
personName: newVal.personName || '',
|
||||
remark: newVal.remark || '',
|
||||
comment: '',
|
||||
action: '',
|
||||
status: newVal.status || 'AUDIT'
|
||||
})
|
||||
|
||||
// 2. 设置事项字段
|
||||
let reasonName = ''
|
||||
let reasonId = ''
|
||||
|
||||
// 尝试从多个来源获取事项信息
|
||||
if (newVal.reason && typeof newVal.reason === 'object') {
|
||||
reasonId = newVal.reason.id || ''
|
||||
reasonName = newVal.reason.name || ''
|
||||
} else if ((newVal as any).reasonName) {
|
||||
// 备选:reasonName 字段
|
||||
reasonName = (newVal as any).reasonName
|
||||
reasonId = newVal.reasonId || ''
|
||||
} else if (newVal.reasonId) {
|
||||
reasonId = newVal.reasonId
|
||||
const reasonOption = reasonOptions.value.find(r => r.id === reasonId)
|
||||
reasonName = reasonOption?.name || ''
|
||||
}
|
||||
|
||||
entity.reason = {
|
||||
id: reasonId,
|
||||
name: reasonName || '-'
|
||||
}
|
||||
|
||||
// 3. 设置积分记录
|
||||
if (newVal.userIntegralRecords && Array.isArray(newVal.userIntegralRecords) && newVal.userIntegralRecords.length > 0) {
|
||||
// 使用原始积分记录
|
||||
entity.userIntegralRecords = newVal.userIntegralRecords.map(item => ({
|
||||
userId: item.userId || '',
|
||||
userName: item.userName || '',
|
||||
score: item.score || 0
|
||||
}))
|
||||
} else {
|
||||
// 创建默认积分记录
|
||||
entity.userIntegralRecords = [{
|
||||
userId: newVal.personId || '',
|
||||
userName: newVal.personName || '',
|
||||
score: 0
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(
|
||||
() => props.dialogVisible,
|
||||
(newVal) => {
|
||||
if (!newVal) {
|
||||
// 弹窗关闭时重置表单
|
||||
setTimeout(() => {
|
||||
Object.assign(entity, {
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
personId: '',
|
||||
personName: '',
|
||||
reason: { id: '', name: '' },
|
||||
userIntegralRecords: [{ userId: '', userName: '', score: 0 }],
|
||||
remark: '',
|
||||
comment: '',
|
||||
action: '',
|
||||
status: 'AUDIT'
|
||||
})
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
// 确保部门数据已加载
|
||||
if (!deptStore.getIsSetDept) {
|
||||
await deptStore.setDeptInfoAction()
|
||||
}
|
||||
// 确保人员数据已加载
|
||||
if (!userListStore.getIsSetUserList) {
|
||||
await userListStore.setUserListAction()
|
||||
}
|
||||
// 确保奖惩原因数据已加载
|
||||
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.member {
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.labelTop {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.labelTop :deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.fw {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.score-instruction {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.new-label {
|
||||
.res-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e4e7ed;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.user-select {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
/* 带加减按钮的分值输入 */
|
||||
.score-control {
|
||||
flex: 0 0 160px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.score-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.decrease-btn {
|
||||
color: #f56c6c;
|
||||
border-color: #fde2e2;
|
||||
background-color: #fef0f0;
|
||||
}
|
||||
|
||||
.decrease-btn:hover {
|
||||
background-color: #fde2e2;
|
||||
border-color: #f56c6c;
|
||||
}
|
||||
|
||||
.increase-btn {
|
||||
color: #67c23a;
|
||||
border-color: #e1f3d8;
|
||||
background-color: #f0f9eb;
|
||||
}
|
||||
|
||||
.increase-btn:hover {
|
||||
background-color: #e1f3d8;
|
||||
border-color: #67c23a;
|
||||
}
|
||||
|
||||
.score-input {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.score-input :deep(.el-input__inner) {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 操作图标样式 */
|
||||
.action-icons {
|
||||
flex: 0 0 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
color: #67c23a;
|
||||
background-color: #f0f9eb;
|
||||
border: 1px solid #e1f3d8;
|
||||
}
|
||||
|
||||
.add-icon:hover {
|
||||
background-color: #e1f3d8;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 2px 4px rgba(103, 194, 58, 0.2);
|
||||
}
|
||||
|
||||
.remove-icon {
|
||||
color: #f56c6c;
|
||||
background-color: #fef0f0;
|
||||
border: 1px solid #fde2e2;
|
||||
}
|
||||
|
||||
.remove-icon:hover {
|
||||
background-color: #fde2e2;
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 2px 4px rgba(245, 108, 108, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-display {
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #eee;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.status-display .el-tag {
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
:deep(.el-input.is-disabled .el-input__wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
:deep(.el-textarea.is-disabled .el-textarea__inner) {
|
||||
background-color: #f5f7fa;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,560 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-container">
|
||||
<el-card shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="标题">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-select
|
||||
v-model="queryParams.deptId"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in deptOptions"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="dept.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="涉事人">
|
||||
<el-input
|
||||
v-model="queryParams.personName"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="事项">
|
||||
<el-select
|
||||
v-model="queryParams.reasonId"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reportTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
@change="handleStatusChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reportStatus"
|
||||
:key="item.value"
|
||||
:label="item.text"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="填报时间">
|
||||
<el-date-picker
|
||||
v-model="createTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-container">
|
||||
<el-card shadow="never">
|
||||
<div class="table-header">
|
||||
<el-button @click="handleExport" :loading="exportLoading">
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="reportList"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty description="暂无审批数据" :image-size="200">
|
||||
<el-button type="primary" @click="handleQuery">刷新数据</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
<el-table-column
|
||||
label="标题"
|
||||
prop="title"
|
||||
min-width="200"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
prop="deptName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="涉事人"
|
||||
prop="personName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="事项" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
prop="remark"
|
||||
min-width="250"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="执行人"
|
||||
prop="executorName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="填报时间" min-width="160" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="审批人"
|
||||
prop="approveUserName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="审批时间"
|
||||
prop="approveTime"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="120"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span
|
||||
class="table-opera-item"
|
||||
@click="handleApprove(scope.row)"
|
||||
v-if="scope.row.status === 'AUDIT' || scope.row.status === 2 || scope.row.status === '待审批'"
|
||||
:title="'审批'"
|
||||
style="z-index: 10; position: relative;"
|
||||
>
|
||||
<el-icon class="table-opera-icon"><Notification /></el-icon>
|
||||
</span>
|
||||
<span
|
||||
class="table-opera-item ml10"
|
||||
@click="handleView(scope.row)"
|
||||
:title="'查看'"
|
||||
style="z-index: 10; position: relative;"
|
||||
>
|
||||
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 引入审批弹窗组件 -->
|
||||
<Approve
|
||||
:dialog-visible="dialogVisible"
|
||||
:opt-type="optType"
|
||||
:form-data="formData"
|
||||
@update:dialog-visible="handleDialogVisibleUpdate"
|
||||
@success="handleApproveSuccess"
|
||||
/>
|
||||
|
||||
<!-- 查看详情组件 -->
|
||||
<DetailComponent
|
||||
v-model:visible="detailDialogVisible"
|
||||
:report-id="selectedReportId"
|
||||
@refresh="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { View, Notification } from '@element-plus/icons-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import DetailComponent from '../common/detail.vue'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import Approve from './approve.vue'
|
||||
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||
import { getApproveList, exportMyReport } from '@/api/submit/myApprove'
|
||||
import { enumsType } from '@/store/static'
|
||||
|
||||
// Store
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
const deptStore = useDeptStore()
|
||||
|
||||
// Router
|
||||
const route = useRoute()
|
||||
|
||||
// 计算属性
|
||||
const deptOptions = computed(() => deptStore.getDeptList)
|
||||
const reportTypeOptions = computed(() => rewardsPunishReasonStore.getRewardsPunishReasonList)
|
||||
const reportStatus = computed(() => {
|
||||
return enumsType.reportStatus.filter(item => item.value !== 'STAGING' && item.value !== 1)
|
||||
})
|
||||
|
||||
// 响应式数据
|
||||
const reportList = ref<MyReportDTO[]>([])
|
||||
const total = ref<number>(0)
|
||||
const loading = ref<boolean>(true)
|
||||
const exportLoading = ref<boolean>(false)
|
||||
|
||||
const createTimeRange = ref<string[] | null>(null)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const detailDialogVisible = ref<boolean>(false)
|
||||
const selectedReportId = ref<string>('')
|
||||
const optType = ref<'approve'>('approve')
|
||||
const formData = ref<MyReportDTO>({
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
reasonId: '',
|
||||
reportTypeName: '',
|
||||
personName: '',
|
||||
content: '',
|
||||
remark: '',
|
||||
imageUrls: [],
|
||||
status: 'AUDIT',
|
||||
approverId: '',
|
||||
approverName: '',
|
||||
approveTime: '',
|
||||
approveComment: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
userIntegralRecords: [],
|
||||
comments: []
|
||||
})
|
||||
|
||||
// 查询参数
|
||||
const queryParams = reactive<QueryParams>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '',
|
||||
status: 'AUDIT',
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
})
|
||||
|
||||
// 处理对话框显示状态更新
|
||||
const handleDialogVisibleUpdate = (visible: boolean) => {
|
||||
dialogVisible.value = visible
|
||||
}
|
||||
|
||||
// 工具函数
|
||||
const getStatusTagType = (status: string | number) => {
|
||||
const typeMap: Record<string | number, string> = {
|
||||
AUDIT: 'primary',
|
||||
2: 'primary',
|
||||
APPROVED: 'success',
|
||||
3: 'success',
|
||||
REVIEW_FAILED: 'danger',
|
||||
4: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string | number) => {
|
||||
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||
return statusItem ? statusItem.text : '未知状态'
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTime: string | undefined) => {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const formatDisplayTime = (time: string | undefined) => {
|
||||
return time ? formatDateTime(time) : '-'
|
||||
}
|
||||
|
||||
// 状态变化处理
|
||||
const handleStatusChange = () => {
|
||||
queryParams.isOwnApprove = queryParams.status === 'AUDIT' || queryParams.status === 2 ? false : true
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await initData()
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 并行加载数据
|
||||
await Promise.all([
|
||||
!rewardsPunishReasonStore.getIsSetRewardsPunishReason
|
||||
? rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||
: Promise.resolve(),
|
||||
!deptStore.getIsSetDept
|
||||
? deptStore.setDeptInfoAction()
|
||||
: Promise.resolve()
|
||||
])
|
||||
|
||||
await getList()
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
ElMessage.error('初始化数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理审批成功回调
|
||||
const handleApproveSuccess = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getApproveList(queryParams)
|
||||
reportList.value = response.list || []
|
||||
total.value = response.total || 0
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
ElMessage.error('获取列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
|
||||
// 处理填报时间范围
|
||||
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||
queryParams.startTime = `${createTimeRange.value[0]}T00:00:00`
|
||||
queryParams.endTime = `${createTimeRange.value[1]}T23:59:59`
|
||||
} else {
|
||||
queryParams.startTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '',
|
||||
status: 'AUDIT',
|
||||
startTime: undefined,
|
||||
endTime: undefined
|
||||
})
|
||||
|
||||
createTimeRange.value = null
|
||||
getList()
|
||||
}
|
||||
|
||||
// 审批 - 修复后的函数
|
||||
const handleApprove = (row: MyReportDTO) => {
|
||||
|
||||
optType.value = 'approve'
|
||||
|
||||
// 修复:确保传递的数据包含所有必要字段
|
||||
formData.value = row
|
||||
|
||||
console.log('传递给弹窗的数据:', formData.value)
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row: MyReportDTO) => {
|
||||
selectedReportId.value = row.id
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||
watch(
|
||||
[() => route.path, () => route.fullPath],
|
||||
() => {
|
||||
detailDialogVisible.value = false
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await exportMyReport(queryParams)
|
||||
|
||||
// 处理文件下载
|
||||
if (response instanceof Blob) {
|
||||
const blob = new Blob([response], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||
})
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
const filename = '审批数据.xlsx'
|
||||
link.download = filename
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
} else {
|
||||
ElMessage.success('导出请求已发送')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.table-opera-item {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
transition: color 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.table-opera-item:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.table-opera-icon {
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/* 使用 :deep() 简化样式 */
|
||||
:deep(.el-card) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,478 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
:title="dialogTitle"
|
||||
v-model="visible"
|
||||
width="800px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="标题:" prop="title">
|
||||
<el-input
|
||||
v-model="form.title"
|
||||
placeholder="请输入标题"
|
||||
style="width: 100%"
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="部门:" prop="deptId">
|
||||
<el-select
|
||||
v-model="form.deptId"
|
||||
placeholder="请选择部门"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
@change="handleDeptChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in deptOptions"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="String(dept.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="涉事人:" prop="personId">
|
||||
<el-select
|
||||
v-model="form.personId"
|
||||
placeholder="请选择涉事人"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
@change="handlePersonChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in personOptions"
|
||||
:key="item.id"
|
||||
:label="item.nickname"
|
||||
:value="String(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="事项:" prop="reasonId">
|
||||
<el-select
|
||||
v-model="form.reasonId"
|
||||
placeholder="请选择事项"
|
||||
style="width: 100%"
|
||||
@change="handleReasonChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reportTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="分值:" prop="score">
|
||||
<el-input-number
|
||||
v-if="showScoreInput"
|
||||
v-model="form.userIntegralRecords[0].score"
|
||||
:min="-10"
|
||||
:max="10"
|
||||
:precision="1"
|
||||
:step="0.5"
|
||||
placeholder="请输入分值"
|
||||
style="width: 100%"
|
||||
:disabled="scoreDisabled"
|
||||
/>
|
||||
<el-select
|
||||
v-if="showScoreSelect"
|
||||
v-model="form.userIntegralRecords[0].score"
|
||||
placeholder="请选择分值"
|
||||
style="width: 100%"
|
||||
:disabled="scoreDisabled"
|
||||
>
|
||||
<el-option
|
||||
v-for="score in ruleScore.scores"
|
||||
:key="score"
|
||||
:label="score"
|
||||
:value="score"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="图片:">
|
||||
<UploadImgs
|
||||
v-model="form.imageUrls"
|
||||
:limit="5"
|
||||
:file-size="10"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注:">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
:rows="3"
|
||||
resize="none"
|
||||
style="width: 100%"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button
|
||||
@click="handleClose"
|
||||
size="large"
|
||||
style="width: 100px;"
|
||||
:disabled="submitLoading"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitForm"
|
||||
size="large"
|
||||
style="width: 100px;"
|
||||
:loading="submitLoading"
|
||||
>
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import { UploadImgs } from '@/components/UploadFile'
|
||||
import type { MyReportDTO, SaveParams, UserIntegralRecord, RuleScoreResponse } from '@/api/submit/myReporting/types'
|
||||
import { createMyReport, updateMyReport, getRuleScore } from '@/api/submit/myReporting'
|
||||
import { getUserPage } from '@/api/system/user'
|
||||
import { enumsType } from '@/store/static'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
dialogVisible: boolean
|
||||
optType: 'add' | 'edit'
|
||||
formData?: MyReportDTO
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
dialogVisible: false,
|
||||
optType: 'add',
|
||||
formData: () => ({})
|
||||
})
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:dialogVisible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
// Store
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
const deptStore = useDeptStore()
|
||||
|
||||
// 计算属性
|
||||
const deptOptions = computed(() => deptStore.getDeptList)
|
||||
|
||||
// 响应式数据
|
||||
const submitLoading = ref<boolean>(false)
|
||||
const personLoading = ref<boolean>(false)
|
||||
const dialogTitle = ref<string>('新增上报')
|
||||
const formRef = ref<FormInstance>()
|
||||
const visible = ref<boolean>(props.dialogVisible)
|
||||
|
||||
// 监听弹窗显示状态变化
|
||||
watch(
|
||||
() => visible.value,
|
||||
(newVal) => {
|
||||
emit('update:dialogVisible', newVal)
|
||||
}
|
||||
)
|
||||
|
||||
// 监听父组件传入的dialogVisible变化
|
||||
watch(
|
||||
() => props.dialogVisible,
|
||||
(newVal) => {
|
||||
visible.value = newVal
|
||||
}
|
||||
)
|
||||
|
||||
const personOptions = ref<any[]>([])
|
||||
const reportTypeOptions = ref<any[]>([])
|
||||
|
||||
// 分值相关状态
|
||||
const ruleScore = ref<RuleScoreResponse>({ type: '', scores: [] })
|
||||
const showScoreInput = ref(true)
|
||||
const showScoreSelect = ref(false)
|
||||
const scoreDisabled = ref(true)
|
||||
|
||||
// 表单初始值
|
||||
const getInitialForm = (): SaveParams => ({
|
||||
id: undefined,
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
reasonId: '',
|
||||
personName: '',
|
||||
personId: '',
|
||||
remark: '',
|
||||
content: '',
|
||||
imageUrls: [],
|
||||
userIntegralRecords: [{
|
||||
userId: '',
|
||||
userName: '',
|
||||
score: null
|
||||
}],
|
||||
comments: []
|
||||
})
|
||||
|
||||
const form = reactive<SaveParams>(getInitialForm())
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<FormRules>({
|
||||
title: [
|
||||
{ required: true, message: '标题不能为空', trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: '标题长度在 1 到 50 个字符', trigger: 'blur' }
|
||||
],
|
||||
deptId: [{ required: true, message: '部门不能为空', trigger: 'change' }],
|
||||
reasonId: [{ required: true, message: '事项不能为空', trigger: 'change' }],
|
||||
personId: [{ required: true, message: '涉事人不能为空', trigger: 'change' }],
|
||||
'userIntegralRecords[0].score': [{ required: true, message: '分值不能为空', trigger: 'blur' }]
|
||||
})
|
||||
|
||||
// 监听弹窗显示状态
|
||||
watch(visible, (newVal) => {
|
||||
if (newVal) {
|
||||
dialogTitle.value = props.optType === 'add' ? '新增上报' : '编辑上报'
|
||||
|
||||
// 加载事项类型
|
||||
loadReportTypes()
|
||||
|
||||
if (props.optType === 'edit' && props.formData) {
|
||||
// 编辑模式下填充表单数据
|
||||
// 确保userIntegralRecords只保留一条记录
|
||||
const integralRecord = props.formData.userIntegralRecords && props.formData.userIntegralRecords.length > 0
|
||||
? props.formData.userIntegralRecords[0]
|
||||
: { userId: '', userName: '', score: null }
|
||||
|
||||
// 过滤评论数据,只保留后端需要的字段
|
||||
const filteredComments = (props.formData.comments || []).map(comment => ({
|
||||
comment: comment.comment,
|
||||
createUserName: comment.createUserName
|
||||
}));
|
||||
|
||||
Object.assign(form, {
|
||||
id: props.formData.id,
|
||||
title: props.formData.title,
|
||||
deptId: String(props.formData.deptId),
|
||||
deptName: props.formData.deptName,
|
||||
reasonId: String(props.formData.reportType || props.formData.reasonId),
|
||||
personName: props.formData.personName,
|
||||
personId: String(props.formData.personId),
|
||||
remark: props.formData.remark,
|
||||
imageUrls: props.formData.imageUrls || [],
|
||||
userIntegralRecords: [integralRecord],
|
||||
comments: filteredComments
|
||||
})
|
||||
|
||||
// 加载部门用户
|
||||
if (form.deptId) {
|
||||
loadPersonOptions(form.deptId)
|
||||
}
|
||||
} else {
|
||||
// 新增模式下重置表单
|
||||
Object.assign(form, getInitialForm())
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
// 加载事项类型
|
||||
const loadReportTypes = async () => {
|
||||
try {
|
||||
// 确保数据已加载
|
||||
if (!rewardsPunishReasonStore.getIsSetRewardsPunishReason) {
|
||||
await rewardsPunishReasonStore.setRewardsPunishReasonAction()
|
||||
}
|
||||
reportTypeOptions.value = rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||
} catch (error) {
|
||||
console.error('加载事项类型失败:', error)
|
||||
reportTypeOptions.value = []
|
||||
}
|
||||
}
|
||||
|
||||
// 加载部门用户
|
||||
const loadPersonOptions = async (deptId: string | number) => {
|
||||
if (!deptId) {
|
||||
personOptions.value = []
|
||||
return
|
||||
}
|
||||
|
||||
personLoading.value = true
|
||||
try {
|
||||
const response = await getUserPage({
|
||||
pageNo: 1,
|
||||
pageSize: 100,
|
||||
deptId
|
||||
})
|
||||
personOptions.value = response.list || []
|
||||
} catch (error) {
|
||||
console.error('获取部门用户失败:', error)
|
||||
ElMessage.error('获取部门用户失败')
|
||||
personOptions.value = []
|
||||
} finally {
|
||||
personLoading.value = false
|
||||
|
||||
// 加载完成后,检查是否有已选的涉事人,如果有则自动填充名称
|
||||
if (form.personId) {
|
||||
const selectedPerson = personOptions.value.find(item => String(item.id) === form.personId)
|
||||
if (selectedPerson) {
|
||||
form.personName = selectedPerson.nickname
|
||||
form.userIntegralRecords[0].userId = Number(form.personId!) || ''
|
||||
form.userIntegralRecords[0].userName = form.personName
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 加载分值规则
|
||||
const loadScore = async () => {
|
||||
if (!form.personId || !form.reasonId) {
|
||||
return
|
||||
}
|
||||
|
||||
scoreDisabled.value = true
|
||||
try {
|
||||
const response = await getRuleScore(form.reasonId, form.personId)
|
||||
ruleScore.value = response
|
||||
|
||||
switch (ruleScore.value.type) {
|
||||
case 'MANUAL_VALUE':
|
||||
form.userIntegralRecords[0].score = null
|
||||
showScoreSelect.value = false
|
||||
showScoreInput.value = true
|
||||
break
|
||||
case 'FIXED_VALUE':
|
||||
case 'CALC_VALUE':
|
||||
showScoreSelect.value = false
|
||||
showScoreInput.value = true
|
||||
form.userIntegralRecords[0].score = ruleScore.value.scores[0] || null
|
||||
break
|
||||
case 'CHOOSE_VALUE':
|
||||
showScoreSelect.value = true
|
||||
showScoreInput.value = false
|
||||
form.userIntegralRecords[0].score = null
|
||||
break
|
||||
default:
|
||||
showScoreSelect.value = false
|
||||
showScoreInput.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分值规则失败:', error)
|
||||
form.userIntegralRecords[0].score = null
|
||||
showScoreSelect.value = false
|
||||
showScoreInput.value = true
|
||||
} finally {
|
||||
scoreDisabled.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 涉事人变更处理
|
||||
const handlePersonChange = () => {
|
||||
const selectedPerson = personOptions.value.find(item => String(item.id) === form.personId)
|
||||
if (selectedPerson) {
|
||||
form.personName = selectedPerson.nickname
|
||||
form.userIntegralRecords[0].userId = Number(form.personId!) || ''
|
||||
form.userIntegralRecords[0].userName = form.personName
|
||||
}
|
||||
loadScore()
|
||||
}
|
||||
|
||||
// 部门变更处理
|
||||
const handleDeptChange = async () => {
|
||||
// 清除已选涉事人
|
||||
form.personId = ''
|
||||
form.personName = ''
|
||||
form.userIntegralRecords[0].userId = ''
|
||||
form.userIntegralRecords[0].userName = ''
|
||||
|
||||
// 填充部门名称
|
||||
const selectedDept = deptOptions.value.find(dept => String(dept.id) === form.deptId)
|
||||
if (selectedDept) {
|
||||
form.deptName = selectedDept.name
|
||||
} else {
|
||||
form.deptName = ''
|
||||
}
|
||||
|
||||
await loadPersonOptions(form.deptId)
|
||||
loadScore()
|
||||
}
|
||||
|
||||
// 事项变更处理
|
||||
const handleReasonChange = () => {
|
||||
loadScore()
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
await formRef.value.validate(async (valid) => {
|
||||
if (valid) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
if (props.optType === 'add') {
|
||||
await createMyReport(form)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
await updateMyReport(form)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
emit('success')
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error(error.msg || '操作失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
|
@ -0,0 +1,700 @@
|
|||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 筛选区域 -->
|
||||
<div class="filter-container">
|
||||
<el-card shadow="never">
|
||||
<el-form :inline="true" :model="queryParams" class="demo-form-inline">
|
||||
<el-form-item label="标题">
|
||||
<el-input
|
||||
v-model="queryParams.title"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-select
|
||||
v-model="queryParams.deptId"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="dept in deptOptions"
|
||||
:key="dept.id"
|
||||
:label="dept.name"
|
||||
:value="dept.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="涉事人">
|
||||
<el-input
|
||||
v-model="queryParams.personName"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="事项">
|
||||
<el-select
|
||||
v-model="queryParams.reasonId"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in reportTypeOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-select
|
||||
v-model="queryParams.status"
|
||||
placeholder="请选择"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in enumsType.reportStatus"
|
||||
:key="item.value"
|
||||
:label="item.text"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="填报时间">
|
||||
<el-date-picker
|
||||
v-model="createTimeRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleQuery" :loading="loading">
|
||||
查询
|
||||
</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 表格区域 -->
|
||||
<div class="table-container">
|
||||
<el-card shadow="never">
|
||||
<div class="table-header">
|
||||
<el-button type="primary" @click="handleAdd">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button @click="handleExport" :loading="exportLoading">
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="reportList"
|
||||
:header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-table-column
|
||||
label="标题"
|
||||
prop="title"
|
||||
min-width="200"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
label="部门"
|
||||
prop="deptName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="涉事人"
|
||||
prop="personName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column label="事项" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ row.reportTypeName || row.reason?.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分值" min-width="80" align="center">
|
||||
<template #default="scope">
|
||||
{{ scope.row.userIntegralRecords?.[0]?.score || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="备注"
|
||||
prop="remark"
|
||||
min-width="250"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="scope">
|
||||
{{ scope.row.remark || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="填报时间" min-width="160" align="center">
|
||||
<template #default="scope">
|
||||
{{ formatDisplayTime(scope.row.fillingTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" min-width="100" align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">
|
||||
{{ getStatusText(scope.row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="审批人"
|
||||
prop="approverName"
|
||||
min-width="120"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="审批时间"
|
||||
prop="approveTime"
|
||||
min-width="160"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="120"
|
||||
align="center"
|
||||
fixed="right"
|
||||
>
|
||||
<template #default="scope">
|
||||
<span class="table-opera-item" @click="handleView(scope.row)" :title="'查看'">
|
||||
<el-icon class="table-opera-icon"><View /></el-icon>
|
||||
</span>
|
||||
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleEdit(scope.row)" :title="'编辑'">
|
||||
<el-icon class="table-opera-icon"><Edit /></el-icon>
|
||||
</span>
|
||||
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleCommit(scope.row.id)" :title="'提交'">
|
||||
<el-icon class="table-opera-icon" :class="{ 'loading-icon': commitLoading === scope.row.id }">
|
||||
<UploadFilled /></el-icon>
|
||||
</span>
|
||||
<span v-if="scope.row.status === 1 || scope.row.status === 'STAGING'" class="table-opera-item ml10" @click="handleDelete(scope.row.id)" :title="'删除'">
|
||||
<el-icon class="table-opera-icon delete-icon"><Delete /></el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<template v-if="!loading && reportList.length === 0">
|
||||
<el-empty description="暂无上报数据" :image-size="200">
|
||||
<el-button type="primary" @click="handleAdd">新增上报</el-button>
|
||||
</el-empty>
|
||||
</template>
|
||||
|
||||
<pagination
|
||||
v-show="total > 0"
|
||||
:total="total"
|
||||
v-model:page="queryParams.pageNo"
|
||||
v-model:limit="queryParams.pageSize"
|
||||
@pagination="getList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 引入新增/编辑弹窗组件 -->
|
||||
<AddReport
|
||||
v-model:dialogVisible="dialogVisible"
|
||||
:optType="optType"
|
||||
:formData="formData"
|
||||
@success="handleAddEditSuccess"
|
||||
/>
|
||||
|
||||
<!-- 查看详情组件 -->
|
||||
<DetailComponent
|
||||
v-model:visible="detailDialogVisible"
|
||||
:report-id="selectedReportId"
|
||||
@refresh="getList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref, computed, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { View, Edit, UploadFilled, Delete } from '@element-plus/icons-vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import DetailComponent from '../common/detail.vue'
|
||||
import { useRewardsPunishReasonStore } from '@/store/modules/rewardsPunishReason'
|
||||
import { useDeptStore } from '@/store/modules/dept'
|
||||
import Pagination from '@/components/Pagination/index.vue'
|
||||
import AddReport from './add-report.vue'
|
||||
import type { MyReportDTO, QueryParams } from '@/api/submit/myReporting/types'
|
||||
import { getMyReportPage, deleteMyReport, exportMyReport, commitMyReport } from '@/api/submit/myReporting'
|
||||
import { getMyReportDetail } from '@/api/submit/common/common'
|
||||
import { enumsType } from '@/store/static'
|
||||
|
||||
// Store
|
||||
const rewardsPunishReasonStore = useRewardsPunishReasonStore()
|
||||
const deptStore = useDeptStore()
|
||||
|
||||
// Router
|
||||
const route = useRoute()
|
||||
|
||||
// 计算属性
|
||||
const deptOptions = computed(() => deptStore.getDeptList)
|
||||
|
||||
// 响应式数据
|
||||
const reportList = ref<MyReportDTO[]>([])
|
||||
const reportTypeOptions = ref<any[]>([])
|
||||
const total = ref<number>(0)
|
||||
const loading = ref<boolean>(true)
|
||||
const exportLoading = ref<boolean>(false)
|
||||
const commitLoading = ref<string>('')
|
||||
|
||||
const createTimeRange = ref<string[] | null>(null)
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const detailDialogVisible = ref<boolean>(false)
|
||||
const selectedReportId = ref<string>('')
|
||||
const optType = ref<'add' | 'edit'>('add')
|
||||
const formData = ref<MyReportDTO>({
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
reasonId: '',
|
||||
reportTypeName: '',
|
||||
personName: '',
|
||||
content: '',
|
||||
remark: '',
|
||||
imageUrls: [],
|
||||
status: 1,
|
||||
approverId: '',
|
||||
approverName: '',
|
||||
approveTime: '',
|
||||
approveComment: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
userIntegralRecords: [],
|
||||
comments: []
|
||||
})
|
||||
|
||||
// 查询参数 - 搜索时使用 reasonId
|
||||
const queryParams = reactive<QueryParams>({
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '', // 搜索时使用 reasonId
|
||||
status: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
isOwnReport: true
|
||||
})
|
||||
|
||||
// 工具函数
|
||||
const getStatusTagType = (status: string | number) => {
|
||||
const typeMap: Record<string | number, string> = {
|
||||
'STAGING': 'warning', 1: 'warning',
|
||||
'AUDIT': 'primary', 2: 'primary',
|
||||
'APPROVED': 'success', 3: 'success',
|
||||
'REVIEW_FAILED': 'danger', 4: 'danger'
|
||||
}
|
||||
return typeMap[status] || 'info'
|
||||
}
|
||||
|
||||
const getStatusText = (status: string | number) => {
|
||||
const statusItem = enumsType.reportStatus.find(item => item.value === status)
|
||||
return statusItem ? statusItem.text : '未知状态'
|
||||
}
|
||||
|
||||
const formatDateTime = (dateTime: string | undefined) => {
|
||||
if (!dateTime) return ''
|
||||
const date = new Date(dateTime)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
const formatDisplayTime = (time: string | undefined) => {
|
||||
return time ? formatDateTime(time) : '-'
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(async () => {
|
||||
await initData()
|
||||
})
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
try {
|
||||
// 并行加载数据
|
||||
await Promise.all([
|
||||
!rewardsPunishReasonStore.getIsSetRewardsPunishReason ? rewardsPunishReasonStore.setRewardsPunishReasonAction() : Promise.resolve(),
|
||||
!deptStore.getIsSetDept ? deptStore.setDeptInfoAction() : Promise.resolve()
|
||||
])
|
||||
|
||||
// 加载事项类型
|
||||
reportTypeOptions.value = rewardsPunishReasonStore.getRewardsPunishReasonList
|
||||
|
||||
await getList()
|
||||
} catch (error) {
|
||||
console.error('初始化数据失败:', error)
|
||||
ElMessage.error('初始化数据失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理新增/编辑成功回调
|
||||
const handleAddEditSuccess = () => {
|
||||
getList()
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getMyReportPage(queryParams)
|
||||
reportList.value = response.list || []
|
||||
total.value = response.total || 0
|
||||
} catch (error) {
|
||||
console.error('获取列表失败:', error)
|
||||
ElMessage.error('获取列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 查询
|
||||
const handleQuery = () => {
|
||||
queryParams.pageNo = 1
|
||||
|
||||
// 处理填报时间范围
|
||||
if (createTimeRange.value && createTimeRange.value.length === 2) {
|
||||
queryParams.startTime = createTimeRange.value[0] + 'T00:00:00'
|
||||
queryParams.endTime = createTimeRange.value[1] + 'T23:59:59'
|
||||
} else {
|
||||
queryParams.startTime = undefined
|
||||
queryParams.endTime = undefined
|
||||
}
|
||||
|
||||
getList()
|
||||
}
|
||||
|
||||
// 重置查询
|
||||
const resetQuery = () => {
|
||||
Object.assign(queryParams, {
|
||||
pageNo: 1,
|
||||
pageSize: 10,
|
||||
title: '',
|
||||
deptId: '',
|
||||
personName: '',
|
||||
reasonId: '', // 重置 reasonId
|
||||
status: '',
|
||||
startTime: undefined,
|
||||
endTime: undefined,
|
||||
isOwnReport: true
|
||||
})
|
||||
|
||||
createTimeRange.value = null
|
||||
getList()
|
||||
}
|
||||
|
||||
// 新增
|
||||
const handleAdd = () => {
|
||||
optType.value = 'add'
|
||||
// 重置formData为初始值
|
||||
formData.value = {
|
||||
id: '',
|
||||
title: '',
|
||||
deptId: '',
|
||||
deptName: '',
|
||||
reasonId: '',
|
||||
reportTypeName: '',
|
||||
personName: '',
|
||||
content: '',
|
||||
remark: '',
|
||||
imageUrls: [],
|
||||
status: 1,
|
||||
approverId: '',
|
||||
approverName: '',
|
||||
approveTime: '',
|
||||
approveComment: '',
|
||||
createBy: '',
|
||||
createTime: '',
|
||||
userIntegralRecords: [],
|
||||
comments: []
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
// 修改
|
||||
const handleEdit = async (row: MyReportDTO) => {
|
||||
try {
|
||||
// 获取完整的详情数据,包括comments字段
|
||||
const response = await getMyReportDetail(row.id)
|
||||
optType.value = 'edit'
|
||||
formData.value = response
|
||||
dialogVisible.value = true
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error)
|
||||
ElMessage.error('获取详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleView = (row: MyReportDTO) => {
|
||||
selectedReportId.value = row.id
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 监听路由变化,当路由发生变化时关闭抽屉
|
||||
watch(
|
||||
[() => route.path, () => route.fullPath],
|
||||
() => {
|
||||
detailDialogVisible.value = false
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 删除
|
||||
const handleDelete = async (id: string) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定删除该上报记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
await deleteMyReport(id)
|
||||
ElMessage.success('删除成功')
|
||||
getList()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('删除失败:', error)
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 提交上报
|
||||
const handleCommit = async (id: string) => {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定提交该上报记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
})
|
||||
|
||||
commitLoading.value = id
|
||||
await commitMyReport(id)
|
||||
ElMessage.success('提交成功')
|
||||
getList()
|
||||
} catch (error) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('提交失败:', error)
|
||||
ElMessage.error('提交失败')
|
||||
}
|
||||
} finally {
|
||||
commitLoading.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 导出
|
||||
// 导出
|
||||
const handleExport = async () => {
|
||||
exportLoading.value = true
|
||||
try {
|
||||
const response = await exportMyReport(queryParams)
|
||||
|
||||
// 处理文件下载
|
||||
if (response instanceof Blob) {
|
||||
// 创建Blob URL
|
||||
const blob = new Blob([response], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8'
|
||||
})
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
|
||||
// 创建下载链接
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
|
||||
// 从响应头获取文件名,或者使用默认文件名
|
||||
const filename = getFilenameFromResponse(response) || `上报数据.xlsx`
|
||||
link.download = filename
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
|
||||
// 清理
|
||||
document.body.removeChild(link)
|
||||
window.URL.revokeObjectURL(url)
|
||||
|
||||
ElMessage.success('导出成功')
|
||||
} else {
|
||||
ElMessage.success('导出请求已发送')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败')
|
||||
} finally {
|
||||
exportLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 从响应中获取文件名
|
||||
const getFilenameFromResponse = (response: any) => {
|
||||
try {
|
||||
// 如果是Response对象,从headers中获取
|
||||
if (response.headers) {
|
||||
const contentDisposition = response.headers.get('content-disposition')
|
||||
if (contentDisposition) {
|
||||
const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i)
|
||||
if (filenameMatch && filenameMatch[1]) {
|
||||
return decodeURIComponent(filenameMatch[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('获取文件名失败:', e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.table-header {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-card) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-card__body) {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.image-preview {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.comments-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.comment-item {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.comment-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.comment-content {
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 操作列样式 */
|
||||
.table-opera-item {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
transition: color 0.3s;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.table-opera-item:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
|
||||
.table-opera-icon {
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.delete-icon {
|
||||
color: #eb333d;
|
||||
}
|
||||
|
||||
.delete-icon:hover {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
title="导入数据"
|
||||
v-model="showDialog"
|
||||
:before-close="handleClose"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form
|
||||
size="default"
|
||||
@submit.prevent
|
||||
:model="entity"
|
||||
ref="addForm"
|
||||
:rules="rules"
|
||||
class="member"
|
||||
label-position="left"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="月份" class="labelTop" prop="yearMonth">
|
||||
<el-date-picker
|
||||
v-model="entity.yearMonth"
|
||||
type="month"
|
||||
placeholder="请选择..."
|
||||
value-format="YYYY-MM"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="文件" class="labelTop" prop="file">
|
||||
<el-upload
|
||||
ref="uploadFileRef"
|
||||
:limit="1"
|
||||
:auto-upload="false"
|
||||
drag
|
||||
action="none"
|
||||
accept=".xlsx"
|
||||
:on-exceed="exceedFile"
|
||||
:file-list="fileList"
|
||||
:show-file-list="true"
|
||||
:on-remove="handleRemove"
|
||||
:on-change="handleChange"
|
||||
>
|
||||
<el-icon class="el-icon--upload">
|
||||
<UploadFilled />
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
Drop file here or <em>click to upload</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
<el-link type="primary" @click="downloadTemplate" target="_blank">
|
||||
<template #default>
|
||||
<el-icon>
|
||||
<Download />
|
||||
</el-icon>
|
||||
下载模板
|
||||
</template>
|
||||
</el-link>
|
||||
,仅支持 .xlsx 格式文件
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer v-if="optType !== 'view'">
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="save">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, watch } from 'vue'
|
||||
import { ElMessage, ElLoading } from 'element-plus'
|
||||
import { UploadFilled, Download } from '@element-plus/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { importPaySlip, downloadImportTemplate } from '@/api/submit/paySlip'
|
||||
|
||||
const props = defineProps({
|
||||
dialogVisible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
optType: {
|
||||
type: String,
|
||||
default: 'import'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['handle-close'])
|
||||
|
||||
const { t: $t } = useI18n()
|
||||
|
||||
const showDialog = computed({
|
||||
get: () => props.dialogVisible,
|
||||
set: (val) => emit('handle-close', val)
|
||||
})
|
||||
|
||||
const addForm = ref()
|
||||
const uploadFileRef = ref()
|
||||
|
||||
const entity = reactive({
|
||||
yearMonth: '',
|
||||
file: ''
|
||||
})
|
||||
|
||||
const fileList = ref([])
|
||||
|
||||
const rules = {
|
||||
yearMonth: [
|
||||
{ required: true, message: '请选择月份', trigger: 'change' }
|
||||
],
|
||||
file: [
|
||||
{ required: true, message: '请选择文件', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
const exceedFile = () => {
|
||||
ElMessage.warning('只能上传一个文件')
|
||||
}
|
||||
|
||||
const handleRemove = (file) => {
|
||||
fileList.value = []
|
||||
entity.file = ''
|
||||
}
|
||||
|
||||
const handleChange = (file) => {
|
||||
fileList.value = [file]
|
||||
entity.file = file.raw
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
showDialog.value = false
|
||||
resetForm()
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
if (addForm.value) {
|
||||
addForm.value.resetFields()
|
||||
}
|
||||
fileList.value = []
|
||||
entity.file = ''
|
||||
}
|
||||
|
||||
const downloadTemplate = () => {
|
||||
downloadImportTemplate().then((res) => {
|
||||
const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = '工资条导入模板.xlsx'
|
||||
link.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
})
|
||||
}
|
||||
|
||||
const save = () => {
|
||||
addForm.value.validate((valid) => {
|
||||
if (valid) {
|
||||
if (!entity.file) {
|
||||
ElMessage.warning('请选择文件')
|
||||
return
|
||||
}
|
||||
const loading = ElLoading.service({
|
||||
lock: true,
|
||||
text: '导入中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
const formData = new FormData()
|
||||
formData.append('file', entity.file)
|
||||
// 将yearMonth拆分成year和month两个字段
|
||||
if (entity.yearMonth) {
|
||||
const [year, month] = entity.yearMonth.split('-')
|
||||
formData.append('year', year)
|
||||
formData.append('month', month)
|
||||
}
|
||||
importPaySlip(formData).then(() => {
|
||||
loading.close()
|
||||
ElMessage.success('导入成功')
|
||||
showDialog.value = false
|
||||
resetForm()
|
||||
emit('handle-close')
|
||||
}).catch(() => {
|
||||
loading.close()
|
||||
ElMessage.error('导入失败')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dialog-footer {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.member {
|
||||
padding: 20px;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.labelTop {
|
||||
.el-form-item__label {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,538 @@
|
|||
<template>
|
||||
<div class="manage-container">
|
||||
<div class="wrap">
|
||||
<el-form
|
||||
class="wrap b-white top"
|
||||
:model="search"
|
||||
ref="searchForm"
|
||||
label-width="120px"
|
||||
label-position="right"
|
||||
size="default"
|
||||
>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="月份">
|
||||
<el-date-picker
|
||||
v-model="search.yearMonth"
|
||||
type="month"
|
||||
placeholder="请选择..."
|
||||
value-format="YYYY-MM"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="工号">
|
||||
<el-input
|
||||
v-model="search.employeeId"
|
||||
:maxlength="20"
|
||||
auto-complete="off"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="手机号">
|
||||
<el-input
|
||||
v-model="search.mobileNo"
|
||||
:maxlength="30"
|
||||
auto-complete="off"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="姓名">
|
||||
<el-input
|
||||
v-model="search.employeeName"
|
||||
:maxlength="30"
|
||||
auto-complete="off"
|
||||
placeholder="请输入"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="已发短信">
|
||||
<el-select v-model="search.msmSend" placeholder="请选择..." clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in yesOrNoList"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="4">
|
||||
<el-form-item class="label1" label="已签署">
|
||||
<el-select v-model="search.signed" placeholder="请选择..." clearable>
|
||||
<el-option
|
||||
v-for="(item, index) in yesOrNoList"
|
||||
:key="index"
|
||||
:label="item.name"
|
||||
:value="item.code"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="6" :offset="14" class="mt4">
|
||||
<el-button size="small" type="primary" @click="searchFun">
|
||||
<el-icon class="btn-icon">
|
||||
<Search />
|
||||
</el-icon>
|
||||
查询
|
||||
</el-button>
|
||||
<el-button size="small" type @click="reset">
|
||||
<el-icon class="btn-icon">
|
||||
<RefreshLeft />
|
||||
</el-icon>
|
||||
重置
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="wrap">
|
||||
<div class="table-content">
|
||||
<div class="b-white">
|
||||
<el-button type="primary" size="small" @click="importData">
|
||||
<el-icon class="btn-icon">
|
||||
<UploadFilled />
|
||||
</el-icon>
|
||||
导入
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="ml10"
|
||||
@click="batchSend"
|
||||
:disabled="!canBatchSend"
|
||||
>
|
||||
<el-icon class="btn-icon">
|
||||
<Message />
|
||||
</el-icon>
|
||||
批量发送
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
border
|
||||
stripe
|
||||
size="default"
|
||||
ref="table"
|
||||
class="mt16"
|
||||
v-loading="listLoading"
|
||||
:data="tableListData"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<template #empty>
|
||||
<el-empty description="暂无数据" />
|
||||
</template>
|
||||
<el-table-column type="selection" width="55" :selectable="isRowSelectable" />
|
||||
<el-table-column show-overflow-tooltip label="月份" prop="fullYearMonth" />
|
||||
<el-table-column show-overflow-tooltip label="工号" prop="employeeId" />
|
||||
<el-table-column
|
||||
show-overflow-tooltip
|
||||
label="手机号"
|
||||
prop="mobileNo"
|
||||
min-width="100px"
|
||||
/>
|
||||
<el-table-column show-overflow-tooltip label="姓名" prop="employeeName" />
|
||||
<el-table-column show-overflow-tooltip label="工资" prop="salary" />
|
||||
<el-table-column show-overflow-tooltip label="出勤天数" prop="attendanceDays" />
|
||||
<el-table-column show-overflow-tooltip label="出勤工资" prop="attendanceSalary" />
|
||||
<el-table-column show-overflow-tooltip label="通讯费" prop="communicationFees" />
|
||||
<el-table-column show-overflow-tooltip label="加班费" prop="overtimePay" />
|
||||
<el-table-column show-overflow-tooltip label="岗位补贴" prop="jobSubsidies" />
|
||||
<el-table-column show-overflow-tooltip label="全勤奖" prop="fullAttendanceBonus" />
|
||||
<el-table-column show-overflow-tooltip label="扣餐费" prop="mealExpenses" />
|
||||
<el-table-column show-overflow-tooltip label="房补" prop="housingSupplement" />
|
||||
<el-table-column show-overflow-tooltip label="补发" prop="supplementFees" />
|
||||
<el-table-column show-overflow-tooltip label="扣保险" prop="insurancePremiumExpenses" />
|
||||
<el-table-column show-overflow-tooltip label="扣罚" prop="penaltyDeductionExpenses" />
|
||||
<el-table-column show-overflow-tooltip label="扣保证金" prop="marginExpenses" />
|
||||
<el-table-column show-overflow-tooltip label="退回保证金" prop="refundOfDeposit" />
|
||||
<el-table-column show-overflow-tooltip label="应发工资" prop="salariesPayable" />
|
||||
<el-table-column show-overflow-tooltip label="安排签署日期" prop="arrangeSigningDate" />
|
||||
<el-table-column show-overflow-tooltip label="发送时间" prop="msmSendTime" />
|
||||
<el-table-column show-overflow-tooltip label="签署时间" prop="signedTime" />
|
||||
<el-table-column fixed="right" label="操作">
|
||||
<template #default="{ row }">
|
||||
<span
|
||||
class="my-primary"
|
||||
@click="deleteRow(row)"
|
||||
v-if="row.msmSend === 'N' && row.signed === 'N'"
|
||||
>
|
||||
<el-icon class="table-opera-icon" title="删除">
|
||||
<Delete color="#eb333d" />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span class="my-primary ml10" @click="handleSendMsm(row)" v-if="row.msmSend === 'N'">
|
||||
<el-icon class="table-opera-icon" title="发送">
|
||||
<Message />
|
||||
</el-icon>
|
||||
</span>
|
||||
<span class="my-primary ml10" @click="view(row)">
|
||||
<el-icon class="table-opera-icon" title="查看">
|
||||
<View />
|
||||
</el-icon>
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="mt16 tr"
|
||||
@size-change="handleSizeChange"
|
||||
:page-sizes="[10, 20, 50, 100, 200]"
|
||||
@current-change="pageList"
|
||||
:current-page="pageData.pageNum"
|
||||
:page-size="pageData.pageSize"
|
||||
:total="pageData.total"
|
||||
layout="->,total, sizes, prev, pager, next, jumper"
|
||||
:page-count="pageData.pages"
|
||||
/>
|
||||
</div>
|
||||
<ImportPaySlip :optType="optType" :dialogVisible="dialogVisible" @handle-close="close" />
|
||||
<PaySlipDetail
|
||||
ref="paySlipDetail"
|
||||
:dialog-visible="detailDialogVisible"
|
||||
@update:dialog-visible="detailDialogVisible = $event"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, RefreshLeft, UploadFilled, Delete, Message, View } from '@element-plus/icons-vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import ImportPaySlip from './import.vue'
|
||||
import PaySlipDetail from './paySlipDetail.vue'
|
||||
import { getPaySlipPage, deletePaySlip, sendMsm, batchSendMsm } from '@/api/submit/paySlip'
|
||||
import { PaySlipDTO, QueryParams } from '@/api/submit/paySlip/types'
|
||||
|
||||
const { t: $t } = useI18n()
|
||||
|
||||
const searchForm = ref()
|
||||
const table = ref()
|
||||
const paySlipDetail = ref()
|
||||
|
||||
// 定义搜索条件类型
|
||||
interface SearchParams {
|
||||
yearMonth: string;
|
||||
employeeId: string;
|
||||
employeeName: string;
|
||||
mobileNo: string;
|
||||
signed: string; // 前端使用字符串'Y'/'N',后端接口需要布尔值
|
||||
msmSend: string; // 前端使用字符串'Y'/'N',后端接口需要布尔值
|
||||
year: string;
|
||||
month: string;
|
||||
}
|
||||
|
||||
const search = reactive<SearchParams>({
|
||||
yearMonth: '',
|
||||
employeeId: '',
|
||||
employeeName: '',
|
||||
mobileNo: '',
|
||||
signed: '',
|
||||
msmSend: '',
|
||||
year: '',
|
||||
month: ''
|
||||
})
|
||||
|
||||
const listLoading = ref(false)
|
||||
const tableListData = ref([])
|
||||
const pageData = reactive({
|
||||
total: 0,
|
||||
pages: 1,
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
})
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const optType = ref('import')
|
||||
const detailDialogVisible = ref(false)
|
||||
|
||||
// 选中的行数据
|
||||
const selectionData = ref([])
|
||||
|
||||
// 判断是否可以批量发送
|
||||
const canBatchSend = computed(() => {
|
||||
if (!selectionData.value || selectionData.value.length === 0) {
|
||||
return false
|
||||
}
|
||||
// 检查是否有未发送的记录
|
||||
return selectionData.value.some(item => item?.msmSend === 'N')
|
||||
})
|
||||
|
||||
// 选择变化时的处理函数
|
||||
const handleSelectionChange = (selection) => {
|
||||
selectionData.value = selection || []
|
||||
}
|
||||
|
||||
// 批量发送
|
||||
const batchSend = () => {
|
||||
if (!selectionData.value || selectionData.value.length === 0) {
|
||||
ElMessage.warning('请先选择要发送的数据')
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤出未发送的记录
|
||||
const unSendList = selectionData.value.filter(item => item?.msmSend === 'N')
|
||||
if (!unSendList || unSendList.length === 0) {
|
||||
ElMessage.warning('所选数据中没有未发送的记录')
|
||||
return
|
||||
}
|
||||
|
||||
// 确认批量发送
|
||||
ElMessageBox.confirm('确定要批量发送选中的记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const ids = unSendList.map(item => item.id)
|
||||
batchSendMsm(ids).then(() => {
|
||||
ElMessage.success('批量发送成功')
|
||||
pageList() // 刷新列表
|
||||
}).catch(() => {
|
||||
ElMessage.error('批量发送失败')
|
||||
})
|
||||
}).catch(() => {
|
||||
// 用户取消操作
|
||||
})
|
||||
}
|
||||
|
||||
const yesOrNoList = [
|
||||
{ code: 'Y', name: '是' },
|
||||
{ code: 'N', name: '否' }
|
||||
]
|
||||
|
||||
onMounted(() => {
|
||||
pageList()
|
||||
})
|
||||
|
||||
const searchFun = () => {
|
||||
pageData.pageNum = 1
|
||||
pageList()
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
Object.assign(search, {
|
||||
yearMonth: '',
|
||||
employeeId: '',
|
||||
employeeName: '',
|
||||
mobileNo: '',
|
||||
signed: '',
|
||||
msmSend: ''
|
||||
})
|
||||
pageData.pageNum = 1
|
||||
pageList()
|
||||
}
|
||||
|
||||
const pageList = () => {
|
||||
listLoading.value = true
|
||||
// 处理查询参数
|
||||
const query: QueryParams = {
|
||||
// 将页码和每页条数映射为接口需要的字段名
|
||||
pageNo: pageData.pageNum,
|
||||
pageSize: pageData.pageSize,
|
||||
// 提取工号、姓名、手机号
|
||||
employeeId: search.employeeId,
|
||||
employeeName: search.employeeName,
|
||||
mobileNo: search.mobileNo
|
||||
}
|
||||
|
||||
// 将yearMonth拆分为year和month
|
||||
if (search.yearMonth) {
|
||||
const [year, month] = search.yearMonth.split('-')
|
||||
query.year = year
|
||||
query.month = month
|
||||
} else {
|
||||
// 如果没有选择yearMonth,使用单独的year和month字段
|
||||
query.year = search.year
|
||||
query.month = search.month
|
||||
}
|
||||
|
||||
// 将signed直接传递为'Y'/'N'
|
||||
if (search.signed) {
|
||||
query.signed = search.signed
|
||||
}
|
||||
|
||||
// 将msmSend直接传递为'Y'/'N'
|
||||
if (search.msmSend) {
|
||||
query.msmSend = search.msmSend
|
||||
}
|
||||
|
||||
getPaySlipPage(query).then(res => {
|
||||
listLoading.value = false
|
||||
tableListData.value = res.list; // 列表字段是 list
|
||||
pageData.total = res.total; // total 是根节点的字段
|
||||
pageData.pages = Math.ceil(res.total / pageData.pageSize);
|
||||
}).catch(() => {
|
||||
listLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleSizeChange = (size: number) => {
|
||||
pageData.pageSize = size
|
||||
pageData.pageNum = 1
|
||||
pageList()
|
||||
}
|
||||
|
||||
const importData = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
pageList()
|
||||
}
|
||||
|
||||
const deleteRow = (row: PaySlipDTO) => {
|
||||
ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deletePaySlip(row.id).then(() => {
|
||||
ElMessage.success('删除成功')
|
||||
pageList()
|
||||
}).catch(() => {
|
||||
ElMessage.error('删除失败')
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const handleSendMsm = (row: PaySlipDTO) => {
|
||||
ElMessageBox.confirm('确定要发送短信吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
sendMsm(row.id).then(() => {
|
||||
ElMessage.success('发送成功')
|
||||
pageList()
|
||||
}).catch(() => {
|
||||
ElMessage.error('发送失败')
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
const view = (row: PaySlipDTO) => {
|
||||
paySlipDetail.value.setEntity(row)
|
||||
detailDialogVisible.value = true
|
||||
}
|
||||
|
||||
// 判断行是否可以选择
|
||||
const isRowSelectable = (row: PaySlipDTO) => {
|
||||
// 只有查看按钮可见的情况:
|
||||
// 1. msmSend为Y且signed为Y:删除和发送按钮都不可见
|
||||
// 2. msmSend为Y且signed为N:删除和发送按钮都不可见
|
||||
// 因此,当msmSend为Y时,只有查看按钮可见,禁止选择
|
||||
return row.msmSend !== 'Y'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.manage-container {
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 60px);
|
||||
background-color: #f5f7fa;
|
||||
padding: 20px;
|
||||
|
||||
// 整体容器样式
|
||||
.wrap {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// 分割线样式
|
||||
.line {
|
||||
height: 1px;
|
||||
background-color: #e8e8e8;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// 卡片样式
|
||||
.table-content {
|
||||
.b-white {
|
||||
background-color: #fff;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 间距类
|
||||
.mt16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.mt4 {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.tr {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
// 链接样式
|
||||
.my-primary {
|
||||
color: #409eff;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮图标样式
|
||||
.btn-icon {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
// 表格操作图标样式
|
||||
.table-opera-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
// 表格样式优化
|
||||
.el-table {
|
||||
border-radius: 4px;
|
||||
|
||||
// 优化表头样式
|
||||
th {
|
||||
background-color: #fafafa;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 优化表格内容
|
||||
td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// 优化表格行悬停效果
|
||||
.el-table__row {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分页器样式
|
||||
.el-pagination {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue