|
|
@@ -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>
|