Просмотр исходного кода

feat:【bpm】oa 请假:90% 发起等流程

YunaiV 4 месяцев назад
Родитель
Сommit
1eb03fe652

+ 3 - 8
src/api/bpm/oa/leave/index.ts

@@ -8,9 +8,9 @@ export interface Leave {
   type: number
   type: number
   reason: string
   reason: string
   processInstanceId: string
   processInstanceId: string
-  startTime: number
-  endTime: number
-  createTime: number
+  startTime: Date | any
+  endTime: Date | any
+  createTime: Date
   startUserSelectAssignees?: Record<string, string[]>
   startUserSelectAssignees?: Record<string, string[]>
 }
 }
 
 
@@ -19,11 +19,6 @@ export function createLeave(data: Partial<Leave>) {
   return http.post<number>('/bpm/oa/leave/create', data)
   return http.post<number>('/bpm/oa/leave/create', data)
 }
 }
 
 
-/** 更新请假申请 */
-export function updateLeave(data: Partial<Leave>) {
-  return http.put<boolean>('/bpm/oa/leave/update', data)
-}
-
 /** 获得请假申请 */
 /** 获得请假申请 */
 export function getLeave(id: number) {
 export function getLeave(id: number) {
   return http.get<Leave>(`/bpm/oa/leave/get?id=${id}`)
   return http.get<Leave>(`/bpm/oa/leave/get?id=${id}`)

+ 0 - 5
src/pages-bpm/oa/leave/components/search-form.vue

@@ -7,7 +7,6 @@
   <!-- 搜索弹窗 -->
   <!-- 搜索弹窗 -->
   <wd-popup v-model="visible" position="top" @close="visible = false">
   <wd-popup v-model="visible" position="top" @close="visible = false">
     <view class="yd-search-form-container" :style="{ paddingTop: `${getNavbarHeight()}px` }">
     <view class="yd-search-form-container" :style="{ paddingTop: `${getNavbarHeight()}px` }">
-      <!-- 请假类型 -->
       <view class="yd-search-form-item">
       <view class="yd-search-form-item">
         <view class="yd-search-form-label">
         <view class="yd-search-form-label">
           请假类型
           请假类型
@@ -21,7 +20,6 @@
           </wd-radio>
           </wd-radio>
         </wd-radio-group>
         </wd-radio-group>
       </view>
       </view>
-      <!-- 审批结果 -->
       <view class="yd-search-form-item">
       <view class="yd-search-form-item">
         <view class="yd-search-form-label">
         <view class="yd-search-form-label">
           审批结果
           审批结果
@@ -35,7 +33,6 @@
           </wd-radio>
           </wd-radio>
         </wd-radio-group>
         </wd-radio-group>
       </view>
       </view>
-      <!-- 创建时间 -->
       <view class="yd-search-form-item">
       <view class="yd-search-form-item">
         <view class="yd-search-form-label">
         <view class="yd-search-form-label">
           创建时间
           创建时间
@@ -72,7 +69,6 @@
           </wd-button>
           </wd-button>
         </view>
         </view>
       </view>
       </view>
-      <!-- 请假原因 -->
       <view class="yd-search-form-item">
       <view class="yd-search-form-item">
         <view class="yd-search-form-label">
         <view class="yd-search-form-label">
           请假原因
           请假原因
@@ -83,7 +79,6 @@
           clearable
           clearable
         />
         />
       </view>
       </view>
-      <!-- 操作按钮 -->
       <view class="yd-search-form-actions">
       <view class="yd-search-form-actions">
         <wd-button class="flex-1" plain @click="handleReset">
         <wd-button class="flex-1" plain @click="handleReset">
           重置
           重置

+ 64 - 149
src/pages-bpm/oa/leave/create/index.vue

@@ -2,95 +2,60 @@
   <view class="yd-page-container">
   <view class="yd-page-container">
     <!-- 顶部导航栏 -->
     <!-- 顶部导航栏 -->
     <wd-navbar
     <wd-navbar
-      :title="formData.id ? '编辑请假' : '发起请假'"
+      title="发起请假"
       left-arrow placeholder safe-area-inset-top fixed
       left-arrow placeholder safe-area-inset-top fixed
       @click-left="handleBack"
       @click-left="handleBack"
     />
     />
 
 
     <!-- 表单内容 -->
     <!-- 表单内容 -->
-    <view class="p-24rpx">
-      <!-- 基本信息卡片 -->
-      <view class="overflow-hidden rounded-16rpx bg-white">
-        <view class="p-24rpx">
-          <view class="mb-24rpx text-32rpx text-[#333] font-bold">
-            请假信息
-          </view>
-          <!-- 请假类型 -->
-          <view class="mb-24rpx">
-            <view class="mb-12rpx text-28rpx text-[#333]">
-              <text class="text-[#ff4d4f]">*</text> 请假类型
-            </view>
-            <wd-picker
-              v-model="formData.type"
-              :columns="leaveTypeOptions"
-              label=""
-              placeholder="请选择请假类型"
-            />
-          </view>
-          <!-- 开始时间 -->
-          <view class="mb-24rpx">
-            <view class="mb-12rpx text-28rpx text-[#333]">
-              <text class="text-[#ff4d4f]">*</text> 开始时间
-            </view>
-            <wd-datetime-picker
-              v-model="formData.startTime"
-              type="datetime"
-              label=""
-              placeholder="请选择开始时间"
-            />
-          </view>
-          <!-- 结束时间 -->
-          <view class="mb-24rpx">
-            <view class="mb-12rpx text-28rpx text-[#333]">
-              <text class="text-[#ff4d4f]">*</text> 结束时间
-            </view>
-            <wd-datetime-picker
-              v-model="formData.endTime"
-              type="datetime"
-              label=""
-              placeholder="请选择结束时间"
-            />
-          </view>
-          <!-- 请假原因 -->
-          <view>
-            <view class="mb-12rpx text-28rpx text-[#333]">
-              <text class="text-[#ff4d4f]">*</text> 请假原因
-            </view>
-            <wd-textarea
-              v-model="formData.reason"
-              placeholder="请输入请假原因"
-              :maxlength="200"
-              show-word-limit
-            />
-          </view>
-        </view>
-      </view>
-
-      <!-- TODO:流程预览卡片 -->
-      <!-- 原始 vben 版本有流程节点预览和发起人选择审批人功能 -->
-      <!-- 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/create.vue 第 40-50 行 -->
-      <!-- uniapp 端暂不实现流程节点预览,因为需要复杂的 ProcessInstanceTimeline 组件 -->
-      <view class="mt-24rpx overflow-hidden rounded-16rpx bg-white">
-        <view class="p-24rpx">
-          <view class="mb-16rpx text-32rpx text-[#333] font-bold">
-            流程信息
-          </view>
-          <view class="text-28rpx text-[#999]">
-            提交后将进入审批流程
-          </view>
-          <!-- TODO:实现流程节点预览 -->
-          <!-- 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/create.vue 第 40-50 行 -->
-          <!-- 需要实现 ProcessInstanceTimeline 组件和 getApprovalDetail API -->
-        </view>
-      </view>
-    </view>
+    <wd-form ref="formRef" :model="formData" :rules="formRules">
+      <wd-cell-group border title="请假信息">
+        <wd-picker
+          v-model="formData.type"
+          :columns="getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE)"
+          label="请假类型"
+          label-width="200rpx"
+          prop="type"
+          :rules="[{ required: true, message: '请选择请假类型' }]"
+          placeholder="请选择请假类型"
+        />
+        <wd-datetime-picker
+          v-model="formData.startTime"
+          label="开始时间"
+          label-width="200rpx"
+          prop="startTime"
+          :rules="[{ required: true, message: '请选择开始时间' }]"
+          placeholder="请选择开始时间"
+        />
+        <wd-datetime-picker
+          v-model="formData.endTime"
+          label="结束时间"
+          label-width="200rpx"
+          prop="endTime"
+          :rules="[{ required: true, message: '请选择结束时间' }]"
+          placeholder="请选择结束时间"
+        />
+        <wd-textarea
+          v-model="formData.reason"
+          label="请假原因"
+          label-width="200rpx"
+          prop="reason"
+          :rules="[{ required: true, message: '请输入请假原因' }]"
+          placeholder="请输入请假原因"
+          :maxlength="200"
+          show-word-limit
+        />
+      </wd-cell-group>
+    </wd-form>
+
+    <!-- TODO:@jason:流程预览卡片 -->
+    <!-- 原始 vben 版本有流程节点预览和发起人选择审批人功能 -->
+    <!-- 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/create.vue 第 40-50 行 -->
+    <!-- uniapp 端暂不实现流程节点预览,因为需要复杂的 ProcessInstanceTimeline 组件 -->
 
 
     <!-- 底部提交按钮 -->
     <!-- 底部提交按钮 -->
     <view class="yd-detail-footer">
     <view class="yd-detail-footer">
       <view class="yd-detail-footer-actions">
       <view class="yd-detail-footer-actions">
-        <wd-button type="default" class="flex-1" @click="handleBack">
-          取消
-        </wd-button>
         <wd-button type="primary" class="flex-1" :loading="formLoading" @click="handleSubmit">
         <wd-button type="primary" class="flex-1" :loading="formLoading" @click="handleSubmit">
           提交
           提交
         </wd-button>
         </wd-button>
@@ -100,11 +65,11 @@
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
+import type { FormInstance } from 'wot-design-uni/components/wd-form/types'
 import type { Leave } from '@/api/bpm/oa/leave'
 import type { Leave } from '@/api/bpm/oa/leave'
-import { onLoad } from '@dcloudio/uni-app'
-import { computed, ref } from 'vue'
+import { ref } from 'vue'
 import { useMessage, useToast } from 'wot-design-uni'
 import { useMessage, useToast } from 'wot-design-uni'
-import { createLeave, updateLeave } from '@/api/bpm/oa/leave'
+import { createLeave } from '@/api/bpm/oa/leave'
 import { getIntDictOptions } from '@/hooks/useDict'
 import { getIntDictOptions } from '@/hooks/useDict'
 import { navigateBackPlus } from '@/utils'
 import { navigateBackPlus } from '@/utils'
 import { DICT_TYPE } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/constants'
@@ -119,21 +84,19 @@ definePage({
 const toast = useToast()
 const toast = useToast()
 const message = useMessage()
 const message = useMessage()
 const formLoading = ref(false)
 const formLoading = ref(false)
-
 const formData = ref<Partial<Leave>>({
 const formData = ref<Partial<Leave>>({
   type: undefined,
   type: undefined,
   startTime: undefined,
   startTime: undefined,
   endTime: undefined,
   endTime: undefined,
   reason: undefined,
   reason: undefined,
 })
 })
-
-/** 请假类型选项 */
-const leaveTypeOptions = computed(() => {
-  return getIntDictOptions(DICT_TYPE.BPM_OA_LEAVE_TYPE).map(item => ({
-    label: item.label,
-    value: item.value,
-  }))
-})
+const formRules = {
+  type: [{ required: true, message: '请选择请假类型' }],
+  startTime: [{ required: true, message: '请选择开始时间' }],
+  endTime: [{ required: true, message: '请选择结束时间' }],
+  reason: [{ required: true, message: '请输入请假原因' }],
+}
+const formRef = ref<FormInstance>()
 
 
 /** 返回上一页 */
 /** 返回上一页 */
 function handleBack() {
 function handleBack() {
@@ -147,74 +110,26 @@ function handleBack() {
   })
   })
 }
 }
 
 
-/** 表单校验 */
-function validateForm(): boolean {
-  if (formData.value.type === undefined) {
-    toast.show('请选择请假类型')
-    return false
-  }
-  if (!formData.value.startTime) {
-    toast.show('请选择开始时间')
-    return false
-  }
-  if (!formData.value.endTime) {
-    toast.show('请选择结束时间')
-    return false
-  }
-  if (formData.value.startTime >= formData.value.endTime) {
-    toast.show('结束时间必须大于开始时间')
-    return false
-  }
-  if (!formData.value.reason?.trim()) {
-    toast.show('请输入请假原因')
-    return false
-  }
-  return true
-}
-
 /** 提交表单 */
 /** 提交表单 */
 async function handleSubmit() {
 async function handleSubmit() {
-  if (!validateForm()) {
+  const { valid } = await formRef.value!.validate()
+  if (!valid) {
+    return
+  }
+  if (formData.value.startTime! >= formData.value.endTime!) {
+    toast.show('结束时间必须大于开始时间')
     return
     return
   }
   }
 
 
-  // TODO:校验发起人选择审批人
-  // 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/create.vue 第 68-78 行
-  // uniapp 端暂不实现发起人选择审批人功能
-
+  formLoading.value = true
   try {
   try {
-    formLoading.value = true
-    const submitData: Partial<Leave> = {
-      ...formData.value,
-      startTime: Number(formData.value.startTime),
-      endTime: Number(formData.value.endTime),
-    }
-
-    if (formData.value.id) {
-      await updateLeave(submitData)
-    } else {
-      await createLeave(submitData)
-    }
-
+    await createLeave(formData.value)
     uni.showToast({ title: '提交成功', icon: 'success' })
     uni.showToast({ title: '提交成功', icon: 'success' })
-    // 返回列表页
     setTimeout(() => {
     setTimeout(() => {
-      uni.navigateBack()
+      navigateBackPlus('/pages-bpm/oa/leave/index')
     }, 1500)
     }, 1500)
-  } catch (error) {
-    console.error('[leave create] 提交失败:', error)
   } finally {
   } finally {
     formLoading.value = false
     formLoading.value = false
   }
   }
 }
 }
-
-/** 初始化 */
-onLoad((options) => {
-  // 如果有 id 参数,则为编辑模式
-  if (options?.id) {
-    // TODO:加载请假详情进行编辑
-    // 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/create.vue
-    toast.show('编辑功能开发中')
-  }
-})
 </script>
 </script>

+ 29 - 77
src/pages-bpm/oa/leave/detail/index.vue

@@ -7,75 +7,37 @@
       @click-left="handleBack"
       @click-left="handleBack"
     />
     />
 
 
-    <!-- 加载中 -->
-    <view v-if="loading" class="flex items-center justify-center py-100rpx">
-      <wd-loading />
-    </view>
-
     <!-- 详情内容 -->
     <!-- 详情内容 -->
-    <view v-else class="p-24rpx">
-      <!-- 基本信息卡片 -->
-      <view class="overflow-hidden rounded-16rpx bg-white">
-        <view class="p-24rpx">
-          <view class="mb-24rpx text-32rpx text-[#333] font-bold">
-            请假信息
-          </view>
-          <!-- 请假类型 -->
-          <view class="mb-16rpx flex items-center">
-            <text class="w-160rpx text-28rpx text-[#999]">请假类型</text>
-            <dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="formData.type" />
-          </view>
-          <!-- 开始时间 -->
-          <view class="mb-16rpx flex items-center">
-            <text class="w-160rpx text-28rpx text-[#999]">开始时间</text>
-            <text class="text-28rpx text-[#333]">{{ formatDateTime(formData.startTime) }}</text>
-          </view>
-          <!-- 结束时间 -->
-          <view class="mb-16rpx flex items-center">
-            <text class="w-160rpx text-28rpx text-[#999]">结束时间</text>
-            <text class="text-28rpx text-[#333]">{{ formatDateTime(formData.endTime) }}</text>
-          </view>
-          <!-- 请假原因 -->
-          <view class="flex">
-            <text class="w-160rpx text-28rpx text-[#999]">请假原因</text>
-            <text class="flex-1 text-28rpx text-[#333]">{{ formData.reason }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 审批状态卡片 -->
-      <view class="mt-24rpx overflow-hidden rounded-16rpx bg-white">
-        <view class="p-24rpx">
-          <view class="mb-24rpx text-32rpx text-[#333] font-bold">
-            审批状态
-          </view>
-          <view class="flex items-center">
-            <text class="w-160rpx text-28rpx text-[#999]">当前状态</text>
-            <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="formData.status" />
-          </view>
-        </view>
-      </view>
-
-      <!-- 查看审批进度按钮 -->
-      <view v-if="formData.processInstanceId" class="mt-24rpx">
-        <wd-button type="primary" block @click="handleProgress">
-          查看审批进度
-        </wd-button>
-      </view>
+    <view>
+      <wd-cell-group border>
+        <wd-cell title="请假类型">
+          <dict-tag :type="DICT_TYPE.BPM_OA_LEAVE_TYPE" :value="formData.type" />
+        </wd-cell>
+        <wd-cell title="开始时间" :value="formatDateTime(formData.startTime) || '-'" />
+        <wd-cell title="结束时间" :value="formatDateTime(formData.endTime) || '-'" />
+        <wd-cell title="请假原因" :value="formData.reason || '-'" />
+        <wd-cell title="审批状态">
+          <dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS" :value="formData.status" />
+        </wd-cell>
+        <wd-cell title="创建时间" :value="formatDateTime(formData.createTime) || '-'" />
+      </wd-cell-group>
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
 
 
 <script lang="ts" setup>
 <script lang="ts" setup>
 import type { Leave } from '@/api/bpm/oa/leave'
 import type { Leave } from '@/api/bpm/oa/leave'
-import { onLoad } from '@dcloudio/uni-app'
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
 import { useToast } from 'wot-design-uni'
 import { useToast } from 'wot-design-uni'
 import { getLeave } from '@/api/bpm/oa/leave'
 import { getLeave } from '@/api/bpm/oa/leave'
 import { navigateBackPlus } from '@/utils'
 import { navigateBackPlus } from '@/utils'
 import { DICT_TYPE } from '@/utils/constants'
 import { DICT_TYPE } from '@/utils/constants'
 import { formatDateTime } from '@/utils/date'
 import { formatDateTime } from '@/utils/date'
 
 
+const props = defineProps<{
+  id?: number | string
+}>()
+
 definePage({
 definePage({
   style: {
   style: {
     navigationBarTitleText: '',
     navigationBarTitleText: '',
@@ -84,39 +46,29 @@ definePage({
 })
 })
 
 
 const toast = useToast()
 const toast = useToast()
-const loading = ref(false)
-const formData = ref<Partial<Leave>>({})
+const formData = ref<Leave>({})
 
 
 /** 返回上一页 */
 /** 返回上一页 */
 function handleBack() {
 function handleBack() {
   navigateBackPlus('/pages-bpm/oa/leave/index')
   navigateBackPlus('/pages-bpm/oa/leave/index')
 }
 }
 
 
-/** 查看审批进度 */
-function handleProgress() {
-  if (formData.value.processInstanceId) {
-    uni.navigateTo({ url: `/pages-bpm/processInstance/detail/index?id=${formData.value.processInstanceId}` })
-  }
-}
-
 /** 获取详情数据 */
 /** 获取详情数据 */
-async function getDetailData(id: number) {
+async function getDetail() {
+  if (!props.id) {
+    toast.show('参数错误')
+    return
+  }
   try {
   try {
-    loading.value = true
-    formData.value = await getLeave(id)
-  } catch (error) {
-    console.error('[leave detail] 获取详情失败:', error)
+    toast.loading('加载中...')
+    formData.value = await getLeave(Number(props.id))
   } finally {
   } finally {
-    loading.value = false
+    toast.close()
   }
   }
 }
 }
 
 
 /** 初始化 */
 /** 初始化 */
-onLoad((options) => {
-  if (!options?.id) {
-    toast.show('参数错误')
-    return
-  }
-  getDetailData(Number(options.id))
+onMounted(() => {
+  getDetail()
 })
 })
 </script>
 </script>

+ 26 - 34
src/pages-bpm/oa/leave/index.vue

@@ -49,14 +49,13 @@
             <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
             <text class="bpm-time">{{ formatDateTime(item.createTime) }}</text>
           </view>
           </view>
         </view>
         </view>
-        <!-- 操作按钮 -->
         <view class="bpm-actions">
         <view class="bpm-actions">
           <view class="bpm-action-btn" @click.stop="handleDetail(item)">
           <view class="bpm-action-btn" @click.stop="handleDetail(item)">
-            <wd-icon name="eye-on" size="32rpx" />
+            <wd-icon name="view" size="32rpx" />
             <text class="ml-8rpx">详情</text>
             <text class="ml-8rpx">详情</text>
           </view>
           </view>
           <view class="bpm-action-btn" @click.stop="handleProgress(item)">
           <view class="bpm-action-btn" @click.stop="handleProgress(item)">
-            <wd-icon name="flow" size="32rpx" />
+            <wd-icon name="queue" size="32rpx" />
             <text class="ml-8rpx">审批进度</text>
             <text class="ml-8rpx">审批进度</text>
           </view>
           </view>
           <view
           <view
@@ -81,12 +80,12 @@
       />
       />
 
 
       <!-- 新增按钮 -->
       <!-- 新增按钮 -->
-      <view
-        class="fixed bottom-100rpx right-32rpx z-10 h-100rpx w-100rpx flex items-center justify-center rounded-full bg-[#1890ff] shadow-lg"
+      <wd-fab
+        position="right-bottom"
+        type="primary"
+        :expandable="false"
         @click="handleCreate"
         @click="handleCreate"
-      >
-        <wd-icon name="add" size="24px" color="#fff" />
-      </view>
+      />
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
@@ -96,24 +95,15 @@ import type { Leave } from '@/api/bpm/oa/leave'
 import type { LoadMoreState } from '@/http/types'
 import type { LoadMoreState } from '@/http/types'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { onReachBottom } from '@dcloudio/uni-app'
 import { computed, onMounted, ref } from 'vue'
 import { computed, onMounted, ref } from 'vue'
-import { useMessage } from 'wot-design-uni'
 import { getLeavePage } from '@/api/bpm/oa/leave'
 import { getLeavePage } from '@/api/bpm/oa/leave'
 import { cancelProcessInstanceByStartUser } from '@/api/bpm/processInstance'
 import { cancelProcessInstanceByStartUser } from '@/api/bpm/processInstance'
 import { useUserStore } from '@/store'
 import { useUserStore } from '@/store'
 import { navigateBackPlus } from '@/utils'
 import { navigateBackPlus } from '@/utils'
-import { DICT_TYPE } from '@/utils/constants'
+import { BpmProcessInstanceStatus, DICT_TYPE } from '@/utils/constants'
 import { formatDateTime } from '@/utils/date'
 import { formatDateTime } from '@/utils/date'
 import LeaveSearchForm from './components/search-form.vue'
 import LeaveSearchForm from './components/search-form.vue'
 import '@/pages/bpm/styles/index.scss'
 import '@/pages/bpm/styles/index.scss'
 
 
-/** 流程实例状态枚举 */
-const BpmProcessInstanceStatus = {
-  RUNNING: 1, // 审批中
-  APPROVE: 2, // 审批通过
-  REJECT: 3, // 审批不通过
-  CANCEL: 4, // 已取消
-}
-
 definePage({
 definePage({
   style: {
   style: {
     navigationBarTitleText: '',
     navigationBarTitleText: '',
@@ -122,7 +112,6 @@ definePage({
 })
 })
 
 
 const userStore = useUserStore()
 const userStore = useUserStore()
-const message = useMessage()
 const userNickname = computed(() => userStore.userInfo?.nickname || '')
 const userNickname = computed(() => userStore.userInfo?.nickname || '')
 
 
 const total = ref(0)
 const total = ref(0)
@@ -136,7 +125,7 @@ const queryParams = ref({
 
 
 /** 返回上一页 */
 /** 返回上一页 */
 function handleBack() {
 function handleBack() {
-  navigateBackPlus('/pages/bpm/index')
+  navigateBackPlus()
 }
 }
 
 
 /** 查询列表 */
 /** 查询列表 */
@@ -190,22 +179,25 @@ function handleProgress(item: Leave) {
 
 
 /** 取消请假 */
 /** 取消请假 */
 function handleCancel(item: Leave) {
 function handleCancel(item: Leave) {
-  message.confirm({
+  uni.showModal({
     title: '取消流程',
     title: '取消流程',
-    msg: '确定要取消该请假申请吗?',
-  }).then(async ({ action }) => {
-    if (action !== 'confirm') {
-      return
-    }
-    // TODO:原始 vben 版本支持输入取消原因,uniapp 的 message.confirm 不支持输入框
-    // 参考:yudao-ui-admin-vben-v5/apps/web-antd/src/views/bpm/oa/leave/index.vue 第 35-60 行
-    try {
-      await cancelProcessInstanceByStartUser(String(item.id), '用户取消')
+    editable: true,
+    placeholderText: '请输入取消原因',
+    success: async (res) => {
+      if (!res.confirm) {
+        return
+      }
+      const reason = res.content?.trim()
+      if (!reason) {
+        uni.showToast({ title: '请输入取消原因', icon: 'none' })
+        return
+      }
+      await cancelProcessInstanceByStartUser(String(item.processInstanceId), reason)
+      // 更新状态
       uni.showToast({ title: '取消成功', icon: 'success' })
       uni.showToast({ title: '取消成功', icon: 'success' })
-      handleSearch()
-    } catch (error) {
-      console.error('[leave] 取消失败:', error)
-    }
+      item.status = BpmProcessInstanceStatus.CANCEL
+      item.endTime = new Date()
+    },
   })
   })
 }
 }
 
 

+ 30 - 38
src/pages/index/index.ts

@@ -30,7 +30,6 @@ const menuGroupsData: MenuGroup[] = [
     key: 'system',
     key: 'system',
     name: '系统管理',
     name: '系统管理',
     menus: [
     menus: [
-      // === 用户权限相关(蓝色系)===
       {
       {
         key: 'user',
         key: 'user',
         name: '用户管理',
         name: '用户管理',
@@ -42,7 +41,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'role',
         key: 'role',
         name: '角色管理',
         name: '角色管理',
-        icon: 'secured', // 安全/权限
+        icon: 'secured',
         url: '/pages-system/role/index',
         url: '/pages-system/role/index',
         iconColor: '#2f54eb',
         iconColor: '#2f54eb',
         permission: 'system:role:query',
         permission: 'system:role:query',
@@ -55,11 +54,10 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#597ef7',
         iconColor: '#597ef7',
         permission: 'system:menu:query',
         permission: 'system:menu:query',
       },
       },
-      // === 组织架构相关(青色系)===
       {
       {
         key: 'dept',
         key: 'dept',
         name: '部门管理',
         name: '部门管理',
-        icon: 'layers', // 层级结构
+        icon: 'layers',
         url: '/pages-system/dept/index',
         url: '/pages-system/dept/index',
         iconColor: '#13c2c2',
         iconColor: '#13c2c2',
         permission: 'system:dept:query',
         permission: 'system:dept:query',
@@ -72,11 +70,10 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#36cfc9',
         iconColor: '#36cfc9',
         permission: 'system:post:query',
         permission: 'system:post:query',
       },
       },
-      // === 日志相关(紫色系)===
       {
       {
         key: 'operateLog',
         key: 'operateLog',
         name: '操作日志',
         name: '操作日志',
-        icon: 'history', // 历史记录
+        icon: 'history',
         url: '/pages-system/operate-log/index',
         url: '/pages-system/operate-log/index',
         iconColor: '#722ed1',
         iconColor: '#722ed1',
         permission: 'system:operate-log:query',
         permission: 'system:operate-log:query',
@@ -84,16 +81,15 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'loginLog',
         key: 'loginLog',
         name: '登录日志',
         name: '登录日志',
-        icon: 'login', // 登录
+        icon: 'login',
         url: '/pages-system/login-log/index',
         url: '/pages-system/login-log/index',
         iconColor: '#9254de',
         iconColor: '#9254de',
         permission: 'system:login-log:query',
         permission: 'system:login-log:query',
       },
       },
-      // === 消息通知相关(橙色系)===
       {
       {
         key: 'notice',
         key: 'notice',
         name: '通知公告',
         name: '通知公告',
-        icon: 'notification', // 通知
+        icon: 'notification',
         url: '/pages-system/notice/index',
         url: '/pages-system/notice/index',
         iconColor: '#fa8c16',
         iconColor: '#fa8c16',
         permission: 'system:notice:query',
         permission: 'system:notice:query',
@@ -101,7 +97,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'sms',
         key: 'sms',
         name: '短信管理',
         name: '短信管理',
-        icon: 'chat1', // 消息
+        icon: 'chat1',
         url: '/pages-system/sms/index',
         url: '/pages-system/sms/index',
         iconColor: '#faad14',
         iconColor: '#faad14',
         permission: 'system:sms-channel:query',
         permission: 'system:sms-channel:query',
@@ -117,12 +113,11 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'notify',
         key: 'notify',
         name: '站内信管理',
         name: '站内信管理',
-        icon: 'read', // 阅读/消息
+        icon: 'read',
         url: '/pages-system/notify/index',
         url: '/pages-system/notify/index',
         iconColor: '#ff7a45',
         iconColor: '#ff7a45',
         permission: 'system:notify-template:query',
         permission: 'system:notify-template:query',
       },
       },
-      // === 系统配置相关(粉色系)===
       {
       {
         key: 'tenant',
         key: 'tenant',
         name: '租户管理',
         name: '租户管理',
@@ -142,7 +137,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'oauth2',
         key: 'oauth2',
         name: 'OAuth2.0',
         name: 'OAuth2.0',
-        icon: 'lock-on', // 授权/锁
+        icon: 'lock-on',
         url: '/pages-system/oauth2/index',
         url: '/pages-system/oauth2/index',
         iconColor: '#ff85c0',
         iconColor: '#ff85c0',
         permission: 'system:oauth2-client:query',
         permission: 'system:oauth2-client:query',
@@ -150,7 +145,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'dict',
         key: 'dict',
         name: '字典管理',
         name: '字典管理',
-        icon: 'books', // 字典/书籍
+        icon: 'books',
         url: '/pages-system/dict/index',
         url: '/pages-system/dict/index',
         iconColor: '#c41d7f',
         iconColor: '#c41d7f',
         permission: 'system:dict:query',
         permission: 'system:dict:query',
@@ -168,11 +163,10 @@ const menuGroupsData: MenuGroup[] = [
     key: 'infra',
     key: 'infra',
     name: '基础设施',
     name: '基础设施',
     menus: [
     menus: [
-      // === 日志监控相关(红蓝色系)===
       {
       {
         key: 'accessLog',
         key: 'accessLog',
         name: '访问日志',
         name: '访问日志',
-        icon: 'view-list', // 列表/日志
+        icon: 'view-list',
         url: '/pages-infra/api-access-log/index',
         url: '/pages-infra/api-access-log/index',
         iconColor: '#1890ff',
         iconColor: '#1890ff',
         permission: 'infra:api-access-log:query',
         permission: 'infra:api-access-log:query',
@@ -185,7 +179,6 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#f5222d',
         iconColor: '#f5222d',
         permission: 'infra:api-error-log:query',
         permission: 'infra:api-error-log:query',
       },
       },
-      // === 配置相关(青紫色系)===
       {
       {
         key: 'config',
         key: 'config',
         name: '参数配置',
         name: '参数配置',
@@ -197,12 +190,11 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'dataSourceConfig',
         key: 'dataSourceConfig',
         name: '数据源配置',
         name: '数据源配置',
-        icon: 'server', // 服务器/数据源
+        icon: 'server',
         url: '/pages-infra/data-source-config/index',
         url: '/pages-infra/data-source-config/index',
         iconColor: '#9254de',
         iconColor: '#9254de',
         permission: 'infra:data-source-config:query',
         permission: 'infra:data-source-config:query',
       },
       },
-      // === 文件存储相关(蓝色系)===
       {
       {
         key: 'file',
         key: 'file',
         name: '文件管理',
         name: '文件管理',
@@ -211,24 +203,21 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#2f54eb',
         iconColor: '#2f54eb',
         permission: 'infra:file:query',
         permission: 'infra:file:query',
       },
       },
-      // === 通信相关(青色系)===
       {
       {
         key: 'websocket',
         key: 'websocket',
         name: 'WebSocket',
         name: 'WebSocket',
-        icon: 'wifi', // 网络连接
+        icon: 'wifi',
         url: '/pages-infra/web-socket/index',
         url: '/pages-infra/web-socket/index',
         iconColor: '#13c2c2',
         iconColor: '#13c2c2',
       },
       },
-      // === 任务调度相关(橙色系)===
       {
       {
         key: 'job',
         key: 'job',
         name: '定时任务',
         name: '定时任务',
-        icon: 'time', // 时间/定时
+        icon: 'time',
         url: '/pages-infra/job/index',
         url: '/pages-infra/job/index',
         iconColor: '#fa8c16',
         iconColor: '#fa8c16',
         permission: 'infra:job:query',
         permission: 'infra:job:query',
       },
       },
-      // === 开发工具相关(绿色系)===
       {
       {
         key: 'codegen',
         key: 'codegen',
         name: '代码生成',
         name: '代码生成',
@@ -253,7 +242,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'druid',
         key: 'druid',
         name: '监控中心',
         name: '监控中心',
-        icon: 'dashboard', // 仪表盘
+        icon: 'dashboard',
         url: ONLY_PC_PAGE,
         url: ONLY_PC_PAGE,
         iconColor: '#389e0d',
         iconColor: '#389e0d',
       },
       },
@@ -263,11 +252,10 @@ const menuGroupsData: MenuGroup[] = [
     key: 'bpm',
     key: 'bpm',
     name: '工作流程',
     name: '工作流程',
     menus: [
     menus: [
-      // === 个人工作台(蓝色系)===
       {
       {
         key: 'bpmMy',
         key: 'bpmMy',
         name: '我的流程',
         name: '我的流程',
-        icon: 'user-circle', // 个人
+        icon: 'user-circle',
         url: '/pages/bpm/index?tab=my',
         url: '/pages/bpm/index?tab=my',
         iconColor: '#1890ff',
         iconColor: '#1890ff',
         permission: 'bpm:process-instance:query',
         permission: 'bpm:process-instance:query',
@@ -275,7 +263,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'bpmTodo',
         key: 'bpmTodo',
         name: '待办任务',
         name: '待办任务',
-        icon: 'time', // 待处理/时间
+        icon: 'time',
         url: '/pages/bpm/index?tab=todo',
         url: '/pages/bpm/index?tab=todo',
         iconColor: '#fa8c16',
         iconColor: '#fa8c16',
         permission: 'bpm:task:query',
         permission: 'bpm:task:query',
@@ -296,11 +284,18 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#13c2c2',
         iconColor: '#13c2c2',
         permission: 'bpm:process-instance-cc:query',
         permission: 'bpm:process-instance-cc:query',
       },
       },
-      // === 流程设计相关(紫色系)===
+      {
+        key: 'oaLeave',
+        name: '请假申请',
+        icon: 'calendar',
+        url: '/pages-bpm/oa/leave/index',
+        iconColor: '#52c41a',
+        permission: 'bpm:oa-leave:query',
+      },
       {
       {
         key: 'bpmModel',
         key: 'bpmModel',
         name: '流程模型',
         name: '流程模型',
-        icon: 'app', // 应用/模型
+        icon: 'app',
         url: ONLY_PC_PAGE,
         url: ONLY_PC_PAGE,
         iconColor: '#722ed1',
         iconColor: '#722ed1',
         permission: 'bpm:process-definition:query',
         permission: 'bpm:process-definition:query',
@@ -308,16 +303,15 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'bpmForm',
         key: 'bpmForm',
         name: '流程表单',
         name: '流程表单',
-        icon: 'edit-1', // 编辑/表单
+        icon: 'edit-1',
         url: ONLY_PC_PAGE,
         url: ONLY_PC_PAGE,
         iconColor: '#9254de',
         iconColor: '#9254de',
         permission: 'bpm:form:query',
         permission: 'bpm:form:query',
       },
       },
-      // === 流程配置相关(橙黄色系)===
       {
       {
         key: 'bpmCategory',
         key: 'bpmCategory',
         name: '流程分类',
         name: '流程分类',
-        icon: 'folder-open', // 分类/文件夹
+        icon: 'folder-open',
         url: '/pages-bpm/category/index',
         url: '/pages-bpm/category/index',
         iconColor: '#faad14',
         iconColor: '#faad14',
         permission: 'bpm:category:query',
         permission: 'bpm:category:query',
@@ -330,11 +324,10 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#ffc53d',
         iconColor: '#ffc53d',
         permission: 'bpm:user-group:query',
         permission: 'bpm:user-group:query',
       },
       },
-      // === 流程扩展相关(粉色系)===
       {
       {
         key: 'bpmProcessListener',
         key: 'bpmProcessListener',
         name: '流程监听器',
         name: '流程监听器',
-        icon: 'sound', // 监听/声音
+        icon: 'sound',
         url: '/pages-bpm/process-listener/index',
         url: '/pages-bpm/process-listener/index',
         iconColor: '#eb2f96',
         iconColor: '#eb2f96',
         permission: 'bpm:process-listener:query',
         permission: 'bpm:process-listener:query',
@@ -347,11 +340,10 @@ const menuGroupsData: MenuGroup[] = [
         iconColor: '#f759ab',
         iconColor: '#f759ab',
         permission: 'bpm:process-expression:query',
         permission: 'bpm:process-expression:query',
       },
       },
-      // === 流程管理相关(青色系)===
       {
       {
         key: 'bpmProcessInstanceManager',
         key: 'bpmProcessInstanceManager',
         name: '流程实例',
         name: '流程实例',
-        icon: 'queue', // 队列/实例
+        icon: 'queue',
         url: '/pages-bpm/processInstance/manager/index',
         url: '/pages-bpm/processInstance/manager/index',
         iconColor: '#36cfc9',
         iconColor: '#36cfc9',
         permission: 'bpm:process-instance:manager-query',
         permission: 'bpm:process-instance:manager-query',
@@ -359,7 +351,7 @@ const menuGroupsData: MenuGroup[] = [
       {
       {
         key: 'bpmTaskManager',
         key: 'bpmTaskManager',
         name: '流程任务',
         name: '流程任务',
-        icon: 'bulletpoint', // 任务列表
+        icon: 'bulletpoint',
         url: '/pages-bpm/task/manager/index',
         url: '/pages-bpm/task/manager/index',
         iconColor: '#5cdbd3',
         iconColor: '#5cdbd3',
         permission: 'bpm:task:manager-query',
         permission: 'bpm:task:manager-query',

+ 1 - 0
src/utils/constants.ts

@@ -1,3 +1,4 @@
+export * from './constants/biz-bpm-enum'
 export * from './constants/biz-infra-enum'
 export * from './constants/biz-infra-enum'
 export * from './constants/biz-system-enum'
 export * from './constants/biz-system-enum'
 export * from './constants/dict-enum'
 export * from './constants/dict-enum'

+ 306 - 0
src/utils/constants/biz-bpm-enum.ts

@@ -0,0 +1,306 @@
+// 候选人策略枚举 ( 用于审批节点。抄送节点 )
+export enum BpmCandidateStrategyEnum {
+  /**
+   * 审批人自选
+   */
+  APPROVE_USER_SELECT = 34,
+  /**
+   * 部门的负责人
+   */
+  DEPT_LEADER = 21,
+  /**
+   * 部门成员
+   */
+  DEPT_MEMBER = 20,
+  /**
+   * 流程表达式
+   */
+  EXPRESSION = 60,
+  /**
+   * 表单内部门负责人
+   */
+  FORM_DEPT_LEADER = 51,
+  /**
+   * 表单内用户字段
+   */
+  FORM_USER = 50,
+  /**
+   * 连续多级部门的负责人
+   */
+  MULTI_LEVEL_DEPT_LEADER = 23,
+  /**
+   * 指定岗位
+   */
+  POST = 22,
+  /**
+   * 指定角色
+   */
+  ROLE = 10,
+  /**
+   * 发起人自己
+   */
+  START_USER = 36,
+  /**
+   * 发起人部门负责人
+   */
+  START_USER_DEPT_LEADER = 37,
+  /**
+   * 发起人连续多级部门的负责人
+   */
+  START_USER_MULTI_LEVEL_DEPT_LEADER = 38,
+  /**
+   * 发起人自选
+   */
+  START_USER_SELECT = 35,
+  /**
+   * 指定用户
+   */
+  USER = 30,
+  /**
+   * 指定用户组
+   */
+  USER_GROUP = 40,
+}
+
+/**
+ * 节点类型
+ */
+export enum BpmNodeTypeEnum {
+  /**
+   * 子流程节点
+   */
+  CHILD_PROCESS_NODE = 20,
+  /**
+   * 条件分支节点 (对应排他网关)
+   */
+  CONDITION_BRANCH_NODE = 51,
+  /**
+   * 条件节点
+   */
+  CONDITION_NODE = 50,
+
+  /**
+   * 抄送人节点
+   */
+  COPY_TASK_NODE = 12,
+
+  /**
+   * 延迟器节点
+   */
+  DELAY_TIMER_NODE = 14,
+
+  /**
+   * 结束节点
+   */
+  END_EVENT_NODE = 1,
+
+  /**
+   * 包容分支节点 (对应包容网关)
+   */
+  INCLUSIVE_BRANCH_NODE = 53,
+
+  /**
+   * 并行分支节点 (对应并行网关)
+   */
+  PARALLEL_BRANCH_NODE = 52,
+
+  /**
+   * 路由分支节点
+   */
+  ROUTER_BRANCH_NODE = 54,
+  /**
+   * 发起人节点
+   */
+  START_USER_NODE = 10,
+  /**
+   * 办理人节点
+   */
+  TRANSACTOR_NODE = 13,
+
+  /**
+   * 触发器节点
+   */
+  TRIGGER_NODE = 15,
+  /**
+   * 审批人节点
+   */
+  USER_TASK_NODE = 11,
+}
+
+/**
+ *  流程任务操作按钮
+ */
+export enum BpmTaskOperationButtonTypeEnum {
+  /**
+   * 加签
+   */
+  ADD_SIGN = 5,
+  /**
+   * 通过
+   */
+  APPROVE = 1,
+  /**
+   * 抄送
+   */
+  COPY = 7,
+  /**
+   * 委派
+   */
+  DELEGATE = 4,
+  /**
+   * 拒绝
+   */
+  REJECT = 2,
+  /**
+   * 退回
+   */
+  RETURN = 6,
+  /**
+   * 转办
+   */
+  TRANSFER = 3,
+}
+
+/**
+ * 任务状态枚举
+ */
+export enum BpmTaskStatusEnum {
+  /**
+   * 审批通过
+   */
+  APPROVE = 2,
+  /**
+   * 审批通过中
+   */
+  APPROVING = 7,
+
+  /**
+   * 已取消
+   */
+  CANCEL = 4,
+  /**
+   * 未开始
+   */
+  NOT_START = -1,
+  /**
+   * 审批不通过
+   */
+  REJECT = 3,
+
+  /**
+   * 已退回
+   */
+  RETURN = 5,
+
+  /**
+   * 审批中
+   */
+  RUNNING = 1,
+  /**
+   * 跳过
+   */
+  SKIP = -2,
+  /**
+   * 待审批
+   */
+  WAIT = 0,
+}
+
+/**
+ * 节点 Id 枚举
+ */
+export enum BpmNodeIdEnum {
+  /**
+   * 发起人节点 Id
+   */
+  END_EVENT_NODE_ID = 'EndEvent',
+
+  /**
+   * 发起人节点 Id
+   */
+  START_USER_NODE_ID = 'StartUserNode',
+}
+
+/**
+ * 表单权限的枚举
+ */
+export enum BpmFieldPermissionType {
+  /**
+   * 隐藏
+   */
+  NONE = '3',
+  /**
+   * 只读
+   */
+  READ = '1',
+  /**
+   * 编辑
+   */
+  WRITE = '2',
+}
+
+/**
+ * 流程模型类型
+ */
+export const BpmModelType = {
+  BPMN: 10, // BPMN 设计器
+  SIMPLE: 20, // 简易设计器
+}
+
+/**
+ * 流程模型表单类型
+ */
+export const BpmModelFormType = {
+  NORMAL: 10, // 流程表单
+  CUSTOM: 20, // 业务表单
+}
+
+/**
+ * 流程实例状态
+ */
+export const BpmProcessInstanceStatus = {
+  NOT_START: -1, // 未开始
+  RUNNING: 1, // 审批中
+  APPROVE: 2, // 审批通过
+  REJECT: 3, // 审批不通过
+  CANCEL: 4, // 已取消
+}
+
+/**
+ * 自动审批类型
+ */
+export const BpmAutoApproveType = {
+  NONE: 0, // 不自动通过
+  APPROVE_ALL: 1, // 仅审批一次,后续重复的审批节点均自动通过
+  APPROVE_SEQUENT: 2, // 仅针对连续审批的节点自动通过
+}
+
+/**
+ * 审批操作按钮名称
+ */
+export const OPERATION_BUTTON_NAME = new Map<number, string>()
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.APPROVE, '通过')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.REJECT, '拒绝')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.TRANSFER, '转办')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.DELEGATE, '委派')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.ADD_SIGN, '加签')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.RETURN, '退回')
+OPERATION_BUTTON_NAME.set(BpmTaskOperationButtonTypeEnum.COPY, '抄送')
+
+/**
+ * 流程实例的变量枚举
+ */
+export enum ProcessVariableEnum {
+  /**
+   * 流程定义名称
+   */
+  PROCESS_DEFINITION_NAME = 'PROCESS_DEFINITION_NAME',
+  /**
+   * 发起时间
+   */
+  START_TIME = 'PROCESS_START_TIME',
+  /**
+   * 发起用户 ID
+   */
+  START_USER_ID = 'PROCESS_START_USER_ID',
+}