Переглянути джерело

feat(aidop): add data platform management demo

Add the Ai-DOP data platform menu, demo pages, data map, and planning artifacts so the module can be shown as a lightweight closed loop. Bump Web to 2.4.144 and server to 1.0.111 for the frontend/backend changes.

Co-authored-by: Cursor <cursoragent@cursor.com>
skygu 6 днів тому
батько
коміт
8ae1214d13

+ 1 - 1
Web/package.json

@@ -1,7 +1,7 @@
 {
 	"name": "admin.net",
 	"type": "module",
-	"version": "2.4.143",
+	"version": "2.4.144",
 	"packageManager": "pnpm@10.32.1",
 	"lastBuildTime": "2026.03.15",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",

+ 507 - 0
Web/src/views/aidop/data-platform/dataMap.vue

@@ -0,0 +1,507 @@
+<template>
+	<AidopDemoShell title="数据地图" subtitle="外部系统、DB/API 接入、数据处理层级和服务出口总览" card-body-padding="0">
+		<template #bar-right>
+			<el-button icon="ele-Operation" @click="go('/aidop/data-platform/sync-tasks')">数据任务</el-button>
+			<el-button type="primary" icon="ele-AlarmClock" @click="go('/platform/job')">任务调度</el-button>
+		</template>
+
+		<div class="data-map">
+			<section class="map-hero">
+				<div>
+					<div class="eyebrow">Ai-DOP Data Platform</div>
+					<h3>企业数据流转总览</h3>
+					<p>统一呈现数据库同步、API 入站、API 拉取与 API 出站服务,帮助演示数据从来源到业务应用的完整路径。</p>
+				</div>
+				<div class="legend">
+					<span v-for="item in legends" :key="item.label"><i :class="item.className" />{{ item.label }}</span>
+				</div>
+			</section>
+
+			<section class="map-canvas">
+				<div class="flow-steps">
+					<div v-for="(step, index) in flowSteps" :key="step" class="flow-step">
+						<span>{{ step }}</span>
+						<i v-if="index < flowSteps.length - 1">→</i>
+					</div>
+				</div>
+
+				<div class="lane lane-source">
+					<div class="lane__title">外部系统 / 来源</div>
+					<div v-for="node in sourceNodes" :key="node.title" class="node-card is-clickable" @click="openDetail(node.detailKey)">
+						<div class="node-card__top">
+							<span class="node-card__title">{{ node.title }}</span>
+							<el-tag size="small" effect="plain" :type="node.statusType">{{ node.status }}</el-tag>
+						</div>
+						<div class="node-card__desc">{{ node.desc }}</div>
+						<div class="node-card__meta">{{ node.meta }}</div>
+					</div>
+				</div>
+
+				<div class="lane lane-access">
+					<div class="lane__title">接入层</div>
+					<div v-for="node in accessNodes" :key="node.title" class="process-card is-clickable" :class="node.kind" @click="openDetail(node.detailKey)">
+						<div class="process-card__icon">{{ node.icon }}</div>
+						<div>
+							<div class="process-card__title">{{ node.title }}</div>
+							<div class="process-card__desc">{{ node.desc }}</div>
+						</div>
+					</div>
+				</div>
+
+				<div class="hub">
+					<div class="hub__ring">
+						<div class="hub__core">
+							<div class="hub__title">数据中台</div>
+							<div class="hub__desc">接入 · 治理 · 标准化 · 服务</div>
+						</div>
+					</div>
+					<div class="pipeline">
+						<div v-for="step in pipelineSteps" :key="step.title" class="pipeline-step is-clickable" @click="openDetail(step.detailKey)">
+							<div class="pipeline-step__index">{{ step.index }}</div>
+							<div>
+								<div class="pipeline-step__title">{{ step.title }}</div>
+								<div class="pipeline-step__desc">{{ step.desc }}</div>
+							</div>
+						</div>
+					</div>
+				</div>
+
+				<div class="lane lane-service">
+					<div class="lane__title">服务出口</div>
+					<div v-for="node in serviceNodes" :key="node.title" class="process-card is-clickable" :class="node.kind" @click="openDetail(node.detailKey)">
+						<div class="process-card__icon">{{ node.icon }}</div>
+						<div>
+							<div class="process-card__title">{{ node.title }}</div>
+							<div class="process-card__desc">{{ node.desc }}</div>
+						</div>
+					</div>
+				</div>
+
+				<div class="lane lane-consumer">
+					<div class="lane__title">业务消费 / 回写</div>
+					<div v-for="node in consumerNodes" :key="node.title" class="node-card node-card--consumer is-clickable" @click="openDetail(node.detailKey)">
+						<div class="node-card__top">
+							<span class="node-card__title">{{ node.title }}</span>
+							<el-tag size="small" effect="plain">{{ node.type }}</el-tag>
+						</div>
+						<div class="node-card__desc">{{ node.desc }}</div>
+						<div class="node-card__meta">{{ node.meta }}</div>
+					</div>
+				</div>
+			</section>
+
+			<el-drawer v-model="detailVisible" :title="currentDetail?.title || '节点详情'" size="420px">
+				<div v-if="currentDetail" class="detail-drawer">
+					<el-tag effect="plain" :type="currentDetail.tagType">{{ currentDetail.category }}</el-tag>
+					<p class="detail-summary">{{ currentDetail.summary }}</p>
+
+					<div class="detail-section">
+						<div class="detail-section__title">关联对象</div>
+						<div class="detail-object" v-for="item in currentDetail.objects" :key="item.label">
+							<span>{{ item.label }}</span>
+							<b>{{ item.value }}</b>
+						</div>
+					</div>
+
+					<div class="detail-section">
+						<div class="detail-section__title">操作入口</div>
+						<div class="detail-actions">
+							<el-button v-for="action in currentDetail.actions" :key="action.label" type="primary" plain @click="go(action.path)">
+								{{ action.label }}
+							</el-button>
+						</div>
+					</div>
+				</div>
+			</el-drawer>
+		</div>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopDataPlatformDataMap">
+import { computed, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import AidopDemoShell from '../components/AidopDemoShell.vue';
+
+const router = useRouter();
+
+const legends = [
+	{ label: 'DB 同步', className: 'legend-db' },
+	{ label: 'API 入站', className: 'legend-api-in' },
+	{ label: 'API 拉取', className: 'legend-api-pull' },
+	{ label: 'API 出站', className: 'legend-api-out' },
+];
+
+const flowSteps = ['外部系统 / 来源', '接入层', '数据中台', '服务出口', '业务消费 / 回写'];
+
+const sourceNodes = [
+	{ title: 'ERP / SAP', desc: '物料、供应商、采购与财务主数据', meta: 'DB / API PULL', status: '正常', statusType: 'success', detailKey: 'source-erp' },
+	{ title: 'MES 生产系统', desc: '工单执行、过站、报工与设备数据', meta: 'DB / API IN', status: '规划中', statusType: 'info', detailKey: 'source-mes' },
+	{ title: 'WMS 仓储系统', desc: '库存快照、入出库事件与库位状态', meta: 'API IN / PULL', status: '延迟', statusType: 'warning', detailKey: 'source-wms' },
+];
+
+const accessNodes = [
+	{ title: 'DB 同步接入', desc: '通过任务调度周期读取表 / 视图 / 增量字段', icon: 'DB', kind: 'kind-db', detailKey: 'access-db' },
+	{ title: 'API 入站接收', desc: 'Webhook / REST 推送', icon: 'IN', kind: 'kind-api-in', detailKey: 'access-api-in' },
+	{ title: 'API 定时拉取', desc: '分页 / 游标 / 时间窗', icon: 'GET', kind: 'kind-api-pull', detailKey: 'access-api-pull' },
+];
+
+const pipelineSteps = [
+	{ index: '01', title: '原始接入区', desc: '先按来源原样落地,保留批次和追溯信息', detailKey: 'stage-raw' },
+	{ index: '02', title: '清洗校验', desc: '必填、字典、格式和异常样本', detailKey: 'stage-clean' },
+	{ index: '03', title: '字段映射', desc: '源字段到标准字段转换', detailKey: 'stage-mapping' },
+	{ index: '04', title: '标准模型', desc: '主数据、业务事实、主题模型', detailKey: 'stage-standard' },
+	{ index: '05', title: '指标与服务', desc: '面向页面、报表和外部 API', detailKey: 'stage-service' },
+];
+
+const serviceNodes = [
+	{ title: 'API 出站服务', desc: '对外提供标准数据接口', icon: 'OUT', kind: 'kind-api-out', detailKey: 'service-api-out' },
+	{ title: '业务查询服务', desc: 'S3 / S4 / S8 页面消费', icon: 'APP', kind: 'kind-db', detailKey: 'service-query' },
+	{ title: '同步日志追踪', desc: '批次、行数、异常和重试', icon: 'LOG', kind: 'kind-job', detailKey: 'service-log' },
+];
+
+const consumerNodes = [
+	{ title: '业务作业', desc: '采购、生产、交付等业务页面直接消费', meta: '业务操作', type: '业务', detailKey: 'consumer-work' },
+	{ title: '异常监控', desc: '规则识别、异常跟踪、任务流转', meta: '监控闭环', type: '监控', detailKey: 'consumer-exception' },
+	{ title: '指标看板', desc: '经营指标、模块看板、BI 报表', meta: '数据分析', type: '看板', detailKey: 'consumer-dashboard' },
+	{ title: '外部系统', desc: '通过标准 API 回写或提供数据服务', meta: 'API OUT', type: '服务', detailKey: 'consumer-external' },
+];
+
+const detailVisible = ref(false);
+const activeDetailKey = ref('access-db');
+const detailMap: Record<string, any> = {
+	'source-erp': { title: 'ERP / SAP', category: '外部系统', tagType: 'success', summary: '提供物料、供应商、采购、财务等主数据,可通过数据库同步或 API 拉取进入数据中台。', objects: [{ label: '接入方式', value: 'DB 同步 / API 拉取' }, { label: '关联任务', value: '物料主数据 API 拉取' }, { label: '健康状态', value: '正常' }], actions: [{ label: '去数据源管理', path: '/aidop/data-platform/sources' }, { label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'source-mes': { title: 'MES 生产系统', category: '外部系统', tagType: 'info', summary: '提供工单执行、过站、报工和设备数据,后续用于生产执行、异常监控和指标看板。', objects: [{ label: '接入方式', value: 'DB / API 入站' }, { label: '状态', value: '规划中' }, { label: '消费方', value: '业务作业、异常监控' }], actions: [{ label: '去数据源管理', path: '/aidop/data-platform/sources' }] },
+	'source-wms': { title: 'WMS 仓储系统', category: '外部系统', tagType: 'warning', summary: '提供库存快照、入出库事件和库位状态,支持 API 入站推送或数据中台定时拉取。', objects: [{ label: '接入方式', value: 'API 入站 / API 拉取' }, { label: '最近状态', value: '延迟' }, { label: '异常查看', value: '数据任务日志' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }, { label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'access-db': { title: 'DB 同步接入', category: '接入层', tagType: 'primary', summary: '通过任务调度周期读取外部系统数据库表、视图或增量字段,适合批量同步和可追溯入库。', objects: [{ label: '典型来源', value: 'ERP / SAP、MES' }, { label: '关联 Job', value: 'job_s3_mdp_sync_transform' }, { label: '目标表', value: 'mdp_stg_* / mdp_std_*' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }, { label: '去任务调度', path: '/platform/job' }, { label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'access-api-in': { title: 'API 入站接收', category: '接入层', tagType: 'success', summary: '外部系统主动调用数据中台入站 API,适合事件推送、状态回写和实时性要求较高的场景。', objects: [{ label: '协议', value: 'REST / Webhook' }, { label: '认证方式', value: 'Token / API Key' }, { label: '日志', value: '请求批次与异常样本' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }, { label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'access-api-pull': { title: 'API 定时拉取', category: '接入层', tagType: 'warning', summary: '数据中台按分页、游标或时间窗定时调用外部 API,适合外部系统不支持主动推送的场景。', objects: [{ label: '策略', value: '分页 / 游标 / 时间窗' }, { label: '调度', value: '复用任务调度' }, { label: '失败处理', value: '重试与日志追踪' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }, { label: '去任务调度', path: '/platform/job' }] },
+	'stage-raw': { title: '原始接入区', category: '数据中台', tagType: 'info', summary: '先把来源数据原样落地,保留批次号、来源系统和时间戳,便于追溯和重放。', objects: [{ label: '对应表', value: 'mdp_stg_*' }, { label: '保留信息', value: '来源、批次、原始字段' }], actions: [{ label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'stage-clean': { title: '清洗校验', category: '数据中台', tagType: 'warning', summary: '对必填字段、字典值、格式和重复数据进行校验,异常样本进入日志追踪。', objects: [{ label: '校验内容', value: '必填、字典、格式、重复' }, { label: '异常出口', value: '数据任务日志' }], actions: [{ label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'stage-mapping': { title: '字段映射', category: '数据中台', tagType: 'primary', summary: '把外部系统字段转换为新系统统一字段,屏蔽各系统编码和字段命名差异。', objects: [{ label: '配置表', value: 'mdp_field_mapping' }, { label: '映射方式', value: '直连、常量、脚本、字典' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'stage-standard': { title: '标准模型', category: '数据中台', tagType: 'success', summary: '形成主数据、业务事实和主题模型,供业务页面、异常监控和指标看板复用。', objects: [{ label: '模型类型', value: '主数据 / 事实 / 主题' }, { label: '示例', value: '物料、供应商、采购订单' }], actions: [{ label: '去数据地图', path: '/aidop/data-platform/data-map' }] },
+	'stage-service': { title: '指标与服务', category: '数据中台', tagType: 'success', summary: '把标准模型封装为页面查询、指标计算和对外 API 服务。', objects: [{ label: '服务对象', value: '业务作业、指标看板、外部系统' }, { label: '出口', value: 'API 出站 / 业务查询' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'service-api-out': { title: 'API 出站服务', category: '服务出口', tagType: 'success', summary: '把数据中台标准数据以 API 形式提供给外部系统、BI 或移动端使用。', objects: [{ label: '协议', value: 'REST' }, { label: '认证', value: 'Token / API Key' }, { label: '状态', value: '规划中' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'service-query': { title: '业务查询服务', category: '服务出口', tagType: 'primary', summary: '为 S3、S4、S8 等业务页面提供统一查询能力,减少页面直接依赖外部系统。', objects: [{ label: '消费方', value: '业务作业、异常监控' }, { label: '服务类型', value: '页面查询 / 聚合查询' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'service-log': { title: '同步日志追踪', category: '服务出口', tagType: 'warning', summary: '记录每次数据任务的读取、写入、异常和耗时,用于演示和排障。', objects: [{ label: '日志表', value: 'mdp_sync_log' }, { label: '内容', value: '批次、行数、异常样本' }], actions: [{ label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'consumer-work': { title: '业务作业', category: '业务消费', tagType: 'primary', summary: '采购、生产、交付等业务页面直接消费数据中台标准模型。', objects: [{ label: '示例', value: 'S3、S4 业务页面' }, { label: '数据来源', value: '标准模型 / 业务查询服务' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+	'consumer-exception': { title: '异常监控', category: '业务消费', tagType: 'warning', summary: '规则识别、异常跟踪和任务流转基于数据中台统一数据口径。', objects: [{ label: '示例', value: 'S8 异常监控' }, { label: '数据来源', value: '规则模型 / 标准模型' }], actions: [{ label: '去任务日志', path: '/aidop/data-platform/sync-logs' }] },
+	'consumer-dashboard': { title: '指标看板', category: '业务消费', tagType: 'success', summary: '经营指标、模块看板和 BI 报表使用中台模型沉淀后的指标口径。', objects: [{ label: '示例', value: '智慧运营看板 / BI' }, { label: '出口', value: '指标与服务' }], actions: [{ label: '去数据地图', path: '/aidop/data-platform/data-map' }] },
+	'consumer-external': { title: '外部系统', category: '业务消费', tagType: 'info', summary: '外部系统可以通过标准 API 获取或回写数据,形成数据闭环。', objects: [{ label: '方向', value: 'API 出站 / 回写' }, { label: '认证', value: 'Token / API Key' }], actions: [{ label: '去数据任务', path: '/aidop/data-platform/sync-tasks' }] },
+};
+
+const currentDetail = computed(() => detailMap[activeDetailKey.value]);
+
+function openDetail(key: string) {
+	activeDetailKey.value = key;
+	detailVisible.value = true;
+}
+
+function go(path: string) {
+	router.push(path);
+}
+</script>
+
+<style scoped>
+.data-map {
+	background: linear-gradient(180deg, #f7f9fc 0%, #eef3f8 100%);
+	padding: 18px;
+}
+.map-hero {
+	align-items: flex-start;
+	background: linear-gradient(135deg, #10223d 0%, #173c67 60%, #1f6f8b 100%);
+	border-radius: 14px;
+	color: #fff;
+	display: flex;
+	justify-content: space-between;
+	margin-bottom: 14px;
+	padding: 18px 20px;
+}
+.eyebrow {
+	color: rgba(255, 255, 255, 0.68);
+	font-size: 12px;
+	letter-spacing: 0.12em;
+	text-transform: uppercase;
+}
+.map-hero h3 {
+	font-size: 24px;
+	margin: 6px 0;
+}
+.map-hero p {
+	color: rgba(255, 255, 255, 0.78);
+	margin: 0;
+	max-width: 720px;
+}
+.legend {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 8px;
+	justify-content: flex-end;
+	max-width: 360px;
+}
+.legend span {
+	align-items: center;
+	background: rgba(255, 255, 255, 0.12);
+	border: 1px solid rgba(255, 255, 255, 0.18);
+	border-radius: 999px;
+	display: inline-flex;
+	font-size: 12px;
+	gap: 6px;
+	padding: 6px 10px;
+}
+.legend i {
+	border-radius: 999px;
+	display: inline-block;
+	height: 8px;
+	width: 8px;
+}
+.map-canvas {
+	background: #fff;
+	border: 1px solid #dbe4ef;
+	border-radius: 14px;
+	display: grid;
+	gap: 16px;
+	grid-template-columns: 1.1fr 1fr 1.45fr 1fr 1.1fr;
+	min-height: 560px;
+	overflow: hidden;
+	padding: 18px;
+	position: relative;
+}
+.flow-steps {
+	align-items: center;
+	background: linear-gradient(90deg, rgba(47, 128, 237, 0.1), rgba(25, 169, 116, 0.1));
+	border: 1px solid #d8e7f5;
+	border-radius: 12px;
+	display: grid;
+	grid-column: 1 / -1;
+	grid-template-columns: repeat(5, minmax(0, 1fr));
+	margin-bottom: 2px;
+	padding: 10px 12px;
+	position: relative;
+	z-index: 3;
+}
+.flow-step {
+	align-items: center;
+	color: #17324d;
+	display: flex;
+	font-size: 14px;
+	font-weight: 700;
+	justify-content: center;
+	min-height: 30px;
+	position: relative;
+	text-align: center;
+}
+.flow-step span {
+	background: #fff;
+	border: 1px solid #cfe0ef;
+	border-radius: 999px;
+	box-shadow: 0 6px 14px rgba(20, 43, 77, 0.08);
+	padding: 7px 12px;
+}
+.flow-step i {
+	color: #2f80ed;
+	font-size: 26px;
+	font-style: normal;
+	font-weight: 800;
+	position: absolute;
+	right: -14px;
+}
+.lane,
+.hub {
+	position: relative;
+	z-index: 2;
+}
+.lane__title {
+	color: #56657a;
+	display: none;
+	font-size: 13px;
+	font-weight: 700;
+	letter-spacing: 0.05em;
+	margin-bottom: 12px;
+}
+.node-card,
+.process-card,
+.pipeline-step {
+	background: rgba(255, 255, 255, 0.96);
+	border: 1px solid #dce5ef;
+	border-radius: 12px;
+	box-shadow: 0 8px 22px rgba(20, 43, 77, 0.06);
+	margin-bottom: 12px;
+	padding: 12px;
+}
+.is-clickable {
+	cursor: pointer;
+	position: relative;
+	transition: border-color 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
+}
+.is-clickable:hover {
+	border-color: #2f80ed;
+	box-shadow: 0 10px 26px rgba(47, 128, 237, 0.14);
+	transform: translateY(-1px);
+}
+.is-clickable:hover::after {
+	color: #2f80ed;
+	content: '点击查看';
+	font-size: 12px;
+	position: absolute;
+	right: 10px;
+	top: -18px;
+}
+.node-card__top {
+	align-items: center;
+	display: flex;
+	gap: 8px;
+	justify-content: space-between;
+}
+.node-card__title,
+.process-card__title,
+.pipeline-step__title {
+	color: #172033;
+	font-weight: 700;
+}
+.node-card__desc,
+.process-card__desc,
+.pipeline-step__desc,
+.node-card__meta {
+	color: #65738a;
+	font-size: 12px;
+	line-height: 1.6;
+	margin-top: 4px;
+}
+.node-card__meta {
+	color: #2f6f9f;
+	font-family: Consolas, Monaco, monospace;
+	font-weight: 700;
+}
+.process-card {
+	align-items: center;
+	display: flex;
+	gap: 10px;
+}
+.process-card__icon {
+	align-items: center;
+	border-radius: 10px;
+	color: #fff;
+	display: flex;
+	flex: 0 0 40px;
+	font-size: 12px;
+	font-weight: 700;
+	height: 40px;
+	justify-content: center;
+}
+.kind-db .process-card__icon {
+	background: #2f80ed;
+}
+.kind-api-in .process-card__icon {
+	background: #7b61ff;
+}
+.kind-api-pull .process-card__icon {
+	background: #00a7b5;
+}
+.kind-api-out .process-card__icon {
+	background: #19a974;
+}
+.kind-job .process-card__icon {
+	background: #f39c12;
+}
+.hub {
+	align-items: center;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+}
+.hub__ring {
+	align-items: center;
+	background: radial-gradient(circle, rgba(47, 128, 237, 0.18), rgba(47, 128, 237, 0.04) 68%);
+	border: 1px solid rgba(47, 128, 237, 0.2);
+	border-radius: 50%;
+	display: flex;
+	height: 190px;
+	justify-content: center;
+	margin-bottom: 16px;
+	width: 190px;
+}
+.hub__core {
+	align-items: center;
+	background: linear-gradient(135deg, #163a63, #1d6f92);
+	border-radius: 50%;
+	box-shadow: 0 18px 36px rgba(22, 58, 99, 0.24);
+	color: #fff;
+	display: flex;
+	flex-direction: column;
+	height: 132px;
+	justify-content: center;
+	text-align: center;
+	width: 132px;
+}
+.hub__title {
+	font-size: 22px;
+	font-weight: 700;
+}
+.hub__desc {
+	color: rgba(255, 255, 255, 0.75);
+	font-size: 12px;
+	margin-top: 6px;
+}
+.pipeline {
+	width: 100%;
+}
+.pipeline-step {
+	align-items: center;
+	display: flex;
+	gap: 10px;
+	margin-bottom: 8px;
+	padding: 10px;
+}
+.pipeline-step__index {
+	color: #2f80ed;
+	font-weight: 800;
+}
+.legend-db,
+.channel-dot.legend-db {
+	background: #2f80ed;
+}
+.legend-api-in,
+.channel-dot.legend-api-in {
+	background: #7b61ff;
+}
+.legend-api-pull,
+.channel-dot.legend-api-pull {
+	background: #00a7b5;
+}
+.legend-api-out,
+.legend-api-out {
+	background: #19a974;
+}
+.detail-summary {
+	color: #3f4d60;
+	line-height: 1.8;
+	margin: 14px 0 18px;
+}
+.detail-section {
+	border-top: 1px solid #edf1f6;
+	padding: 14px 0;
+}
+.detail-section__title {
+	color: #172033;
+	font-weight: 700;
+	margin-bottom: 10px;
+}
+.detail-object {
+	display: grid;
+	gap: 10px;
+	grid-template-columns: 92px 1fr;
+	line-height: 28px;
+}
+.detail-object span {
+	color: #65738a;
+}
+.detail-object b {
+	color: #172033;
+	font-weight: 600;
+}
+.detail-actions {
+	display: flex;
+	flex-wrap: wrap;
+	gap: 8px;
+}
+</style>

+ 135 - 0
Web/src/views/aidop/data-platform/overview.vue

@@ -0,0 +1,135 @@
+<template>
+	<AidopDemoShell title="数据中台工作台" subtitle="数据源、数据地图、数据任务、调度入口与质量状态集中查看">
+		<template #bar-right>
+			<el-button type="primary" icon="ele-Share" @click="go('/aidop/data-platform/data-map')">数据地图</el-button>
+			<el-button type="primary" icon="ele-AlarmClock" @click="go('/platform/job')">去任务调度</el-button>
+			<el-button icon="ele-Operation" @click="go('/aidop/data-platform/sync-tasks')">数据任务</el-button>
+		</template>
+
+		<el-alert
+			class="mb12"
+			type="info"
+			show-icon
+			:closable="false"
+			title="调度能力复用 Admin.NET 任务调度;数据中台集中展示 DB 同步、API 入站、API 拉取和 API 出站服务。"
+		/>
+
+		<el-row :gutter="12">
+			<el-col v-for="item in kpis" :key="item.label" :span="6">
+				<el-card shadow="hover" class="kpi-card">
+					<div class="kpi-card__label">{{ item.label }}</div>
+					<div class="kpi-card__value">{{ item.value }}</div>
+					<div class="kpi-card__desc">{{ item.desc }}</div>
+				</el-card>
+			</el-col>
+		</el-row>
+
+		<el-row :gutter="12" class="mt12">
+			<el-col :span="14">
+				<el-card shadow="never" header="最近同步状态">
+					<el-table :data="recentSyncs" border>
+						<el-table-column prop="entityName" label="同步实体" min-width="150" />
+						<el-table-column prop="sourceName" label="数据源" width="130" />
+						<el-table-column prop="lastSync" label="最近同步" width="160" />
+						<el-table-column prop="rows" label="写入行数" width="100" align="right" />
+						<el-table-column prop="status" label="状态" width="100" align="center">
+							<template #default="{ row }">
+								<el-tag :type="row.statusType" effect="plain">{{ row.status }}</el-tag>
+							</template>
+						</el-table-column>
+					</el-table>
+				</el-card>
+			</el-col>
+			<el-col :span="10">
+				<el-card shadow="never" header="数据中台入口">
+					<div class="entry-list">
+						<div v-for="entry in entries" :key="entry.path" class="entry" @click="go(entry.path)">
+							<div>
+								<div class="entry__title">{{ entry.title }}</div>
+								<div class="entry__desc">{{ entry.desc }}</div>
+							</div>
+							<el-icon><ArrowRight /></el-icon>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopDataPlatformOverview">
+import { useRouter } from 'vue-router';
+import { ArrowRight } from '@element-plus/icons-vue';
+import AidopDemoShell from '../components/AidopDemoShell.vue';
+
+const router = useRouter();
+
+const kpis = [
+	{ label: '接入系统', value: '5', desc: '数据库 / API / 外部系统' },
+	{ label: '数据任务', value: '8', desc: '入站、拉取、出站与调度' },
+	{ label: '今日成功率', value: '98.6%', desc: '按最近批次与调用统计' },
+	{ label: '待处理异常', value: '2', desc: '字段缺失、接口延迟' },
+];
+
+const recentSyncs = [
+	{ entityName: 'S3 需求计划', sourceName: '旧 SQL Server', lastSync: '10 分钟前', rows: 128, status: '成功', statusType: 'success' },
+	{ entityName: 'S3 采购订单', sourceName: '旧 SQL Server', lastSync: '28 分钟前', rows: 64, status: '成功', statusType: 'success' },
+	{ entityName: '物料主数据', sourceName: 'ERP 主库', lastSync: '1 小时前', rows: 320, status: '部分成功', statusType: 'warning' },
+	{ entityName: '库存快照', sourceName: 'WMS', lastSync: '2 小时前', rows: 0, status: '待处理', statusType: 'danger' },
+];
+
+const entries = [
+	{ title: '数据地图', desc: '图形化查看系统连接、处理层级与数据流转', path: '/aidop/data-platform/data-map' },
+	{ title: '数据源管理', desc: '集中维护外部系统、数据库连接、API 端点与健康状态', path: '/aidop/data-platform/sources' },
+	{ title: '数据任务管理', desc: '查看 DB/API 入站出站任务,并跳转 Admin.NET 任务调度维护 CRON', path: '/aidop/data-platform/sync-tasks' },
+	{ title: '数据任务日志', desc: '查看批次、调用结果、读取/写入行数与异常样本', path: '/aidop/data-platform/sync-logs' },
+	{ title: '任务调度', desc: '复用平台任务调度,不在数据中台重复造调度管理', path: '/platform/job' },
+];
+
+function go(path: string) {
+	router.push(path);
+}
+</script>
+
+<style scoped>
+.mb12 {
+	margin-bottom: 12px;
+}
+.mt12 {
+	margin-top: 12px;
+}
+.kpi-card {
+	min-height: 118px;
+}
+.kpi-card__label,
+.entry__desc {
+	color: var(--el-text-color-secondary);
+	font-size: 13px;
+}
+.kpi-card__value {
+	font-size: 28px;
+	font-weight: 700;
+	margin: 10px 0 6px;
+}
+.entry-list {
+	display: flex;
+	flex-direction: column;
+	gap: 10px;
+}
+.entry {
+	align-items: center;
+	border: 1px solid var(--el-border-color-lighter);
+	border-radius: 8px;
+	cursor: pointer;
+	display: flex;
+	justify-content: space-between;
+	padding: 12px;
+}
+.entry:hover {
+	border-color: var(--el-color-primary);
+}
+.entry__title {
+	font-weight: 600;
+	margin-bottom: 4px;
+}
+</style>

+ 71 - 0
Web/src/views/aidop/data-platform/sources.vue

@@ -0,0 +1,71 @@
+<template>
+	<AidopDemoShell title="数据源管理" subtitle="演示数据库、API 入站端点、API 拉取端点和 API 出站服务接入状态">
+		<template #bar-right>
+			<el-button type="primary" icon="ele-CirclePlus" plain>新增数据源</el-button>
+			<el-button icon="ele-Refresh">连接测试</el-button>
+		</template>
+
+		<el-card shadow="never" class="mb12">
+			<el-form :inline="true" :model="query">
+				<el-form-item label="关键字">
+					<el-input v-model="query.keyword" placeholder="编码 / 名称" clearable />
+				</el-form-item>
+				<el-form-item label="类型">
+					<el-select v-model="query.type" placeholder="全部" clearable style="width: 140px">
+						<el-option label="DB" value="DB" />
+						<el-option label="API" value="API" />
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" icon="ele-Search">查询</el-button>
+					<el-button icon="ele-RefreshLeft">重置</el-button>
+				</el-form-item>
+			</el-form>
+		</el-card>
+
+		<el-table :data="rows" border>
+			<el-table-column prop="sourceCode" label="编码" width="130" />
+			<el-table-column prop="sourceName" label="名称" min-width="160" />
+			<el-table-column prop="sourceType" label="类型" width="100" />
+			<el-table-column prop="direction" label="方向" width="90" align="center" />
+			<el-table-column prop="dbType" label="DB 类型" width="120" />
+			<el-table-column prop="host" label="连接地址 / API 端点" min-width="220" show-overflow-tooltip />
+			<el-table-column prop="health" label="健康状态" width="120" align="center">
+				<template #default="{ row }">
+					<el-tag :type="row.healthType" effect="plain">{{ row.health }}</el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column prop="lastCheck" label="最近检测" width="160" />
+			<el-table-column label="操作" width="180" fixed="right" align="center">
+				<template #default>
+					<el-button link type="primary">编辑</el-button>
+					<el-button link type="primary">检测</el-button>
+					<el-button link type="danger">停用</el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopDataPlatformSources">
+import { reactive } from 'vue';
+import AidopDemoShell from '../components/AidopDemoShell.vue';
+
+const query = reactive({
+	keyword: '',
+	type: '',
+});
+
+const rows = [
+	{ sourceCode: 'LEGACY_SQL', sourceName: '旧系统 SQL Server', sourceType: 'DB', direction: '入站', dbType: 'SQLServer', host: 'dopdemorq / Business', health: '正常', healthType: 'success', lastCheck: '10 分钟前' },
+	{ sourceCode: 'ERP_API', sourceName: 'ERP 主数据 API', sourceType: 'API', direction: '拉取', dbType: '-', host: 'https://erp.example.local/api/master-data', health: '正常', healthType: 'success', lastCheck: '25 分钟前' },
+	{ sourceCode: 'WMS_WEBHOOK', sourceName: '仓储系统 Webhook', sourceType: 'API', direction: '入站', dbType: '-', host: '/api/mdp/inbound/wms-stock', health: '异常', healthType: 'danger', lastCheck: '2 小时前' },
+	{ sourceCode: 'MDP_SERVICE', sourceName: '数据中台出站服务', sourceType: 'API', direction: '出站', dbType: '-', host: '/api/mdp/service/*', health: '规划中', healthType: 'info', lastCheck: '-' },
+];
+</script>
+
+<style scoped>
+.mb12 {
+	margin-bottom: 12px;
+}
+</style>

+ 100 - 0
Web/src/views/aidop/data-platform/syncLogs.vue

@@ -0,0 +1,100 @@
+<template>
+	<AidopDemoShell title="数据任务日志" subtitle="查看 DB/API 数据任务批次、调用结果、行数和异常样本">
+		<template #bar-right>
+			<el-button icon="ele-Operation" @click="goTasks">数据任务</el-button>
+			<el-button type="primary" icon="ele-AlarmClock" @click="goJob">去任务调度</el-button>
+		</template>
+
+		<el-card shadow="never" class="mb12">
+			<el-form :inline="true" :model="query">
+				<el-form-item label="同步实体">
+					<el-input v-model="query.entityCode" placeholder="实体编码" clearable />
+				</el-form-item>
+				<el-form-item label="状态">
+					<el-select v-model="query.status" placeholder="全部" clearable style="width: 140px">
+						<el-option label="成功" value="SUCCESS" />
+						<el-option label="部分成功" value="PARTIAL" />
+						<el-option label="失败" value="FAILED" />
+					</el-select>
+				</el-form-item>
+				<el-form-item>
+					<el-button type="primary" icon="ele-Search">查询</el-button>
+					<el-button icon="ele-RefreshLeft">重置</el-button>
+				</el-form-item>
+			</el-form>
+		</el-card>
+
+		<el-table :data="rows" border>
+			<el-table-column type="expand">
+				<template #default="{ row }">
+					<div class="log-detail">
+						<div>批次说明:{{ row.detail }}</div>
+						<div v-if="row.errorSample" class="log-detail__error">异常样本:{{ row.errorSample }}</div>
+					</div>
+				</template>
+			</el-table-column>
+			<el-table-column prop="batchId" label="批次号" width="130" />
+			<el-table-column prop="entityName" label="同步实体" min-width="150" />
+			<el-table-column prop="syncType" label="类型" width="90" align="center" />
+			<el-table-column prop="triggerType" label="触发方式" width="100" align="center" />
+			<el-table-column prop="rowsRead" label="读取" width="90" align="right" />
+			<el-table-column prop="rowsWrite" label="写入" width="90" align="right" />
+			<el-table-column prop="rowsError" label="异常" width="90" align="right" />
+			<el-table-column prop="duration" label="耗时" width="100" align="right" />
+			<el-table-column prop="startTime" label="开始时间" width="170" />
+			<el-table-column prop="status" label="状态" width="110" align="center">
+				<template #default="{ row }">
+					<el-tag :type="row.statusType" effect="plain">{{ row.status }}</el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column label="操作" width="120" fixed="right" align="center">
+				<template #default>
+					<el-button link type="primary">重试</el-button>
+					<el-button link type="primary">详情</el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopDataPlatformSyncLogs">
+import { reactive } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import AidopDemoShell from '../components/AidopDemoShell.vue';
+
+const route = useRoute();
+const router = useRouter();
+
+const query = reactive({
+	entityCode: String(route.query.entityCode || ''),
+	status: '',
+});
+
+const rows = [
+	{ batchId: 'S3-1028', entityName: 'S3 需求计划', syncType: 'INCR', triggerType: 'AUTO', rowsRead: 128, rowsWrite: 128, rowsError: 0, duration: '2.3s', startTime: '2026-05-16 21:50', status: '成功', statusType: 'success', detail: '读取旧系统已发布且待生成交货单的需求计划,转换后写入新系统演示表。', errorSample: '' },
+	{ batchId: 'S3-1027', entityName: 'S3 采购订单', syncType: 'INCR', triggerType: 'MANUAL', rowsRead: 64, rowsWrite: 64, rowsError: 0, duration: '1.8s', startTime: '2026-05-16 21:32', status: '成功', statusType: 'success', detail: '人工触发 S3 采购订单同步,用于演示采购链路。', errorSample: '' },
+	{ batchId: 'MDP-0911', entityName: '物料主数据', syncType: 'FULL', triggerType: 'AUTO', rowsRead: 320, rowsWrite: 318, rowsError: 2, duration: '5.6s', startTime: '2026-05-16 20:45', status: '部分成功', statusType: 'warning', detail: '物料主数据全量同步,2 条因必填字段缺失进入待处理。', errorSample: 'item_code 为空;plant_code 未匹配到字典' },
+	{ batchId: 'WMS-0802', entityName: '库存快照', syncType: 'PAGE', triggerType: 'AUTO', rowsRead: 0, rowsWrite: 0, rowsError: 1, duration: '0.9s', startTime: '2026-05-16 19:58', status: '失败', statusType: 'danger', detail: 'WMS API 暂不可达,等待连接配置修复后重试。', errorSample: 'HTTP 503 Service Unavailable' },
+];
+
+function goTasks() {
+	router.push('/aidop/data-platform/sync-tasks');
+}
+
+function goJob() {
+	router.push('/platform/job');
+}
+</script>
+
+<style scoped>
+.mb12 {
+	margin-bottom: 12px;
+}
+.log-detail {
+	line-height: 24px;
+	padding: 8px 24px;
+}
+.log-detail__error {
+	color: var(--el-color-danger);
+}
+</style>

+ 73 - 0
Web/src/views/aidop/data-platform/syncTasks.vue

@@ -0,0 +1,73 @@
+<template>
+	<AidopDemoShell title="数据任务管理" subtitle="DB 同步、API 入站、API 拉取、API 出站任务集中查看;调度计划跳转到 Admin.NET 任务调度维护">
+		<template #bar-right>
+			<el-button type="primary" icon="ele-AlarmClock" @click="goJob">去任务调度</el-button>
+			<el-button icon="ele-CirclePlus" plain>新建数据任务</el-button>
+		</template>
+
+		<el-alert
+			class="mb12"
+			type="success"
+			show-icon
+			:closable="false"
+			title="这里不重复实现 CRON、启停和调度历史;数据任务支持入站、出站和双向集成,需要维护执行计划时跳到平台管理 / 任务调度。"
+		/>
+
+		<el-table :data="rows" border>
+			<el-table-column prop="entityName" label="数据任务" min-width="150" />
+			<el-table-column prop="direction" label="方向" width="90" align="center">
+				<template #default="{ row }">
+					<el-tag size="small" effect="plain" :type="row.directionType">{{ row.direction }}</el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column prop="protocol" label="协议" width="100" />
+			<el-table-column prop="sourceName" label="数据源" width="140" />
+			<el-table-column prop="targetTable" label="目标表 / 服务" min-width="160" show-overflow-tooltip />
+			<el-table-column prop="syncMode" label="模式" width="90" align="center" />
+			<el-table-column prop="incrColumn" label="增量字段" width="130" />
+			<el-table-column prop="jobId" label="关联调度任务" min-width="220" show-overflow-tooltip />
+			<el-table-column prop="lastRun" label="最近执行" width="150" />
+			<el-table-column prop="status" label="状态" width="110" align="center">
+				<template #default="{ row }">
+					<el-tag :type="row.statusType" effect="plain">{{ row.status }}</el-tag>
+				</template>
+			</el-table-column>
+			<el-table-column label="操作" width="230" fixed="right" align="center">
+				<template #default="{ row }">
+					<el-button link type="primary" @click="goJob">调度管理</el-button>
+					<el-button link type="primary" @click="goLogs(row.entityCode)">日志</el-button>
+					<el-button link type="primary">映射配置</el-button>
+				</template>
+			</el-table-column>
+		</el-table>
+	</AidopDemoShell>
+</template>
+
+<script setup lang="ts" name="aidopDataPlatformSyncTasks">
+import { useRouter } from 'vue-router';
+import AidopDemoShell from '../components/AidopDemoShell.vue';
+
+const router = useRouter();
+
+const rows = [
+	{ entityCode: 'S3_DEMAND', entityName: 'S3 需求计划', direction: '入站', directionType: 'success', protocol: 'DB', sourceName: '旧 SQL Server', targetTable: 'ado_s3_demand_schedule', syncMode: 'INCR', incrColumn: 'update_time', jobId: 'job_s3_mdp_sync_transform', lastRun: '10 分钟前', status: '运行中', statusType: 'success' },
+	{ entityCode: 'S3_PO', entityName: 'S3 采购订单', direction: '入站', directionType: 'success', protocol: 'DB', sourceName: '旧 SQL Server', targetTable: 'ado_s3_purchase_order', syncMode: 'INCR', incrColumn: 'modified_at', jobId: 'job_s3_mdp_sync_transform', lastRun: '28 分钟前', status: '正常', statusType: 'success' },
+	{ entityCode: 'ITEM_MASTER', entityName: '物料主数据 API 拉取', direction: '入站', directionType: 'success', protocol: 'REST', sourceName: 'ERP 主库', targetTable: 'mdp_std_item', syncMode: 'PAGE', incrColumn: 'cursor', jobId: 'job_mdp_item_master_sync', lastRun: '1 小时前', status: '部分成功', statusType: 'warning' },
+	{ entityCode: 'WMS_STOCK', entityName: '库存快照 API 入站', direction: '入站', directionType: 'warning', protocol: 'Webhook', sourceName: '仓储系统 API', targetTable: 'mdp_stg_wms_stock', syncMode: 'PUSH', incrColumn: '-', jobId: 'job_mdp_wms_stock_sync', lastRun: '2 小时前', status: '待处理', statusType: 'danger' },
+	{ entityCode: 'BI_METRIC_API', entityName: '指标服务 API 出站', direction: '出站', directionType: 'primary', protocol: 'REST', sourceName: '数据中台', targetTable: '/api/mdp/metrics', syncMode: 'SERVICE', incrColumn: '-', jobId: '-', lastRun: '实时', status: '规划中', statusType: 'info' },
+];
+
+function goJob() {
+	router.push('/platform/job');
+}
+
+function goLogs(entityCode: string) {
+	router.push({ path: '/aidop/data-platform/sync-logs', query: { entityCode } });
+}
+</script>
+
+<style scoped>
+.mb12 {
+	margin-bottom: 12px;
+}
+</style>

+ 241 - 0
doc/plan/Ai-DOP数据中台建设规划方案.md

@@ -0,0 +1,241 @@
+# Ai-DOP 数据中台建设规划方案
+
+> 用途:预算沟通与建设方案说明。本文先作为 PPT 内容底稿,后续可按页转换为正式演示文稿。
+> 版本:2026-05-17 初稿
+
+---
+
+## 1. 封面与定位
+
+**主题**:Ai-DOP 数据中台建设规划方案
+
+**副标题**:统一工厂系统对接
+
+**一句话定位**:
+
+> 数据中台统一负责工厂第三方系统接入、数据标准化、任务调度、日志追踪、数据服务输出和行业数据沉淀,为 Ai-DOP 业务作业、异常监控、指标看板以及政府行业统计提供可信数据底座。
+
+---
+
+## 2. 为什么要做
+
+### 2.1 企业侧:解耦并标准化各工厂第三方系统对接
+
+不同工厂已建设或正在使用不同的 ERP、MES、WMS、供应商系统、设备平台等第三方系统。这些系统的数据结构、接口协议、字段编码和业务口径差异较大,如果 Ai-DOP 各业务模块直接对接这些系统,会带来重复开发和长期维护成本。
+
+```text
+不同工厂系统差异大
+ERP / MES / WMS / 供应商系统 / 设备平台
+        ↓
+接口协议不同、字段口径不同、数据质量不同
+        ↓
+每接一个工厂都要重复开发,维护成本高
+```
+
+数据中台需要解决的问题:
+
+- 统一 DB 同步、API 入站、API 拉取、API 出站等接入方式。
+- 标准化字段、编码、业务口径和数据质量校验。
+- 让 Ai-DOP 业务模块不直接依赖各工厂第三方系统。
+- 后续新增工厂时,更多通过配置、映射和少量适配完成接入,而不是重复开发。
+
+### 2.2 政府侧:沉淀真实业务数据,辅助行业统计和政策制定
+
+数据中台不仅服务企业内部业务,也可以在授权、脱敏和安全隔离前提下,沉淀对接工厂的真实业务数据,形成区域或行业级运行态势分析基础。
+
+```text
+工厂真实业务数据
+订单 / 生产 / 采购 / 库存 / 交付 / 异常
+        ↓
+脱敏汇聚、标准化指标、行业趋势分析
+        ↓
+辅助政府了解行业运行状态和制定政策
+```
+
+可支撑的政府侧价值:
+
+- 行业产能、订单、交付、库存等运行态势统计。
+- 产业链供应风险、异常波动、产能瓶颈识别。
+- 政策扶持、产业治理、资源调度的数据依据。
+- 在脱敏和授权前提下形成可持续积累的区域工业数据资产。
+
+---
+
+## 3. 最终要做成什么
+
+### 3.1 总体形态
+
+```text
+外部系统/来源  →  接入层        →  数据中台      →  服务出口      →  业务消费/回写
+ERP/SAP          DB同步            原始接入区       API出站          业务作业
+MES              API入站           清洗校验         查询服务          异常监控
+WMS              API拉取           字段映射         同步日志          指标看板
+外部系统                           标准模型                         政府统计/行业分析
+```
+
+### 3.2 建设目标
+
+数据中台最终要形成一个可持续扩展的数据服务底座:
+
+- **统一接入**:支持数据库同步、API 入站、API 定时拉取、API 出站服务。
+- **统一治理**:将各工厂、各系统的数据进行清洗、校验、字段映射和标准化。
+- **统一调度**:复用 Admin.NET 任务调度能力,支持定时执行、手动触发、重试补偿。
+- **统一追踪**:记录同步批次、读取行数、写入行数、异常样本、耗时和状态。
+- **统一服务**:为业务作业、异常监控、指标看板、外部系统和政府行业统计提供数据服务。
+
+---
+
+## 4. 核心能力清单
+
+### 4.1 统一接入能力
+
+- DB 同步:对接 ERP、MES、WMS 等系统数据库表和视图。
+- API 入站:外部系统主动调用数据中台 API 推送业务事件。
+- API 拉取:数据中台定时调用外部 API,支持分页、游标、时间窗。
+- API 出站:向业务系统、BI、移动端或外部系统提供标准数据服务。
+
+### 4.2 标准化治理能力
+
+- 字段映射:将源系统字段转换为统一标准字段。
+- 编码转换:统一物料、供应商、客户、工序、库位等编码口径。
+- 清洗校验:处理必填、格式、字典、重复、异常样本。
+- 标准模型:沉淀主数据、业务事实、主题模型和指标模型。
+
+### 4.3 数据服务能力
+
+- 服务业务作业:采购、生产、交付等业务页面直接消费数据中台模型。
+- 服务异常监控:为异常识别、责任分派、任务流转提供统一数据口径。
+- 服务指标看板:为经营指标、模块看板、BI 报表提供稳定数据来源。
+- 服务外部系统:通过标准 API 支持外部系统查询或回写。
+
+### 4.4 运行可观测能力
+
+- 同步日志:记录每次任务的来源、目标、行数、耗时、状态。
+- 异常样本:保留失败行和错误原因,便于排查。
+- 批次追踪:按批次追踪数据从来源到标准模型的全过程。
+- 重试补偿:支持失败任务重试和数据补偿。
+
+### 4.5 行业数据沉淀能力
+
+- 脱敏汇聚:在授权和安全隔离前提下汇聚工厂真实业务数据。
+- 区域统计:形成订单、产能、库存、交付、异常等区域统计口径。
+- 趋势分析:辅助识别行业波动、产业链风险和产能瓶颈。
+- 政策辅助:为政策制定、扶持评估和资源调度提供数据依据。
+
+---
+
+## 5. 分阶段实施计划
+
+| 阶段 | 周期 | 目标 | 主要交付 |
+|---|---:|---|---|
+| P1 数据服务底座构建 | 2-3 周 | 完成数据中台基础入口和 S3 试点固化 | 数据地图、数据源、数据任务、任务日志、S3 试点链路固化 |
+| P2 核心业务接入 | 6-8 周 | 接入 ERP/MES/WMS 关键数据,支撑 S3/S4/S8 | 核心数据源接入、主题模型、关键业务页面消费、异常监控数据口径 |
+| P3 平台化增强 | 6-10 周 | 提升配置化、可观测、服务化和行业统计能力 | 配置化映射、API 出站、重试补偿、权限审计、监控告警、行业统计模型 |
+
+### 5.1 P1 数据服务底座构建
+
+目标是先形成可演示、可扩展、可继续迭代的数据服务底座:
+
+- 完成数据中台管理入口和数据地图。
+- 固化数据源、数据任务、日志追踪的基本页面。
+- 复用 Admin.NET 任务调度,不重复造调度管理。
+- 以 S3 现有试点为样板,明确后续模块接入范式。
+
+### 5.2 P2 核心业务接入
+
+目标是把数据中台从演示入口推进到真实业务链路:
+
+- 接入 ERP/MES/WMS 的关键数据。
+- 优先服务 S3 供应协同、S4 采购执行、S8 异常监控。
+- 建立主数据、业务事实和主题模型。
+- 形成从数据源到业务页面、异常监控、指标看板的闭环。
+
+### 5.3 P3 平台化增强
+
+目标是形成可规模化复制到更多工厂和业务模块的平台能力:
+
+- 字段映射、编码转换、清洗规则配置化。
+- API 出站服务能力完善,支持外部系统和政府侧统计服务。
+- 任务重试、补偿、告警、审计能力完善。
+- 建立区域/行业统计模型和数据安全治理机制。
+
+---
+
+## 6. 成本预估
+
+> 说明:以下为建设工作量区间,用于预算测算。实际商务报价可结合单人月成本、现场联调复杂度和数据安全要求进一步细化。
+
+| 成本项 | 估算 |
+|---|---:|
+| 后端开发 | 2-3 人月 |
+| 前端开发 | 1.5-2 人月 |
+| 数据建模 / ETL | 2-3 人月 |
+| 联调测试 | 1-1.5 人月 |
+| 项目管理与上线支持 | 0.5-1 人月 |
+| **合计** | **7-10.5 人月** |
+
+成本影响因素:
+
+- 对接工厂数量和第三方系统数量。
+- 外部系统接口质量和文档完整度。
+- 是否需要现场部署、专线/VPN、数据脱敏和安全审计。
+- 行业统计模型的复杂度和报表口径确认成本。
+
+---
+
+## 7. 预期收益
+
+### 7.1 企业侧收益
+
+- 新工厂接入从重复开发变成标准接入,缩短实施周期。
+- Ai-DOP 模块不直接绑定第三方系统,降低后续维护成本。
+- 数据口径统一,异常监控和指标看板更可信。
+- 同步过程可追溯,问题定位从查代码变成查批次日志。
+- 后续 AI 分析、ChatBI、经营指标有稳定的数据底座。
+
+### 7.2 政府侧收益
+
+- 获取更接近真实生产经营的一线数据。
+- 支撑行业运行监测、产业链风险识别和产能趋势分析。
+- 为政策制定、资源调度、扶持评估提供数据依据。
+- 形成可持续积累的区域工业数据资产。
+
+### 7.3 平台侧收益
+
+- 形成可复制的数据接入和治理框架。
+- 后续 S4、S8、S9 及更多模块可沿用同一套数据底座。
+- 减少页面、报表、异常规则各自重复取数和重复清洗。
+- 提升产品演示效果和预算沟通说服力。
+
+---
+
+## 8. 风险与控制
+
+| 风险 | 表现 | 控制措施 |
+|---|---|---|
+| 工厂系统差异大 | 每个工厂系统、字段、接口都不同 | 先做标准接入模型,再按工厂做适配 |
+| 外部接口不稳定 | 调用失败、数据延迟、字段变化 | 支持重试、日志、补偿和异常样本追踪 |
+| 数据口径争议 | 指标、异常、业务模型口径不一致 | 每个主题模型明确业务负责人和验收口径 |
+| 数据安全与隐私 | 涉及企业经营数据和政府统计数据 | 做租户隔离、授权访问、脱敏汇聚、审计追踪 |
+| 范围过大 | 一次性接入所有系统导致周期失控 | 按 S3 试点范式分阶段推进,先关键链路后全面推广 |
+
+---
+
+## 9. 后续 PPT 化建议
+
+正式 PPT 建议控制在 8 页:
+
+1. 封面与定位
+2. 为什么要做:企业侧 + 政府侧
+3. 最终要做成什么:数据中台全景图
+4. 核心能力清单:五类能力卡片
+5. 分阶段实施计划:P1/P2/P3 路线图
+6. 成本预估:人月区间和影响因素
+7. 预期收益:企业侧、政府侧、平台侧
+8. 风险与控制:风险表 + 控制措施
+
+视觉风格建议:
+
+- 少文字,多图示。
+- 重点使用“数据流转图”“能力卡片”“路线图”“成本表”“收益对照图”。
+- 面向预算沟通时,优先讲业务价值和可分阶段落地,不展开过多底层技术细节。

+ 19 - 0
doc/plan/新系统数据库设计方案.md

@@ -67,6 +67,7 @@
 | **配置驱动编排** | **未**替代代码 | `mdp_pipeline*`、`mdp_field_mapping` 等在设计中已定义;**运行时管道**仍以 **C# 内 SQL** 为主;非代码配置与热更新见专项讨论 |
 | **S4 / S8 扩展** | 文档与 DDL 先行 | 已提供 [`数据中台模块扩展开发指南-S3范式.md`](./数据中台模块扩展开发指南-S3范式.md)(含四库示意与 Cursor 协作模板);[`doc/db/mdp/`](../db/mdp/) 下脚本覆盖 **mdp 配置/管道/API 占位表** 及 **L2 五张 `dwd_*` 基础 DDL**(供 S4 采购宽表、S8 关联分析),**不在此进展中**代表 S4/S8 业务作业已全部实现 |
 | **与方案命名差异** | 已知并存 | 试点路径使用 `mdp_std_purchase_order`、`dwd_supplier_delivery` 等**业务向命名**;方案正文中另有 `mdp_std_po`、`dwd_po_trans` 等主题命名——宽表层以脚本与设计 §6.2 为准,后续由 ETL 对齐或映射 |
+| **数据中台管理界面** | 首版演示闭环 | **2026-05-17** 已在 `Ai-DOP` 下新增「数据中台管理」入口,包含数据中台工作台、数据地图、数据源管理、数据任务管理、数据任务日志。数据地图已可视化表达 DB 同步、API 入站、API 拉取、API 出站,以及「外部系统/来源 → 接入层 → 数据中台 → 服务出口 → 业务消费/回写」主链路;节点支持点击展开详情抽屉并跳转到数据任务、日志和 Admin.NET 任务调度。当前为演示/入口闭环,页面数据仍为静态演示,尚未接真实 API 网关与完整 `mdp_*` CRUD |
 
 **相关人员入口**:扩展开发与 PR 自检见《数据中台模块扩展开发指南-S3范式》;建表顺序见 `doc/db/mdp/README.md`。
 
@@ -3738,6 +3739,24 @@ VALUES
 
 **核心思路**:每个外部系统的一张待同步表 = 一个 `SysJobDetail`,用现有 Quartz 调度,新建 4 张管理表做配置持久化。
 
+#### 4.5.0 首版演示实现进展(2026-05-17)
+
+本轮已先按“演示可见、入口集中、调度复用”的原则实现数据中台管理首版,重点不是重造调度或一次性完成 ETL 引擎,而是把数据中台相关能力集中到一个业务入口,便于 AIDOPDemo 演示和后续继续接真实配置。
+
+已落地内容:
+
+- **菜单入口**:在 `Ai-DOP` 下新增「数据中台管理」目录,子入口包括「数据中台工作台」「数据地图」「数据源管理」「数据任务管理」「数据任务日志」。
+- **数据地图**:以图形化方式呈现 `外部系统/来源 → 接入层 → 数据中台 → 服务出口 → 业务消费/回写`,并区分 DB 同步、API 入站、API 拉取、API 出站。
+- **节点展开**:数据地图节点可点击,右侧抽屉展示节点说明、关联对象、操作入口;例如点击「DB 同步接入」可查看关联任务、目标表、调度 Job,并跳转到数据任务、任务调度或任务日志。
+- **调度复用**:不重复实现 Cron、启停、调度历史等能力;数据任务中的调度入口跳转到 Admin.NET 现有任务调度(如 `job_s3_mdp_sync_transform`)。
+- **API 口径补充**:数据中台首版展示口径已覆盖数据库表同步、API 入站接收、API 定时拉取、API 出站服务,后续真实实现再接 `mdp_source` / `mdp_entity` / `mdp_sync_log` / API 网关或服务层。
+
+当前边界:
+
+- 页面数据仍为**静态演示数据**,主要用于说明能力边界和演示流转路径。
+- 尚未把所有 `mdp_*` 配置后台 CRUD 化,也未实现完整 API 网关、出站授权、回放重试、灰度发布等生产能力。
+- 真实 ETL 执行层仍以当前已落地的 S3 试点为主;更多模块推广应继续按 S3 范式分批接入。
+
 #### 4.5.2 新增管理表
 
 ```sql

BIN
doc/ppt/Ai-DOP数据中台建设规划方案.pptx


+ 3 - 3
server/Admin.NET.Web.Entry/Admin.NET.Web.Entry.csproj

@@ -11,9 +11,9 @@
     <GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
     <Copyright>Admin.NET</Copyright>
     <Description>Admin.NET 通用权限开发平台</Description>
-    <AssemblyVersion>1.0.110</AssemblyVersion>
-    <FileVersion>1.0.110</FileVersion>
-    <Version>1.0.110</Version>
+    <AssemblyVersion>1.0.111</AssemblyVersion>
+    <FileVersion>1.0.111</FileVersion>
+    <Version>1.0.111</Version>
   </PropertyGroup>
 
   <ItemGroup>

+ 77 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/Infrastructure/AidopMenuLinkSync.cs

@@ -24,6 +24,7 @@ public static class AidopMenuLinkSync
         // Database.json 常关闭 EnableInitSeed/EnableIncreSeed,新增种子菜单不会入库;先补插 S2 三级结构再挂租户/角色。
         EnsureS2ManufacturingCollaborationSeedMenus(db);
         EnsureS4ProcurementExecutionSeedMenus(db);
+        EnsureDataPlatformSeedMenus(db);
         NormalizeMaterialSubstitutionMenu(db);
         NormalizeS1OrderWorkOrderParents(db);
         NormalizeS2ManufacturingParents(db);
@@ -153,6 +154,82 @@ public static class AidopMenuLinkSync
             1329004300002L,     // 供应商欠料看板
         };
 
+        var existingMenus = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).ToList();
+        var existing = existingMenus.Select(m => m.Id).ToHashSet();
+        var cumulativeIds = new HashSet<long>(existing);
+        var toInsert = new List<SysMenu>();
+        foreach (var id in orderedIds)
+        {
+            if (cumulativeIds.Contains(id))
+                continue;
+            if (!byId.TryGetValue(id, out var row))
+                continue;
+            if (row.Pid != 0 && !cumulativeIds.Contains(row.Pid))
+                continue;
+            toInsert.Add(row);
+            cumulativeIds.Add(id);
+        }
+
+        if (toInsert.Count > 0)
+            db.Insertable(toInsert).ExecuteCommand();
+
+        // 已插入过的演示菜单也需要随种子纠偏;例如「数据源管理」改为「数据地图」时,旧动态路由不更新会导致 404。
+        foreach (var menu in existingMenus)
+        {
+            if (!byId.TryGetValue(menu.Id, out var seed))
+                continue;
+            var needFix = menu.Pid != seed.Pid
+                          || menu.Title != seed.Title
+                          || menu.Path != seed.Path
+                          || menu.Name != seed.Name
+                          || menu.Component != seed.Component
+                          || menu.Icon != seed.Icon
+                          || menu.Type != seed.Type
+                          || menu.OrderNo != seed.OrderNo
+                          || menu.Redirect != seed.Redirect
+                          || menu.IsHide != seed.IsHide
+                          || menu.Remark != seed.Remark;
+            if (!needFix)
+                continue;
+
+            menu.Pid = seed.Pid;
+            menu.Title = seed.Title;
+            menu.Path = seed.Path;
+            menu.Name = seed.Name;
+            menu.Component = seed.Component;
+            menu.Icon = seed.Icon;
+            menu.Type = seed.Type;
+            menu.OrderNo = seed.OrderNo;
+            menu.Redirect = seed.Redirect;
+            menu.IsHide = seed.IsHide;
+            menu.Remark = seed.Remark;
+            db.Updateable(menu)
+                .UpdateColumns(m => new { m.Pid, m.Title, m.Path, m.Name, m.Component, m.Icon, m.Type, m.OrderNo, m.Redirect, m.IsHide, m.Remark })
+                .ExecuteCommand();
+        }
+    }
+
+    /// <summary>
+    /// 数据中台管理:数据库关闭种子时,补插 Ai-DOP 下的数据中台统一入口和演示子页。
+    /// </summary>
+    private static void EnsureDataPlatformSeedMenus(ISqlSugarClient db)
+    {
+        const long aidopRootId = 1320990000101L;
+
+        var fullSeed = new global::Admin.NET.Plugin.AiDOP.SysMenuSeedData().HasData().ToList();
+        var byId = fullSeed.GroupBy(m => m.Id).ToDictionary(g => g.Key, g => g.First());
+
+        var orderedIds = new long[]
+        {
+            aidopRootId,
+            1320990000400L,
+            1320990000401L,
+            1320990000402L,
+            1320990000403L,
+            1320990000404L,
+            1320990000405L,
+        };
+
         var existing = db.Queryable<SysMenu>().Where(m => orderedIds.Contains(m.Id)).Select(m => m.Id).ToList().ToHashSet();
         var cumulativeIds = new HashSet<long>(existing);
         var toInsert = new List<SysMenu>();

+ 52 - 0
server/Plugins/Admin.NET.Plugin.AiDOP/SeedData/SysMenuSeedData.cs

@@ -84,6 +84,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
 
         foreach (var m in BuildAidopSmartOpsSeedMenus(ct))
             list.Add(m);
+        foreach (var m in BuildDataPlatformSeedMenus(ct))
+            list.Add(m);
         foreach (var m in BuildS0SalesMenus(ct))
             list.Add(m);
         foreach (var m in BuildS0SupplyMenus(ct))
@@ -267,6 +269,56 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
         }
     }
 
+    /// <summary>
+    /// 数据中台管理:集中承载数据源、同步任务、日志和质量看板;调度能力跳转复用 Admin.NET 任务调度。
+    /// </summary>
+    private static IEnumerable<SysMenu> BuildDataPlatformSeedMenus(DateTime ct)
+    {
+        const long dataPlatformDirId = 1320990000400L;
+        yield return new SysMenu
+        {
+            Id = dataPlatformDirId,
+            Pid = AidopRootId,
+            Title = "数据中台管理",
+            Path = "/aidop/data-platform",
+            Name = "aidopDataPlatformRoot",
+            Component = "Layout",
+            Icon = "ele-SetUp",
+            Type = MenuTypeEnum.Dir,
+            CreateTime = ct,
+            OrderNo = 253,
+            Redirect = "/aidop/data-platform/overview",
+            Remark = "Ai-DOP 数据中台管理入口,调度能力复用平台任务调度"
+        };
+
+        var children = new (long Id, string Path, string Name, string Title, string Component, string Icon, int Order, string Remark)[]
+        {
+            (1320990000401L, "/aidop/data-platform/overview", "aidopDataPlatformOverview", "数据中台工作台", "/aidop/data-platform/overview", "ele-DataBoard", 10, "数据中台质量与运行概览"),
+            (1320990000402L, "/aidop/data-platform/data-map", "aidopDataPlatformDataMap", "数据地图", "/aidop/data-platform/dataMap", "ele-Share", 20, "数据源、接入方式、处理层级和服务出口可视化"),
+            (1320990000403L, "/aidop/data-platform/sources", "aidopDataPlatformSources", "数据源管理", "/aidop/data-platform/sources", "ele-Coin", 30, "外部系统、数据库与 API 连接配置入口"),
+            (1320990000404L, "/aidop/data-platform/sync-tasks", "aidopDataPlatformSyncTasks", "数据任务管理", "/aidop/data-platform/syncTasks", "ele-Operation", 40, "DB/API 入站出站任务与 Admin.NET 调度任务关联入口"),
+            (1320990000405L, "/aidop/data-platform/sync-logs", "aidopDataPlatformSyncLogs", "数据任务日志", "/aidop/data-platform/syncLogs", "ele-Tickets", 50, "数据任务批次日志与异常样本入口"),
+        };
+
+        foreach (var (id, path, name, title, component, icon, order, remark) in children)
+        {
+            yield return new SysMenu
+            {
+                Id = id,
+                Pid = dataPlatformDirId,
+                Title = title,
+                Path = path,
+                Name = name,
+                Component = component,
+                Icon = icon,
+                Type = MenuTypeEnum.Menu,
+                CreateTime = ct,
+                OrderNo = order,
+                Remark = remark
+            };
+        }
+    }
+
     private static string ResolveComponent(string modCode, int leafIndex) =>
         ComponentOverrides.TryGetValue((modCode, leafIndex), out var c) ? c : "/aidop/planning/index";