Commit dcdc9689 authored by David Yang's avatar David Yang

feat: 动态详情页

parent b52ff11f
This diff is collapsed.
...@@ -39,7 +39,7 @@ function transformSaveValue(newValue, fieldDef) { ...@@ -39,7 +39,7 @@ function transformSaveValue(newValue, fieldDef) {
return newValue; return newValue;
} }
export function useSubjectRecord(recordName, refreshKey, readonly) { export function useSubjectRecord(recordName, refreshKey, readonly, subjectName) {
const [remoteRecord, setRemoteRecord] = useState(null); const [remoteRecord, setRemoteRecord] = useState(null);
const [editableRecord, setEditableRecord] = useState(null); const [editableRecord, setEditableRecord] = useState(null);
const [subject, setSubject] = useState(null); const [subject, setSubject] = useState(null);
...@@ -67,9 +67,9 @@ export function useSubjectRecord(recordName, refreshKey, readonly) { ...@@ -67,9 +67,9 @@ export function useSubjectRecord(recordName, refreshKey, readonly) {
try { try {
const record = await loadRecord(recordName); const record = await loadRecord(recordName);
const subjectName = record?.__t || record?.subject?.name || record?.subject; const resolvedSubjectName = subjectName || record?.__t || record?.subject?.name || record?.subject;
const [subjectData, viewsData] = subjectName const [subjectData, viewsData] = resolvedSubjectName
? await Promise.all([loadSubject(subjectName), getSubjectView(subjectName)]) ? await Promise.all([loadSubject(resolvedSubjectName), getSubjectView(resolvedSubjectName)])
: [null, []]; : [null, []];
if (!cancelled && requestIdRef.current === requestId) { if (!cancelled && requestIdRef.current === requestId) {
...@@ -98,7 +98,7 @@ export function useSubjectRecord(recordName, refreshKey, readonly) { ...@@ -98,7 +98,7 @@ export function useSubjectRecord(recordName, refreshKey, readonly) {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [recordName, refreshKey]); }, [recordName, refreshKey, subjectName]);
const handleFieldUpdate = async (fieldPath, newValue, fieldDef) => { const handleFieldUpdate = async (fieldPath, newValue, fieldDef) => {
if (readonly || !editableRecord) { if (readonly || !editableRecord) {
......
...@@ -59,7 +59,8 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -59,7 +59,8 @@ const RecordDetailDrawer = (props, ref) => {
width = 860, width = 860,
open, open,
recordName, recordName,
setCustomComponentClose, subjectName,
onClose,
onDataChange, onDataChange,
readonly = false, readonly = false,
filterView = [], filterView = [],
...@@ -79,7 +80,7 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -79,7 +80,7 @@ const RecordDetailDrawer = (props, ref) => {
handleFieldUpdate, handleFieldUpdate,
isFieldDisabled, isFieldDisabled,
loading, loading,
} = useSubjectRecord(recordName, refreshKey, readonly); } = useSubjectRecord(recordName, refreshKey, readonly, subjectName);
useEffect(() => { useEffect(() => {
if (open) { if (open) {
...@@ -156,7 +157,7 @@ const RecordDetailDrawer = (props, ref) => { ...@@ -156,7 +157,7 @@ const RecordDetailDrawer = (props, ref) => {
]; ];
const closeDrawer = () => { const closeDrawer = () => {
setCustomComponentClose?.(); onClose?.();
}; };
return ( return (
......
import { countRecords, getSubjectView, loadRecord, loadSubject, queryRecords } from '@/services/DataService';
import {
buildViewColumns,
checkViewCondition,
formatFieldValue,
getFieldValue,
isViewAccessible,
normalizeText,
recordMatchesRestrictions,
sortRecordsByView,
} from '@/utils/subjectView';
export const ISSUE_RECTIFICATION_ARCHIVE_SUBJECT = 'issue_rectification_archive';
export const PROBLEM_RECTIFICATION_ARCHIVE_RECORDS_SUBJECT = 'problem_rectification_archive_records';
export function normalizeArchiveBatchRecord(record) {
const referenceRecords = Array.isArray(record?.metadata?.reference_records)
? record.metadata.reference_records
: [];
return {
id: record?._id || record?.name || '',
name: record?.name || '',
title: record?.title || record?.name || '归档批次',
archiveCount: referenceRecords.length,
referenceRecords,
raw: record,
};
}
export async function loadArchiveWorkbenchResources() {
const [archiveRecordsResult, archiveSubject, archiveViews] = await Promise.all([
queryRecords({
subject: ISSUE_RECTIFICATION_ARCHIVE_SUBJECT,
page: 0,
page_size: 1000,
sort: '-updated_at -created_at',
mode: 'full',
}),
loadSubject(ISSUE_RECTIFICATION_ARCHIVE_SUBJECT),
getSubjectView(ISSUE_RECTIFICATION_ARCHIVE_SUBJECT),
]);
const records = (archiveRecordsResult?.records || []).map(normalizeArchiveBatchRecord);
return {
records,
subject: archiveSubject,
views: archiveViews || [],
};
}
export function buildArchiveGridViews(views = []) {
const gridViews = views
.filter((view) => view.type === 'grid')
.filter((view) => isViewAccessible(view));
if (gridViews.length > 0) {
return gridViews;
}
return [
{
title: '全部批次',
type: 'grid',
name: 'all',
settings: {},
},
];
}
export function buildArchiveColumns(view, subject) {
return buildViewColumns(view, subject).filter((column) => column.field !== 'title' || !column.hide);
}
export function buildArchiveConfiguredView(records, filters, gridView, subject) {
const keyword = normalizeText(filters?.keyword).toLowerCase();
const columns = buildArchiveColumns(gridView, subject);
const filteredRecords = records.filter((record) => {
const rawRecord = record.raw || record;
if (!recordMatchesRestrictions(rawRecord, gridView?.settings?.restrictions)) {
return false;
}
if (
!checkViewCondition(
{
relationship: gridView?.settings?.filters?.relationship || 'all',
rules: gridView?.settings?.filters?.conditions || [],
},
rawRecord,
)
) {
return false;
}
if (!keyword) {
return true;
}
const searchText = columns
.map((column) => formatFieldValue(getFieldValue(rawRecord, column.field), column.fieldDef))
.join(' ')
.toLowerCase();
return searchText.includes(keyword);
});
return {
filteredRecords: sortRecordsByView(filteredRecords, gridView),
columns,
};
}
export function buildArchiveViewCountMap(records, filters, views, subject) {
return views.reduce((counts, view) => {
counts[view.name] = buildArchiveConfiguredView(records, filters, view, subject).filteredRecords.length;
return counts;
}, {});
}
export async function loadArchiveDetailMetrics(recordName) {
const batchRecord = await loadRecord(recordName);
const referenceRecordIds = Array.isArray(batchRecord?.metadata?.reference_records)
? batchRecord.metadata.reference_records
.map((item) => (typeof item === 'object' ? item._id || item.name : item))
.filter(Boolean)
: [];
const detailCount = referenceRecordIds.length > 0
? await countRecords({
subject: PROBLEM_RECTIFICATION_ARCHIVE_RECORDS_SUBJECT,
filter: {
_id: { $in: referenceRecordIds },
},
})
: { count: 0 };
return {
batchRecord,
detailCount: detailCount?.count || 0,
referenceRecordIds,
};
}
import ModuleEmptyState from '../ModuleEmptyState'; import RecordWorkbench from '@/components/record-workbench';
import RecordDetailDrawer from '@/pages/RecordDetailDrawer';
import { useCallback, useEffect, useMemo, useState } from 'react';
import {
buildArchiveConfiguredView,
buildArchiveGridViews,
buildArchiveViewCountMap,
ISSUE_RECTIFICATION_ARCHIVE_SUBJECT,
loadArchiveWorkbenchResources,
} from './archiveWorkbenchService';
export default function ProblemRectificationArchivePage() { export default function ProblemRectificationArchivePage() {
const [records, setRecords] = useState([]);
const [subject, setSubject] = useState(null);
const [views, setViews] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [filters, setFilters] = useState({
keyword: '',
});
const [activeViewName, setActiveViewName] = useState('');
const [selectedIds, setSelectedIds] = useState([]);
const reloadList = useCallback(async () => {
setLoading(true);
setError('');
try {
const resources = await loadArchiveWorkbenchResources();
setRecords(resources.records || []);
setSubject(resources.subject || null);
setViews(resources.views || []);
} catch (requestError) {
setRecords([]);
setSubject(null);
setViews([]);
setError(requestError?.message || '归档批次列表加载失败,请稍后重试。');
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
reloadList();
}, [reloadList]);
const gridViews = useMemo(() => buildArchiveGridViews(views), [views]);
useEffect(() => {
if (gridViews.length === 0) {
setActiveViewName('');
return;
}
if (!gridViews.some((view) => view.name === activeViewName)) {
setActiveViewName(gridViews[0].name);
}
}, [gridViews, activeViewName]);
const activeGridView = useMemo(
() => gridViews.find((view) => view.name === activeViewName) || gridViews[0] || null,
[gridViews, activeViewName],
);
const configuredView = useMemo(
() => buildArchiveConfiguredView(records, filters, activeGridView, subject),
[records, filters, activeGridView, subject],
);
const filteredRecords = configuredView.filteredRecords || [];
useEffect(() => {
const visibleIds = new Set(filteredRecords.map((item) => item.id));
setSelectedIds((current) => current.filter((id) => visibleIds.has(id)));
}, [filteredRecords]);
const gridViewCounts = useMemo(
() => buildArchiveViewCountMap(records, filters, gridViews, subject),
[records, filters, gridViews, subject],
);
const summaryItems = useMemo(
() => [
{
label: '归档批次数',
value: filteredRecords.length,
hint: activeGridView?.title ? `${activeGridView.title} 视图` : '当前筛选结果',
tone: 'amber',
},
{
label: '已归档明细数',
value: filteredRecords.reduce((total, item) => total + (item.archiveCount || 0), 0),
hint: '按当前视图统计关联快照数量',
tone: 'emerald',
},
{
label: '当前已选批次',
value: selectedIds.length,
hint: '支持后续批量操作扩展',
tone: 'rose',
},
],
[filteredRecords, selectedIds.length, activeGridView],
);
return ( return (
<ModuleEmptyState <RecordWorkbench
title="往期材料归档" summaryItems={summaryItems}
description="查看归档批次和归档明细。" views={gridViews.map((view) => ({
...view,
count: gridViewCounts[view.name] || 0,
}))}
activeViewName={activeGridView?.name || ''}
onViewChange={(viewName) => {
setActiveViewName(viewName);
setSelectedIds([]);
}}
filters={filters}
filterSchema={[
{
key: 'keyword',
label: '关键词搜索',
type: 'text',
placeholder: '搜索归档批次标题、编码或关联字段...',
},
]}
onFiltersChange={(key, value) => setFilters((current) => ({ ...current, [key]: value }))}
onClearFilters={() => {
setFilters({ keyword: '' });
setSelectedIds([]);
}}
title="归档批次"
description={`当前视图显示 ${filteredRecords.length} 条批次记录,已选中 ${selectedIds.length} 条。`}
columns={configuredView.columns || []}
records={filteredRecords}
selectedIds={selectedIds}
onSelectionChange={setSelectedIds}
toolbarActions={[
{
key: 'archive-export',
label: '导出批次',
variant: 'outline',
disabled: true,
onClick: () => {},
},
{
key: 'archive-compare',
label: '批次对比',
variant: 'outline',
disabled: true,
onClick: () => {},
},
]}
rowActions={[
{
key: 'view-detail',
label: '查看详情',
variant: 'plain',
onClick: ({ openDetail, record }) => openDetail(record),
},
]}
detailPanel={{
getRecordName: (record) => record.name || record.id,
onDirtyClose: reloadList,
render: ({ open, recordName, onClose, onDataChange }) => (
<RecordDetailDrawer
open={open}
recordName={recordName}
subjectName={ISSUE_RECTIFICATION_ARCHIVE_SUBJECT}
onClose={onClose}
onDataChange={onDataChange}
preferredViewName={activeGridView?.title || activeGridView?.name}
/>
),
}}
states={{
loading,
error,
loadingTitle: '正在加载归档批次',
loadingDescription: '正在读取归档对象、视图配置和批次记录,请稍候。',
emptyTitle: '暂无归档批次',
emptyDescription: '当前还没有归档批次记录,后续完成“一键归档”后会在这里展示。',
}}
renderCell={({ record, column }) => {
if (column.field === 'metadata.reference_records') {
return (
<div className="whitespace-normal break-words text-sm text-slate-700">
{record.archiveCount || 0} 条明细
</div>
);
}
return column.render ? column.render(record, column) : null;
}}
/> />
); );
} }
...@@ -64,6 +64,13 @@ ...@@ -64,6 +64,13 @@
因此前端新增字段展示、部门视图调整、列顺序变化时,应优先改 Subject View 配置,而不是直接修改页面常量。 因此前端新增字段展示、部门视图调整、列顺序变化时,应优先改 Subject View 配置,而不是直接修改页面常量。
当前项目实现约定补充:
- `problem_rectification` 与 `issue_rectification_archive` 列表页统一复用 `RecordWorkbench` 作为“结构化表格 + 工具栏 + 详情抽屉入口”的工作台容器
- 页面本身只负责提供 subject 对应的数据加载、统计口径、筛选 schema、行操作与批量操作
- 详情侧栏统一复用 `RecordDetailDrawer`,通过 `recordName`、`subjectName`、`preferredViewName` 驱动,不再在各页面重复实现抽屉骨架
- 新增同类列表页时,优先复用工作台与详情抽屉组合,避免再次复制问题整改页 JSX
### 3.1 目标页面拆分 ### 3.1 目标页面拆分
为便于当前项目直接重构,建议把模块拆成以下 4 个页面入口: 为便于当前项目直接重构,建议把模块拆成以下 4 个页面入口:
...@@ -229,8 +236,20 @@ ...@@ -229,8 +236,20 @@
- 问题类型 - 问题类型
- 反馈单位 - 反馈单位
- 问题涉及业务领域 - 问题涉及业务领域
3. 可预先填写预计整改完成时间 3. 根据 2026 年整改驾驶舱要求,补录扩展治理字段:
4. 初始时可不指定填报人、复核人 - 问题后果:是否造成经济损失、对应经济损失、是否造成外部影响、处罚机构、是否涉及员工问责、问责人数
- 问题画像:问题综合严重程度、问题一级至四级分类、问题原因
- 整改质效:整改时间评估、整改证明材料质量、是否涉及制度修订、制度名称、是否涉及系统建设、系统名称、是否为往期发现问题、问题是否重复出现、综合整改评定等级
- 责任识别:主责部门、实际整改完成时间、实际检查完成日期
4. 可预先填写预计整改完成时间
5. 初始时可不指定填报人、复核人
推荐新交互:
- 工作台筛选区增加严重程度、整改评级、主责部门、问题来源、问题原因、重点关注等维度
- 列表标题列增加风险标签,优先暴露“高严重程度”“重点后果问题”“已逾期”等重点状态
- 新增弹窗除基础字段外,还应补充严重程度、分类、原因和整改质效字段,避免驾驶舱后续无数据可用
- 摘要卡优先展示高严重程度、重点后果问题、往期/重复问题、逾期未完成、整改评级偏低等指标
### 7.2 发起整改填报 ### 7.2 发起整改填报
...@@ -369,12 +388,12 @@ ...@@ -369,12 +388,12 @@
## 9. 驾驶舱展示规则 ## 9. 驾驶舱展示规则
旧驾驶舱页面以 `problem_rectification` 为唯一数据源,主要分为三部分: 当前驾驶舱页面以 `problem_rectification` 为唯一数据源,围绕 2026 年整改驾驶舱需求分为四层:
1. 按反馈单位分组的统计看板 1. 宏观总览层
2. 临近整改问题列表 2. 问题分析与风险定位层
3. 整改逾期问题列表 3. 问题责任追踪与执行层
4. 问题涉及业务领域词云 4. 审计整改质效层
### 9.1 顶部标签分组 ### 9.1 顶部标签分组
...@@ -399,47 +418,78 @@ ...@@ -399,47 +418,78 @@
- 页面文案可以更贴近业务,例如“资产管理部”与“鑫沅资产”统一命名 - 页面文案可以更贴近业务,例如“资产管理部”与“鑫沅资产”统一命名
- 分组不必写死在组件内部,建议抽为配置映射 - 分组不必写死在组件内部,建议抽为配置映射
推荐新实现: 当前实现约定:
- 顶部继续保留按 `feedback_unit` 分组的轻量 tabs
- tabs 下方分为 KPI 卡片、ECharts 图表区和重点问题清单区
- 驾驶舱仅负责统计展示,不承接编辑动作
- 如果线上 Subject 后续把新增字段从 `text` 升级为 `select` / `date` / `boolean`,驾驶舱统计口径无需调整,只需复用标准字段值
### 9.2 宏观总览层
当前 KPI 统计口径:
- 问题总数:当前分组内问题总数
- 已完成整改数:`rectification_progress` 为 `已整改` 或 `已优化`
- 整改完成率:`已完成整改数 / 问题总数`
- 逾期未完成数:`the_time_of_rectification` 早于当前日期且未闭环的问题数
- 逾期率:`逾期未完成数 / 问题总数`
- 顶部使用轻量 tabs 整改进度分布:
- tabs 下方展示 KPI 卡片
- 页面中下部拆成“临近整改”“整改逾期”“问题领域分布”三个区块
- 若词云阅读性差,可直接换成条形排名,不影响业务含义
### 9.2 统计指标口径 - 以 `rectification_progress` 为维度统计数量和占比
- 当前页面使用环形图表达,不再沿用旧版折线趋势表达
旧 `MainPanel` 已确认的统计口径: ### 9.3 问题分析与风险定位层
- 已整改问题:`rectification_progress` 为 `已整改` 或 `已优化` 当前图表与字段映射:
- 问题数量:当前分组内问题总数
- 历史问题累计整改率:
`(已整改或已优化数量 / 问题总数) * 100`
- 本年度问题整改率:
当年 `check_the_implementation_time` 所属问题中,已整改或已优化的占比
其中“当年”是通过 `check_the_implementation_time` 中提取四位年份数字判断,不是通过日期字段直接计算。 - 问题类型 / 业务领域分布:优先使用 `problem_category_level_1` 至 `problem_category_level_4` 生成矩形树图;若分类未填写,则回退到 `related_issue_domain`
- 问题根本原因分析:使用 `problem_root_cause` 聚合
- 问题来源对比:以 `problem_from` 为横轴,并按 `inspect_the_implementing_unit` 做堆叠比较
- 重点问题集中展示:
- 经济损失问题:`has_economic_loss` + `economic_loss_amount`
- 外部影响问题:`has_external_impact` + `penalty_organization`
- 员工问责问题:`has_employee_accountability` + `accountability_person_count`
- 重点问题清单中同时展示 `problem_severity_level`
### 9.3 临近整改问题 ### 9.4 问题责任追踪与执行层
筛选逻辑: 当前图表与字段映射:
- `the_time_of_rectification` 在当前时间之后 - 逾期问题排行:按 `responsible_department` 聚合逾期未完成问题数
- `the_time_of_rectification` 在未来 1 个月内 - 部门整改责任矩阵:
- `rectification_progress !== "已整改"` - X 轴:责任部门问题总数
- Y 轴:整改完成率
- 气泡大小:逾期问题数
- 整改时效分析散点图:
- X 轴:`the_time_of_rectification - inspection_completed_at`
- Y 轴:`actual_rectification_completed_at - inspection_completed_at`
- 仅统计同时填写了预计整改时间、实际整改完成时间、实际检查完成日期且天数非负的问题
### 9.4 整改逾期问题 当前逾期 / 延期判定说明:
筛选逻辑: - 当前项目线上尚无稳定使用的显式“是否延期”字段,因此页面暂以三类规则综合判断:
- 整改进度文案包含“延期”
- 实际整改完成时间晚于预计整改完成时间
- 截止当前日期预计整改完成时间已到且问题尚未闭环
- 其中 KPI 和逾期排行的“逾期未完成”使用第三条规则
- `the_time_of_rectification` 早于当前时间 ### 9.5 审计整改质效层
- `rectification_progress !== "已整改"`
### 9.5 问题涉及业务领域统计 当前图表与字段映射:
- 使用 `related_issue_domain` 做聚合统计 - 问题整改时效:按责任部门计算 `rectification_timeliness_level` 平均分,低=1、中=2、高=3
- 旧应用通过 `subjectSearch('problem_rectification', ...)` 获取 bucket - 问题整改质量:按责任部门计算 `rectification_material_quality_level` 平均分,低=1、中=2、高=3
- 新项目可以保留词云,也可以替换成更轻量的条形列表或标签热度图 - 问题整改助力总结:
- 如果追求性能和可读性,优先推荐横向条形图或排名卡片,而不是强依赖词云 - 制度修订数量:`involves_policy_revision = 是`
- 系统建设数量:`involves_system_construction = 是`
- 风险-质量热力图:
- X 轴:`problem_severity_level`
- Y 轴:`rectification_overall_rating`
- 部门综合整改评定等级排行:
- 以责任部门为维度统计 `rectification_overall_rating` 的低/中/高占比
- 按高评级占比降序展示
--- ---
......
...@@ -125,6 +125,30 @@ type Record = { ...@@ -125,6 +125,30 @@ type Record = {
| 最终审批状态 | `final_audit_status` | select | 否 | 否 | 否 | | 最终审批状态 | `final_audit_status` | select | 否 | 否 | 否 |
| 备注 | `remark` | long_text | 否 | 否 | 否 | | 备注 | `remark` | long_text | 否 | 否 | 否 |
| 整改证明材料 | `rectification_proof_materials` | file | 否 | 否 | 否 | | 整改证明材料 | `rectification_proof_materials` | file | 否 | 否 | 否 |
| 是否造成经济损失 | `has_economic_loss` | text | 否 | 否 | 否 |
| 对应经济损失 | `economic_loss_amount` | text | 否 | 否 | 否 |
| 是否造成外部影响 | `has_external_impact` | text | 否 | 否 | 否 |
| 处罚机构 | `penalty_organization` | text | 否 | 否 | 否 |
| 是否涉及员工问责 | `has_employee_accountability` | text | 否 | 否 | 否 |
| 问责人数 | `accountability_person_count` | text | 否 | 否 | 否 |
| 问题综合严重程度 | `problem_severity_level` | text | 否 | 否 | 否 |
| 问题一级分类 | `problem_category_level_1` | text | 否 | 否 | 否 |
| 问题二级分类 | `problem_category_level_2` | text | 否 | 否 | 否 |
| 问题三级分类 | `problem_category_level_3` | text | 否 | 否 | 否 |
| 问题四级分类 | `problem_category_level_4` | text | 否 | 否 | 否 |
| 问题原因 | `problem_root_cause` | text | 否 | 否 | 否 |
| 整改时间评估 | `rectification_timeliness_level` | text | 否 | 否 | 否 |
| 整改证明材料质量 | `rectification_material_quality_level` | text | 否 | 否 | 否 |
| 是否涉及制度修订 | `involves_policy_revision` | text | 否 | 否 | 否 |
| 制度名称 | `policy_revision_name` | text | 否 | 否 | 否 |
| 是否涉及系统建设 | `involves_system_construction` | text | 否 | 否 | 否 |
| 系统名称 | `system_construction_name` | text | 否 | 否 | 否 |
| 是否为往期发现问题 | `is_historical_problem` | text | 否 | 否 | 否 |
| 问题是否重复出现 | `is_repeated_problem` | text | 否 | 否 | 否 |
| 综合整改评定等级 | `rectification_overall_rating` | text | 否 | 否 | 否 |
| 实际整改完成时间 | `actual_rectification_completed_at` | text | 否 | 否 | 否 |
| 实际检查完成日期 | `inspection_completed_at` | text | 否 | 否 | 否 |
| 主责部门 | `primary_responsible_department` | text | 否 | 否 | 否 |
### 3.1.1 字段说明 ### 3.1.1 字段说明
...@@ -133,6 +157,7 @@ type Record = { ...@@ -133,6 +157,7 @@ type Record = {
- `audit_status` 用于复核阶段结果,旧应用已使用 `已审批`、`已驳回` - `audit_status` 用于复核阶段结果,旧应用已使用 `已审批`、`已驳回`
- `final_audit_status` 用于最终审批阶段结果,旧应用已使用 `已审批`、`已驳回` - `final_audit_status` 用于最终审批阶段结果,旧应用已使用 `已审批`、`已驳回`
- `rectification_progress` 在旧驾驶舱统计中已使用 `已整改`、`已优化` 两个值判断“已整改问题” - `rectification_progress` 在旧驾驶舱统计中已使用 `已整改`、`已优化` 两个值判断“已整改问题”
- 新增驾驶舱扩展字段当前在线上 Subject 中大多仍为 `text` 类型,前端工作台按标准值引导录入,用于支持后续严重程度、问题分类、问题原因和整改质效分析
- 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码 - 其余 `select` 类型的可选值必须以线上 Subject 配置中的 `settings.options` 为准,禁止在页面内硬编码
### 3.1.2 metadata 示例 ### 3.1.2 metadata 示例
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment