Pre Merge pull request !838 from wuxingyu123456/report

pull/838/MERGE
wuxingyu123456 2025-12-06 14:20:56 +00:00 committed by Gitee
commit 9d454497ac
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
107 changed files with 9101 additions and 1662 deletions

8
.env
View File

@ -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

View File

@ -22,7 +22,7 @@ VITE_DROP_CONSOLE=false
VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH=/
VITE_BASE_PATH='/boxd-web/'
# 输出路径
VITE_OUT_DIR=dist

View File

@ -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'

View File

@ -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'

View File

@ -18,7 +18,7 @@
## 🐯 平台简介
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
**捷汇链**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
* 采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 实现
* 改换 saas自动引入等功能

View File

@ -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',

View File

@ -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>

529
package-lock.json generated
View File

@ -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",

View File

@ -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",

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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}`
})
}

View File

@ -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
})
}

View File

@ -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 }

View File

@ -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;
}

View File

@ -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}`
})
}

View File

@ -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 }>;
}

View File

@ -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 });
};

View File

@ -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;
}

View File

@ -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 } })
}

View File

@ -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;
}

View File

@ -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}` })
}

View File

@ -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
}

View File

@ -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 })
}
}

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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[] // 事件配置
}

View File

@ -642,7 +642,7 @@ const previewProcessJson = () => {
})
}
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
/* ------------------------------------------------ 捷汇链源码 methods ------------------------------------------------------ */
onMounted(() => {
initBpmnModeler()
createNewDiagram(props.value)

View File

@ -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',

93
src/store/modules/dept.ts Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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])
}

View File

@ -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)
}

22
src/store/static.js Normal file
View File

@ -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:'选择分值'},
],
}

View File

@ -1,5 +1,5 @@
/**
* Created by
* Created by
*
*
*/

View File

@ -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 = '/'
}

View File

@ -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 = '/'
}

View File

@ -133,7 +133,7 @@ const loginData = reactive({
},
loginForm: {
uuid: '',
tenantName: '芋道源码',
tenantName: '捷汇链源码',
mobileNumber: '',
code: ''
}

View File

@ -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' },

View File

@ -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)]"

View File

@ -1,5 +1,5 @@
/**
* Created by
* Created by
*
* AI
*

View File

@ -1,5 +1,5 @@
/**
* Created by
* Created by
*
* AI
*

View File

@ -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">

View File

@ -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>

View File

@ -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 = () => {

View File

@ -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>`

View File

@ -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'

View File

@ -1,4 +1,4 @@
import { IButtonMenu, IDomEditor } from '@wangeditor/editor'
import { IButtonMenu, IDomEditor } from '@wangeditor-next/editor'
class ProcessRecordMenu implements IButtonMenu {
readonly tag: string

View File

@ -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 = {

View File

@ -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

View File

@ -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,

View File

@ -242,7 +242,6 @@ const handleProcessDetail = (row) => {
})
}
// fix:
watch(
() => router.currentRoute.value,
() => {

View File

@ -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>

View File

@ -1,4 +1,5 @@
<template>
<doc-alert title="发起流程页面" url="https://doc.iocoder.cn/bpm/task-todo-done/" />
<!-- 第一步通过流程定义的列表选择对应的流程 -->
<template v-if="!selectProcessDefinition">
<el-input

View File

@ -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" />&nbsp; 再次提交
</div>
</div> -->
</div>
<!-- 签名弹窗 -->

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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

View File

@ -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">

View File

@ -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()

View File

@ -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 } })
}
/** 修改操作 */

View File

@ -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']

View File

@ -196,7 +196,7 @@ const getOrderCountTrendComparison = async (
}
eChartOptions.xAxis!['data'] = dates
eChartOptions.series = series
// legend424
// legend 4 2 4
eChartOptions.legend['data'] = series.map((item) => item.name)
loading.value = false
}

View File

@ -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
//

View File

@ -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" />

View File

@ -20,7 +20,7 @@ const crudSchemas = reactive<CrudSchema[]>([
}
},
{
label: '积分商城活动商品',
label: '活动商品',
field: 'spuId',
isTable: true,
isSearch: false,

View File

@ -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

View File

@ -29,7 +29,6 @@ const terminalChartOptions = reactive<EChartsOption>({
orient: 'vertical',
left: 'right'
},
roseType: 'area',
series: [
{
name: '会员终端',

View File

@ -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()
})

View File

@ -1,7 +1,7 @@
<!--
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
芋道源码
捷汇链源码
移除 avue 组件使用 ElementUI 原生组件
-->
<template>

View File

@ -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,

View File

@ -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>

View File

@ -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
}

View File

@ -2,7 +2,7 @@
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 视频
芋道源码
捷汇链源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放

View File

@ -2,7 +2,7 @@
- Copyright (C) 2018-2019
- All rights reserved, Designed By www.joolun.com
微信消息 - 语音
芋道源码
捷汇链源码
bug 修复
1joolun 的做法使用 mediaId 从微信公众号下载对应的 mp4 素材从而播放内容
存在的问题mediaId 有效期是 3 超过时间后无法播放

View File

@ -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" />
<!-- 操作按钮 -->

View File

@ -186,7 +186,9 @@ const removeNews = async (index: number) => {
if (activeNewsIndex.value === index) {
activeNewsIndex.value = 0
}
} catch {}
} catch {
// empty
}
}
//

View File

@ -1,4 +1,4 @@
import { IEditorConfig } from '@wangeditor/editor'
import { IEditorConfig } from '@wangeditor-next/editor'
import { getAccessToken, getTenantId } from '@/utils/auth'
const message = useMessage()

View File

@ -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>

View File

@ -97,7 +97,9 @@ const handleDelete = async (item: any) => {
message.success(t('common.delSuccess'))
//
await getList()
} catch {}
} catch {
//
}
}
</script>
<style lang="scss" scoped>

View File

@ -63,7 +63,7 @@ const emit = defineEmits<{
column-count: 5;
margin-top: 10px;
/* 芋道源码:增加 10px避免顶着上面 */
/* 捷汇链源码:增加 10px避免顶着上面 */
}
.waterfall-item {

View File

@ -101,7 +101,7 @@ const uploadVideoRef = ref<UploadInstance | null>(null)
const submitVideo = () => {
uploadFormRef.value?.validate((valid) => {
if (!valid) {
return false
return
}
uploadVideoRef.value?.submit()
})

View File

@ -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

View File

@ -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 //

View File

@ -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>

View File

@ -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>

View File

@ -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 {
//
}
}
/** 初始化 */

View File

@ -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' }]
}

View File

@ -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
}
/** 提交支付 */

View File

@ -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>

View File

@ -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 {
// Responseheaders
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {
// Responseheaders
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>

View File

@ -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)
// yearMonthyearmonth
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>

View File

@ -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
}
// yearMonthyearmonth
if (search.yearMonth) {
const [year, month] = search.yearMonth.split('-')
query.year = year
query.month = month
} else {
// yearMonth使yearmonth
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. msmSendYsignedY
// 2. msmSendYsignedN
// msmSendY
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