瀏覽代碼

Merge branch 'master' of http://39.164.159.226:3000/hnsh-smart-steward/smart-steward-admin

闪电 6 天之前
父節點
當前提交
a0c2d755b2
共有 27 個文件被更改,包括 1865 次插入568 次删除
  1. 11 1
      apps/web-ele/src/api/system/infoEntry/stationInfo/stationInfo.ts
  2. 13 5
      apps/web-ele/src/api/workflow/instance/index.ts
  3. 10 12
      apps/web-ele/src/components/dict/src/index.vue
  4. 23 1
      apps/web-ele/src/router/routes/local.ts
  5. 11 3
      apps/web-ele/src/utils/render.tsx
  6. 3 3
      apps/web-ele/src/views/workflow/components/actions/flow-actions.vue
  7. 6 8
      apps/web-ele/src/views/workflow/components/approval-card.vue
  8. 4 10
      apps/web-ele/src/views/workflow/components/approval-details.vue
  9. 25 33
      apps/web-ele/src/views/workflow/components/approval-panel.vue
  10. 25 7
      apps/web-ele/src/views/workflow/components/approval-timeline.vue
  11. 74 0
      apps/web-ele/src/views/workflow/leave/api/index.ts
  12. 138 0
      apps/web-ele/src/views/workflow/leave/api/model.d.ts
  13. 189 0
      apps/web-ele/src/views/workflow/leave/data.tsx
  14. 33 0
      apps/web-ele/src/views/workflow/leave/hook.ts
  15. 244 0
      apps/web-ele/src/views/workflow/leave/index.vue
  16. 50 0
      apps/web-ele/src/views/workflow/leave/leave-description.vue
  17. 190 0
      apps/web-ele/src/views/workflow/leave/leave-drawer.vue
  18. 197 0
      apps/web-ele/src/views/workflow/leave/leave-form.vue
  19. 43 0
      apps/web-ele/src/views/workflow/leave/leaveEdit.vue
  20. 246 0
      apps/web-ele/src/views/workflow/order/creatOrder.vue
  21. 6 4
      apps/web-ele/src/views/workflow/register.ts
  22. 91 87
      apps/web-ele/src/views/workflow/task/allTaskWaiting.vue
  23. 24 22
      apps/web-ele/src/views/workflow/task/myDocument.vue
  24. 67 124
      apps/web-ele/src/views/workflow/task/taskCopyList.vue
  25. 78 129
      apps/web-ele/src/views/workflow/task/taskFinish.vue
  26. 63 119
      apps/web-ele/src/views/workflow/task/taskWaiting.vue
  27. 1 0
      packages/@core/base/shared/src/constants/dict-enum.ts

+ 11 - 1
apps/web-ele/src/api/system/infoEntry/stationInfo/stationInfo.ts

@@ -15,6 +15,7 @@ enum Api {
15 15
   root = '/sysStation/station',
16 16
   // 查询所有片区信息
17 17
   selectAllSysStationList = '/sysStationArea/area/selectAllSysStationAreaList',
18
+  selectAllSysStation = '/sysStation/station/selectAllSysStationList'
18 19
 }
19 20
 
20 21
 
@@ -26,6 +27,14 @@ enum Api {
26 27
 export function selectAllSysStationAreaList() {
27 28
   return requestClient.get<BaseResult<StationInfoModel[]>>(Api.selectAllSysStationList);
28 29
 }
30
+
31
+/**
32
+ * 查询所有油站信息
33
+ * @returns 油站列表
34
+ */
35
+export function selectAllSysStation() {
36
+  return requestClient.get<BaseResult<StationInfoModel[]>>(Api.selectAllSysStation);
37
+}
29 38
 /**
30 39
  * 查询油站信息列表
31 40
  * @param params 查询参数
@@ -86,4 +95,5 @@ export function deleteStationInfo(ids: number[]) {
86 95
  */
87 96
 export function exportStationInfo(data: Partial<StationInfoModel>) {
88 97
   return commonExport(Api.export, data);
89
-}
98
+}
99
+

+ 13 - 5
apps/web-ele/src/api/workflow/instance/index.ts

@@ -21,7 +21,7 @@ export function getTaskByBusinessId(businessId: string) {
21 21
  * @returns
22 22
  */
23 23
 export function pageByRunning(params?: PageQuery) {
24
-  return requestClient.get('/workflow/instance/pageByRunning', { params });
24
+  return requestClient.get('/workOrder/order/myList', { params });
25 25
 }
26 26
 
27 27
 /**
@@ -70,7 +70,7 @@ export function cancelProcessApply(data: { businessId: ID; message?: string }) {
70 70
  * @param active
71 71
  */
72 72
 export function workflowInstanceActive(instanceId: ID, active: boolean) {
73
-  return requestClient.putWithMsg<void>(
73
+  return requestClient.put<void>(
74 74
     `/workflow/instance/active/${instanceId}?active=${active}`,
75 75
   );
76 76
 }
@@ -81,13 +81,21 @@ export function workflowInstanceActive(instanceId: ID, active: boolean) {
81 81
  * @returns PageResult<Flow>
82 82
  */
83 83
 export function pageByCurrent(params?: PageQuery) {
84
-  return requestClient.get<PageResult<TaskInfo>>(
85
-    '/workflow/instance/pageByCurrent',
84
+  return requestClient.get<PageResult<TaskInfo>>('/workOrder/order/list',
86 85
     { params },
87 86
   );
88 87
 }
89 88
 
90 89
 /**
90
+ * 获取工单详情
91
+ * @param id 工单id
92
+ * @returns TaskInfo
93
+ */
94
+export function getWorkOrderDetail(id: string) {
95
+  return requestClient.get<TaskInfo>(`/workOrder/order/${id}`);
96
+}
97
+
98
+/**
91 99
  * 获取流程图,流程记录
92 100
  * @param businessId 业务标识
93 101
  * @returns 流程图,流程记录
@@ -136,4 +144,4 @@ export function updateFlowVariable(data: {
136 144
     '/workflow/instance/updateVariable',
137 145
     data,
138 146
   );
139
-}
147
+}

+ 10 - 12
apps/web-ele/src/components/dict/src/index.vue

@@ -15,16 +15,9 @@ const props = withDefaults(defineProps<Props>(), {
15 15
   dicts: undefined,
16 16
 });
17 17
 
18
-const color = computed<string>(() => {
18
+const tagType = computed<string>(() => {
19 19
   const current = props.dicts.find((item) => item.dictValue == props.value);
20
-  // // 是否为默认的颜色
21
-  // const isDefault = Reflect.has(tagTypes, listClass);
22
-  // // 判断是默认还是自定义颜色
23
-  // if (isDefault) {
24
-  //   // 这里做了antd - element-plus的兼容
25
-  //   return tagTypes[listClass]!.color;
26
-  // }
27
-  return current?.listClass ?? 'primary';
20
+  return current?.listClass ?? 'default';
28 21
 });
29 22
 
30 23
 const cssClass = computed<string>(() => {
@@ -37,16 +30,21 @@ const label = computed<number | string>(() => {
37 30
   return current?.dictLabel ?? 'unknown';
38 31
 });
39 32
 
40
-const tagComponent = computed(() => (color.value ? ElTag : 'div'));
33
+// 只有当type是Element Plus支持的类型时才传递type属性
34
+const elTagType = computed<string | undefined>(() => {
35
+  const supportedTypes = ['primary', 'success', 'info', 'warning', 'danger'];
36
+  return supportedTypes.includes(tagType.value) ? tagType.value : undefined;
37
+});
38
+
39
+const tagComponent = computed(() => ElTag);
41 40
 </script>
42 41
 
43 42
 <template>
44 43
   <div>
45 44
     <component
46 45
       :is="tagComponent"
47
-      :type="color"
46
+      :type="elTagType"
48 47
       :class="cssClass"
49
-      :color="color"
50 48
     >
51 49
       {{ label }}
52 50
     </component>

+ 23 - 1
apps/web-ele/src/router/routes/local.ts

@@ -144,7 +144,28 @@ const localRoutes: RouteRecordStringComponent[] = [
144 144
     name: 'ScheduleDetail',
145 145
     path: '/schedule/detail/:taskId',
146 146
   },
147
-  {    component: 'workflow/processDefinition/design',    meta: {      activePath: '/workflow/processDefinition',      icon: 'carbon:data-base',      title: '流程设计',      hideInMenu: true,    },    name: 'WorkflowDesign',    path: '/workflow/design/index',  },
147
+  {    
148
+    component: 'workflow/processDefinition/design',    
149
+    meta: {      
150
+      activePath: '/workflow/processDefinition',      
151
+      icon: 'carbon:data-base',      
152
+      title: '流程设计',      
153
+      hideInMenu: true,    
154
+    },    
155
+    name: 'WorkflowDesign',    
156
+    path: '/workflow/design/index',  
157
+  },
158
+  {    
159
+    component: '/workflow/order/creatOrder',    
160
+    meta: {      
161
+      activePath: '/workflow/order',      
162
+      icon: 'carbon:add',      
163
+      title: '创建工单',      
164
+      hideInMenu: false,    
165
+    },    
166
+    name: 'WorkflowCreatOrder',    
167
+    path: '/workflow/order/create',  
168
+  },
148 169
   // 新增路由开始
149 170
   {
150 171
     component: '/examManage/questScoring/cpn/examScoring/index',
@@ -298,3 +319,4 @@ export const localMenuList: RouteRecordStringComponent[] = [
298 319
 
299 320
 
300 321
 
322
+

+ 11 - 3
apps/web-ele/src/utils/render.tsx

@@ -110,13 +110,16 @@ export function renderHttpMethodTag(type: string) {
110 110
     PUT: 'orange',
111 111
   };
112 112
 
113
-  const color = colors[method] ?? 'default';
113
+  const color = colors[method] ?? 'primary'; // 将default改为primary
114 114
   const title = `${method}请求`;
115 115
 
116 116
   return <ElTag color={color}>{title}</ElTag>;
117 117
 }
118 118
 
119
-export function renderDictTag(value: string, dicts: DictData[]) {
119
+export function renderDictTag(value: string | undefined, dicts: DictData[]) {
120
+  if (!value) {
121
+    return <div></div>;
122
+  }
120 123
   return <DictTag dicts={dicts} value={value}></DictTag>;
121 124
 }
122 125
 
@@ -155,7 +158,10 @@ export function renderDictTags(
155 158
  * @param dictName dictName
156 159
  * @returns tag
157 160
  */
158
-export function renderDict(value: string, dictName: string) {
161
+export function renderDict(value: string | undefined, dictName: string) {
162
+  if (!value) {
163
+    return <div></div>;
164
+  }
159 165
   const dictInfo = getDictOptions(dictName);
160 166
   return renderDictTag(value, dictInfo);
161 167
 }
@@ -228,3 +234,5 @@ export function renderBrowserIcon(browser: string, center = false) {
228 234
   const icon = current ? current.icon : DefaultBrowserIcon;
229 235
   return renderIconSpan(icon, browser, center, '5px');
230 236
 }
237
+
238
+

+ 3 - 3
apps/web-ele/src/views/workflow/components/actions/flow-actions.vue

@@ -384,21 +384,21 @@ const showMultiActions = computed(() => {
384 384
                 key="2"
385 385
                 @click="() => transferDrawerApi.open()"
386 386
               >
387
-                <ElRollback class="mr-2" /> 转办
387
+               转办
388 388
               </ElMenuItem>
389 389
               <ElMenuItem
390 390
                 v-if="showMultiActions && buttonPermissions?.addSign"
391 391
                 key="3"
392 392
                 @click="() => addSignatureDrawerApi.open()"
393 393
               >
394
-                <ElUserPlus class="mr-2" /> 加签
394
+               加签
395 395
               </ElMenuItem>
396 396
               <ElMenuItem
397 397
                 v-if="showMultiActions && buttonPermissions?.subSign"
398 398
                 key="4"
399 399
                 @click="() => reductionSignatureDrawerApi.open()"
400 400
               >
401
-                <ElUserMinus class="mr-2" /> 减签
401
+                减签
402 402
               </ElMenuItem>
403 403
             </ElMenu>
404 404
           </template>

+ 6 - 8
apps/web-ele/src/views/workflow/components/approval-card.vue

@@ -45,16 +45,14 @@ const diffUpdateTimeString = computed(() => {
45 45
   >
46 46
     <ElDescriptions
47 47
       :column="1"
48
-      :title="info.businessTitle ?? info.flowName"
48
+      :title="info.orderNo"
49 49
       size="large"
50 50
     >
51 51
       <template #extra>
52
-        <component
53
-          :is="renderDict(info.flowStatus, DictEnum.WF_BUSINESS_STATUS)"
54
-        />
52
+        <component :is="renderDict(info.status, DictEnum.TICKET_STATUS)" />
55 53
       </template>
56
-      <ElDescriptionsItem label="当前任务">
57
-        <div class="font-bold">{{ info.nodeName }}</div>
54
+      <ElDescriptionsItem>
55
+        <div class="font-bold">{{ info.content }}</div>
58 56
       </ElDescriptionsItem>
59 57
       <ElDescriptionsItem label="提交时间">
60 58
         {{ info.createTime }}
@@ -66,12 +64,12 @@ const diffUpdateTimeString = computed(() => {
66 64
     <div class="flex w-full items-center justify-between text-[14px]">
67 65
       <div class="flex items-center gap-1 overflow-hidden whitespace-nowrap">
68 66
         <VbenAvatar
69
-          :alt="info?.createByName"
67
+          :alt="info?.currentHandler"
70 68
           class="bg-primary size-[24px] rounded-full text-[10px] text-white"
71 69
           src=""
72 70
         />
73 71
         <span class="overflow-hidden text-ellipsis opacity-50">
74
-          {{ info.createByName }}
72
+          {{ info.currentHandler }}
75 73
         </span>
76 74
       </div>
77 75
       <div class="text-nowrap opacity-50">

+ 4 - 10
apps/web-ele/src/views/workflow/components/approval-details.vue

@@ -3,13 +3,8 @@
3 3
 动态渲染要显示的内容 需要再flowDescripionsMap先定义好组件
4 4
 -->
5 5
 <script setup lang="ts">
6
-import type { FlowComponentsMapMapKey } from '../register';
7
-
8
-import type { FlowInfoResponse } from '#/api/workflow/instance/model';
9 6
 import type { TaskInfo } from '#/api/workflow/task/model';
10 7
 
11
-import { Divider } from 'element-plus';
12
-
13 8
 import { ApprovalTimeline } from '.';
14 9
 import { flowComponentsMap } from '../register';
15 10
 
@@ -19,7 +14,6 @@ defineOptions({
19 14
 });
20 15
 
21 16
 defineProps<{
22
-  currentFlowInfo: FlowInfoResponse;
23 17
   task: TaskInfo;
24 18
 }>();
25 19
 </script>
@@ -30,11 +24,11 @@ defineProps<{
30 24
      动态渲染要显示的内容 需要再flowDescripionsMap先定义好组件
31 25
      business-id为业务ID 必传
32 26
     -->
27
+    <!-- <component :is="flowComponentsMap[task.formPath as FlowComponentsMapMapKey]" :business-id="task.businessId" /> -->
33 28
     <component
34
-      :is="flowComponentsMap[task.formPath as FlowComponentsMapMapKey]"
35
-      :business-id="task.businessId"
29
+      :is="flowComponentsMap['/workflow/leaveEdit/index']"
30
+      :task="task"
36 31
     />
37
-    <el-divider />
38
-    <ApprovalTimeline :list="currentFlowInfo.list" />
32
+    <ApprovalTimeline :id="task.id" />
39 33
   </div>
40 34
 </template>

+ 25 - 33
apps/web-ele/src/views/workflow/components/approval-panel.vue

@@ -15,16 +15,14 @@ import { DictEnum } from '@vben/constants';
15 15
 import { cn } from '@vben/utils';
16 16
 
17 17
 // import { Copy } from '@element-plus/icons-vue';
18
+import { CopyDocument } from '@element-plus/icons-vue';
18 19
 import { useClipboard } from '@vueuse/core';
19
-import { ElCard, ElDivider, ElMessage, ElTabPane, ElTabs } from 'element-plus';
20
+import { ElCard, ElMessage, ElTabPane, ElTabs } from 'element-plus';
20 21
 
21
-import { flowInfo } from '#/api/workflow/instance';
22
-import { getTaskByTaskId } from '#/api/workflow/task';
23 22
 import { renderDict } from '#/utils/render';
24 23
 
25 24
 import { FlowActions } from './actions';
26 25
 import ApprovalDetails from './approval-details.vue';
27
-import FlowPreview from './flow-preview.vue';
28 26
 
29 27
 defineOptions({
30 28
   name: 'ApprovalPanel',
@@ -97,23 +95,23 @@ async function handleLoadInfo(task: TaskInfo | undefined) {
97 95
     /**
98 96
      * 不为审批不需要调用`getTaskByTaskId`接口
99 97
      */
100
-    if (props.type !== 'approve') {
101
-      const flowResp = await flowInfo(task.businessId);
102
-      currentFlowInfo.value = flowResp;
103
-      return;
104
-    }
98
+    // if (props.type !== 'approve') {
99
+    //   const flowResp = await flowInfo(task.businessId);
100
+    //   currentFlowInfo.value = flowResp;
101
+    //   return;
102
+    // }
105 103
 
106 104
     /**
107 105
      * getTaskByTaskId主要为了获取按钮权限 目前没有其他功能
108 106
      * 行数据(即props.task)获取的是没有按钮权限的
109 107
      */
110
-    const [flowResp, taskResp] = await Promise.all([
111
-      flowInfo(task.businessId),
112
-      getTaskByTaskId(task.id),
113
-    ]);
108
+    // const [flowResp, taskResp] = await Promise.all([
109
+    //   flowInfo(task.businessId),
110
+    //   getTaskByTaskId(task.id),
111
+    // ]);
114 112
 
115
-    currentFlowInfo.value = flowResp;
116
-    onlyForBtnPermissionTask.value = taskResp;
113
+    // currentFlowInfo.value = flowResp;
114
+    // onlyForBtnPermissionTask.value = taskResp;
117 115
   } catch (error) {
118 116
     console.error(error);
119 117
   } finally {
@@ -143,13 +141,6 @@ async function handleCopy(text: string) {
143 141
       class="thin-scrollbar flex-1 overflow-y-hidden"
144 142
       size="small"
145 143
     >
146
-      <template #title>
147
-        <div class="flex items-center gap-2">
148
-          <div>编号: {{ task.id }}</div>
149
-          <!-- <Copy class="cursor-pointer" @click="handleCopy(task.id)" /> -->
150
-        </div>
151
-      </template>
152
-
153 144
       <template #extra>
154 145
         <el-button size="small" @click="() => handleLoadInfo(task)">
155 146
           <div class="flex items-center justify-center">
@@ -162,32 +153,33 @@ async function handleCopy(text: string) {
162 153
         <div class="flex flex-col gap-3">
163 154
           <div class="flex items-center gap-2">
164 155
             <div class="text-2xl font-bold">
165
-              {{ task.businessTitle ?? task.flowName }}
156
+              {{ task.orderNo }}
166 157
             </div>
158
+            <el-icon @click="() => handleCopy(task.orderNo)">
159
+              <CopyDocument />
160
+            </el-icon>
167 161
             <div>
168 162
               <component
169
-                :is="renderDict(task.flowStatus, DictEnum.WF_BUSINESS_STATUS)"
163
+                :is="renderDict(task.status, DictEnum.TICKET_STATUS)"
170 164
               />
171 165
             </div>
172 166
           </div>
173 167
 
174 168
           <div class="flex items-center gap-2">
175 169
             <VbenAvatar
176
-              :alt="task?.createByName ?? ''"
170
+              :alt="task?.createBy ?? ''"
177 171
               class="bg-primary size-[28px] rounded-full text-white"
178 172
               src=""
179 173
             />
180 174
 
181
-            <span>{{ task.createByName }}</span>
175
+            <span>{{ task.createBy }}</span>
182 176
 
183 177
             <div class="flex items-center opacity-50">
184 178
               <div class="flex items-center gap-1">
185 179
                 <span class="icon-[bxs--category-alt] size-[16px]"></span>
186
-                流程分类: {{ task.categoryName }}
180
+                流程分类: {{ task.ticketTypeName }}
187 181
               </div>
188 182
 
189
-              <ElDivider direction="vertical" />
190
-
191 183
               <div class="flex items-center gap-1">
192 184
                 <span class="icon-[mdi--clock-outline] size-[16px]"></span>
193 185
                 提交时间: {{ task.createTime }}
@@ -196,17 +188,17 @@ async function handleCopy(text: string) {
196 188
           </div>
197 189
         </div>
198 190
 
199
-        <ElTabs v-if="currentFlowInfo" class="flex-1">
200
-          <ElTabPane key="1" label="审批详情">
191
+        <ElTabs class="flex-1">
192
+          <ElTabPane key="1" label="工单详情">
201 193
             <ApprovalDetails
202 194
               :current-flow-info="currentFlowInfo"
203 195
               :task="task"
204 196
             />
205 197
           </ElTabPane>
206 198
 
207
-          <ElTabPane key="2" label="审批流程图">
199
+          <!-- <ElTabPane key="2" label="审批流程图">
208 200
             <FlowPreview :instance-id="currentFlowInfo.instanceId" />
209
-          </ElTabPane>
201
+          </ElTabPane> -->
210 202
         </ElTabs>
211 203
       </div>
212 204
 

+ 25 - 7
apps/web-ele/src/views/workflow/components/approval-timeline.vue

@@ -1,19 +1,37 @@
1 1
 <script setup lang="ts">
2
-import type { Flow } from '#/api/workflow/instance/model';
2
+import { onMounted, shallowRef } from 'vue';
3
+
4
+import { ElEmpty, ElMessage, ElTimeline } from 'element-plus';
5
+
6
+import { requestClient } from '#/api/request';
3 7
 
4
-// import { Empty, Timeline } from 'ant-design-vue';
5 8
 import ApprovalTimelineItem from './approval-timeline-item.vue';
6 9
 
7 10
 interface Props {
8
-  list: Flow[];
11
+  id: string;
9 12
 }
10 13
 
11
-defineProps<Props>();
14
+const props = defineProps<Props>();
15
+const list = shallowRef([]);
16
+
17
+onMounted(async () => {
18
+  if (!props.id) return;
19
+
20
+  try {
21
+    const resp = await requestClient.get('/workOrderRecord/record/list', {
22
+      params: { orderId: props.id },
23
+    });
24
+    list.value = resp.data;
25
+    console.log(resp.data);
26
+  } catch {
27
+    ElMessage.error('获取工单记录失败');
28
+  }
29
+});
12 30
 </script>
13 31
 
14 32
 <template>
15
-  <Timeline v-if="list.length > 0">
33
+  <ElTimeline v-if="id">
16 34
     <ApprovalTimelineItem v-for="item in list" :key="item.id" :item="item" />
17
-  </Timeline>
18
-  <Empty v-else />
35
+  </ElTimeline>
36
+  <ElEmpty v-else />
19 37
 </template>

+ 74 - 0
apps/web-ele/src/views/workflow/leave/api/index.ts

@@ -0,0 +1,74 @@
1
+import type { LeaveForm, LeaveQuery, LeaveVO } from './model';
2
+
3
+import type { ID, IDS, PageResult } from '#/api/common';
4
+
5
+import { commonExport } from '#/api/helper';
6
+import { requestClient } from '#/api/request';
7
+
8
+/**
9
+ * 查询请假申请列表
10
+ * @param params
11
+ * @returns 请假申请列表
12
+ */
13
+export function leaveList(params?: LeaveQuery) {
14
+  return requestClient.get<PageResult<LeaveVO>>('/workflow/leave/list', {
15
+    params,
16
+  });
17
+}
18
+
19
+/**
20
+ * 导出请假申请列表
21
+ * @param params
22
+ * @returns 请假申请列表
23
+ */
24
+export function leaveExport(params?: LeaveQuery) {
25
+  return commonExport('/workflow/leave/export', params ?? {});
26
+}
27
+
28
+/**
29
+ * 查询请假申请详情
30
+ * @param id id
31
+ * @returns 请假申请详情
32
+ */
33
+export function leaveInfo(id: ID) {
34
+  return requestClient.get<LeaveVO>(`/workflow/leave/${id}`);
35
+}
36
+
37
+/**
38
+ * 新增请假申请
39
+ * @param data
40
+ * @returns void
41
+ */
42
+export function leaveAdd(data: LeaveForm) {
43
+  return requestClient.postWithMsg<LeaveVO>('/workflow/leave', data);
44
+}
45
+
46
+/**
47
+ * 更新请假申请
48
+ * @param data
49
+ * @returns void
50
+ */
51
+export function leaveUpdate(data: LeaveForm) {
52
+  return requestClient.putWithMsg<LeaveVO>('/workflow/leave', data);
53
+}
54
+
55
+/**
56
+ * 删除请假申请
57
+ * @param id id
58
+ * @returns void
59
+ */
60
+export function leaveRemove(id: ID | IDS) {
61
+  return requestClient.deleteWithMsg<void>(`/workflow/leave/${id}`);
62
+}
63
+
64
+/**
65
+ * 提交 & 发起流程(后端发起)
66
+ * @param data data
67
+ * @returns void
68
+ */
69
+export function submitAndStartWorkflow(data: LeaveForm) {
70
+  return requestClient.postWithMsg<void>(
71
+    '/workflow/leave/submitAndFlowStart',
72
+    data,
73
+  );
74
+}

+ 138 - 0
apps/web-ele/src/views/workflow/leave/api/model.d.ts

@@ -0,0 +1,138 @@
1
+import type { BaseEntity, PageQuery } from '#/api/common';
2
+
3
+export interface LeaveVO {
4
+  /**
5
+   * 主键
6
+   */
7
+  id: number | string;
8
+
9
+  /**
10
+   * 工单类型
11
+   */
12
+  ticketTypeName: string;
13
+
14
+  /**
15
+   * 场站名称
16
+   */
17
+  stationName: string;
18
+
19
+  /**
20
+   * 填报内容
21
+   */
22
+  content: string;
23
+
24
+  /**
25
+   * 附件
26
+   */
27
+  attachment: string;
28
+
29
+  /**
30
+   * 请假类型
31
+   */
32
+  leaveType: string;
33
+
34
+  /**
35
+   * 联系人
36
+   */
37
+  contact: string;
38
+
39
+  /**
40
+   * 联系电话
41
+   */
42
+  phone: string;
43
+
44
+  /**
45
+   * 开始时间
46
+   */
47
+  startDate: string;
48
+
49
+  /**
50
+   * 结束时间
51
+   */
52
+  endDate: string;
53
+
54
+  /**
55
+   * 请假天数
56
+   */
57
+  leaveDays: number;
58
+
59
+  /**
60
+   * 请假原因
61
+   */
62
+  remark: string;
63
+
64
+  /**
65
+   * 状态 0:草稿 1:已提交 2:已同意 3:已拒绝
66
+   */
67
+  status: string;
68
+  applyCode?: string;
69
+}
70
+
71
+export interface LeaveForm extends BaseEntity {
72
+  /**
73
+   * 主键
74
+   */
75
+  id?: number | string;
76
+
77
+  /**
78
+   * 请假类型
79
+   */
80
+  leaveType?: string;
81
+
82
+  /**
83
+   * 开始时间
84
+   */
85
+  startDate?: string;
86
+
87
+  /**
88
+   * 结束时间
89
+   */
90
+  endDate?: string;
91
+
92
+  /**
93
+   * 请假天数
94
+   */
95
+  leaveDays?: number;
96
+
97
+  /**
98
+   * 请假原因
99
+   */
100
+  remark?: string;
101
+
102
+  /**
103
+   *
104
+   */
105
+  status?: string;
106
+}
107
+
108
+export interface LeaveQuery extends PageQuery {
109
+  /**
110
+   * 请假类型
111
+   */
112
+  leaveType?: string;
113
+
114
+  /**
115
+   * 开始时间
116
+   */
117
+  startDate?: string;
118
+
119
+  /**
120
+   * 结束时间
121
+   */
122
+  endDate?: string;
123
+
124
+  /**
125
+   * 请假天数
126
+   */
127
+  leaveDays?: number;
128
+
129
+  /**
130
+   *
131
+   */
132
+  status?: string;
133
+
134
+  /**
135
+   * 日期范围参数
136
+   */
137
+  params?: any;
138
+}

+ 189 - 0
apps/web-ele/src/views/workflow/leave/data.tsx

@@ -0,0 +1,189 @@
1
+import type { FormSchemaGetter, VbenFormSchema } from '#/adapter/form';
2
+import type { VxeGridProps } from '#/adapter/vxe-table';
3
+
4
+import { DictEnum } from '@vben/constants';
5
+import { getPopupContainer } from '@vben/utils';
6
+
7
+import dayjs from 'dayjs';
8
+
9
+import { OptionsTag } from '#/components/table';
10
+import { renderDict } from '#/utils/render';
11
+
12
+export const leaveTypeOptions = [
13
+  { label: '病假 😷', value: '1' },
14
+  { label: '事假 🖥', value: '2' },
15
+  { label: '年假 🏝', value: '3' },
16
+  { label: '婚假 💒', value: '4' },
17
+  { label: '产假 🤰', value: '5' },
18
+  { label: '其他 🤔', value: '7' },
19
+];
20
+
21
+export const leaveFlowOptions = [
22
+  { label: '请假流程-普通', value: 'leave1' },
23
+  { label: '请假流程-排他网关', value: 'leave2' },
24
+  { label: '请假流程-并行网关', value: 'leave3' },
25
+  { label: '请假流程-会签', value: 'leave4' },
26
+  { label: '请假申请-并行会签网关', value: 'leave5' },
27
+  { label: '请假申请-排他并行网关', value: 'leave6' },
28
+];
29
+
30
+export const querySchema: FormSchemaGetter = () => [
31
+  {
32
+    component: 'InputNumber',
33
+    componentProps: {
34
+      min: 1,
35
+    },
36
+    fieldName: 'startLeaveDays',
37
+    label: '请假天数',
38
+  },
39
+  {
40
+    component: 'InputNumber',
41
+    componentProps: {
42
+      min: 1,
43
+    },
44
+    fieldName: 'endLeaveDays',
45
+    label: '至',
46
+    labelClass: 'justify-center',
47
+  },
48
+];
49
+
50
+export const columns: VxeGridProps['columns'] = [
51
+  { type: 'checkbox', width: 60 },
52
+  {
53
+    title: '请假类型',
54
+    field: 'leaveType',
55
+    slots: {
56
+      default: ({ row }) => {
57
+        return <OptionsTag options={leaveTypeOptions} value={row.leaveType} />;
58
+      },
59
+    },
60
+  },
61
+  {
62
+    title: '开始时间',
63
+    field: 'startDate',
64
+    formatter: ({ cellValue }) => dayjs(cellValue).format('YYYY-MM-DD'),
65
+  },
66
+  {
67
+    title: '结束时间',
68
+    field: 'endDate',
69
+    formatter: ({ cellValue }) => dayjs(cellValue).format('YYYY-MM-DD'),
70
+  },
71
+  {
72
+    title: '请假天数',
73
+    field: 'leaveDays',
74
+    formatter: ({ cellValue }) => `${cellValue}天`,
75
+  },
76
+  {
77
+    title: '请假原因',
78
+    field: 'remark',
79
+  },
80
+  {
81
+    title: '流程状态',
82
+    field: 'status',
83
+    slots: {
84
+      default: ({ row }) => {
85
+        return renderDict(row.status, DictEnum.WF_BUSINESS_STATUS);
86
+      },
87
+    },
88
+  },
89
+  {
90
+    field: 'action',
91
+    fixed: 'right',
92
+    slots: { default: 'action' },
93
+    title: '操作',
94
+    resizable: false,
95
+    width: 'auto',
96
+  },
97
+];
98
+
99
+export const formSchema: () => VbenFormSchema[] = () => [
100
+  {
101
+    label: '主键',
102
+    fieldName: 'id',
103
+    component: 'Input',
104
+    dependencies: {
105
+      show: () => false,
106
+      triggerFields: [''],
107
+    },
108
+  },
109
+  {
110
+    label: '流程类型',
111
+    fieldName: 'flowType',
112
+    component: 'Select',
113
+    help: '这里仅仅为了发起流程方便, 实际不应该包含此字段',
114
+    componentProps: {
115
+      options: leaveFlowOptions,
116
+      getPopupContainer,
117
+    },
118
+    defaultValue: 'leave1',
119
+    rules: 'selectRequired',
120
+    dependencies: {
121
+      triggerFields: [''],
122
+    },
123
+  },
124
+  {
125
+    label: '发起类型',
126
+    fieldName: 'type',
127
+    component: 'Select',
128
+    help: '这里仅仅为了测试, 实际不应该包含此字段',
129
+    componentProps: {
130
+      options: [
131
+        {
132
+          label: '前端发起 (可选审批人, 选抄送人, 上传附件)',
133
+          value: 'frontend',
134
+        },
135
+        {
136
+          label: '后端发起 (自行编写后端逻辑, 由后端发起流程)',
137
+          value: 'backend',
138
+        },
139
+      ],
140
+      getPopupContainer,
141
+    },
142
+    defaultValue: 'frontend',
143
+  },
144
+  {
145
+    label: '请假类型',
146
+    fieldName: 'leaveType',
147
+    component: 'Select',
148
+    componentProps: {
149
+      options: leaveTypeOptions,
150
+      getPopupContainer,
151
+    },
152
+    rules: 'selectRequired',
153
+  },
154
+  {
155
+    label: '开始时间',
156
+    fieldName: 'dateRange',
157
+    component: 'RangePicker',
158
+    componentProps(model) {
159
+      return {
160
+        format: 'YYYY-MM-DD',
161
+        valueFormat: 'YYYY-MM-DD HH:mm:ss',
162
+        onChange: (dates: [string, string]) => {
163
+          if (!dates) {
164
+            model.leaveDays = null;
165
+            return;
166
+          }
167
+          const [start, end] = dates;
168
+          const leaveDays = dayjs(end).diff(dayjs(start), 'day') + 1;
169
+          model.leaveDays = leaveDays;
170
+        },
171
+      };
172
+    },
173
+    rules: 'required',
174
+  },
175
+  {
176
+    label: '请假天数',
177
+    fieldName: 'leaveDays',
178
+    component: 'Input',
179
+    componentProps: {
180
+      disabled: true,
181
+    },
182
+    // rules: 'required',
183
+  },
184
+  {
185
+    label: '请假原因',
186
+    fieldName: 'remark',
187
+    component: 'Textarea',
188
+  },
189
+];

+ 33 - 0
apps/web-ele/src/views/workflow/leave/hook.ts

@@ -0,0 +1,33 @@
1
+import { onActivated, onMounted, ref } from 'vue';
2
+
3
+import { createGlobalState } from '@vueuse/core';
4
+
5
+export function useRouteIdEdit(callback: (id: string) => void, timeout = 500) {
6
+  const { businessId } = useQueryId();
7
+  function openEditFromRouteId() {
8
+    const id = businessId.value;
9
+    if (!id) {
10
+      return;
11
+    }
12
+    setTimeout(() => {
13
+      // 回调
14
+      callback?.(id);
15
+      // 执行完 清理id
16
+      businessId.value = '';
17
+    }, timeout);
18
+  }
19
+
20
+  onMounted(openEditFromRouteId);
21
+  onActivated(openEditFromRouteId);
22
+}
23
+
24
+/**
25
+ * 用来存储业务ID 传值
26
+ */
27
+export const useQueryId = createGlobalState(() => {
28
+  const businessId = ref('');
29
+
30
+  return {
31
+    businessId,
32
+  };
33
+});

+ 244 - 0
apps/web-ele/src/views/workflow/leave/index.vue

@@ -0,0 +1,244 @@
1
+<script setup lang="ts">
2
+import type { VbenFormProps } from '@vben/common-ui';
3
+
4
+import type { LeaveForm } from './api/model';
5
+
6
+import type { VxeGridProps } from '#/adapter/vxe-table';
7
+
8
+import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui';
9
+import { getVxePopupContainer } from '@vben/utils';
10
+
11
+import { Modal, Popconfirm, Space } from 'ant-design-vue';
12
+
13
+import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table';
14
+import { cancelProcessApply } from '#/api/workflow/instance';
15
+import { commonDownloadExcel } from '#/utils/file/download';
16
+
17
+import { applyModal, flowInfoModal } from '../components';
18
+import { leaveExport, leaveList, leaveRemove } from './api';
19
+import { columns, querySchema } from './data';
20
+import { useRouteIdEdit } from './hook';
21
+import leaveDrawer from './leave-drawer.vue';
22
+
23
+const formOptions: VbenFormProps = {
24
+  commonConfig: {
25
+    labelWidth: 80,
26
+    componentProps: {
27
+      allowClear: true,
28
+    },
29
+  },
30
+  schema: querySchema(),
31
+  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
32
+};
33
+
34
+const gridOptions: VxeGridProps = {
35
+  checkboxConfig: {
36
+    // 高亮
37
+    highlight: true,
38
+    // 翻页时保留选中状态
39
+    reserve: true,
40
+    // 选中 需要根据状态判断
41
+    checkMethod: ({ row }) => ['back', 'cancel', 'draft'].includes(row.status),
42
+  },
43
+  columns,
44
+  height: 'auto',
45
+  keepSource: true,
46
+  pagerConfig: {},
47
+  proxyConfig: {
48
+    ajax: {
49
+      query: async ({ page }, formValues = {}) => {
50
+        return await leaveList({
51
+          pageNum: page.currentPage,
52
+          pageSize: page.pageSize,
53
+          ...formValues,
54
+        });
55
+      },
56
+    },
57
+  },
58
+  rowConfig: {
59
+    keyField: 'id',
60
+  },
61
+  // 表格全局唯一表示 保存列配置需要用到
62
+  id: 'workflow-leave-index',
63
+  cellClassName: ({ row }) => {
64
+    // 草稿状态 可点击
65
+    if (row.status !== 'draft') {
66
+      return 'cursor-pointer';
67
+    }
68
+  },
69
+};
70
+
71
+const [BasicTable, tableApi] = useVbenVxeGrid({
72
+  formOptions,
73
+  gridOptions,
74
+  gridEvents: {
75
+    cellClick: ({ row, column }) => {
76
+      // 草稿状态 不做处理
77
+      // 操作列 不做处理
78
+      if (row.status === 'draft' || column.field === 'action') {
79
+        return;
80
+      }
81
+      // 查看详情
82
+      handleInfo(row);
83
+    },
84
+  },
85
+});
86
+
87
+const [ApplyModal, applyModalApi] = useVbenModal({
88
+  connectedComponent: applyModal,
89
+});
90
+const [LeaveDrawer, leaveDrawerApi] = useVbenDrawer({
91
+  connectedComponent: leaveDrawer,
92
+});
93
+
94
+function handleAdd() {
95
+  leaveDrawerApi.setData({ applyModalApi }).open();
96
+}
97
+
98
+async function handleEdit(row: Required<LeaveForm>) {
99
+  leaveDrawerApi.setData({ id: row.id, applyModalApi }).open();
100
+}
101
+
102
+useRouteIdEdit((id) => {
103
+  // 打开编辑
104
+  leaveDrawerApi.setData({ id, applyModalApi }).open();
105
+});
106
+
107
+async function handleCompleteOrCancel() {
108
+  leaveDrawerApi.close();
109
+  tableApi.query();
110
+}
111
+
112
+async function handleDelete(row: Required<LeaveForm>) {
113
+  await leaveRemove(row.id);
114
+  await tableApi.query();
115
+}
116
+
117
+async function handleRevoke(row: Required<LeaveForm>) {
118
+  await cancelProcessApply({
119
+    businessId: row.id,
120
+    message: '申请人撤销流程!',
121
+  });
122
+  await tableApi.query();
123
+}
124
+
125
+function handleMultiDelete() {
126
+  const rows = tableApi.grid.getCheckboxRecords();
127
+  const ids = rows.map((row: Required<LeaveForm>) => row.id);
128
+  Modal.confirm({
129
+    title: '提示',
130
+    okType: 'danger',
131
+    content: `确认删除选中的${ids.length}条记录吗?`,
132
+    onOk: async () => {
133
+      await leaveRemove(ids);
134
+      await tableApi.query();
135
+    },
136
+  });
137
+}
138
+
139
+function handleDownloadExcel() {
140
+  commonDownloadExcel(
141
+    leaveExport,
142
+    '请假申请数据',
143
+    tableApi.formApi.form.values,
144
+    {
145
+      fieldMappingTime: formOptions.fieldMappingTime,
146
+    },
147
+  );
148
+}
149
+const [FlowInfoModal, flowInfoModalApi] = useVbenModal({
150
+  connectedComponent: flowInfoModal,
151
+});
152
+
153
+function handleInfo(row: Required<LeaveForm>) {
154
+  flowInfoModalApi.setData({ businessId: row.id });
155
+  flowInfoModalApi.open();
156
+}
157
+</script>
158
+
159
+<template>
160
+  <Page :auto-content-height="true">
161
+    <BasicTable table-title="请假申请列表">
162
+      <template #toolbar-tools>
163
+        <Space>
164
+          <a-button
165
+            v-access:code="['workflow:leave:export']"
166
+            @click="handleDownloadExcel"
167
+          >
168
+            {{ $t('pages.common.export') }}
169
+          </a-button>
170
+          <a-button
171
+            :disabled="!vxeCheckboxChecked(tableApi)"
172
+            danger
173
+            type="primary"
174
+            v-access:code="['workflow:leave:remove']"
175
+            @click="handleMultiDelete"
176
+          >
177
+            {{ $t('pages.common.delete') }}
178
+          </a-button>
179
+          <a-button
180
+            type="primary"
181
+            v-access:code="['workflow:leave:add']"
182
+            @click="handleAdd"
183
+          >
184
+            {{ $t('pages.common.add') }}
185
+          </a-button>
186
+        </Space>
187
+      </template>
188
+      <template #action="{ row }">
189
+        <a-button
190
+          size="small"
191
+          type="link"
192
+          :disabled="!['draft', 'cancel', 'back'].includes(row.status)"
193
+          v-access:code="['workflow:leave:edit']"
194
+          @click.stop="handleEdit(row)"
195
+        >
196
+          {{ $t('pages.common.edit') }}
197
+        </a-button>
198
+        <Popconfirm
199
+          :get-popup-container="getVxePopupContainer"
200
+          placement="left"
201
+          title="确认撤销?"
202
+          :disabled="!['waiting'].includes(row.status)"
203
+          @confirm.stop="handleRevoke(row)"
204
+          @cancel.stop=""
205
+        >
206
+          <a-button
207
+            size="small"
208
+            type="link"
209
+            :disabled="!['waiting'].includes(row.status)"
210
+            v-access:code="['workflow:leave:edit']"
211
+            @click.stop=""
212
+          >
213
+            撤销
214
+          </a-button>
215
+        </Popconfirm>
216
+        <Popconfirm
217
+          :get-popup-container="getVxePopupContainer"
218
+          placement="left"
219
+          title="确认删除?"
220
+          :disabled="!['draft', 'cancel', 'back'].includes(row.status)"
221
+          @confirm.stop="handleDelete(row)"
222
+          @cancel.stop=""
223
+        >
224
+          <a-button
225
+            size="small"
226
+            type="link"
227
+            :disabled="!['draft', 'cancel', 'back'].includes(row.status)"
228
+            danger
229
+            v-access:code="['workflow:leave:remove']"
230
+            @click.stop=""
231
+          >
232
+            {{ $t('pages.common.delete') }}
233
+          </a-button>
234
+        </Popconfirm>
235
+      </template>
236
+    </BasicTable>
237
+    <FlowInfoModal />
238
+    <ApplyModal
239
+      @complete="handleCompleteOrCancel"
240
+      @cancel="handleCompleteOrCancel"
241
+    />
242
+    <LeaveDrawer @reload="() => tableApi.query()" />
243
+  </Page>
244
+</template>

+ 50 - 0
apps/web-ele/src/views/workflow/leave/leave-description.vue

@@ -0,0 +1,50 @@
1
+<script setup lang="ts">
2
+import type { LeaveVO } from '../leave/api/model';
3
+
4
+import { onMounted, shallowRef } from 'vue';
5
+
6
+import dayjs from 'dayjs';
7
+
8
+defineOptions({
9
+  name: 'LeaveDescription',
10
+  inheritAttrs: false,
11
+});
12
+
13
+const props = defineProps<{ task: LeaveVO }>();
14
+
15
+const data = shallowRef<LeaveVO>();
16
+onMounted(async () => {
17
+  data.value = props.task;
18
+});
19
+
20
+function formatDate(date: string) {
21
+  return dayjs(date).format('YYYY-MM-DD');
22
+}
23
+</script>
24
+
25
+<template>
26
+  <div class="rounded-[6px] border p-2">
27
+    <ElDescriptions v-if="data" :column="1">
28
+      <ElDescriptionsItem label="工单类型:">
29
+        {{ data.ticketTypeName }}
30
+      </ElDescriptionsItem>
31
+      <ElDescriptionsItem label="场站:">
32
+        {{ data.stationName || '无' }}
33
+      </ElDescriptionsItem>
34
+      <ElDescriptionsItem label="联系人:">
35
+        {{ data.contact || '无' }}
36
+      </ElDescriptionsItem>
37
+      <ElDescriptionsItem label="联系电话:">
38
+        {{ data.phone || '无' }}
39
+      </ElDescriptionsItem>
40
+      <ElDescriptionsItem label="提报内容:">
41
+        {{ data.content || '无' }}
42
+      </ElDescriptionsItem>
43
+      <ElDescriptionsItem label="附件:">
44
+        {{ data.attachment || '无' }}
45
+      </ElDescriptionsItem>
46
+    </ElDescriptions>
47
+
48
+    <ElSkeleton active v-else :rows="4" />
49
+  </div>
50
+</template>

+ 190 - 0
apps/web-ele/src/views/workflow/leave/leave-drawer.vue

@@ -0,0 +1,190 @@
1
+<script setup lang="ts">
2
+import type { ExtendedModalApi } from '@vben/common-ui';
3
+
4
+import type { StartWorkFlowReqData } from '#/api/workflow/task/model';
5
+
6
+import { computed, ref, shallowRef } from 'vue';
7
+
8
+import { useVbenDrawer } from '@vben/common-ui';
9
+import { $t } from '@vben/locales';
10
+import { cloneDeep } from '@vben/utils';
11
+
12
+import dayjs from 'dayjs';
13
+import { omit } from 'lodash-es';
14
+
15
+import { useVbenForm } from '#/adapter/form';
16
+import { startWorkFlow } from '#/api/workflow/task';
17
+
18
+import {
19
+  leaveAdd,
20
+  leaveInfo,
21
+  leaveUpdate,
22
+  submitAndStartWorkflow,
23
+} from './api';
24
+import { formSchema } from './data';
25
+
26
+const emit = defineEmits<{ reload: [] }>();
27
+
28
+const isUpdate = ref(false);
29
+const title = computed(() => {
30
+  return isUpdate.value ? $t('pages.common.edit') : $t('pages.common.add');
31
+});
32
+
33
+const [BasicForm, formApi] = useVbenForm({
34
+  layout: 'vertical',
35
+  commonConfig: {
36
+    formItemClass: 'col-span-2',
37
+    componentProps: {
38
+      class: 'w-full',
39
+    },
40
+    labelWidth: 100,
41
+  },
42
+  schema: formSchema(),
43
+  showDefaultActions: false,
44
+  wrapperClass: 'grid-cols-2',
45
+});
46
+
47
+const modalApi = shallowRef<ExtendedModalApi | null>(null);
48
+
49
+const [BasicDrawer, drawerApi] = useVbenDrawer({
50
+  closeOnClickModal: false,
51
+  onClosed: handleClosed,
52
+  onConfirm: handleStartWorkFlow,
53
+  async onOpenChange(isOpen) {
54
+    if (!isOpen) {
55
+      return null;
56
+    }
57
+    drawerApi.drawerLoading(true);
58
+
59
+    const { id, applyModalApi } = drawerApi.getData() as {
60
+      applyModalApi: ExtendedModalApi;
61
+      id?: number | string;
62
+    };
63
+    modalApi.value = applyModalApi;
64
+    isUpdate.value = !!id;
65
+    // 赋值
66
+    if (isUpdate.value && id) {
67
+      const resp = await leaveInfo(id);
68
+      await formApi.setValues(resp);
69
+      const dateRange = [dayjs(resp.startDate), dayjs(resp.endDate)];
70
+      await formApi.setFieldValue('dateRange', dateRange);
71
+    }
72
+
73
+    drawerApi.drawerLoading(false);
74
+  },
75
+});
76
+
77
+async function handleClosed() {
78
+  await formApi.resetForm();
79
+}
80
+
81
+/**
82
+ * 获取已经处理好的表单参数
83
+ */
84
+async function getFormData() {
85
+  const { valid } = await formApi.validate();
86
+  if (!valid) {
87
+    throw new Error('表单验证失败');
88
+  }
89
+  let data = cloneDeep(await formApi.getValues()) as any;
90
+  data = omit(data, 'flowType', 'type');
91
+  // 处理日期
92
+  data.startDate = dayjs(data.dateRange[0]).format('YYYY-MM-DD HH:mm:ss');
93
+  data.endDate = dayjs(data.dateRange[1]).format('YYYY-MM-DD HH:mm:ss');
94
+  return data;
95
+}
96
+
97
+/**
98
+ * 暂存/提交 提取通用逻辑
99
+ */
100
+async function handleSaveOrUpdate() {
101
+  const data = await getFormData();
102
+  return await (isUpdate.value ? leaveUpdate(data) : leaveAdd(data));
103
+}
104
+
105
+/**
106
+ * 暂存 草稿状态
107
+ */
108
+async function handleTempSave() {
109
+  try {
110
+    await handleSaveOrUpdate();
111
+    emit('reload');
112
+    drawerApi.close();
113
+  } catch (error) {
114
+    console.error(error);
115
+  }
116
+}
117
+
118
+/**
119
+ * 保存业务 & 发起流程
120
+ */
121
+async function handleStartWorkFlow() {
122
+  drawerApi.lock(true);
123
+  try {
124
+    const { valid } = await formApi.validate();
125
+    if (!valid) {
126
+      return;
127
+    }
128
+    // 获取发起类型
129
+    const { type } = await formApi.getValues();
130
+    /**
131
+     * 这里只是demo 实际只会用到一种
132
+     */
133
+    switch (type) {
134
+      // 后端发起流程
135
+      case 'backend': {
136
+        const data = await getFormData();
137
+        await submitAndStartWorkflow(data);
138
+        emit('reload');
139
+        drawerApi.close();
140
+        break;
141
+      }
142
+      // 前端发起流程
143
+      case 'frontend': {
144
+        // 保存业务
145
+        const leaveResp = await handleSaveOrUpdate();
146
+        // 启动流程
147
+        const taskVariables = {
148
+          leaveDays: leaveResp!.leaveDays,
149
+          userList: ['1', '3', '4'],
150
+        };
151
+        const formValues = await formApi.getValues();
152
+        const flowCode = formValues?.flowType ?? 'leave1';
153
+        const startWorkFlowData: StartWorkFlowReqData = {
154
+          businessId: leaveResp!.id,
155
+          flowCode,
156
+          variables: taskVariables,
157
+          flowInstanceBizExtBo: {
158
+            businessTitle: '请假申请 - 自定义标题',
159
+            businessCode: leaveResp!.applyCode,
160
+          },
161
+        };
162
+        const { taskId } = await startWorkFlow(startWorkFlowData);
163
+        // 打开窗口
164
+        modalApi.value?.setData({
165
+          taskId,
166
+          taskVariables,
167
+          variables: {},
168
+        });
169
+        modalApi.value?.open();
170
+        break;
171
+      }
172
+    }
173
+    emit('reload');
174
+    drawerApi.close();
175
+  } catch (error) {
176
+    console.error(error);
177
+  } finally {
178
+    drawerApi.lock(false);
179
+  }
180
+}
181
+</script>
182
+
183
+<template>
184
+  <BasicDrawer :title="title" class="w-[600px]">
185
+    <BasicForm />
186
+    <template #center-footer>
187
+      <a-button @click="handleTempSave">暂存</a-button>
188
+    </template>
189
+  </BasicDrawer>
190
+</template>

+ 197 - 0
apps/web-ele/src/views/workflow/leave/leave-form.vue

@@ -0,0 +1,197 @@
1
+<!--
2
+这个文件用不上  已经更改交互为drawer
3
+-->
4
+
5
+<script setup lang="ts">
6
+import type { StartWorkFlowReqData } from '#/api/workflow/task/model';
7
+
8
+import { onMounted, ref } from 'vue';
9
+import { useRoute, useRouter } from 'vue-router';
10
+
11
+import { useVbenModal } from '@vben/common-ui';
12
+import { useTabs } from '@vben/hooks';
13
+
14
+import { Card, Spin } from 'ant-design-vue';
15
+import dayjs from 'dayjs';
16
+import { cloneDeep, omit } from 'lodash-es';
17
+
18
+import { useVbenForm } from '#/adapter/form';
19
+import { startWorkFlow } from '#/api/workflow/task';
20
+
21
+import { applyModal } from '../components';
22
+import {
23
+  leaveAdd,
24
+  leaveInfo,
25
+  leaveUpdate,
26
+  submitAndStartWorkflow,
27
+} from './api';
28
+import { formSchema } from './data';
29
+
30
+const route = useRoute();
31
+const id = route.query?.id as string;
32
+
33
+const [BasicForm, formApi] = useVbenForm({
34
+  commonConfig: {
35
+    // 默认占满两列
36
+    formItemClass: 'col-span-2',
37
+    // 默认label宽度 px
38
+    labelWidth: 100,
39
+    // 通用配置项 会影响到所有表单项
40
+    componentProps: {
41
+      class: 'w-full',
42
+    },
43
+  },
44
+  schema: formSchema(),
45
+  showDefaultActions: false,
46
+  wrapperClass: 'grid-cols-2',
47
+});
48
+
49
+const loading = ref(false);
50
+onMounted(async () => {
51
+  // 只读 获取信息赋值
52
+  if (id) {
53
+    loading.value = true;
54
+
55
+    const resp = await leaveInfo(id);
56
+    await formApi.setValues(resp);
57
+    const dateRange = [dayjs(resp.startDate), dayjs(resp.endDate)];
58
+    await formApi.setFieldValue('dateRange', dateRange);
59
+
60
+    loading.value = false;
61
+  }
62
+});
63
+
64
+const router = useRouter();
65
+
66
+/**
67
+ * 获取已经处理好的表单参数
68
+ */
69
+async function getFormData() {
70
+  let data = cloneDeep(await formApi.getValues()) as any;
71
+  data = omit(data, 'flowType', 'type');
72
+  // 处理日期
73
+  data.startDate = dayjs(data.dateRange[0]).format('YYYY-MM-DD HH:mm:ss');
74
+  data.endDate = dayjs(data.dateRange[1]).format('YYYY-MM-DD HH:mm:ss');
75
+  return data;
76
+}
77
+
78
+/**
79
+ * 暂存/提交 提取通用逻辑
80
+ */
81
+async function handleSaveOrUpdate() {
82
+  const data = await getFormData();
83
+  if (id) {
84
+    data.id = id;
85
+    return await leaveUpdate(data);
86
+  } else {
87
+    return await leaveAdd(data);
88
+  }
89
+}
90
+
91
+const [ApplyModal, applyModalApi] = useVbenModal({
92
+  connectedComponent: applyModal,
93
+});
94
+/**
95
+ * 暂存 草稿状态
96
+ */
97
+async function handleTempSave() {
98
+  try {
99
+    await handleSaveOrUpdate();
100
+    router.push('/demo/leave');
101
+  } catch (error) {
102
+    console.error(error);
103
+  }
104
+}
105
+
106
+/**
107
+ * 保存业务 & 发起流程
108
+ */
109
+async function handleStartWorkFlow() {
110
+  loading.value = true;
111
+  try {
112
+    const { valid } = await formApi.validate();
113
+    if (!valid) {
114
+      return;
115
+    }
116
+    // 获取发起类型
117
+    const { type } = await formApi.getValues();
118
+    /**
119
+     * 这里只是demo 实际只会用到一种
120
+     */
121
+    switch (type) {
122
+      // 后端发起流程
123
+      case 'backend': {
124
+        const data = await getFormData();
125
+        await submitAndStartWorkflow(data);
126
+        await handleCompleteOrCancel();
127
+        break;
128
+      }
129
+      // 前端发起流程
130
+      case 'frontend': {
131
+        // 保存业务
132
+        const leaveResp = await handleSaveOrUpdate();
133
+        // 启动流程
134
+        const taskVariables = {
135
+          leaveDays: leaveResp!.leaveDays,
136
+          userList: ['1', '3', '4'],
137
+        };
138
+        const formValues = await formApi.getValues();
139
+        const flowCode = formValues?.flowType ?? 'leave1';
140
+        const startWorkFlowData: StartWorkFlowReqData = {
141
+          businessId: leaveResp!.id,
142
+          flowCode,
143
+          variables: taskVariables,
144
+          flowInstanceBizExtBo: {
145
+            businessTitle: '请假申请 - 自定义标题',
146
+            businessCode: leaveResp!.applyCode,
147
+          },
148
+        };
149
+        const { taskId } = await startWorkFlow(startWorkFlowData);
150
+        // 打开窗口
151
+        applyModalApi.setData({
152
+          taskId,
153
+          taskVariables,
154
+          variables: {},
155
+        });
156
+        applyModalApi.open();
157
+        break;
158
+      }
159
+    }
160
+  } catch (error) {
161
+    console.error(error);
162
+  } finally {
163
+    loading.value = false;
164
+  }
165
+}
166
+
167
+const { closeCurrentTab } = useTabs();
168
+
169
+/**
170
+ * 通用提交/取消回调
171
+ *
172
+ * 提交后点击取消 这时候已经变成草稿状态了
173
+ * 每次点击都会生成新记录 直接跳转回列表
174
+ */
175
+async function handleCompleteOrCancel() {
176
+  formApi.resetForm();
177
+  await closeCurrentTab();
178
+  router.push('/demo/leave');
179
+}
180
+</script>
181
+
182
+<template>
183
+  <Spin :spinning="loading">
184
+    <Card>
185
+      <BasicForm />
186
+      <div class="flex justify-end gap-2">
187
+        <a-button @click="handleTempSave">暂存</a-button>
188
+        <a-button type="primary" @click="handleStartWorkFlow">提交</a-button>
189
+      </div>
190
+      <ApplyModal
191
+        :modal-api="applyModalApi"
192
+        @complete="handleCompleteOrCancel"
193
+        @cancel="handleCompleteOrCancel"
194
+      />
195
+    </Card>
196
+  </Spin>
197
+</template>

+ 43 - 0
apps/web-ele/src/views/workflow/leave/leaveEdit.vue

@@ -0,0 +1,43 @@
1
+<!--
2
+后端版本>=5.4.0  这个从本地路由变为从后台返回
3
+未修改文件名 而是新加了这个文件
4
+-->
5
+<script setup lang="ts">
6
+import { onMounted } from 'vue';
7
+import { useRoute, useRouter } from 'vue-router';
8
+
9
+import { useTabs } from '@vben/hooks';
10
+
11
+import { Spin } from 'ant-design-vue';
12
+
13
+import { useQueryId } from './hook';
14
+
15
+const router = useRouter();
16
+const route = useRoute();
17
+const id = route.query.id as string;
18
+
19
+/**
20
+ * 从我的任务 -> 点击重新编辑会跳转到这里
21
+ * 相当于一个中转 因为我的任务无法获取到列表页的路径(与ele交互不同)
22
+ *
23
+ * 为什么不使用路由的query来实现?
24
+ * 因为刷新后参数不会丢失 且tab存的也是全路径 切换也不会丢失 这不符合预期
25
+ * 可以通过window.history.replaceState来删除query参数 但是tab切换还是会保留
26
+ */
27
+const { closeCurrentTab } = useTabs();
28
+const { businessId } = useQueryId();
29
+onMounted(async () => {
30
+  await closeCurrentTab();
31
+  if (id) {
32
+    // 设置业务ID 存储在内存
33
+    businessId.value = id;
34
+    router.push({ path: '/demo/leave' });
35
+  }
36
+});
37
+</script>
38
+
39
+<template>
40
+  <div>
41
+    <Spin :spinning="true" />
42
+  </div>
43
+</template>

+ 246 - 0
apps/web-ele/src/views/workflow/order/creatOrder.vue

@@ -0,0 +1,246 @@
1
+<script lang="ts" setup>
2
+import { ref, reactive } from 'vue';
3
+import { Page } from '@vben/common-ui';
4
+import { ElForm, ElFormItem, ElInput, ElSelect, ElUpload, ElSwitch, ElButton, ElMessage, ElTreeSelect } from 'element-plus';
5
+import { UploadFilled } from '@element-plus/icons-vue';
6
+import { categoryTree } from '#/api/workflow/category/index';
7
+import { selectAllSysStation } from '#/api/system/infoEntry/stationInfo/stationInfo';
8
+import { getSingleImageUploadConfig } from '#/components/upload/config';
9
+import { requestClient } from '#/api/request';
10
+
11
+// 表单数据
12
+const formData = reactive({
13
+  ticketType: '',
14
+  stationId: '',
15
+  contact: '',
16
+  phone: '',
17
+  content: '',
18
+  attachments: '',
19
+  isSubmit: false
20
+});
21
+
22
+// 表单规则
23
+const formRules = reactive({
24
+  ticketType: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
25
+  stationId: [{ required: true, message: '请选择场站', trigger: 'change' }],
26
+  contact: [{ required: true, message: '请输入联系人', trigger: 'blur' }],
27
+  phone: [
28
+    { required: true, message: '请输入联系电话', trigger: 'blur' },
29
+    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
30
+  ],
31
+  content: [{ required: true, message: '请输入提报内容', trigger: 'blur' }]
32
+});
33
+
34
+// 工单类型选项
35
+const ticketTypeOptions = ref<any[]>([]);
36
+// 场站选项
37
+const stationOptions = ref<any[]>([]);
38
+// 上传文件列表
39
+const fileList = ref([]);
40
+
41
+// 表单引用
42
+const formRef = ref();
43
+
44
+// 获取工单类型
45
+const getTicketTypes = async () => {
46
+  try {
47
+    const res = await categoryTree();
48
+    console.log(res);
49
+    ticketTypeOptions.value = res || [];
50
+  } catch (error) {
51
+    ElMessage.error('获取工单类型失败');
52
+    console.error('获取工单类型失败:', error);
53
+  }
54
+};
55
+
56
+// 获取场站列表
57
+const getStations = async () => {
58
+  try {
59
+    const res = await selectAllSysStation();
60
+    console.log(res);
61
+    stationOptions.value = res || [];
62
+  } catch (error) {
63
+    ElMessage.error('获取场站列表失败');
64
+    console.error('获取场站列表失败:', error);
65
+  }
66
+};
67
+
68
+// 上传成功回调
69
+const handleUploadSuccess = (response, file, fileList) => {
70
+  if (response.code === 200) {
71
+    // 将文件路径添加到attachments字段
72
+    console.log(response, file, fileList);
73
+    const paths = fileList.map(f => f.response.data.fileName).join(',');
74
+    formData.attachments = paths;
75
+  } else {
76
+    ElMessage.error('文件上传失败');
77
+  }
78
+};
79
+
80
+// 上传失败回调
81
+const handleUploadError = (error) => {
82
+  ElMessage.error('文件上传失败');
83
+  console.error('文件上传失败:', error);
84
+};
85
+
86
+// 移除文件回调
87
+const handleRemove = (file, fileList) => {
88
+  // 更新attachments字段
89
+  const paths = fileList.map(f => f.response.data.url).join(',');
90
+  formData.attachments = paths;
91
+};
92
+
93
+// 表单提交
94
+const handleSubmit = async () => {
95
+  if (!formRef.value) return;
96
+  
97
+  try {
98
+    await formRef.value.validate();
99
+    console.log(formData);
100
+    // 调用创建工单接口
101
+    await requestClient.post('/workOrder/order', formData);
102
+    
103
+    ElMessage.success('工单创建成功');
104
+    
105
+    // 重置表单
106
+    formRef.value.resetFields();
107
+    fileList.value = [];
108
+    formData.attachments = '';
109
+  } catch (error) {
110
+    if (error.name === 'ValidationError') {
111
+      // 表单验证失败,Element Plus会自动提示错误信息
112
+      return;
113
+    }
114
+    
115
+    ElMessage.error('工单创建失败');
116
+    console.error('工单创建失败:', error);
117
+  }
118
+};
119
+
120
+// 初始化数据
121
+const initData = () => {
122
+  getTicketTypes();
123
+  getStations();
124
+};
125
+
126
+// 页面加载时初始化数据
127
+initData();
128
+</script>
129
+
130
+<template>
131
+  <Page>
132
+    <div class="wrap">
133
+      <ElForm
134
+        ref="formRef"
135
+        :model="formData"
136
+        :rules="formRules"
137
+        label-width="100px"
138
+        class="create-order-form"
139
+      >
140
+        <!-- 工单类型 -->
141
+        <ElFormItem label="工单类型" prop="ticketType">
142
+          <ElTreeSelect
143
+            v-model="formData.ticketType"
144
+            :data="ticketTypeOptions"
145
+            check-strictly
146
+            placeholder="请选择工单类型"
147
+            style="width: 100%"
148
+            :props="{ label: 'label', value: 'id', children: 'children' }"
149
+            filterable
150
+          ></ElTreeSelect>
151
+        </ElFormItem>
152
+
153
+        <!-- 场站 -->
154
+        <ElFormItem label="场站" prop="stationId">
155
+          <ElSelect
156
+            v-model="formData.stationId"
157
+            placeholder="请选择场站"
158
+            style="width: 100%"
159
+          >
160
+            <ElOption
161
+              v-for="option in stationOptions"
162
+              :key="option.id"
163
+              :label="option.stationName"
164
+              :value="option.id"
165
+            ></ElOption>
166
+          </ElSelect>
167
+        </ElFormItem>
168
+
169
+        <!-- 联系人 -->
170
+        <ElFormItem label="联系人" prop="contact">
171
+          <ElInput
172
+            v-model="formData.contact"
173
+            placeholder="请输入联系人"
174
+          ></ElInput>
175
+        </ElFormItem>
176
+
177
+        <!-- 联系电话 -->
178
+        <ElFormItem label="联系电话" prop="phone">
179
+          <ElInput
180
+            v-model="formData.phone"
181
+            placeholder="请输入联系电话"
182
+          ></ElInput>
183
+        </ElFormItem>
184
+
185
+        <!-- 提报内容 -->
186
+        <ElFormItem label="提报内容" prop="content">
187
+          <ElInput
188
+            v-model="formData.content"
189
+            type="textarea"
190
+            :rows="4"
191
+            placeholder="请输入提报内容"
192
+          ></ElInput>
193
+        </ElFormItem>
194
+
195
+        <!-- 提报附件 -->
196
+        <ElFormItem label="提报附件">
197
+          <ElUpload
198
+            :file-list="fileList"
199
+            :action="getSingleImageUploadConfig().action"
200
+            :headers="getSingleImageUploadConfig().headers"
201
+            :on-success="handleUploadSuccess"
202
+            :on-error="handleUploadError"
203
+            :on-remove="handleRemove"
204
+            list-type="picture-card"
205
+            multiple
206
+            :limit="5"
207
+          >
208
+            <UploadFilled />
209
+            <template #tip>
210
+              <div class="el-upload__tip">
211
+                支持上传图片格式文件,最多5个,多个文件用逗号分隔
212
+              </div>
213
+            </template>
214
+          </ElUpload>
215
+        </ElFormItem>
216
+
217
+        <!-- 是否提交 -->
218
+        <ElFormItem label="是否提交">
219
+          <ElSwitch v-model="formData.isSubmit"></ElSwitch>
220
+        </ElFormItem>
221
+
222
+        <!-- 提交按钮 -->
223
+        <ElFormItem>
224
+          <ElButton type="primary" @click="handleSubmit">创建工单</ElButton>
225
+        </ElFormItem>
226
+      </ElForm>
227
+    </div>
228
+  </Page>
229
+</template>
230
+
231
+<style lang="scss" scoped>
232
+.wrap {
233
+  padding: 16px;
234
+  background-color: #fff;
235
+  border-radius: 6px;
236
+}
237
+
238
+.create-order-form {
239
+  max-width: 600px;
240
+}
241
+</style>
242
+
243
+
244
+
245
+
246
+

+ 6 - 4
apps/web-ele/src/views/workflow/register.ts

@@ -1,10 +1,12 @@
1
+import { defineAsyncComponent, markRaw } from 'vue';
2
+
1 3
 /**
2 4
  * 这里定义流程描述组件
3 5
  */
4 6
 
5
-// const LeaveDescription = defineAsyncComponent(
6
-//   () => import('#/views/workflow/leave/leave-description.vue'),
7
-// );
7
+const LeaveDescription = defineAsyncComponent(
8
+  () => import('#/views/workflow/leave/leave-description.vue'),
9
+);
8 10
 
9 11
 /**
10 12
  * key为流程的路径(task.formPath) value为要显示的组件
@@ -13,7 +15,7 @@ export const flowComponentsMap = {
13 15
   /**
14 16
    * 请假申请 详情
15 17
    */
16
-  '/workflow/leaveEdit/index': null,
18
+  '/workflow/leaveEdit/index': markRaw(LeaveDescription),
17 19
 };
18 20
 
19 21
 export type FlowComponentsMapMapKey = keyof typeof flowComponentsMap;

+ 91 - 87
apps/web-ele/src/views/workflow/task/allTaskWaiting.vue

@@ -7,8 +7,19 @@ import { computed, nextTick, onMounted, ref, useTemplateRef } from 'vue';
7 7
 
8 8
 import { Page } from '@vben/common-ui';
9 9
 import { useTabs } from '@vben/hooks';
10
-import { addFullName, getPopupContainer } from '@vben/utils';
11 10
 
11
+<<<<<<< HEAD
12
+import { Filter, Refresh } from '@element-plus/icons-vue';
13
+import {
14
+  ElButton,
15
+  ElForm,
16
+  ElFormItem,
17
+  ElInput,
18
+  ElMessage,
19
+  ElPopover,
20
+  ElTooltip,
21
+} from 'element-plus';
22
+=======
12 23
 // import {
13 24
 //   Empty,
14 25
 //   Form,
@@ -21,6 +32,7 @@ import { addFullName, getPopupContainer } from '@vben/utils';
21 32
 //   Tooltip,
22 33
 //   TreeSelect,
23 34
 // } from 'ant-design-vue';
35
+>>>>>>> 0a5e399be8a3dc56c82fd915b6765781a5d30748
24 36
 import { cloneDeep, debounce, uniqueId } from 'lodash-es';
25 37
 
26 38
 import { categoryTree } from '#/api/workflow/category';
@@ -147,20 +159,25 @@ const handleScroll = debounce(async (e: Event) => {
147 159
   // 滚动到底部且没有加载完成
148 160
   if (isBottom && !isLoadComplete.value) {
149 161
     loading.value = true;
150
-    page.value += 1;
151
-    const resp = await currentApi.value({
152
-      pageSize: 10,
153
-      pageNum: page.value,
154
-      ...formData.value,
155
-    });
156
-    taskList.value.push(
157
-      ...resp.rows.map((item) => ({
158
-        ...item,
159
-        active: false,
160
-        randomId: uniqueId(),
161
-      })),
162
-    );
163
-    loading.value = false;
162
+    try {
163
+      page.value += 1;
164
+      const resp = await currentApi.value({
165
+        pageSize: 10,
166
+        pageNum: page.value,
167
+        ...formData.value,
168
+      });
169
+      taskList.value.push(
170
+        ...resp.rows.map((item) => ({
171
+          ...item,
172
+          active: false,
173
+          randomId: uniqueId(),
174
+        })),
175
+      );
176
+    } catch {
177
+      ElMessage.error('加载更多任务失败');
178
+    } finally {
179
+      loading.value = false;
180
+    }
164 181
   }
165 182
 }, 200);
166 183
 
@@ -210,92 +227,79 @@ onMounted(async () => {
210 227
         <div
211 228
           class="bg-background z-100 sticky left-0 top-0 w-full rounded-t-lg border-b-[1px] border-solid p-2"
212 229
         >
213
-          <Segmented
214
-            v-model:value="currentType"
215
-            :options="typeOptions"
216
-            block
217
-            class="mb-2"
218
-            @change="handleTypeChange"
219
-          />
230
+          <div class="mb-2 flex items-center gap-1">
231
+            <ElButton
232
+              :type="currentType === 'todo' ? 'primary' : 'default'"
233
+              @click="
234
+                currentType = 'todo';
235
+                handleTypeChange();
236
+              "
237
+            >
238
+              待办任务
239
+            </ElButton>
240
+            <ElButton
241
+              :type="currentType === 'done' ? 'primary' : 'default'"
242
+              @click="
243
+                currentType = 'done';
244
+                handleTypeChange();
245
+              "
246
+            >
247
+              已办任务
248
+            </ElButton>
249
+          </div>
220 250
           <div class="flex items-center gap-1">
221
-            <InputSearch
222
-              v-model:value="formData.flowName"
251
+            <ElInput
252
+              v-model="formData.flowName"
223 253
               placeholder="流程名称搜索"
224
-              @search="reload(false)"
254
+              clearable
255
+              class="w-full"
256
+              @keyup.enter="reload(false)"
225 257
             />
226
-            <Tooltip placement="top" title="重置">
227
-              <a-button @click="reload(true)">
228
-                <RedoOutlined />
229
-              </a-button>
230
-            </Tooltip>
231
-            <Popover
232
-              v-model:open="popoverOpen"
233
-              :get-popup-container="getPopupContainer"
234
-              placement="rightTop"
235
-              trigger="click"
236
-            >
237
-              <template #title>
238
-                <div class="w-full border-b pb-[12px] text-[16px]">搜索</div>
258
+            <ElTooltip placement="top" content="重置">
259
+              <ElButton @click="reload(true)" circle>
260
+                <Refresh />
261
+              </ElButton>
262
+            </ElTooltip>
263
+            <ElPopover placement="top-end" trigger="click">
264
+              <template #reference>
265
+                <ElButton circle>
266
+                  <Filter />
267
+                </ElButton>
239 268
               </template>
240
-              <template #content>
241
-                <Form
242
-                  :colon="false"
243
-                  :label-col="{ span: 6 }"
269
+              <template #default>
270
+                <ElForm
244 271
                   :model="formData"
245
-                  autocomplete="off"
272
+                  label-width="80px"
246 273
                   class="w-[300px]"
247
-                  @finish="() => reload(false)"
274
+                  @submit.prevent="reload(false)"
248 275
                 >
249
-                  <FormItem label="申请人">
250
-                    <!-- 弹窗关闭后仍然显示表单浮层 -->
276
+                  <ElFormItem label="申请人">
251 277
                     <CopyComponent
252 278
                       v-model:user-list="selectedUserList"
253 279
                       @cancel="() => (popoverOpen = true)"
254 280
                       @finish="handleFinish"
255 281
                     />
256
-                  </FormItem>
257
-                  <FormItem label="流程分类">
258
-                    <TreeSelect
259
-                      v-model:value="formData.category"
260
-                      :allow-clear="true"
261
-                      :field-names="{ label: 'label', value: 'id' }"
262
-                      :get-popup-container="getPopupContainer"
263
-                      :tree-data="treeData"
264
-                      :tree-default-expand-all="true"
265
-                      :tree-line="{ showLeafIcon: false }"
266
-                      placeholder="请选择"
267
-                      tree-node-filter-prop="label"
268
-                      tree-node-label-prop="fullName"
269
-                    />
270
-                  </FormItem>
271
-                  <FormItem label="任务名称">
272
-                    <Input
273
-                      v-model:value="formData.nodeName"
274
-                      placeholder="请输入"
275
-                    />
276
-                  </FormItem>
277
-                  <FormItem label="流程编码">
278
-                    <Input
279
-                      v-model:value="formData.flowCode"
280
-                      placeholder="请输入"
281
-                    />
282
-                  </FormItem>
283
-                  <FormItem>
284
-                    <div class="flex">
285
-                      <a-button block html-type="submit" type="primary">
282
+                  </ElFormItem>
283
+                  <ElFormItem label="流程分类">
284
+                    <ElInput v-model="formData.category" placeholder="请选择" />
285
+                  </ElFormItem>
286
+                  <ElFormItem label="任务名称">
287
+                    <ElInput v-model="formData.nodeName" placeholder="请输入" />
288
+                  </ElFormItem>
289
+                  <ElFormItem label="流程编码">
290
+                    <ElInput v-model="formData.flowCode" placeholder="请输入" />
291
+                  </ElFormItem>
292
+                  <ElFormItem>
293
+                    <div class="flex justify-end gap-2">
294
+                      <ElButton type="primary" @click="reload(false)">
286 295
                         搜索
287
-                      </a-button>
288
-                      <a-button block class="ml-2" @click="reload(true)">
289
-                        重置
290
-                      </a-button>
296
+                      </ElButton>
297
+                      <ElButton @click="reload(true)"> 重置 </ElButton>
291 298
                     </div>
292
-                  </FormItem>
293
-                </Form>
299
+                  </ElFormItem>
300
+                </ElForm>
294 301
               </template>
295
-              <a-button>
296
-                <FilterOutlined />
297
-              </a-button>
298
-            </Popover>
302
+            </ElPopover>
299 303
           </div>
300 304
         </div>
301 305
         <div
@@ -353,7 +357,7 @@ onMounted(async () => {
353 357
   }
354 358
 }
355 359
 
356
-:deep(.ant-card-body) {
360
+:deep(.el-card__body) {
357 361
   @apply thin-scrollbar;
358 362
 }
359 363
 </style>

+ 24 - 22
apps/web-ele/src/views/workflow/task/myDocument.vue

@@ -7,22 +7,15 @@ import { computed, onMounted, ref, useTemplateRef } from 'vue';
7 7
 import { Page } from '@vben/common-ui';
8 8
 import { useTabs } from '@vben/hooks';
9 9
 
10
-// import {
11
-//   ElButton,
12
-//   ElEmpty,
13
-//   ElForm,
14
-//   ElFormItem,
15
-//   ElInput,
16
-//   ElMessage,
17
-//   ElPopover,
18
-//   ElSpin,
19
-//   ElTooltip,
20
-// } from 'element-plus';
10
+import { Filter, Refresh, Search } from '@element-plus/icons-vue';
11
+import { ElButton, ElEmpty, ElForm, ElFormItem, ElInput, ElMessage, ElPopover, ElTooltip } from 'element-plus';
12
+
21 13
 import { cloneDeep, debounce } from 'lodash-es';
22 14
 
23
-import { pageByCurrent } from '#/api/workflow/instance';
15
+import { pageByCurrent, getWorkOrderDetail } from '#/api/workflow/instance';
24 16
 
25 17
 import { bottomOffset } from './constant';
18
+import { ApprovalCard, ApprovalPanel } from '../components';
26 19
 
27 20
 const emptyImage = ElEmpty.PRESENTED_IMAGE_SIMPLE;
28 21
 
@@ -72,6 +65,7 @@ async function reload(resetFields: boolean = false) {
72 65
       pageNum: page.value,
73 66
       ...formData.value,
74 67
     });
68
+    console.log(resp);
75 69
     taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
76 70
     taskTotal.value = resp.total;
77 71
   } catch {
@@ -111,6 +105,7 @@ const handleScroll = debounce(async (e: Event) => {
111 105
         pageNum: page.value,
112 106
         ...formData.value,
113 107
       });
108
+      console.log(resp);
114 109
       taskList.value.push(
115 110
         ...resp.rows.map((item) => ({ ...item, active: false })),
116 111
       );
@@ -136,6 +131,17 @@ async function handleCardClick(item: TaskInfo) {
136 131
     item.active = item.id === id;
137 132
   });
138 133
   lastSelectId.value = id;
134
+  console.log(lastSelectId.value);
135
+  
136
+  // 调用获取工单详情的接口
137
+  try {
138
+    const detail = await getWorkOrderDetail(id);
139
+    console.log('工单详情:', detail);
140
+    // 可以在这里使用获取到的工单详情数据
141
+  } catch (error) {
142
+    console.error('获取工单详情失败:', error);
143
+    ElMessage.error('获取工单详情失败');
144
+  }
139 145
 }
140 146
 
141 147
 const { refreshTab } = useTabs();
@@ -157,18 +163,15 @@ const { refreshTab } = useTabs();
157 163
               placeholder="流程名称搜索"
158 164
               clearable
159 165
               class="w-full"
166
+              :suffix-icon="Search"
160 167
               @keyup.enter="reload(false)"
161 168
             />
162 169
             <ElTooltip placement="top" content="重置">
163
-              <ElButton @click="reload(true)" circle>
164
-                <Refresh />
165
-              </ElButton>
170
+              <ElButton @click="reload(true)" :icon="Refresh" />
166 171
             </ElTooltip>
167
-            <ElPopover placement="right-top" trigger="click">
172
+            <ElPopover placement="top-end" trigger="click">
168 173
               <template #reference>
169
-                <ElButton circle>
170
-                  <Filter />
171
-                </ElButton>
174
+                <ElButton :icon="Filter"  style="margin-left: 0;" />
172 175
               </template>
173 176
               <template #default>
174 177
                 <ElForm
@@ -221,9 +224,7 @@ const { refreshTab } = useTabs();
221 224
           <div
222 225
             v-if="loading"
223 226
             class="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-[rgba(0,0,0,0.1)]"
224
-          >
225
-            <ElSpin tip="加载中..." />
226
-          </div>
227
+          ></div>
227 228
         </div>
228 229
         <!-- total显示 -->
229 230
         <div
@@ -250,3 +251,4 @@ const { refreshTab } = useTabs();
250 251
   @apply thin-scrollbar;
251 252
 }
252 253
 </style>
254
+

+ 67 - 124
apps/web-ele/src/views/workflow/task/taskCopyList.vue

@@ -1,33 +1,21 @@
1 1
 <!-- eslint-disable no-use-before-define -->
2 2
 <script setup lang="ts">
3
-import type { User } from '#/api/system/user/model';
4 3
 import type { TaskInfo } from '#/api/workflow/task/model';
5 4
 
6 5
 import { computed, onMounted, ref, useTemplateRef } from 'vue';
7 6
 
8 7
 import { Page } from '@vben/common-ui';
9
-import { addFullName, getPopupContainer } from '@vben/utils';
10 8
 
11
-// import {
12
-//   Empty,
13
-//   Form,
14
-//   FormItem,
15
-//   Input,
16
-//   InputSearch,
17
-//   Popover,
18
-//   Spin,
19
-//   Tooltip,
20
-//   TreeSelect,
21
-// } from 'ant-design-vue';
9
+import { ElEmpty, ElMessage } from 'element-plus';
10
+
22 11
 import { cloneDeep, debounce } from 'lodash-es';
23 12
 
24
-import { categoryTree } from '#/api/workflow/category';
25 13
 import { pageByTaskCopy } from '#/api/workflow/task';
26 14
 
27
-import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
15
+import { ApprovalCard, ApprovalPanel } from '../components';
28 16
 import { bottomOffset } from './constant';
29 17
 
30
-const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
18
+const emptyImage = ElEmpty.PRESENTED_IMAGE_SIMPLE;
31 19
 
32 20
 const taskList = ref<(TaskInfo & { active: boolean })[]>([]);
33 21
 const taskTotal = ref(0);
@@ -38,7 +26,6 @@ const defaultFormData = {
38 26
   flowName: '', // 流程定义名称
39 27
   nodeName: '', // 任务名称
40 28
   flowCode: '', // 流程定义编码
41
-  createByIds: [] as string[], // 创建人
42 29
   category: null as null | number, // 流程分类
43 30
 };
44 31
 const formData = ref(cloneDeep(defaultFormData));
@@ -67,19 +54,23 @@ async function reload(resetFields: boolean = false) {
67 54
 
68 55
   if (resetFields) {
69 56
     formData.value = cloneDeep(defaultFormData);
70
-    selectedUserList.value = [];
71 57
   }
72 58
 
73 59
   loading.value = true;
74
-  const resp = await pageByTaskCopy({
75
-    pageSize: 10,
76
-    pageNum: page.value,
77
-    ...formData.value,
78
-  });
79
-  taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
80
-  taskTotal.value = resp.total;
60
+  try {
61
+    const resp = await pageByTaskCopy({
62
+      pageSize: 10,
63
+      pageNum: page.value,
64
+      ...formData.value,
65
+    });
66
+    taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
67
+    taskTotal.value = resp.total;
68
+  } catch {
69
+    ElMessage.error('加载任务列表失败');
70
+  } finally {
71
+    loading.value = false;
72
+  }
81 73
 
82
-  loading.value = false;
83 74
   // 默认选中第一个
84 75
   if (taskList.value.length > 0) {
85 76
     const firstTask = taskList.value[0]!;
@@ -104,16 +95,21 @@ const handleScroll = debounce(async (e: Event) => {
104 95
   // 滚动到底部且没有加载完成
105 96
   if (isBottom && !isLoadComplete.value) {
106 97
     loading.value = true;
107
-    page.value += 1;
108
-    const resp = await pageByTaskCopy({
109
-      pageSize: 10,
110
-      pageNum: page.value,
111
-      ...formData.value,
112
-    });
113
-    taskList.value.push(
114
-      ...resp.rows.map((item) => ({ ...item, active: false })),
115
-    );
116
-    loading.value = false;
98
+    try {
99
+      page.value += 1;
100
+      const resp = await pageByTaskCopy({
101
+        pageSize: 10,
102
+        pageNum: page.value,
103
+        ...formData.value,
104
+      });
105
+      taskList.value.push(
106
+        ...resp.rows.map((item) => ({ ...item, active: false })),
107
+      );
108
+    } catch {
109
+      ElMessage.error('加载更多任务失败');
110
+    } finally {
111
+      loading.value = false;
112
+    }
117 113
   }
118 114
 }, 200);
119 115
 
@@ -132,23 +128,6 @@ async function handleCardClick(item: TaskInfo) {
132 128
   });
133 129
   lastSelectId.value = id;
134 130
 }
135
-
136
-// 由于失去焦点浮层会消失 使用v-model选择人员完毕后强制显示
137
-const popoverOpen = ref(false);
138
-const selectedUserList = ref<User[]>([]);
139
-function handleFinish(userList: User[]) {
140
-  popoverOpen.value = true;
141
-  selectedUserList.value = userList;
142
-  formData.value.createByIds = userList.map((item) => item.userId);
143
-}
144
-
145
-const treeData = ref<any[]>([]);
146
-onMounted(async () => {
147
-  // menu
148
-  const tree = await categoryTree();
149
-  addFullName(tree, 'label', ' / ');
150
-  treeData.value = tree;
151
-});
152 131
 </script>
153 132
 
154 133
 <template>
@@ -162,84 +141,48 @@ onMounted(async () => {
162 141
           class="bg-background z-100 sticky left-0 top-0 w-full rounded-t-lg border-b-[1px] border-solid p-2"
163 142
         >
164 143
           <div class="flex items-center gap-1">
165
-            <InputSearch
166
-              v-model:value="formData.flowName"
144
+            <ElInput
145
+              v-model="formData.flowName"
167 146
               placeholder="流程名称搜索"
168
-              @search="reload(false)"
147
+              clearable
148
+              class="w-full"
149
+              @keyup.enter="reload(false)"
169 150
             />
170
-            <Tooltip placement="top" title="重置">
171
-              <a-button @click="reload(true)">
172
-                <RedoOutlined />
173
-              </a-button>
174
-            </Tooltip>
175
-            <Popover
176
-              v-model:open="popoverOpen"
177
-              :get-popup-container="getPopupContainer"
178
-              placement="rightTop"
179
-              trigger="click"
180
-            >
181
-              <template #title>
182
-                <div class="w-full border-b pb-[12px] text-[16px]">搜索</div>
151
+            <ElTooltip placement="top" content="重置">
152
+              <ElButton @click="reload(true)" circle>
153
+                <Refresh />
154
+              </ElButton>
155
+            </ElTooltip>
156
+            <ElPopover placement="top-end" trigger="click">
157
+              <template #reference>
158
+                <ElButton circle>
159
+                  <Filter />
160
+                </ElButton>
183 161
               </template>
184
-              <template #content>
185
-                <Form
186
-                  :colon="false"
187
-                  :label-col="{ span: 6 }"
162
+              <template #default>
163
+                <ElForm
188 164
                   :model="formData"
189
-                  autocomplete="off"
165
+                  label-width="80px"
190 166
                   class="w-[300px]"
191
-                  @finish="() => reload(false)"
167
+                  @submit.prevent="reload(false)"
192 168
                 >
193
-                  <FormItem label="申请人">
194
-                    <!-- 弹窗关闭后仍然显示表单浮层 -->
195
-                    <CopyComponent
196
-                      v-model:user-list="selectedUserList"
197
-                      @cancel="() => (popoverOpen = true)"
198
-                      @finish="handleFinish"
199
-                    />
200
-                  </FormItem>
201
-                  <FormItem label="流程分类">
202
-                    <TreeSelect
203
-                      v-model:value="formData.category"
204
-                      :allow-clear="true"
205
-                      :field-names="{ label: 'label', value: 'id' }"
206
-                      :get-popup-container="getPopupContainer"
207
-                      :tree-data="treeData"
208
-                      :tree-default-expand-all="true"
209
-                      :tree-line="{ showLeafIcon: false }"
210
-                      placeholder="请选择"
211
-                      tree-node-filter-prop="label"
212
-                      tree-node-label-prop="fullName"
213
-                    />
214
-                  </FormItem>
215
-                  <FormItem label="任务名称">
216
-                    <Input
217
-                      v-model:value="formData.nodeName"
218
-                      placeholder="请输入"
219
-                    />
220
-                  </FormItem>
221
-                  <FormItem label="流程编码">
222
-                    <Input
223
-                      v-model:value="formData.flowCode"
224
-                      placeholder="请输入"
225
-                    />
226
-                  </FormItem>
227
-                  <FormItem>
228
-                    <div class="flex">
229
-                      <a-button block html-type="submit" type="primary">
169
+                  <ElFormItem label="任务名称">
170
+                    <ElInput v-model="formData.nodeName" placeholder="请输入" />
171
+                  </ElFormItem>
172
+                  <ElFormItem label="流程编码">
173
+                    <ElInput v-model="formData.flowCode" placeholder="请输入" />
174
+                  </ElFormItem>
175
+                  <ElFormItem>
176
+                    <div class="flex justify-end gap-2">
177
+                      <ElButton type="primary" @click="reload(false)">
230 178
                         搜索
231
-                      </a-button>
232
-                      <a-button block class="ml-2" @click="reload(true)">
233
-                        重置
234
-                      </a-button>
179
+                      </ElButton>
180
+                      <ElButton @click="reload(true)"> 重置 </ElButton>
235 181
                     </div>
236
-                  </FormItem>
237
-                </Form>
182
+                  </ElFormItem>
183
+                </ElForm>
238 184
               </template>
239
-              <a-button>
240
-                <FilterOutlined />
241
-              </a-button>
242
-            </Popover>
185
+            </ElPopover>
243 186
           </div>
244 187
         </div>
245 188
         <div
@@ -256,7 +199,7 @@ onMounted(async () => {
256 199
               @click="handleCardClick(item)"
257 200
             />
258 201
           </template>
259
-          <Empty v-else :image="emptyImage" />
202
+          <ElEmpty v-else :image="emptyImage" description="暂无数据" />
260 203
           <div
261 204
             v-if="isLoadComplete && taskList.length > 0"
262 205
             class="flex items-center justify-center text-[14px] opacity-50"
@@ -292,7 +235,7 @@ onMounted(async () => {
292 235
   }
293 236
 }
294 237
 
295
-:deep(.ant-card-body) {
238
+:deep(.el-card__body) {
296 239
   @apply thin-scrollbar;
297 240
 }
298 241
 </style>

+ 78 - 129
apps/web-ele/src/views/workflow/task/taskFinish.vue

@@ -1,33 +1,31 @@
1 1
 <!-- eslint-disable no-use-before-define -->
2 2
 <script setup lang="ts">
3
-import type { User } from '#/api/system/user/model';
4 3
 import type { TaskInfo } from '#/api/workflow/task/model';
5 4
 
6 5
 import { computed, onMounted, ref, useTemplateRef } from 'vue';
7 6
 
8 7
 import { Page } from '@vben/common-ui';
9
-import { addFullName, getPopupContainer } from '@vben/utils';
10 8
 
11
-// import {
12
-//   Empty,
13
-//   Form,
14
-//   FormItem,
15
-//   Input,
16
-//   InputSearch,
17
-//   Popover,
18
-//   Spin,
19
-//   Tooltip,
20
-//   TreeSelect,
21
-// } from 'ant-design-vue';
9
+import { Filter, Refresh } from '@element-plus/icons-vue';
10
+import {
11
+  ElButton,
12
+  ElEmpty,
13
+  ElForm,
14
+  ElFormItem,
15
+  ElInput,
16
+  ElMessage,
17
+  ElPopover,
18
+  ElTooltip,
19
+} from 'element-plus';
20
+
22 21
 import { cloneDeep, debounce } from 'lodash-es';
23 22
 
24
-import { categoryTree } from '#/api/workflow/category';
25 23
 import { pageByTaskFinish } from '#/api/workflow/task';
26 24
 
27
-import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
25
+import { ApprovalCard, ApprovalPanel } from '../components';
28 26
 import { bottomOffset } from './constant';
29 27
 
30
-const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
28
+const emptyImage = ElEmpty.PRESENTED_IMAGE_SIMPLE;
31 29
 
32 30
 const taskList = ref<(TaskInfo & { active: boolean })[]>([]);
33 31
 const taskTotal = ref(0);
@@ -38,8 +36,6 @@ const defaultFormData = {
38 36
   flowName: '', // 流程定义名称
39 37
   nodeName: '', // 任务名称
40 38
   flowCode: '', // 流程定义编码
41
-  createByIds: [] as string[], // 创建人
42
-  category: null as null | number, // 流程分类
43 39
 };
44 40
 const formData = ref(cloneDeep(defaultFormData));
45 41
 
@@ -67,19 +63,22 @@ async function reload(resetFields: boolean = false) {
67 63
 
68 64
   if (resetFields) {
69 65
     formData.value = cloneDeep(defaultFormData);
70
-    selectedUserList.value = [];
71 66
   }
72 67
 
73 68
   loading.value = true;
74
-  const resp = await pageByTaskFinish({
75
-    pageSize: 10,
76
-    pageNum: page.value,
77
-    ...formData.value,
78
-  });
79
-  taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
80
-  taskTotal.value = resp.total;
81
-
82
-  loading.value = false;
69
+  try {
70
+    const resp = await pageByTaskFinish({
71
+      pageSize: 10,
72
+      pageNum: page.value,
73
+      ...formData.value,
74
+    });
75
+    taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
76
+    taskTotal.value = resp.total;
77
+  } catch {
78
+    ElMessage.error('加载任务列表失败');
79
+  } finally {
80
+    loading.value = false;
81
+  }
83 82
   // 默认选中第一个
84 83
   if (taskList.value.length > 0) {
85 84
     const firstTask = taskList.value[0]!;
@@ -104,16 +103,21 @@ const handleScroll = debounce(async (e: Event) => {
104 103
   // 滚动到底部且没有加载完成
105 104
   if (isBottom && !isLoadComplete.value) {
106 105
     loading.value = true;
107
-    page.value += 1;
108
-    const resp = await pageByTaskFinish({
109
-      pageSize: 10,
110
-      pageNum: page.value,
111
-      ...formData.value,
112
-    });
113
-    taskList.value.push(
114
-      ...resp.rows.map((item) => ({ ...item, active: false })),
115
-    );
116
-    loading.value = false;
106
+    try {
107
+      page.value += 1;
108
+      const resp = await pageByTaskFinish({
109
+        pageSize: 10,
110
+        pageNum: page.value,
111
+        ...formData.value,
112
+      });
113
+      taskList.value.push(
114
+        ...resp.rows.map((item) => ({ ...item, active: false })),
115
+      );
116
+    } catch {
117
+      ElMessage.error('加载更多任务失败');
118
+    } finally {
119
+      loading.value = false;
120
+    }
117 121
   }
118 122
 }, 200);
119 123
 
@@ -132,23 +136,6 @@ async function handleCardClick(item: TaskInfo) {
132 136
   });
133 137
   lastSelectId.value = id;
134 138
 }
135
-
136
-// 由于失去焦点浮层会消失 使用v-model选择人员完毕后强制显示
137
-const popoverOpen = ref(false);
138
-const selectedUserList = ref<User[]>([]);
139
-function handleFinish(userList: User[]) {
140
-  popoverOpen.value = true;
141
-  selectedUserList.value = userList;
142
-  formData.value.createByIds = userList.map((item) => item.userId);
143
-}
144
-
145
-const treeData = ref<any[]>([]);
146
-onMounted(async () => {
147
-  // menu
148
-  const tree = await categoryTree();
149
-  addFullName(tree, 'label', ' / ');
150
-  treeData.value = tree;
151
-});
152 139
 </script>
153 140
 
154 141
 <template>
@@ -162,84 +149,48 @@ onMounted(async () => {
162 149
           class="bg-background z-100 sticky left-0 top-0 w-full rounded-t-lg border-b-[1px] border-solid p-2"
163 150
         >
164 151
           <div class="flex items-center gap-1">
165
-            <InputSearch
166
-              v-model:value="formData.flowName"
152
+            <ElInput
153
+              v-model="formData.flowName"
167 154
               placeholder="流程名称搜索"
168
-              @search="reload(false)"
155
+              clearable
156
+              class="w-full"
157
+              @keyup.enter="reload(false)"
169 158
             />
170
-            <Tooltip placement="top" title="重置">
171
-              <a-button @click="reload(true)">
172
-                <RedoOutlined />
173
-              </a-button>
174
-            </Tooltip>
175
-            <Popover
176
-              v-model:open="popoverOpen"
177
-              :get-popup-container="getPopupContainer"
178
-              placement="rightTop"
179
-              trigger="click"
180
-            >
181
-              <template #title>
182
-                <div class="w-full border-b pb-[12px] text-[16px]">搜索</div>
159
+            <ElTooltip placement="top" content="重置">
160
+              <ElButton @click="reload(true)" circle>
161
+                <Refresh />
162
+              </ElButton>
163
+            </ElTooltip>
164
+            <ElPopover placement="top-end" trigger="click">
165
+              <template #reference>
166
+                <ElButton circle>
167
+                  <Filter />
168
+                </ElButton>
183 169
               </template>
184
-              <template #content>
185
-                <Form
186
-                  :colon="false"
187
-                  :label-col="{ span: 6 }"
170
+              <template #default>
171
+                <ElForm
188 172
                   :model="formData"
189
-                  autocomplete="off"
173
+                  label-width="80px"
190 174
                   class="w-[300px]"
191
-                  @finish="() => reload(false)"
175
+                  @submit.prevent="reload(false)"
192 176
                 >
193
-                  <FormItem label="申请人">
194
-                    <!-- 弹窗关闭后仍然显示表单浮层 -->
195
-                    <CopyComponent
196
-                      v-model:user-list="selectedUserList"
197
-                      @cancel="() => (popoverOpen = true)"
198
-                      @finish="handleFinish"
199
-                    />
200
-                  </FormItem>
201
-                  <FormItem label="流程分类">
202
-                    <TreeSelect
203
-                      v-model:value="formData.category"
204
-                      :allow-clear="true"
205
-                      :field-names="{ label: 'label', value: 'id' }"
206
-                      :get-popup-container="getPopupContainer"
207
-                      :tree-data="treeData"
208
-                      :tree-default-expand-all="true"
209
-                      :tree-line="{ showLeafIcon: false }"
210
-                      placeholder="请选择"
211
-                      tree-node-filter-prop="label"
212
-                      tree-node-label-prop="fullName"
213
-                    />
214
-                  </FormItem>
215
-                  <FormItem label="任务名称">
216
-                    <Input
217
-                      v-model:value="formData.nodeName"
218
-                      placeholder="请输入"
219
-                    />
220
-                  </FormItem>
221
-                  <FormItem label="流程编码">
222
-                    <Input
223
-                      v-model:value="formData.flowCode"
224
-                      placeholder="请输入"
225
-                    />
226
-                  </FormItem>
227
-                  <FormItem>
228
-                    <div class="flex">
229
-                      <a-button block html-type="submit" type="primary">
177
+                  <ElFormItem label="任务名称">
178
+                    <ElInput v-model="formData.nodeName" placeholder="请输入" />
179
+                  </ElFormItem>
180
+                  <ElFormItem label="流程编码">
181
+                    <ElInput v-model="formData.flowCode" placeholder="请输入" />
182
+                  </ElFormItem>
183
+                  <ElFormItem>
184
+                    <div class="flex justify-end gap-2">
185
+                      <ElButton type="primary" @click="reload(false)">
230 186
                         搜索
231
-                      </a-button>
232
-                      <a-button block class="ml-2" @click="reload(true)">
233
-                        重置
234
-                      </a-button>
187
+                      </ElButton>
188
+                      <ElButton @click="reload(true)"> 重置 </ElButton>
235 189
                     </div>
236
-                  </FormItem>
237
-                </Form>
190
+                  </ElFormItem>
191
+                </ElForm>
238 192
               </template>
239
-              <a-button>
240
-                <FilterOutlined />
241
-              </a-button>
242
-            </Popover>
193
+            </ElPopover>
243 194
           </div>
244 195
         </div>
245 196
         <div
@@ -256,7 +207,7 @@ onMounted(async () => {
256 207
               @click="handleCardClick(item)"
257 208
             />
258 209
           </template>
259
-          <Empty v-else :image="emptyImage" />
210
+          <ElEmpty v-else :image="emptyImage" description="暂无数据" />
260 211
           <div
261 212
             v-if="isLoadComplete && taskList.length > 0"
262 213
             class="flex items-center justify-center text-[14px] opacity-50"
@@ -267,9 +218,7 @@ onMounted(async () => {
267 218
           <div
268 219
             v-if="loading"
269 220
             class="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-[rgba(0,0,0,0.1)]"
270
-          >
271
-            <Spin tip="加载中..." />
272
-          </div>
221
+          ></div>
273 222
         </div>
274 223
         <!-- total显示 -->
275 224
         <div
@@ -292,7 +241,7 @@ onMounted(async () => {
292 241
   }
293 242
 }
294 243
 
295
-:deep(.ant-card-body) {
244
+:deep(.el-card__body) {
296 245
   @apply thin-scrollbar;
297 246
 }
298 247
 </style>

+ 63 - 119
apps/web-ele/src/views/workflow/task/taskWaiting.vue

@@ -1,34 +1,32 @@
1 1
 <!-- eslint-disable no-use-before-define -->
2 2
 <script setup lang="ts">
3
-import type { User } from '#/api/system/user/model';
4 3
 import type { TaskInfo } from '#/api/workflow/task/model';
5 4
 
6 5
 import { computed, onMounted, ref, useTemplateRef } from 'vue';
7 6
 
8 7
 import { Page } from '@vben/common-ui';
9 8
 import { useTabs } from '@vben/hooks';
10
-import { addFullName, getPopupContainer } from '@vben/utils';
11 9
 
12
-// import {
13
-//   Empty,
14
-//   Form,
15
-//   FormItem,
16
-//   Input,
17
-//   InputSearch,
18
-//   Popover,
19
-//   Spin,
20
-//   Tooltip,
21
-//   TreeSelect,
22
-// } from 'ant-design-vue';
10
+import { Filter, Refresh } from '@element-plus/icons-vue';
11
+import {
12
+  ElButton,
13
+  ElEmpty,
14
+  ElForm,
15
+  ElFormItem,
16
+  ElInput,
17
+  ElMessage,
18
+  ElPopover,
19
+  ElTooltip,
20
+} from 'element-plus';
21
+
23 22
 import { cloneDeep, debounce } from 'lodash-es';
24 23
 
25
-import { categoryTree } from '#/api/workflow/category';
26 24
 import { pageByTaskWait } from '#/api/workflow/task';
27 25
 
28
-import { ApprovalCard, ApprovalPanel, CopyComponent } from '../components';
26
+import { ApprovalCard, ApprovalPanel } from '../components';
29 27
 import { bottomOffset } from './constant';
30 28
 
31
-const emptyImage = Empty.PRESENTED_IMAGE_SIMPLE;
29
+const emptyImage = ElEmpty.PRESENTED_IMAGE_SIMPLE;
32 30
 
33 31
 const taskList = ref<(TaskInfo & { active: boolean })[]>([]);
34 32
 const taskTotal = ref(0);
@@ -39,8 +37,6 @@ const defaultFormData = {
39 37
   flowName: '', // 流程定义名称
40 38
   nodeName: '', // 任务名称
41 39
   flowCode: '', // 流程定义编码
42
-  createByIds: [] as string[], // 创建人
43
-  category: null as null | number, // 流程分类
44 40
 };
45 41
 const formData = ref(cloneDeep(defaultFormData));
46 42
 
@@ -68,19 +64,22 @@ async function reload(resetFields: boolean = false) {
68 64
 
69 65
   if (resetFields) {
70 66
     formData.value = cloneDeep(defaultFormData);
71
-    selectedUserList.value = [];
72 67
   }
73 68
 
74 69
   loading.value = true;
75
-  const resp = await pageByTaskWait({
76
-    pageSize: 10,
77
-    pageNum: page.value,
78
-    ...formData.value,
79
-  });
80
-  taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
81
-  taskTotal.value = resp.total;
82
-
83
-  loading.value = false;
70
+  try {
71
+    const resp = await pageByTaskWait({
72
+      pageSize: 10,
73
+      pageNum: page.value,
74
+      ...formData.value,
75
+    });
76
+    taskList.value = resp.rows.map((item) => ({ ...item, active: false }));
77
+    taskTotal.value = resp.total;
78
+  } catch {
79
+    ElMessage.error('加载任务列表失败');
80
+  } finally {
81
+    loading.value = false;
82
+  }
84 83
   // 默认选中第一个
85 84
   if (taskList.value.length > 0) {
86 85
     const firstTask = taskList.value[0]!;
@@ -135,23 +134,6 @@ async function handleCardClick(item: TaskInfo) {
135 134
 }
136 135
 
137 136
 const { refreshTab } = useTabs();
138
-
139
-// 由于失去焦点浮层会消失 使用v-model选择人员完毕后强制显示
140
-const popoverOpen = ref(false);
141
-const selectedUserList = ref<User[]>([]);
142
-function handleFinish(userList: User[]) {
143
-  popoverOpen.value = true;
144
-  selectedUserList.value = userList;
145
-  formData.value.createByIds = userList.map((item) => item.userId);
146
-}
147
-
148
-const treeData = ref<any[]>([]);
149
-onMounted(async () => {
150
-  // menu
151
-  const tree = await categoryTree();
152
-  addFullName(tree, 'label', ' / ');
153
-  treeData.value = tree;
154
-});
155 137
 </script>
156 138
 
157 139
 <template>
@@ -165,84 +147,48 @@ onMounted(async () => {
165 147
           class="bg-background z-100 sticky left-0 top-0 w-full rounded-t-lg border-b-[1px] border-solid p-2"
166 148
         >
167 149
           <div class="flex items-center gap-1">
168
-            <InputSearch
169
-              v-model:value="formData.flowName"
150
+            <ElInput
151
+              v-model="formData.flowName"
170 152
               placeholder="流程名称搜索"
171
-              @search="reload(false)"
153
+              clearable
154
+              class="w-full"
155
+              @keyup.enter="reload(false)"
172 156
             />
173
-            <Tooltip placement="top" title="重置">
174
-              <a-button @click="reload(true)">
175
-                <RedoOutlined />
176
-              </a-button>
177
-            </Tooltip>
178
-            <Popover
179
-              v-model:open="popoverOpen"
180
-              :get-popup-container="getPopupContainer"
181
-              placement="rightTop"
182
-              trigger="click"
183
-            >
184
-              <template #title>
185
-                <div class="w-full border-b pb-[12px] text-[16px]">搜索</div>
157
+            <ElTooltip placement="top" content="重置">
158
+              <ElButton @click="reload(true)" circle>
159
+                <Refresh />
160
+              </ElButton>
161
+            </ElTooltip>
162
+            <ElPopover placement="top-end" trigger="click">
163
+              <template #reference>
164
+                <ElButton circle>
165
+                  <Filter />
166
+                </ElButton>
186 167
               </template>
187
-              <template #content>
188
-                <Form
189
-                  :colon="false"
190
-                  :label-col="{ span: 6 }"
168
+              <template #default>
169
+                <ElForm
191 170
                   :model="formData"
192
-                  autocomplete="off"
171
+                  label-width="80px"
193 172
                   class="w-[300px]"
194
-                  @finish="() => reload(false)"
173
+                  @submit.prevent="reload(false)"
195 174
                 >
196
-                  <FormItem label="申请人">
197
-                    <!-- 弹窗关闭后仍然显示表单浮层 -->
198
-                    <CopyComponent
199
-                      v-model:user-list="selectedUserList"
200
-                      @cancel="() => (popoverOpen = true)"
201
-                      @finish="handleFinish"
202
-                    />
203
-                  </FormItem>
204
-                  <FormItem label="流程分类">
205
-                    <TreeSelect
206
-                      v-model:value="formData.category"
207
-                      :allow-clear="true"
208
-                      :field-names="{ label: 'label', value: 'id' }"
209
-                      :get-popup-container="getPopupContainer"
210
-                      :tree-data="treeData"
211
-                      :tree-default-expand-all="true"
212
-                      :tree-line="{ showLeafIcon: false }"
213
-                      placeholder="请选择"
214
-                      tree-node-filter-prop="label"
215
-                      tree-node-label-prop="fullName"
216
-                    />
217
-                  </FormItem>
218
-                  <FormItem label="任务名称">
219
-                    <Input
220
-                      v-model:value="formData.nodeName"
221
-                      placeholder="请输入"
222
-                    />
223
-                  </FormItem>
224
-                  <FormItem label="流程编码">
225
-                    <Input
226
-                      v-model:value="formData.flowCode"
227
-                      placeholder="请输入"
228
-                    />
229
-                  </FormItem>
230
-                  <FormItem>
231
-                    <div class="flex">
232
-                      <a-button block html-type="submit" type="primary">
175
+                  <ElFormItem label="任务名称">
176
+                    <ElInput v-model="formData.nodeName" placeholder="请输入" />
177
+                  </ElFormItem>
178
+                  <ElFormItem label="流程编码">
179
+                    <ElInput v-model="formData.flowCode" placeholder="请输入" />
180
+                  </ElFormItem>
181
+                  <ElFormItem>
182
+                    <div class="flex justify-end gap-2">
183
+                      <ElButton type="primary" @click="reload(false)">
233 184
                         搜索
234
-                      </a-button>
235
-                      <a-button block class="ml-2" @click="reload(true)">
236
-                        重置
237
-                      </a-button>
185
+                      </ElButton>
186
+                      <ElButton @click="reload(true)"> 重置 </ElButton>
238 187
                     </div>
239
-                  </FormItem>
240
-                </Form>
188
+                  </ElFormItem>
189
+                </ElForm>
241 190
               </template>
242
-              <a-button>
243
-                <FilterOutlined />
244
-              </a-button>
245
-            </Popover>
191
+            </ElPopover>
246 192
           </div>
247 193
         </div>
248 194
         <div
@@ -259,7 +205,7 @@ onMounted(async () => {
259 205
               @click="handleCardClick(item)"
260 206
             />
261 207
           </template>
262
-          <Empty v-else :image="emptyImage" />
208
+          <ElEmpty v-else :image="emptyImage" description="暂无数据" />
263 209
           <div
264 210
             v-if="isLoadComplete && taskList.length > 0"
265 211
             class="flex items-center justify-center text-[14px] opacity-50"
@@ -270,9 +216,7 @@ onMounted(async () => {
270 216
           <div
271 217
             v-if="loading"
272 218
             class="absolute left-0 top-0 flex h-full w-full items-center justify-center bg-[rgba(0,0,0,0.1)]"
273
-          >
274
-            <Spin tip="加载中..." />
275
-          </div>
219
+          ></div>
276 220
         </div>
277 221
         <!-- total显示 -->
278 222
         <div
@@ -295,7 +239,7 @@ onMounted(async () => {
295 239
   }
296 240
 }
297 241
 
298
-:deep(.ant-card-body) {
242
+:deep(.el-card__body) {
299 243
   @apply thin-scrollbar;
300 244
 }
301 245
 </style>

+ 1 - 0
packages/@core/base/shared/src/constants/dict-enum.ts

@@ -29,4 +29,5 @@ export enum DictEnum {
29 29
   WF_BUSINESS_STATUS = 'wf_business_status', // 业务状态
30 30
   WF_FORM_TYPE = 'wf_form_type', // 表单类型
31 31
   WF_TASK_STATUS = 'wf_task_status', // 任务状态
32
+  TICKET_STATUS = 'ticket_status', // 工单状态
32 33
 }