소스 검색

feat: 5项需求改动

- 我的待办默认按创建时间升序
- 我的待办增加流程字段筛选抽屉
- 审批通过时展示分支/节点名称
- 移除审批角色页面的企微配置按钮及相关代码
- 审批角色列表按创建时间从新到旧排序
ye-zhaojia 1 주 전
부모
커밋
a103b95108
7개의 변경된 파일174개의 추가작업 그리고 86개의 파일을 삭제
  1. 1 1
      src/api/flow/task.ts
  2. 0 4
      src/api/system/role.ts
  3. 2 0
      src/types/flow.ts
  4. 0 2
      src/types/system.ts
  5. 164 4
      src/views/flow/task/todo.vue
  6. 1 74
      src/views/system/role/index.vue
  7. 6 1
      src/views/workbench/index.vue

+ 1 - 1
src/api/flow/task.ts

@@ -3,7 +3,7 @@ import type { FlowTask, ApprovalAction, NextNode } from '@/types/flow'
 import type { PageQuery, PageResult } from '@/types/api'
 import type { User } from '@/types/system'
 
-export function listTodo(params?: PageQuery): Promise<PageResult<FlowTask>> {
+export function listTodo(params?: PageQuery & { processName?: string; formFilters?: string }): Promise<PageResult<FlowTask>> {
   return request.get('/flow/task/todo', { params })
 }
 

+ 0 - 4
src/api/system/role.ts

@@ -22,10 +22,6 @@ export function deleteRole(id: number): Promise<void> {
   return request.delete(`/system/role/${id}`)
 }
 
-export function bindRoleWeCom(id: number, data: { wecomUserId?: string; wecomRemindEnabled?: number }): Promise<void> {
-  return request.put(`/system/role/${id}/wecom`, data)
-}
-
 /** 查询拥有某角色的用户列表(转办选人) */
 export function listUsersByRole(roleId: number): Promise<User[]> {
   return request.get(`/system/role/${roleId}/users`)

+ 2 - 0
src/types/flow.ts

@@ -94,6 +94,8 @@ export interface NextNode {
   nodeId: string
   nodeName: string
   nodeType: string
+  branchName?: string
+  condition?: string
 }
 
 export interface ApprovalAction {

+ 0 - 2
src/types/system.ts

@@ -27,8 +27,6 @@ export interface Role {
   deptId?: number
   deptName?: string
   status: number // 0-禁用 1-正常
-  wecomUserId?: string
-  wecomRemindEnabled?: number
   createTime?: string
 }
 

+ 164 - 4
src/views/flow/task/todo.vue

@@ -4,7 +4,10 @@
       <template #header>
         <div class="card-header">
           <span>我的待办</span>
-          <el-tag v-if="total > 0" type="danger" effect="dark" round>{{ total }}</el-tag>
+          <div class="header-actions">
+            <el-tag v-if="total > 0" type="danger" effect="dark" round>{{ total }}</el-tag>
+            <el-button type="primary" size="small" :icon="Filter" @click="openFilterDrawer">筛选</el-button>
+          </div>
         </div>
       </template>
 
@@ -201,7 +204,12 @@
         <el-form :model="form" label-width="80px">
           <el-form-item v-if="!batchMode && form.action === 'pass' && nextNodes.length > 0" label="选择分支">
             <el-select v-model="form.targetNodeId" placeholder="请选择下一节点" style="width: 100%">
-              <el-option v-for="n in nextNodes" :key="n.nodeId" :label="n.nodeName" :value="n.nodeId" />
+              <el-option
+                v-for="n in nextNodes"
+                :key="n.nodeId"
+                :label="n.branchName || n.nodeName"
+                :value="n.nodeId"
+              />
             </el-select>
           </el-form-item>
           <el-form-item label="附件">
@@ -261,13 +269,73 @@
 
     <!-- 附件预览 -->
     <FilePreview v-model="previewVisible" :url="previewUrl" />
+
+    <!-- 表单字段筛选抽屉 -->
+    <el-drawer
+      v-model="filterDrawerVisible"
+      title="筛选"
+      direction="rtl"
+      size="360px"
+      :destroy-on-close="false"
+      @closed="closeFilterDrawer"
+    >
+      <div class="filter-drawer-content">
+        <el-form :model="filterForm" label-width="100px">
+          <el-form-item label="流程名称">
+            <el-select v-model="filterForm.processName" placeholder="全部流程" clearable style="width: 100%" @change="onFilterProcessChange">
+              <el-option v-for="def in definitionList" :key="def.id" :label="def.name" :value="def.name" />
+            </el-select>
+          </el-form-item>
+          <template v-if="filterFields.length > 0">
+            <el-divider>表单字段</el-divider>
+            <el-form-item v-for="field in filterFields" :key="field.name" :label="field.label">
+              <el-input
+                v-if="!field.type || field.type === 'text' || field.type === 'textarea'"
+                v-model="filterForm[field.name]"
+                :placeholder="`请输入${field.label}`"
+                clearable
+              />
+              <el-input-number
+                v-else-if="field.type === 'number'"
+                v-model="filterForm[field.name]"
+                style="width: 100%;"
+                :placeholder="`请输入${field.label}`"
+                controls-position="right"
+              />
+              <el-select
+                v-else-if="field.type === 'select'"
+                v-model="filterForm[field.name]"
+                style="width: 100%;"
+                :placeholder="`请选择${field.label}`"
+                clearable
+              >
+                <el-option v-for="opt in normalizeFilterOptions(field.options)" :key="String(opt.value)" :label="opt.label" :value="String(opt.value)" />
+              </el-select>
+              <el-date-picker
+                v-else-if="field.type === 'date'"
+                v-model="filterForm[field.name]"
+                type="date"
+                style="width: 100%;"
+                value-format="YYYY-MM-DD"
+                :placeholder="`请选择${field.label}`"
+              />
+            </el-form-item>
+          </template>
+        </el-form>
+        <div class="drawer-footer">
+          <el-button @click="filterDrawerVisible = false">取消</el-button>
+          <el-button @click="resetFilter">重置</el-button>
+          <el-button type="primary" @click="applyFilter">确定</el-button>
+        </div>
+      </div>
+    </el-drawer>
   </div>
 </template>
 
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { Check, Close, Back, Plus, View, ArrowDown } from '@element-plus/icons-vue'
+import { Check, Close, Back, Plus, View, ArrowDown, Filter } from '@element-plus/icons-vue'
 import { listTodo, approveTask, rejectTask, returnTask, addSignTask, batchApproveTask, batchRejectTask, listNextNodes, getTransferableUsers } from '@/api/flow/task'
 import { listEnabled } from '@/api/flow/definition'
 import { uploadFile } from '@/api/file'
@@ -289,9 +357,88 @@ const definitionList = ref<FlowDefinition[]>([])
 const queryParams = reactive({
   pageNum: 1,
   pageSize: 10,
-  processName: undefined as string | undefined
+  processName: undefined as string | undefined,
+  formFilters: undefined as string | undefined
+})
+
+// 表单字段筛选
+const filterDrawerVisible = ref(false)
+const filterForm = reactive<Record<string, any>>({
+  processName: undefined
+})
+
+const filterFields = computed<FormField[]>(() => {
+  const name = filterForm.processName
+  if (!name) return []
+  const def = definitionList.value.find(d => d.name === name)
+  if (!def || !def.formSchema) return []
+  try {
+    const parsed = JSON.parse(def.formSchema)
+    return Array.isArray(parsed) ? parsed.filter((f: FormField) => f.name && f.label) : []
+  } catch {
+    return []
+  }
 })
 
+function openFilterDrawer() {
+  filterForm.processName = queryParams.processName
+  // 初始化已保存的筛选值
+  if (queryParams.formFilters) {
+    try {
+      const saved = JSON.parse(queryParams.formFilters)
+      Object.assign(filterForm, saved)
+    } catch { /* ignore */ }
+  }
+  filterDrawerVisible.value = true
+}
+
+function closeFilterDrawer() {
+  // 不自动保存,只有点击确定才保存
+}
+
+function onFilterProcessChange() {
+  // 切换流程时清空旧表单字段值
+  Object.keys(filterForm).forEach(key => {
+    if (key !== 'processName') {
+      delete filterForm[key]
+    }
+  })
+}
+
+function normalizeFilterOptions(options?: FormFieldOption[] | string[]): FormFieldOption[] {
+  if (!options || !Array.isArray(options)) return []
+  return options.map((opt: any) => {
+    if (typeof opt === 'string') {
+      return { label: opt, value: opt }
+    }
+    return { label: String(opt.label ?? opt.value), value: String(opt.value) }
+  })
+}
+
+function resetFilter() {
+  Object.keys(filterForm).forEach(key => delete filterForm[key])
+  queryParams.processName = undefined
+  queryParams.formFilters = undefined
+  queryParams.pageNum = 1
+  filterDrawerVisible.value = false
+  loadData()
+}
+
+function applyFilter() {
+  const filters: Record<string, string> = {}
+  filterFields.value.forEach(field => {
+    const value = filterForm[field.name]
+    if (value !== undefined && value !== null && String(value).trim() !== '') {
+      filters[field.name] = String(value)
+    }
+  })
+  queryParams.processName = filterForm.processName
+  queryParams.formFilters = Object.keys(filters).length > 0 ? JSON.stringify(filters) : undefined
+  queryParams.pageNum = 1
+  filterDrawerVisible.value = false
+  loadData()
+}
+
 const activeUrgency = ref<'all' | 'overdue' | 'warning' | 'normal'>('all')
 
 const urgencyText = (urgency?: number) => {
@@ -651,8 +798,10 @@ function handleQuery() {
 
 function resetQuery() {
   queryParams.processName = undefined
+  queryParams.formFilters = undefined
   activeUrgency.value = 'all'
   queryParams.pageNum = 1
+  Object.keys(filterForm).forEach(key => delete filterForm[key])
   loadData()
 }
 
@@ -677,6 +826,11 @@ onMounted(() => {
   align-items: center;
   gap: 8px;
 }
+.header-actions {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
 .search-form {
   margin-bottom: 20px;
 }
@@ -819,4 +973,10 @@ onMounted(() => {
   font-size: 12px;
   color: #909399;
 }
+.filter-drawer-content {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  padding-right: 8px;
+}
 </style>

+ 1 - 74
src/views/system/role/index.vue

@@ -61,12 +61,6 @@
             <el-table-column prop="roleName" label="角色名称" />
             <el-table-column prop="deptName" label="所属部门" />
             <el-table-column prop="roleScope" label="描述" />
-            <el-table-column prop="wecomUserId" label="企微账号" width="120">
-              <template #default="{ row }">
-                <el-tag v-if="row.wecomUserId" type="success" size="small">{{ row.wecomUserId }}</el-tag>
-                <span v-else class="text-gray">未绑定</span>
-              </template>
-            </el-table-column>
             <el-table-column prop="status" label="状态" width="100">
               <template #default="{ row }">
                 <el-tag :type="row.status === 1 ? 'success' : 'danger'">
@@ -78,7 +72,6 @@
             <el-table-column label="操作" width="220">
               <template #default="{ row }">
                 <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
-                <el-button link type="primary" @click="handleWeComConfig(row)">企微配置</el-button>
                 <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
               </template>
             </el-table-column>
@@ -134,39 +127,6 @@
       </template>
     </el-dialog>
 
-    <!-- 企微配置抽屉 -->
-    <el-drawer v-model="wecomDrawerVisible" title="企微提醒配置" direction="rtl" size="400px">
-      <el-form :model="wecomForm" label-width="100px">
-        <el-alert
-          title="提示:实际企微消息推送由外部对接系统完成,本系统仅保存配置和接收人映射。"
-          type="info"
-          :closable="false"
-          style="margin-bottom: 20px;"
-        />
-        <el-form-item label="角色名称">
-          <el-input v-model="wecomForm.roleName" disabled />
-        </el-form-item>
-        <el-form-item label="企微账号">
-          <el-input v-model="wecomForm.wecomUserId" placeholder="请输入企业微信用户ID" />
-        </el-form-item>
-        <el-form-item label="开启提醒">
-          <el-switch
-            v-model="wecomForm.wecomRemindEnabled"
-            :active-value="1"
-            :inactive-value="0"
-            active-text="开启"
-            inactive-text="关闭"
-          />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <div style="flex: auto">
-          <el-button @click="wecomDrawerVisible = false">取消</el-button>
-          <el-button type="primary" :loading="wecomSubmitting" @click="submitWeComConfig">保存</el-button>
-        </div>
-      </template>
-    </el-drawer>
-
     <!-- 部门弹窗 -->
     <el-dialog v-model="deptDialogVisible" :title="deptDialogTitle" width="500px">
       <el-form ref="deptFormRef" :model="deptForm" :rules="deptFormRules" label-width="100px">
@@ -209,7 +169,7 @@
 import { ref, shallowRef, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import type { FormInstance, FormRules } from 'element-plus'
-import { listRole, addRole, updateRole, deleteRole, bindRoleWeCom } from '@/api/system/role'
+import { listRole, addRole, updateRole, deleteRole } from '@/api/system/role'
 import { listDept, addDept, updateDept, deleteDept } from '@/api/system/dept'
 import type { Role, Dept } from '@/types/system'
 
@@ -263,16 +223,6 @@ const formRules: FormRules = {
   }]
 }
 
-// 部门弹窗
-const wecomDrawerVisible = ref(false)
-const wecomSubmitting = ref(false)
-const wecomForm = reactive({
-  roleId: 0,
-  roleName: '',
-  wecomUserId: '',
-  wecomRemindEnabled: 1
-})
-
 const deptDialogVisible = ref(false)
 const deptDialogTitle = ref('')
 const isEditDept = ref(false)
@@ -374,29 +324,6 @@ async function handleDelete(row: Role) {
   }
 }
 
-function handleWeComConfig(row: Role) {
-  wecomForm.roleId = row.id
-  wecomForm.roleName = row.roleName
-  wecomForm.wecomUserId = row.wecomUserId || ''
-  wecomForm.wecomRemindEnabled = row.wecomRemindEnabled ?? 1
-  wecomDrawerVisible.value = true
-}
-
-async function submitWeComConfig() {
-  wecomSubmitting.value = true
-  try {
-    await bindRoleWeCom(wecomForm.roleId, {
-      wecomUserId: wecomForm.wecomUserId,
-      wecomRemindEnabled: wecomForm.wecomRemindEnabled
-    })
-    ElMessage.success('保存成功')
-    wecomDrawerVisible.value = false
-    loadData()
-  } finally {
-    wecomSubmitting.value = false
-  }
-}
-
 async function submitForm() {
   const valid = await formRef.value?.validate().catch(() => false)
   if (!valid) return

+ 6 - 1
src/views/workbench/index.vue

@@ -189,7 +189,12 @@
         <el-form :model="form" label-width="80px">
           <el-form-item v-if="form.action === 'pass' && nextNodes.length > 0" label="选择分支">
             <el-select v-model="form.targetNodeId" placeholder="请选择下一节点" style="width: 100%">
-              <el-option v-for="n in nextNodes" :key="n.nodeId" :label="n.nodeName" :value="n.nodeId" />
+              <el-option
+                v-for="n in nextNodes"
+                :key="n.nodeId"
+                :label="n.branchName || n.nodeName"
+                :value="n.nodeId"
+              />
             </el-select>
           </el-form-item>
           <el-form-item label="附件">