Kaynağa Gözat

Merge remote-tracking branch 'origin/master'

lanceJiang 1 yıl önce
ebeveyn
işleme
905a1bfcb7

+ 10 - 1
src/api/flow/definition.ts

@@ -5,7 +5,7 @@ import { AxiosPromise } from 'axios'
 const api = {
 	listCategory: '/v1/process/list-category', // 获取所有分类流程定义列表
 	updateStateId: '/v1/process/update-state', // 根据流程定义ID更新流程状态
-	updateSortId: '/v1/process/update-sort', // 根据流程定义ID更新排序
+	updateProcessSort: '/v1/process/sort', // 流程组排序
 	delete: '/v1/process/delete', // 根据流程定义ID删除流程定义相关信息
 	create: '/v1/process/create', // 创建添加
 	get: '/v1/process/get', // 查询Id信息
@@ -27,6 +27,14 @@ function flowDefinitionUpdateStateIdApi(data: any): AxiosPromise {
 	})
 }
 
+function updateProcessSortApi(data: any): AxiosPromise {
+	return request({
+		url: api.updateProcessSort,
+		method: 'post',
+		data
+	})
+}
+
 function flowDefinitionUpdateSortIdApi(data: any): AxiosPromise {
 	return request({
 		url: `${api.updateStateId}-${data.id}`,
@@ -68,6 +76,7 @@ function flowDefinitionCloneApi(data: any): AxiosPromise {
 export default {
 	flowDefinitionListCategoryApi,
 	flowDefinitionUpdateStateIdApi,
+	updateProcessSortApi,
 	flowDefinitionUpdateSortIdApi,
 	flowDefinitionDeleteApi,
 	flowDefinitionCreateApi,

+ 12 - 3
src/api/flow/process.ts

@@ -7,13 +7,14 @@ const api = {
 	delete: '/v1/process/delete', // 删除流程
 	listCategory: '/v1/process/list-category', // 流程分类定义列表列表
 	clone: '/v1/process/clone', // 复制流程
-	updateProcessState: 'v1/process/update-state', // 更新流程状态
+	updateProcessState: '/v1/process/update-state', // 更新流程状态
 	detailProcess: '/v1/process/get', // 流程详情
 	launchProcessList: '/v1/process/list-launch', // 发起审批流程列表
 	releaseProcess: '/v1/process/release', // 发布流程
 	processListNodeMap: '/v1/process/list-node-map', // 根据 id 获取节点 map 列表
 	processNodeModel: '/v1/process/node-model', // 根据 id 获取模型
-	processLaunch: '/v1/process/launch' // 发起流程
+	processLaunch: '/v1/process/launch', // 发起流程
+	childProcessTop10: '/v1/process/list-child-top10' // 查询满足条件的前10条子流程列表
 }
 
 function progressCreateApi(data: any): AxiosPromise {
@@ -88,6 +89,13 @@ function processUpdateStateApi(data: any): AxiosPromise {
 	})
 }
 
+function childProcessTop10Api(data: any): AxiosPromise {
+	return request({
+		url: `${api.childProcessTop10}?keyword=${data.keyword}`,
+		method: 'post'
+	})
+}
+
 function processDetailApi(id: any): AxiosPromise {
 	return request({
 		url: `${api.detailProcess}?id=${id}`,
@@ -106,5 +114,6 @@ export default {
 	processUpdateStateApi,
 	processDetailApi,
 	launchProcessListApi,
-	releaseProcessApi
+	releaseProcessApi,
+	childProcessTop10Api
 }

+ 48 - 0
src/api/test/purchaseOrder.ts

@@ -0,0 +1,48 @@
+import request from '@/utils/request'
+import { AxiosPromise } from 'axios'
+
+// apiUrl 采购订单管理
+const api = {
+	page: '/v1/purchase-order/page',
+	create: '/v1/purchase-order/create',
+	update: '/v1/purchase-order/update',
+	delete: '/v1/purchase-order/delete'
+}
+
+/**
+ * 采购订单管理 - 列表
+ */
+function postPageApi(data: any): AxiosPromise {
+	return request({
+		url: api.page,
+		method: 'post',
+		data
+	})
+}
+
+/**
+ * 采购订单管理 - 新增编辑保存
+ */
+function postAddOrEditSaveApi(data: any): AxiosPromise {
+	return request({
+		url: data.id ? api.update : api.create,
+		method: 'post',
+		data
+	})
+}
+
+/**
+ * 采购订单管理 - 删除
+ */
+function postDeleteApi(data: any): AxiosPromise {
+	return request({
+		url: api.delete,
+		method: 'post',
+		data
+	})
+}
+export default {
+	postPageApi,
+	postAddOrEditSaveApi,
+	postDeleteApi
+}

+ 1 - 0
src/assets/icons/auth-product.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M512 924.458667L0 380.416l195.541333-280.874667h632.917334L1024 380.416l-512 544z" fill="#388FDF"></path><path d="M303.274667 380.416L512 924.416 0 380.458667h303.274667z" fill="#388FDF"></path><path d="M0 380.416l195.541333-280.874667h632.917334L1024 380.416H0z" fill="#489CEA" p-id="5482"></path><path d="M0 380.416l195.541333-280.874667 107.733334 280.874667H0z" fill="#489CEA"></path><path d="M195.541333 99.541333L512 924.458667l316.458667-824.917334H195.541333z" fill="#83BBFF"></path><path d="M303.274667 380.416h417.450666L512 99.541333 303.274667 380.416z" fill="#B7D7FF"></path><path d="M303.274667 380.416L195.584 99.541333H512L303.274667 380.416z" fill="#83BBFF"></path><path d="M512 924.458667L303.274667 380.416h417.450666L512 924.416z" fill="#489CEA"></path></svg>

+ 0 - 1
src/assets/icons/authProduct.svg

@@ -1 +0,0 @@
-<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1713131183567" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5479" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16"><path d="M512 924.458667L0 380.416l195.541333-280.874667h632.917334L1024 380.416l-512 544z" fill="#388FDF" p-id="5480"></path><path d="M303.274667 380.416L512 924.416 0 380.458667h303.274667z" fill="#388FDF" p-id="5481"></path><path d="M0 380.416l195.541333-280.874667h632.917334L1024 380.416H0z" fill="#489CEA" p-id="5482"></path><path d="M0 380.416l195.541333-280.874667 107.733334 280.874667H0z" fill="#489CEA" p-id="5483"></path><path d="M195.541333 99.541333L512 924.458667l316.458667-824.917334H195.541333z" fill="#83BBFF" p-id="5484"></path><path d="M303.274667 380.416h417.450666L512 99.541333 303.274667 380.416z" fill="#B7D7FF" p-id="5485"></path><path d="M303.274667 380.416L195.584 99.541333H512L303.274667 380.416z" fill="#83BBFF" p-id="5486"></path><path d="M512 924.458667L303.274667 380.416h417.450666L512 924.416z" fill="#489CEA" p-id="5487"></path></svg>

+ 1 - 0
src/assets/icons/auto-copy.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 1024a512 512 0 1 0 0-1024 512 512 0 1 0 0 1024z m-113.2-398.8c62.4 62.4 163.8 62.4 226.2 0 18.8-18.8 49.2-18.8 67.8 0s18.8 49.2 0 67.8c-100 100-262 100-362 0s-100-262 0-362 262-100 362 0c18.8 18.8 18.8 49.2 0 67.8s-49.2 18.8-67.8 0c-62.4-62.4-163.8-62.4-226.2 0s-62.4 163.8 0 226.2z" fill="#17abe3"></path></svg>

+ 1 - 0
src/assets/icons/auto-jump.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" id="mx_n_1717822744090" width="200" height="200"><path d="M511.441658 3.031002c-281.883159 0-510.484499 228.441813-510.4845 510.484499s228.441813 510.484499 510.4845 510.484499 510.484499-228.441813 510.484499-510.484499-228.60134-510.484499-510.484499-510.484499z m251.333852 512.478579L575.172457 700.959028c-3.908397 3.908397-9.172768 5.184608-14.43714 2.552422-5.264371-2.552422-7.896557-6.460819-7.896557-11.645427V566.079452c-43.311419 5.184608-178.350522 31.107649-234.82287 130.971179-1.276211 5.184608-6.540583 7.816794-10.528742 7.816794h-3.908397c-5.264371-1.276211-9.172768-7.816794-9.172769-13.001402 0-2.632186 17.069325-220.465493 258.432778-241.203926V322.323103c0-5.184608 2.632186-10.369216 7.896557-11.645427 5.264371-2.632186 10.528743-1.276211 14.43714 2.552422l187.52329 184.173236c5.344135 5.184608 5.344135 12.921639 0.079763 18.106247z" fill="#db639b"></path></svg>

+ 1 - 0
src/assets/icons/cancel.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 48 48">	<path fill="#e6a23c" d="M44,24c0,11.045-8.955,20-20,20S4,35.045,4,24S12.955,4,24,4S44,12.955,44,24z"></path><path fill="#fff" d="M29.656,15.516l2.828,2.828l-14.14,14.14l-2.828-2.828L29.656,15.516z"></path><path fill="#fff" d="M32.484,29.656l-2.828,2.828l-14.14-14.14l2.828-2.828L32.484,29.656z"></path></svg>

+ 1 - 0
src/assets/icons/comment.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M511.994151 0C229.204236 0 0 199.644966 0 445.927371c0 246.288254 229.210084 445.927371 512 445.927371v132.145258s311.565461-217.781748 365.952411-266.214734c90.327138-80.355125 146.053438-190.497618 146.053438-311.863744C1024 199.644966 794.784067 0 511.994151 0m253.072732 514.49739c-37.706492 0-68.283433-29.570967-68.283433-66.06678 0-36.431478 30.576941-66.002445 68.283433-66.002445 37.724038 0 68.254189 29.570967 68.254189 66.002445-0.005849 36.495813-30.530152 66.06678-68.254189 66.06678m-255.078832 0c-37.706492 0-68.283433-29.570967-68.283433-66.06678 0-36.431478 30.576941-66.002445 68.283433-66.002445 37.712341 0 68.300979 29.570967 68.300979 66.002445 0 36.495813-30.58279 66.06678-68.300979 66.06678m-257.055688 0c-37.648005 0-68.219097-29.570967-68.219097-66.06678 0-36.431478 30.571092-66.002445 68.219097-66.002445 37.706492 0 68.277584 29.570967 68.277584 66.002445-0.005849 36.495813-30.576941 66.06678-68.277584 66.06678" fill="#00B42A"></path></svg>

+ 1 - 0
src/assets/icons/in-progress.svg

@@ -0,0 +1 @@
+<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="200" height="200"><path d="M512 512m-448 0a448 448 0 1 0 896 0 448 448 0 1 0-896 0Z" fill="#00B42A"></path><path d="M448 288a32 32 0 0 1 32-32H512a32 32 0 0 1 32 32v256A32 32 0 0 1 512 576h-32a32 32 0 0 1-32-32v-256z" fill="#FFFFFF"></path><path d="M704 480a32 32 0 0 1 32 32v32a32 32 0 0 1-32 32H480a32 32 0 0 1-32-32V512a32 32 0 0 1 32-32H704z" fill="#FFFFFF"></path></svg>

+ 20 - 5
src/components/Flow/FlowTypeDot.vue

@@ -17,14 +17,29 @@
 
 		<!-- 状态 -->
 		<div class="badge">
-			<template v-if="type === 3">
+			<template v-if="type === 0">
+				<svg-icon icon-class="comment" />
+			</template>
+			<template v-else-if="type === 1">
+				<svg-icon icon-class="approval" />
+			</template>
+			<template v-else-if="type === 2">
+				<svg-icon icon-class="auto-copy" />
+			</template>
+			<template v-else-if="type === 3">
+				<svg-icon icon-class="approval" />
+			</template>
+			<template v-else-if="type === 4">
 				<svg-icon icon-class="reject" />
 			</template>
-			<template v-else-if="status === 1">
-				<svg-icon icon-class="active" color="#2a5eff" />
+			<template v-else-if="type === 15">
+				<svg-icon icon-class="cancel" />
+			</template>
+			<template v-else-if="type === 19">
+				<svg-icon icon-class="auto-jump" />
 			</template>
 			<template v-else>
-				<svg-icon icon-class="approval" />
+				<svg-icon icon-class="in-progress" />
 			</template>
 		</div>
 	</div>
@@ -33,7 +48,7 @@
 <script setup lang="ts">
 import SvgIcon from '../SvgIcon/index.vue'
 import FlowNodeAvatar from './FlowNodeAvatar.vue'
-import { TaskTypeEnum, TaskStatusEnum } from './enums'
+import { TaskTypeEnum } from './enums'
 
 defineProps({
 	status: { type: Number, default: 0 },

+ 1 - 8
src/components/scWorkflow/nodes/addNode.vue

@@ -141,14 +141,7 @@ export default {
 					nodeName: '子流程',
 					nodeKey: getNodeKey(),
 					type: 8,
-					delayType: '1', // 延时类型
-					triggerType: '1', // 触发器类型: 立即执行('1') 延时执行('2')
-					// 一小时后触发 {"time": "1:h"} 单位【 d 天 h 时 m 分 】 发起后一小时三十分后触发 {"time": "01:30:00"}
-					extendConfig: {
-						time: '', // 立即执行 time不用设置
-						args: '',
-						trigger: ''
-					},
+          callProcess: undefined, // 设置 id:name  格式
 					childNode: this.modelValue
 				}
 			}

+ 72 - 26
src/components/scWorkflow/nodes/subProcess.vue

@@ -51,11 +51,20 @@
 					<div v-show="radio1 === '1'">
 						<div class=""><el-text class="mx-1">选择子流程</el-text></div>
 						<div>
-							<el-select v-model="subProcessValue" placeholder="选择子流程" style="width: 240px; margin: 10px 20px 10px 0px">
-								<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+							<el-select
+								v-model="form.subProcessValue"
+								placeholder="选择子流程"
+								filterable
+								remote
+								:remote-method="fetchData"
+								:loading="loading"
+								style="width: 240px; margin: 10px 20px 10px 0px"
+								@change="chooseProcess"
+							>
+								<el-option v-for="item in options" :key="item.id" :label="item.processName" :value="item.id" />
 							</el-select>
 
-							<el-link :underline="false"
+							<el-link :underline="false" @click="openViewEv"
 								><el-icon class="el-icon--right"><View /></el-icon> 预览子流程</el-link
 							>
 						</div>
@@ -94,6 +103,7 @@ import { subProcessType } from './config'
 import { ElMessage } from 'element-plus'
 import { mapState } from 'pinia'
 import useFlowStore from '@/store/modules/flow'
+import process from '@/api/flow/process'
 
 export default {
 	components: {
@@ -114,8 +124,9 @@ export default {
 			form: {},
 			radio1: '1',
 			subProcessType,
-			subProcessValue: '',
-			options: []
+			options: [],
+			loading: false,
+			processName: ''
 		}
 	},
 	watch: {
@@ -127,6 +138,9 @@ export default {
 		this.nodeConfig = this.modelValue
 	},
 	methods: {
+		chooseProcess(e) {
+			this.processName = this.options.filter(i => i.id === e)[0].processName
+		},
 		show() {
 			if (this.disabled) return
 			this.form = {}
@@ -143,6 +157,23 @@ export default {
 				return { label: item.label, id: item.id, opera: opera }
 			})
 			this.form.extendConfig = { formConfig: operateTable }
+			/**
+			 * 将后台给的 callProcess 重新组装
+			 * 拿着id去获取process值,在塞入子流程下拉列中
+			 * @type {*}
+			 */
+			const newCallProcess = this.form?.callProcess
+			if (newCallProcess) {
+				const val = newCallProcess.split(':')
+				this.form.subProcessValue = val[0]
+				this.processName = val[1]
+				this.form.callProcess = `${val[0]}:${val[1]}`
+				this.getProcessDetail(val[0])
+			} else {
+				process.childProcessTop10Api({ keyword: '' }).then(res => {
+					this.options = res || []
+				})
+			}
 			this.drawer = true
 		},
 		editTitle(refName) {
@@ -158,18 +189,10 @@ export default {
 			this.isEditTitle = false
 		},
 		save() {
-			if (!this.form.extendConfig.trigger) {
-				return ElMessage.warning('请填写 TaskTrigger 实现 class')
-			}
-			if (this.form.triggerType === '2' && this.form.delayType === '1' && !Number(this.fixedDuration)) {
-				return ElMessage.warning('等待时间数值最小为1')
+			if (!this.form.subProcessValue) {
+				return ElMessage.warning('请设置子流程')
 			}
-			this.form.extendConfig.time =
-				this.form.triggerType === '2'
-					? this.form.delayType === '1'
-						? `${this.fixedDuration}:${this.fixedDurationType}`
-						: `${this.automaticComputed}`
-					: ''
+			this.form.callProcess = `${this.form.subProcessValue}:${this.processName}`
 			this.$emit('update:modelValue', this.form)
 			this.drawer = false
 		},
@@ -177,19 +200,42 @@ export default {
 			this.$emit('update:modelValue', this.nodeConfig.childNode)
 		},
 		toText(nodeConfig) {
-			const { triggerType, delayType } = nodeConfig
-			if (triggerType === '1') {
-				return `立即执行`
-			} else if (triggerType === '2') {
-				if (delayType === '1') {
-					return `延迟执行,等待${this.fixedDuration}${mapTip[this.fixedDurationType]}`
-				} else if (delayType === '2') {
-					const tip = `延迟执行,至当天`
-					return !this.automaticComputed ? tip : `${tip}${this.automaticComputed}`
-				}
+			const { callProcess } = nodeConfig
+			if (callProcess) {
+				const val = callProcess.split(':')
+				return `${val[1]}`
 			} else {
 				return false
 			}
+		},
+		async fetchData(query) {
+			// 如果输入为空,则不调用接口
+			if (!query) {
+				return
+			}
+			this.loading = true
+			try {
+				const data = await process.childProcessTop10Api({ keyword: query })
+				this.options = data
+				this.loading = false
+			} catch (e) {
+				this.loading = false
+			}
+		},
+		async getProcessDetail(id) {
+			const { processId, processName } = await process.processDetailApi(id)
+			this.options = [{ id: processId, processName }]
+		},
+		openViewEv() {
+			const id = this.form.subProcessValue
+			if (!id) {
+				ElMessage.error('您还没有选择需要预览的子流程')
+				return false
+			}
+			const { origin, hash } = window.location
+			const hashFlag = hash.indexOf('#') !== -1 // 路由是hash模式
+			const jumpRouterUrl = `${origin}/${hashFlag ? '#' : ''}/flow_create/child?id=${id}`
+			window.open(jumpRouterUrl)
 		}
 	},
 	computed: {

+ 6 - 0
src/router/index.ts

@@ -79,6 +79,12 @@ export const sysStaticRouter: Array<AppRouteRecordRaw> = [
 				name: 'flow_create_index',
 				meta: { hidden: true, title: '创建流程', icon: '' }
 			},
+			{
+				path: 'business',
+				component: () => import('@/views/flow/create/business.vue'),
+				name: 'flow_create_business',
+				meta: { hidden: true, title: '创建业务审批', icon: '' }
+			},
 			{
 				path: 'child',
 				component: () => import('@/views/flow/create/child.vue'),

+ 1 - 1
src/styles/index.scss

@@ -157,7 +157,7 @@ div:focus {
 	}
 }
 
-.aside-box {
+.aside-box-system {
 	background-color: var(--el-bg-color) !important;
 	margin-right: 10px;
 }

+ 18 - 0
src/utils/index.ts

@@ -232,6 +232,24 @@ export const format_milliseconds = (milliseconds: number) => {
 	return timeString
 }
 
+/**
+ * 毫秒转分钟 不到一分钟显示秒
+ * @param ms
+ */
+export const millisecondsToMinutesAndSeconds = (milliseconds: number) => {
+	let seconds = Math.floor(milliseconds / 1000); // 先将毫秒转换为秒
+	let minutes = Math.floor(seconds / 60); // 将秒转换为分钟
+	seconds = seconds % 60; // 计算剩余的秒数
+
+	// 如果不到一分钟,则只显示秒
+	if (minutes === 0) {
+		return seconds + '秒';
+	} else {
+		// 否则显示分钟和秒
+		return minutes + '分钟' + (seconds > 0 ? seconds + '秒' : '');
+	}
+}
+
 // 静态图标
 export const ImageArr = [
 	'approval',

+ 4 - 4
src/views/approve/components/approvedContent.vue

@@ -56,7 +56,7 @@
 						<el-timeline style="margin-left: 50px">
 							<el-timeline-item v-for="active in activeData" :key="active.id" hollow :timestamp="active.local_timestamp">
 								<template #dot>
-									<FlowTypeDot :status="active.id ? 0 : 1" :type="active.taskType" :name="active.createBy" />
+									<FlowTypeDot :status="active.id ? 0 : 1" :type="active.type" :name="active.createBy" />
 								</template>
 								<div v-show="active.type === 0" class="timeline-box flex-1">
 									<span style="color: #86909c; display: block; margin-bottom: 3px; padding-left: 4px">评论</span>
@@ -373,9 +373,9 @@ const getTaskDetail = () => {
 			const activeList = data.processApprovals
 			activeList.forEach(v => {
 				v.local_timestamp = v.id && formatTimestamp(v.createTime)
-				const _content = v.content // JSON.parse(v.content || '{}')
-				v.local_nodeUserList = _content.nodeUserList || []
-				v.local_nodeRoleList = _content.nodeRoleList || []
+				const _content = v.content
+				v.local_nodeUserList = _content?.nodeUserList || []
+				v.local_nodeRoleList = _content?.nodeRoleList || []
 				v.local_content = _content?.opinion
 			})
 			activeData.value = activeList

+ 3 - 3
src/views/approve/components/approvedItem.vue

@@ -92,11 +92,11 @@
 							</div>
 							<div v-if="i.duration" class="summary-item">
 								<div class="label">处理耗时:</div>
-								<div class="value">{{ format_milliseconds(i.duration) }}</div>
+								<div class="value">{{ millisecondsToMinutesAndSeconds(i.duration) }}</div>
 							</div>
 						</template>
 
-						<div v-if="i.currentNodeName !== 'complete'" class="summary-item">
+						<div v-if="i.currentNodeName !== 'complete' && i.endTime ==null" class="summary-item">
 							<div class="label">当前所在节点:</div>
 							<div class="value">{{ i.currentNodeName }}</div>
 						</div>
@@ -124,7 +124,7 @@ import FlowNodeAvatar from '@/components/Flow/FlowNodeAvatar.vue'
 // import { Search, Filter, Refresh } from '@element-plus/icons-vue'
 import EditPopover from '@/components/EditPopover.vue'
 import { computed, onMounted, reactive, ref, watch } from 'vue'
-import { format_milliseconds } from '@/utils'
+import { millisecondsToMinutesAndSeconds } from '@/utils'
 import {
 	processTaskPagePendingApprovalApi,
 	processTaskPageMyApplicationApi,

+ 29 - 57
src/views/approve/components/config.ts

@@ -1,21 +1,18 @@
-export type ModelContentConfig = {
-	// 唯一key
+type ModelContentConfig = {
 	nodeKey: string
 	nodeName: string
 	type: number
-	setType: number
 	local_status?: string // 填充自定义状态
 	p_nodeNames?: string[] // 填充自定义状态
+	p_nodeKeys?: string[] // 填充自定义状态
 	childNode: ModelContentConfig | null
 	lastNode_local_status?: string // 结束标记状态标记
 	conditionNodes?: {
-		nodeKey: string
 		nodeName: string
 		local_status?: string // 填充自定义状态
 		conditionList: any[]
 		childNode: ModelContentConfig
 	}[]
-	[key: string]: any
 }
 type RenderNodes = {
 	[key: string]: 0 | 1 // 0已执行 1执行中
@@ -23,87 +20,66 @@ type RenderNodes = {
 export const package_modelContentConfig = (data: ModelContentConfig, renderNodes: RenderNodes) => {
 	/**local_status: 0: 已执行(success) 1:执行中(error) // 默认未执行(info)*/
 	// 通过nodeName 作为唯一值 记录 每个key(nodeName) 对应的childNode 信息
-	const nodeNameObj: { [nodeName: string]: ModelContentConfig } = {}
-	const fn = (data: ModelContentConfig, p_nodeNames = []) => {
-		// console.error(data, p_nodeNames, 'data, p_nodeNames')
+	const nodeKeyObj: { [nodeKey: string]: ModelContentConfig } = {}
+	const fn = (data: ModelContentConfig, p_nodeKeys = []) => {
 		if (!data) return
-		data.p_nodeNames = p_nodeNames
+		data.p_nodeKeys = p_nodeKeys
+		console.log(p_nodeKeys, 'p_nodeKeys')
+		console.log(data, 'data')
 		// 记录
-		nodeNameObj[data.nodeName] = data
-		// const _p_nodeNames = [...p_nodeNames, data.nodeName]
-		const _p_nodeNames = [...p_nodeNames, data.nodeName]
-		// let has_conditionNodes = false
+		nodeKeyObj[data.nodeKey] = data
+		const _p_nodeKeys = [...p_nodeKeys, data.nodeKey]
 		if (data.conditionNodes && Array.isArray(data.conditionNodes)) {
-			// has_conditionNodes = true
 			// 条件分支节点
 			data.conditionNodes.forEach(v => {
-				fn(v.childNode, _p_nodeNames)
+				fn(v.childNode, _p_nodeKeys)
 				// fn(v.childNode, [...p_nodeNames])
 			})
 		}
 		if (data.childNode) {
-			// _p_nodeNames.push(data.nodeName)
-			// const _p_nodeNames = [...p_nodeNames, data.nodeName]
-			// if (!has_conditionNodes) {
-			// 	_p_nodeNames.push(data.nodeName)
-			// }
 			// 正常子节点
-			fn(data.childNode, _p_nodeNames)
+			fn(data.childNode, _p_nodeKeys)
 		}
 	}
 	fn(data)
-	/*Object.keys(nodeNameObj).forEach(k => {
-		const _o = nodeNameObj[k]
-		_o.local_status = 'info'
-	})
+
 	// 给需要修改状态的*/
-	console.log(nodeNameObj, 'nodeNameObj')
+	console.log(nodeKeyObj, 'nodeKeyObj')
 	const local_status_obj = {
 		0: 'success', // 已执行
 		1: 'error' // 执行中
 	}
-	let cur_nodeName = ''
+	let cur_nodeKey = ''
 	/**local_status: 0: 已执行(success) 1:执行中(error) // 默认未执行(info)*/
-	Object.keys(renderNodes).forEach(nodeName => {
-		// console.log(nodeName, 'nodeName')
-		const local_status = local_status_obj[renderNodes[nodeName]] || ''
-		const node = nodeNameObj[nodeName]
+	Object.keys(renderNodes).forEach(nodeKey => {
+		const local_status = local_status_obj[renderNodes[nodeKey]] || ''
+		const node = nodeKeyObj[nodeKey]
 		if (node) {
 			node.local_status = local_status
 			// 执行中的key 提取出来
 			if (node.local_status === 'error') {
-				cur_nodeName = nodeName
+				cur_nodeKey = nodeKey
 			}
 		}
-		if (nodeName === '结束') {
+		if (nodeKey === 'flk_end') {
 			// 给顶级加结束标签状态
-			data.lastNode_local_status = local_status_obj[renderNodes[nodeName]]
+			data.lastNode_local_status = local_status_obj[renderNodes[nodeKey]]
 		}
 	})
 
-	/*const cur_actionKey = Object.keys(nodeNameObj).find(nodeName => {
-		const node = nodeNameObj[nodeName]
-		// 执行中的key 提取出来
-		if (node?.local_status === 'error') {
-			return true
-		}
-	})
-	console.warn(cur_actionKey, '当前标红 cur_actionKey')
-	*/
-	const curNode = nodeNameObj[cur_nodeName] as ModelContentConfig
-	console.warn('当前标红 curNode', curNode)
+	const curNode = nodeKeyObj[cur_nodeKey] as ModelContentConfig
 	// 如果有执行中的 node 给父级做状态渲染
 	if (curNode) {
-		const p_nodeNames = curNode.p_nodeNames || []
-		if (p_nodeNames.length) {
-			p_nodeNames.forEach(nodeName => {
-				const p_node = nodeNameObj[nodeName]
+		const p_nodeKeys = curNode.p_nodeKeys || []
+		if (p_nodeKeys.length) {
+			p_nodeKeys.forEach(nodeKeys => {
+				const p_node = nodeKeyObj[nodeKeys]
 				// 父级node 设置为 已执行
 				// if (!p_node.local_status) {
 				if (p_node) {
 					// 如果是条件节点
 					if (p_node.conditionNodes && Array.isArray(p_node.conditionNodes)) {
-						const idx = p_node.conditionNodes.findIndex(v => v.childNode?.nodeName === curNode.nodeName)
+						const idx = p_node.conditionNodes.findIndex(v => v.childNode?.nodeKey === curNode.nodeKey)
 						p_node.conditionNodes.forEach((v, i) => {
 							if (i === idx) {
 								v.local_status = 'success'
@@ -118,15 +94,11 @@ export const package_modelContentConfig = (data: ModelContentConfig, renderNodes
 				}
 			})
 		}
-	} /*else {
-		// 若没有标红 节点 根据 标绿节点处理额外节点样式
-		// 处理条件节点
-		// 处理结束节点
-	}*/
+	}
 
 	// 给没有标记 local_status 的 默认填充 未处理
-	Object.keys(nodeNameObj).forEach(nodeName => {
-		const node = nodeNameObj[nodeName]
+	Object.keys(nodeKeyObj).forEach(nodeKey => {
+		const node = nodeKeyObj[nodeKey]
 		if (!node.local_status) {
 			node.local_status = 'info'
 		}

+ 1 - 1
src/views/approve/pendingApproval/detail.vue

@@ -37,7 +37,7 @@
 					<el-timeline style="margin-left: 50px">
 						<el-timeline-item v-for="active in activeData" :key="active.id" hollow :timestamp="active.taskName" placement="top">
 							<template #dot>
-								<FlowTypeDot :status="active.taskState" :type="active.taskType" />
+								<FlowTypeDot :status="active.taskState" :type="active.type" />
 							</template>
 
 							<div class="timeline-box flex-1">

+ 320 - 0
src/views/flow/create/business.vue

@@ -0,0 +1,320 @@
+<template>
+	<div class="create-approval">
+		<div class="create-approval-header flex flex_align-center">
+			<div v-if="false" class="create-approval-header-back">
+				<el-icon :size="18">
+					<ArrowLeft />
+				</el-icon>
+			</div>
+			<div class="create-approval-header-left-zh">
+				<div class="create-approval-header-name">{{ processName }}</div>
+				<div v-if="false" class="create-approval-header-time">最近保存:6 分钟前</div>
+			</div>
+			<div class="create-approval-header-tab-list">
+				<div
+					v-for="(item, idx) in componentsArr"
+					:key="idx"
+					class="create-approval-header-tab-item"
+					:class="[item.value === activeTab ? 'active' : '']"
+					@click="activeComponent(idx)"
+				>
+					<span class="create-approval-header-tab-counter">{{ idx + 1 }}</span>
+					<span>{{ item.label }}</span>
+				</div>
+			</div>
+			<div class="create-approval-header-right">
+				<el-button type="primary" @click="submitHandler">发布</el-button>
+			</div>
+		</div>
+		<div class="create-approval-main">
+			<component :is="item.component" v-for="(item, idx) in componentsArr" v-show="item.value === activeTab" ref="compRefs" :key="idx" />
+		</div>
+	</div>
+</template>
+
+<script setup name="flow_create">
+import { computed, nextTick, ref } from 'vue'
+import { storeToRefs } from 'pinia'
+import useFlowStore from '@/store/modules/flow'
+import BasicInfoTab from './components/BasicInfo.vue'
+import FlowDesignTab from './components/FlowDesign.vue'
+import { useRoute, useRouter } from 'vue-router'
+import process from '@/api/flow/process'
+import useStore from '@/store'
+import { ElMessage } from 'element-plus'
+import ExtendSetTab from "@/views/flow/create/components/ExtendSet.vue";
+const { tagsView } = useStore()
+const router = useRouter()
+const route = useRoute()
+const flowStore = useFlowStore()
+const { categoryId, processId, processIcon, processKey, processName, remark, modelContent, processForm, processSetting } = storeToRefs(flowStore)
+
+const compRefs = ref() // 实例化子组件
+const cache_components = ref({})
+const componentsArr = [
+	{
+		component: BasicInfoTab,
+		label: '基础信息',
+		value: '基础信息'
+		// ref: 'basicInfoRef'
+	},
+	{
+		component: FlowDesignTab,
+		label: '流程设计',
+		value: '流程设计'
+		// ref: 'flowDesignRef'
+	},
+	{
+		component: ExtendSetTab,
+		label: '扩展设置',
+		value: '扩展设置'
+		// ref: 'extendSetRef'
+	}
+]
+const activeTab = ref('基础信息')
+const removeCurTab = () => {
+	cache_components.value = {}
+	const _view = tagsView.visitedViews.find(v => v.path === '/flow_create/business')
+	if (_view)
+		tagsView.delView(_view).then(res => {
+			const latestView = res.visitedViews.slice(-1)[0]
+			if (latestView && latestView.fullPath) {
+				router.push(latestView.fullPath)
+			} else {
+				router.push('/')
+			}
+			flowStore.$reset()
+		})
+}
+const submitHandler = async () => {
+	// 基础信息
+	// 表单设计
+	// 流程设计
+	// 扩展设置
+	let leavePageFlag = await validateTabs()
+	// let _id = queryObj.value.id
+	// if (_id) {
+	//
+	// } else {
+	// 	leavePageFlag = await validateTabs()
+	// }
+	if (!leavePageFlag) return
+	const params = {
+		categoryId: categoryId.value,
+		processIcon: processIcon.value,
+		processType: 'business',
+		processKey: processKey.value,
+		processName: processName.value,
+		remark: remark.value,
+		processId: processId.value,
+		processForm: processForm.value,
+		modelContent: JSON.stringify({
+			key: processKey.value,
+			name: processName.value,
+			nodeConfig: JSON.parse(modelContent.value || '{}')
+		}),
+		processSetting: processSetting.value
+	}
+	const res = await process.progressCreateApi(params)
+	ElMessage.success('操作成功')
+	// 创建完成 删除 当前tab页
+	removeCurTab()
+}
+
+// 切换选项卡之前,做相应的保存操作
+const validateTabs = async () => {
+	const _refs = compRefs.value
+	// await nextTick()
+	for (let i = 0; i < _refs.length; i++) {
+		let bool = true
+		/*// 若没开启过的 tab 需要尝试 进行更新数据
+		if (!cache_components.value[i]) {
+			cache_components.value[i]?.updateCompInfo()
+		}*/
+		const _validate =
+			_refs[i]?.validate ||
+			function () {
+				return Promise.resolve()
+			}
+		await _validate().catch(e => {
+			activeTab.value = componentsArr[i].label
+			if (activeTab.value === '表单设计') {
+				ElMessage.error('请为流程设计表单内容')
+			}
+			bool = false
+		})
+		if (!bool) return false
+	}
+	return true
+}
+
+const activeComponent = index => {
+	const cur = componentsArr[index]
+	if (activeTab.value !== cur.value) {
+		if (activeTab.value === '表单设计') {
+			compRefs.value[1]?.exportJsonEv()
+		}
+		// 当前缓存
+		if (!cache_components.value[index]) {
+			// 更新数据
+			const updateCompInfo = compRefs.value[index]?.updateCompInfo
+			// console.error('刷新数据')
+			// console.error(updateCompInfo, 'updateCompInfo')
+			if (updateCompInfo) {
+				updateCompInfo()
+			}
+		}
+		activeTab.value = cur.value
+	}
+}
+
+const queryObj = computed(() => route.query)
+
+const getCurrentProcessDetailEv = () => {
+	let _id = queryObj.value.id
+	if (_id) {
+		cache_components.value = {}
+		process.processDetailApi(_id).then(res => {
+			processId.value = res.processId
+			categoryId.value = res.categoryId
+			processIcon.value = res.processIcon
+			processKey.value = res.processKey
+			processName.value = res.processName
+			remark.value = res.remark
+			let nodeConfig = JSON.parse(res.modelContent).nodeConfig
+			modelContent.value = JSON.stringify(nodeConfig)
+			processForm.value = res.processForm
+			flowStore.setProcessForm(processForm)
+			flowStore.setProcessSetting(res.processSetting)
+			// 默认执行一次保存
+			const _refs = compRefs.value
+			for (let i = 0; i < _refs.length; i++) {
+				const updateCompInfo = compRefs.value[i]?.updateCompInfo
+				if (updateCompInfo) {
+					updateCompInfo()
+				}
+			}
+		})
+	}
+}
+
+getCurrentProcessDetailEv()
+</script>
+
+<style scoped lang="scss">
+.create-approval {
+	height: 100%;
+	min-width: 1200px;
+	min-height: 600px;
+	overflow: auto;
+
+	&-header {
+		justify-content: flex-start;
+		height: 64px;
+		//z-index: 12;
+		position: relative;
+		box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.06);
+		padding-left: 16px;
+		padding-right: 20px;
+
+		&-back {
+			display: flex;
+			justify-content: flex-start;
+			align-items: center;
+			height: 100%;
+			margin-right: 15px;
+		}
+
+		&-left-zh {
+			width: calc(50% - 316.5px);
+			min-width: 248px;
+		}
+
+		&-name {
+			font-weight: 500;
+			font-size: 16px;
+			white-space: nowrap;
+			cursor: pointer;
+			width: -webkit-fit-content;
+			width: -moz-fit-content;
+			width: fit-content;
+			max-width: 100%;
+			height: 24px;
+			overflow: hidden;
+			text-overflow: ellipsis;
+		}
+
+		&-time {
+			width: 100%;
+			height: 20px;
+			line-height: 20px;
+			font-size: 12px;
+			color: #8f959e;
+			overflow: hidden;
+			white-space: nowrap;
+			text-overflow: ellipsis;
+		}
+
+		&-tab-list {
+			margin-left: 10px;
+			flex-shrink: 0;
+			display: flex;
+			justify-content: center;
+		}
+
+		&-tab-item {
+			font-family: PingFang SC, Microsoft YaHei;
+			font-size: 18px;
+			line-height: 28px;
+			color: #646a73;
+			margin-right: 42px;
+			padding: 18px 0;
+			border-bottom: 2px solid transparent;
+			cursor: pointer;
+
+			&.active {
+				// var(--el-color-primary);
+				border-bottom-color: var(--el-color-primary);
+				color: var(--el-color-primary);
+				font-weight: 500;
+
+				.create-approval-header-tab-counter {
+					border-color: var(--el-color-primary);
+					background-color: var(--el-color-primary);
+					color: var(--el-color-white);
+					font-weight: 400;
+				}
+			}
+		}
+
+		&-tab-counter {
+			display: inline-block;
+			margin-right: 6px;
+			width: 24px;
+			height: 24px;
+			border-radius: 50%;
+			border: 1px solid #646a73;
+			font-size: 16px;
+			line-height: 22px;
+			text-align: center;
+		}
+
+		&-right {
+			flex: 1 1;
+			flex-shrink: 0;
+			display: flex;
+			justify-content: flex-end;
+			align-items: center;
+			position: relative;
+			height: 100%;
+			width: -webkit-fit-content;
+			width: -moz-fit-content;
+			width: fit-content;
+		}
+	}
+
+	&-main {
+		height: calc(100% - 64px);
+	}
+}
+</style>

+ 6 - 3
src/views/flow/form/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="pageWrap bgc">
-		<div class="aside-box">
+		<div class="aside-box-system">
 			<el-aside width="200px" style="height: 100%">
 				<el-container style="height: 100%">
 					<el-header>
@@ -465,13 +465,16 @@ const submitTemplateHandler = async params => {
 const addHandler = mode => {
 	isCreate.value = true
 	if (mode !== 'tableTemplate') {
-		activeData.value = {}
+		activeData.value = {
+        status: true
+		}
 		visible.value = true
 		current_mode.value = mode
 	} else {
 		activeDataTemplate.value = {
 			// 设计表单
-			type: 0
+			type: 0,
+			status: true
 		}
 		visibleTemplate.value = true
 	}

+ 107 - 55
src/views/flow/group/components/listGroup.vue

@@ -9,39 +9,59 @@
 				</div>
 			</div>
 		</div>
-		<div v-for="(item, idx) in myArray" :key="item.categoryId" class="group_list">
-			<div class="group_header flex flex-pack-justify flex-align-center">
-				<div class="group_header_title">
-					<span>
-						<template v-if="!item.editor">
-							{{ item.categoryName }}
-						</template>
-						<template v-else>
-							<el-input v-model="item.categoryName" v-autoFocus placeholder="请输入流程组名称" @blur="editFlowGroup('save', item, idx)" />
-						</template>
-					</span>
-				</div>
-				<div class="group_header_nameOperate">
-					<el-space>
-						<el-icon :size="16" class="edit_icon" @click="editFlowGroup('edit', item, idx)"><EditPen /></el-icon>
-					</el-space>
-					<el-space style="margin-left: 10px">
-						<el-tooltip effect="dark" content="删除" placement="top">
-							<el-icon :size="16" @click="deleteFlowGroup(item)"><Delete /></el-icon>
-						</el-tooltip>
-					</el-space>
-				</div>
-			</div>
-			<div class="group_body">
-				<ul class="group_list_ul">
-					<draggable :list="item.processList" item-key="id">
-						<template #item="{ element }">
-							<div>
+		<draggable
+			v-bind="dragOptions"
+			v-model="categoryList"
+			handle=".group_header"
+			item-key="categoryId"
+			@update:model-value="update_item_categoryList"
+		>
+			<template #item="{ element: item, index }">
+				<div class="group_list">
+					<div class="group_header flex flex-pack-justify flex-align-center">
+						<div class="group_header_title">
+							<span>
+								<template v-if="!item.editor">
+									{{ item.categoryName }}
+								</template>
+								<template v-else>
+									<el-input v-model="item.categoryName" v-autoFocus placeholder="请输入流程组名称" @blur="editFlowGroup('save', item, index)" />
+								</template>
+							</span>
+						</div>
+						<div class="group_header_nameOperate">
+							<el-space>
+								<el-icon :size="16" class="edit_icon" @click="editFlowGroup('edit', item, index)"><EditPen /></el-icon>
+							</el-space>
+							<el-space style="margin-left: 10px">
+								<el-tooltip effect="dark" content="删除" placement="top">
+									<el-icon :size="16" @click="deleteFlowGroup(item)"><Delete /></el-icon>
+								</el-tooltip>
+							</el-space>
+						</div>
+					</div>
+					<div class="group_body">
+						<draggable
+							v-model="item.processList"
+							v-bind="dragOptions"
+							group="categoryId"
+							tag="ul"
+							class="group_list_ul"
+							:component-data="{
+								type: 'transition-group',
+								name: !drag ? 'flip-list' : null
+							}"
+							item-key="processId"
+							@update:model-value="update_item_processList"
+							@start="drag = true"
+							@end="drag = false"
+						>
+							<template #item="{ element }">
 								<li class="group_item flex flex-align-center">
 									<LeIcon class="group_itemIcon" :icon-class="`${flowIconPrefix}${element.processIcon}`" />
 									<!--									<div class="group_itemIcon">
-                    <img :src="getAssetsFile(element.processIcon + '.svg')" />
-									</div>-->
+									<img :src="getAssetsFile(element.processIcon + '.svg')" />
+								</div>-->
 									<div class="group_itemLeft">
 										<div class="group_itemNameWrapper flex flex-align-center" style="margin-bottom: 5px">
 											<div class="group_itemName">
@@ -55,24 +75,25 @@
 									<div class="group_itemSeeable">
 										<el-tag round>V{{ element.processVersion }}</el-tag>
 										<el-tag v-if="element.processType === 'child'" type="warning" round>子流程</el-tag>
+										<el-tag v-if="element.processType === 'business'" type="success" round>业务流程</el-tag>
 										<el-tag v-if="element.processState === 0" type="danger" round>已停用</el-tag>
 									</div>
 									<div class="group_itemSeeable">{{ element.processKey }}</div>
 									<div class="group_itemOperations">
 										<el-space wrap>
 											<el-tooltip effect="dark" content="编辑" placement="top">
-												<el-icon :size="16" @click="updateEv(element.processId)"><EditPen /></el-icon>
+												<el-icon :size="16" @click="updateEv(element)"><EditPen /></el-icon>
 											</el-tooltip>
 										</el-space>
-										<el-space wrap style="margin-left: 10px">
-											<el-tooltip effect="dark" content="复制" placement="top">
+										<el-tooltip effect="dark" content="复制" placement="top">
+											<el-space wrap style="margin-left: 10px">
 												<el-popconfirm title="确定复制 ?" @confirm="copyEv(element.processId)">
 													<template #reference>
 														<el-icon :size="16"><CopyDocument /></el-icon>
 													</template>
 												</el-popconfirm>
-											</el-tooltip>
-										</el-space>
+											</el-space>
+										</el-tooltip>
 										<el-space wrap style="margin-left: 10px">
 											<el-tooltip v-if="element.processState === 1" effect="dark" content="禁用" placement="top">
 												<el-icon :size="16" @click="enabledEv(element.processId, 0)"><CircleClose /></el-icon>
@@ -90,29 +111,35 @@
 										</el-space>
 									</div>
 								</li>
-							</div>
-						</template>
-					</draggable>
-				</ul>
-			</div>
-		</div>
+							</template>
+						</draggable>
+					</div>
+				</div>
+			</template>
+		</draggable>
 	</div>
 </template>
 
 <script lang="tsx" setup>
 import Draggable from 'vuedraggable'
-import { Delete, CircleClose, EditPen, CopyDocument } from '@element-plus/icons-vue'
+// import { Delete, CircleClose, EditPen } from '@element-plus/icons-vue'
 import { ref, onActivated, nextTick, onMounted } from 'vue'
 import flowGroup from '@/api/flow/group'
 import flowDefinition from '@/api/flow/definition'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import process from '@/api/flow/process'
 import router from '@/router'
-import { flowIconPrefix /*, getAssetsFile*/ } from '@/utils/index'
-const myArray = ref([])
+import { debounce, flowIconPrefix /*, getAssetsFile*/ } from '@/utils/index'
+const categoryList = ref<any[]>([])
 const hideAddInput = ref(true)
 const listGroupName = ref('')
-
+const dragOptions = {
+	animation: 200,
+	group: 'processId',
+	disabled: false,
+	ghostClass: 'ghost'
+}
+const drag = ref(false)
 // 显示隐藏添加流程组元素
 const showAddInput = () => {
 	hideAddInput.value = !hideAddInput.value
@@ -136,12 +163,32 @@ const addFlowGroup = async () => {
 		console.log(e)
 	}
 }
-
+const update_categoryListSort = debounce(() => {
+	// console.error('刷新数据........', xxx)
+	// {categoryId:string; processIds: string[]}[]
+	flowDefinition.updateProcessSortApi(
+		categoryList.value.map(v => ({
+			categoryId: v.categoryId,
+			processIds: v.processList && v.processList.length ? v.processList.map(_v => _v.processId) : undefined
+		}))
+	)
+	/*.then(res => {
+			console.warn(res, 'res......')
+		})*/
+}, 50)
+const update_item_categoryList = (values: any[]) => {
+	// console.warn(values, 'values update_item_categoryList', categoryList.value)
+	update_categoryListSort()
+}
+const update_item_processList = (values: any[]) => {
+	// console.warn(values, 'values update_item_processList', categoryList.value)
+	update_categoryListSort()
+}
 // 流程组列表
 const flowGroupListAll = async (item = {}) => {
 	try {
-		const data = await flowDefinition.flowDefinitionListCategoryApi(item)
-		myArray.value = data || []
+		const data: any = await flowDefinition.flowDefinitionListCategoryApi(item)
+		categoryList.value = data || []
 		console.log(data)
 	} catch (e) {
 		console.log(e)
@@ -184,15 +231,15 @@ const deleteFlowGroup = (item: any) => {
  */
 const editFlowGroup = async (type, item, idx) => {
 	if (type === 'edit') {
-		myArray.value[idx].editor = true
+		categoryList.value[idx].editor = true
 		return false
 	}
 	try {
-		myArray.value[idx].editor = false
+		categoryList.value[idx].editor = false
 		const param = {
-			id: myArray.value[idx].categoryId,
-			name: myArray.value[idx].categoryName,
-			sort: myArray.value[idx].categorySort
+			id: categoryList.value[idx].categoryId,
+			name: categoryList.value[idx].categoryName
+			// sort: categoryList.value[idx].categorySort
 		}
 		await flowGroup.flowGroupAddOrEditSaveApi(param)
 		flowGroupListAll()
@@ -231,8 +278,9 @@ const copyEv = async (id: any) => {
 }
 
 // 修改
-const updateEv = async (id: any) => {
-	router.push('/flow_create?id=' + id)
+const updateEv = async (element: any) => {
+	const jumpRouterUrl = '/flow_create/' + (element.processType === 'main' ? 'index' : element.processType)
+	router.push(`${jumpRouterUrl}?id=${element.processId}`)
 }
 
 // keep-alive缓存树,防止创建完流程过后,回到当前窗口未刷新
@@ -375,5 +423,9 @@ defineExpose({
 			}
 		}
 	}
+	.ghost {
+		opacity: 0.5;
+		box-shadow: 1px 1px 5px 2px rgba(0, 0, 0, 0.15);
+	}
 }
 </style>

+ 44 - 4
src/views/flow/group/components/sortGroup.vue

@@ -1,19 +1,40 @@
 <template>
 	<div>
-		<draggable :list="myArray" item-key="id" chosen-class="chosen" style="height: 100%" @start="dragging = true" @end="endDraggableEv">
-			<template #item="{ element }">
+		<draggable v-bind="dragOptions" v-model="myArray" handle=".group_header" item-key="categoryId" @update:model-value="update_item_categoryList">
+			<template #item="{ element: item }">
 				<div class="group_list">
 					<div class="group_header flex flex-pack-justify flex-align-center">
-						<div class="group_header_title disabled">{{ element.categoryName }}</div>
+						<div class="group_header_title disabled">{{ item.categoryName }}</div>
 						<div class="group_header_nameOperate">
 							<el-tooltip effect="dark" content="删除" placement="top">
-								<el-icon><Delete @click="deleteItem(element)" /></el-icon>
+								<el-icon><Delete @click="deleteItem(item)" /></el-icon>
 							</el-tooltip>
 						</div>
 					</div>
 				</div>
 			</template>
 		</draggable>
+
+		<!--		<draggable-->
+		<!--			:list="myArray"-->
+		<!--			item-key="id"-->
+		<!--			chosen-class="chosen"-->
+		<!--			style="height: 100%"-->
+		<!--			@start="dragging = true"-->
+		<!--			@end="endDraggableEv">-->
+		<!--			<template #item="{ element }">-->
+		<!--				<div class="group_list">-->
+		<!--					<div class="group_header flex flex-pack-justify flex-align-center">-->
+		<!--						<div class="group_header_title disabled">{{ element.categoryName }}</div>-->
+		<!--						<div class="group_header_nameOperate">-->
+		<!--							<el-tooltip effect="dark" content="删除" placement="top">-->
+		<!--								<el-icon><Delete @click="deleteItem(element)" /></el-icon>-->
+		<!--							</el-tooltip>-->
+		<!--						</div>-->
+		<!--					</div>-->
+		<!--				</div>-->
+		<!--			</template>-->
+		<!--		</draggable>-->
 	</div>
 </template>
 
@@ -24,9 +45,16 @@ import { ref, nextTick, onMounted } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import flowGroup from '@/api/flow/group'
 import flowDefinition from '@/api/flow/definition'
+import { debounce } from '@/utils'
 const dragging = ref(false)
 const myArray = ref([])
 const sortFinallyValue = ref([])
+const dragOptions = {
+	animation: 200,
+	group: 'processId',
+	disabled: false,
+	ghostClass: 'ghost'
+}
 
 const endDraggableEv = () => {
 	dragging.value = false
@@ -74,6 +102,18 @@ const deleteFlowGroup = async item => {
 	}
 }
 
+const update_categoryListSort = debounce(() => {
+	flowDefinition.updateProcessSortApi(
+		myArray.value.map(v => ({
+			categoryId: v.categoryId,
+			processIds: v.processList && v.processList.length ? v.processList.map(_v => _v.processId) : undefined
+		}))
+	)
+}, 50)
+const update_item_categoryList = (values: any[]) => {
+	update_categoryListSort()
+}
+
 // 流程组列表
 const flowGroupListAll = async (item = {}) => {
 	try {

+ 12 - 6
src/views/flow/group/index.vue

@@ -10,9 +10,14 @@
 					</template>
 					<template v-else>
 						<el-button type="primary" plain :icon="CircleCheck" @click="changeComponent('sort')">完 成</el-button>
-						<el-button type="info" plain @click="changeComponent">取 消</el-button>
+						<el-button type="info" plain @click="changeComponent" v-if="false">取 消</el-button>
 					</template>
-					<el-button icon="Plus" @click="createProcessEv('child')">创建子流程</el-button>
+					<el-tooltip content="不可直接审批只能作为流程任务" placement="bottom" effect="light">
+						<el-button icon="Plus" @click="createProcessEv('child')">创建子流程</el-button>
+					</el-tooltip>
+					<el-tooltip content="关联业务逻辑执行的审批流程" placement="bottom" effect="light">
+						<el-button icon="Plus" @click="createProcessEv('business')">创建业务审批</el-button>
+					</el-tooltip>
 					<el-button :type="sortFlag ? 'info' : 'primary'" icon="Plus" :disabled="sortFlag" @click="createProcessEv('index')">创建审批</el-button>
 				</div>
 			</el-header>
@@ -48,10 +53,11 @@ const changeComponent: (sort) => void = async sort => {
 		return
 	}
 	if (pageType.value === 2) {
-		if (sort) {
-			const data = dyncComponent.value.exportSortData()
-			await flowGroup.flowGroupSortApi(data)
-		}
+		// 无效代码
+		// if (sort) {
+		// 	const data = dyncComponent.value.exportSortData()
+		// 	await flowGroup.flowGroupSortApi(data)
+		// }
 		pageType.value = 1
 	}
 }

+ 258 - 0
src/views/flow/test/business.vue

@@ -0,0 +1,258 @@
+<template>
+	<div class="pageWrap bgc">
+		<div class="content-warp flex-column-page-wrap">
+			<!-- 公用搜索组件 -->
+			<LeSearchForm ref="searchForm" v-model:searchData="searchData" :forms="forms" :loading="tableOpts.options.loading"> </LeSearchForm>
+			<div style="margin: 20px; font-size: 1.2em;color: red">
+				演示关联业务审批逻辑,对应 业务流程KEY为 purchaseOrder
+			</div>
+			<!--  LeTable 组件使用  -->
+			<LeTable
+				ref="tableRef"
+				v-model:searchParams="tableOpts.searchParams"
+				v-bind="tableOpts"
+				v-model:curRow="tableOpts.curRow"
+				v-model:checked-options="checkedColumns"
+				:columns="activeColumns"
+			>
+				<template #toolLeft>
+					<el-button type="primary" @click="addHandler">
+						<el-icon class="btn-icon">
+							<Plus />
+						</el-icon>
+					</el-button>
+					<el-button type="danger" :disabled="curSelectionRows.length === 0" @click="batch_del">
+						<el-icon class="btn-icon">
+							<Delete />
+						</el-icon>
+					</el-button>
+				</template>
+
+				<template #statusSlot="scope">
+					<el-tag v-if="scope.row.status === 1" type="danger">已拒绝</el-tag>
+					<el-tag v-else-if="scope.row.status === 2" type="success">已通过</el-tag>
+					<el-tag v-else type="info">待审批</el-tag>
+				</template>
+
+				<template #actionSlot="scope">
+					<el-tooltip content="提交审批" placement="bottom" effect="light">
+						<el-icon class="ibt0" @click="table_edit(scope.row)">
+							<Promotion />
+						</el-icon>
+					</el-tooltip>
+					<el-divider direction="vertical"></el-divider>
+					<el-popconfirm title="确定删除吗?" @confirm="table_del(scope.row)">
+						<template #reference>
+							<el-icon class="ibt0">
+								<Delete />
+							</el-icon>
+						</template>
+					</el-popconfirm>
+				</template>
+			</LeTable>
+		</div>
+
+		<LeFormConfigDialog
+			v-if="visible"
+			ref="dialogUserRef"
+			v-model="visible"
+			title="新增采购订单"
+			width="600px"
+			:form-data="activeData"
+			:form-options="formOptions"
+			@submit="submitHandler"
+		/>
+	</div>
+</template>
+<script lang="tsx" setup>
+import purchaseOrder from '@/api/test/purchaseOrder'
+import { computed, nextTick, ref, watch } from 'vue'
+import { ElMessage, ElMessageBox, ElTree } from 'element-plus'
+import { useTablePage } from '@/hooks/useTablePage'
+import { Plus, Delete } from '@element-plus/icons-vue'
+
+const visible = ref(false) // 弹窗显示隐藏
+const activeData = ref({})
+const formsDialog = [
+	{
+		prop: 'title',
+		label: '标题',
+		itemType: 'input',
+		rules: [{ required: true, message: '请输入采购订单标题', trigger: 'blur' }]
+	},
+	{
+		prop: 'content',
+		label: '内容',
+		rows: "2",
+		type: 'textarea',
+		rules: [{ required: true, message: '请输入采购订单内容', trigger: 'blur' }]
+	}
+]
+// 新增的表单 和 编辑的表单
+const formOptions = computed(() => {
+	return {
+		forms: formsDialog,
+		labelWidth: 120,
+		span: 30,
+		showResetBtn: true,
+		formConfig: {
+			showCancelBtn: true,
+			submitLoading: false
+		}
+	}
+})
+const groupFilterText = ref('')
+const treeRef = ref<InstanceType<typeof ElTree>>()
+
+// 表格搜索条件
+const forms = ref([
+	{
+		prop: 'keyword',
+		label: '采购单名称:',
+		itemType: 'input',
+		placeholder: '请输入采购单名称'
+	}
+])
+
+// table列表数据请求
+const queryList = async () => {
+	const { options, searchParams } = tableOpts
+	options.loading = true
+	try {
+		const { records: list, total } = await purchaseOrder.postPageApi(searchParams)
+		tableOpts.total = total
+		tableOpts.list = list
+	} catch {
+		console.log('获取列表数据失败')
+		tableOpts.total = 0
+		tableOpts.list = []
+		options.loading = false // 更改加载中的 loading值
+	} finally {
+		options.loading = false
+	}
+}
+
+// table 参数
+const columns = [
+	{
+		prop: 'title',
+		label: '标题',
+		minWidth: 80
+	},
+	{
+		prop: 'content',
+		label: '内容',
+		minWidth: 100
+	},
+	{
+		prop: 'status',
+		label: '状态',
+		minWidth: 50,
+		slots: {
+			default: 'statusSlot'
+		}
+	},
+	{
+		prop: 'flwComment',
+		label: '审批内容',
+		minWidth: 100
+	},
+	{
+		prop: 'createBy',
+		label: '创建人',
+		minWidth: 100
+	},
+	{
+		prop: 'createTime',
+		label: '创建时间',
+		minWidth: 126
+	},
+	{
+		prop: 'action',
+		label: '操作',
+		width: 100,
+		fixed: 'right',
+		slots: {
+			default: 'actionSlot'
+		}
+	}
+]
+
+const { searchData, tableOpts, checkedColumns, activeColumns, curSelectionRows, updateParams } = useTablePage(
+	{
+		options: {
+			showIndex: false
+		},
+		// 需要展示的列
+		columns,
+		// 控制列配置
+		columnsConfig: {
+			columns
+		}
+	},
+	{
+		queryList,
+		fetchImmediate: false
+	}
+)
+
+// 删除
+const deleteItem = async ids => {
+	// try {
+	await purchaseOrder.postDeleteApi(ids)
+	ElMessage.error(`删除成功~`)
+	updateParams()
+	// } catch (e) {
+	// 	console.log('删除失败')
+	// 	ElMessage.error(`删除失败~`)
+	// }
+}
+
+// 单个删除
+const table_del = row => {
+	deleteItem([row.id])
+}
+
+//批量删除
+const batch_del = () => {
+	const ids = curSelectionRows.value.map(item => item.id) // 多选数据
+	ElMessageBox.confirm('是否删除选中数据?', '提示', {
+		confirmButtonText: '确认',
+		cancelButtonText: '取消',
+		type: 'error',
+		buttonSize: 'default'
+	}).then(() => {
+		deleteItem(ids)
+	})
+}
+
+// 弹窗事件
+const submitHandler = async params => {
+	formOptions.value.formConfig.submitLoading = true
+	try {
+		params.status = params.status ? 1 : 0
+		params.id = null
+		await purchaseOrder.postAddOrEditSaveApi(params)
+		ElMessage.success(`新增成功~`)
+		visible.value = false
+		updateParams()
+		formOptions.value.formConfig.submitLoading = false
+	} catch (e) {
+		console.log(e)
+		formOptions.value.formConfig.submitLoading = false
+	}
+}
+
+const addHandler = () => {
+	activeData.value = {}
+	visible.value = true
+}
+
+nextTick(() => {
+	queryList()
+})
+
+watch(groupFilterText, val => {
+	treeRef.value!.filter(val)
+})
+</script>

+ 1 - 1
src/views/setting/dict/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="pageWrap bgc">
-		<div class="aside-box">
+		<div class="aside-box-system">
 			<el-aside width="200px" style="height: 100%">
 				<el-container style="height: 100%">
 					<el-header>

+ 2 - 8
src/views/setting/menu/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="pageWrap bgc">
-		<div class="aside-box">
+		<div class="aside-box-system">
 			<el-aside v-loading="menuLoading" width="300px" style="height: 100%">
 				<el-container style="height: 100%">
 					<header style="padding: 13px 15px; border-bottom: 1px solid var(--el-border-color-light)">
@@ -43,7 +43,7 @@
 
 		<el-container class="container-bg">
 			<el-main ref="mainRef" class="nopadding" style="padding: 20px">
-				<save ref="saveRef" :menu="menuList"></save>
+				<save ref="saveRef" :menu="menuList" @success-cb="getMenu()"></save>
 			</el-main>
 		</el-container>
 	</div>
@@ -166,12 +166,6 @@ onMounted(() => {
 </script>
 
 <style scoped lang="scss">
-:deep(.el-aside) {
-	&.aside-box {
-		background-color: var(--el-bg-color-page) !important;
-		margin-right: 10px;
-	}
-}
 
 .nopadding {
 	padding: 0px;

+ 15 - 4
src/views/setting/menu/save.vue

@@ -7,8 +7,13 @@
 			<el-col :lg="12">
 				<h2>{{ i18n_formTitle || '新增菜单' }}</h2>
 				<el-form ref="dialogForm" :model="form" :rules="rules" label-width="80px" label-position="right">
-					<el-form-item label="上级菜单" prop="parentName">
-						<el-input v-model="form.parentName" disabled clearable placeholder="顶级菜单"></el-input>
+					<el-form-item label="上级菜单" prop="pid">
+						<el-cascader
+							v-model="form.pid"
+							:options="menuTreeList"
+							:props="{ value: 'id', label: 'title', children: 'children', checkStrictly: true, emitPath: false }"
+							clearable
+						/>
 					</el-form-item>
 					<el-form-item class="local_form-item_title" label="显示名称" prop="title">
 						<template #label>
@@ -117,7 +122,7 @@
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import resource from '@/api/system/resource'
 import IconPicker from '@/components/IconPicker/index.vue'
 import ScFormTable from '@/components/ScFormTable'
@@ -127,7 +132,6 @@ const dialogForm = ref()
 const form = ref({
 	id: '',
 	pid: '',
-	parentName: '',
 	alias: '',
 	path: '',
 	component: '',
@@ -142,6 +146,7 @@ const form = ref({
 	// 指定父级菜单(路由)名称
 	parentRoute: ''
 })
+const menuTreeList = ref()
 const i18n_formTitle = computed(() => {
 	return generateTitle(form.value.title)
 })
@@ -157,6 +162,7 @@ const apiListAddTemplate = ref({
 	url: ''
 })
 const loading = ref(false)
+const $myEmit = defineEmits(['successCb'])
 
 // methods
 const save = () =>
@@ -167,6 +173,7 @@ const save = () =>
 			try {
 				await resource.resourceAddOrEditSaveApi(form.value)
 				ElMessage.success('保存成功')
+				$myEmit('successCb')
 				loading.value = false
 			} catch (e) {
 				loading.value = false
@@ -187,6 +194,7 @@ const setData = async data => {
 			...data,
 			apiList: res
 		}
+		console.log(form.value, 'form.value =')
 		// dialogForm.value?.validateField()
 		dialogForm.value?.resetFields()
 	} catch (e) {
@@ -194,6 +202,9 @@ const setData = async data => {
 		form.value.id = ''
 		loading.value = false
 	}
+
+	// 上级菜单
+	menuTreeList.value = await resource.resourceListTreeApi()
 }
 
 // 父组件使用的话需要导出

+ 1 - 8
src/views/setting/user/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div class="pageWrap bgc">
-		<div class="aside-box">
+		<div class="aside-box-system">
 			<el-aside width="200px" style="height: 100%">
 				<el-container>
 					<el-header>
@@ -483,13 +483,6 @@ watch(groupFilterText, val => {
 })
 </script>
 <style scoped lang="scss">
-:deep(.el-aside) {
-	&.aside-box {
-		background-color: var(--el-bg-color-page) !important;
-		margin-right: 10px;
-	}
-}
-
 .nopadding {
 	padding: 0px;
 }