Files
shanxia2.0/web-admin/src/views/system/workflow/process/index.vue
T
2026-05-26 19:04:27 +08:00

392 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 流程定义管理 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<header class="page-head">
<h1>流程管理</h1>
<p class="page-desc">管理审批流程定义,支持图形化设计、版本发布与启停控制。</p>
</header>
<section class="panel">
<div class="panel-top">
<h2>流程列表</h2>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="关键词">
<el-input
v-model="searchForm.keyword"
placeholder="流程名称/编码"
clearable
@keyup.enter="handleSearch"
/>
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint">按流程名称、编码模糊查询。</span>
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset"
><el-icon><RefreshLeft /></el-icon>重置</el-button
>
<el-button class="btn-main" type="primary" @click="handleSearch"
><el-icon><Search /></el-icon>搜索</el-button
>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增流程
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="selectedRows.length === 0"
@click="handleBatchDelete"
>
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
/>
<el-table-column
prop="processCode"
label="流程编码"
min-width="150"
align="center"
header-align="center"
/>
<el-table-column
prop="processName"
label="流程名称"
min-width="180"
align="center"
header-align="center"
/>
<el-table-column label="状态" min-width="100" align="center" header-align="center">
<template #default="{ row }">
<el-switch
:model-value="row.status"
active-value="1"
inactive-value="0"
@change="(val) => handleStatusChange(row, val)"
/>
</template>
</el-table-column>
<el-table-column label="最新版本" min-width="100" align="center" header-align="center">
<template #default="{ row }">
{{ row.latestVersion != null ? `V${row.latestVersion}` : '--' }}
</template>
</el-table-column>
<el-table-column
prop="createTime"
label="创建时间"
min-width="160"
align="center"
header-align="center"
/>
<el-table-column
label="操作"
min-width="400"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="handleDesign(row)"
><el-icon><SetUp /></el-icon>流程设计</el-button
>
<el-button class="btn-op-item" @click="handleEdit(row)"
><el-icon><Edit /></el-icon>编辑</el-button
>
<el-button class="btn-op-item" @click="handleDelete(row)"
><el-icon><Delete /></el-icon>删除</el-button
>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
@current-change="(val) => ((pagination.page = val), loadData())"
/>
</div>
</div>
<!-- 新增/编辑弹窗 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
style="backdrop-filter: none"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
scroll-to-error
:scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
>
<div class="form-grid-2">
<el-form-item label="流程编码" prop="processCode">
<el-input v-model="formData.processCode" placeholder="请输入流程编码" />
</el-form-item>
<el-form-item label="流程名称" prop="processName">
<el-input v-model="formData.processName" placeholder="请输入流程名称" />
</el-form-item>
<el-form-item label="备注">
<el-input v-model="formData.remark" placeholder="可选" />
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="dialogVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitLoading" @click="handleSubmit">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
</section>
<!-- 流程设计器 -->
<FlowDesigner
v-model:visible="designerVisible"
:process-def-id="designerDefId"
:process-name="designerDefName"
@saved="handleDesignerSaved"
/>
</div>
</template>
<script setup lang="ts">
import {
Plus,
Close,
Check,
Search,
RefreshLeft,
Delete,
SetUp,
Edit
} from '@element-plus/icons-vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import {
fetchProcessDefList,
fetchCreateProcessDef,
fetchUpdateProcessDef,
fetchDeleteProcessDef
} from '@/api/workflow'
import FlowDesigner from '../designer/FlowDesigner.vue'
defineOptions({ name: 'WorkflowProcess' })
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
const tableData = ref<Api.Workflow.ProcessDefItem[]>([])
const selectedRows = ref<Api.Workflow.ProcessDefItem[]>([])
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
// 新增/编辑
const dialogVisible = ref(false)
const dialogTitle = ref('新增流程')
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref()
const editingId = ref<string | null>(null)
const formData = reactive({
processCode: '',
processName: '',
orderNum: 0,
remark: ''
})
const formRules = {
processCode: [{ required: true, message: '请输入流程编码', trigger: 'blur' }],
processName: [{ required: true, message: '请输入流程名称', trigger: 'blur' }]
}
// 设计器
const designerVisible = ref(false)
const designerDefId = ref('')
const designerDefName = ref('')
const handleSelectionChange = (rows: Api.Workflow.ProcessDefItem[]) => {
selectedRows.value = rows
}
async function loadData() {
loading.value = true
try {
const res = await fetchProcessDefList({
page: pagination.page,
pageSize: pagination.pageSize,
keyword: searchForm.keyword || undefined
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
searchForm.keyword = ''
pagination.page = 1
loadData()
}
function resetForm() {
formData.processCode = ''
formData.processName = ''
formData.orderNum = 0
formData.remark = ''
editingId.value = null
}
function handleAdd() {
isEdit.value = false
dialogTitle.value = '新增流程'
resetForm()
dialogVisible.value = true
formRef.value?.clearValidate?.()
}
function handleEdit(row: Api.Workflow.ProcessDefItem) {
isEdit.value = true
dialogTitle.value = '编辑流程'
editingId.value = row.id
formData.processCode = row.processCode || ''
formData.processName = row.processName || ''
formData.orderNum = row.orderNum ?? 0
formData.remark = row.remark || ''
dialogVisible.value = true
formRef.value?.clearValidate?.()
}
async function handleSubmit() {
await formRef.value?.validate?.()
submitLoading.value = true
try {
const payload = {
processCode: formData.processCode.trim(),
processName: formData.processName.trim(),
orderNum: formData.orderNum,
remark: formData.remark?.trim() || undefined
}
if (isEdit.value && editingId.value) {
await fetchUpdateProcessDef(editingId.value, payload)
} else {
await fetchCreateProcessDef(payload)
}
dialogVisible.value = false
await loadData()
} catch {
// 校验或接口错误已由全局处理
} finally {
submitLoading.value = false
}
}
async function handleStatusChange(
row: Api.Workflow.ProcessDefItem,
val: string | number | boolean
) {
const newStatus = String(val)
await fetchUpdateProcessDef(row.id, { status: newStatus })
await loadData()
}
async function handleDelete(row: Api.Workflow.ProcessDefItem) {
await ElMessageBox.confirm('确定要删除该流程吗?', '提示', { type: 'warning' })
await fetchDeleteProcessDef(row.id)
await loadData()
}
async function handleBatchDelete() {
const ids = selectedRows.value.map((r) => r.id)
if (!ids.length) return
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个流程吗?`, '提示', {
type: 'warning'
})
await withFullscreenLoading(async () => {
for (const id of ids) {
await fetchDeleteProcessDef(id)
}
ElMessage.success('批量删除成功')
selectedRows.value = []
await loadData()
}, fullscreenLoadingText.batchDelete)
}
function handleDesign(row: Api.Workflow.ProcessDefItem) {
designerDefId.value = row.id
designerDefName.value = row.processName
designerVisible.value = true
}
function handleDesignerSaved() {
loadData()
}
// 输入检索条件时自动搜索(防抖 300ms)
watchDebounced(
searchForm,
() => {
pagination.page = 1
loadData()
},
{ debounce: 300, deep: true }
)
onMounted(() => {
loadData()
})
</script>