feat: add interactive campus workflow graph
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent
8dff9847e3
commit
248078113c
+23
-6
@@ -15,8 +15,10 @@ python3 -m http.server 8080
|
||||
## 核心产品能力
|
||||
|
||||
- 校级供需决策总览、周期切换、专业搜索和七步产品导览
|
||||
- “企业需求 → 学校接收/分析快照 → 专业建议决策 → 任务/项目 → 执行证据 → 企业评价 → 校级验收 → 报告审批发布 → 数据使用反馈 → 下一周期复核”校园业务闭环
|
||||
- 闭环实体、角色动作、审批历史与审计事件使用 `localStorage` 持久化,支持刷新续办、Schema 迁移和样板重置
|
||||
- “企业需求 → 学校接收/分析快照 → 专业建议决策 → 任务/项目 → 执行证据 → 企业评价 → 校级验收 → 报告审批发布 → 数据使用反馈 → 成效复核”11 节点可交互业务流程图
|
||||
- 流程节点直接读取同一套业务实体和状态机,支持节点选择、输入输出、责任角色、关联 ID、当前待办定位及节点内办理
|
||||
- “需整改 → 补充证据”“验收退回 → 补充证据”“进入下一周期 → 企业需求”三类回流边与实际状态同步
|
||||
- 流程实体、角色动作、审批历史与审计事件使用 `localStorage` 持久化,支持刷新续办、Schema 迁移和样板重置
|
||||
- 技术接收同时强校验租户、包号、版本、交付记录数和完整 64 位 SHA-256;任一不一致只写失败审计,不推进回执或分析快照
|
||||
- 交付包记录数与分析样本量分开保存:样板包回执为 `2,846,320` 条,生成的校园分析快照为 `128,642` 条
|
||||
- 专业技能供需、岗位流向、薪资对标、课程技能映射与专业对比
|
||||
@@ -43,6 +45,20 @@ python3 -m http.server 8080
|
||||
- **校级数据**:来自学校实习、就业、教务等业务系统,数据权属归学校。
|
||||
- **企业数据**:由合作企业确认提交,按照合作协议限定使用范围。
|
||||
|
||||
## 可交互业务流程图
|
||||
|
||||
“业务流程”不是状态卡片列表,而是由节点和有向边构成的可操作流程:
|
||||
|
||||
- 11 个节点分别对应企业需求、分析快照、专业建议、任务、项目、证据、企业评价、校级验收、报告、使用反馈和成效复核;
|
||||
- 节点状态分为已完成、当前待办、退回/整改和前置未满足,流程完成度与当前责任角色实时计算;
|
||||
- 点击任一节点可查看节点输入、节点输出、关键字段、责任角色和关联实体;
|
||||
- 当前角色具备权限且前置条件满足时,可在节点详情直接办理;无权限时显示责任边界而非伪造可用按钮;
|
||||
- 企业评价需整改、校级验收退回和进入下一周期均使用独立回流边表达;
|
||||
- 桌面端显示完整流程拓扑,移动端转换为带连接线的纵向节点流程,并用三张可点击关系卡保留整改、退回与下一周期回流;
|
||||
- “进入下一周期”会归档上一周期报告,创建带 `previousVersionId` 的新需求、快照和回执版本,并清空新周期尚未产生的下游实体。
|
||||
|
||||
流程图只负责展示并调用现有状态机,不维护第二套演示状态。因此原有租户、SHA、记录数、证据冻结、独立审批和角色授权规则继续生效。
|
||||
|
||||
## 数据与报告依赖图谱
|
||||
|
||||
“数据资产”页提供两种追溯方向:
|
||||
@@ -78,6 +94,7 @@ python3 -m http.server 8080
|
||||
- 当前报告一经生成,支撑该版本的任务证据冻结;新增证据必须进入下一周期或重开新版本;
|
||||
- 阶段进度必须达到 100%,企业导师才能提交通过或整改评价;
|
||||
- 企业评价为“需整改”时,校级验收继续阻断,补充证据并重新评价后才可进入验收;
|
||||
- 就业侧最新反馈为“需补充”时,成效复核继续阻断;改为“可用”后才可复核,周期关闭后不再接受追加反馈;
|
||||
- 报告提交人不能批准或退回自己的提交,非校级管理者不显示报告审批与发布动作;
|
||||
- 报告正式发布前,就业老师不能登记使用反馈,下载和打印均明确标识非正式版。
|
||||
|
||||
@@ -102,7 +119,7 @@ python3 -m http.server 8080
|
||||
|
||||
## 开发与验收说明
|
||||
|
||||
- 闭环样板使用命名空间 `eduIndustry.campusClosedLoop.TENANT-HZVTC.v1`,当前 `schemaVersion=2`。页面“重置样板”会恢复种子;旧 V1 或不满足深层实体/历史数组不变量的损坏 V2 状态,均按安全策略失效并重建 V2 种子。
|
||||
- 业务流程样板沿用命名空间 `eduIndustry.campusClosedLoop.TENANT-HZVTC.v1`,当前 `schemaVersion=2`。页面“重置样板”会恢复种子;旧 V1 或不满足深层实体/历史数组不变量的损坏 V2 状态,均按安全策略失效并重建 V2 种子。
|
||||
- 为避免静态原型长期演示导致浏览器存储无界增长,审计、证据、反馈、自定义集合和实体历史采用明确保留上限;正式系统应改用服务端分页、归档和不可变审计存储。
|
||||
- 共享样板标识为租户 `TENANT-HZVTC`、厂商包 `EDU-PKG-20260823-01`、版本 `V2026.08.23`、学校回执 `RCT-HZVTC-20260823-01`。这些标识只用于本静态原型的业务链对照,不表示厂商端和校园端页面实时同步。
|
||||
- 当前前端不向外部服务发送请求,可直接在隔离网络运行。
|
||||
@@ -111,10 +128,10 @@ python3 -m http.server 8080
|
||||
- JavaScript 语法检查:`node --check app.js && node --check report-visuals.js`
|
||||
- 推荐桌面验收视口为 `1440 × 900`,移动验收视口为 `390 × 844`。
|
||||
|
||||
新增闭环预览:
|
||||
业务流程图预览:
|
||||
|
||||
- `preview-campus-closed-loop.png`
|
||||
- `preview-campus-closed-loop-mobile.png`
|
||||
- `preview-campus-workflow.png`
|
||||
- `preview-campus-workflow-mobile.png`
|
||||
|
||||
数据依赖图谱预览:
|
||||
|
||||
|
||||
+919
-103
File diff suppressed because it is too large
Load Diff
+719
-76
@@ -2255,6 +2255,10 @@ body textarea {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.app-shell .eyebrow {
|
||||
font-size: 13px !important;
|
||||
}
|
||||
|
||||
.app-shell .main-content span,
|
||||
.app-shell .main-content em {
|
||||
font-size: 13px !important;
|
||||
@@ -2722,113 +2726,558 @@ body textarea {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.loop-chain {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
.loop-status-panel {
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(circle at 96% 2%, rgba(198, 230, 111, 0.08), transparent 24%),
|
||||
white;
|
||||
}
|
||||
|
||||
.loop-stage {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
grid-template-columns: 36px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
padding: 13px;
|
||||
background: #f8faf8;
|
||||
border: 1px solid var(--line-soft);
|
||||
.loop-flow-toolbar {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin: 18px 0 12px;
|
||||
padding: 10px;
|
||||
background: #f3f7f4;
|
||||
border: 1px solid #e1e9e3;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
.loop-stage > i {
|
||||
.loop-flow-summary {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.loop-flow-summary > span {
|
||||
min-width: 0;
|
||||
padding: 9px 11px;
|
||||
background: white;
|
||||
border: 1px solid #e5ebe6;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.loop-flow-summary small,
|
||||
.loop-flow-summary strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loop-flow-summary small {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.loop-flow-summary strong {
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
color: var(--ink);
|
||||
font-size: 14px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-flow-toolbar > .button {
|
||||
align-self: stretch;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.loop-flow-scroll {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
background:
|
||||
linear-gradient(rgba(35, 107, 79, 0.045) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(35, 107, 79, 0.045) 1px, transparent 1px),
|
||||
#f7f9f7;
|
||||
background-size: 25px 25px;
|
||||
border: 1px solid #dce5df;
|
||||
border-radius: 13px;
|
||||
}
|
||||
|
||||
.loop-flow-canvas {
|
||||
--loop-canvas-width: 1000px;
|
||||
--loop-canvas-height: 600px;
|
||||
--loop-node-width: 170px;
|
||||
--loop-node-height: 130px;
|
||||
position: relative;
|
||||
width: var(--loop-canvas-width);
|
||||
min-width: var(--loop-canvas-width);
|
||||
height: var(--loop-canvas-height);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.loop-flow-edges,
|
||||
.loop-flow-nodes {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.loop-flow-edges {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.loop-flow-edge path {
|
||||
fill: none;
|
||||
stroke: #bdc9c2;
|
||||
stroke-width: 2;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.loop-flow-edge text {
|
||||
fill: #77867e;
|
||||
stroke: #f7f9f7;
|
||||
stroke-width: 5px;
|
||||
paint-order: stroke;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.loop-flow-edge.complete path {
|
||||
stroke: var(--green);
|
||||
}
|
||||
|
||||
.loop-flow-edge.current path {
|
||||
stroke: #d69a31;
|
||||
stroke-dasharray: 7 5;
|
||||
}
|
||||
|
||||
.loop-flow-edge.risk path {
|
||||
stroke: #c76656;
|
||||
stroke-dasharray: 6 4;
|
||||
}
|
||||
|
||||
.loop-flow-edge.branch path {
|
||||
stroke: #96a69d;
|
||||
stroke-dasharray: 5 5;
|
||||
}
|
||||
|
||||
#loop-arrow-complete path {
|
||||
fill: var(--green);
|
||||
}
|
||||
|
||||
#loop-arrow-current path {
|
||||
fill: #d69a31;
|
||||
}
|
||||
|
||||
#loop-arrow-risk path {
|
||||
fill: #c76656;
|
||||
}
|
||||
|
||||
#loop-arrow-locked path,
|
||||
#loop-arrow-branch path {
|
||||
fill: #96a69d;
|
||||
}
|
||||
|
||||
.loop-flow-node {
|
||||
position: absolute;
|
||||
left: var(--loop-x);
|
||||
top: var(--loop-y);
|
||||
z-index: 2;
|
||||
display: grid;
|
||||
width: var(--loop-node-width);
|
||||
height: var(--loop-node-height);
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
padding: 11px;
|
||||
color: var(--ink);
|
||||
background: white;
|
||||
border: 1px solid #dfe7e1;
|
||||
border-radius: 11px;
|
||||
box-shadow: 0 7px 20px rgba(24, 61, 46, 0.07);
|
||||
text-align: left;
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.loop-flow-node:hover,
|
||||
.loop-flow-node:focus-visible {
|
||||
z-index: 3;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 28px rgba(24, 61, 46, 0.14);
|
||||
}
|
||||
|
||||
.loop-flow-node.selected {
|
||||
z-index: 3;
|
||||
outline: 3px solid rgba(35, 135, 101, 0.16);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.loop-flow-node.complete {
|
||||
background: #f2f8f5;
|
||||
border-color: #a9d0ba;
|
||||
}
|
||||
|
||||
.loop-flow-node.complete.has-risk {
|
||||
background: #fff8f5;
|
||||
border-color: #d9a293;
|
||||
box-shadow: inset 0 3px #c76656;
|
||||
}
|
||||
|
||||
.loop-flow-node.current {
|
||||
background: #fffaf0;
|
||||
border-color: #ddac55;
|
||||
box-shadow: 0 9px 25px rgba(181, 124, 27, 0.16);
|
||||
}
|
||||
|
||||
.loop-flow-node.risk {
|
||||
background: #fff5f2;
|
||||
border-color: #db9b8c;
|
||||
box-shadow: 0 9px 25px rgba(173, 78, 69, 0.13);
|
||||
}
|
||||
|
||||
.loop-flow-node.locked {
|
||||
color: #66736c;
|
||||
background: #f6f8f6;
|
||||
border-style: dashed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.loop-flow-node.locked.has-risk {
|
||||
background: #fff8f5;
|
||||
border-color: #d9a293;
|
||||
}
|
||||
|
||||
.loop-flow-node-step {
|
||||
display: grid;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
place-items: center;
|
||||
color: var(--green);
|
||||
background: #e7f3ed;
|
||||
color: #6f7e76;
|
||||
background: #e9eeeb;
|
||||
border-radius: 9px;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.loop-stage.review > i {
|
||||
color: #916217;
|
||||
background: #fff6df;
|
||||
}
|
||||
|
||||
.loop-stage.risk > i {
|
||||
color: #ad4e45;
|
||||
background: #fff0ed;
|
||||
}
|
||||
|
||||
.loop-stage.done > i {
|
||||
.loop-flow-node.complete .loop-flow-node-step {
|
||||
color: white;
|
||||
background: var(--green);
|
||||
}
|
||||
|
||||
.loop-stage span,
|
||||
.loop-stage small,
|
||||
.loop-stage strong,
|
||||
.loop-stage em {
|
||||
.loop-flow-node.complete.has-risk .loop-flow-node-step {
|
||||
background: #b95b4d;
|
||||
}
|
||||
|
||||
.loop-flow-node.current .loop-flow-node-step {
|
||||
color: #825813;
|
||||
background: #ffecc5;
|
||||
}
|
||||
|
||||
.loop-flow-node.risk .loop-flow-node-step {
|
||||
color: #a34e42;
|
||||
background: #ffe3dc;
|
||||
}
|
||||
|
||||
.loop-flow-node-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.loop-flow-node-copy small,
|
||||
.loop-flow-node-copy strong,
|
||||
.loop-flow-node-copy em {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.loop-stage small {
|
||||
.loop-flow-node-copy small {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.loop-stage strong {
|
||||
margin: 3px 0;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 13px;
|
||||
.loop-flow-node-copy strong {
|
||||
margin: 4px 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.loop-stage em {
|
||||
color: var(--green-2);
|
||||
font-size: 13px;
|
||||
.loop-flow-node-copy em {
|
||||
overflow: hidden;
|
||||
color: var(--green);
|
||||
font-style: normal;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-entity-grid {
|
||||
.loop-flow-node.current .loop-flow-node-copy em {
|
||||
color: #9b6817;
|
||||
}
|
||||
|
||||
.loop-flow-node.complete.has-risk .loop-flow-node-copy em,
|
||||
.loop-flow-node.risk .loop-flow-node-copy em {
|
||||
color: #a34e42;
|
||||
}
|
||||
|
||||
.loop-flow-node.locked .loop-flow-node-copy em {
|
||||
color: #88948e;
|
||||
}
|
||||
|
||||
.loop-flow-node-owner {
|
||||
grid-column: 1 / -1;
|
||||
margin-top: 4px;
|
||||
padding-top: 7px;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
border-top: 1px solid rgba(94, 113, 103, 0.14);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-flow-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 11px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.loop-mobile-branches {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loop-flow-legend > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.loop-flow-legend i {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
background: #f4f6f4;
|
||||
border: 2px dashed #aebbb4;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.loop-flow-legend i.complete {
|
||||
background: var(--green);
|
||||
border-color: var(--green);
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.loop-flow-legend i.current {
|
||||
background: #fff2d5;
|
||||
border-color: #d59a31;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.loop-flow-legend i.risk {
|
||||
background: #ffe5df;
|
||||
border-color: #c76656;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.loop-node-detail {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loop-node-hero {
|
||||
display: grid;
|
||||
grid-template-columns: 54px minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 13px;
|
||||
padding: 16px;
|
||||
color: #dcebe4;
|
||||
background:
|
||||
radial-gradient(circle at 92% 10%, rgba(198, 230, 111, 0.14), transparent 26%),
|
||||
linear-gradient(135deg, #173f33, #102e26);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.loop-node-hero.current {
|
||||
background:
|
||||
radial-gradient(circle at 92% 10%, rgba(255, 228, 167, 0.18), transparent 26%),
|
||||
linear-gradient(135deg, #4b3819, #2e281d);
|
||||
}
|
||||
|
||||
.loop-node-hero.risk {
|
||||
background:
|
||||
radial-gradient(circle at 92% 10%, rgba(255, 213, 202, 0.16), transparent 26%),
|
||||
linear-gradient(135deg, #543128, #33231f);
|
||||
}
|
||||
|
||||
.loop-node-hero.locked {
|
||||
color: #d7dfda;
|
||||
background: linear-gradient(135deg, #45534c, #303a35);
|
||||
}
|
||||
|
||||
.loop-node-hero-step {
|
||||
display: grid;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
place-items: center;
|
||||
color: #173f33;
|
||||
background: var(--lime);
|
||||
border-radius: 12px;
|
||||
font-size: 17px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.loop-node-hero small,
|
||||
.loop-node-hero strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loop-node-hero small {
|
||||
color: #9db3aa;
|
||||
}
|
||||
|
||||
.loop-node-hero strong {
|
||||
margin-top: 5px;
|
||||
color: white;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.loop-node-hero p {
|
||||
margin: 7px 0 0;
|
||||
color: #a8bdb4;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.loop-node-hero > em {
|
||||
padding: 6px 8px;
|
||||
color: var(--lime);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 7px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-node-io {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 28px minmax(0, 1fr);
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loop-node-io article {
|
||||
padding: 12px;
|
||||
background: #f7faf8;
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.loop-node-io span,
|
||||
.loop-node-io strong {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loop-node-io span {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.loop-node-io strong {
|
||||
margin-top: 6px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.loop-node-io > i {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: #91a49a;
|
||||
}
|
||||
|
||||
.loop-node-io svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.loop-node-facts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.loop-entity-grid article {
|
||||
.loop-node-facts > span {
|
||||
min-width: 0;
|
||||
padding: 15px;
|
||||
background: #f9fbf9;
|
||||
padding: 11px;
|
||||
background: white;
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: 11px;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.loop-entity-grid span,
|
||||
.loop-entity-grid strong,
|
||||
.loop-entity-grid small {
|
||||
.loop-node-facts small,
|
||||
.loop-node-facts strong {
|
||||
display: block;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.loop-entity-grid span {
|
||||
color: var(--green);
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.loop-entity-grid strong {
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loop-entity-grid small {
|
||||
margin-top: 5px;
|
||||
.loop-node-facts small {
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.loop-node-facts strong {
|
||||
margin-top: 5px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.loop-node-relations {
|
||||
padding: 12px;
|
||||
background: #f6f8f6;
|
||||
border: 1px solid var(--line-soft);
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.loop-node-relations > span {
|
||||
display: block;
|
||||
margin-bottom: 7px;
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.loop-node-relations > div {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.loop-node-relations button {
|
||||
max-width: 100%;
|
||||
padding: 6px 8px;
|
||||
overflow: hidden;
|
||||
color: var(--green);
|
||||
background: white;
|
||||
border: 1px solid #cfdfd6;
|
||||
border-radius: 6px;
|
||||
font-family: "SFMono-Regular", Consolas, monospace;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.loop-node-action {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
background: #edf7f1;
|
||||
border: 1px solid #d2e6da;
|
||||
border-radius: 9px;
|
||||
}
|
||||
|
||||
.loop-node-action.locked,
|
||||
.loop-node-action.readonly {
|
||||
background: #f6f8f6;
|
||||
border-color: var(--line-soft);
|
||||
}
|
||||
|
||||
.loop-node-action span,
|
||||
.loop-node-action strong,
|
||||
.loop-node-action small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.loop-node-action small {
|
||||
margin-top: 4px;
|
||||
color: var(--muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.loop-role-boundary {
|
||||
@@ -2857,12 +3306,13 @@ body textarea {
|
||||
}
|
||||
|
||||
.loop-action-list > button {
|
||||
display: flex;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
grid-template-columns: 34px minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 13px;
|
||||
gap: 9px;
|
||||
padding: 11px;
|
||||
color: var(--ink);
|
||||
background: white;
|
||||
border: 1px solid var(--line-soft);
|
||||
@@ -2870,33 +3320,60 @@ body textarea {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.loop-action-list > button:not(:disabled):hover {
|
||||
background: #f3f9f6;
|
||||
border-color: #a9cfbb;
|
||||
}
|
||||
|
||||
.loop-action-list button:disabled {
|
||||
opacity: 0.58;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.loop-action-list > button > i {
|
||||
display: grid;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
place-items: center;
|
||||
color: var(--green);
|
||||
background: var(--green-soft);
|
||||
border-radius: 8px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.loop-action-list span,
|
||||
.loop-action-list strong,
|
||||
.loop-action-list small {
|
||||
.loop-action-list small,
|
||||
.loop-action-list em {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.loop-action-list strong {
|
||||
margin-top: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.loop-action-list small {
|
||||
margin-top: 4px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.loop-action-list em {
|
||||
margin-top: 4px;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
line-height: 1.45;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-action-list b {
|
||||
flex: 0 0 auto;
|
||||
color: var(--green);
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@@ -3085,11 +3562,13 @@ body textarea {
|
||||
.loop-action-panel {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.loop-flow-summary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.loop-chain,
|
||||
.loop-entity-grid,
|
||||
.loop-modal-fields {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -3103,6 +3582,170 @@ body textarea {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loop-flow-toolbar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loop-flow-summary {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.loop-flow-toolbar > .button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loop-flow-scroll {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.loop-flow-canvas {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.loop-flow-edges {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loop-flow-nodes {
|
||||
position: static;
|
||||
display: grid;
|
||||
gap: 13px;
|
||||
}
|
||||
|
||||
.loop-flow-node {
|
||||
position: relative;
|
||||
left: auto;
|
||||
top: auto;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.loop-flow-node:not(:last-child)::after {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: -14px;
|
||||
left: 28px;
|
||||
width: 2px;
|
||||
height: 14px;
|
||||
background: #9fb1a7;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.loop-flow-legend {
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.loop-mobile-branches {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.loop-mobile-branches button {
|
||||
display: grid;
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 4px 8px;
|
||||
padding: 10px;
|
||||
color: var(--ink);
|
||||
background: #f7f9f7;
|
||||
border: 1px dashed #b9c6bf;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.loop-mobile-branches button.active.risk {
|
||||
background: #fff4f1;
|
||||
border-color: #cf7969;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.loop-mobile-branches button.active.cycle {
|
||||
background: #fff9ec;
|
||||
border-color: #d9a44a;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.loop-mobile-branches span {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.loop-mobile-branches i,
|
||||
.loop-mobile-branches em {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.loop-mobile-branches i {
|
||||
color: #8c9992;
|
||||
}
|
||||
|
||||
.loop-mobile-branches small {
|
||||
grid-column: 1 / -1;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.loop-mobile-branches em {
|
||||
align-self: start;
|
||||
color: var(--muted);
|
||||
font-weight: 800;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.loop-mobile-branches button.active.risk em {
|
||||
color: #ad4e45;
|
||||
}
|
||||
|
||||
.loop-mobile-branches button.active.cycle em {
|
||||
color: #956313;
|
||||
}
|
||||
|
||||
.loop-node-hero {
|
||||
grid-template-columns: 44px minmax(0, 1fr);
|
||||
padding: 13px;
|
||||
}
|
||||
|
||||
.loop-node-hero-step {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.loop-node-hero > em {
|
||||
grid-column: 2;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.loop-node-io {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.loop-node-io > i {
|
||||
height: 14px;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.loop-node-facts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.loop-node-action {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.loop-node-action .button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.report-approval-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
|
||||
+34
-14
@@ -51,7 +51,7 @@
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M20 7h-9a4 4 0 0 0-4 4v1M4 17h9a4 4 0 0 0 4-4v-1M16 4l4 3-4 3M8 14l-4 3 4 3" />
|
||||
</svg>
|
||||
<span>闭环工作台</span>
|
||||
<span>业务流程</span>
|
||||
<span class="nav-dot"></span>
|
||||
</button>
|
||||
<button class="nav-item" data-view="profession" data-menu-key="profession">
|
||||
@@ -218,13 +218,13 @@
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<!-- 校园业务闭环 -->
|
||||
<section class="view" id="view-loop" data-title="闭环工作台">
|
||||
<!-- 校园业务流程 -->
|
||||
<section class="view" id="view-loop" data-title="业务流程">
|
||||
<div class="page-heading heading-with-actions">
|
||||
<div>
|
||||
<div class="eyebrow"><span></span> CAMPUS CLOSED LOOP</div>
|
||||
<h1>校园端业务闭环工作台</h1>
|
||||
<p>从企业需求到下一周期复核,一条样板链按角色推进、按实体留痕、刷新后继续。</p>
|
||||
<div class="eyebrow"><span></span> CAMPUS WORKFLOW</div>
|
||||
<h1>企业需求到成效复核流程</h1>
|
||||
<p>点击流程节点查看实体、责任人与输入输出;当前角色可在已解锁节点直接推进业务。</p>
|
||||
</div>
|
||||
<button class="button secondary" id="resetClosedLoop">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 11a8 8 0 1 0-2.34 5.66M20 4v7h-7" /></svg>
|
||||
@@ -232,31 +232,51 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="loop-identity-strip" id="loopIdentityStrip"></div>
|
||||
<div class="loop-layout">
|
||||
<section class="loop-main">
|
||||
<article class="panel loop-status-panel">
|
||||
<div class="panel-header">
|
||||
<div><span class="panel-kicker">实体状态链</span><h2>样板闭环进度</h2></div>
|
||||
<div><span class="panel-kicker">INTERACTIVE WORKFLOW</span><h2>需求到复核流程图</h2></div>
|
||||
<span class="version-badge" id="loopSchemaBadge">SCHEMA V2</span>
|
||||
</div>
|
||||
<div class="loop-chain" id="loopChain"></div>
|
||||
<div class="loop-flow-toolbar">
|
||||
<div class="loop-flow-summary" id="loopFlowSummary"></div>
|
||||
<button class="button tertiary" id="loopFocusCurrent">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 8v8m-4-4h8M4 12a8 8 0 1 0 16 0 8 8 0 0 0-16 0Z" /></svg>
|
||||
定位待办节点
|
||||
</button>
|
||||
</div>
|
||||
<div class="loop-flow-scroll">
|
||||
<div class="loop-flow-canvas">
|
||||
<svg class="loop-flow-edges" id="loopFlowEdges" preserveAspectRatio="none" aria-hidden="true"></svg>
|
||||
<div class="loop-flow-nodes" id="loopChain" role="group" aria-label="校园业务流程节点"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="loop-mobile-branches" id="loopMobileBranches" aria-label="流程回流关系"></div>
|
||||
<div class="loop-flow-legend" aria-label="流程状态图例">
|
||||
<span><i class="complete"></i>已完成</span>
|
||||
<span><i class="current"></i>当前待办</span>
|
||||
<span><i class="risk"></i>退回或整改</span>
|
||||
<span><i class="locked"></i>前置未满足</span>
|
||||
</div>
|
||||
</article>
|
||||
<div class="loop-layout">
|
||||
<section class="loop-main">
|
||||
<article class="panel loop-detail-panel">
|
||||
<div class="panel-header">
|
||||
<div><span class="panel-kicker">业务实体</span><h2>关联对象与关键字段</h2></div>
|
||||
<div><span class="panel-kicker" id="loopNodeKicker">NODE DETAIL</span><h2 id="loopNodeTitle">节点详情</h2></div>
|
||||
<span class="status-badge" id="loopNodeStatus">待选择</span>
|
||||
</div>
|
||||
<div class="loop-entity-grid" id="loopEntityGrid"></div>
|
||||
<div class="loop-node-detail" id="loopNodeDetail" aria-live="polite"></div>
|
||||
</article>
|
||||
<article class="panel loop-audit-panel">
|
||||
<div class="panel-header">
|
||||
<div><span class="panel-kicker">AUDIT EVENTS</span><h2>闭环审计事件</h2></div>
|
||||
<div><span class="panel-kicker">AUDIT EVENTS</span><h2>流程审计事件</h2></div>
|
||||
<span class="panel-count" id="loopAuditCount">0 条</span>
|
||||
</div>
|
||||
<div class="loop-audit-list" id="loopAuditList"></div>
|
||||
</article>
|
||||
</section>
|
||||
<aside class="panel loop-action-panel">
|
||||
<div class="panel-header"><div><span class="panel-kicker">ROLE ACTION</span><h2 id="loopRoleActionTitle">当前角色下一步</h2></div></div>
|
||||
<div class="panel-header"><div><span class="panel-kicker">ROLE QUEUE</span><h2 id="loopRoleActionTitle">当前角色待办节点</h2></div></div>
|
||||
<div class="loop-role-boundary" id="loopRoleBoundary"></div>
|
||||
<div class="loop-action-list" id="loopActionList"></div>
|
||||
</aside>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 290 KiB |
@@ -21,8 +21,8 @@
|
||||
| 校级管理者 | 决策总览 | 查看全校供需态势、批准重点行动、协调学院与企业资源 | 可查看全校汇总和授权范围内明细 |
|
||||
| 校级技术管理员 | 学校数据运维中心 | 接入学校数据源、执行更新、处理质量问题、管理版本与审计 | 可维护学校租户数据,不默认拥有业务审批权 |
|
||||
| 学院负责人 | 专业洞察 | 复核本院专业差距、推进课程和项目调整、提交阶段证据 | 仅本学院及所属专业 |
|
||||
| 专业负责人 | 闭环工作台 | 决策专业建议、推进关联任务与项目、提交证据、完成下一周期复核 | 仅负责专业及其关联需求、项目和任务 |
|
||||
| 就业老师 | 闭环工作台 | 确认就业侧数据可用性、记录使用反馈和下一周期关注项 | 就业业务域及授权汇总,不拥有校级报告审批权 |
|
||||
| 专业负责人 | 业务流程 | 决策专业建议、推进关联任务与项目、提交证据、完成下一周期复核 | 仅负责专业及其关联需求、项目和任务 |
|
||||
| 就业老师 | 业务流程 | 确认就业侧数据可用性、记录使用反馈和下一周期关注项 | 就业业务域及授权汇总,不拥有校级报告审批权 |
|
||||
| 企业导师 | 企业协同工作台 | 提交企业需求、参与项目和课程共建、确认企业评价 | 仅本企业授权及参与项目 |
|
||||
| 厂商超级管理员 | 平台运营中心 | 维护基础数据包、租户、平台账号、备份、服务和平台审计 | 默认不可读取学校明文业务数据 |
|
||||
|
||||
@@ -36,6 +36,32 @@
|
||||
- 校级管理者负责任务校级验收以及业务报告的批准、退回和正式发布;校级技术管理员不得以数据质量职责代替业务审批。
|
||||
- 专业负责人保存建议“采纳/暂缓/驳回”的理由、操作者和时间;企业导师只能评价本企业对象;就业老师只能记录就业侧使用反馈。
|
||||
|
||||
### 2.2 可交互业务流程图
|
||||
|
||||
校园端“业务流程”必须用节点和有向边表达真实状态流,而不是把实体状态平铺成卡片:
|
||||
|
||||
1. 企业需求;
|
||||
2. 接收与分析快照;
|
||||
3. 专业建议决策;
|
||||
4. 改进任务;
|
||||
5. 关联共育项目;
|
||||
6. 执行证据;
|
||||
7. 企业评价;
|
||||
8. 校级验收;
|
||||
9. 报告审批发布;
|
||||
10. 业务使用反馈;
|
||||
11. 成效复核。
|
||||
|
||||
节点必须直接读取正式业务实体和状态机,不得另建仅供可视化的第二套状态。每个节点至少展示状态、责任角色、输入、输出、关键字段和关联实体;点击节点可查看详情,在角色授权和前置条件均满足时可直接办理。
|
||||
|
||||
流程至少包含以下回流关系:
|
||||
|
||||
- 企业评价“需整改”回到执行证据;
|
||||
- 校级验收“退回整改”回到执行证据,并要求企业重新评价;
|
||||
- 成效复核“进入下一周期”回到新的企业需求版本。
|
||||
|
||||
桌面端展示完整拓扑与分支连线;移动端转换为保持顺序和状态的纵向节点流程,并以可点击关系卡保留三类回流,不得因隐藏 SVG 丢失分支语义。选择“进入下一周期”时必须完整归档上一周期报告实体及其证据、审批历史和关联 ID,按持久化周期序列生成不重复的新需求、新快照、新回执及下游实体 ID,并清空新周期尚未产生的任务、证据、评价、报告、反馈和复核。就业侧最新反馈为“需补充”时不得进入复核,周期关闭后不得追加反馈或重复复核。流程图中的按钮只是交互入口,正式权限仍必须由服务端逐对象校验。
|
||||
|
||||
## 3. 三类数据资产
|
||||
|
||||
### 3.1 厂商基础数据包
|
||||
@@ -175,7 +201,9 @@
|
||||
## 8. 产品验收基线
|
||||
|
||||
- 七种角色默认入口和菜单范围正确;
|
||||
- 闭环工作台按企业导师、校级技术管理员、专业负责人、学院负责人、校级管理者和就业老师显示可执行动作与禁用原因,厂商超级管理员只读;
|
||||
- 业务流程图完整显示 11 个节点、主路径和三类回流边,节点选择会联动输入、输出、责任角色、关键字段和关联实体;
|
||||
- 业务流程按企业导师、校级技术管理员、专业负责人、学院负责人、校级管理者和就业老师显示可办理节点与禁用原因,厂商超级管理员只读;
|
||||
- 当前角色可从已解锁节点直接打开原有业务表单,节点状态推进后流程图、详情、角色待办和审计同步更新;
|
||||
- 闭环状态、审批历史和审计事件刷新后保留,重置后恢复 Schema V2 种子;
|
||||
- 技术接收后当前企业需求版本锁定,报告生成后当前任务证据冻结;
|
||||
- 损坏的 V2 深层实体或历史数组不会导致页面崩溃,而是按安全策略重建并记录 `SECURITY_SCHEMA_RESET`;
|
||||
@@ -203,7 +231,7 @@
|
||||
2. 决策总览展示可信数据和本期关键变化;
|
||||
3. 专业洞察展示岗位、薪资、课程和技能证据;
|
||||
4. 校企协同展示企业需求到项目验收;
|
||||
5. 闭环工作台依次切换企业导师、技术管理员、专业负责人、校级管理者和就业老师验证状态流;
|
||||
5. 在业务流程图依次点击需求、快照、建议、任务、项目、证据、评价、验收、报告、反馈和复核节点,并切换角色验证办理与回流;
|
||||
6. 改进任务展示负责人、进度和验收证据;
|
||||
7. 在数据资产页双向演示“报告需要哪些数据”和“数据可形成哪些报告”,切换只看缺口并进入补数责任处置;
|
||||
8. 切换校级技术管理员,展示数据导入、更新、质量、接收反馈和审计;
|
||||
|
||||
Reference in New Issue
Block a user