feat: 四端设计总览生成、截图补全与模板工具链完善
- 新增 operator/onsite/professional/employee-app 四端 design-overview.html - 补全约 30 张弹窗/状态截图,重拍长图(fullPage) - 新增 design-overview-template.html 模板与 design-overview skill - index.html 注册设计总览入口、修复 md-viewer 路径 - 删除已迁移的 md-viewer.html 和本地服务器方案文档 - 更新 package.json 依赖与项目背景文档 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -0,0 +1,110 @@
|
||||
# Skill: 生成模块设计总览页
|
||||
|
||||
## 触发方式
|
||||
用户说"生成设计总览"、"design overview"或指定某个模块需要生成设计总览时触发。
|
||||
|
||||
## 输入参数
|
||||
- **模块路径**(必须):如 `employee-app/emergency-medical`
|
||||
- **模块中文名**(必须):如 `应急就医员工端APP`
|
||||
|
||||
## 执行流程
|
||||
|
||||
### Step 1: 扫描模块页面结构
|
||||
1. 列出模块目录下所有 `.html` 文件
|
||||
2. 逐个读取每个 HTML 文件,识别:
|
||||
- 页面标题和主要内容
|
||||
- 弹窗/对话框(`display:none` 的 modal、mask、overlay、panel 等)
|
||||
- Tab 切换 / 状态切换(语音/文字模式、展开/收起等)
|
||||
- 触发弹窗的 CSS 类名和切换方式
|
||||
3. 读取 `doc/PRD.md`(如存在),获取业务主线分组
|
||||
|
||||
### Step 2: 整理页面形态清单 & 复用已有截图
|
||||
1. 检查 `doc/screenshots/` 目录下已有的截图文件
|
||||
2. 将已有截图与页面一一对应(按文件名匹配)
|
||||
3. 标记出缺失的弹窗/状态截图(仅这些需要新截取)
|
||||
4. **已有截图直接复用,不重新截取**
|
||||
|
||||
### Step 3: 截取缺失的弹窗截图
|
||||
|
||||
**截图工具优先级**(按可用性依次尝试):
|
||||
1. **agent-browser**(首选):原生 Rust CLI,截图质量最好
|
||||
2. **chrome-devtools MCP**(备选):通过 MCP 协议控制 Chrome
|
||||
3. **Puppeteer 脚本**(兜底):使用 `_templates/screenshot-modals.js`
|
||||
|
||||
#### 使用 agent-browser 截图:
|
||||
```bash
|
||||
agent-browser open "file:///<绝对路径>/<页面>.html"
|
||||
agent-browser set viewport 390 844 2
|
||||
# 触发弹窗状态
|
||||
agent-browser snapshot -i # 获取元素 ref
|
||||
agent-browser click @eN # 或用 evaluate 执行 JS
|
||||
agent-browser screenshot "<输出路径>.jpg"
|
||||
agent-browser close
|
||||
```
|
||||
|
||||
#### 使用 Puppeteer 脚本截图(兜底方案):
|
||||
编写 config.json 配置文件,只包含缺失的截图任务:
|
||||
```json
|
||||
{
|
||||
"baseDir": "<模块路径>",
|
||||
"tasks": [
|
||||
{ "page": "xxx.html", "output": "xxx-modal.jpg",
|
||||
"action": "() => { /* 触发弹窗的JS */ }" }
|
||||
]
|
||||
}
|
||||
```
|
||||
运行:`node _templates/screenshot-modals.js <config.json>`
|
||||
|
||||
**关键参数**(Puppeteer):
|
||||
```javascript
|
||||
// 视口必须大于 phone-shell 尺寸(390px宽),避免布局溢出
|
||||
page.setViewport({ width: 500, height: 900, deviceScaleFactor: 1 });
|
||||
|
||||
// 只截取 .phone-shell 元素,避免外壳嵌套感
|
||||
const shell = await page.$('.phone-shell');
|
||||
if (shell) await shell.screenshot({ path, type: 'jpeg', quality: 85 });
|
||||
```
|
||||
|
||||
**截图规范(参考项目背景.md 7.4 节)**:
|
||||
- 输出格式:JPEG,质量 85%(兼顾清晰度与文件体积)
|
||||
- deviceScaleFactor 设为 1(设计总览卡片仅 260px 宽,不需要 2x 分辨率)
|
||||
- 单张截图体积控制在 **60KB 以内**,避免项目膨胀
|
||||
- 视口宽度 500px(大于 phone-shell 的 390px 避免溢出),高度 900px
|
||||
- 只截取 `.phone-shell` 元素的可视区域(不展开滚动内容),保持手机屏幕视角
|
||||
- 截图命名规范:`{页面名}-{状态}.jpg`,如 `emergency-chat-confirm.jpg`
|
||||
- 常规页面截图命名与 HTML 文件名一致(去掉 `.html`),如 `index.jpg`
|
||||
|
||||
### Step 4: 生成 design-overview.html
|
||||
使用 `_templates/design-overview-template.html` 模板,填充:
|
||||
- 导航标题:`{模块中文名} · 设计总览`
|
||||
- 各业务主线 section(id 为 `sec-{英文缩写}`)
|
||||
- 每张截图一个 card,包含图片、标签名、弹窗/状态标记
|
||||
- **截图路径必须与 `doc/screenshots/` 下的实际文件名一致**
|
||||
|
||||
**样式规范(必须与模板一致)**:
|
||||
- 暗色背景 `#1a1a2e`
|
||||
- 卡片尺寸 `260×564px`(手机比例 375:812 的 ~70%)
|
||||
- 每条业务线横向滚动不换行(`overflow-x: auto`)
|
||||
- 弹窗标记用橙色 tag `tag--modal`
|
||||
- 状态切换标记用蓝色 tag `tag--state`
|
||||
- 点击图片灯箱放大查看
|
||||
|
||||
### Step 5: 注册到导航页
|
||||
在根目录 `index.html` 的 `SITE_MAP` 中,找到对应模块的 `pages` 数组,在第一个位置插入:
|
||||
```javascript
|
||||
{ file:'../doc/design-overview.html', label:'页面总览' },
|
||||
```
|
||||
|
||||
## 输出物
|
||||
1. `{模块目录}/doc/design-overview.html` — 设计总览页
|
||||
2. `{模块目录}/doc/screenshots/*-{状态}.jpg` — 新截取的弹窗截图(仅缺失部分)
|
||||
3. 根 `index.html` SITE_MAP 更新
|
||||
|
||||
## 注意事项
|
||||
- **复用优先**:已有截图绝不重新截取,只补拍缺失的弹窗/状态截图
|
||||
- 截图脚本 `_templates/screenshot-modals.js` 放在 _templates 目录,每个模块复用
|
||||
- Puppeteer 视口必须设为 `500x900`(大于 phone-shell 的 390px),避免"空白页嵌套小页面"问题
|
||||
- 截图只截取 `.phone-shell` 元素,不截整个页面
|
||||
- 所有模块的 design-overview 共用同一套 CSS 样式(从模板复制)
|
||||
- 不要遗漏任何页面形态(弹窗、Tab 状态、展开/收起等)
|
||||
- 截图配置 json 文件用完后保留在项目根目录,方便后续重新截取
|
||||
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{MODULE_NAME}} - 页面设计总览</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
||||
|
||||
/* 顶部导航 */
|
||||
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; }
|
||||
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
|
||||
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
|
||||
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
|
||||
|
||||
/* 业务主线区块 */
|
||||
.section { padding: 24px 24px 8px; }
|
||||
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
|
||||
|
||||
/* 横向滚动容器 */
|
||||
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
||||
.scroll-row::-webkit-scrollbar { height: 6px; }
|
||||
.scroll-row::-webkit-scrollbar-track { background: transparent; }
|
||||
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
|
||||
|
||||
/* 卡片 */
|
||||
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
|
||||
.card:hover { transform: scale(1.02); }
|
||||
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
|
||||
.card:hover .card__img { border-color: #4fc3f7; }
|
||||
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
||||
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
|
||||
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
|
||||
.tag--modal { background: #4a2800; color: #ffa940; }
|
||||
.tag--state { background: #002a4a; color: #4fc3f7; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
|
||||
|
||||
/* 灯箱 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.show { display: flex; }
|
||||
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
|
||||
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
|
||||
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">{{MODULE_NAME}} · 设计总览</span>
|
||||
<!-- {{NAV_LINKS}} -->
|
||||
</nav>
|
||||
|
||||
<!-- {{SECTIONS}} -->
|
||||
|
||||
<div style="height:40px;"></div>
|
||||
|
||||
<!-- 灯箱 -->
|
||||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox__close">×</span>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div class="lightbox__name" id="lightbox-name"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLightbox(src, name) {
|
||||
document.getElementById('lightbox-img').src = src;
|
||||
document.getElementById('lightbox-name').textContent = name;
|
||||
document.getElementById('lightbox').classList.add('show');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,218 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>驻场人员端 - 页面设计总览</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
||||
|
||||
/* 顶部导航 */
|
||||
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; }
|
||||
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
|
||||
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
|
||||
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
|
||||
|
||||
/* 业务主线区块 */
|
||||
.section { padding: 24px 24px 8px; }
|
||||
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
|
||||
|
||||
/* 横向滚动容器 */
|
||||
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
||||
.scroll-row::-webkit-scrollbar { height: 6px; }
|
||||
.scroll-row::-webkit-scrollbar-track { background: transparent; }
|
||||
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
|
||||
|
||||
/* 卡片 */
|
||||
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
|
||||
.card:hover { transform: scale(1.02); }
|
||||
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
|
||||
.card:hover .card__img { border-color: #4fc3f7; }
|
||||
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
||||
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
|
||||
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
|
||||
.tag--modal { background: #4a2800; color: #ffa940; }
|
||||
.tag--state { background: #002a4a; color: #4fc3f7; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
|
||||
|
||||
/* 灯箱 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.show { display: flex; }
|
||||
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
|
||||
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
|
||||
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">驻场人员端 · 设计总览</span>
|
||||
<a class="nav__link" href="#sec-workorder">应急工单</a>
|
||||
<a class="nav__link" href="#sec-serious">大病就医</a>
|
||||
<a class="nav__link" href="#sec-knowledge">知识库</a>
|
||||
<a class="nav__link" href="#sec-profile">个人中心</a>
|
||||
</nav>
|
||||
|
||||
<!-- 应急工单 -->
|
||||
<section class="section" id="sec-workorder">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">应急工单</h2>
|
||||
<span class="section__count">6 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/index.jpg','工作台首页')">
|
||||
<div class="card__img"><img src="screenshots/index.jpg" alt="工作台首页"></div>
|
||||
<div class="card__label">工作台首页</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing.jpg','工单详情(进行中)')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing.jpg" alt="工单详情(进行中)"></div>
|
||||
<div class="card__label">工单详情(进行中)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing-complete.jpg','进行中·完成处理弹窗')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing-complete.jpg" alt="进行中·完成处理弹窗"></div>
|
||||
<div class="card__label">进行中·完成处理弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing-contact.jpg','进行中·联系操作人员')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing-contact.jpg" alt="进行中·联系操作人员"></div>
|
||||
<div class="card__label">进行中·联系操作人员</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing-transfer.jpg','进行中·转单选择')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing-transfer.jpg" alt="进行中·转单选择"></div>
|
||||
<div class="card__label">进行中·转单选择</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail.jpg','工单详情(已完成)')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail.jpg" alt="工单详情(已完成)"></div>
|
||||
<div class="card__label">工单详情(已完成)</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 大病就医 -->
|
||||
<section class="section" id="sec-serious">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">大病就医</h2>
|
||||
<span class="section__count">6 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-list.jpg','大病就医列表')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-list.jpg" alt="大病就医列表"></div>
|
||||
<div class="card__label">大病就医列表</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending.jpg','详情(待处理)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending.jpg" alt="详情(待处理)"></div>
|
||||
<div class="card__label">详情(待处理)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending-submit.jpg','待处理·提交挂号结果')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending-submit.jpg" alt="待处理·提交挂号结果"></div>
|
||||
<div class="card__label">待处理·提交挂号结果</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending-transfer.jpg','待处理·转单选择')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending-transfer.jpg" alt="待处理·转单选择"></div>
|
||||
<div class="card__label">待处理·转单选择</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-done.jpg','详情(已完成)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-done.jpg" alt="详情(已完成)"></div>
|
||||
<div class="card__label">详情(已完成)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-cancelled.jpg','详情(已取消)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-cancelled.jpg" alt="详情(已取消)"></div>
|
||||
<div class="card__label">详情(已取消)</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 知识库 -->
|
||||
<section class="section" id="sec-knowledge">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">知识库</h2>
|
||||
<span class="section__count">2 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-index.jpg','知识库首页')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-index.jpg" alt="知识库首页"></div>
|
||||
<div class="card__label">知识库首页</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-article-detail.jpg','知识详情')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-article-detail.jpg" alt="知识详情"></div>
|
||||
<div class="card__label">知识详情</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 个人中心 -->
|
||||
<section class="section" id="sec-profile">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">个人中心</h2>
|
||||
<span class="section__count">5 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index.jpg','个人中心主页')">
|
||||
<div class="card__img"><img src="screenshots/profile-index.jpg" alt="个人中心主页"></div>
|
||||
<div class="card__label">个人中心主页</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index-logout.jpg','退出登录确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/profile-index-logout.jpg" alt="退出登录确认弹窗"></div>
|
||||
<div class="card__label">退出登录确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/edit-profile.jpg','修改个人信息')">
|
||||
<div class="card__img"><img src="screenshots/edit-profile.jpg" alt="修改个人信息"></div>
|
||||
<div class="card__label">修改个人信息</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/edit-profile-avatar.jpg','修改信息·头像选择')">
|
||||
<div class="card__img"><img src="screenshots/edit-profile-avatar.jpg" alt="修改信息·头像选择"></div>
|
||||
<div class="card__label">修改信息·头像选择</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">操作列表</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/change-pwd.jpg','修改密码')">
|
||||
<div class="card__img"><img src="screenshots/change-pwd.jpg" alt="修改密码"></div>
|
||||
<div class="card__label">修改密码</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="height:40px;"></div>
|
||||
|
||||
<!-- 灯箱 -->
|
||||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox__close">×</span>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div class="lightbox__name" id="lightbox-name"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLightbox(src, name) {
|
||||
document.getElementById('lightbox-img').src = src;
|
||||
document.getElementById('lightbox-name').textContent = name;
|
||||
document.getElementById('lightbox').classList.add('show');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('show');
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,257 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>操作人员端 - 页面设计总览</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
||||
|
||||
/* 顶部导航 */
|
||||
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; }
|
||||
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
|
||||
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
|
||||
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
|
||||
|
||||
/* 业务主线区块 */
|
||||
.section { padding: 24px 24px 8px; }
|
||||
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
|
||||
|
||||
/* 横向滚动容器 */
|
||||
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
||||
.scroll-row::-webkit-scrollbar { height: 6px; }
|
||||
.scroll-row::-webkit-scrollbar-track { background: transparent; }
|
||||
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
|
||||
|
||||
/* 卡片 */
|
||||
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
|
||||
.card:hover { transform: scale(1.02); }
|
||||
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
|
||||
.card:hover .card__img { border-color: #4fc3f7; }
|
||||
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
||||
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
|
||||
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
|
||||
.tag--modal { background: #4a2800; color: #ffa940; }
|
||||
.tag--state { background: #002a4a; color: #4fc3f7; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
|
||||
|
||||
/* 灯箱 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.show { display: flex; }
|
||||
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
|
||||
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
|
||||
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">操作人员端 · 设计总览</span>
|
||||
<a class="nav__link" href="#sec-emergency">应急求助</a>
|
||||
<a class="nav__link" href="#sec-serious">大病就医</a>
|
||||
<a class="nav__link" href="#sec-knowledge">知识库</a>
|
||||
<a class="nav__link" href="#sec-profile">个人中心</a>
|
||||
</nav>
|
||||
|
||||
<!-- 应急求助 -->
|
||||
<section class="section" id="sec-emergency">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">应急求助</h2>
|
||||
<span class="section__count">11 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/index.jpg','工作台首页')">
|
||||
<div class="card__img"><img src="screenshots/index.jpg" alt="工作台首页"></div>
|
||||
<div class="card__label">工作台首页</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat.jpg','应急聊天(进行中)')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat.jpg" alt="应急聊天(进行中)"></div>
|
||||
<div class="card__label">应急聊天(进行中)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing.jpg','应急聊天·媒体面板')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing.jpg" alt="应急聊天·媒体面板"></div>
|
||||
<div class="card__label">应急聊天·媒体面板</div>
|
||||
<div class="card__tags"><span class="tag tag--state">面板</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/end-emergency.jpg','结束应急弹窗')">
|
||||
<div class="card__img"><img src="screenshots/end-emergency.jpg" alt="结束应急弹窗"></div>
|
||||
<div class="card__label">结束应急弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-done.jpg','应急聊天(已结束)')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-done.jpg" alt="应急聊天(已结束)"></div>
|
||||
<div class="card__label">应急聊天(已结束)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/dispatch-onsite.jpg','驻场派单')">
|
||||
<div class="card__img"><img src="screenshots/dispatch-onsite.jpg" alt="驻场派单"></div>
|
||||
<div class="card__label">驻场派单</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/dispatch-ambulance.jpg','救护车支援')">
|
||||
<div class="card__img"><img src="screenshots/dispatch-ambulance.jpg" alt="救护车支援"></div>
|
||||
<div class="card__label">救护车支援</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/rescue-summary.jpg','救助信息汇总')">
|
||||
<div class="card__img"><img src="screenshots/rescue-summary.jpg" alt="救助信息汇总"></div>
|
||||
<div class="card__label">救助信息汇总</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/patient-location.jpg','患者定位地图')">
|
||||
<div class="card__img"><img src="screenshots/patient-location.jpg" alt="患者定位地图"></div>
|
||||
<div class="card__label">患者定位地图</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing.jpg','工单详情(进行中)')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing.jpg" alt="工单详情(进行中)"></div>
|
||||
<div class="card__label">工单详情(进行中)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail.jpg','工单详情(已完成)')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail.jpg" alt="工单详情(已完成)"></div>
|
||||
<div class="card__label">工单详情(已完成)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-dialog.jpg','工单详情·补充小结弹窗')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-dialog.jpg" alt="工单详情·补充小结弹窗"></div>
|
||||
<div class="card__label">工单详情·补充小结弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 大病就医 -->
|
||||
<section class="section" id="sec-serious">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">大病就医</h2>
|
||||
<span class="section__count">8 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-list.jpg','大病就医列表')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-list.jpg" alt="大病就医列表"></div>
|
||||
<div class="card__label">大病就医列表</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending.jpg','详情(待确认)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending.jpg" alt="详情(待确认)"></div>
|
||||
<div class="card__label">详情(待确认)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending-confirm.jpg','待确认·确认派单弹窗')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending-confirm.jpg" alt="待确认·确认派单弹窗"></div>
|
||||
<div class="card__label">待确认·确认派单弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-pending-cancel.jpg','待确认·取消预约弹窗')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-pending-cancel.jpg" alt="待确认·取消预约弹窗"></div>
|
||||
<div class="card__label">待确认·取消预约弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-confirmed.jpg','详情(已确认)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-confirmed.jpg" alt="详情(已确认)"></div>
|
||||
<div class="card__label">详情(已确认)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-confirmed-cancel.jpg','已确认·取消预约弹窗')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-confirmed-cancel.jpg" alt="已确认·取消预约弹窗"></div>
|
||||
<div class="card__label">已确认·取消预约弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-done.jpg','详情(已完成)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-done.jpg" alt="详情(已完成)"></div>
|
||||
<div class="card__label">详情(已完成)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/serious-illness-detail-cancelled.jpg','详情(已取消)')">
|
||||
<div class="card__img"><img src="screenshots/serious-illness-detail-cancelled.jpg" alt="详情(已取消)"></div>
|
||||
<div class="card__label">详情(已取消)</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 知识库 -->
|
||||
<section class="section" id="sec-knowledge">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">知识库</h2>
|
||||
<span class="section__count">2 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-index.jpg','知识库首页')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-index.jpg" alt="知识库首页"></div>
|
||||
<div class="card__label">知识库首页</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-article-detail.jpg','知识详情')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-article-detail.jpg" alt="知识详情"></div>
|
||||
<div class="card__label">知识详情</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 个人中心 -->
|
||||
<section class="section" id="sec-profile">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">个人中心</h2>
|
||||
<span class="section__count">7 页</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index.jpg','个人中心主页')">
|
||||
<div class="card__img"><img src="screenshots/profile-index.jpg" alt="个人中心主页"></div>
|
||||
<div class="card__label">个人中心主页</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index-logout.jpg','退出登录确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/profile-index-logout.jpg" alt="退出登录确认弹窗"></div>
|
||||
<div class="card__label">退出登录确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/edit-profile.jpg','修改个人信息')">
|
||||
<div class="card__img"><img src="screenshots/edit-profile.jpg" alt="修改个人信息"></div>
|
||||
<div class="card__label">修改个人信息</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/change-pwd.jpg','修改密码')">
|
||||
<div class="card__img"><img src="screenshots/change-pwd.jpg" alt="修改密码"></div>
|
||||
<div class="card__label">修改密码</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/change-pwd-success.jpg','修改密码成功提示')">
|
||||
<div class="card__img"><img src="screenshots/change-pwd-success.jpg" alt="修改密码成功提示"></div>
|
||||
<div class="card__label">修改密码成功提示</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/duty-schedule.jpg','值班信息')">
|
||||
<div class="card__img"><img src="screenshots/duty-schedule.jpg" alt="值班信息"></div>
|
||||
<div class="card__label">值班信息</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/duty-config.jpg','排班配置')">
|
||||
<div class="card__img"><img src="screenshots/duty-config.jpg" alt="排班配置"></div>
|
||||
<div class="card__label">排班配置</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="height:40px;"></div>
|
||||
|
||||
<!-- 灯箱 -->
|
||||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox__close">×</span>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div class="lightbox__name" id="lightbox-name"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLightbox(src, name) {
|
||||
document.getElementById('lightbox-img').src = src;
|
||||
document.getElementById('lightbox-name').textContent = name;
|
||||
document.getElementById('lightbox').classList.add('show');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('show');
|
||||
}
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') closeLightbox();
|
||||
});
|
||||
</script>
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -0,0 +1,238 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>健康应急APP专业人员端 - 页面设计总览</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
||||
|
||||
/* 顶部导航 */
|
||||
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; }
|
||||
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
|
||||
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
|
||||
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
|
||||
|
||||
/* 业务主线区块 */
|
||||
.section { padding: 24px 24px 8px; }
|
||||
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
|
||||
|
||||
/* 横向滚动容器 */
|
||||
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
||||
.scroll-row::-webkit-scrollbar { height: 6px; }
|
||||
.scroll-row::-webkit-scrollbar-track { background: transparent; }
|
||||
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
|
||||
|
||||
/* 卡片 */
|
||||
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
|
||||
.card:hover { transform: scale(1.02); }
|
||||
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
|
||||
.card:hover .card__img { border-color: #4fc3f7; }
|
||||
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
||||
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
|
||||
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
|
||||
.tag--modal { background: #4a2800; color: #ffa940; }
|
||||
.tag--state { background: #002a4a; color: #4fc3f7; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
|
||||
|
||||
/* 灯箱 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.show { display: flex; }
|
||||
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
|
||||
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
|
||||
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">健康应急APP专业人员端 · 设计总览</span>
|
||||
<a class="nav__link" href="#sec-workbench">工作台</a>
|
||||
<a class="nav__link" href="#sec-knowledge">知识库</a>
|
||||
<a class="nav__link" href="#sec-profile">我的</a>
|
||||
</nav>
|
||||
|
||||
<!-- 工作台 -->
|
||||
<section class="section" id="sec-workbench">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">工作台</h2>
|
||||
<span class="section__count">17 页面形态</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/index.jpg','工单列表-进行中')">
|
||||
<div class="card__img"><img src="screenshots/index.jpg" alt=""></div>
|
||||
<div class="card__label">工单列表-进行中</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/index-done.jpg','工单列表-已结束')">
|
||||
<div class="card__img"><img src="screenshots/index-done.jpg" alt=""></div>
|
||||
<div class="card__label">工单列表-已结束</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing.jpg','应急聊天-进行中')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-进行中</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing-info.jpg','应急聊天-患者信息展开')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing-info.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-患者信息展开</div>
|
||||
<div class="card__tags"><span class="tag tag--state">展开状态</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing-voice.jpg','应急聊天-语音模式')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing-voice.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-语音模式</div>
|
||||
<div class="card__tags"><span class="tag tag--state">模式切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing-media.jpg','应急聊天-媒体面板')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing-media.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-媒体面板</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">面板</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-ongoing-confirm.jpg','应急聊天-结束确认')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-ongoing-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-结束确认</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-done.jpg','应急聊天-已结束')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-done.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天-已结束</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-done-info.jpg','应急聊天已结束-信息展开')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-done-info.jpg" alt=""></div>
|
||||
<div class="card__label">应急聊天已结束-信息展开</div>
|
||||
<div class="card__tags"><span class="tag tag--state">展开状态</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail.jpg','工单详情-已完成')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail.jpg" alt=""></div>
|
||||
<div class="card__label">工单详情-已完成</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-edit.jpg','工单详情-编辑意见弹窗')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-edit.jpg" alt=""></div>
|
||||
<div class="card__label">工单详情-编辑意见弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing.jpg','工单详情-进行中')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing.jpg" alt=""></div>
|
||||
<div class="card__label">工单详情-进行中</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing-edit.jpg','工单详情进行中-编辑弹窗')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing-edit.jpg" alt=""></div>
|
||||
<div class="card__label">工单详情进行中-编辑弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/work-order-detail-ongoing-confirm.jpg','工单详情进行中-结束确认')">
|
||||
<div class="card__img"><img src="screenshots/work-order-detail-ongoing-confirm.jpg" alt=""></div>
|
||||
<div class="card__label">工单详情进行中-结束确认</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/patient-location.jpg','患者定位')">
|
||||
<div class="card__img"><img src="screenshots/patient-location.jpg" alt=""></div>
|
||||
<div class="card__label">患者定位</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/patient-location-card.jpg','患者定位-资源卡片')">
|
||||
<div class="card__img"><img src="screenshots/patient-location-card.jpg" alt=""></div>
|
||||
<div class="card__label">患者定位-资源卡片</div>
|
||||
<div class="card__tags"><span class="tag tag--state">卡片展开</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/rescue-opinion.jpg','救助意见')">
|
||||
<div class="card__img"><img src="screenshots/rescue-opinion.jpg" alt=""></div>
|
||||
<div class="card__label">救助意见</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 知识库 -->
|
||||
<section class="section" id="sec-knowledge">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">知识库</h2>
|
||||
<span class="section__count">3 页面形态</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-index.jpg','知识库-文章列表')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-index.jpg" alt=""></div>
|
||||
<div class="card__label">知识库-文章列表</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-index-rule.jpg','知识库-规章制度')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-index-rule.jpg" alt=""></div>
|
||||
<div class="card__label">知识库-规章制度</div>
|
||||
<div class="card__tags"><span class="tag tag--state">Tab切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/knowledge-article-detail.jpg','知识详情')">
|
||||
<div class="card__img"><img src="screenshots/knowledge-article-detail.jpg" alt=""></div>
|
||||
<div class="card__label">知识详情</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- 我的 -->
|
||||
<section class="section" id="sec-profile">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">我的</h2>
|
||||
<span class="section__count">7 页面形态</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index.jpg','个人中心')">
|
||||
<div class="card__img"><img src="screenshots/profile-index.jpg" alt=""></div>
|
||||
<div class="card__label">个人中心</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-index-logout.jpg','个人中心-退出确认')">
|
||||
<div class="card__img"><img src="screenshots/profile-index-logout.jpg" alt=""></div>
|
||||
<div class="card__label">个人中心-退出确认</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/edit-profile.jpg','修改个人信息')">
|
||||
<div class="card__img"><img src="screenshots/edit-profile.jpg" alt=""></div>
|
||||
<div class="card__label">修改个人信息</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-edit-sheet.jpg','修改信息-头像选择')">
|
||||
<div class="card__img"><img src="screenshots/profile-edit-sheet.jpg" alt=""></div>
|
||||
<div class="card__label">修改信息-头像选择</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">ActionSheet</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/change-pwd.jpg','修改密码')">
|
||||
<div class="card__img"><img src="screenshots/change-pwd.jpg" alt=""></div>
|
||||
<div class="card__label">修改密码</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/profile-change-pwd-modal.jpg','修改密码-成功确认')">
|
||||
<div class="card__img"><img src="screenshots/profile-change-pwd-modal.jpg" alt=""></div>
|
||||
<div class="card__label">修改密码-成功确认</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/duty-schedule.jpg','值班信息')">
|
||||
<div class="card__img"><img src="screenshots/duty-schedule.jpg" alt=""></div>
|
||||
<div class="card__label">值班信息</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="height:40px;"></div>
|
||||
|
||||
<!-- 灯箱 -->
|
||||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox__close">×</span>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div class="lightbox__name" id="lightbox-name"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLightbox(src, name) {
|
||||
document.getElementById('lightbox-img').src = src;
|
||||
document.getElementById('lightbox-name').textContent = name;
|
||||
document.getElementById('lightbox').classList.add('show');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1,288 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>应急就医 - 页面设计总览</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; }
|
||||
|
||||
/* 顶部导航 */
|
||||
.nav { position: sticky; top: 0; z-index: 100; background: #16213e; border-bottom: 1px solid #0f3460; padding: 14px 24px; display: flex; gap: 12px; align-items: center; }
|
||||
.nav__title { font-size: 16px; font-weight: 600; color: #4fc3f7; margin-right: 20px; white-space: nowrap; }
|
||||
.nav__link { font-size: 13px; color: #aaa; text-decoration: none; padding: 5px 12px; border-radius: 20px; transition: all .2s; white-space: nowrap; border: 1px solid transparent; }
|
||||
.nav__link:hover { background: #0f3460; color: #4fc3f7; border-color: #4fc3f7; }
|
||||
|
||||
/* 业务主线区块 */
|
||||
.section { padding: 24px 24px 8px; }
|
||||
.section__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
|
||||
.section__title { font-size: 15px; font-weight: 600; color: #fff; }
|
||||
.section__count { font-size: 12px; color: #666; background: #2a2a4a; padding: 2px 8px; border-radius: 10px; }
|
||||
|
||||
/* 横向滚动容器 */
|
||||
.scroll-row { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 16px; scrollbar-width: thin; scrollbar-color: #333 transparent; }
|
||||
.scroll-row::-webkit-scrollbar { height: 6px; }
|
||||
.scroll-row::-webkit-scrollbar-track { background: transparent; }
|
||||
.scroll-row::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
|
||||
|
||||
/* 卡片 */
|
||||
.card { flex-shrink: 0; width: 260px; cursor: pointer; transition: transform .2s; }
|
||||
.card:hover { transform: scale(1.02); }
|
||||
.card__img { width: 260px; height: 564px; border-radius: 12px; overflow: hidden; border: 2px solid #2a2a4a; background: #111; transition: border-color .2s; }
|
||||
.card:hover .card__img { border-color: #4fc3f7; }
|
||||
.card__img img { width: 100%; height: 100%; object-fit: cover; object-position: top; }
|
||||
.card__label { margin-top: 8px; font-size: 12px; color: #999; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.card__tags { display: flex; justify-content: center; gap: 4px; margin-top: 4px; flex-wrap: wrap; }
|
||||
.tag { font-size: 10px; padding: 1px 5px; border-radius: 3px; }
|
||||
.tag--modal { background: #4a2800; color: #ffa940; }
|
||||
.tag--state { background: #002a4a; color: #4fc3f7; }
|
||||
|
||||
/* 子标题 */
|
||||
.sub-title { font-size: 12px; color: #666; margin: 12px 0 8px 4px; padding-left: 24px; }
|
||||
|
||||
/* 分隔线 */
|
||||
.divider { height: 1px; background: #2a2a4a; margin: 8px 24px; }
|
||||
|
||||
/* 灯箱 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.92); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.show { display: flex; }
|
||||
.lightbox img { max-width: 90vw; max-height: 92vh; border-radius: 8px; }
|
||||
.lightbox__close { position: absolute; top: 16px; right: 20px; color: #fff; font-size: 24px; cursor: pointer; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: 50%; background: rgba(255,255,255,0.1); }
|
||||
.lightbox__name { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #ccc; font-size: 13px; background: rgba(0,0,0,0.6); padding: 6px 16px; border-radius: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="nav">
|
||||
<span class="nav__title">应急就医员工端APP · 设计总览</span>
|
||||
<a class="nav__link" href="#sec-entry">入口</a>
|
||||
<a class="nav__link" href="#sec-map">资源地图</a>
|
||||
<a class="nav__link" href="#sec-detail">资源详情</a>
|
||||
<a class="nav__link" href="#sec-help">应急求助</a>
|
||||
<a class="nav__link" href="#sec-video">急救教学</a>
|
||||
<a class="nav__link" href="#sec-appt">大病就医预约</a>
|
||||
</nav>
|
||||
|
||||
<!-- ===== 1. 入口与总览 ===== -->
|
||||
<section class="section" id="sec-entry">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">入口与总览</h2>
|
||||
<span class="section__count">2</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/index.jpg','应急就医首页')">
|
||||
<div class="card__img"><img src="screenshots/index.jpg" alt=""></div>
|
||||
<div class="card__label">应急就医首页</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-contacts.jpg','紧急联系人')">
|
||||
<div class="card__img"><img src="screenshots/emergency-contacts.jpg" alt=""></div>
|
||||
<div class="card__label">紧急联系人</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
<!-- ===== 2. 应急资源地图 ===== -->
|
||||
<section class="section" id="sec-map">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">应急资源地图</h2>
|
||||
<span class="section__count">6</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map.jpg','油田医院地图')">
|
||||
<div class="card__img"><img src="screenshots/resource-map.jpg" alt=""></div>
|
||||
<div class="card__label">油田医院地图(默认)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map-clinic.jpg','医疗点地图')">
|
||||
<div class="card__img"><img src="screenshots/resource-map-clinic.jpg" alt=""></div>
|
||||
<div class="card__label">医疗点地图</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map-aed.jpg','AED地图')">
|
||||
<div class="card__img"><img src="screenshots/resource-map-aed.jpg" alt=""></div>
|
||||
<div class="card__label">AED 地图</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map-ambulance.jpg','救护车地图')">
|
||||
<div class="card__img"><img src="screenshots/resource-map-ambulance.jpg" alt=""></div>
|
||||
<div class="card__label">救护车地图</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map-kit.jpg','急救包地图')">
|
||||
<div class="card__img"><img src="screenshots/resource-map-kit.jpg" alt=""></div>
|
||||
<div class="card__label">急救包地图</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/resource-map-simulate.jpg','模拟急救面板')">
|
||||
<div class="card__img"><img src="screenshots/resource-map-simulate.jpg" alt=""></div>
|
||||
<div class="card__label">模拟急救面板</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ===== 3. 资源详情 ===== -->
|
||||
<section class="section" id="sec-detail">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">资源详情页</h2>
|
||||
<span class="section__count">6</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/hospital-detail.jpg','油田医院详情')">
|
||||
<div class="card__img"><img src="screenshots/hospital-detail.jpg" alt=""></div>
|
||||
<div class="card__label">油田医院详情</div>
|
||||
<div class="card__tags"><span class="tag tag--state">展开/收起</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/clinic-detail.jpg','医疗点详情')">
|
||||
<div class="card__img"><img src="screenshots/clinic-detail.jpg" alt=""></div>
|
||||
<div class="card__label">医疗点详情</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/aed-detail.jpg','AED详情')">
|
||||
<div class="card__img"><img src="screenshots/aed-detail.jpg" alt=""></div>
|
||||
<div class="card__label">AED 详情</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/ambulance-detail.jpg','救护车详情')">
|
||||
<div class="card__img"><img src="screenshots/ambulance-detail.jpg" alt=""></div>
|
||||
<div class="card__label">救护车详情</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/first-aid-kit-detail.jpg','急救包详情')">
|
||||
<div class="card__img"><img src="screenshots/first-aid-kit-detail.jpg" alt=""></div>
|
||||
<div class="card__label">急救包详情</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-tips.jpg','急救提示书')">
|
||||
<div class="card__img"><img src="screenshots/emergency-tips.jpg" alt=""></div>
|
||||
<div class="card__label">急救提示书</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ===== 4. 应急求助 ===== -->
|
||||
<section class="section" id="sec-help">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">应急求助</h2>
|
||||
<span class="section__count">5</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat.jpg','应急视频对话')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat.jpg" alt=""></div>
|
||||
<div class="card__label">应急视频对话(文字模式)</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-voice.jpg','语音输入模式')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-voice.jpg" alt=""></div>
|
||||
<div class="card__label">语音输入模式</div>
|
||||
<div class="card__tags"><span class="tag tag--state">状态切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-plus.jpg','加号菜单展开')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-plus.jpg" alt=""></div>
|
||||
<div class="card__label">加号菜单展开</div>
|
||||
<div class="card__tags"><span class="tag tag--state">状态切换</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/emergency-chat-modal.jpg','结束应急确认弹窗')">
|
||||
<div class="card__img"><img src="screenshots/emergency-chat-modal.jpg" alt=""></div>
|
||||
<div class="card__label">结束应急确认弹窗</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/help-records.jpg','应急求助记录')">
|
||||
<div class="card__img"><img src="screenshots/help-records.jpg" alt=""></div>
|
||||
<div class="card__label">应急求助记录</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ===== 5. 急救教学 ===== -->
|
||||
<section class="section" id="sec-video">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">急救教学</h2>
|
||||
<span class="section__count">1</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/first-aid-videos.jpg','急救教学视频')">
|
||||
<div class="card__img"><img src="screenshots/first-aid-videos.jpg" alt=""></div>
|
||||
<div class="card__label">急救教学视频列表</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- ===== 6. 大病就医预约 ===== -->
|
||||
<section class="section" id="sec-appt">
|
||||
<div class="section__header">
|
||||
<h2 class="section__title">大病就医预约</h2>
|
||||
<span class="section__count">11</span>
|
||||
</div>
|
||||
<div class="scroll-row">
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-list.jpg','预约列表·待确认')">
|
||||
<div class="card__img"><img src="screenshots/appointment-list.jpg" alt=""></div>
|
||||
<div class="card__label">列表 · 待确认</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-list-confirmed.jpg','预约列表·已确认')">
|
||||
<div class="card__img"><img src="screenshots/appointment-list-confirmed.jpg" alt=""></div>
|
||||
<div class="card__label">列表 · 已确认</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-list-done.jpg','预约列表·已完成')">
|
||||
<div class="card__img"><img src="screenshots/appointment-list-done.jpg" alt=""></div>
|
||||
<div class="card__label">列表 · 已完成</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-list-cancelled.jpg','预约列表·已取消')">
|
||||
<div class="card__img"><img src="screenshots/appointment-list-cancelled.jpg" alt=""></div>
|
||||
<div class="card__label">列表 · 已取消</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-form.jpg','发起预约表单')">
|
||||
<div class="card__img"><img src="screenshots/appointment-form.jpg" alt=""></div>
|
||||
<div class="card__label">发起预约表单</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-pending.jpg','预约详情·待确认')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-pending.jpg" alt=""></div>
|
||||
<div class="card__label">详情 · 待确认</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-pending-modal.jpg','取消预约弹窗(待确认)')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-pending-modal.jpg" alt=""></div>
|
||||
<div class="card__label">取消弹窗(待确认)</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-confirmed.jpg','预约详情·已确认')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-confirmed.jpg" alt=""></div>
|
||||
<div class="card__label">详情 · 已确认</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-confirmed-modal.jpg','取消预约弹窗(已确认)')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-confirmed-modal.jpg" alt=""></div>
|
||||
<div class="card__label">取消弹窗(已确认)</div>
|
||||
<div class="card__tags"><span class="tag tag--modal">弹窗</span></div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-done.jpg','预约详情·已完成')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-done.jpg" alt=""></div>
|
||||
<div class="card__label">详情 · 已完成</div>
|
||||
</div>
|
||||
<div class="card" onclick="openLightbox('screenshots/appointment-detail-cancelled.jpg','预约详情·已取消')">
|
||||
<div class="card__img"><img src="screenshots/appointment-detail-cancelled.jpg" alt=""></div>
|
||||
<div class="card__label">详情 · 已取消</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="height:40px;"></div>
|
||||
|
||||
<!-- 灯箱 -->
|
||||
<div class="lightbox" id="lightbox" onclick="closeLightbox()">
|
||||
<span class="lightbox__close">×</span>
|
||||
<img id="lightbox-img" src="" alt="">
|
||||
<div class="lightbox__name" id="lightbox-name"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function openLightbox(src, name) {
|
||||
document.getElementById('lightbox-img').src = src;
|
||||
document.getElementById('lightbox-name').textContent = name;
|
||||
document.getElementById('lightbox').classList.add('show');
|
||||
}
|
||||
function closeLightbox() {
|
||||
document.getElementById('lightbox').classList.remove('show');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 88 KiB |
@@ -559,6 +559,7 @@ const SITE_MAP = [
|
||||
{ file:'consult-flowchart.html', label:'咨询流程图' }
|
||||
]},
|
||||
{ name:'应急就医', dir:'employee-app/emergency-medical', entry:'index.html', prd:'employee-app/emergency-medical/doc/PRD.md', done:true, pages:[
|
||||
{ file:'doc/design-overview.html', label:'页面总览' },
|
||||
{ file:'index.html', label:'应急就医主页' },
|
||||
{ file:'emergency-chat.html', label:'应急对话' },
|
||||
{ file:'resource-map.html', label:'应急资源地图' },
|
||||
@@ -645,6 +646,7 @@ const SITE_MAP = [
|
||||
]},
|
||||
{ role:'应急操作人员', id:'operator', modules:[
|
||||
{name:'工作台',dir:'emergency-app/operator/workbench',prd:'emergency-app/operator/doc/PRD.md',done:true,pages:[
|
||||
{file:'../doc/design-overview.html',label:'页面总览'},
|
||||
{file:'index.html',label:'工作台首页(应急求助)'},
|
||||
{file:'serious-illness-list.html',label:'大病就医首页'},
|
||||
{file:'emergency-chat.html',label:'应急聊天'},
|
||||
@@ -673,6 +675,7 @@ const SITE_MAP = [
|
||||
]},
|
||||
{ role:'应急专业人员', id:'professional', modules:[
|
||||
{ name:'工作台', dir:'emergency-app/professional/workbench', prd:'emergency-app/professional/doc/PRD.md', done:true, pages:[
|
||||
{file:'../doc/design-overview.html',label:'页面总览'},
|
||||
{file:'index.html',label:'工作台首页'},
|
||||
{file:'emergency-chat-ongoing.html',label:'应急聊天(进行中)'},
|
||||
{file:'emergency-chat-done.html',label:'应急聊天(已结束)'},
|
||||
@@ -692,6 +695,7 @@ const SITE_MAP = [
|
||||
]},
|
||||
{ role:'应急驻场人员', id:'onsite', modules:[
|
||||
{name:'工作台',dir:'emergency-app/onsite/workbench',prd:'emergency-app/onsite/doc/PRD.md',done:true,pages:[
|
||||
{file:'../doc/design-overview.html',label:'页面总览'},
|
||||
{file:'index.html',label:'工作台首页(应急求助)'},
|
||||
{file:'serious-illness-list.html',label:'大病就医首页'},
|
||||
{file:'work-order-detail-ongoing.html',label:'工单详情(待处理)'},
|
||||
@@ -968,7 +972,7 @@ tabNav.addEventListener('click', e => {
|
||||
|
||||
/* ===== Markdown 预览(新标签页打开) ===== */
|
||||
function openMdPreview(filePath, title) {
|
||||
window.open('md-viewer.html?file=' + encodeURIComponent(filePath) + '&title=' + encodeURIComponent(title || ''), '_blank');
|
||||
window.open('_templates/md-viewer.html?file=' + encodeURIComponent(filePath) + '&title=' + encodeURIComponent(title || ''), '_blank');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>文档预览</title>
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--primary: #1677FF;
|
||||
--primary-light: #E6F7FF;
|
||||
--text-primary: #1a1a2e;
|
||||
--text-secondary: #5a607f;
|
||||
--text-placeholder: #999;
|
||||
--border: #e8ecf1;
|
||||
--bg: #ffffff;
|
||||
}
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', 'Segoe UI', sans-serif; background: #f5f7fa; color: var(--text-primary); }
|
||||
|
||||
/* 顶部导航栏 */
|
||||
.topbar { position: sticky; top: 0; z-index: 100; background: var(--bg); border-bottom: 1px solid var(--border); padding: 12px 24px; display: flex; align-items: center; gap: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||
.topbar__back { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--primary); cursor: pointer; text-decoration: none; padding: 4px 10px; border-radius: 6px; transition: background .15s; border: none; background: none; }
|
||||
.topbar__back:hover { background: var(--primary-light); }
|
||||
.topbar__title { font-size: 15px; font-weight: 600; color: var(--text-primary); }
|
||||
.topbar__path { font-size: 12px; color: var(--text-placeholder); margin-left: auto; }
|
||||
|
||||
/* 内容区 */
|
||||
.content { max-width: 900px; margin: 0 auto; padding: 32px 40px 60px; background: var(--bg); min-height: calc(100vh - 50px); font-size: 14px; line-height: 1.8; }
|
||||
.loading { display: flex; align-items: center; justify-content: center; height: 300px; color: var(--text-placeholder); font-size: 14px; }
|
||||
.error { color: #ff4d4f; }
|
||||
|
||||
/* Markdown 渲染样式 */
|
||||
.content h1 { font-size: 24px; font-weight: 800; margin: 0 0 16px; padding-bottom: 10px; border-bottom: 2px solid var(--border); }
|
||||
.content h2 { font-size: 20px; font-weight: 700; margin: 32px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
|
||||
.content h3 { font-size: 17px; font-weight: 600; margin: 24px 0 8px; }
|
||||
.content h4 { font-size: 15px; font-weight: 600; margin: 20px 0 6px; }
|
||||
.content p { font-size: 14px; line-height: 1.8; margin: 8px 0; }
|
||||
.content ul, .content ol { padding-left: 20px; margin: 8px 0; font-size: 14px; line-height: 1.8; }
|
||||
.content table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 13px; }
|
||||
.content th { background: #fafafa; font-weight: 600; text-align: left; padding: 8px 12px; border: 1px solid var(--border); }
|
||||
.content td { padding: 8px 12px; border: 1px solid var(--border); }
|
||||
.content code { background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; }
|
||||
.content pre { background: #f6f8fa; border-radius: 8px; padding: 16px; overflow-x: auto; margin: 12px 0; }
|
||||
.content pre code { background: none; padding: 0; font-size: 13px; line-height: 1.6; }
|
||||
.content blockquote { border-left: 4px solid var(--primary); padding: 8px 16px; margin: 12px 0; background: var(--primary-light); border-radius: 0 4px 4px 0; }
|
||||
.content blockquote p { color: var(--text-secondary); margin: 0; }
|
||||
.content strong { color: var(--text-primary); }
|
||||
.content hr { border: none; border-top: 1px solid var(--border); margin: 24px 0; }
|
||||
.content img { max-width: 100%; height: auto; border-radius: 8px; border: 1px solid var(--border); margin: 8px 0; cursor: pointer; transition: opacity .15s; }
|
||||
.content img:hover { opacity: .85; }
|
||||
|
||||
/* Lightbox 图片放大遮罩 */
|
||||
.lightbox { display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0,0,0,.75); align-items: center; justify-content: center; cursor: zoom-out; }
|
||||
.lightbox.active { display: flex; }
|
||||
.lightbox img { max-width: 92vw; max-height: 92vh; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,.4); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="topbar">
|
||||
<button class="topbar__back" onclick="window.close();history.back()">← 返回</button>
|
||||
<span class="topbar__title" id="doc-title">文档预览</span>
|
||||
<span class="topbar__path" id="doc-path"></span>
|
||||
</div>
|
||||
<div class="content" id="content">
|
||||
<div class="loading">加载中...</div>
|
||||
</div>
|
||||
<!-- Lightbox 图片放大遮罩 -->
|
||||
<div class="lightbox" id="lightbox" onclick="this.classList.remove('active')">
|
||||
<img id="lightbox-img" src="" alt="放大预览">
|
||||
</div>
|
||||
<script>
|
||||
const params = new URLSearchParams(location.search);
|
||||
const filePath = params.get('file');
|
||||
const title = params.get('title');
|
||||
|
||||
if (title) {
|
||||
document.getElementById('doc-title').textContent = title;
|
||||
document.title = title + ' - 文档预览';
|
||||
}
|
||||
if (filePath) {
|
||||
document.getElementById('doc-path').textContent = filePath;
|
||||
fetch(filePath)
|
||||
.then(res => {
|
||||
if (!res.ok) throw new Error('文件未找到 (' + res.status + ')');
|
||||
return res.text();
|
||||
})
|
||||
.then(md => {
|
||||
/* 检测是否为自带 HTML 结构的文件(如共性需求说明.md) */
|
||||
const isRawHtml = /^\s*<(?:style|div|!doctype|html)/i.test(md);
|
||||
if (isRawHtml) {
|
||||
/* 为标题生成锚点 id(marked 新版不再自动生成) */
|
||||
function addHeadingIds(html) {
|
||||
return html.replace(/<h([1-6])>([\s\S]*?)<\/h\1>/g, function(_, lvl, inner) {
|
||||
var plain = inner.replace(/<[^>]*>/g, '');
|
||||
var id = plain.toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, '').trim().replace(/\s+/g, '-');
|
||||
return '<h' + lvl + ' id="' + id + '">' + inner + '</h' + lvl + '>';
|
||||
});
|
||||
}
|
||||
let processed = md;
|
||||
if (typeof marked !== 'undefined') {
|
||||
processed = md.replace(/(^[\s\S]*?<div id="main-content">)([\s\S]*?)(<\/div>\s*$)/i, function(_, before, mdBody, after) {
|
||||
return before + addHeadingIds(marked.parse(mdBody)) + after;
|
||||
});
|
||||
processed = processed.replace(/(<div id="toc-container">)([\s\S]*?)(<\/div>)/, function(_, before, tocBody, after) {
|
||||
return before + marked.parse(tocBody) + after;
|
||||
});
|
||||
}
|
||||
/* iframe 内注入:表格框线 + 排版样式 */
|
||||
const extraCss = '<style>'
|
||||
+ 'html{scroll-behavior:smooth}'
|
||||
+ 'body{font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Helvetica Neue","Segoe UI",sans-serif}'
|
||||
+ '#main-content table{width:100%;border-collapse:collapse;margin:12px 0;font-size:14px}'
|
||||
+ '#main-content th{background:#fafafa;font-weight:600;text-align:left;padding:8px 12px;border:1px solid #e8ecf1}'
|
||||
+ '#main-content td{padding:8px 12px;border:1px solid #e8ecf1}'
|
||||
+ '#main-content h1{font-size:24px;font-weight:800;margin:0 0 16px;padding-bottom:10px;border-bottom:2px solid #e8ecf1}'
|
||||
+ '#main-content h2{font-size:20px;font-weight:700;margin:32px 0 12px;padding-bottom:8px;border-bottom:1px solid #e8ecf1}'
|
||||
+ '#main-content h3{font-size:17px;font-weight:600;margin:24px 0 8px}'
|
||||
+ '#main-content h4{font-size:15px;font-weight:600;margin:20px 0 6px}'
|
||||
+ '#main-content p{font-size:14px;line-height:1.8;margin:8px 0}'
|
||||
+ '#main-content ul,#main-content ol{padding-left:20px;margin:8px 0;font-size:14px;line-height:1.8}'
|
||||
+ '#main-content code{background:#f5f5f5;padding:2px 6px;border-radius:4px;font-size:13px}'
|
||||
+ '#main-content blockquote{border-left:4px solid #1677FF;padding:8px 16px;margin:12px 0;background:#E6F7FF;border-radius:0 4px 4px 0}'
|
||||
+ '#toc-container a.active{color:#1677FF;font-weight:600}'
|
||||
+ '</style>';
|
||||
/* iframe 内注入:滚动时高亮当前目录项 */
|
||||
const tocScript = '<script>'
|
||||
+ 'document.addEventListener("DOMContentLoaded",function(){'
|
||||
+ 'var links=document.querySelectorAll("#toc-container a[href^=\\"#\\"]");'
|
||||
+ 'var items=[];'
|
||||
+ 'links.forEach(function(a){var id=decodeURIComponent(a.getAttribute("href").slice(1));var el=document.getElementById(id);if(el)items.push({el:el,link:a})});'
|
||||
+ 'if(!items.length)return;'
|
||||
+ 'function update(){var sy=window.scrollY||document.documentElement.scrollTop;var cur=items[0];'
|
||||
+ 'for(var i=0;i<items.length;i++){if(items[i].el.offsetTop<=sy+80)cur=items[i]}'
|
||||
+ 'links.forEach(function(a){a.classList.remove("active")});if(cur)cur.link.classList.add("active")}'
|
||||
+ 'window.addEventListener("scroll",update);update()});'
|
||||
+ '<\/script>';
|
||||
/* 用 iframe 隔离渲染 */
|
||||
document.querySelector('.topbar').style.display = 'none';
|
||||
const wrap = document.getElementById('content');
|
||||
wrap.style.cssText = 'max-width:none;margin:0;padding:0;min-height:100vh';
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.style.cssText = 'width:100%;height:100vh;border:none';
|
||||
iframe.srcdoc = '<!DOCTYPE html><html><head><meta charset="UTF-8">' + extraCss + '</head><body>' + processed + tocScript + '</body></html>';
|
||||
wrap.innerHTML = '';
|
||||
wrap.appendChild(iframe);
|
||||
} else {
|
||||
/* 计算 Markdown 文件所在目录,用于重写图片相对路径 */
|
||||
const mdBase = filePath.substring(0, filePath.lastIndexOf('/') + 1);
|
||||
const content = document.getElementById('content');
|
||||
if (typeof marked !== 'undefined') {
|
||||
content.innerHTML = marked.parse(md);
|
||||
/* 将图片相对路径补全为基于 Markdown 文件目录的路径 */
|
||||
content.querySelectorAll('img').forEach(img => {
|
||||
const src = img.getAttribute('src');
|
||||
if (src && !src.startsWith('http') && !src.startsWith('/') && !src.startsWith('data:')) {
|
||||
img.src = mdBase + src;
|
||||
}
|
||||
});
|
||||
/* 绑定图片点击放大 */
|
||||
content.querySelectorAll('img').forEach(img => {
|
||||
img.addEventListener('click', () => {
|
||||
document.getElementById('lightbox-img').src = img.src;
|
||||
document.getElementById('lightbox').classList.add('active');
|
||||
});
|
||||
});
|
||||
} else {
|
||||
content.innerHTML = '<pre style="white-space:pre-wrap;font-size:14px;line-height:1.8">' + md.replace(/</g,'<').replace(/>/g,'>') + '</pre>';
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById('content').innerHTML = '<div class="loading error">加载失败:' + err.message + '<br><small style="color:var(--text-placeholder);margin-top:8px;display:block">请确认文件路径:' + filePath + '</small></div>';
|
||||
});
|
||||
} else {
|
||||
document.getElementById('content').innerHTML = '<div class="loading">未指定文件路径</div>';
|
||||
}
|
||||
/* ESC 键关闭 lightbox */
|
||||
document.addEventListener('keydown', e => {
|
||||
if (e.key === 'Escape') document.getElementById('lightbox').classList.remove('active');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,21 +1,46 @@
|
||||
{
|
||||
"name": "platform-prototype",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"chrome-devtools-mcp": "^0.21.0"
|
||||
},
|
||||
"name": "platform-prototype",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"playwright": "^1.59.1",
|
||||
"puppeteer-core": "^24.43.0"
|
||||
"puppeteer": "^24.43.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
|
||||
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.29.7",
|
||||
"js-tokens": "^4.0.0",
|
||||
"picocolors": "^1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.29.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
|
||||
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@puppeteer/browsers": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.1.tgz",
|
||||
"integrity": "sha512-zmS4RTK9fbrc++WlAJhxYbfz3IjDeOmkK/CwwbLmk7ydfS9e2CiEeRJHEPvjDVElO/bwXbidwGA37Bsm6LzCnQ==",
|
||||
"version": "2.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz",
|
||||
"integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -42,14 +67,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz",
|
||||
"integrity": "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==",
|
||||
"version": "25.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
|
||||
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.19.0"
|
||||
"undici-types": ">=7.24.0 <7.24.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/yauzl": {
|
||||
@@ -99,6 +124,13 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/ast-types": {
|
||||
"version": "0.13.4",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
||||
@@ -128,9 +160,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/bare-events": {
|
||||
"version": "2.8.2",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz",
|
||||
"integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==",
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz",
|
||||
"integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
@@ -244,17 +276,14 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/chrome-devtools-mcp": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/chrome-devtools-mcp/-/chrome-devtools-mcp-0.21.0.tgz",
|
||||
"integrity": "sha512-d+iqrRmcwpRFV3Q4DRCF2LCoq+WCRU3GhISKQ9v8g+1C2Uh8upj3urkjxNO4QIjhBMIYei/VQ1OQLFceby80Og==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"chrome-devtools": "build/src/bin/chrome-devtools.js",
|
||||
"chrome-devtools-mcp": "build/src/bin/chrome-devtools-mcp.js"
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.12.0 || >=23"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
@@ -306,6 +335,33 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz",
|
||||
"integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"env-paths": "^2.2.1",
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.9.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
|
||||
@@ -373,6 +429,26 @@
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
"integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/error-ex": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
|
||||
"integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
@@ -487,21 +563,6 @@
|
||||
"pend": "~1.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -571,6 +632,23 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
|
||||
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parent-module": "^1.0.0",
|
||||
"resolve-from": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-address": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
|
||||
@@ -581,6 +659,13 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/is-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -591,6 +676,40 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
@@ -669,6 +788,38 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
|
||||
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"callsites": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
|
||||
"integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.0.0",
|
||||
"error-ex": "^1.3.1",
|
||||
"json-parse-even-better-errors": "^2.3.0",
|
||||
"lines-and-columns": "^1.1.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
@@ -676,37 +827,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
|
||||
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.59.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.59.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
|
||||
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/progress": {
|
||||
"version": "2.0.3",
|
||||
@@ -756,14 +882,36 @@
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer-core": {
|
||||
"version": "24.43.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.0.tgz",
|
||||
"integrity": "sha512-cCRNXsUlhyPoKDz6+TiSpfZpRS3mD6Y1YFKhkdr6ik6TMfuJb7fAtXq9ThUFc4sphxObDk3BuAvdxc1Y6YOnqQ==",
|
||||
"node_modules/puppeteer": {
|
||||
"version": "24.43.1",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz",
|
||||
"integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.13.2",
|
||||
"chromium-bidi": "14.0.0",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"devtools-protocol": "0.0.1608973",
|
||||
"puppeteer-core": "24.43.1",
|
||||
"typed-query-selector": "^2.12.2"
|
||||
},
|
||||
"bin": {
|
||||
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/puppeteer-core": {
|
||||
"version": "24.43.1",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz",
|
||||
"integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.13.1",
|
||||
"@puppeteer/browsers": "2.13.2",
|
||||
"chromium-bidi": "14.0.0",
|
||||
"debug": "^4.4.3",
|
||||
"devtools-protocol": "0.0.1608973",
|
||||
@@ -785,10 +933,20 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz",
|
||||
"integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==",
|
||||
"version": "7.8.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
|
||||
"integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
@@ -953,9 +1111,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.19.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz",
|
||||
"integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==",
|
||||
"version": "7.24.6",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
|
||||
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
@@ -993,9 +1151,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
|
||||
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"chrome-devtools-mcp": "^0.21.0"
|
||||
},
|
||||
"name": "platform-prototype",
|
||||
"version": "1.0.0",
|
||||
"main": "server.js",
|
||||
"devDependencies": {
|
||||
"playwright": "^1.59.1",
|
||||
"puppeteer-core": "^24.43.0"
|
||||
}
|
||||
"puppeteer": "^24.43.1"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": ""
|
||||
}
|
||||
|
||||
@@ -1,392 +0,0 @@
|
||||
# 本地服务器独立微服务化方案
|
||||
|
||||
> **项目**:智能餐养系统 - 边缘服务节点重构
|
||||
> **版本**:v1.0
|
||||
> **日期**:2026-05-26
|
||||
> **作者**:技术团队
|
||||
> **范围**:面向技术团队的设计与落地方案
|
||||
> **周期**:1 个月落地,边跑边迁
|
||||
|
||||
---
|
||||
|
||||
## 一、定位与边界
|
||||
|
||||
**本地服务器 ≠ 业务系统**。本次重构将其重新定位为:**部署在每个食堂边缘的独立微服务,只做"硬件 ↔ 云端"的可靠中转桥梁**,与现有"健康长庆"业务系统、小程序后端、admin 后台**完全解耦**。
|
||||
|
||||
| 做(In Scope) | 不做(Out of Scope) | 不混用(独立部署) |
|
||||
| --- | --- | --- |
|
||||
| 任务化数据中转 | 业务规则、菜品算法 | 不依赖小程序后端 |
|
||||
| 任务链状态管理与重试 | 用户登录鉴权 | 不调用四合一业务接口 |
|
||||
| 终端设备接入与协议适配 | 报表/统计/分析 | 不耦合 admin 业务模块 |
|
||||
| 本地资源缓存(视频/人脸/升级包) | 任何 UI 业务逻辑 | 独立进程、独立升级 |
|
||||
| 设备远程升级 | | |
|
||||
|
||||
**一句话原则**:本地服务器只回答两个问题——*云端要的数据收齐了吗?云端下发的指令落地了吗?* 其他一概不管。
|
||||
|
||||
---
|
||||
|
||||
## 二、总体架构
|
||||
|
||||
<!-- APPEND_HERE -->
|
||||
### 2.1 系统拓扑
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph Cloud["云端"]
|
||||
CloudAPI[云端任务网关<br>Spring Boot]
|
||||
CloudDB[(任务主库<br>PostgreSQL)]
|
||||
OpsUI[运维后台<br>Vue3 + Ant Design Vue<br>复用 admin 技术栈]
|
||||
end
|
||||
|
||||
subgraph Edge["食堂本地 - 每个食堂一套"]
|
||||
LS[本地服务器微服务<br>Spring Boot 独立 JAR]
|
||||
LSDB[(本地任务库<br>SQLite)]
|
||||
Cache[本地资源缓存<br>视频/人脸库/升级包]
|
||||
end
|
||||
|
||||
subgraph Devices["终端硬件"]
|
||||
T1[点餐终端]
|
||||
T2[入库秤/配比秤]
|
||||
T3[智能货柜/绑盘机]
|
||||
T4[人脸设备]
|
||||
T5[显示大屏]
|
||||
end
|
||||
|
||||
OpsUI --> CloudAPI
|
||||
CloudAPI <-->|MQTT QoS2 持久会话| LS
|
||||
CloudAPI --- CloudDB
|
||||
LS --- LSDB
|
||||
LS --- Cache
|
||||
LS <-->|HTTP/MQTT 局域网| T1
|
||||
LS <-->|HTTP/MQTT 局域网| T2
|
||||
LS <-->|HTTP/MQTT 局域网| T3
|
||||
LS <-->|HTTP/MQTT 局域网| T4
|
||||
LS -->|本地 HTTP / RTSP| T5
|
||||
```
|
||||
|
||||
### 2.2 通信协议选型(针对弱网)
|
||||
|
||||
| 链路 | 协议 | 选型理由 |
|
||||
| --- | --- | --- |
|
||||
| 云端 ↔ 本地服务器 | **MQTT QoS 2 + Persistent Session**(EMQX) | 弱网友好、断线自动续传、消息至少一次且不重复 |
|
||||
| 大文件下发(视频/升级包) | HTTPS 断点续传(Range) | 弱网重连不重头,降低带宽浪费 |
|
||||
| 本地服务器 ↔ 终端 | HTTP / MQTT(局域网) | 局域网稳定,简单优先,按设备类型适配 |
|
||||
| 运维后台 ↔ 云端 | HTTPS REST | 复用 admin 现有 axios + token 鉴权 |
|
||||
|
||||
### 2.3 技术栈
|
||||
|
||||
| 模块 | 选型 | 备注 |
|
||||
| --- | --- | --- |
|
||||
| **云端任务网关** | Spring Boot 3 + Spring Integration MQTT | 与现有 Java 后端栈一致 |
|
||||
| **本地服务器微服务** | Spring Boot 3,打包成单 JAR | 一键部署,内存占用控制在 512MB 以内 |
|
||||
| **MQTT Broker** | EMQX 5(集群) | 支持 QoS 2、共享订阅、规则引擎 |
|
||||
| **云端数据库** | **PostgreSQL 15** ✅ 推荐 / MySQL 8 备选 | PG 的 JSONB 对 `target_scope` 等字段更友好,CTE/窗口函数利于运维查询 |
|
||||
| **本地任务库** | SQLite + WAL 模式 | 零运维,适合边缘场景,单文件备份 |
|
||||
| **运维后台** | Vue 3 + Vite + TypeScript + Ant Design Vue | **复用现有 admin 工程技术栈与登录体系** |
|
||||
| **配置中心 / 注册** | 暂不引入,本地服务器走配置文件 + 远程下发 | 边缘节点数量级小,过早引入 Nacos 增加复杂度 |
|
||||
| **部署** | Docker 镜像 + 一键安装脚本 | 边缘节点需支持离线部署 |
|
||||
|
||||
---
|
||||
|
||||
## 三、核心机制:全链路任务化
|
||||
|
||||
### 3.1 设计原则
|
||||
|
||||
每一次数据流转(无论下行还是上行)都是一个**有状态、可追溯、可重试**的任务。云端与本地各持一份任务记录,通过 `task_id` 关联,任意时刻**任意任务卡在哪一跳一查就知道**。
|
||||
|
||||
### 3.2 任务状态机
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Created: 云端创建任务
|
||||
Created --> Dispatched: 推送到本地服务器
|
||||
Dispatched --> LocalReceived: 本地落库 ACK
|
||||
LocalReceived --> TerminalDispatched: 分发到终端
|
||||
TerminalDispatched --> TerminalDone: 终端确认完成
|
||||
TerminalDone --> CloudReported: 上报云端
|
||||
CloudReported --> [*]: 任务完成
|
||||
|
||||
Dispatched --> RetryPending: 网络中断
|
||||
LocalReceived --> RetryPending: 终端离线
|
||||
TerminalDispatched --> RetryPending: 终端执行失败
|
||||
RetryPending --> Dispatched: 自动指数退避重试
|
||||
RetryPending --> Failed: 达到最大重试次数
|
||||
Failed --> Dispatched: 运维后台人工触发重试
|
||||
```
|
||||
|
||||
**重试策略**:指数退避 `1s → 5s → 30s → 2min → 10min`,默认最多 5 次,达到上限转 `Failed` 并触发告警。
|
||||
|
||||
### 3.3 数据库 Schema(核心三表)
|
||||
|
||||
> 以下 DDL 以 PostgreSQL 为例,MySQL 版本仅类型微调(JSONB → JSON、ENUM → VARCHAR + CHECK)。
|
||||
|
||||
```sql
|
||||
-- ============================================
|
||||
-- 1. 任务主表
|
||||
-- ============================================
|
||||
CREATE TABLE task (
|
||||
task_id UUID PRIMARY KEY,
|
||||
task_type VARCHAR(32) NOT NULL,
|
||||
-- 'DISH_DOWN','FACE_DOWN','UPGRADE_DOWN','VIDEO_DOWN','MEAL_UP',...
|
||||
direction VARCHAR(8) NOT NULL, -- 'DOWN' / 'UP'
|
||||
target_scope JSONB NOT NULL,
|
||||
-- {"org_id":1,"canteen_ids":[10,11],"device_ids":["T01","T02"]}
|
||||
-- 支持二级单位/食堂/设备级定向下发
|
||||
payload_ref VARCHAR(255), -- 大字段指针(对象存储 key 或外部表 ID)
|
||||
total_count INT NOT NULL DEFAULT 0,
|
||||
success_count INT NOT NULL DEFAULT 0,
|
||||
failed_count INT NOT NULL DEFAULT 0,
|
||||
state VARCHAR(24) NOT NULL,
|
||||
-- CREATED/DISPATCHED/LOCAL_RECEIVED/TERMINAL_DISPATCHED/
|
||||
-- TERMINAL_DONE/CLOUD_REPORTED/RETRY_PENDING/FAILED
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
max_retry INT NOT NULL DEFAULT 5,
|
||||
next_retry_at TIMESTAMP,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
finished_at TIMESTAMP,
|
||||
creator VARCHAR(64)
|
||||
);
|
||||
CREATE INDEX idx_task_state_retry ON task(state, next_retry_at);
|
||||
CREATE INDEX idx_task_target ON task USING GIN (target_scope);
|
||||
|
||||
-- ============================================
|
||||
-- 2. 任务跳转流转表(全链路审计)
|
||||
-- ============================================
|
||||
CREATE TABLE task_hop (
|
||||
hop_id BIGSERIAL PRIMARY KEY,
|
||||
task_id UUID NOT NULL REFERENCES task(task_id),
|
||||
hop_name VARCHAR(24) NOT NULL,
|
||||
-- CLOUD_OUT/LOCAL_IN/LOCAL_OUT/TERMINAL_IN/TERMINAL_DONE/
|
||||
-- LOCAL_REPORT/CLOUD_IN
|
||||
status VARCHAR(8) NOT NULL, -- 'OK','FAIL','TIMEOUT'
|
||||
err_code VARCHAR(32),
|
||||
err_msg TEXT,
|
||||
occurred_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX idx_task_hop_task ON task_hop(task_id, occurred_at);
|
||||
|
||||
-- ============================================
|
||||
-- 3. 任务明细表(批量任务的逐条结果)
|
||||
-- ============================================
|
||||
CREATE TABLE task_item (
|
||||
item_id BIGSERIAL PRIMARY KEY,
|
||||
task_id UUID NOT NULL REFERENCES task(task_id),
|
||||
biz_key VARCHAR(128) NOT NULL, -- 业务键,例如菜品 ID、人脸 ID
|
||||
status VARCHAR(8) NOT NULL, -- 'OK','FAIL','PENDING'
|
||||
err_msg TEXT,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE (task_id, biz_key)
|
||||
);
|
||||
CREATE INDEX idx_task_item_task ON task_item(task_id, status);
|
||||
```
|
||||
|
||||
**这套表的关键能力**:
|
||||
|
||||
- 任意任务卡在哪一跳,一查 `task_hop` 就知道
|
||||
- 1000 条菜品下发,失败的 3 条精准告诉你是哪 3 条(`task_item.status='FAIL'`)
|
||||
- 本地服务器同样跑这套 Schema(SQLite 简化版),云端拉取本地 hop 状态做对账
|
||||
|
||||
### 3.4 可靠性三大不变量
|
||||
|
||||
1. **先持久化再前进**:每一跳必须本地落库成功后才回 ACK;落库失败 → 让上游重发
|
||||
2. **幂等键 = `task_id` + `biz_key`**:重发不会产生重复数据;终端拿到同一个 task_id 直接覆盖原记录
|
||||
3. **任务永不丢**:本地服务器即使断电重启,任务从最近持久化的状态继续往下走;运维后台可强制重放
|
||||
|
||||
### 3.5 接口契约(节选)
|
||||
|
||||
**云端 → 本地:任务下发**
|
||||
|
||||
```
|
||||
Topic: tenant/{org_id}/canteen/{canteen_id}/task/down
|
||||
QoS: 2
|
||||
Payload:
|
||||
{
|
||||
"task_id": "uuid",
|
||||
"task_type": "DISH_DOWN",
|
||||
"target_scope": { "device_ids": ["T01","T02"] },
|
||||
"payload_ref": "https://oss.../dish-snapshot-2026052601.json",
|
||||
"items_count": 487,
|
||||
"issued_at": "2026-05-26T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**本地 → 云端:跳转回执**
|
||||
|
||||
```
|
||||
Topic: tenant/{org_id}/canteen/{canteen_id}/task/hop
|
||||
Payload:
|
||||
{
|
||||
"task_id": "uuid",
|
||||
"hop_name": "TERMINAL_DONE",
|
||||
"status": "OK",
|
||||
"success_count": 485,
|
||||
"failed_count": 2,
|
||||
"failed_items": [
|
||||
{"biz_key":"DISH_1024","err_msg":"image download timeout"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**本地 → 云端:就餐数据上传**
|
||||
|
||||
```
|
||||
Topic: tenant/{org_id}/canteen/{canteen_id}/task/up
|
||||
Payload: 同 task 主表结构 + items 数组
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、运维后台
|
||||
|
||||
> 复用现有 admin 工程的 **Vue 3 + Vite + TypeScript + Ant Design Vue + Pinia + axios** 技术栈,**沿用 admin 登录体系与权限模型**,作为 admin 工程内的一组独立路由模块(`/edge-ops/*`),与业务模块隔离。
|
||||
|
||||
### 4.1 功能模块
|
||||
|
||||
```
|
||||
运维后台 (admin 工程内 /edge-ops 路由组)
|
||||
├── 任务总览
|
||||
│ ├── 实时大盘:各食堂在线状态、今日任务数、失败率、堆积量
|
||||
│ └── 异常告警:失败率突增、任务堆积超阈值、设备掉线
|
||||
├── 任务管理
|
||||
│ ├── 任务列表(按食堂/类型/状态/时间筛选)
|
||||
│ ├── 任务详情(全链路流转图 + 明细 + 错误堆栈)
|
||||
│ ├── 一键重试(单任务/批量)
|
||||
│ └── 手动创建任务(应急下发)
|
||||
├── 设备管理
|
||||
│ ├── 食堂/本地服务器健康状态
|
||||
│ ├── 终端设备清单(在线、版本、最后心跳)
|
||||
│ └── 远程软件升级触发
|
||||
├── 资源管理
|
||||
│ ├── 视频/人脸库/升级包上传与下发
|
||||
│ └── 各食堂本地缓存命中情况
|
||||
├── 日志与查询
|
||||
│ ├── 实时日志流(SSE 推送)
|
||||
│ └── 数据库可视化查询(只读 SQL + 预设查询模板)
|
||||
└── 系统配置
|
||||
├── 重试策略、超时阈值
|
||||
└── 二级单位/食堂组织树
|
||||
```
|
||||
|
||||
### 4.2 关键页面示意:任务详情
|
||||
|
||||
```
|
||||
[任务 #abc123 · 菜品下发 · 目标:总部食堂 + 5 个分食堂]
|
||||
状态: ⚠ 部分失败 进度: 487/500 耗时: 2m13s
|
||||
|
||||
全链路流转:
|
||||
✓ 云端创建 10:23:01
|
||||
✓ 推送本地 10:23:02 (6/6 食堂)
|
||||
✓ 本地落库 10:23:03 (6/6 食堂)
|
||||
⚠ 终端分发 10:23:05 (5 食堂成功 / 1 食堂超时,自动重试中)
|
||||
└─ 失败明细: 分食堂C - 终端 T07 离线
|
||||
|
||||
操作: [一键重试] [跳过失败项] [取消任务] [导出日志]
|
||||
```
|
||||
|
||||
### 4.3 路由与权限
|
||||
|
||||
- 在 `src/router/index.ts` 的 `modules` 数组中新增「边缘运维」分组,`meta.menuKey = 'edge-ops'`
|
||||
- 路由命名:`/edge-ops/dashboard`、`/edge-ops/tasks`、`/edge-ops/devices`、`/edge-ops/resources`、`/edge-ops/logs`、`/edge-ops/config`
|
||||
- 权限模型:复用 admin 已有 `role`,新增 `edge:read`、`edge:retry`、`edge:upgrade`、`edge:config` 四类细粒度权限
|
||||
- 仅限内部运维人员访问,不对客户开放
|
||||
|
||||
---
|
||||
|
||||
## 五、1 个月落地计划(边跑边迁)
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title 落地排期(2026-05-26 至 2026-06-22)
|
||||
dateFormat YYYY-MM-DD
|
||||
section 第1周 框架
|
||||
项目骨架/任务模型/DB Schema :a1, 2026-05-26, 3d
|
||||
云端↔本地通信层(MQTT QoS2) :a2, after a1, 4d
|
||||
section 第2周 三大功能迁移
|
||||
菜品/营养数据下发改造 :b1, after a2, 3d
|
||||
就餐数据上传改造 :b2, after a2, 3d
|
||||
视频资源下发改造 :b3, after b1, 4d
|
||||
section 第3周 运维后台 + 联调
|
||||
运维后台前后端 :c1, after b3, 5d
|
||||
全链路联调 + 弱网压测 :c2, after b3, 7d
|
||||
section 第4周 灰度 + 切换
|
||||
单食堂灰度试运行 :d1, after c2, 3d
|
||||
分批切换 + 监控 + 回滚预案 :d2, after d1, 4d
|
||||
```
|
||||
|
||||
### 边跑边迁策略
|
||||
|
||||
- **新老并存**:旧 MQ 链路保留,新任务系统**双写一段时间**(老链路真实下发,新链路影子运行)
|
||||
- **每日对账**:跑对账脚本比对新老链路数据一致性,差异 > 0 立即告警
|
||||
- **按食堂灰度**:第 4 周第 1-3 天选 1-2 个食堂切换,稳定后批量推
|
||||
- **回滚开关**:每个食堂有独立的 `use_new_pipeline` 开关,出问题秒级切回老链路
|
||||
- **冻结老链路功能**:迁移期间老链路只修 P0 bug,不再加新功能
|
||||
|
||||
### 验收标准
|
||||
|
||||
| 指标 | 目标 |
|
||||
| --- | --- |
|
||||
| 弱网模拟丢包 30% 下任务最终成功率 | ≥ 99.9% |
|
||||
| 任务平均端到端延迟(局域网正常) | ≤ 3s |
|
||||
| 任意失败任务可定位到具体跳转 + 失败明细 | 100% |
|
||||
| 运维后台一键重试可用率 | 100% |
|
||||
| 灰度食堂连续 3 天无 P0 故障 | 通过 |
|
||||
|
||||
---
|
||||
|
||||
## 六、风险与对策
|
||||
|
||||
| 风险 | 影响 | 对策 |
|
||||
| --- | --- | --- |
|
||||
| 弱网导致云端推送积压 | 数据延迟 | MQTT 持久会话 + 本地服务器主动拉取兜底 + 队列水位告警 |
|
||||
| 本地服务器断电/磁盘损坏 | 任务丢失 | SQLite 启用 WAL + 每日云端快照备份 + 关键任务云端可重放 |
|
||||
| 终端固件升级失败变砖 | 设备瘫痪 | 双分区 A/B 升级 + 失败自动回滚 + 升级前强制健康检查 |
|
||||
| 1 个月排期紧张 | 延期 | **第 1 周架构定型不可压缩**;运维后台 V1 可砍范围(先保任务列表 + 重试 + 日志,大盘和告警 V1.5 补) |
|
||||
| 与四合一系统数据不一致 | 业务事故 | 双写期间每日对账脚本,差异 > 0 告警;切换前必须对账连续 3 天为 0 |
|
||||
| MQTT Broker 单点 | 整体瘫痪 | EMQX 至少双节点集群 + VIP/DNS 漂移 |
|
||||
| 边缘节点版本不一致 | 兼容问题 | 本地服务器协议层做版本协商,云端兼容旧版本 N-1 |
|
||||
|
||||
---
|
||||
|
||||
## 七、待办与开放问题
|
||||
|
||||
1. **EMQX 部署形态**:云端机房自建 vs 公有云托管,需运维侧拉齐
|
||||
2. **对象存储**:大文件(视频/升级包)走哪个 OSS,断点续传 SDK 选型
|
||||
3. **告警通道**:钉钉机器人 / 企业微信 / 短信,需明确接入哪一种
|
||||
4. **灰度食堂选择**:建议选 1 个网络条件好的 + 1 个网络条件差的,覆盖两种典型场景
|
||||
5. **历史任务数据迁移**:老 MQ 在途消息如何处理,是否需要补录到新任务表
|
||||
|
||||
---
|
||||
|
||||
## 八、附录
|
||||
|
||||
### 8.1 与现有 admin 工程的接入点
|
||||
|
||||
- **新增路由模块**:`src/views/edge-ops/`(dashboard / tasks / devices / resources / logs / config)
|
||||
- **新增 API 聚合**:`src/api/edge-ops.ts`,baseURL `/api/edge`,走云端任务网关
|
||||
- **菜单配置**:`MainLayout.vue` 新增「边缘运维」一级菜单,key = `edge-ops`
|
||||
- **权限**:复用 `src/stores/user.ts` 的角色与权限校验,新增四个权限位
|
||||
- **登录态**:完全沿用 admin 现有 token + axios 拦截器机制,**不新建鉴权体系**
|
||||
|
||||
### 8.2 文件清单(新增)
|
||||
|
||||
```
|
||||
zhican/admin/
|
||||
├── src/views/edge-ops/
|
||||
│ ├── dashboard/index.vue
|
||||
│ ├── tasks/index.vue
|
||||
│ ├── tasks/detail.vue
|
||||
│ ├── devices/index.vue
|
||||
│ ├── resources/index.vue
|
||||
│ ├── logs/index.vue
|
||||
│ └── config/index.vue
|
||||
├── src/api/edge-ops.ts
|
||||
└── docs/本地服务器独立微服务化方案.md (本文件)
|
||||
|
||||
服务端(独立工程):
|
||||
zhican-edge/
|
||||
├── edge-cloud-gateway/ 云端任务网关 Spring Boot
|
||||
├── edge-local-server/ 本地服务器 Spring Boot(单 JAR)
|
||||
└── edge-common/ 协议/DTO/任务模型共享包
|
||||
```
|
||||
|
||||
@@ -478,7 +478,7 @@ window.location.href = '../health-consult/consult-list.html';
|
||||
|
||||
**PRD 中引用方式:**
|
||||
- 在每个页面/弹窗的三级标题下方紧跟插入:``
|
||||
- 使用相对路径(基于 `doc/` 目录),md-viewer.html 会自动补全为完整路径
|
||||
- 使用相对路径(基于 `doc/` 目录),`_templates/md-viewer.html` 会自动补全为完整路径
|
||||
|
||||
**截图更新时机:**
|
||||
- 与 PRD 文档同步,仅在用户明确要求更新 PRD 时重新截取
|
||||
|
||||