251 lines
7.5 KiB
Vue
251 lines
7.5 KiB
Vue
<script setup>
|
||
/**
|
||
* 系统管理 —— 流程管理:流程定义列表(《FMS审批流程交互重构设计》§4.1)。
|
||
*
|
||
* 数据源 v_wf_definition(wf_definition 带出已发布版本、版本数、绑定模块数等派生列)。
|
||
* 模块的 b_save_table 为空:列表不进入可编辑模式——流程的写入必须走流程服务
|
||
* (发布校验、版本快照、发布后不可改、幂等,见《FMS工作流与审批设计》§6.2 / §12),
|
||
* 通用保存接口不参与。
|
||
*
|
||
* 操作分工:需要草稿的操作(保存 / 校验 / 模拟 / 发布)都在编辑页的工具栏,
|
||
* 因为草稿只在那里存在;列表这边提供进入编辑、查看版本记录、停用已发布版本。
|
||
* 「批量删除」仍是占位 —— 删除定义同样要流程服务,接口未接入时只提示不假装成功。
|
||
*/
|
||
import { computed, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { History, Plus, Trash2 } from '@lucide/vue'
|
||
import { Button, Message, Modal, Tag } from '@/components/ui'
|
||
import { FmsModuleListPage } from '@/components/fms-module-list'
|
||
import { VERSION_STATUS } from './designer/constants'
|
||
import {
|
||
WORKFLOW_SERVICE_MISSING_MESSAGE,
|
||
isWorkflowServiceMissing,
|
||
loadVersionList,
|
||
retireVersionApi,
|
||
} from './designer/api'
|
||
|
||
const MODULE_CODE = 'v_wf_definition'
|
||
|
||
const router = useRouter()
|
||
const selectedRowKeys = ref([])
|
||
|
||
const historyOpen = ref(false)
|
||
const historyLoading = ref(false)
|
||
const historyDefinition = ref(null)
|
||
const historyVersions = ref([])
|
||
const retiringId = ref('')
|
||
|
||
const rowSelection = computed(() => ({
|
||
selectedRowKeys: selectedRowKeys.value,
|
||
onChange: (keys) => {
|
||
selectedRowKeys.value = keys
|
||
},
|
||
}))
|
||
|
||
/* ================= 新增 / 设计 ================= */
|
||
|
||
/** 新增:不带参数进设计页,由新建向导收集流程元数据(§4.2) */
|
||
function onCreate() {
|
||
router.push({ name: 'system-workflow-design' })
|
||
}
|
||
|
||
/** 行操作:进设计页编辑这条流程 */
|
||
const rowActions = [
|
||
{
|
||
key: 'design',
|
||
label: '设计',
|
||
onClick: (row) => router.push({ name: 'system-workflow-design', query: { id: row.b_id } }),
|
||
},
|
||
{
|
||
key: 'versions',
|
||
label: '版本记录',
|
||
onClick: (row) => openHistory(row),
|
||
},
|
||
]
|
||
|
||
/* ================= 版本记录 ================= */
|
||
|
||
async function openHistory(row) {
|
||
historyDefinition.value = row
|
||
historyVersions.value = []
|
||
historyOpen.value = true
|
||
historyLoading.value = true
|
||
try {
|
||
historyVersions.value = await loadVersionList(row.b_id)
|
||
} catch {
|
||
historyVersions.value = []
|
||
} finally {
|
||
historyLoading.value = false
|
||
}
|
||
}
|
||
|
||
function statusMeta(status) {
|
||
return VERSION_STATUS.find((item) => item.value === status) || { label: status, tag: 'default' }
|
||
}
|
||
|
||
/** 停用:不再接受新实例,运行中的实例继续走完(§4.2) */
|
||
async function onRetire(row) {
|
||
retiringId.value = row.b_id
|
||
try {
|
||
await retireVersionApi(row.b_id)
|
||
Message.success(`v${row.b_version_no} 已停用`)
|
||
if (historyDefinition.value) openHistory(historyDefinition.value)
|
||
} catch (error) {
|
||
if (isWorkflowServiceMissing(error)) Message.warning(WORKFLOW_SERVICE_MISSING_MESSAGE)
|
||
else Message.error(error?.message || '停用失败')
|
||
} finally {
|
||
retiringId.value = ''
|
||
}
|
||
}
|
||
|
||
/* ================= 批量删除 ================= */
|
||
|
||
/** 批量删除:占位,未落库;删除定义要走流程服务,不能借通用保存接口删 */
|
||
function confirmBatchDelete() {
|
||
const keys = [...selectedRowKeys.value]
|
||
if (!keys.length) return
|
||
Modal.confirm({
|
||
title: `删除 ${keys.length} 条流程定义?`,
|
||
content: `${WORKFLOW_SERVICE_MISSING_MESSAGE}。删除前还要确认没有运行中的实例引用这些流程。`,
|
||
okText: '知道了',
|
||
onOk: () => {
|
||
Message.info('流程定义的删除需要工作流服务接口,当前未接入')
|
||
},
|
||
})
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="fms-page">
|
||
<FmsModuleListPage :data-code="MODULE_CODE" :row-selection="rowSelection" :row-actions="rowActions">
|
||
<template #actions="{ loading }">
|
||
<Button type="primary" :disabled="loading" @click="onCreate">
|
||
<Plus :size="14" />
|
||
新增
|
||
</Button>
|
||
<Button
|
||
type="danger-outline"
|
||
:disabled="loading || !selectedRowKeys.length"
|
||
@click="confirmBatchDelete"
|
||
>
|
||
<Trash2 :size="14" />
|
||
批量删除{{ selectedRowKeys.length ? `(${selectedRowKeys.length})` : '' }}
|
||
</Button>
|
||
</template>
|
||
</FmsModuleListPage>
|
||
|
||
<Modal v-model:open="historyOpen" title="版本记录" :width="640" :footer="false">
|
||
<div class="version-history">
|
||
<p class="version-history__subject">
|
||
{{ historyDefinition?.b_name }}({{ historyDefinition?.b_id }})
|
||
</p>
|
||
|
||
<p v-if="historyLoading" class="version-history__hint">读取中…</p>
|
||
<p v-else-if="!historyVersions.length" class="version-history__hint">
|
||
没有读到版本记录。流程服务未接入时,版本由后端创建,这里只会是空的。
|
||
</p>
|
||
|
||
<ul v-else class="version-history__list">
|
||
<li v-for="row in historyVersions" :key="row.b_id" class="version-history__item">
|
||
<span class="version-history__no">v{{ row.b_version_no }}</span>
|
||
<Tag :variant="statusMeta(row.b_status).tag">{{ statusMeta(row.b_status).label }}</Tag>
|
||
<span class="version-history__meta">
|
||
{{ row.b_published_by || '—' }} · {{ row.b_published_at || row.b_updated_at || '—' }}
|
||
</span>
|
||
<span class="version-history__nodes">{{ row.v_node_count ?? '—' }} 个节点</span>
|
||
<Button
|
||
v-if="row.b_status === 'published'"
|
||
type="outline"
|
||
:loading="retiringId === row.b_id"
|
||
@click="onRetire(row)"
|
||
>
|
||
停用
|
||
</Button>
|
||
</li>
|
||
</ul>
|
||
|
||
<p class="version-history__hint">
|
||
已发布版本不可原地编辑:改流程时复制为新版本,重新校验并发布;运行中的实例继续绑定原版本。
|
||
版本记录只读,b_canvas_json 不进视图。
|
||
</p>
|
||
|
||
<div class="version-history__footer">
|
||
<Button
|
||
type="primary"
|
||
@click="router.push({ name: 'system-workflow-design', query: { id: historyDefinition?.b_id } })"
|
||
>
|
||
<History :size="14" />
|
||
去编辑
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.version-history {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.version-history__subject {
|
||
margin: 0;
|
||
font-size: 13px;
|
||
color: var(--fms-text);
|
||
}
|
||
|
||
.version-history__list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
padding: 0;
|
||
margin: 0;
|
||
max-height: 50vh;
|
||
overflow: auto;
|
||
list-style: none;
|
||
}
|
||
|
||
.version-history__item {
|
||
display: flex;
|
||
gap: 10px;
|
||
align-items: center;
|
||
padding: 8px 10px;
|
||
background: var(--fms-background);
|
||
border: 1px solid var(--fms-border);
|
||
border-radius: var(--fms-control-radius);
|
||
}
|
||
|
||
.version-history__no {
|
||
flex: none;
|
||
font-size: 13px;
|
||
color: var(--fms-text);
|
||
}
|
||
|
||
.version-history__meta {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 12px;
|
||
color: var(--fms-text-secondary);
|
||
}
|
||
|
||
.version-history__nodes {
|
||
flex: none;
|
||
font-size: 12px;
|
||
color: var(--fms-text-secondary);
|
||
}
|
||
|
||
.version-history__hint {
|
||
margin: 0;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
color: var(--fms-text-secondary);
|
||
}
|
||
|
||
.version-history__footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
}
|
||
</style>
|