Files
X-Financial/web/src/components/audit/AuditRuleDialogs.vue
caoxiaozhu 7989f3a159 feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL
校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计,
优化 agent 运行和编排执行链路,清理旧开发文档,前端新增
系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈
对话框和工作台日期选择器,优化报销创建和审批详情交互,
补充单元测试覆盖。
2026-05-30 15:46:51 +08:00

361 lines
13 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.
<template>
<ConfirmDialog
:open="riskRuleCreateOpen"
badge="自然语言规则"
badge-tone="info"
title="新建风险规则"
description="默认创建费用类风险规则。选择业务环节和费用领域后填写规则标题与自然语言描述,系统会根据评分模型自动计算风险分数和等级。"
cancel-text="取消"
confirm-text="开始生成"
busy-text="生成中..."
confirm-tone="primary"
confirm-icon="mdi mdi-auto-fix"
:busy="riskRuleCreateBusy"
:close-on-mask="!riskRuleCreateBusy"
@close="emit('close-risk-rule-create')"
@confirm="emit('submit-risk-rule-create')"
>
<div class="risk-rule-create-form">
<label>
<span>业务环节</span>
<EnterpriseSelect
v-model="riskRuleCreateForm.business_stage"
:options="riskRuleBusinessStageOptions"
:disabled="riskRuleCreateBusy"
/>
</label>
<label>
<span>费用领域</span>
<EnterpriseSelect
v-model="riskRuleCreateForm.expense_category"
:options="riskRuleExpenseCategoryOptions"
:disabled="riskRuleCreateBusy"
/>
</label>
<label>
<span>是否上传附件</span>
<EnterpriseSelect
v-model="riskRuleCreateForm.requires_attachment"
:options="riskRuleAttachmentOptions"
:disabled="riskRuleCreateBusy"
/>
</label>
<label class="span-2">
<span>规则标题</span>
<input
v-model="riskRuleCreateForm.rule_title"
:disabled="riskRuleCreateBusy"
maxlength="80"
placeholder="例如:差旅目的地与票据城市一致性校验"
/>
</label>
<label class="span-2">
<span>自然语言规则</span>
<textarea
v-model="riskRuleCreateForm.natural_language"
:disabled="riskRuleCreateBusy"
placeholder="例如:住宿城市必须出现在本次差旅行程城市中,否则提示高风险并要求补充说明。"
></textarea>
</label>
</div>
</ConfirmDialog>
<RiskRuleTestDialog
:open="riskRuleTestOpen"
:rule="selectedSkill"
@close="emit('close-risk-rule-test')"
@report-saved="emit('report-saved', $event)"
/>
<ConfirmDialog
:open="riskRuleEditOpen"
badge="规则维护"
badge-tone="info"
:title="riskRuleEditMode === 'revision' ? '创建修订版本' : '编辑风险规则'"
:description="riskRuleEditMode === 'revision' ? '已上线规则不会被直接覆盖,系统会先创建一个新的修订草稿。' : '未上线规则可以直接调整标题、费用领域、附件要求和自然语言描述。'"
cancel-text="取消"
:confirm-text="riskRuleEditMode === 'revision' ? '创建修订' : '保存草稿'"
busy-text="保存中..."
confirm-tone="primary"
confirm-icon="mdi mdi-content-save-outline"
:busy="riskRuleEditBusy"
:close-on-mask="!riskRuleEditBusy"
@close="emit('close-risk-rule-edit')"
@confirm="emit('submit-risk-rule-edit')"
>
<div class="risk-rule-create-form">
<label>
<span>费用领域</span>
<EnterpriseSelect
v-model="riskRuleEditForm.expense_category"
:options="riskRuleExpenseCategoryOptions"
:disabled="riskRuleEditBusy"
/>
</label>
<label>
<span>是否上传附件</span>
<EnterpriseSelect
v-model="riskRuleEditForm.requires_attachment"
:options="riskRuleAttachmentOptions"
:disabled="riskRuleEditBusy"
/>
</label>
<label class="span-2">
<span>规则标题</span>
<input
v-model="riskRuleEditForm.rule_title"
:disabled="riskRuleEditBusy"
maxlength="80"
placeholder="例如:差旅目的地与票据城市一致性校验"
/>
</label>
<label class="span-2">
<span>自然语言规则</span>
<textarea
v-model="riskRuleEditForm.natural_language"
:disabled="riskRuleEditBusy"
placeholder="请用自然语言描述风险判断流程、字段、例外条件和处理动作。"
></textarea>
</label>
<label v-if="riskRuleEditMode === 'revision'" class="span-2">
<span>修订原因</span>
<textarea
v-model="riskRuleEditForm.change_reason"
:disabled="riskRuleEditBusy"
placeholder="请说明本次修订要解决的规则问题或业务变化。"
></textarea>
</label>
</div>
</ConfirmDialog>
<ConfirmDialog
:open="riskRuleDeleteOpen"
badge="删除规则"
badge-tone="danger"
title="删除未发布风险规则"
description="该操作会删除规则草稿、版本记录和关联 JSON 文件。只有从未发布过的规则允许删除。"
cancel-text="取消"
confirm-text="确认删除"
busy-text="删除中..."
confirm-tone="danger"
confirm-icon="mdi mdi-delete-outline"
:busy="actionState === 'delete-risk-rule'"
@close="emit('close-delete-risk-rule')"
@confirm="emit('delete-selected-risk-rule')"
>
<div class="risk-rule-action-confirm">
<span>规则名称</span>
<strong>{{ selectedSkill?.name }}</strong>
</div>
</ConfirmDialog>
<ConfirmDialog
:open="riskRuleReturnOpen"
badge="回退规则"
badge-tone="warning"
title="回退风险规则"
description="回退后规则会回到草稿状态,编写人需要根据原因重新调整并测试。"
cancel-text="取消"
confirm-text="确认回退"
busy-text="回退中..."
confirm-tone="warning"
confirm-icon="mdi mdi-keyboard-return"
:busy="actionState === 'return-risk-rule'"
@close="emit('close-return-risk-rule')"
@confirm="emit('return-selected-risk-rule')"
>
<label class="risk-rule-action-note">
<span>回退原因</span>
<textarea
v-model="returnNoteModel"
rows="4"
:disabled="actionState === 'return-risk-rule'"
placeholder="请说明需要编写人调整的规则问题"
></textarea>
</label>
</ConfirmDialog>
<ConfirmDialog
:open="riskRulePublishOpen"
badge="发布上线"
badge-tone="info"
title="发布风险规则"
description="发布后该规则会进入真实业务风险扫描,只加载正式上线规则。"
cancel-text="取消"
confirm-text="确认发布"
busy-text="发布中..."
confirm-tone="primary"
confirm-icon="mdi mdi-rocket-launch-outline"
:busy="actionState === 'publish-risk-rule'"
@close="emit('close-publish-risk-rule')"
@confirm="emit('publish-selected-risk-rule')"
>
<div class="risk-rule-action-confirm">
<span>测试状态</span>
<strong>{{ riskRuleTestPassed ? '已确认通过' : '未确认通过' }}</strong>
</div>
</ConfirmDialog>
<ConfirmDialog
:open="Boolean(versionSwitchTarget)"
badge="切换版本"
badge-tone="info"
title="切换规则版本"
description="切换后编辑器只会替换当前展示内容,不会直接回滚后端当前版本。"
cancel-text="取消"
confirm-text="确认切换"
busy-text="切换中..."
confirm-tone="primary"
confirm-icon="mdi mdi-swap-horizontal"
@close="emit('cancel-version-switch')"
@confirm="emit('confirm-version-switch')"
>
<div class="version-modal-summary">
<div>
<span>当前展示版本</span>
<strong>{{ selectedSkill?.displayVersion }}</strong>
</div>
<i class="mdi mdi-arrow-right"></i>
<div>
<span>目标版本</span>
<strong>{{ versionSwitchTarget?.version }}</strong>
</div>
</div>
<div v-if="versionSwitchTarget" class="version-modal-note">
<strong>{{ versionSwitchTarget.note }}</strong>
<span>{{ versionSwitchTarget.time }}</span>
</div>
</ConfirmDialog>
<ConfirmDialog
:open="reviewSubmitOpen"
badge="提交审核"
badge-tone="info"
title="提交规则版本审核"
description="请先确认本次送审采用的版本号,并选择负责审核的高级管理员。若填写新的版本号,系统会将当前工作稿固化为该版本后再送审。"
cancel-text="取消"
confirm-text="确认提交"
busy-text="提交中..."
confirm-tone="primary"
confirm-icon="mdi mdi-send-outline"
:busy="actionState === 'review-pending'"
@close="emit('close-submit-review')"
@confirm="emit('submit-selected-rule-for-review')"
>
<div class="review-submit-form">
<label>
<span>送审版本号</span>
<input
v-model="reviewSubmitVersionModel"
type="text"
placeholder="例如v1.1.0"
:disabled="actionState === 'review-pending'"
/>
</label>
<label>
<span>审核人</span>
<EnterpriseSelect
v-model="reviewSubmitReviewerModel"
:options="reviewSubmitReviewerOptions"
:placeholder="reviewSubmitReviewerLoading ? '加载审核人中...' : '请选择高级管理员'"
:disabled="reviewSubmitReviewerLoading || actionState === 'review-pending'"
/>
</label>
<p
v-if="!reviewSubmitReviewerLoading && !hasReviewSubmitReviewers"
class="review-submit-hint"
>
当前没有可选的高级管理员请先在员工管理中配置具备管理员角色的员工
</p>
<div v-if="selectedSkillUsesJsonRisk" class="review-submit-test-state">
<span>测试确认</span>
<strong :class="{ passed: riskRuleTestPassed }">
{{ riskRuleTestPassed ? '当前版本已通过测试确认' : '当前版本尚未确认测试通过' }}
</strong>
<p>只有保存测试报告的风险规则才能提交给高级财务人员审核</p>
</div>
</div>
</ConfirmDialog>
</template>
<script setup>
import { computed } from 'vue'
import ConfirmDialog from '../shared/ConfirmDialog.vue'
import EnterpriseSelect from '../shared/EnterpriseSelect.vue'
import RiskRuleTestDialog from '../shared/RiskRuleTestDialog.vue'
defineOptions({
name: 'AuditRuleDialogs'
})
const props = defineProps({
selectedSkill: { type: Object, default: null },
versionSwitchTarget: { type: Object, default: null },
actionState: { type: String, default: '' },
riskRuleCreateOpen: { type: Boolean, default: false },
riskRuleCreateBusy: { type: Boolean, default: false },
riskRuleCreateForm: { type: Object, required: true },
riskRuleBusinessStageOptions: { type: Array, default: () => [] },
riskRuleExpenseCategoryOptions: { type: Array, default: () => [] },
riskRuleAttachmentOptions: { type: Array, default: () => [] },
riskRuleTestOpen: { type: Boolean, default: false },
riskRuleEditOpen: { type: Boolean, default: false },
riskRuleEditMode: { type: String, default: 'draft' },
riskRuleEditForm: { type: Object, default: () => ({}) },
riskRuleEditBusy: { type: Boolean, default: false },
riskRuleDeleteOpen: { type: Boolean, default: false },
riskRuleReturnOpen: { type: Boolean, default: false },
riskRulePublishOpen: { type: Boolean, default: false },
riskRuleReturnNote: { type: String, default: '' },
riskRuleTestPassed: { type: Boolean, default: false },
reviewSubmitOpen: { type: Boolean, default: false },
reviewSubmitVersion: { type: String, default: '' },
reviewSubmitReviewer: { type: String, default: '' },
reviewSubmitReviewerLoading: { type: Boolean, default: false },
reviewSubmitReviewerOptions: { type: Array, default: () => [] },
hasReviewSubmitReviewers: { type: Boolean, default: false },
selectedSkillUsesJsonRisk: { type: Boolean, default: false }
})
const emit = defineEmits([
'update:riskRuleReturnNote',
'update:reviewSubmitVersion',
'update:reviewSubmitReviewer',
'close-risk-rule-create',
'submit-risk-rule-create',
'close-risk-rule-test',
'report-saved',
'close-risk-rule-edit',
'submit-risk-rule-edit',
'close-delete-risk-rule',
'delete-selected-risk-rule',
'close-return-risk-rule',
'return-selected-risk-rule',
'close-publish-risk-rule',
'publish-selected-risk-rule',
'cancel-version-switch',
'confirm-version-switch',
'close-submit-review',
'submit-selected-rule-for-review'
])
const returnNoteModel = computed({
get: () => props.riskRuleReturnNote,
set: (value) => emit('update:riskRuleReturnNote', value)
})
const reviewSubmitVersionModel = computed({
get: () => props.reviewSubmitVersion,
set: (value) => emit('update:reviewSubmitVersion', value)
})
const reviewSubmitReviewerModel = computed({
get: () => props.reviewSubmitReviewer,
set: (value) => emit('update:reviewSubmitReviewer', value)
})
</script>
<style scoped src="../../assets/styles/views/audit-view.css"></style>
<style scoped src="../../assets/styles/views/audit-view-part2.css"></style>