Commit 00b6e86c authored by drigle's avatar drigle

feat: group model batches by model

parent d99022c1
{
"name": "model_analysis",
"version": "1.0.2",
"version": "1.0.3",
"build": "abc",
"description": "merge master",
"date": "2025-07-14 19:27:20",
"author": "yang"
"author": "yang",
"spa": true
}
-- MySQL 5.7+ fixture data for the model-monitor source connector.
-- This script is repeatable: existing fixture rows are updated by their stable keys.
-- Fixture coverage: 4 models, 4 batches per model, 2 calendar years.
CREATE TABLE IF NOT EXISTS ANALYZE_MXVIEW_INFO (
ID VARCHAR(64) NOT NULL COMMENT '来源模型 ID',
......@@ -81,6 +82,22 @@ CREATE TABLE IF NOT EXISTS ANALYZE_MODEL_DATA_MX_411 (
KEY IDX_ANALYZE_DATA_411_BATCH (MODEL_RUN_BATCH_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='授信敞口监测模型结果';
CREATE TABLE IF NOT EXISTS ANALYZE_MODEL_DATA_MX_420 (
MODEL_ID VARCHAR(64) NOT NULL COMMENT '来源模型 ID',
MODEL_NAME VARCHAR(128) NOT NULL COMMENT '模型技术名称',
MODEL_RUN_BATCH_ID VARCHAR(64) NOT NULL COMMENT '来源跑批 ID',
MODEL_RUN_BATCH_DATE DATETIME(3) NOT NULL COMMENT '来源跑批时间',
REMARK VARCHAR(1000) NULL COMMENT '来源备注',
LOAN_ID VARCHAR(64) NOT NULL COMMENT '贷款编号',
CUSTOMER_ID VARCHAR(64) NOT NULL COMMENT '客户编号',
OUTSTANDING_AMOUNT DECIMAL(18, 2) NOT NULL COMMENT '逾期贷款余额',
DUE_DATE DATETIME(3) NOT NULL COMMENT '应还日期',
OVERDUE_DAYS INT NOT NULL COMMENT '逾期天数',
IS_OVERDUE TINYINT(1) NOT NULL COMMENT '是否逾期',
UNIQUE KEY UK_ANALYZE_DATA_420_ROW (MODEL_RUN_BATCH_ID, LOAN_ID),
KEY IDX_ANALYZE_DATA_420_BATCH (MODEL_RUN_BATCH_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='贷款逾期监测模型结果';
START TRANSACTION;
INSERT INTO ANALYZE_MXVIEW_INFO (
......@@ -90,7 +107,8 @@ INSERT INTO ANALYZE_MXVIEW_INFO (
) VALUES
('430', 'AML_CUSTOMER_RISK', '客户洗钱风险模型', '2026-08-26 08:00:00.000', 'enabled', 'applied', 'high', '12', '风险分析'),
('412', 'TRANSACTION_ALERT', '异常交易预警模型', '2026-08-25 08:00:00.000', 'enabled', 'applied', 'medium', '12', '风险分析'),
('411', 'CREDIT_EXPOSURE', '授信敞口监测模型', '2026-08-24 08:00:00.000', 'enabled', 'applied', 'medium', '13', '合规分析')
('411', 'CREDIT_EXPOSURE', '授信敞口监测模型', '2026-08-24 08:00:00.000', 'enabled', 'applied', 'medium', '13', '合规分析'),
('420', 'LOAN_OVERDUE', '贷款逾期监测模型', '2026-09-01 08:00:00.000', 'enabled', 'applied', 'high', '13', '合规分析')
ON DUPLICATE KEY UPDATE
MODEL_NAME = VALUES(MODEL_NAME),
MODEL_ZH_NAME = VALUES(MODEL_ZH_NAME),
......@@ -109,12 +127,19 @@ INSERT INTO ANALYZE_MODEL_RUN_BATCH_INFO (
('690', '430', 'AML_CUSTOMER_RISK', '2026-08-26 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_430', '2026 年 8 月批次', 5, 5, 0, 0, 'success', NULL),
('652', '430', 'AML_CUSTOMER_RISK', '2026-07-31 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_430', '2026 年 7 月批次', 5, 3, 1, 1, 'success', NULL),
('590', '430', 'AML_CUSTOMER_RISK', '2025-12-31 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_430', '2025 年 12 月批次', 5, 0, 0, 5, 'success', NULL),
('691', '430', 'AML_CUSTOMER_RISK', '2026-09-01 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_430', '2026 年 9 月批次', 5, 5, 0, 0, 'success', NULL),
('670', '412', 'TRANSACTION_ALERT', '2026-08-25 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_412', '2026 年第 35 周', 7, 7, 0, 0, 'success', NULL),
('669', '412', 'TRANSACTION_ALERT', '2026-08-18 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_412', '2026 年第 34 周', 7, 4, 2, 1, 'success', NULL),
('570', '412', 'TRANSACTION_ALERT', '2025-12-29 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_412', '2025 年第 52 周', 7, 0, 0, 7, 'success', NULL),
('671', '412', 'TRANSACTION_ALERT', '2026-09-01 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_412', '2026 年第 36 周', 7, 7, 0, 0, 'success', NULL),
('630', '411', 'CREDIT_EXPOSURE', '2026-08-24 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_411', '2026 年 8 月批次', 4, 4, 0, 0, 'success', NULL),
('629', '411', 'CREDIT_EXPOSURE', '2026-06-30 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_411', '2026 年第二季度', 4, 2, 1, 1, 'success', NULL),
('530', '411', 'CREDIT_EXPOSURE', '2025-12-31 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_411', '2025 年第四季度', 4, 0, 0, 4, 'success', NULL)
('530', '411', 'CREDIT_EXPOSURE', '2025-12-31 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_411', '2025 年第四季度', 4, 0, 0, 4, 'success', NULL),
('631', '411', 'CREDIT_EXPOSURE', '2026-09-01 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_411', '2026 年 9 月批次', 4, 4, 0, 0, 'success', NULL),
('720', '420', 'LOAN_OVERDUE', '2026-09-01 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_420', '2026 年 9 月第一批', 4, 4, 0, 0, 'success', NULL),
('719', '420', 'LOAN_OVERDUE', '2026-08-25 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_420', '2026 年 8 月第四批', 4, 3, 1, 0, 'success', NULL),
('718', '420', 'LOAN_OVERDUE', '2026-08-18 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_420', '2026 年 8 月第三批', 4, 2, 1, 1, 'success', NULL),
('717', '420', 'LOAN_OVERDUE', '2025-12-22 08:00:00.000', 'ANALYZE_MODEL_DATA_MX_420', '2025 年历史批次', 4, 1, 1, 2, 'success', NULL)
ON DUPLICATE KEY UPDATE
MODEL_ID = VALUES(MODEL_ID),
MODEL_NAME = VALUES(MODEL_NAME),
......@@ -147,7 +172,12 @@ INSERT INTO ANALYZE_MODEL_DATA_MX_430 (
('430', 'AML_CUSTOMER_RISK', '590', '2025-12-31 08:00:00.000', NULL, 'C0002', '2025-12-04 00:00:00.000', '海川商贸', '高', 80.00, 1),
('430', 'AML_CUSTOMER_RISK', '590', '2025-12-31 08:00:00.000', NULL, 'C0003', '2025-12-05 00:00:00.000', '恒信科技', '中', 71.00, 0),
('430', 'AML_CUSTOMER_RISK', '590', '2025-12-31 08:00:00.000', NULL, 'C0004', '2025-12-06 00:00:00.000', '嘉禾供应链', '中', 62.00, 0),
('430', 'AML_CUSTOMER_RISK', '590', '2025-12-31 08:00:00.000', NULL, 'C0005', '2025-12-07 00:00:00.000', '远景投资', '低', 53.00, 0)
('430', 'AML_CUSTOMER_RISK', '590', '2025-12-31 08:00:00.000', NULL, 'C0005', '2025-12-07 00:00:00.000', '远景投资', '低', 53.00, 0),
('430', 'AML_CUSTOMER_RISK', '691', '2026-09-01 08:00:00.000', NULL, 'C0001', '2026-09-01 00:00:00.000', '华远实业', '高', 94.00, 1),
('430', 'AML_CUSTOMER_RISK', '691', '2026-09-01 08:00:00.000', NULL, 'C0002', '2026-09-02 00:00:00.000', '海川商贸', '高', 85.00, 1),
('430', 'AML_CUSTOMER_RISK', '691', '2026-09-01 08:00:00.000', NULL, 'C0003', '2026-09-03 00:00:00.000', '恒信科技', '中', 76.00, 0),
('430', 'AML_CUSTOMER_RISK', '691', '2026-09-01 08:00:00.000', NULL, 'C0004', '2026-09-04 00:00:00.000', '嘉禾供应链', '中', 67.00, 0),
('430', 'AML_CUSTOMER_RISK', '691', '2026-09-01 08:00:00.000', NULL, 'C0005', '2026-09-05 00:00:00.000', '远景投资', '低', 58.00, 0)
ON DUPLICATE KEY UPDATE
MODEL_ID = VALUES(MODEL_ID), MODEL_NAME = VALUES(MODEL_NAME),
MODEL_RUN_BATCH_DATE = VALUES(MODEL_RUN_BATCH_DATE), REMARK = VALUES(REMARK),
......@@ -179,7 +209,14 @@ INSERT INTO ANALYZE_MODEL_DATA_MX_412 (
('412', 'TRANSACTION_ALERT', '570', '2025-12-29 08:00:00.000', NULL, 'AL52004', '监测账户 4', 385500.00, '2025-12-25 03:30:00.000', '交易对手集中度异常'),
('412', 'TRANSACTION_ALERT', '570', '2025-12-29 08:00:00.000', NULL, 'AL52005', '监测账户 5', 459000.00, '2025-12-26 04:30:00.000', '短期高频大额交易'),
('412', 'TRANSACTION_ALERT', '570', '2025-12-29 08:00:00.000', NULL, 'AL52006', '监测账户 6', 532500.00, '2025-12-27 05:30:00.000', '交易对手集中度异常'),
('412', 'TRANSACTION_ALERT', '570', '2025-12-29 08:00:00.000', NULL, 'AL52007', '监测账户 7', 606000.00, '2025-12-28 06:30:00.000', '短期高频大额交易')
('412', 'TRANSACTION_ALERT', '570', '2025-12-29 08:00:00.000', NULL, 'AL52007', '监测账户 7', 606000.00, '2025-12-28 06:30:00.000', '短期高频大额交易'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36001', '监测账户 1', 190000.00, '2026-08-25 00:30:00.000', '短期高频大额交易'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36002', '监测账户 2', 263500.00, '2026-08-26 01:30:00.000', '交易对手集中度异常'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36003', '监测账户 3', 337000.00, '2026-08-27 02:30:00.000', '短期高频大额交易'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36004', '监测账户 4', 410500.00, '2026-08-28 03:30:00.000', '交易对手集中度异常'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36005', '监测账户 5', 484000.00, '2026-08-29 04:30:00.000', '短期高频大额交易'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36006', '监测账户 6', 557500.00, '2026-08-30 05:30:00.000', '交易对手集中度异常'),
('412', 'TRANSACTION_ALERT', '671', '2026-09-01 08:00:00.000', NULL, 'AL36007', '监测账户 7', 631000.00, '2026-08-31 06:30:00.000', '短期高频大额交易')
ON DUPLICATE KEY UPDATE
MODEL_ID = VALUES(MODEL_ID), MODEL_NAME = VALUES(MODEL_NAME),
MODEL_RUN_BATCH_DATE = VALUES(MODEL_RUN_BATCH_DATE), REMARK = VALUES(REMARK),
......@@ -202,7 +239,11 @@ INSERT INTO ANALYZE_MODEL_DATA_MX_411 (
('411', 'CREDIT_EXPOSURE', '530', '2025-12-31 08:00:00.000', NULL, 'ORG1', '2025-Q4', '总行营业部', 10800000.00, 7560000.00, 0),
('411', 'CREDIT_EXPOSURE', '530', '2025-12-31 08:00:00.000', NULL, 'ORG2', '2025-Q4', '华东分行', 13950000.00, 11160000.00, 0),
('411', 'CREDIT_EXPOSURE', '530', '2025-12-31 08:00:00.000', NULL, 'ORG3', '2025-Q4', '华南分行', 17100000.00, 15390000.00, 0),
('411', 'CREDIT_EXPOSURE', '530', '2025-12-31 08:00:00.000', NULL, 'ORG4', '2025-Q4', '西部中心', 20250000.00, 20452500.00, 1)
('411', 'CREDIT_EXPOSURE', '530', '2025-12-31 08:00:00.000', NULL, 'ORG4', '2025-Q4', '西部中心', 20250000.00, 20452500.00, 1),
('411', 'CREDIT_EXPOSURE', '631', '2026-09-01 08:00:00.000', NULL, 'ORG1', '2026-09', '总行营业部', 12200000.00, 8784000.00, 0),
('411', 'CREDIT_EXPOSURE', '631', '2026-09-01 08:00:00.000', NULL, 'ORG2', '2026-09', '华东分行', 15750000.00, 12915000.00, 0),
('411', 'CREDIT_EXPOSURE', '631', '2026-09-01 08:00:00.000', NULL, 'ORG3', '2026-09', '华南分行', 19300000.00, 17756000.00, 0),
('411', 'CREDIT_EXPOSURE', '631', '2026-09-01 08:00:00.000', NULL, 'ORG4', '2026-09', '西部中心', 22850000.00, 23377000.00, 1)
ON DUPLICATE KEY UPDATE
MODEL_ID = VALUES(MODEL_ID), MODEL_NAME = VALUES(MODEL_NAME),
MODEL_RUN_BATCH_DATE = VALUES(MODEL_RUN_BATCH_DATE), REMARK = VALUES(REMARK),
......@@ -210,4 +251,31 @@ ON DUPLICATE KEY UPDATE
CREDIT_LIMIT = VALUES(CREDIT_LIMIT), USED_AMOUNT = VALUES(USED_AMOUNT),
OVER_LIMIT = VALUES(OVER_LIMIT);
INSERT INTO ANALYZE_MODEL_DATA_MX_420 (
MODEL_ID, MODEL_NAME, MODEL_RUN_BATCH_ID, MODEL_RUN_BATCH_DATE, REMARK,
LOAN_ID, CUSTOMER_ID, OUTSTANDING_AMOUNT, DUE_DATE, OVERDUE_DAYS, IS_OVERDUE
) VALUES
('420', 'LOAN_OVERDUE', '720', '2026-09-01 08:00:00.000', NULL, 'LN72001', 'C0001', 850000.00, '2026-08-15 00:00:00.000', 17, 1),
('420', 'LOAN_OVERDUE', '720', '2026-09-01 08:00:00.000', NULL, 'LN72002', 'C0002', 420000.00, '2026-08-20 00:00:00.000', 12, 1),
('420', 'LOAN_OVERDUE', '720', '2026-09-01 08:00:00.000', NULL, 'LN72003', 'C0003', 275000.00, '2026-09-01 00:00:00.000', 0, 0),
('420', 'LOAN_OVERDUE', '720', '2026-09-01 08:00:00.000', NULL, 'LN72004', 'C0004', 960000.00, '2026-08-01 00:00:00.000', 31, 1),
('420', 'LOAN_OVERDUE', '719', '2026-08-25 08:00:00.000', NULL, 'LN71901', 'C0001', 835000.00, '2026-08-08 00:00:00.000', 17, 1),
('420', 'LOAN_OVERDUE', '719', '2026-08-25 08:00:00.000', NULL, 'LN71902', 'C0002', 410000.00, '2026-08-15 00:00:00.000', 10, 1),
('420', 'LOAN_OVERDUE', '719', '2026-08-25 08:00:00.000', NULL, 'LN71903', 'C0003', 265000.00, '2026-08-25 00:00:00.000', 0, 0),
('420', 'LOAN_OVERDUE', '719', '2026-08-25 08:00:00.000', NULL, 'LN71904', 'C0004', 945000.00, '2026-07-28 00:00:00.000', 28, 1),
('420', 'LOAN_OVERDUE', '718', '2026-08-18 08:00:00.000', NULL, 'LN71801', 'C0001', 820000.00, '2026-08-01 00:00:00.000', 17, 1),
('420', 'LOAN_OVERDUE', '718', '2026-08-18 08:00:00.000', NULL, 'LN71802', 'C0002', 400000.00, '2026-08-08 00:00:00.000', 10, 1),
('420', 'LOAN_OVERDUE', '718', '2026-08-18 08:00:00.000', NULL, 'LN71803', 'C0003', 255000.00, '2026-08-18 00:00:00.000', 0, 0),
('420', 'LOAN_OVERDUE', '718', '2026-08-18 08:00:00.000', NULL, 'LN71804', 'C0004', 930000.00, '2026-07-21 00:00:00.000', 28, 1),
('420', 'LOAN_OVERDUE', '717', '2025-12-22 08:00:00.000', NULL, 'LN71701', 'C0001', 805000.00, '2025-12-05 00:00:00.000', 17, 1),
('420', 'LOAN_OVERDUE', '717', '2025-12-22 08:00:00.000', NULL, 'LN71702', 'C0002', 390000.00, '2025-12-12 00:00:00.000', 10, 1),
('420', 'LOAN_OVERDUE', '717', '2025-12-22 08:00:00.000', NULL, 'LN71703', 'C0003', 245000.00, '2025-12-22 00:00:00.000', 0, 0),
('420', 'LOAN_OVERDUE', '717', '2025-12-22 08:00:00.000', NULL, 'LN71704', 'C0004', 915000.00, '2025-11-25 00:00:00.000', 31, 1)
ON DUPLICATE KEY UPDATE
MODEL_ID = VALUES(MODEL_ID), MODEL_NAME = VALUES(MODEL_NAME),
MODEL_RUN_BATCH_DATE = VALUES(MODEL_RUN_BATCH_DATE), REMARK = VALUES(REMARK),
CUSTOMER_ID = VALUES(CUSTOMER_ID), OUTSTANDING_AMOUNT = VALUES(OUTSTANDING_AMOUNT),
DUE_DATE = VALUES(DUE_DATE), OVERDUE_DAYS = VALUES(OVERDUE_DAYS),
IS_OVERDUE = VALUES(IS_OVERDUE);
COMMIT;
......@@ -326,6 +326,21 @@ export default function Models() {
return Number(right) - Number(left);
}), [batchesByYear]);
const activeYearEntries = batchesByYear[activeBatchYear] || [];
const activeYearModelGroups = useMemo(() => {
const grouped = new Map();
activeYearEntries.forEach((entry) => {
const modelKey = `${entry.sourceModel.source_model_id || entry.sourceModel.model_code || entry.sourceModel.model_name}`;
if (!grouped.has(modelKey)) {
grouped.set(modelKey, {
key: modelKey,
model: entry.sourceModel,
entries: [],
});
}
grouped.get(modelKey).entries.push(entry);
});
return [...grouped.values()];
}, [activeYearEntries]);
const activeYearSelectableEntries = activeYearEntries.filter((entry) => !syncedBatchCodes.includes(entry.key));
const allActiveYearSelected = activeYearSelectableEntries.length > 0
&& activeYearSelectableEntries.every((entry) => selectedBatchCodes.includes(entry.key));
......@@ -337,8 +352,8 @@ export default function Models() {
: current.filter((code) => !activeCodes.includes(code)));
}
function handleActiveYearBatchSelection(keys) {
const selectableCodes = new Set(activeYearSelectableEntries.map((entry) => entry.key));
function handleBatchSelection(keys, entries) {
const selectableCodes = new Set(entries.filter((entry) => !syncedBatchCodes.includes(entry.key)).map((entry) => entry.key));
const nextCodes = keys === 'all' ? selectableCodes : new Set(keys);
setSelectedBatchCodes((current) => [
...current.filter((code) => !selectableCodes.has(code)),
......@@ -521,32 +536,51 @@ export default function Models() {
{activeYearEntries.length ? (
<ScrollShadow className="max-h-[48vh] overflow-y-auto" orientation="vertical" size={24}>
<ListView
aria-label="当前年份可同步批次"
disabledKeys={new Set(activeYearEntries.filter((entry) => syncedBatchCodes.includes(entry.key)).map((entry) => entry.key))}
selectedKeys={new Set(activeYearEntries.filter((entry) => selectedBatchCodes.includes(entry.key)).map((entry) => entry.key))}
selectionBehavior="toggle"
selectionMode="multiple"
variant="primary"
onSelectionChange={handleActiveYearBatchSelection}
>
{activeYearEntries.map((entry) => {
const synced = syncedBatchCodes.includes(entry.key);
<div className="space-y-3 pr-1">
{activeYearModelGroups.map((modelGroup) => {
const groupSelectableEntries = modelGroup.entries.filter((entry) => !syncedBatchCodes.includes(entry.key));
const groupSelectedCount = groupSelectableEntries.filter((entry) => selectedBatchCodes.includes(entry.key)).length;
return (
<ListView.Item id={entry.key} key={entry.key} textValue={entry.sourceBatch.batch_name}>
<ListView.ItemContent>
<span className="min-w-0 flex-1">
<ListView.Title className="block">{entry.sourceBatch.batch_name}</ListView.Title>
<ListView.Description className="block tabular-nums">{entry.sourceModel.model_name} · {entry.sourceModel.model_code} · {formatDateTime(entry.sourceBatch.source_batch_date)} · {entry.sourceBatch.batch_code}</ListView.Description>
<section className="overflow-hidden rounded-xl border border-default bg-surface" key={modelGroup.key}>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-default bg-surface-secondary px-4 py-3">
<div className="min-w-0">
<h3 className="truncate text-sm font-semibold text-foreground">{modelGroup.model.model_name}</h3>
<p className="mt-0.5 truncate text-xs text-muted">{modelGroup.model.model_code}</p>
</div>
<span className="shrink-0 text-xs tabular-nums text-muted">
{modelGroup.entries.length} 个批次{groupSelectedCount ? ` · 已选 ${groupSelectedCount}` : ''}
</span>
</ListView.ItemContent>
<ListView.ItemAction>
{synced ? <Tag tone="emerald">已同步</Tag> : <Description className="tabular-nums">{Number(entry.sourceBatch.source_model_data_count || 0).toLocaleString('zh-CN')} 条</Description>}
</ListView.ItemAction>
</ListView.Item>
</div>
<ListView
aria-label={`${modelGroup.model.model_name} 批次`}
disabledKeys={new Set(modelGroup.entries.filter((entry) => syncedBatchCodes.includes(entry.key)).map((entry) => entry.key))}
selectedKeys={new Set(modelGroup.entries.filter((entry) => selectedBatchCodes.includes(entry.key)).map((entry) => entry.key))}
selectionBehavior="toggle"
selectionMode="multiple"
variant="primary"
onSelectionChange={(keys) => handleBatchSelection(keys, modelGroup.entries)}
>
{modelGroup.entries.map((entry) => {
const synced = syncedBatchCodes.includes(entry.key);
return (
<ListView.Item id={entry.key} key={entry.key} textValue={entry.sourceBatch.batch_name}>
<ListView.ItemContent>
<span className="min-w-0 flex-1">
<ListView.Title className="block">{entry.sourceBatch.batch_name}</ListView.Title>
<ListView.Description className="block tabular-nums">{formatDateTime(entry.sourceBatch.source_batch_date)} · {entry.sourceBatch.batch_code}</ListView.Description>
</span>
</ListView.ItemContent>
<ListView.ItemAction>
{synced ? <Tag tone="emerald">已同步</Tag> : <Description className="tabular-nums">{Number(entry.sourceBatch.source_model_data_count || 0).toLocaleString('zh-CN')} 条</Description>}
</ListView.ItemAction>
</ListView.Item>
);
})}
</ListView>
</section>
);
})}
</ListView>
</div>
</ScrollShadow>
) : <EmptyCollection title="该年份没有来源批次" description="请选择其他年份查看可同步批次。" />}
</div>
......
......@@ -60,7 +60,7 @@ loadSourceBatchData(sourceModel: SourceModel, sourceBatch: SourceBatch): Promise
`ANALYZE_MODEL_DATA_MX_*` 结果行不要求业务主键。每个批次只允许同步一次,结果行按来源读取顺序记录 `source_row_number` 并纯新增,不进行去重或更新。
MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql` 初始化。脚本只创建不存在的来源表,并按稳定键更新 3 个模型、9 个批次和 48 条结果样例;使用测试源启动前端时,必须把 `MODEL_MONITOR_CONNECTOR_NAME` 配置为对应的 MySQL Connector。该环境只验证通用同步流程,不替代 Oracle 驱动和方言的集成验收。
MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql` 初始化。脚本只创建不存在的来源表,并按稳定键更新 4 个模型、16 个批次和 80 条结果样例;每个模型包含 2025 年历史批次和 2026 年当前批次,便于验证按年份分组、年份切换、跨年份勾选和全选口径。使用测试源启动前端时,必须把 `MODEL_MONITOR_CONNECTOR_NAME` 配置为对应的 MySQL Connector;当前测试环境的 Connector 名称为 `oqJDeVZszjRM`,可用 `MODEL_MONITOR_CONNECTOR_NAME=oqJDeVZszjRM npm run dev` 启动。该环境只验证通用同步流程,不替代 Oracle 驱动和方言的集成验收。
未来若固定数据源配置变化,只替换构建常量和适配器实现,不修改页面和本地持久化服务。
......@@ -179,6 +179,6 @@ MySQL 联调数据可通过 `scripts/mysql/model-monitor-source-fixtures.sql`
- 勾选批次记录后在视口固定 ActionBar 中执行转发、隐藏或清空选择;所有按钮继续调用原处理函数。
- 模型同步、批次同步、隐藏管理、转发提交、审批详情和填写使用 HeroUI Modal/AlertDialog;关闭或取消不触发保存请求。
- 弹窗宽度按内容密度使用统一梯度:删除确认使用 `md`,短单列表单使用 `lg`,普通双列表单使用 `2xl`,内容型详情、转发、批量同步和填写流程使用 `4xl`(1088px);移动端保留 16px 安全边距,不改变关闭、校验、保存和审批回调。
- 批量同步模型批次弹窗使用紧凑步骤条、年份筛选与全选工具行,以及单一 HeroUI Pro ListView 原生多选;每个批次行展示来源模型信息,切换年份继续保留其它年份已选项,已同步批次继续禁用,选择和同步口径不变。
- 批量同步模型批次弹窗使用紧凑步骤条、年份筛选与全选工具行;年份下按来源模型分段展示批次,同一模型的批次集中在同一列表区,并在分段标题展示模型名称、编码和批次数量。各分段使用 HeroUI Pro ListView 原生多选,切换年份继续保留其它年份已选项,已同步批次继续禁用,选择和同步口径不变。
- 查看态详情统一放在单一 secondary Surface;编辑态字段直接位于 Modal Overlay,字段必填、枚举来源、主键禁用和完成填写校验保持原规则。
- Loading、错误和空状态由 HeroUI/HeroUI Pro 语义组件展示;不得生成测试记录或用伪造数值填充空页面。
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