Commit aa99c537 by YunaiV

Merge branch 'master' of https://gitee.com/yudaocode/yudao-ui-admin-vue3 into dev

# Conflicts:
#	src/views/mall/statistics/member/index.vue
parents 66ec6108 0242c841
...@@ -39,14 +39,14 @@ ...@@ -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 |
......
...@@ -31,31 +31,31 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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) => { ...@@ -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) => { ...@@ -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 = { ...@@ -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) => { ...@@ -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) => { ...@@ -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( ...@@ -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( ...@@ -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; ...@@ -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 { ...@@ -141,6 +141,7 @@ export default {
}, },
router: { router: {
login: '登录', login: '登录',
socialLogin: '社交登录',
home: '首页', home: '首页',
analysis: '分析页', analysis: '分析页',
workplace: '工作台' workplace: '工作台'
......
...@@ -196,6 +196,16 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -196,6 +196,16 @@ const remainingRouter: AppRouteRecordRaw[] = [
} }
}, },
{ {
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'),
name: 'NoAccess', name: 'NoAccess',
...@@ -333,6 +343,7 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -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[] = [ ...@@ -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[] = [ ...@@ -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 => { ...@@ -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> => { ...@@ -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 => { ...@@ -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
}
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({ ...@@ -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 ...@@ -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
} }
......
...@@ -284,8 +284,13 @@ const doSocialLogin = async (type: number) => { ...@@ -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
......
<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 @@ ...@@ -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 @@ ...@@ -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) => { ...@@ -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>
<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>
<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 @@ ...@@ -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 @@ ...@@ -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' ...@@ -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) => { ...@@ -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,7 +275,20 @@ const getTaskList = async () => { ...@@ -261,7 +275,20 @@ const getTaskList = async () => {
// 获得需要自己审批的任务 // 获得需要自己审批的任务
runningTasks.value = [] runningTasks.value = []
auditForms.value = [] auditForms.value = []
tasks.value.forEach((task) => { loadRunningTask(tasks.value)
} finally {
tasksLoad.value = false
}
}
/**
* 设置 runningTasks 中的任务
*/
const loadRunningTask = (tasks) => {
tasks.forEach((task) => {
if (!isEmpty(task.children)) {
loadRunningTask(task.children)
}
// 2.1 只有待处理才需要 // 2.1 只有待处理才需要
if (task.result !== 1 && task.result !== 6) { if (task.result !== 1 && task.result !== 6) {
return return
...@@ -276,9 +303,6 @@ const getTaskList = async () => { ...@@ -276,9 +303,6 @@ const getTaskList = async () => {
reason: '' reason: ''
}) })
}) })
} finally {
tasksLoad.value = false
}
} }
/** 初始化 */ /** 初始化 */
......
...@@ -6,7 +6,7 @@ ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 () => { ...@@ -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 () => { ...@@ -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 @@ ...@@ -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 @@ ...@@ -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 () => { ...@@ -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 @@ ...@@ -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 () => { ...@@ -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()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment