docs: add design specification and 6-tab comparison audit (49 items, color/layout/dialog/component/type/spec)

This commit is contained in:
2026-05-19 06:29:37 +08:00
parent 51c2598fb7
commit eefcc06a5e
2 changed files with 353 additions and 299 deletions
@@ -0,0 +1,134 @@
# CraftLabs 设计规范 v1.0
> 基于 Figma「安徽地质博物馆 v2.0」设计 Token → delivery-platform-ui 映射评估
> 审核日期: 2026-05-18
---
## 1. 色彩系统
| Token | Figma | 当前 | 评估 |
|-------|-------|------|:--:|
| 页面底色 | `#EAEFFA` | `#f0f2f5` | ⚠️ 建议调整 |
| 卡片面板 | `#FFFFFF` | `#FFFFFF` | ✅ 一致 |
| 主色 | `#2C3E6B` | `#409EFF` | ⚠️ 可选项 |
| 正文文字 | `#000000` | `#303133` | ✅ 可接受 |
| 辅助文字 | `#313131` | `#909399` | ⚠️ 调整 |
| 表头背景 | `#F2F5FC` | `#f5f7fa` | ✅ 可接受 |
| 成功色 | `#E6F7EE/#1A7A3A` | `#f0f9eb/#67c23a` | ✅ 可接受 |
| 警告色 | — | `#E6A23C` | ✅ 默认 |
| 错误色 | `#D54941` | `#F56C6C` | ✅ 可接受 |
| 通知 Badge | `#D54941` | el-badge | ⚠️ 自定义 |
| 边框线 | `#D6DFF0/#E8ECF1` | `#EBEEF5` | ✅ 接近 |
| 侧边栏底 | `#FFFFFF` | `#001529` | 🔴 需修改 |
### CSS 变量建议
```css
:root {
--color-page-bg: #EAEFFA;
--color-card-bg: #FFFFFF;
--color-primary: #2C3E6B;
--color-primary-hover: #3D5A99;
--color-text-primary: #303133;
--color-text-secondary: #606266;
--color-border: #E8ECF1;
--color-th-bg: #F2F5FC;
--color-success: #1A7A3A;
--color-success-bg: #E6F7EE;
--color-danger: #F56C6C;
--color-danger-bg: #FEF0F0;
--color-warning: #E6A23C;
--color-badge: #D54941;
}
```
---
## 2. 布局结构
| 元素 | Figma (px) | 当前 (px) | 评估 |
|------|-----------|----------|:--:|
| Header 高度 | 60 | auto | 🔴 固定 60px |
| Sidebar 宽度 | 232 | 220 | ✅ 可忽略 |
| Sidebar 底色 | `#FFFFFF` | `#001529` 深色 | 🔴 改白色 |
| 面包屑 | 46 | 无 | 🔴 加 el-breadcrumb |
| 左侧 Tree | 280 | 无 | 🔴 许可证页加 el-tree |
| 内容内边距 | 20 | 16-20 | ✅ 一致 |
| 搜索栏 | Header+Tree | Card header | ⚠️ Header 加全站搜索 |
| 卡片 | 6px 圆角+border | 4px 无边框 | ⚠️ 调整 |
---
## 3. 弹框规范
| 类型 | 宽度 | 圆角 | 关键特征 |
|------|:--:|:--:|------|
| 签发许可证 | 560px | 8px | 完整表单 + 特性开关 |
| 新建/编辑 | 480px | 8px | 简化表单 |
| 详情查看 | 480px | 8px | 标签(100px) + 值 |
| 许可证详情 | 520px | 8px | monospace ID + 虚线框 |
| 确认/吊销 | 420px | 8px | 危险色背景 `#FEF0F0` |
```
通用弹框 Token:
- 圆角: 8px
- 阴影: 0 8px 40px rgba(0,0,0,.15)
- Header: padding 16px/20px
- Body: padding 20px
- Footer: gap 10px, 按钮右对齐
- 遮罩: rgba(0,0,0,.45)
- 动画: scale(.96→1) 200ms
- 关闭按钮: 28x28, hover 背景 #F2F5FC
```
---
## 4. 组件
| 组件 | Figma Token | 当前 | 建议 |
|------|-----------|------|------|
| 主按钮 | `#2C3E6B` + shadow | `#409EFF` | 改色 + shadow |
| 表格表头 | `#F2F5FC` / `#2C3E6B` bold | 默认 | 改淡蓝底+深蓝字 |
| 状态标签 | `#E6F7EE/#1A7A3A` | el-tag | 微调色值 |
| 通知 Badge | `#D54941` 20x20 | el-badge | 自定义颜色 |
| 输入框 | focus `#2C3E6B` | focus `#409EFF` | 改 focus 色 |
| 搜索框 | `#F8F9FB` 6px radius | el-input | 加圆角+背景 |
| 侧边菜单 | 白底+蓝选中 | 深色底 | 🔴 改白色方案 |
---
## 5. 字体排版
| 层级 | Figma | 当前 | 评估 |
|------|-------|------|:--:|
| 页面标题 | — | 16px bold | ✅ 增 22px 级 |
| 正文 | 14px `#000000` | 14px `#303133` | ✅ |
| 辅助 | 13px `#313131` | 13px `#909399` | ⚠️ `#606266` |
| 表头 | 12px `#2C3E6B` bold | 默认 | ⚠️ 改色 |
| 代码/ID | — | 12px monospace | ✅ |
---
## 6. 实施方案
### Element Plus CSS 变量覆盖(3 行核心)
```css
:root {
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
}
```
### 工作量估算
| 优先级 | 项目 | 工作量 | 影响 |
|:--:|------|:--:|------|
| P0 | 色彩 CSS 变量覆盖 | 30min | 全局风格统一 |
| P0 | 侧边栏白色方案 | 1h | 视觉对齐 |
| P1 | 面包屑 + Tree | 3h | 导航体验 |
| P1 | Header 搜索+通知 | 2h | 运营效率 |
| P2 | 弹框规范统一 | 2h | 交互一致 |
| P2 | 组件 Token 细化 | 1h | 细节打磨 |
@@ -1,346 +1,266 @@
<template> <template>
<div style="height:100vh;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:13px;display:flex;flex-direction:column"> <div style="background:#EAEFFA;min-height:100vh;font-family:-apple-system,BlinkMacSystemFont,sans-serif">
<!-- ====== HEADER 60px ====== --> <div style="background:linear-gradient(135deg,#2C3E6B,#3D5A99);color:#fff;padding:20px 32px">
<header style="height:60px;background:#fff;border-bottom:1px solid #E8ECF1;display:flex;align-items:center;padding:0 20px;flex-shrink:0;z-index:10"> <div style="max-width:1400px;margin:0 auto"><div style="font-size:22px;font-weight:700;margin-bottom:4px">CraftLabs 设计规范 · 对比审核</div><div style="font-size:13px;opacity:.8">Figma Token delivery-platform-ui 映射评估</div></div>
<div style="display:flex;align-items:center;gap:32px"> </div>
<div style="display:flex;align-items:center;gap:8px"> <div style="background:#fff;border-bottom:1px solid #E8ECF1;position:sticky;top:0;z-index:10">
<div style="width:28px;height:28px;background:linear-gradient(135deg,#2C3E6B,#3D5A99);border-radius:6px"></div> <div style="max-width:1400px;margin:0 auto;display:flex">
<span style="font-weight:700;font-size:16px;color:#2C3E6B;letter-spacing:.5px">CraftLabs</span> <div v-for="t in tabs" :key="t" @click="tab=t" :style="{padding:'12px 24px',cursor:'pointer',fontSize:'14px',fontWeight:tab===t?600:400,color:tab===t?'#2C3E6B':'#606266',borderBottom:tab===t?'2px solid #2C3E6B':'2px solid transparent'}">{{ t }}</div>
</div>
<nav style="display:flex;gap:0"><div v-for="n in ['授权平台','运营分析','系统设置']" :key="n" :style="{padding:'0 16px',height:'60px',lineHeight:'60px',cursor:'pointer',color:n===activeTopNav?'#2C3E6B':'#606266',fontSize:'14px',fontWeight:n===activeTopNav?600:400,borderBottom:n===activeTopNav?'2px solid #2C3E6B':'none'}" @click="activeTopNav=n">{{ n }}</div></nav>
</div>
<div style="margin-left:auto;display:flex;align-items:center;gap:12px">
<div style="display:flex;align-items:center;border:1px solid #E0E3E8;border-radius:6px;padding:4px 10px;gap:6px;width:220px;background:#F8F9FB"><span style="color:#C0C4CC;font-size:14px">🔍</span><input placeholder="搜索..." style="border:none;outline:none;flex:1;font-size:13px;background:transparent;color:#303133"></div>
<div v-for="icon in ['🌐','🔔','⚙️']" :key="icon" style="width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;cursor:pointer;position:relative"><span style="font-size:14px">{{ icon }}</span><span v-if="icon==='🔔'" style="position:absolute;top:4px;right:6px;width:16px;height:16px;background:#D54941;border-radius:50%;font-size:10px;color:#fff;line-height:16px;text-align:center">3</span></div>
<div style="display:flex;align-items:center;gap:6px;padding:4px 10px;border-radius:6px;cursor:pointer"><div style="width:28px;height:28px;border-radius:50%;background:#2C3E6B;color:#fff;text-align:center;line-height:28px;font-size:12px;font-weight:600"></div><span style="color:#303133;font-weight:500">huangping</span></div>
</div>
</header>
<div style="flex:1;display:flex;overflow:hidden;background:#EAEFFA">
<!-- ====== SIDEBAR 232px ====== -->
<aside style="width:232px;background:#fff;border-right:1px solid #E8ECF1;overflow-y:auto;flex-shrink:0;display:flex;flex-direction:column;padding-top:8px">
<div v-for="g in sidebarGroups" :key="g.label" style="margin-bottom:4px">
<div v-if="g.label" style="padding:4px 20px;font-size:11px;color:#C0C4CC;text-transform:uppercase;font-weight:600">{{ g.label }}</div>
<div v-for="item in g.items" :key="item.key" @click="activeModule=item.key;closeAllDialogs()" :style="{display:'flex',alignItems:'center',gap:10,padding:'8px 20px',cursor:'pointer',fontSize:'14px',color:item.key===activeModule?'#2C3E6B':'#606266',background:item.key===activeModule?'#F2F5FC':'',borderRight:item.key===activeModule?'3px solid #2C3E6B':'3px solid transparent',fontWeight:item.key===activeModule?600:400}">
<span style="width:20px;text-align:center">{{ item.icon }}</span><span>{{ item.name }}</span>
<span v-if="item.badge" style="margin-left:auto;background:#D54941;color:#fff;font-size:10px;padding:1px 6px;border-radius:10px;font-weight:600">{{ item.badge }}</span>
</div>
</div>
<div style="margin-top:auto;padding:12px 20px;border-top:1px solid #F2F5FC;font-size:11px;color:#C0C4CC">CraftLabs v0.1.0</div>
</aside>
<div style="flex:1;overflow:hidden;display:flex;flex-direction:column">
<div style="height:46px;background:#fff;border-bottom:1px solid #E8ECF1;display:flex;align-items:center;padding:0 20px;gap:6px;font-size:13px;flex-shrink:0">
<span style="color:#909399">授权运营</span><span style="color:#C0C4CC"></span><span style="color:#2C3E6B;font-weight:600">{{ pageNames[activeModule] }}</span>
</div>
<div style="flex:1;display:flex;overflow:hidden">
<div v-if="showTree" style="width:280px;background:#fff;border-right:1px solid #E8ECF1;overflow-y:auto;flex-shrink:0;padding:12px 0">
<div style="padding:0 16px 10px"><div style="display:flex;align-items:center;border:1px solid #E0E3E8;border-radius:6px;padding:6px 10px;gap:6px;background:#F8F9FB"><span style="color:#C0C4CC;font-size:12px">🔍</span><input placeholder="搜索..." style="border:none;outline:none;flex:1;font-size:12px;background:transparent"></div></div>
<div v-for="t in treeNodes" :key="t.label" @click="treeNodes.forEach(n=>n.active=false);t.active=true;t.expanded=!t.expanded" :style="{padding:'5px 16px 5px 20px',cursor:'pointer',fontSize:'13px',color:t.active?'#2C3E6B':'#606266',fontWeight:t.active?600:400,background:t.active?'#F2F5FC':'',borderRight:t.active?'3px solid #2C3E6B':'none',display:'flex',alignItems:'center',gap:6}">
<span style="font-size:12px;width:16px;text-align:center">{{ t.expanded?'▾':'▸' }}</span><span>{{ t.icon }}</span><span>{{ t.label }}</span><span v-if="t.count" style="margin-left:auto;font-size:11px;color:#C0C4CC">{{ t.count }}</span>
</div>
</div>
<div style="flex:1;overflow-y:auto;padding:16px 20px">
<!-- ====== DASHBOARD ====== -->
<template v-if="activeModule==='dashboard'">
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-bottom:16px">
<div v-for="s in dashboardStats" :key="s.label" class="card card-hover" style="padding:18px"><div style="font-size:12px;color:#909399;margin-bottom:6px">{{ s.label }}</div><div style="font-size:28px;font-weight:700;color:#303133;margin-bottom:4px">{{ s.value }}</div><div :style="{fontSize:'12px',color:s.trend>0?'#67C23A':'#F56C6C'}">{{ s.trend>0?'↑':'↓' }} {{ Math.abs(s.trend) }}%</div></div>
</div>
<div style="display:grid;grid-template-columns:2fr 1fr;gap:14px">
<div class="card" style="padding:16px"><div style="font-weight:600;color:#303133;margin-bottom:12px">📊 许可证签发趋势</div><div style="height:200px;display:flex;align-items:flex-end;gap:12px;padding:0 8px"><div v-for="(v,i) in [45,52,38,60,55,70,65,80]" :key="i" style="flex:1;background:linear-gradient(180deg,#2C3E6B,#3D5A99);border-radius:4px 4px 0 0" :style="{height:v+'px'}"></div></div></div>
<div class="card" style="padding:16px"><div style="font-weight:600;color:#303133;margin-bottom:12px"> 待处理</div><div v-for="a in alerts" :key="a.text" style="padding:8px 0;border-bottom:1px solid #F2F5FC;display:flex;align-items:center;gap:8px"><span :style="{width:6,height:6,borderRadius:'50%',background:a.color}"></span><span style="flex:1;color:#606266;font-size:12px">{{ a.text }}</span><span style="font-size:11px;color:#C0C4CC">{{ a.time }}</span></div></div>
</div>
</template>
<!-- ====== LIST PAGES (customers/contracts/deliveries/license-sns) ====== -->
<template v-if="showListPage">
<div class="card" style="padding:12px 16px;margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px">
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<div style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;display:flex;align-items:center;gap:6px;background:#F8F9FB;width:200px"><span style="color:#C0C4CC;font-size:12px">🔍</span><input placeholder="搜索..." style="border:none;outline:none;flex:1;font-size:13px;background:transparent"></div>
<select style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>全部状态</option></select>
<button class="btn-primary" style="padding:6px 14px;font-size:13px" @click="openViewList">查询</button>
</div>
<button class="btn-primary" style="padding:7px 16px;font-size:13px;box-shadow:0 2px 6px rgba(44,62,107,.2)" @click="openDialog('create')"> 新建{{ pageNames[activeModule] }}</button>
</div>
<div class="card" style="overflow:hidden">
<table class="dt"><thead><tr><th>ID/编号</th><th>名称</th><th>关联</th><th>状态</th><th>时间</th><th style="width:120px">操作</th></tr></thead>
<tbody><tr v-for="i in 5" :key="i" style="border-bottom:1px solid #F2F5FC"><td style="font-family:monospace;font-size:12px;color:#303133">{{ 'CT-2026-0'+((i-1)*2+1).toString().padStart(3,'0') }}</td><td style="font-weight:500;color:#303133">{{ names[i-1] }}</td><td style="color:#606266">{{ refs[i-1] }}</td><td><span class="tag" :style="tagStyle(i===3?'revoked':'active')">{{ i===3?'已吊销':'活跃' }}</span></td><td style="color:#909399;font-size:12px">2026-05-{{ (15-i*2).toString().padStart(2,'0') }}</td><td><span class="lnk" @click="openDialog('detail',names[i-1])">详情</span><span v-if="i!==3" class="lnk" style="color:#F56C6C" @click="openDialog('confirm',names[i-1])">删除</span></td></tr></tbody>
</table>
<div style="padding:10px 16px;display:flex;justify-content:flex-end;gap:8px;font-size:12px;color:#909399;border-top:1px solid #F2F5FC"><span> 128 </span><span class="pc"></span><span class="pc" style="color:#2C3E6B;font-weight:600">1</span><span class="pc">2</span><span></span><span class="pc">13</span><span class="pc"></span></div>
</div>
</template>
<!-- ====== LICENSE MANAGEMENT ====== -->
<template v-if="activeModule==='licenses'">
<div class="card" style="padding:12px 16px;margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px">
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<div style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;display:flex;align-items:center;gap:6px;background:#F8F9FB;width:200px"><span style="color:#C0C4CC;font-size:12px">🔍</span><input placeholder="搜索许可证ID..." style="border:none;outline:none;flex:1;font-size:13px;background:transparent"></div>
<select style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>全部类型</option><option>订阅</option><option>永久</option><option>试用</option></select>
<select style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>全部状态</option></select>
<button class="btn-primary" style="padding:6px 14px;font-size:13px">查询</button>
</div>
<button class="btn-primary" style="padding:7px 16px;font-size:13px;box-shadow:0 2px 6px rgba(44,62,107,.2)" @click="openDialog('issue')"> 签发许可证</button>
</div>
<div class="card" style="overflow:hidden">
<table class="dt"><thead><tr><th>许可证 ID</th><th>租户</th><th>产品</th><th>类型</th><th>终端</th><th>宽限</th><th>状态</th><th>签发时间</th><th style="width:120px">操作</th></tr></thead>
<tbody><tr v-for="l in licenseData" :key="l.id" style="border-bottom:1px solid #F2F5FC"><td style="font-family:monospace;font-size:12px;color:#303133">{{ l.id }}</td><td style="color:#303133">{{ l.tenant }}</td><td style="color:#606266">{{ l.product }}</td><td>{{ l.type }}</td><td style="color:#606266;text-align:center">{{ l.devices }}</td><td style="color:#606266;text-align:center">{{ l.grace }}天</td><td><span :class="'tag tag-'+l.status">{{ statusLabels[l.status] }}</span></td><td style="color:#909399;font-size:12px">{{ l.issued }}</td><td><span class="lnk" @click="openDialog('detail-license',l)">详情</span><span v-if="l.status==='active'" class="lnk" style="color:#F56C6C" @click="openDialog('revoke',l)">吊销</span></td></tr></tbody>
</table>
<div style="padding:10px 16px;display:flex;justify-content:flex-end;gap:8px;font-size:12px;color:#909399;border-top:1px solid #F2F5FC"><span> 47 </span><span class="pc"></span><span class="pc" style="color:#2C3E6B;font-weight:600">1</span><span class="pc">2</span><span></span><span class="pc">5</span><span class="pc"></span></div>
</div>
</template>
<!-- ====== CALLBACKS ====== -->
<template v-if="activeModule==='callbacks'">
<div class="card" style="padding:12px 16px;margin-bottom:12px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
<select style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>全部状态</option></select>
<select style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>全部事件</option></select>
<button class="btn-primary" style="padding:6px 14px;font-size:13px">查询</button>
</div>
<div class="card" style="overflow:hidden">
<table class="dt"><thead><tr><th>来源</th><th>事件类型</th><th>SN</th><th>状态</th><th>收件时间</th><th style="width:100px">操作</th></tr></thead>
<tbody><tr v-for="c in callbackData" :key="c.id" style="border-bottom:1px solid #F2F5FC"><td style="color:#303133">{{ c.source }}</td><td style="font-family:monospace;font-size:12px;color:#606266">{{ c.event }}</td><td style="font-family:monospace;font-size:12px;color:#303133">{{ c.sn }}</td><td><span :class="'tag tag-'+c.status.toLowerCase()">{{ c.status==='PENDING'?'待处理':c.status==='PROCESSED'?'已处理':'忽略' }}</span></td><td style="color:#909399;font-size:12px">{{ c.time }}</td><td><span class="lnk" @click="openDialog('detail-callback',c)">详情</span></td></tr></tbody>
</table>
</div>
</template>
<!-- ====== INTEGRATION ====== -->
<template v-if="activeModule==='integration-envs'||activeModule==='integration-plines'">
<div class="card" style="overflow:hidden"><table class="dt"><thead><tr><th>名称</th><th>标识</th><th>说明</th><th>状态</th></tr></thead>
<tbody><tr v-for="i in 4" :key="i" style="border-bottom:1px solid #F2F5FC"><td style="font-weight:500;color:#303133">{{ ['生产环境','预发布','测试环境','开发环境'][i-1] }}</td><td style="font-family:monospace;font-size:12px;color:#606266">{{ ['prod','staging','test','dev'][i-1] }}</td><td style="color:#909399">—</td><td><span :class="'tag tag-'+(i===1?'active':'inactive')">{{ i===1?'启用':'维护中' }}</span></td></tr></tbody></table></div>
</template>
<!-- ====== SETTINGS - KEYS ====== -->
<template v-if="activeModule==='settings-keys'">
<div class="card" style="padding:12px 16px;margin-bottom:12px;display:flex;justify-content:space-between;align-items:center"><span style="font-weight:600;color:#303133">RSA 密钥对管理</span><button class="btn-primary" style="padding:7px 16px;font-size:13px"> 生成密钥对</button></div>
<div class="card" style="overflow:hidden"><table class="dt"><thead><tr><th>Key ID</th><th>算法</th><th>状态</th><th>创建时间</th><th style="width:120px">操作</th></tr></thead>
<tbody><tr v-for="k in keyData" :key="k.id" style="border-bottom:1px solid #F2F5FC"><td style="font-family:monospace;font-size:12px;color:#303133">{{ k.id }}</td><td style="color:#606266">{{ k.algo }}</td><td><span :class="'tag tag-'+(k.status==='active'?'active':'inactive')">{{ k.status==='active'?'活跃':'已轮换' }}</span></td><td style="color:#909399;font-size:12px">{{ k.time }}</td><td><span class="lnk">查看公钥</span><span v-if="k.status==='active'" class="lnk" style="color:#E6A23C">轮换</span></td></tr></tbody></table></div>
</template>
</div>
</div>
</div> </div>
</div> </div>
<div style="max-width:1400px;margin:0 auto;padding:24px 32px">
<!-- COLOR -->
<div v-if="tab==='🎨 色彩'">
<h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">🎨 色彩系统</h2>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:14px">
<div v-for="c in colors" :key="c.name" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:16px;box-shadow:0 1px 2px rgba(0,0,0,.03)">
<div style="display:flex;align-items:center;gap:10px">
<div :style="{width:48,height:48,borderRadius:6,border:'1px solid #E8ECF1',background:c.figma}"></div>
<span style="color:#C0C4CC;font-size:18px"></span>
<div :style="{width:48,height:48,borderRadius:6,border:'1px solid #E8ECF1',background:c.current}"></div>
</div>
<div style="margin-top:8px;font-weight:600;color:#303133;font-size:13px">{{ c.name }}</div>
<div style="font-size:12px;color:#909399;margin-top:2px">{{ c.usage }}</div>
<div style="display:flex;gap:4px;margin-top:6px;flex-wrap:wrap">
<code style="font-size:10px;background:#F2F5FC;padding:2px 5px;border-radius:3px">{{ c.figma }}</code>
<code style="font-size:10px;background:#F2F5FC;padding:2px 5px;border-radius:3px">{{ c.current }}</code>
</div>
<div :style="{marginTop:8,display:'inline-block',padding:'3px 8px',borderRadius:3,fontSize:11,fontWeight:600,background:c.gap==='lo'?'#E6F7EE':c.gap==='med'?'#FDF6EC':'#fef0f0',color:c.gap==='lo'?'#1A7A3A':c.gap==='med'?'#E6A23C':'#F56C6C'}">{{ c.verdict }}</div>
</div>
</div>
</div>
<!-- ====== DIALOG OVERLAY ====== --> <!-- LAYOUT -->
<div v-if="dialogType" style="position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;display:flex;align-items:center;justify-content:center" @click.self="closeAllDialogs()"> <div v-if="tab==='📐 布局'">
<!-- ISSUE LICENSE DIALOG 520px --> <h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">📐 布局结构</h2>
<div v-if="dialogType==='issue'" class="dlg" style="width:560px;max-height:85vh;overflow-y:auto"> <div style="display:flex;gap:20px;flex-wrap:wrap;margin-bottom:16px">
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">签发许可证</span><span class="dlg-close" @click="closeAllDialogs()"></span></div> <div style="flex:1;min-width:280px;background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:16px">
<div class="dlg-body"> <div style="font-weight:600;color:#2C3E6B;margin-bottom:8px;font-size:14px">Figma 规范</div>
<div class="form-g"><label class="form-l">租户ID <span style="color:#F56C6C">*</span></label><input class="form-i" placeholder="如 craftlabs-wharf-prod"></div> <div style="background:#EAEFFA;border:1px dashed #2C3E6B;border-radius:4px;padding:4px;font-size:11px">
<div class="form-g"><label class="form-l">产品名称</label><input class="form-i" placeholder="如 wharf-inspection-v2"></div> <div style="background:#fff;height:20px;line-height:20px;text-align:center;color:#909399;margin-bottom:2px">Header 60px</div>
<div class="form-g" style="display:flex;gap:12px"> <div style="display:flex">
<div style="flex:1"><label class="form-l" style="display:block;margin-bottom:4px">授权类型</label> <div style="background:#fff;width:52px;min-height:100px;font-size:9px;color:#909399;padding:3px">Menu 232</div>
<select class="form-i" style="width:100%"><option>订阅 (subscription)</option><option>永久 (perpetual)</option><option>试用 (trial)</option></select> <div style="flex:1;padding:3px">
</div> <div style="background:#fff;height:15px;line-height:15px;text-align:center;font-size:9px;color:#606266;margin-bottom:2px">Breadcrumb 46</div>
<div style="flex:1"><label class="form-l" style="display:block;margin-bottom:4px">有效期()</label><input class="form-i" type="number" value="365" style="width:100%"></div> <div style="display:flex;gap:1px">
</div> <div style="background:#fff;width:48px;font-size:8px;color:#909399;padding:2px;text-align:center">Tree 280</div>
<div class="form-g" style="display:flex;gap:12px"> <div style="flex:1;background:#fff;font-size:9px;color:#606266;padding:2px;text-align:center;border:1px dashed #D6DFF0">Main 1368</div>
<div style="flex:1"><label class="form-l" style="display:block;margin-bottom:4px">最大终端数</label><input class="form-i" type="number" value="5" style="width:100%"></div> </div>
<div style="flex:1"><label class="form-l" style="display:block;margin-bottom:4px">离线宽限期()</label><input class="form-i" type="number" value="7" style="width:100%"></div> </div>
</div> </div>
<div class="form-g"><label class="form-l" style="display:block;margin-bottom:6px">特性开关</label>
<div style="display:flex;flex-wrap:wrap;gap:10px 20px">
<label v-for="f in ['advanced_analytics','real_time_monitor','api_export','multi_tenant']" :key="f" style="display:flex;align-items:center;gap:6px;cursor:pointer;font-size:13px;color:#606266"><input type="checkbox" checked style="accent-color:#2C3E6B">{{ featureNames[f] }}</label>
</div> </div>
</div> </div>
<div style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px"> <div style="flex:1;min-width:280px;background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:16px">
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div> <div style="font-weight:600;color:#409EFF;margin-bottom:8px;font-size:14px">当前实现</div>
<div style="font-size:12px;color:#606266;line-height:1.8"> <div style="background:#f0f2f5;border:1px dashed #409EFF;border-radius:4px;padding:4px;font-size:11px">
<div> 弹窗宽度: <b>560px</b>表单类弹窗标准宽度</div> <div style="background:#fff;height:20px;line-height:20px;text-align:right;color:#909399;margin-bottom:2px;padding:0 6px">el-header auto</div>
<div> 表单项间距: <b>16px</b></div> <div style="display:flex">
<div> 标签宽度: <b>自适应</b>左侧标签 + 右侧控件</div> <div style="background:#001529;width:52px;min-height:100px;font-size:9px;color:#fff;padding:3px;opacity:.7">Aside 220</div>
<div> Footer: <b>右对齐</b>取消在左/确认在右</div> <div style="flex:1;padding:3px">
<div style="font-size:9px;color:#F56C6C;padding:2px;text-align:center"> Breadcrumb</div>
<div style="background:#fff;font-size:9px;color:#606266;padding:4px;text-align:center;margin-top:2px;border:1px dashed #C0C4CC">el-card 全宽</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
<div class="dlg-footer"><button class="btn-cancel" @click="closeAllDialogs()">取消</button><button class="btn-primary" style="padding:8px 20px;font-size:14px" @click="closeAllDialogs()">签发</button></div> <div style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow-x:auto">
<table style="width:100%;border-collapse:collapse;font-size:13px">
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600;font-size:12px"><th style="padding:8px 12px;text-align:left">元素</th><th style="padding:8px 12px;text-align:left">Figma</th><th style="padding:8px 12px;text-align:left">当前</th><th style="padding:8px 12px;text-align:left;width:70px">状态</th><th style="padding:8px 12px;text-align:left">建议</th></tr></thead>
<tbody><tr v-for="l in layouts" :key="l.el" style="border-bottom:1px solid #F2F5FC"><td style="padding:8px 12px;font-weight:600;color:#303133">{{ l.el }}</td><td style="padding:8px 12px;color:#606266;font-size:12px;font-family:monospace">{{ l.figma }}</td><td style="padding:8px 12px;color:#606266;font-size:12px;font-family:monospace">{{ l.now }}</td><td style="padding:8px 12px"><span :class="'gap-'+l.gap">{{ {hi:'缺失',med:'差异',lo:'一致'}[l.gap] }}</span></td><td style="padding:8px 12px;color:#303133;font-size:12px">{{ l.sug }}</td></tr></tbody>
</table>
</div>
</div> </div>
<!-- CREATE/EDIT DIALOG 480px --> <!-- DIALOG -->
<div v-if="dialogType==='create'" class="dlg" style="width:480px"> <div v-if="tab==='🪟 弹框'">
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">新建{{ pageNames[activeModule] }}</span><span class="dlg-close" @click="closeAllDialogs()"></span></div> <h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">🪟 弹框规范</h2>
<div class="dlg-body"> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:14px">
<div class="form-g"><label class="form-l">名称 <span style="color:#F56C6C">*</span></label><input class="form-i" placeholder="请输入名称"></div> <div v-for="d in dialogs" :key="d.name" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:16px">
<div class="form-g"><label class="form-l">备注</label><input class="form-i" placeholder="选填"></div> <div style="font-weight:600;color:#303133;font-size:14px;margin-bottom:2px">{{ d.name }}</div>
<div style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px"> <div style="font-size:11px;color:#909399;margin-bottom:10px">{{ d.trigger }} · {{ d.width }}</div>
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div> <div style="background:#F8F9FB;border-radius:4px;padding:10px;font-size:11px;line-height:1.8;color:#606266">
<div style="font-size:12px;color:#606266;line-height:1.8"><div> 弹窗宽度: <b>480px</b>简单表单</div><div> 与签发弹窗同体系字段少时缩小宽度</div></div> <div v-for="(v,k) in d.specs" :key="k"><b>{{ k }}</b>: {{ v }}</div>
</div>
<div style="margin-top:8px;display:flex;gap:4px;flex-wrap:wrap">
<code v-for="t in d.tokens" :key="t" style="font-size:10px;color:#2C3E6B;background:#F2F5FC;padding:2px 5px;border-radius:3px">{{ t }}</code>
</div>
</div> </div>
</div> </div>
<div class="dlg-footer"><button class="btn-cancel" @click="closeAllDialogs()">取消</button><button class="btn-primary" style="padding:8px 20px;font-size:14px" @click="closeAllDialogs()">保存</button></div>
</div> </div>
<!-- DETAIL DIALOG 480px --> <!-- COMPONENT -->
<div v-if="dialogType==='detail'" class="dlg" style="width:480px"> <div v-if="tab==='🧩 组件'">
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">详情 - {{ selectedItem }}</span><span class="dlg-close" @click="closeAllDialogs()"></span></div> <h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">🧩 组件规范</h2>
<div class="dlg-body"> <div style="display:grid;grid-template-columns:repeat(2,1fr);gap:14px">
<div class="detail-row" v-for="r in ['ID/编号','名称','状态','创建时间','关联项目','负责人']" :key="r"><span class="detail-label">{{ r }}</span><span class="detail-value">{{ r==='状态'?'活跃':r==='创建时间'?'2026-05-10':r==='关联项目'?'码头南沙二期':r==='负责人'?'huangping':'' }}</span></div> <div v-for="cp in components" :key="cp.name" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:16px">
<div style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px"> <div style="font-weight:600;color:#303133;font-size:14px;margin-bottom:2px">{{ cp.name }}</div>
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div> <div style="font-size:11px;color:#909399;margin-bottom:8px">{{ cp.cat }}</div>
<div style="font-size:12px;color:#606266;line-height:1.8"><div> 弹窗宽度: <b>480px</b>只读详情</div><div> 布局: <b>标签- 双列</b>标签 100px 宽右对齐</div><div> 行间距: <b>12px</b></div></div> <div style="display:flex;gap:16px;flex-wrap:wrap">
<div style="min-width:100px"><div style="font-size:10px;color:#C0C4CC;margin-bottom:4px">FIGMA</div><code v-for="t in cp.ft" :key="t" style="font-size:10px;color:#2C3E6B;background:#F2F5FC;padding:2px 5px;border-radius:3px;margin:2px;display:inline-block">{{ t }}</code></div>
<div style="min-width:100px"><div style="font-size:10px;color:#C0C4CC;margin-bottom:4px">CURRENT</div><code v-for="t in cp.ct" :key="t" style="font-size:10px;color:#409EFF;background:#ECF5FF;padding:2px 5px;border-radius:3px;margin:2px;display:inline-block">{{ t }}</code></div>
</div>
<div style="margin-top:8px;font-size:12px;color:#606266">{{ cp.ass }}</div>
</div> </div>
</div> </div>
<div class="dlg-footer"><button class="btn-primary" style="padding:8px 20px;font-size:14px" @click="closeAllDialogs()">关闭</button></div>
</div> </div>
<!-- DETAIL-LICENSE DIALOG 520px --> <!-- TYPOGRAPHY -->
<div v-if="dialogType==='detail-license'" class="dlg" style="width:520px"> <div v-if="tab==='🔤 字体'">
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">许可证详情</span><span class="dlg-close" @click="closeAllDialogs()"></span></div> <h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">🔤 字体排版</h2>
<div class="dlg-body"> <div style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow-x:auto">
<div class="detail-row" v-for="r in licenseDetailRows" :key="r.label"><span class="detail-label">{{ r.label }}</span><span class="detail-value" :style="r.code?{fontFamily:'monospace',fontSize:'12px'}:{}">{{ r.value }}</span></div> <table style="width:100%;border-collapse:collapse;font-size:13px">
<div style="background:#F2F5FC;border:1px dashed #D6DFF0;border-radius:6px;padding:12px;margin-top:12px"> <thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600;font-size:12px"><th style="padding:8px 12px;text-align:left">层级</th><th style="padding:8px 12px;text-align:left">Figma</th><th style="padding:8px 12px;text-align:left">当前</th><th style="padding:8px 12px;text-align:left;width:70px">状态</th><th style="padding:8px 12px;text-align:left">建议</th></tr></thead>
<div style="font-size:12px;color:#2C3E6B;font-weight:600;margin-bottom:6px">📐 许可证详情布局</div> <tbody>
<div style="font-size:12px;color:#606266;line-height:1.8"><div> 弹窗宽度: <b>520px</b>含长许可证ID</div><div> 关键字段用 <b>monospace</b> 字体展示许可证ID</div><div> 特性区用 <b>蓝色虚线框</b> 区分于基础信息</div></div> <tr v-for="t in types" :key="t.lv" style="border-bottom:1px solid #F2F5FC"><td style="padding:8px 12px;font-weight:600">{{ t.lv }}</td><td style="padding:8px 12px;font-size:12px;font-family:monospace;color:#606266">{{ t.figma }}</td><td style="padding:8px 12px;font-size:12px;font-family:monospace;color:#606266">{{ t.now }}</td><td style="padding:8px 12px"><span :class="'gap-'+t.gap">{{ {hi:'缺失',med:'差异',lo:'一致'}[t.gap] }}</span></td><td style="padding:8px 12px;font-size:12px;color:#303133">{{ t.sug }}</td></tr>
</div> </tbody>
</table>
</div> </div>
<div class="dlg-footer"><button class="btn-cancel" @click="closeAllDialogs()">关闭</button><button v-if="selectedLicense&&selectedLicense.status==='active'" class="btn-primary" style="padding:8px 20px;font-size:14px;background:#F56C6C" @click="openDialog('revoke',selectedLicense)">吊销</button></div>
</div> </div>
<!-- CONFIRM / REVOKE DIALOG 420px --> <!-- SPEC DOC -->
<div v-if="dialogType==='confirm'" class="dlg" style="width:420px;text-align:center"> <div v-if="tab==='📋 规范'">
<div class="dlg-body" style="padding:32px 24px 20px"> <h2 style="font-size:20px;font-weight:700;color:#303133;margin-bottom:16px">📋 设计规范文档</h2>
<div style="font-size:48px;margin-bottom:16px"></div> <div style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;padding:20px;font-family:monospace;font-size:12px;line-height:2;white-space:pre-wrap;color:#303133">{{ specContent }}</div>
<div style="font-size:16px;font-weight:600;color:#303133;margin-bottom:8px">确认删除</div>
<div style="font-size:13px;color:#909399">确定要删除 <b style="color:#303133">{{ selectedItem }}</b>此操作不可撤销</div>
<div style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:16px;text-align:left">
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div>
<div style="font-size:12px;color:#606266;line-height:1.8"><div> 弹窗宽度: <b>420px</b>确认类最窄</div><div> 内容: <b>居中</b>图标+标题+说明</div><div> 按钮: 取消在左 / 确认在右</div></div>
</div>
</div>
<div class="dlg-footer" style="justify-content:center;gap:12px"><button class="btn-cancel" @click="closeAllDialogs()">取消</button><button style="border:none;background:#F56C6C;color:#fff;padding:8px 20px;border-radius:4px;font-size:14px;cursor:pointer;font-weight:500" @click="closeAllDialogs()">确认删除</button></div>
</div> </div>
<!-- REVOKE LICENSE DIALOG --> <!-- SUMMARY -->
<div v-if="dialogType==='revoke'" class="dlg" style="width:420px;text-align:center"> <div style="background:linear-gradient(135deg,#2C3E6B,#3D5A99);color:#fff;border-radius:8px;padding:24px;margin-top:20px">
<div class="dlg-body" style="padding:32px 24px 20px"> <div style="font-weight:700;font-size:16px;margin-bottom:12px">📊 评估总结</div>
<div style="font-size:48px;margin-bottom:16px">🛑</div> <div style="display:grid;grid-template-columns:repeat(4,1fr);gap:16px;font-size:13px">
<div style="font-size:16px;font-weight:600;color:#303133;margin-bottom:8px">确认吊销许可证</div> <div><span style="font-size:28px;font-weight:700">{{ summary.total }}</span><br>检查项</div>
<div style="font-size:13px;color:#909399">确定要吊销许可证<br><b style="color:#303133;font-family:monospace">{{ selectedLicense?.id }}</b><br>吊销后该许可证立即失效</div> <div><span style="font-size:28px;font-weight:700;color:#67C23A">{{ summary.ok }}</span><br>一致通过</div>
<div style="background:#FEF0F0;border:1px solid #FBC4C4;border-radius:6px;padding:12px;margin-top:16px;text-align:left"> <div><span style="font-size:28px;font-weight:700;color:#E6A23C">{{ summary.warn }}</span><br>需调整</div>
<div style="font-size:12px;color:#F56C6C;font-weight:600;margin-bottom:6px">📐 危险操作布局</div> <div><span style="font-size:28px;font-weight:700;color:#F56C6C">{{ summary.fail }}</span><br>待实现</div>
<div style="font-size:12px;color:#606266;line-height:1.8"><div> 背景: <b>淡红色 #FEF0F0</b> 提示危险</div><div> 确认按钮: <b>红色 #F56C6C</b></div><div> 与普通确认弹窗区分视觉权重</div></div>
</div>
</div> </div>
<div class="dlg-footer" style="justify-content:center;gap:12px"><button class="btn-cancel" @click="closeAllDialogs()">取消</button><button style="border:none;background:#F56C6C;color:#fff;padding:8px 20px;border-radius:4px;font-size:14px;cursor:pointer;font-weight:500" @click="closeAllDialogs()">确认吊销</button></div> <div style="margin-top:12px;font-size:12px;opacity:.8">推荐: Element Plus + CSS变量覆盖, 吸收 Figma 蓝灰底色+深蓝主色, 零组件库迁移</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, computed, reactive } from 'vue' import { ref, computed } from 'vue'
const tab = ref('🎨 色彩')
const activeModule = ref('licenses') const colors = [
const activeTopNav = ref('授权平台') { name:'页面底色', usage:'全局背景', figma:'#EAEFFA', current:'#f0f2f5', gap:'med', verdict:'⚠️ 建议调整' },
const dialogType = ref('') { name:'卡片面板', usage:'表格/菜单/面包屑', figma:'#FFFFFF', current:'#FFFFFF', gap:'lo', verdict:'✅ 一致' },
const selectedItem = ref('') { name:'主色', usage:'CTA/链接/选中', figma:'#2C3E6B', current:'#409EFF', gap:'med', verdict:'⚠️ 可选项' },
const selectedLicense = ref(null) { name:'正文', usage:'标题/内容', figma:'#000000', current:'#303133', gap:'lo', verdict:'✅ 可接受' },
{ name:'辅助文字', usage:'说明/占位', figma:'#313131', current:'#909399', gap:'med', verdict:'⚠️ 建议调整' },
const pageNames = { dashboard:'工作台概览', customers:'客户管理', contracts:'合同管理', deliveries:'交付管理', 'license-sns':'许可 SN', licenses:'许可证管理', callbacks:'Callback 收件箱', 'integration-envs':'集成环境', 'integration-plines':'产品线', 'settings-keys':'密钥管理' } { name:'表头背景', usage:'表格标题行', figma:'#F2F5FC', current:'#f5f7fa', gap:'lo', verdict:'✅ 可接受' },
const showListPage = computed(() => ['customers','contracts','deliveries','license-sns'].includes(activeModule.value)) { name:'成功色', usage:'活跃标签', figma:'#E6F7EE/#1A7A3A', current:'#f0f9eb/#67c23a', gap:'lo', verdict:'✅ 可接受' },
const showTree = computed(() => ['licenses','customers','contracts'].includes(activeModule.value)) { name:'警告色', usage:'待处理', figma:'—', current:'#E6A23C', gap:'lo', verdict:'✅ Element Plus默认' },
{ name:'错误色', usage:'吊销/删除', figma:'#D54941', current:'#F56C6C', gap:'lo', verdict:'✅ 可接受' },
const sidebarGroups = [ { name:'Badge', usage:'通知红点', figma:'#D54941', current:'el-badge默认', gap:'med', verdict:'⚠️ 自定义色' },
{ label:'', items:[{ key:'dashboard', icon:'📊', name:'工作台概览' }] }, { name:'边框', usage:'卡片/表格', figma:'#D6DFF0/#E8ECF1', current:'#EBEEF5', gap:'lo', verdict:'✅ 接近' },
{ label:'业务管理', items:[{ key:'customers', icon:'👥', name:'客户管理' },{ key:'contracts', icon:'📋', name:'合同管理' },{ key:'deliveries', icon:'📦', name:'交付管理' },{ key:'license-sns', icon:'🔑', name:'许可 SN' }] }, { name:'侧边栏', usage:'导航背景', figma:'#FFFFFF白', current:'#001529深', gap:'hi', verdict:'🔴 需修改' },
{ label:'授权运营', items:[{ key:'licenses', icon:'🛡️', name:'许可证管理', badge:'NEW' },{ key:'callbacks', icon:'📨', name:'Callback 收件箱', badge:'3' }] },
{ label:'集成配置', items:[{ key:'integration-envs', icon:'🌐', name:'集成环境' },{ key:'integration-plines', icon:'📱', name:'产品线' }] },
{ label:'系统运维', items:[{ key:'settings-keys', icon:'🔐', name:'密钥管理' }] },
] ]
const opens = (t, item) => { const layouts = [
if (activeModule.value !== 'licenses' && t === 'issue') return { el:'整体框架', figma:'Header60+Sidebar232+Content', now:'Header auto+Aside220+Content', gap:'lo', sug:'✅ 结构一致' },
dialogType.value = t { el:'Header', figma:'60px 固定高, 搜索+通知+用户', now:'auto高, 仅用户名+退出', gap:'hi', sug:'🔴 固定60px, 加搜索/通知/用户' },
selectedItem.value = typeof item === 'string' ? item : '' { el:'Sidebar底色', figma:'#FFFFFF白底+淡边框', now:'#001529深色', gap:'hi', sug:'🔴 改为白色底色方案' },
if (t === 'revoke' || t === 'detail-license') selectedLicense.value = item { el:'面包屑', figma:'46px 高, 内容区顶部', now:'无', gap:'hi', sug:'🔴 加 el-breadcrumb' },
} { el:'左侧树面板', figma:'280px 宽, 搜索框+树', now:'无', gap:'hi', sug:'🔴 许可证/客户页加 el-tree' },
{ el:'内容区内边距', figma:'20px左右', now:'16-20px card内', gap:'lo', sug:'✅ 保持一致' },
const treeNodes = ref([{ icon:'🏢', label:'创飞码头项目', expanded:true, active:true, count:12 },{ icon:'🏫', label:'学校合作项目', expanded:false, active:false, count:8 },{ icon:'🔬', label:'实验室项目', expanded:false, active:false, count:5 },{ icon:'📐', label:'内部测试', expanded:false, active:false, count:3 }]) { el:'搜索栏', figma:'Header全站+Tree内', now:'卡片header内', gap:'med', sug:'Header加全局搜索, 页面内保留' },
{ el:'内容区顶间距', figma:'面包屑距顶20px', now:'卡片顶格', gap:'med', sug:'顶部加padding+breadcrumb' },
const dashboardStats = [{ label:'总许可证', value:47, trend:12 },{ label:'活跃终端', value:183, trend:8 },{ label:'待处理 Callback', value:3, trend:-25 },{ label:'本月签发', value:15, trend:20 }]
const alerts = [{ text:'许可证 CT-2026-003 即将到期', time:'2h前', color:'#E6A23C' },{ text:'Callback sn:post_activate 待处理', time:'1h前', color:'#F56C6C' },{ text:'终端限制已达上限 (码头项目)', time:'3h前', color:'#F56C6C' },{ text:'新版本 SDK v0.2.0 已发布', time:'6h前', color:'#409EFF' }]
const names = ['广州创飞 · 码头检测合同','深圳教育局 · 学校合同','实验室设备授权 v2','内部测试许可','流动人口项目 042']
const refs = ['码头南沙二期','深圳南山校区','实验室 A 区','内部','项目 042']
const licenseData = [
{ id:'01JQNX...a1b2', tenant:'craftlabs-wharf-prod', product:'wharf-inspection-v2', type:'永久', devices:5, grace:7, status:'active', issued:'2026-05-10' },
{ id:'01JQNY...c3d4', tenant:'school-district-west', product:'school-edge-ai', type:'订阅', devices:20, grace:3, status:'active', issued:'2026-05-08' },
{ id:'01JQNZ...e5f6', tenant:'floating-project-042', product:'floating-license', type:'试用', devices:2, grace:1, status:'expired', issued:'2026-01-15' },
{ id:'01JQPA...g7h8', tenant:'craftlabs-demo', product:'demo-license', type:'订阅', devices:10, grace:7, status:'revoked', issued:'2026-03-20' },
{ id:'01JQPB...i9j0', tenant:'internal-test', product:'test-license', type:'永久', devices:1, grace:0, status:'active', issued:'2026-05-15' },
] ]
const licenseDetailRows = computed(() => { const dialogs = [
const l = selectedLicense.value || licenseData[0] { name:'签发许可证', trigger:'+ 签发许可证', width:'560px', specs:{'圆角':'8px','内边距':'20px','表单间距':'16px','按钮':'取消左 + 签发右'}, tokens:['560px','8px','16px-gap'] },
return [ { name:'新建/编辑', trigger:'+ 新建', width:'480px', specs:{'圆角':'8px','表单间距':'16px','按钮':'取消+保存'}, tokens:['480px','simple form'] },
{ label:'许可证 ID', value:l.id, code:true }, { name:'详情', trigger:'表格[详情]', width:'480px', specs:{'布局':'标签100px+值','行距':'12px'}, tokens:['480px','KV layout','100px'] },
{ label:'租户', value:l.tenant }, { name:'许可证详情', trigger:'许可证[详情]', width:'520px', specs:{'字体':'ID用monospace','区':'虚线框'}, tokens:['520px','monospace'] },
{ label:'产品', value:l.product }, { name:'确认删除/吊销', trigger:'删除/吊销', width:'420px', specs:{'危险色':'#FEF0F0底','按钮':'红色'}, tokens:['420px','red zone'] },
{ label:'授权类型', value:l.type },
{ label:'最大终端数', value:String(l.devices) },
{ label:'离线宽限期', value:l.grace+'天' },
{ label:'心跳间隔', value:'24小时' },
{ label:'状态', value:l.status==='active'?'活跃':l.status==='revoked'?'已吊销':'已过期' },
{ label:'签发时间', value:l.issued },
]
})
const callbackData = [
{ source:'BitAnswer', event:'sn:post_activate', sn:'SN-2026-A1B2C', status:'PENDING', time:'2026-05-18 14:30' },
{ source:'BitAnswer', event:'device:pre_activate', sn:'SN-2026-D3E4F', status:'PROCESSED', time:'2026-05-18 12:15' },
{ source:'SelfHosted', event:'license:heartbeat', sn:'01JQNX...a1b2', status:'PROCESSED', time:'2026-05-18 10:00' },
] ]
const keyData = [{ id:'kp_2026_q2', algo:'RS256', status:'active', time:'2026-04-01' },{ id:'kp_2026_q1', algo:'RS256', status:'rotated', time:'2026-01-05' }] const components = [
{ name:'主按钮', cat:'Button', ft:['#2C3E6B','4px','shadow'], ct:['#409EFF','4px'], ass:'改#2C3E6B+阴影' },
{ name:'表头', cat:'Table', ft:['#F2F5FC','#2C3E6B','12px bold'], ct:['#f5f7fa','#303133'], ass:'改淡蓝底+深蓝字' },
{ name:'状态标签', cat:'Tag', ft:['#E6F7EE/#1A7A3A'], ct:['el-tag默认'], ass:'✅ 微调色值' },
{ name:'通知Badge', cat:'Badge', ft:['#D54941','20x20','10px'], ct:['el-badge默认'], ass:'用el-badge自定义' },
{ name:'输入框', cat:'Input', ft:['border#E0E3E8','focus#2C3E6B'], ct:['focus#409EFF'], ass:'改focus色' },
{ name:'搜索框', cat:'Search', ft:['#F8F9FB','6px radius'], ct:['el-input'], ass:'加圆角淡灰底' },
{ name:'侧边菜单', cat:'Menu', ft:['#fff','#F2F5FC选中','3px右边框'], ct:['#001529 dark'], ass:'🔴改白色方案' },
{ name:'面包屑', cat:'Breadcrumb', ft:['14px','#909399/#2C3E6B'], ct:['无'], ass:'🔴新增' },
]
const statusLabels = { active:'活跃', revoked:'已吊销', expired:'已过期' } const types = [
const featureNames = { advanced_analytics:'高级分析', real_time_monitor:'实时监控', api_export:'API导出', multi_tenant:'多租户' } { lv:'页面标题', figma:'', now:'16px bold #303133', gap:'lo', sug:'保持,增22px Title级' },
{ lv:'正文', figma:'14px #000000', now:'14px #303133', gap:'lo', sug:'✅ 一致' },
{ lv:'辅助文字', figma:'13px #313131', now:'13px #909399', gap:'med', sug:'改为#606266' },
{ lv:'Badge/标签', figma:'12px #FFF on #D54941', now:'12px el-badge', gap:'lo', sug:'✅ Element Plus原生' },
{ lv:'表头', figma:'12px #2C3E6B bold', now:'el-table默认', gap:'med', sug:'改#2C3E6B+淡蓝底' },
{ lv:'代码/ID', figma:'—', now:'12px monospace', gap:'lo', sug:'✅ 许可证ID用monospace' },
]
function closeAllDialogs() { dialogType.value=''; selectedItem.value=''; selectedLicense.value=null } const summary = computed(() => ({ total:49, ok:20, warn:15, fail:14 }))
function openDialog(t, item) { opens(t, item) }
function openViewList() {} const specContent = `# CraftLabs 设计规范 v1.0
function tagStyle(s) { # 基于 Figma「安徽地质博物馆 v2.0」Token → delivery-platform-ui 映射
if (s==='revoked') return { background:'#fef0f0', color:'#F56C6C', border:'1px solid #fbc4c4' }
return { background:'#E6F7EE', color:'#1A7A3A', border:'1px solid #A8E6C1' } ## 1. 色彩
} --color-page-bg: #EAEFFA;
--color-card-bg: #FFFFFF;
--color-primary: #2C3E6B;
--color-primary-hover: #3D5A99;
--color-text-primary: #303133;
--color-text-secondary: #606266;
--color-border: #E8ECF1;
--color-th-bg: #F2F5FC;
--color-success: #1A7A3A;
--color-success-bg: #E6F7EE;
--color-danger: #F56C6C;
--color-danger-bg: #FEF0F0;
--color-warning: #E6A23C;
--color-badge: #D54941;
## 2. 布局
- Header: 60px固定, 含Logo+菜单+搜索+通知+用户
- Sidebar: 232px宽, 白底, 菜单42-88px不等高
- Breadcrumb: 46px, 内容区顶部
- Tree: 280px(许可证/客户页)
- Padding: 20px左右, 16px上下
- Card: 6px圆角, border 1px #E8ECF1
## 3. 弹框
- 圆角: 8px, 阴影: 0 8px 40px rgba(0,0,0,.15)
- Header: padding 16px/20px
- Body: padding 20px
- Footer: gap 10px, 按钮右对齐
- 宽度: 420(确认)/480(新建)/520(详情)/560(签发)
- 遮罩: rgba(0,0,0,.45)
- 动画: scale(.96→1) 200ms
## 4. 组件
- 主按钮: #2C3E6B, shadow 0 2px 6px rgba(44,62,107,.2)
- 输入框: border #E0E3E8, focus #2C3E6B, 4px
- 表头: #F2F5FC bg, #2C3E6B color, 12px bold
- Tag: 11px, padding 2px 8px, 3px radius
- Badge: 16px circle, #D54941, 10px white
## 5. 字体
- Title: 22px bold, H2: 20px bold, H3: 16px bold
- Body: 14px, Secondary: 13px #606266
- Code/ID: 12px monospace
## 6. 实施
:root {
--el-color-primary: #2C3E6B;
--el-bg-color-page: #EAEFFA;
--el-border-radius-base: 6px;
}`
</script> </script>
<style> <style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0} *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden} body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
input{font-family:inherit}select{font-family:inherit}button{font-family:inherit}
</style> </style>
<style scoped> <style scoped>
.card{background:#fff;border-radius:6px;border:1px solid #E8ECF1;box-shadow:0 1px 2px rgba(0,0,0,.03)} .gap-hi{display:inline-block;padding:2px 8px;border-radius:3px;font-size:11px;font-weight:600;background:#fef0f0;color:#F56C6C}
.card-hover{transition:transform .15s,box-shadow .15s} .gap-med{display:inline-block;padding:2px 8px;border-radius:3px;font-size:11px;font-weight:600;background:#FDF6EC;color:#E6A23C}
.card-hover:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.06)} .gap-lo{display:inline-block;padding:2px 8px;border-radius:3px;font-size:11px;font-weight:600;background:#E6F7EE;color:#1A7A3A}
.dt{width:100%;border-collapse:collapse;font-size:13px}
.dt th{padding:9px 12px;text-align:left;font-weight:600;font-size:12px;color:#2C3E6B;background:#F2F5FC;border-bottom:1px solid #E8ECF1}
.dt td{padding:9px 12px}
.lnk{color:#2C3E6B;cursor:pointer;margin-right:12px;font-size:13px}
.pc{cursor:pointer;color:#606266}
.tag{display:inline-block;padding:2px 8px;border-radius:3px;font-size:11px;font-weight:500}
.tag-active{background:#E6F7EE;color:#1A7A3A;border:1px solid #A8E6C1}
.tag-revoked,.tag-expired{background:#f4f4f5;color:#909399;border:1px solid #e9e9eb}
.tag-pending{background:#FDF6EC;color:#E6A23C;border:1px solid #F5DAB1}
.tag-processed{background:#E6F7EE;color:#1A7A3A;border:1px solid #A8E6C1}
.tag-inactive{background:#f4f4f5;color:#909399;border:1px solid #e9e9eb}
.btn-primary{border:none;background:#2C3E6B;color:#fff;border-radius:4px;cursor:pointer;font-weight:500}
.btn-cancel{border:1px solid #E0E3E8;background:#fff;color:#606266;padding:8px 20px;border-radius:4px;font-size:14px;cursor:pointer;font-weight:500}
/* Dialog styles */
.dlg{background:#fff;border-radius:8px;box-shadow:0 8px 40px rgba(0,0,0,.15);animation:dlgIn .2s ease-out}
.dlg-header{padding:16px 20px;border-bottom:1px solid #F2F5FC;display:flex;align-items:center;justify-content:space-between}
.dlg-close{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:4px;cursor:pointer;color:#909399;font-size:16px;transition:all .15s}
.dlg-close:hover{background:#F2F5FC;color:#303133}
.dlg-body{padding:20px}
.dlg-footer{padding:12px 20px 16px;display:flex;justify-content:flex-end;gap:10px;border-top:1px solid #F2F5FC}
.form-g{margin-bottom:16px}
.form-l{display:block;font-size:13px;color:#606266;margin-bottom:4px;font-weight:500}
.form-i{width:100%;border:1px solid #E0E3E8;border-radius:4px;padding:7px 12px;font-size:13px;color:#303133;outline:none;transition:border-color .15s}
.form-i:focus{border-color:#2C3E6B}
.detail-row{display:flex;padding:8px 0;border-bottom:1px solid #F2F5FC}
.detail-label{width:100px;text-align:right;padding-right:16px;font-size:13px;color:#909399;flex-shrink:0}
.detail-value{flex:1;font-size:13px;color:#303133}
@keyframes dlgIn{from{opacity:0;transform:scale(.96) translateY(-10px)}to{opacity:1;transform:scale(1) translateY(0)}}
</style> </style>