Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev
# Conflicts: # src/views/mall/statistics/member/index.vue
This commit is contained in:
commit
aa99c53729
12
README.md
12
README.md
@ -39,14 +39,14 @@
|
|||||||
| 框架 | 说明 | 版本 |
|
| 框架 | 说明 | 版本 |
|
||||||
|----------------------------------------------------------------------|------------------|--------|
|
|----------------------------------------------------------------------|------------------|--------|
|
||||||
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.4 |
|
| [Vue](https://staging-cn.vuejs.org/) | Vue 框架 | 3.3.4 |
|
||||||
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.4.9 |
|
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 4.4.11 |
|
||||||
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.3.14 |
|
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.4.0 |
|
||||||
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
|
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 的超集 | 5.2.2 |
|
||||||
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.6 |
|
| [pinia](https://pinia.vuejs.org/) | Vue 存储库 替代 vuex5 | 2.1.7 |
|
||||||
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.4.1 |
|
| [vueuse](https://vueuse.org/) | 常用工具集 | 10.5.0 |
|
||||||
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.4.1 |
|
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 9.5.0 |
|
||||||
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
|
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.2.5 |
|
||||||
| [unocss](https://uno.antfu.me/) | 原子 css | 0.56.1 |
|
| [unocss](https://uno.antfu.me/) | 原子 css | 0.56.5 |
|
||||||
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
|
| [iconify](https://icon-sets.iconify.design/) | 在线图标库 | 3.1.1 |
|
||||||
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
|
| [wangeditor](https://www.wangeditor.com/) | 富文本编辑器 | 5.1.23 |
|
||||||
|
|
||||||
|
70
package.json
70
package.json
@ -31,31 +31,31 @@
|
|||||||
"@form-create/element-ui": "^3.1.24",
|
"@form-create/element-ui": "^3.1.24",
|
||||||
"@iconify/iconify": "^3.1.1",
|
"@iconify/iconify": "^3.1.1",
|
||||||
"@videojs-player/vue": "^1.0.0",
|
"@videojs-player/vue": "^1.0.0",
|
||||||
"@vueuse/core": "^10.4.1",
|
"@vueuse/core": "^10.5.0",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"@zxcvbn-ts/core": "^3.0.4",
|
"@zxcvbn-ts/core": "^3.0.4",
|
||||||
"animate.css": "^4.1.1",
|
"animate.css": "^4.1.1",
|
||||||
"axios": "^1.5.0",
|
"axios": "^1.5.1",
|
||||||
"benz-amr-recorder": "^1.1.5",
|
"benz-amr-recorder": "^1.1.5",
|
||||||
"bpmn-js-token-simulation": "^0.10.0",
|
"bpmn-js-token-simulation": "^0.10.0",
|
||||||
"camunda-bpmn-moddle": "^7.0.1",
|
"camunda-bpmn-moddle": "^7.0.1",
|
||||||
"cropperjs": "^1.6.1",
|
"cropperjs": "^1.6.1",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"diagram-js": "^12.3.0",
|
"diagram-js": "^12.4.0",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "2.3.14",
|
"element-plus": "2.4.0",
|
||||||
"fast-xml-parser": "^4.3.0",
|
"fast-xml-parser": "^4.3.2",
|
||||||
"highlight.js": "^11.8.0",
|
"highlight.js": "^11.9.0",
|
||||||
"intro.js": "^7.2.0",
|
"intro.js": "^7.2.0",
|
||||||
"jsencrypt": "^3.3.2",
|
"jsencrypt": "^3.3.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"min-dash": "^4.1.1",
|
"min-dash": "^4.1.1",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "^0.2.0",
|
"nprogress": "^0.2.0",
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.7",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"steady-xml": "^0.1.0",
|
"steady-xml": "^0.1.0",
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"video.js": "^7.21.5",
|
"video.js": "^7.21.5",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-dompurify-html": "^4.1.4",
|
"vue-dompurify-html": "^4.1.4",
|
||||||
"vue-i18n": "^9.4.1",
|
"vue-i18n": "^9.5.0",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vue-types": "^5.1.1",
|
"vue-types": "^5.1.1",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
@ -71,54 +71,54 @@
|
|||||||
"xml-js": "^1.6.11"
|
"xml-js": "^1.6.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^17.7.1",
|
"@commitlint/cli": "^17.8.0",
|
||||||
"@commitlint/config-conventional": "^17.7.0",
|
"@commitlint/config-conventional": "^17.8.0",
|
||||||
"@iconify/json": "^2.2.119",
|
"@iconify/json": "^2.2.129",
|
||||||
"@intlify/unplugin-vue-i18n": "^1.2.0",
|
"@intlify/unplugin-vue-i18n": "^1.4.0",
|
||||||
"@purge-icons/generated": "^0.9.0",
|
"@purge-icons/generated": "^0.9.0",
|
||||||
"@types/intro.js": "^5.1.1",
|
"@types/intro.js": "^5.1.2",
|
||||||
"@types/lodash-es": "^4.17.9",
|
"@types/lodash-es": "^4.17.9",
|
||||||
"@types/node": "^20.6.0",
|
"@types/node": "^20.8.6",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.1",
|
||||||
"@types/qrcode": "^1.5.2",
|
"@types/qrcode": "^1.5.2",
|
||||||
"@types/qs": "^6.9.8",
|
"@types/qs": "^6.9.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.2",
|
"@typescript-eslint/eslint-plugin": "^6.7.5",
|
||||||
"@typescript-eslint/parser": "^6.7.2",
|
"@typescript-eslint/parser": "^6.7.5",
|
||||||
"@unocss/transformer-variant-group": "^0.56.1",
|
"@unocss/transformer-variant-group": "^0.56.5",
|
||||||
"@unocss/eslint-config": "^0.56.1",
|
"@unocss/eslint-config": "^0.56.5",
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@vitejs/plugin-legacy": "^4.1.1",
|
||||||
"@vitejs/plugin-vue": "^4.3.4",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
"@vitejs/plugin-vue-jsx": "^3.0.2",
|
||||||
"@vue-macros/volar": "^0.14.3",
|
"@vue-macros/volar": "^0.17.0",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"bpmn-js": "8.9.0",
|
"bpmn-js": "8.9.0",
|
||||||
"bpmn-js-properties-panel": "0.46.0",
|
"bpmn-js-properties-panel": "0.46.0",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
"eslint": "^8.49.0",
|
"eslint": "^8.51.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-define-config": "^1.23.0",
|
"eslint-define-config": "^1.24.1",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"lint-staged": "^14.0.1",
|
"lint-staged": "^15.0.1",
|
||||||
"postcss": "^8.4.30",
|
"postcss": "^8.4.31",
|
||||||
"postcss-html": "^1.5.0",
|
"postcss-html": "^1.5.0",
|
||||||
"postcss-scss": "^4.0.8",
|
"postcss-scss": "^4.0.9",
|
||||||
"prettier": "^3.0.3",
|
"prettier": "^3.0.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.5",
|
||||||
"rollup": "^3.29.2",
|
"rollup": "^4.1.4",
|
||||||
"sass": "^1.68.0",
|
"sass": "^1.69.3",
|
||||||
"stylelint": "^15.10.3",
|
"stylelint": "^15.10.3",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"stylelint-config-recommended": "^13.0.0",
|
"stylelint-config-recommended": "^13.0.0",
|
||||||
"stylelint-config-standard": "^34.0.0",
|
"stylelint-config-standard": "^34.0.0",
|
||||||
"stylelint-order": "^6.0.3",
|
"stylelint-order": "^6.0.3",
|
||||||
"terser": "^5.20.0",
|
"terser": "^5.21.0",
|
||||||
"typescript": "5.2.2",
|
"typescript": "5.2.2",
|
||||||
"unocss": "^0.56.1",
|
"unocss": "^0.56.5",
|
||||||
"unplugin-auto-import": "^0.16.6",
|
"unplugin-auto-import": "^0.16.6",
|
||||||
"unplugin-element-plus": "^0.8.0",
|
"unplugin-element-plus": "^0.8.0",
|
||||||
"unplugin-vue-components": "^0.25.2",
|
"unplugin-vue-components": "^0.25.2",
|
||||||
"vite": "4.4.9",
|
"vite": "4.4.11",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-ejs": "^1.6.4",
|
"vite-plugin-ejs": "^1.6.4",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
@ -126,8 +126,8 @@
|
|||||||
"vite-plugin-purge-icons": "^0.9.2",
|
"vite-plugin-purge-icons": "^0.9.2",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vite-plugin-top-level-await": "^1.3.1",
|
"vite-plugin-top-level-await": "^1.3.1",
|
||||||
"vue-eslint-parser": "^9.3.1",
|
"vue-eslint-parser": "^9.3.2",
|
||||||
"vue-tsc": "^1.8.13"
|
"vue-tsc": "^1.8.19"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -58,3 +58,24 @@ export const returnTask = async (data) => {
|
|||||||
export const delegateTask = async (data) => {
|
export const delegateTask = async (data) => {
|
||||||
return await request.put({ url: '/bpm/task/delegate', data })
|
return await request.put({ url: '/bpm/task/delegate', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加签
|
||||||
|
*/
|
||||||
|
export const taskAddSign = async (data) => {
|
||||||
|
return await request.put({ url: '/bpm/task/add-sign', data })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取减签任务列表
|
||||||
|
*/
|
||||||
|
export const getChildrenTaskList = async (id: string) => {
|
||||||
|
return await request.get({ url: '/bpm/task/get-children-task-list?taskId=' + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 减签
|
||||||
|
*/
|
||||||
|
export const taskSubSign = async (data) => {
|
||||||
|
return await request.put({ url: '/bpm/task/sub-sign', data })
|
||||||
|
}
|
||||||
|
@ -47,6 +47,18 @@ export const smsLogin = (data: SmsLoginVO) => {
|
|||||||
return request.post({ url: '/system/auth/sms-login', data })
|
return request.post({ url: '/system/auth/sms-login', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 社交快捷登录,使用 code 授权码
|
||||||
|
export function socialLogin(type: string, code: string, state: string) {
|
||||||
|
return request.post({
|
||||||
|
url: '/system/auth/social-login',
|
||||||
|
data: {
|
||||||
|
type,
|
||||||
|
code,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 社交授权的跳转
|
// 社交授权的跳转
|
||||||
export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
export const socialAuthRedirect = (type: number, redirectUri: string) => {
|
||||||
return request.get({
|
return request.get({
|
||||||
|
@ -2,6 +2,9 @@ export type UserLoginVO = {
|
|||||||
username: string
|
username: string
|
||||||
password: string
|
password: string
|
||||||
captchaVerification: string
|
captchaVerification: string
|
||||||
|
socialType?: string
|
||||||
|
socialCode?: string
|
||||||
|
socialState?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenType = {
|
export type TokenType = {
|
||||||
|
@ -250,6 +250,12 @@ const getResultCss = (result) => {
|
|||||||
} else if (result === 5) {
|
} else if (result === 5) {
|
||||||
// 退回
|
// 退回
|
||||||
return 'highlight-return'
|
return 'highlight-return'
|
||||||
|
} else if (result === 6) {
|
||||||
|
// 委派
|
||||||
|
return 'highlight-return'
|
||||||
|
} else if (result === 7 || result === 8 || result === 9) {
|
||||||
|
// 待后加签任务完成/待前加签任务完成/待前置任务完成
|
||||||
|
return 'highlight-return'
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@ -362,7 +368,7 @@ const elementHover = (element) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(html, 'html111111111111111')
|
console.log(html, 'html111111111111111')
|
||||||
elementOverlayIds.value[element.value.id] = toRaw(overlays.value).add(element.value, {
|
elementOverlayIds.value[element.value.id] = toRaw(overlays.value)?.add(element.value, {
|
||||||
position: { left: 0, bottom: 0 },
|
position: { left: 0, bottom: 0 },
|
||||||
html: `<div class="element-overlays">${html}</div>`
|
html: `<div class="element-overlays">${html}</div>`
|
||||||
})
|
})
|
||||||
@ -591,14 +597,17 @@ watch(
|
|||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
fill-opacity: 0.2 !important;
|
fill-opacity: 0.2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-return.djs-shape .djs-visual > :nth-child(2) {
|
.highlight-return.djs-shape .djs-visual > :nth-child(2) {
|
||||||
fill: #e6a23c !important;
|
fill: #e6a23c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-return.djs-shape .djs-visual > path {
|
.highlight-return.djs-shape .djs-visual > path {
|
||||||
fill: #e6a23c !important;
|
fill: #e6a23c !important;
|
||||||
fill-opacity: 0.2 !important;
|
fill-opacity: 0.2 !important;
|
||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-return.djs-connection > .djs-visual > path {
|
.highlight-return.djs-connection > .djs-visual > path {
|
||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
}
|
}
|
||||||
@ -612,14 +621,17 @@ watch(
|
|||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
fill-opacity: 0.2 !important;
|
fill-opacity: 0.2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
|
:deep(.highlight-return.djs-shape .djs-visual > :nth-child(2)) {
|
||||||
fill: #e6a23c !important;
|
fill: #e6a23c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.highlight-return.djs-shape .djs-visual > path) {
|
:deep(.highlight-return.djs-shape .djs-visual > path) {
|
||||||
fill: #e6a23c !important;
|
fill: #e6a23c !important;
|
||||||
fill-opacity: 0.2 !important;
|
fill-opacity: 0.2 !important;
|
||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.highlight-return.djs-connection > .djs-visual > path) {
|
:deep(.highlight-return.djs-connection > .djs-visual > path) {
|
||||||
stroke: #e6a23c !important;
|
stroke: #e6a23c !important;
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,7 @@ $prefix-cls: #{$elNamespace}-breadcrumb;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(&__item):last-child {
|
:deep(&__item):last-child {
|
||||||
.#{$prefix-cls}__inner {
|
.#{$prefix-cls}__inner {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -141,6 +141,7 @@ export default {
|
|||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
|
socialLogin: '社交登录',
|
||||||
home: '首页',
|
home: '首页',
|
||||||
analysis: '分析页',
|
analysis: '分析页',
|
||||||
workplace: '工作台'
|
workplace: '工作台'
|
||||||
|
@ -195,6 +195,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
noTagsView: true
|
noTagsView: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/social-login',
|
||||||
|
component: () => import('@/views/Login/SocialLogin.vue'),
|
||||||
|
name: 'SocialLogin',
|
||||||
|
meta: {
|
||||||
|
hidden: true,
|
||||||
|
title: t('router.socialLogin'),
|
||||||
|
noTagsView: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/403',
|
path: '/403',
|
||||||
component: () => import('@/views/Error/403.vue'),
|
component: () => import('@/views/Error/403.vue'),
|
||||||
@ -333,6 +343,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/mall/product', // 商品中心
|
path: '/mall/product', // 商品中心
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
name: 'ProductCenter',
|
||||||
meta: {
|
meta: {
|
||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
@ -394,6 +405,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/mall/trade', // 交易中心
|
path: '/mall/trade', // 交易中心
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
name: 'TradeCenter',
|
||||||
meta: {
|
meta: {
|
||||||
hidden: true
|
hidden: true
|
||||||
},
|
},
|
||||||
@ -415,7 +427,7 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/member',
|
path: '/member',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
name: 'member',
|
name: 'MemberCenter',
|
||||||
meta: { hidden: true },
|
meta: { hidden: true },
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
@ -205,6 +205,9 @@ export const floatToFixed2 = (num: number | string | undefined): string => {
|
|||||||
case 1:
|
case 1:
|
||||||
str = f.toString() + '0'
|
str = f.toString() + '0'
|
||||||
break
|
break
|
||||||
|
case 2:
|
||||||
|
str = f.toString()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
return str
|
return str
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ export const isObject = (val: any): val is Record<any, any> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const isEmpty = <T = unknown>(val: T): val is T => {
|
export const isEmpty = <T = unknown>(val: T): val is T => {
|
||||||
|
if (val === null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (isArray(val) || isString(val)) {
|
if (isArray(val) || isString(val)) {
|
||||||
return val.length === 0
|
return val.length === 0
|
||||||
}
|
}
|
||||||
@ -103,3 +106,12 @@ export const isUrl = (path: string): boolean => {
|
|||||||
export const isDark = (): boolean => {
|
export const isDark = (): boolean => {
|
||||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 是否是图片链接
|
||||||
|
export const isImgPath = (path: string): boolean => {
|
||||||
|
return /(https?:\/\/|data:image\/).*?\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isEmptyVal = (val: any): boolean => {
|
||||||
|
return val === '' || val === null || val === undefined
|
||||||
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types'
|
import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
|
||||||
import { CSSProperties } from 'vue'
|
import { CSSProperties } from 'vue'
|
||||||
|
|
||||||
// 自定义扩展vue-types
|
|
||||||
type PropTypes = VueTypesInterface & {
|
type PropTypes = VueTypesInterface & {
|
||||||
readonly style: VueTypeValidableDef<CSSProperties>
|
readonly style: VueTypeValidableDef<CSSProperties>
|
||||||
}
|
}
|
||||||
|
const newPropTypes = createTypes({
|
||||||
const propTypes = createTypes({
|
|
||||||
func: undefined,
|
func: undefined,
|
||||||
bool: undefined,
|
bool: undefined,
|
||||||
string: undefined,
|
string: undefined,
|
||||||
@ -15,14 +13,12 @@ const propTypes = createTypes({
|
|||||||
integer: undefined
|
integer: undefined
|
||||||
}) as PropTypes
|
}) as PropTypes
|
||||||
|
|
||||||
// 需要自定义扩展的类型
|
class propTypes extends newPropTypes {
|
||||||
// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
static get style() {
|
||||||
// propTypes.extend([
|
return toValidableType('style', {
|
||||||
// {
|
type: [String, Object]
|
||||||
// name: 'style',
|
})
|
||||||
// getter: true,
|
}
|
||||||
// type: [String, Object],
|
}
|
||||||
// default: undefined
|
|
||||||
// }
|
|
||||||
// ])
|
|
||||||
export { propTypes }
|
export { propTypes }
|
||||||
|
@ -93,7 +93,10 @@ export const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecord
|
|||||||
meta.alwaysShow = true
|
meta.alwaysShow = true
|
||||||
const childrenData: AppRouteRecordRaw = {
|
const childrenData: AppRouteRecordRaw = {
|
||||||
path: '',
|
path: '',
|
||||||
name: toCamelCase(route.path, true),
|
name:
|
||||||
|
route.componentName && route.componentName.length > 0
|
||||||
|
? route.componentName
|
||||||
|
: toCamelCase(route.path, true),
|
||||||
redirect: route.redirect,
|
redirect: route.redirect,
|
||||||
meta: meta
|
meta: meta
|
||||||
}
|
}
|
||||||
|
343
src/views/Login/SocialLogin.vue
Normal file
343
src/views/Login/SocialLogin.vue
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:class="prefixCls"
|
||||||
|
class="relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px"
|
||||||
|
>
|
||||||
|
<div class="relative mx-auto h-full flex">
|
||||||
|
<div
|
||||||
|
:class="`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`"
|
||||||
|
>
|
||||||
|
<!-- 左上角的 logo + 系统标题 -->
|
||||||
|
<div class="relative flex items-center text-white">
|
||||||
|
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
||||||
|
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||||
|
</div>
|
||||||
|
<!-- 左边的背景图 + 欢迎语 -->
|
||||||
|
<div class="h-[calc(100%-60px)] flex items-center justify-center">
|
||||||
|
<TransitionGroup
|
||||||
|
appear
|
||||||
|
enter-active-class="animate__animated animate__bounceInLeft"
|
||||||
|
tag="div"
|
||||||
|
>
|
||||||
|
<img key="1" alt="" class="w-350px" src="@/assets/svgs/login-box-bg.svg" />
|
||||||
|
<div key="2" class="text-3xl text-white">{{ t('login.welcome') }}</div>
|
||||||
|
<div key="3" class="mt-5 text-14px font-normal text-white">
|
||||||
|
{{ t('login.message') }}
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px">
|
||||||
|
<!-- 右上角的主题、语言选择 -->
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end"
|
||||||
|
>
|
||||||
|
<div class="flex items-center at-2xl:hidden at-xl:hidden">
|
||||||
|
<img alt="" class="mr-10px h-48px w-48px" src="@/assets/imgs/logo.png" />
|
||||||
|
<span class="text-20px font-bold">{{ underlineToHump(appStore.getTitle) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-end space-x-10px">
|
||||||
|
<ThemeSwitch />
|
||||||
|
<LocaleDropdown class="dark:text-white lt-xl:text-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 右边的登录界面 -->
|
||||||
|
<Transition appear enter-active-class="animate__animated animate__bounceInRight">
|
||||||
|
<div
|
||||||
|
class="m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px"
|
||||||
|
>
|
||||||
|
<!-- 账号登录 -->
|
||||||
|
<el-form
|
||||||
|
v-show="getShow"
|
||||||
|
ref="formLogin"
|
||||||
|
:model="loginData.loginForm"
|
||||||
|
:rules="LoginRules"
|
||||||
|
class="login-form"
|
||||||
|
label-position="top"
|
||||||
|
label-width="120px"
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
<el-row style="margin-right: -10px; margin-left: -10px">
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<LoginFormTitle style="width: 100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item v-if="loginData.tenantEnable === 'true'" prop="tenantName">
|
||||||
|
<el-input
|
||||||
|
v-model="loginData.loginForm.tenantName"
|
||||||
|
:placeholder="t('login.tenantNamePlaceholder')"
|
||||||
|
:prefix-icon="iconHouse"
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="username">
|
||||||
|
<el-input
|
||||||
|
v-model="loginData.loginForm.username"
|
||||||
|
:placeholder="t('login.usernamePlaceholder')"
|
||||||
|
:prefix-icon="iconAvatar"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
v-model="loginData.loginForm.password"
|
||||||
|
:placeholder="t('login.passwordPlaceholder')"
|
||||||
|
:prefix-icon="iconLock"
|
||||||
|
show-password
|
||||||
|
type="password"
|
||||||
|
@keyup.enter="getCode()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col
|
||||||
|
:span="24"
|
||||||
|
style="
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
margin-top: -20px;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<el-form-item>
|
||||||
|
<el-row justify="space-between" style="width: 100%">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-checkbox v-model="loginData.loginForm.rememberMe">
|
||||||
|
{{ t('login.remember') }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-col>
|
||||||
|
<el-col :offset="6" :span="12">
|
||||||
|
<el-link style="float: right" type="primary">{{
|
||||||
|
t('login.forgetPassword')
|
||||||
|
}}</el-link>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="24" style="padding-right: 10px; padding-left: 10px">
|
||||||
|
<el-form-item>
|
||||||
|
<XButton
|
||||||
|
:loading="loginLoading"
|
||||||
|
:title="t('login.login')"
|
||||||
|
class="w-[100%]"
|
||||||
|
type="primary"
|
||||||
|
@click="getCode()"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<Verify
|
||||||
|
ref="verify"
|
||||||
|
:captchaType="captchaType"
|
||||||
|
:imgSize="{ width: '400px', height: '200px' }"
|
||||||
|
mode="pop"
|
||||||
|
@success="handleLogin"
|
||||||
|
/>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { underlineToHump } from '@/utils'
|
||||||
|
|
||||||
|
import { ElLoading } from 'element-plus'
|
||||||
|
|
||||||
|
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 * as LoginApi from '@/api/login'
|
||||||
|
import * as authUtil from '@/utils/auth'
|
||||||
|
import { ThemeSwitch } from '@/layout/components/ThemeSwitch'
|
||||||
|
import { LocaleDropdown } from '@/layout/components/LocaleDropdown'
|
||||||
|
import { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'
|
||||||
|
import LoginFormTitle from './components/LoginFormTitle.vue'
|
||||||
|
import router from '@/router'
|
||||||
|
|
||||||
|
defineOptions({ name: 'SocialLogin' })
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const { getPrefixCls } = useDesign()
|
||||||
|
const prefixCls = getPrefixCls('login')
|
||||||
|
const iconHouse = useIcon({ icon: 'ep:house' })
|
||||||
|
const iconAvatar = useIcon({ icon: 'ep:avatar' })
|
||||||
|
const iconLock = useIcon({ icon: 'ep:lock' })
|
||||||
|
const formLogin = ref<any>()
|
||||||
|
const { validForm } = useFormValid(formLogin)
|
||||||
|
const { getLoginState } = useLoginState()
|
||||||
|
const { push } = useRouter()
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
const loginLoading = ref(false)
|
||||||
|
const verify = ref()
|
||||||
|
const captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字
|
||||||
|
|
||||||
|
const getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)
|
||||||
|
|
||||||
|
const LoginRules = {
|
||||||
|
tenantName: [required],
|
||||||
|
username: [required],
|
||||||
|
password: [required]
|
||||||
|
}
|
||||||
|
const loginData = reactive({
|
||||||
|
isShowPassword: false,
|
||||||
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
|
loginForm: {
|
||||||
|
tenantName: '芋道源码',
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin123',
|
||||||
|
captchaVerification: '',
|
||||||
|
rememberMe: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取验证码
|
||||||
|
const getCode = async () => {
|
||||||
|
// 情况一,未开启:则直接登录
|
||||||
|
if (loginData.captchaEnable === 'false') {
|
||||||
|
await handleLogin({})
|
||||||
|
} else {
|
||||||
|
// 情况二,已开启:则展示验证码;只有完成验证码的情况,才进行登录
|
||||||
|
// 弹出验证码
|
||||||
|
verify.value.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//获取租户ID
|
||||||
|
const getTenantId = async () => {
|
||||||
|
if (loginData.tenantEnable === 'true') {
|
||||||
|
const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)
|
||||||
|
authUtil.setTenantId(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 记住我
|
||||||
|
const getCookie = () => {
|
||||||
|
const loginForm = authUtil.getLoginForm()
|
||||||
|
if (loginForm) {
|
||||||
|
loginData.loginForm = {
|
||||||
|
...loginData.loginForm,
|
||||||
|
username: loginForm.username ? loginForm.username : loginData.loginForm.username,
|
||||||
|
password: loginForm.password ? loginForm.password : loginData.loginForm.password,
|
||||||
|
rememberMe: loginForm.rememberMe ? true : false,
|
||||||
|
tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const loading = ref() // ElLoading.service 返回的实例
|
||||||
|
|
||||||
|
// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode,需要在回调后进行decode
|
||||||
|
function getUrlValue(key: string): string {
|
||||||
|
const url = new URL(decodeURIComponent(location.href))
|
||||||
|
return url.searchParams.get(key) ?? ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试登录: 当账号已经绑定,socialLogin会直接获得token
|
||||||
|
const tryLogin = async () => {
|
||||||
|
try {
|
||||||
|
const type = getUrlValue('type')
|
||||||
|
const redirect = getUrlValue('redirect')
|
||||||
|
const code = route?.query?.code as string
|
||||||
|
const state = route?.query?.state as string
|
||||||
|
|
||||||
|
const res = await LoginApi.socialLogin(type, code, state)
|
||||||
|
authUtil.setToken(res)
|
||||||
|
|
||||||
|
router.push({ path: redirect || '/' })
|
||||||
|
} catch (err) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 登录
|
||||||
|
const handleLogin = async (params) => {
|
||||||
|
loginLoading.value = true
|
||||||
|
try {
|
||||||
|
await getTenantId()
|
||||||
|
const data = await validForm()
|
||||||
|
if (!data) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let redirect = getUrlValue('redirect')
|
||||||
|
|
||||||
|
const type = getUrlValue('type')
|
||||||
|
const code = route?.query?.code as string
|
||||||
|
const state = route?.query?.state as string
|
||||||
|
|
||||||
|
const res = await LoginApi.login({
|
||||||
|
// 账号密码登录
|
||||||
|
username: loginData.loginForm.username,
|
||||||
|
password: loginData.loginForm.password,
|
||||||
|
captchaVerification: params.captchaVerification,
|
||||||
|
// 社交登录
|
||||||
|
socialCode: code,
|
||||||
|
socialState: state,
|
||||||
|
socialType: type
|
||||||
|
})
|
||||||
|
if (!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loading.value = ElLoading.service({
|
||||||
|
lock: true,
|
||||||
|
text: '正在加载系统中...',
|
||||||
|
background: 'rgba(0, 0, 0, 0.7)'
|
||||||
|
})
|
||||||
|
if (loginData.loginForm.rememberMe) {
|
||||||
|
authUtil.setLoginForm(loginData.loginForm)
|
||||||
|
} else {
|
||||||
|
authUtil.removeLoginForm()
|
||||||
|
}
|
||||||
|
authUtil.setToken(res)
|
||||||
|
if (!redirect) {
|
||||||
|
redirect = '/'
|
||||||
|
}
|
||||||
|
// 判断是否为SSO登录
|
||||||
|
if (redirect.indexOf('sso') !== -1) {
|
||||||
|
window.location.href = window.location.href.replace('/login?redirect=', '')
|
||||||
|
} else {
|
||||||
|
push({ path: redirect || permissionStore.addRouters[0].path })
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loginLoading.value = false
|
||||||
|
loading.value.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getCookie()
|
||||||
|
tryLogin()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$prefix-cls: #{$namespace}-login;
|
||||||
|
|
||||||
|
.#{$prefix-cls} {
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: url('@/assets/svgs/login-bg.svg');
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -284,8 +284,13 @@ const doSocialLogin = async (type: number) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 计算 redirectUri
|
// 计算 redirectUri
|
||||||
|
// tricky: type、redirect需要先encode一次,否则钉钉回调会丢失。
|
||||||
|
// 配合 Login/SocialLogin.vue#getUrlValue() 使用
|
||||||
const redirectUri =
|
const redirectUri =
|
||||||
location.origin + '/social-login?type=' + type + '&redirect=' + (redirect.value || '/')
|
location.origin +
|
||||||
|
'/social-login?' +
|
||||||
|
encodeURIComponent(`type=${type}&redirect=${redirect.value || '/'}`)
|
||||||
|
|
||||||
// 进行跳转
|
// 进行跳转
|
||||||
const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))
|
||||||
window.location.href = res
|
window.location.href = res
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
<template>
|
||||||
|
<el-drawer v-model="drawerVisible" title="子任务" size="70%">
|
||||||
|
<template #header>
|
||||||
|
<h4>【{{ baseTask.name }} 】审批人:{{ baseTask.assigneeUser?.nickname }}</h4>
|
||||||
|
<el-button style="margin-left: 5px" v-if="showSubSignButton(baseTask)" type="danger" plain @click="handleSubSign(baseTask)">
|
||||||
|
<Icon icon="ep:remove" />
|
||||||
|
减签
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
<el-table :data="tableData" style="width: 100%" row-key="id" border>
|
||||||
|
<el-table-column prop="assigneeUser.nickname" label="审批人" />
|
||||||
|
<el-table-column prop="assigneeUser.deptName" label="所在部门" />
|
||||||
|
<el-table-column label="审批状态" prop="result">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="提交时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
width="180"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="结束时间"
|
||||||
|
align="center"
|
||||||
|
prop="endTime"
|
||||||
|
width="180"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" prop="operation">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
v-if="showSubSignButton(scope.row)"
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="handleSubSign(scope.row)"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:remove" />
|
||||||
|
减签
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 减签 -->
|
||||||
|
<TaskSubSignDialogForm ref="taskSubSignDialogForm" />
|
||||||
|
</el-drawer>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import TaskSubSignDialogForm from './TaskSubSignDialogForm.vue'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
defineOptions({ name: 'ProcessInstancechildrenList' })
|
||||||
|
|
||||||
|
const drawerVisible = ref(false) // 抽屉的是否展示
|
||||||
|
|
||||||
|
const tableData = ref<any[]>([]) //表格数据
|
||||||
|
const baseTask = ref<object>({})
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (task: any) => {
|
||||||
|
if (isEmpty(task.children)) {
|
||||||
|
message.warning('该任务没有子任务')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
baseTask.value = task
|
||||||
|
//设置表格数据
|
||||||
|
tableData.value = task.children
|
||||||
|
//展开抽屉
|
||||||
|
drawerVisible.value = true
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||||
|
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 减签
|
||||||
|
*/
|
||||||
|
const taskSubSignDialogForm = ref()
|
||||||
|
const handleSubSign = (item) => {
|
||||||
|
taskSubSignDialogForm.value.open(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示减签按钮
|
||||||
|
* @param task
|
||||||
|
*/
|
||||||
|
const showSubSignButton = (task:any) => {
|
||||||
|
if(!isEmpty(task.children)){
|
||||||
|
//有子任务,且子任务有任意一个是 待处理 和 待前置任务完成 则显示减签按钮
|
||||||
|
const subTask = task.children.find((item) => item.result === 1 || item.result === 9)
|
||||||
|
return !isEmpty(subTask)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
</script>
|
@ -12,7 +12,18 @@
|
|||||||
:icon="getTimelineItemIcon(item)"
|
:icon="getTimelineItemIcon(item)"
|
||||||
:type="getTimelineItemType(item)"
|
:type="getTimelineItemType(item)"
|
||||||
>
|
>
|
||||||
<p style="font-weight: 700">任务:{{ item.name }}</p>
|
<p style="font-weight: 700">
|
||||||
|
任务:{{ item.name }}
|
||||||
|
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="item.result" />
|
||||||
|
<el-button
|
||||||
|
style="margin-left: 5px"
|
||||||
|
v-if="!isEmpty(item.children)"
|
||||||
|
@click="openChildrenTask(item)"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:memo" />
|
||||||
|
子任务
|
||||||
|
</el-button>
|
||||||
|
</p>
|
||||||
<el-card :body-style="{ padding: '10px' }">
|
<el-card :body-style="{ padding: '10px' }">
|
||||||
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
|
<label v-if="item.assigneeUser" style="margin-right: 30px; font-weight: normal">
|
||||||
审批人:{{ item.assigneeUser.nickname }}
|
审批人:{{ item.assigneeUser.nickname }}
|
||||||
@ -42,11 +53,16 @@
|
|||||||
</el-timeline>
|
</el-timeline>
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<!-- 子任务 -->
|
||||||
|
<ProcessInstanceChildrenTaskList ref="processInstanceChildrenTaskList" />
|
||||||
</el-card>
|
</el-card>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
import { formatDate, formatPast2 } from '@/utils/formatTime'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import ProcessInstanceChildrenTaskList from './ProcessInstanceChildrenTaskList.vue'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
defineOptions({ name: 'BpmProcessInstanceTaskList' })
|
||||||
|
|
||||||
@ -95,6 +111,18 @@ const getTimelineItemType = (item) => {
|
|||||||
if (item.result === 6) {
|
if (item.result === 6) {
|
||||||
return 'default'
|
return 'default'
|
||||||
}
|
}
|
||||||
|
if (item.result === 7 || item.result === 8) {
|
||||||
|
return 'warning'
|
||||||
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子任务
|
||||||
|
*/
|
||||||
|
const processInstanceChildrenTaskList = ref()
|
||||||
|
|
||||||
|
const openChildrenTask = (item) => {
|
||||||
|
processInstanceChildrenTaskList.value.open(item)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="加签" width="500">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="formLoading"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="110px"
|
||||||
|
>
|
||||||
|
<el-form-item label="加签处理人" prop="userIdList">
|
||||||
|
<el-select v-model="formData.userIdList" multiple clearable style="width: 100%">
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="加签理由" prop="reason">
|
||||||
|
<el-input v-model="formData.reason" clearable placeholder="请输入加签理由" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm('before')"
|
||||||
|
>向前加签</el-button
|
||||||
|
>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm('after')"
|
||||||
|
>向后加签</el-button
|
||||||
|
>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as TaskApi from '@/api/bpm/task'
|
||||||
|
import * as UserApi from '@/api/system/user'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
defineOptions({ name: 'BpmTaskUpdateAssigneeForm' })
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const formLoading = ref(false) // 表单的加载中
|
||||||
|
const formData = ref({
|
||||||
|
id: '',
|
||||||
|
userIdList: [],
|
||||||
|
type: ''
|
||||||
|
})
|
||||||
|
const formRules = ref({
|
||||||
|
userIdList: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
|
||||||
|
reason: [{ required: true, message: '加签理由不能为空', trigger: 'change' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const userList = ref<any[]>([]) // 用户列表
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (id: string) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
resetForm()
|
||||||
|
formData.value.id = id
|
||||||
|
// 获得用户列表
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async (type: string) => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
formData.value.type = type
|
||||||
|
try {
|
||||||
|
await TaskApi.taskAddSign(formData.value)
|
||||||
|
message.success('加签成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: '',
|
||||||
|
userIdList: [],
|
||||||
|
type: ''
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog v-model="dialogVisible" title="减签" width="500">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
v-loading="formLoading"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="110px"
|
||||||
|
>
|
||||||
|
<el-form-item label="减签任务" prop="id">
|
||||||
|
<el-radio-group v-model="formData.id">
|
||||||
|
<el-radio-button v-for="item in subTaskList" :key="item.id" :label="item.id">
|
||||||
|
{{ item.name }}({{ item.assigneeUser.deptName }}{{ item.assigneeUser.nickname }}--审批)
|
||||||
|
</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="减签理由" prop="reason">
|
||||||
|
<el-input v-model="formData.reason" clearable placeholder="请输入减签理由" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" name="TaskRollbackDialogForm" setup>
|
||||||
|
import * as TaskApi from '@/api/bpm/task'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const formLoading = ref(false) // 表单的加载中
|
||||||
|
const formData = ref({
|
||||||
|
id: '',
|
||||||
|
reason: ''
|
||||||
|
})
|
||||||
|
const formRules = ref({
|
||||||
|
id: [{ required: true, message: '必须选择减签任务', trigger: 'change' }],
|
||||||
|
reason: [{ required: true, message: '减签理由不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const subTaskList = ref([])
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (id: string) => {
|
||||||
|
subTaskList.value = await TaskApi.getChildrenTaskList(id)
|
||||||
|
if (isEmpty(subTaskList.value)) {
|
||||||
|
message.warning('当前没有可减签的任务')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
dialogVisible.value = true
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 openModal 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
if (!formRef) return
|
||||||
|
const valid = await formRef.value.validate()
|
||||||
|
if (!valid) return
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
await TaskApi.taskSubSign(formData.value)
|
||||||
|
message.success('减签成功')
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: '',
|
||||||
|
reason: ''
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
</script>
|
@ -49,6 +49,10 @@
|
|||||||
<Icon icon="ep:position" />
|
<Icon icon="ep:position" />
|
||||||
委派
|
委派
|
||||||
</el-button>
|
</el-button>
|
||||||
|
<el-button type="primary" @click="handleSign(item)">
|
||||||
|
<Icon icon="ep:plus" />
|
||||||
|
加签
|
||||||
|
</el-button>
|
||||||
<el-button type="warning" @click="handleBack(item)">
|
<el-button type="warning" @click="handleBack(item)">
|
||||||
<Icon icon="ep:back" />
|
<Icon icon="ep:back" />
|
||||||
回退
|
回退
|
||||||
@ -95,6 +99,8 @@
|
|||||||
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
|
<TaskReturnDialog ref="taskReturnDialogRef" @success="getDetail" />
|
||||||
<!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
<!-- 委派,将任务委派给别人处理,处理完成后,会重新回到原审批人手中-->
|
||||||
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
<TaskDelegateForm ref="taskDelegateForm" @success="getDetail" />
|
||||||
|
<!-- 加签,当前任务审批人为A,向前加签选了一个C,则需要C先审批,然后再是A审批,向后加签B,A审批完,需要B再审批完,才算完成这个任务节点 -->
|
||||||
|
<TaskAddSignDialogForm ref="taskAddSignDialogForm" @success="getDetail" />
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@ -109,7 +115,9 @@ import ProcessInstanceBpmnViewer from './ProcessInstanceBpmnViewer.vue'
|
|||||||
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
import ProcessInstanceTaskList from './ProcessInstanceTaskList.vue'
|
||||||
import TaskReturnDialog from './TaskReturnDialogForm.vue'
|
import TaskReturnDialog from './TaskReturnDialogForm.vue'
|
||||||
import TaskDelegateForm from './taskDelegateForm.vue'
|
import TaskDelegateForm from './taskDelegateForm.vue'
|
||||||
|
import TaskAddSignDialogForm from './TaskAddSignDialogForm.vue'
|
||||||
import { registerComponent } from '@/utils/routerHelper'
|
import { registerComponent } from '@/utils/routerHelper'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
|
||||||
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
defineOptions({ name: 'BpmProcessInstanceDetail' })
|
||||||
|
|
||||||
@ -185,6 +193,12 @@ const handleBack = async (task) => {
|
|||||||
taskReturnDialogRef.value.open(task.id)
|
taskReturnDialogRef.value.open(task.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const taskAddSignDialogForm = ref()
|
||||||
|
/** 处理审批加签的操作 */
|
||||||
|
const handleSign = async (task) => {
|
||||||
|
taskAddSignDialogForm.value.open(task.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 获得详情 */
|
/** 获得详情 */
|
||||||
const getDetail = () => {
|
const getDetail = () => {
|
||||||
// 1. 获得流程实例相关
|
// 1. 获得流程实例相关
|
||||||
@ -261,26 +275,36 @@ const getTaskList = async () => {
|
|||||||
// 获得需要自己审批的任务
|
// 获得需要自己审批的任务
|
||||||
runningTasks.value = []
|
runningTasks.value = []
|
||||||
auditForms.value = []
|
auditForms.value = []
|
||||||
tasks.value.forEach((task) => {
|
loadRunningTask(tasks.value)
|
||||||
// 2.1 只有待处理才需要
|
|
||||||
if (task.result !== 1 && task.result !== 6) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2.2 自己不是处理人
|
|
||||||
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// 2.3 添加到处理任务
|
|
||||||
runningTasks.value.push({ ...task })
|
|
||||||
auditForms.value.push({
|
|
||||||
reason: ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} finally {
|
} finally {
|
||||||
tasksLoad.value = false
|
tasksLoad.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 runningTasks 中的任务
|
||||||
|
*/
|
||||||
|
const loadRunningTask = (tasks) => {
|
||||||
|
tasks.forEach((task) => {
|
||||||
|
if (!isEmpty(task.children)) {
|
||||||
|
loadRunningTask(task.children)
|
||||||
|
}
|
||||||
|
// 2.1 只有待处理才需要
|
||||||
|
if (task.result !== 1 && task.result !== 6) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.2 自己不是处理人
|
||||||
|
if (!task.assigneeUser || task.assigneeUser.id !== userId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2.3 添加到处理任务
|
||||||
|
runningTasks.value.push({ ...task })
|
||||||
|
auditForms.value.push({
|
||||||
|
reason: ''
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 */
|
/** 初始化 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDetail()
|
getDetail()
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div
|
<div
|
||||||
class="h-[50px] w-[50px] flex items-center justify-center"
|
class="h-[50px] w-[50px] flex items-center justify-center"
|
||||||
style="color: rgb(24, 144, 255); background-color: rgba(24, 144, 255, 0.1)"
|
style="color: rgb(24 144 255); background-color: rgb(24 144 255 / 10%)"
|
||||||
>
|
>
|
||||||
<Icon :size="23" icon="fa:user-times" />
|
<Icon :size="23" icon="fa:user-times" />
|
||||||
</div>
|
</div>
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div
|
<div
|
||||||
class="h-[50px] w-[50px] flex items-center justify-center"
|
class="h-[50px] w-[50px] flex items-center justify-center"
|
||||||
style="color: rgb(162, 119, 255); background-color: rgba(162, 119, 255, 0.1)"
|
style="color: rgb(162 119 255); background-color: rgb(162 119 255 / 10%)"
|
||||||
>
|
>
|
||||||
<Icon :size="23" icon="fa:user-plus" />
|
<Icon :size="23" icon="fa:user-plus" />
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div
|
<div
|
||||||
class="h-[50px] w-[50px] flex items-center justify-center"
|
class="h-[50px] w-[50px] flex items-center justify-center"
|
||||||
style="color: rgb(162, 119, 255); background-color: rgba(162, 119, 255, 0.1)"
|
style="color: rgb(162 119 255); background-color: rgb(162 119 255 / 10%)"
|
||||||
>
|
>
|
||||||
<Icon :size="23" icon="fa:user-plus" />
|
<Icon :size="23" icon="fa:user-plus" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -307,7 +307,7 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 时间线样式调整
|
// 时间线样式调整
|
||||||
:deep(.el-timeline) {
|
:deep(.el-timeline) {
|
||||||
margin: 10px 0px 0px 160px;
|
margin: 10px 0 0 160px;
|
||||||
|
|
||||||
.el-timeline-item__wrapper {
|
.el-timeline-item__wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -328,27 +328,27 @@ onMounted(async () => {
|
|||||||
background-color: #f7f8fa;
|
background-color: #f7f8fa;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: '';
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 13px;
|
left: 13px;
|
||||||
border-width: 8px; /* 调整尖角大小 */
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
|
border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 8px; /* 调整尖角大小 */
|
||||||
|
content: '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot-node-style {
|
.dot-node-style {
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 50%;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -122,7 +122,7 @@
|
|||||||
<el-image
|
<el-image
|
||||||
v-if="scope.row.accountQrCodeUrl"
|
v-if="scope.row.accountQrCodeUrl"
|
||||||
:src="scope.row.accountQrCodeUrl"
|
:src="scope.row.accountQrCodeUrl"
|
||||||
class="w-40px h-40px"
|
class="h-40px w-40px"
|
||||||
:preview-src-list="[scope.row.accountQrCodeUrl]"
|
:preview-src-list="[scope.row.accountQrCodeUrl]"
|
||||||
preview-teleported
|
preview-teleported
|
||||||
/>
|
/>
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
<el-form-item label="退款理由" prop="afterSaleRefundReasons">
|
<el-form-item label="退款理由" prop="afterSaleRefundReasons">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="formData.afterSaleRefundReasons"
|
v-model="formData.afterSaleRefundReasons"
|
||||||
allow-create
|
|
||||||
filterable
|
filterable
|
||||||
multiple
|
multiple
|
||||||
|
allow-create
|
||||||
placeholder="请直接输入退款理由"
|
placeholder="请直接输入退款理由"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
|
@ -395,27 +395,27 @@ onMounted(async () => {
|
|||||||
background-color: #f7f8fa;
|
background-color: #f7f8fa;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: ''; /* 必须设置 content 属性 */
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 10px;
|
||||||
left: 13px; /* 将伪元素水平居中 */
|
left: 13px; /* 将伪元素水平居中 */
|
||||||
border-width: 8px; /* 调整尖角大小 */
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
|
border-color: transparent #f7f8fa transparent transparent; /* 尖角颜色,左侧朝向 */
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 8px; /* 调整尖角大小 */
|
||||||
|
content: ''; /* 必须设置 content 属性 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot-node-style {
|
.dot-node-style {
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -5px;
|
left: -5px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 50%;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 50%;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -192,7 +192,7 @@
|
|||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<el-image
|
<el-image
|
||||||
:src="row.picUrl"
|
:src="row.picUrl"
|
||||||
class="w-30px h-30px mr-10px"
|
class="mr-10px h-30px w-30px"
|
||||||
@click="imagePreview(row.picUrl)"
|
@click="imagePreview(row.picUrl)"
|
||||||
/>
|
/>
|
||||||
<span class="mr-10px">{{ row.spuName }}</span>
|
<span class="mr-10px">{{ row.spuName }}</span>
|
||||||
|
@ -188,6 +188,7 @@ const refreshMenu = async () => {
|
|||||||
try {
|
try {
|
||||||
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
|
await message.confirm('即将更新缓存刷新浏览器!', '刷新菜单缓存')
|
||||||
// 清空,从而触发刷新
|
// 清空,从而触发刷新
|
||||||
|
wsCache.delete(CACHE_KEY.USER)
|
||||||
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
wsCache.delete(CACHE_KEY.ROLE_ROUTERS)
|
||||||
// 刷新浏览器
|
// 刷新浏览器
|
||||||
location.reload()
|
location.reload()
|
||||||
|
Loading…
Reference in New Issue
Block a user