fix: improve knowledge review scope controls
This commit is contained in:
@@ -892,7 +892,7 @@
|
|||||||
<el-descriptions :column="2" border size="small">
|
<el-descriptions :column="2" border size="small">
|
||||||
<el-descriptions-item label="资料">{{ selectedQualityAsset.sourceName }}</el-descriptions-item>
|
<el-descriptions-item label="资料">{{ selectedQualityAsset.sourceName }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="状态">{{ lifecycleLabel(selectedQualityAsset.lifecycleStatus) }}</el-descriptions-item>
|
<el-descriptions-item label="状态">{{ lifecycleLabel(selectedQualityAsset.lifecycleStatus) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="来源">{{ selectedQualityAsset.sourceAuthority }}</el-descriptions-item>
|
<el-descriptions-item label="来源">{{ sourceAuthorityLabel(selectedQualityAsset.sourceAuthority) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="版本">{{ selectedQualityAsset.sourceVersion }}</el-descriptions-item>
|
<el-descriptions-item label="版本">{{ selectedQualityAsset.sourceVersion }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="语义分析">
|
<el-descriptions-item label="语义分析">
|
||||||
{{ semanticStatusLabel(selectedQualityAsset.semanticAnalysisStatus) }}
|
{{ semanticStatusLabel(selectedQualityAsset.semanticAnalysisStatus) }}
|
||||||
@@ -907,7 +907,7 @@
|
|||||||
v-if="reviewPackage"
|
v-if="reviewPackage"
|
||||||
:type="reviewPackage.riskLevel === 'LOW' ? 'success' : reviewPackage.riskLevel === 'MEDIUM' ? 'warning' : 'danger'"
|
:type="reviewPackage.riskLevel === 'LOW' ? 'success' : reviewPackage.riskLevel === 'MEDIUM' ? 'warning' : 'danger'"
|
||||||
>
|
>
|
||||||
{{ reviewPackage.riskLevel }} 风险
|
{{ riskLevelLabel(reviewPackage.riskLevel) }}风险
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="reviewPackage" class="review-assistant-grid">
|
<div v-if="reviewPackage" class="review-assistant-grid">
|
||||||
@@ -920,7 +920,9 @@
|
|||||||
<el-table-column label="版本" width="86"
|
<el-table-column label="版本" width="86"
|
||||||
><template #default="{ row }">v{{ row.versionNo }}</template></el-table-column
|
><template #default="{ row }">v{{ row.versionNo }}</template></el-table-column
|
||||||
>
|
>
|
||||||
<el-table-column prop="versionStatus" label="状态" width="130" />
|
<el-table-column label="状态" width="130">
|
||||||
|
<template #default="{ row }">{{ versionStatusLabel(row.versionStatus) }}</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column label="指针" width="150">
|
<el-table-column label="指针" width="150">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.published" size="small" type="success">生效版</el-tag>
|
<el-tag v-if="row.published" size="small" type="success">生效版</el-tag>
|
||||||
@@ -995,9 +997,9 @@
|
|||||||
<div v-loading="qualityDetailLoading" class="quality-issue-list">
|
<div v-loading="qualityDetailLoading" class="quality-issue-list">
|
||||||
<h3>检测结果</h3>
|
<h3>检测结果</h3>
|
||||||
<div v-for="issue in qualityIssues" :key="issue.id" class="quality-issue">
|
<div v-for="issue in qualityIssues" :key="issue.id" class="quality-issue">
|
||||||
<el-tag :type="issue.gateType === 'HARD' ? 'danger' : 'warning'" size="small">{{ issue.reasonCode }}</el-tag>
|
<el-tag :type="issue.gateType === 'HARD' ? 'danger' : 'warning'" size="small">{{ qualityReasonLabel(issue.reasonCode) }}</el-tag>
|
||||||
<span>{{ issueEvidence(issue.evidenceJson) }}</span>
|
<span>{{ qualityIssueDescription(issue) }}</span>
|
||||||
<small>{{ issue.recommendedAction }}</small>
|
<small>{{ qualityIssueAction(issue) }}</small>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-if="!qualityDetailLoading && !qualityIssues.length" :image-size="56" description="未发现质量问题" />
|
<el-empty v-if="!qualityDetailLoading && !qualityIssues.length" :image-size="56" description="未发现质量问题" />
|
||||||
</div>
|
</div>
|
||||||
@@ -1089,13 +1091,70 @@
|
|||||||
<el-input v-model="qualityReviewForm.sourceVersion" />
|
<el-input v-model="qualityReviewForm.sourceVersion" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用岗位">
|
<el-form-item label="适用岗位">
|
||||||
<el-input v-model="qualityReviewForm.applicableRole" placeholder="例如:生活顾问" />
|
<div class="quality-scope-field">
|
||||||
|
<el-segmented
|
||||||
|
v-model="qualityScopeModes.applicableRole"
|
||||||
|
:options="scopeModeOptions"
|
||||||
|
size="small"
|
||||||
|
@change="changeQualityScope('applicableRole', $event)"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-if="qualityScopeModes.applicableRole === 'SPECIFIED'"
|
||||||
|
v-model="qualityReviewForm.applicableRole"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
clearable
|
||||||
|
default-first-option
|
||||||
|
:loading="qualityScopeOptionsLoading"
|
||||||
|
placeholder="请选择岗位;无匹配项可输入后回车"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in qualityScopeOptions.applicableRole" :key="item" :label="qualityScopeOptionLabel(item)" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用区域">
|
<el-form-item label="适用区域">
|
||||||
<el-input v-model="qualityReviewForm.applicableRegion" placeholder="例如:南京" />
|
<div class="quality-scope-field">
|
||||||
|
<el-segmented
|
||||||
|
v-model="qualityScopeModes.applicableRegion"
|
||||||
|
:options="scopeModeOptions"
|
||||||
|
size="small"
|
||||||
|
@change="changeQualityScope('applicableRegion', $event)"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-if="qualityScopeModes.applicableRegion === 'SPECIFIED'"
|
||||||
|
v-model="qualityReviewForm.applicableRegion"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
clearable
|
||||||
|
default-first-option
|
||||||
|
:loading="qualityScopeOptionsLoading"
|
||||||
|
placeholder="请选择区域;无匹配项可输入后回车"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in qualityScopeOptions.applicableRegion" :key="item" :label="qualityScopeOptionLabel(item)" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="适用项目">
|
<el-form-item label="适用项目">
|
||||||
<el-input v-model="qualityReviewForm.applicableProject" placeholder="例如:江北项目" />
|
<div class="quality-scope-field">
|
||||||
|
<el-segmented
|
||||||
|
v-model="qualityScopeModes.applicableProject"
|
||||||
|
:options="scopeModeOptions"
|
||||||
|
size="small"
|
||||||
|
@change="changeQualityScope('applicableProject', $event)"
|
||||||
|
/>
|
||||||
|
<el-select
|
||||||
|
v-if="qualityScopeModes.applicableProject === 'SPECIFIED'"
|
||||||
|
v-model="qualityReviewForm.applicableProject"
|
||||||
|
filterable
|
||||||
|
allow-create
|
||||||
|
clearable
|
||||||
|
default-first-option
|
||||||
|
:loading="qualityScopeOptionsLoading"
|
||||||
|
placeholder="请选择项目;无匹配项可输入后回车"
|
||||||
|
>
|
||||||
|
<el-option v-for="item in qualityScopeOptions.applicableProject" :key="item" :label="qualityScopeOptionLabel(item)" :value="item" />
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="生效日期">
|
<el-form-item label="生效日期">
|
||||||
<el-date-picker v-model="qualityReviewForm.effectiveFrom" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
<el-date-picker v-model="qualityReviewForm.effectiveFrom" type="date" value-format="YYYY-MM-DD" placeholder="开始日期" />
|
||||||
@@ -1117,10 +1176,20 @@
|
|||||||
<el-form-item label="审核意见">
|
<el-form-item label="审核意见">
|
||||||
<el-input v-model="qualityReviewForm.reason" type="textarea" :rows="3" maxlength="1000" show-word-limit />
|
<el-input v-model="qualityReviewForm.reason" type="textarea" :rows="3" maxlength="1000" show-word-limit />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="openSoftQualityIssues.length" label="确认已人工核对以下软提示">
|
<el-form-item v-if="openSoftQualityIssues.length" label="人工核对确认(需逐项勾选)">
|
||||||
|
<el-alert
|
||||||
|
class="quality-acknowledgement-tip"
|
||||||
|
title="请先对照上方检测结果核查原文;勾选表示已确认该提示不影响本次发布,系统不会自动修改资料。"
|
||||||
|
type="warning"
|
||||||
|
:closable="false"
|
||||||
|
show-icon
|
||||||
|
/>
|
||||||
<el-checkbox-group v-model="qualityReviewForm.acceptedReasonCodes" class="reason-checks">
|
<el-checkbox-group v-model="qualityReviewForm.acceptedReasonCodes" class="reason-checks">
|
||||||
<el-checkbox v-for="issue in openSoftQualityIssues" :key="issue.id" :value="issue.reasonCode">
|
<el-checkbox v-for="issue in openSoftQualityIssues" :key="issue.id" :value="issue.reasonCode">
|
||||||
{{ issue.reasonCode }}
|
<span class="reason-check-content">
|
||||||
|
<strong>已核对:{{ qualityReasonLabel(issue.reasonCode) }}</strong>
|
||||||
|
<small>{{ qualityAcknowledgementText(issue.reasonCode) }}</small>
|
||||||
|
</span>
|
||||||
</el-checkbox>
|
</el-checkbox>
|
||||||
</el-checkbox-group>
|
</el-checkbox-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -1232,6 +1301,7 @@
|
|||||||
import { CircleCheck, Document, Files, Operation, Plus, Refresh, Search, Upload, WarningFilled } from '@element-plus/icons-vue';
|
import { CircleCheck, Document, Files, Operation, Plus, Refresh, Search, Upload, WarningFilled } from '@element-plus/icons-vue';
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||||
|
import { getOrgSnapshot, type OrgSnapshotResponse } from '@/api/aihr/mobile-admin';
|
||||||
import {
|
import {
|
||||||
addGoldenSample,
|
addGoldenSample,
|
||||||
approveKnowledgeGlossary,
|
approveKnowledgeGlossary,
|
||||||
@@ -1437,7 +1507,10 @@ const sampleReviewForm = ref({
|
|||||||
result: 'CONFIRMED' as NonNullable<RuleReviewSample['reviewResult']>,
|
result: 'CONFIRMED' as NonNullable<RuleReviewSample['reviewResult']>,
|
||||||
note: ''
|
note: ''
|
||||||
});
|
});
|
||||||
const openSoftQualityIssues = computed(() => qualityIssues.value.filter((issue) => issue.status === 'OPEN' && issue.gateType === 'SOFT'));
|
const openSoftQualityIssues = computed(() => {
|
||||||
|
const openIssues = qualityIssues.value.filter((issue) => issue.status === 'OPEN' && issue.gateType === 'SOFT');
|
||||||
|
return Array.from(new Map(openIssues.map((issue) => [issue.reasonCode, issue])).values());
|
||||||
|
});
|
||||||
const hasVersionConflict = computed(() => qualityIssues.value.some((issue) => issue.reasonCode === 'VERSION_CONFLICT'));
|
const hasVersionConflict = computed(() => qualityIssues.value.some((issue) => issue.reasonCode === 'VERSION_CONFLICT'));
|
||||||
const unresolvedQualityConflicts = computed(() =>
|
const unresolvedQualityConflicts = computed(() =>
|
||||||
qualityConflicts.value.filter((conflict) => conflict.status === 'PENDING_REVIEW' || conflict.status === 'CONFIRMED')
|
qualityConflicts.value.filter((conflict) => conflict.status === 'PENDING_REVIEW' || conflict.status === 'CONFIRMED')
|
||||||
@@ -1446,6 +1519,23 @@ const semanticAnalysisComplete = computed(() => ['COMPLETE', 'NOT_REQUIRED'].inc
|
|||||||
const privacyDerivativeReady = computed(() => ['CLEAN', 'REDACTED'].includes(qualityPrivacyPreview.value?.privacyStatus || ''));
|
const privacyDerivativeReady = computed(() => ['CLEAN', 'REDACTED'].includes(qualityPrivacyPreview.value?.privacyStatus || ''));
|
||||||
const selectedQualityAsset = ref<QualityAsset | null>(null);
|
const selectedQualityAsset = ref<QualityAsset | null>(null);
|
||||||
const qualityDialogVisible = ref(false);
|
const qualityDialogVisible = ref(false);
|
||||||
|
type QualityScopeField = 'applicableRole' | 'applicableRegion' | 'applicableProject';
|
||||||
|
type QualityScopeMode = 'ALL' | 'SPECIFIED';
|
||||||
|
const scopeModeOptions = [
|
||||||
|
{ label: '全部', value: 'ALL' },
|
||||||
|
{ label: '指定', value: 'SPECIFIED' }
|
||||||
|
];
|
||||||
|
const qualityScopeModes = ref<Record<QualityScopeField, QualityScopeMode>>({
|
||||||
|
applicableRole: 'ALL',
|
||||||
|
applicableRegion: 'ALL',
|
||||||
|
applicableProject: 'ALL'
|
||||||
|
});
|
||||||
|
const qualityScopeOptionsLoading = ref(false);
|
||||||
|
const qualityScopeOptions = ref<Record<QualityScopeField, string[]>>({
|
||||||
|
applicableRole: [],
|
||||||
|
applicableRegion: [],
|
||||||
|
applicableProject: []
|
||||||
|
});
|
||||||
const qualityReviewForm = ref<QualityReviewRequest>({
|
const qualityReviewForm = ref<QualityReviewRequest>({
|
||||||
reason: '',
|
reason: '',
|
||||||
usageType: 'REFERENCE_ONLY',
|
usageType: 'REFERENCE_ONLY',
|
||||||
@@ -1543,6 +1633,96 @@ const usageOptions = [
|
|||||||
{ label: '仅供参考', value: 'REFERENCE_ONLY' }
|
{ label: '仅供参考', value: 'REFERENCE_ONLY' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const qualityReasonLabels: Record<string, string> = {
|
||||||
|
PARSE_EMPTY: '未解析到正文',
|
||||||
|
PARSE_FAILED: '内容解析异常',
|
||||||
|
PAGE_COUNT_MISMATCH: '页数不一致',
|
||||||
|
SOURCE_UNAVAILABLE: '来源文件不可用',
|
||||||
|
SOURCE_UNKNOWN: '来源权威不明确',
|
||||||
|
SOURCE_EVIDENCE_INSUFFICIENT: '来源依据不足',
|
||||||
|
SOURCE_VERSION_MISSING: '来源版本缺失',
|
||||||
|
PII_REDACTED: '已执行个人信息脱敏',
|
||||||
|
PII_DETECTED: '检测到未脱敏个人信息',
|
||||||
|
SECRET_DETECTED: '检测到疑似密钥',
|
||||||
|
CROSS_TENANT_REFERENCE: '检测到跨租户引用',
|
||||||
|
PROMPT_INJECTION_SUSPECTED: '检测到疑似提示词注入',
|
||||||
|
POLICY_EXPIRED: '资料已超过有效期',
|
||||||
|
SYNTHETIC_UNDECLARED: '合成内容未声明',
|
||||||
|
SYNTHETIC_UNVERIFIED: '合成内容待核验',
|
||||||
|
EXACT_DUPLICATE: '完全重复资料',
|
||||||
|
OCR_LOW_CONFIDENCE: '图片文字识别可信度低',
|
||||||
|
ASR_LOW_CONFIDENCE: '语音识别可信度低',
|
||||||
|
HEADER_FOOTER_NOISE: '页眉页脚噪声',
|
||||||
|
PAGINATION_NOISE: '页码噪声',
|
||||||
|
CHUNK_CONTEXT_BROKEN: '片段上下文不完整',
|
||||||
|
DOMAIN_IRRELEVANT: '业务相关性较低',
|
||||||
|
CONTEXT_INCOMPLETE: '案例要素不完整',
|
||||||
|
NEAR_DUPLICATE: '高度相似资料',
|
||||||
|
SEMANTIC_DUPLICATE: '语义重复资料',
|
||||||
|
SEMANTIC_ANALYSIS_DEGRADED: '语义分析已降级',
|
||||||
|
SEMANTIC_ANALYSIS_FAILED: '语义分析失败',
|
||||||
|
VERSION_CONFLICT: '资料版本冲突',
|
||||||
|
EXPERT_CONFLICT: '专家观点冲突',
|
||||||
|
DOWNSTREAM_ANSWER_DISPUTED: '引用回答存在争议',
|
||||||
|
HUMAN_REVIEW_REQUIRED: '需要人工审核'
|
||||||
|
};
|
||||||
|
|
||||||
|
const qualityReasonDescriptions: Record<string, string> = {
|
||||||
|
PARSE_EMPTY: '系统没有从文件中提取到可审核的正文。',
|
||||||
|
PARSE_FAILED: '解析结果存在异常字符或内容损坏。',
|
||||||
|
PAGE_COUNT_MISMATCH: '文件声明的页数与实际解析页数不一致。',
|
||||||
|
SOURCE_UNAVAILABLE: '原始文件当前无法读取或验证。',
|
||||||
|
SOURCE_UNKNOWN: '尚未确认资料的发布主体或权威级别。',
|
||||||
|
SOURCE_EVIDENCE_INSUFFICIENT: '资料明确缺少事实来源或支撑依据。',
|
||||||
|
SOURCE_VERSION_MISSING: '尚未填写可追溯的资料版本。',
|
||||||
|
PII_REDACTED: '系统已对识别到的个人信息进行脱敏处理。',
|
||||||
|
PII_DETECTED: '正文仍包含可能识别到个人的信息。',
|
||||||
|
SECRET_DETECTED: '正文包含疑似账号、口令或访问密钥。',
|
||||||
|
CROSS_TENANT_REFERENCE: '资料引用了其他租户的数据。',
|
||||||
|
PROMPT_INJECTION_SUSPECTED: '正文包含可能影响模型行为的指令性内容。',
|
||||||
|
POLICY_EXPIRED: '资料已不在标注的有效期内。',
|
||||||
|
SYNTHETIC_UNDECLARED: '系统发现合成内容,但资料没有如实声明。',
|
||||||
|
SYNTHETIC_UNVERIFIED: '合成内容尚未由业务人员核实。',
|
||||||
|
EXACT_DUPLICATE: '检测到内容完全相同的历史资料,不会自动合并。',
|
||||||
|
OCR_LOW_CONFIDENCE: '图片文字识别结果的可信度不足。',
|
||||||
|
ASR_LOW_CONFIDENCE: '语音转写结果的可信度不足。',
|
||||||
|
HEADER_FOOTER_NOISE: '多页重复出现短文本,可能是页眉或页脚。',
|
||||||
|
PAGINATION_NOISE: '正文中检测到独立的页码行。',
|
||||||
|
CHUNK_CONTEXT_BROKEN: '部分切片过短或依赖前后文,单独引用时可能失真。',
|
||||||
|
DOMAIN_IRRELEVANT: '没有检测到已配置的物业服务或企业学习领域内容。',
|
||||||
|
CONTEXT_INCOMPLETE: '案例的场景、事实、行动、依据或结果信息不完整。',
|
||||||
|
NEAR_DUPLICATE: '检测到内容高度相似的历史资料。',
|
||||||
|
SEMANTIC_DUPLICATE: '检测到表达不同但含义接近的历史资料。',
|
||||||
|
SEMANTIC_ANALYSIS_DEGRADED: '当前仅完成降级分析,重复判断能力有限。',
|
||||||
|
SEMANTIC_ANALYSIS_FAILED: '语义重复分析没有成功完成。',
|
||||||
|
VERSION_CONFLICT: '同名资料已有已发布版本,且本次内容发生变化。',
|
||||||
|
EXPERT_CONFLICT: '资料中的结论与已记录的专家结论存在冲突。',
|
||||||
|
DOWNSTREAM_ANSWER_DISPUTED: '使用该资料生成的回答曾被标记为有争议。',
|
||||||
|
HUMAN_REVIEW_REQUIRED: '上传、提取和生成的内容在人工批准前均不视为可信。'
|
||||||
|
};
|
||||||
|
|
||||||
|
const qualityAcknowledgements: Record<string, string> = {
|
||||||
|
PAGINATION_NOISE: '已确认页码不属于正文,不影响内容含义。',
|
||||||
|
HEADER_FOOTER_NOISE: '已确认重复页眉页脚不影响正文理解。',
|
||||||
|
HUMAN_REVIEW_REQUIRED: '已人工阅读资料,并确认内容可进入生产检索。',
|
||||||
|
EXACT_DUPLICATE: '已对照重复资料,确认保留当前版本且不自动合并。',
|
||||||
|
NEAR_DUPLICATE: '已对照相似资料,确认两份资料的保留方式。',
|
||||||
|
SEMANTIC_DUPLICATE: '已对照语义相近资料,确认不会造成错误召回。',
|
||||||
|
SOURCE_UNKNOWN: '已核实并补充正确的来源权威级别。',
|
||||||
|
SOURCE_EVIDENCE_INSUFFICIENT: '已核查缺失依据,并确认当前发布结论可追溯。',
|
||||||
|
SOURCE_VERSION_MISSING: '已补充并核对可追溯的来源版本。',
|
||||||
|
SYNTHETIC_UNVERIFIED: '已人工核实合成内容与正式业务事实一致。',
|
||||||
|
OCR_LOW_CONFIDENCE: '已对照原图复核识别文字。',
|
||||||
|
ASR_LOW_CONFIDENCE: '已回听原音频复核转写内容。',
|
||||||
|
CHUNK_CONTEXT_BROKEN: '已抽查相关片段,确认单独引用不会改变原意。',
|
||||||
|
DOMAIN_IRRELEVANT: '已确认资料属于本次知识库的业务范围。',
|
||||||
|
CONTEXT_INCOMPLETE: '已确认缺失要素不影响该资料的预期用途。',
|
||||||
|
SEMANTIC_ANALYSIS_DEGRADED: '已人工扩大重复资料核查范围并确认可发布。',
|
||||||
|
VERSION_CONFLICT: '已确认新旧版本关系和适用范围。',
|
||||||
|
EXPERT_CONFLICT: '已完成业务裁决并确认采用的权威结论。',
|
||||||
|
DOWNSTREAM_ANSWER_DISPUTED: '已复核争议回答及引用原文,确认本资料可继续使用。'
|
||||||
|
};
|
||||||
|
|
||||||
const seedOverview: ProcessingOverview = {
|
const seedOverview: ProcessingOverview = {
|
||||||
metrics: [
|
metrics: [
|
||||||
{ key: 'total', label: '资料总量', value: '3.2 GB', note: '已接入 1,286 个文件', type: 'danger' },
|
{ key: 'total', label: '资料总量', value: '3.2 GB', note: '已接入 1,286 个文件', type: 'danger' },
|
||||||
@@ -2623,15 +2803,23 @@ async function openQualityReview(asset: QualityAsset) {
|
|||||||
acceptedReasonCodes: [],
|
acceptedReasonCodes: [],
|
||||||
supersedesAssetId: undefined
|
supersedesAssetId: undefined
|
||||||
};
|
};
|
||||||
|
qualityScopeModes.value = {
|
||||||
|
applicableRole: asset.applicableRole?.trim() ? 'SPECIFIED' : 'ALL',
|
||||||
|
applicableRegion: asset.applicableRegion?.trim() ? 'SPECIFIED' : 'ALL',
|
||||||
|
applicableProject: asset.applicableProject?.trim() ? 'SPECIFIED' : 'ALL'
|
||||||
|
};
|
||||||
|
buildQualityScopeOptions(asset, [], undefined);
|
||||||
|
qualityScopeOptionsLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const [issues, privacyPreview, conflicts, published, versions, impact, review] = await Promise.all([
|
const [issues, privacyPreview, conflicts, published, versions, impact, review, orgSnapshot] = await Promise.all([
|
||||||
listQualityIssues(asset.id),
|
listQualityIssues(asset.id),
|
||||||
getQualityPrivacyPreview(asset.id),
|
getQualityPrivacyPreview(asset.id),
|
||||||
listQualityConflicts(asset.id),
|
listQualityConflicts(asset.id),
|
||||||
listQualityAssets('PUBLISHED', 200),
|
listQualityAssets('PUBLISHED', 200),
|
||||||
listAssetVersions(asset.id),
|
listAssetVersions(asset.id),
|
||||||
getDeletionImpact(asset.id),
|
getDeletionImpact(asset.id),
|
||||||
asset.lifecycleStatus === 'REVIEW_PENDING' ? getAssetReviewPackage(asset.id) : Promise.resolve({ data: null })
|
asset.lifecycleStatus === 'REVIEW_PENDING' ? getAssetReviewPackage(asset.id) : Promise.resolve({ data: null }),
|
||||||
|
getOrgSnapshot({ status: 'active', pageNum: 1, pageSize: 500 }).catch(() => undefined)
|
||||||
]);
|
]);
|
||||||
if (selectedQualityAsset.value?.id !== asset.id) return;
|
if (selectedQualityAsset.value?.id !== asset.id) return;
|
||||||
qualityIssues.value = issues.data || [];
|
qualityIssues.value = issues.data || [];
|
||||||
@@ -2641,6 +2829,7 @@ async function openQualityReview(asset: QualityAsset) {
|
|||||||
assetVersions.value = versions.data || [];
|
assetVersions.value = versions.data || [];
|
||||||
deletionImpact.value = impact.data || null;
|
deletionImpact.value = impact.data || null;
|
||||||
reviewPackage.value = review.data || null;
|
reviewPackage.value = review.data || null;
|
||||||
|
buildQualityScopeOptions(asset, published.data || [], orgSnapshot?.data);
|
||||||
} catch {
|
} catch {
|
||||||
if (selectedQualityAsset.value?.id !== asset.id) return;
|
if (selectedQualityAsset.value?.id !== asset.id) return;
|
||||||
qualityIssues.value = [];
|
qualityIssues.value = [];
|
||||||
@@ -2652,8 +2841,35 @@ async function openQualityReview(asset: QualityAsset) {
|
|||||||
deletionImpact.value = null;
|
deletionImpact.value = null;
|
||||||
ElMessage.error('质量原因加载失败');
|
ElMessage.error('质量原因加载失败');
|
||||||
} finally {
|
} finally {
|
||||||
if (selectedQualityAsset.value?.id === asset.id) qualityDetailLoading.value = false;
|
if (selectedQualityAsset.value?.id === asset.id) {
|
||||||
|
qualityScopeOptionsLoading.value = false;
|
||||||
|
qualityDetailLoading.value = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeQualityScope(field: QualityScopeField, mode: string | number | boolean | undefined) {
|
||||||
|
if (mode === 'ALL') qualityReviewForm.value[field] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildQualityScopeOptions(asset: QualityAsset, history: QualityAsset[], snapshot?: OrgSnapshotResponse) {
|
||||||
|
const assets = [...qualityAssets.value, ...history, asset];
|
||||||
|
const distinct = (values: Array<string | undefined>) =>
|
||||||
|
Array.from(new Set(values.map((value) => value?.trim()).filter((value): value is string => Boolean(value)))).sort((left, right) =>
|
||||||
|
left.localeCompare(right, 'zh-CN')
|
||||||
|
);
|
||||||
|
qualityScopeOptions.value = {
|
||||||
|
applicableRole: distinct([...assets.map((item) => item.applicableRole), ...(snapshot?.rows.map((item) => item.positionName) || [])]),
|
||||||
|
applicableRegion: distinct(assets.map((item) => item.applicableRegion)),
|
||||||
|
applicableProject: distinct([
|
||||||
|
...assets.map((item) => item.applicableProject),
|
||||||
|
...(snapshot?.projects.map((item) => item.projectName || item.projectCode) || [])
|
||||||
|
])
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityScopeOptionLabel(value: string) {
|
||||||
|
return ({ employee: '员工' } as Record<string, string>)[value.toLowerCase()] || value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reviewConflict(conflict: QualityConflict, decision: 'CONFIRMED' | 'FALSE_POSITIVE' | 'RESOLVED') {
|
async function reviewConflict(conflict: QualityConflict, decision: 'CONFIRMED' | 'FALSE_POSITIVE' | 'RESOLVED') {
|
||||||
@@ -2688,6 +2904,13 @@ async function publishAsset() {
|
|||||||
ElMessage.warning('请填写来源权威级别、来源版本和审核意见');
|
ElMessage.warning('请填写来源权威级别、来源版本和审核意见');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const missingScope = (Object.keys(qualityScopeModes.value) as QualityScopeField[]).find(
|
||||||
|
(field) => qualityScopeModes.value[field] === 'SPECIFIED' && !form[field]?.trim()
|
||||||
|
);
|
||||||
|
if (missingScope) {
|
||||||
|
ElMessage.warning(`请选择${{ applicableRole: '适用岗位', applicableRegion: '适用区域', applicableProject: '适用项目' }[missingScope]}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const openReasons = openSoftQualityIssues.value.map((item) => item.reasonCode);
|
const openReasons = openSoftQualityIssues.value.map((item) => item.reasonCode);
|
||||||
if (openReasons.some((reason) => !form.acceptedReasonCodes.includes(reason))) {
|
if (openReasons.some((reason) => !form.acceptedReasonCodes.includes(reason))) {
|
||||||
ElMessage.warning('请逐项确认所有软质量提示');
|
ElMessage.warning('请逐项确认所有软质量提示');
|
||||||
@@ -2699,7 +2922,13 @@ async function publishAsset() {
|
|||||||
}
|
}
|
||||||
qualityPublishing.value = true;
|
qualityPublishing.value = true;
|
||||||
try {
|
try {
|
||||||
await publishQualityAsset(asset.id, { ...form, reason: form.reason.trim() });
|
await publishQualityAsset(asset.id, {
|
||||||
|
...form,
|
||||||
|
reason: form.reason.trim(),
|
||||||
|
applicableRole: form.applicableRole?.trim() || undefined,
|
||||||
|
applicableRegion: form.applicableRegion?.trim() || undefined,
|
||||||
|
applicableProject: form.applicableProject?.trim() || undefined
|
||||||
|
});
|
||||||
ElMessage.success('已批准并发布到生产检索');
|
ElMessage.success('已批准并发布到生产检索');
|
||||||
qualityDialogVisible.value = false;
|
qualityDialogVisible.value = false;
|
||||||
await Promise.all([loadQualityAssets(), loadOverview(), loadIndexHealth(), loadQualityMonitoring()]);
|
await Promise.all([loadQualityAssets(), loadOverview(), loadIndexHealth(), loadQualityMonitoring()]);
|
||||||
@@ -2817,14 +3046,67 @@ function usageLabel(usage: string) {
|
|||||||
return usageOptions.find((item) => item.value === usage)?.label || usage;
|
return usageOptions.find((item) => item.value === usage)?.label || usage;
|
||||||
}
|
}
|
||||||
|
|
||||||
function issueEvidence(value: string) {
|
function sourceAuthorityLabel(value: string) {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
FORMAL_POLICY: '正式制度',
|
||||||
|
COMPANY_POLICY: '公司正式制度',
|
||||||
|
APPROVED_SOP: '已批准 SOP',
|
||||||
|
REGULATION: '法规',
|
||||||
|
EMPLOYEE_SUBMISSION: '员工经验投稿',
|
||||||
|
UNKNOWN: '来源待确认'
|
||||||
|
};
|
||||||
|
if (!value) return '未填写';
|
||||||
|
return labels[value] || (/^[A-Z0-9_]+$/.test(value) ? '其他来源(待确认)' : value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function riskLevelLabel(value: string) {
|
||||||
|
return ({ LOW: '低', MEDIUM: '中', HIGH: '高', CRITICAL: '关键' } as Record<string, string>)[value] || '未知';
|
||||||
|
}
|
||||||
|
|
||||||
|
function versionStatusLabel(value: string) {
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
{
|
||||||
|
REVIEW_PENDING: '待审核',
|
||||||
|
QUARANTINED: '已隔离',
|
||||||
|
APPROVED: '已批准',
|
||||||
|
PUBLISHED: '已发布',
|
||||||
|
DEPRECATED: '已撤回'
|
||||||
|
} as Record<string, string>
|
||||||
|
)[value] || '未知状态'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityReasonLabel(reasonCode: string) {
|
||||||
|
return qualityReasonLabels[reasonCode] || '其他质量提示';
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityIssueSummary(value: string) {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(value || '{}').summary || '已命中质量规则';
|
return String(JSON.parse(value || '{}').summary || '');
|
||||||
} catch {
|
} catch {
|
||||||
return '已命中质量规则';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function qualityIssueDescription(issue: QualityIssue) {
|
||||||
|
const summary = qualityIssueSummary(issue.evidenceJson);
|
||||||
|
const count = summary.match(/(?:Detected|covers)\s+(\d+)/i)?.[1];
|
||||||
|
if (issue.reasonCode === 'PAGINATION_NOISE' && count) return `正文中检测到 ${count} 行独立页码。`;
|
||||||
|
if (issue.reasonCode === 'CHUNK_CONTEXT_BROKEN' && count) return `检测到 ${count} 个过短或依赖前后文的片段。`;
|
||||||
|
if (issue.reasonCode === 'CONTEXT_INCOMPLETE' && count) return `案例目前覆盖 5 类必要要素中的 ${count} 类。`;
|
||||||
|
return qualityReasonDescriptions[issue.reasonCode] || '系统命中了一项需要人工核对的质量规则。';
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityIssueAction(issue: QualityIssue) {
|
||||||
|
if (issue.gateType === 'HARD') return '请修订原资料并重新上传,解决后才能发布。';
|
||||||
|
return qualityAcknowledgements[issue.reasonCode] || '请对照原资料人工核查,确认不影响发布后再勾选接受。';
|
||||||
|
}
|
||||||
|
|
||||||
|
function qualityAcknowledgementText(reasonCode: string) {
|
||||||
|
return qualityAcknowledgements[reasonCode] || '已对照原资料完成核查,并确认该提示不影响本次发布。';
|
||||||
|
}
|
||||||
|
|
||||||
function formatLag(seconds: number) {
|
function formatLag(seconds: number) {
|
||||||
if (seconds < 60) return `${seconds} 秒`;
|
if (seconds < 60) return `${seconds} 秒`;
|
||||||
if (seconds < 3600) return `${Math.floor(seconds / 60)} 分钟`;
|
if (seconds < 3600) return `${Math.floor(seconds / 60)} 分钟`;
|
||||||
@@ -4003,10 +4285,54 @@ async function requestAssetPurge(asset: QualityAsset) {
|
|||||||
gap: 0 16px;
|
gap: 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quality-scope-field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-segmented {
|
||||||
|
justify-self: start;
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quality-acknowledgement-tip {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.reason-checks {
|
.reason-checks {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 4px 12px;
|
gap: 8px 12px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.el-checkbox {
|
||||||
|
align-items: flex-start;
|
||||||
|
height: auto;
|
||||||
|
min-height: 62px;
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 6px;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.reason-check-content {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
line-height: 1.45;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #303846;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
color: #667085;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1500px) {
|
@media (max-width: 1500px) {
|
||||||
@@ -4057,6 +4383,10 @@ async function requestAssetPurge(asset: QualityAsset) {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quality-scope-field {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.quality-metrics {
|
.quality-metrics {
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user