mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(web): add full dialog layout simulation (issue/create/detail/confirm/revoke) with Figma tokens
This commit is contained in:
@@ -1,322 +1,267 @@
|
||||
<template>
|
||||
<div style="height:100vh;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:13px;display:flex;flex-direction:column">
|
||||
<!-- ========== HEADER 60px ========== -->
|
||||
<!-- ====== HEADER 60px ====== -->
|
||||
<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="display:flex;align-items:center;gap:32px">
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<div style="width:28px;height:28px;background:linear-gradient(135deg,#2C3E6B,#3D5A99);border-radius:6px"></div>
|
||||
<span style="font-weight:700;font-size:16px;color:#2C3E6B;letter-spacing:.5px">CraftLabs</span>
|
||||
</div>
|
||||
<nav style="display:flex;gap:0">
|
||||
<div v-for="item in topNav" :key="item" :class="['nav-item', { active: item===activeTopNav }]" @click="activeTopNav=item" style="padding:0 16px;height:60px;line-height:60px;cursor:pointer;color:#606266;font-size:14px;position:relative;transition:all .2s"
|
||||
:style="item===activeTopNav?'color:#2C3E6B;font-weight:600':''">
|
||||
{{ item }}
|
||||
<div v-if="item===activeTopNav" style="position:absolute;bottom:0;left:16px;right:16px;height:2px;background:#2C3E6B;border-radius:1px 1px 0 0"></div>
|
||||
</div>
|
||||
</nav>
|
||||
<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 v-model="globalSearch" 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;font-size:14px;position:relative"
|
||||
@mouseenter="hoverIcon=icon" @mouseleave="hoverIcon=''" :style="{background:hoverIcon===icon?'#F2F5FC':''}">
|
||||
{{ icon }}
|
||||
<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" @mouseenter="hoverUser=true" @mouseleave="hoverUser=false" :style="{background:hoverUser?'#F2F5FC':''}">
|
||||
<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 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>
|
||||
|
||||
<!-- ========== BODY ========== -->
|
||||
<div style="flex:1;display:flex;overflow:hidden;background:#EAEFFA">
|
||||
<!-- SIDEBAR 232px -->
|
||||
<!-- ====== 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 style="padding:4px 20px 12px;font-size:12px;color:#C0C4CC;text-transform:uppercase;letter-spacing:1px;font-weight:600">授权运营</div>
|
||||
<div v-for="group in sidebarGroups" :key="group.label" style="margin-bottom:4px">
|
||||
<div style="padding:4px 20px;font-size:11px;color:#C0C4CC;text-transform:uppercase;font-weight:600" v-if="group.label">{{ group.label }}</div>
|
||||
<div v-for="item in group.items" :key="item.key" :class="['menu-item', { active: item.key===activeModule }]" @click="activeModule=item.key"
|
||||
:style="{display:'flex',alignItems:'center',gap:10,padding:'8px 20px',cursor:'pointer',fontSize:'14px',transition:'all .15s',
|
||||
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="font-size:14px;width:20px;text-align:center">{{ item.icon }}</span>
|
||||
<span>{{ item.name }}</span>
|
||||
<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 Platform v0.1.0</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>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div style="flex:1;overflow:hidden;display:flex;flex-direction:column">
|
||||
<!-- Breadcrumb 46px -->
|
||||
<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">{{ activePageName }}</span>
|
||||
<span style="color:#909399">授权运营</span><span style="color:#C0C4CC">›</span><span style="color:#2C3E6B;font-weight:600">{{ pageNames[activeModule] }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Main area with optional tree -->
|
||||
<div style="flex:1;display:flex;overflow:hidden">
|
||||
<!-- Tree Panel 280px (shown for modules with tree) -->
|
||||
<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 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 v-for="t in treeNodes" :key="t.label" style="padding:5px 16px 5px 20px;cursor:pointer;font-size:13px;color:#606266;display:flex;align-items:center;gap:6px;transition:all .1s"
|
||||
:style="t.active?{color:'#2C3E6B',fontWeight:600,background:'#F2F5FC',borderRight:'3px solid #2C3E6B'}:{}"
|
||||
@click="treeNodes.forEach(n=>n.active=false);t.active=true"
|
||||
@mouseenter="t.hover=true" @mouseleave="t.hover=false"
|
||||
:class="{'tree-hover': t.hover && !t.active}">
|
||||
<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>
|
||||
|
||||
<!-- Main content panel -->
|
||||
<div style="flex:1;overflow-y:auto;padding:16px 20px">
|
||||
<!-- Module: Dashboard -->
|
||||
<!-- ====== 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="stat-card" style="background:#fff;border-radius:6px;padding:18px;border:1px solid #E8ECF1;box-shadow:0 1px 2px rgba(0,0,0,.03)">
|
||||
<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 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="background:#fff;border-radius:6px;padding:16px;border:1px solid #E8ECF1">
|
||||
<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="background:#fff;border-radius:6px;padding:16px;border:1px solid #E8ECF1">
|
||||
<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">{{ a.text }}</span>
|
||||
<span style="font-size:11px;color:#C0C4CC">{{ a.time }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Module: List + Table (used for most CRUD pages) -->
|
||||
<template v-else-if="activeModule==='customers'||activeModule==='contracts'||activeModule==='deliveries'||activeModule==='license-sns'">
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;margin-bottom:12px">
|
||||
<div style="padding:12px 16px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px">
|
||||
<!-- ====== 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="'搜索'+activePageName+'...'" style="border:none;outline:none;flex:1;font-size:13px;background:transparent">
|
||||
<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>
|
||||
<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>
|
||||
</select>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:6px 14px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500">查询</button>
|
||||
<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>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:7px 16px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500;box-shadow:0 2px 6px rgba(44,62,107,.2)">+ 新建{{ activePageName }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Table -->
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow:hidden">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600"><th style="padding:10px 14px;text-align:left;font-size:12px">ID/编号</th><th style="padding:10px 14px;text-align:left;font-size:12px">名称/标题</th><th style="padding:10px 14px;text-align:left;font-size:12px">关联</th><th style="padding:10px 14px;text-align:left;font-size:12px">状态</th><th style="padding:10px 14px;text-align:left;font-size:12px">更新时间</th><th style="padding:10px 14px;text-align:left;font-size:12px;width:120px">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="i in 5" :key="i" style="border-bottom:1px solid #F2F5FC" @mouseenter="rowHover=i" @mouseleave="rowHover=0" :style="{background:rowHover===i?'#FAFBFC':''}">
|
||||
<td style="padding:10px 14px;font-family:monospace;color:#303133;font-size:12px">{{ 'CT-2026-0'+((i-1)*2+1).toString().padStart(3,'0') }}</td>
|
||||
<td style="padding:10px 14px;color:#303133;font-weight:500">{{ mockNames[i-1] }}</td>
|
||||
<td style="padding:10px 14px;color:#606266">{{ mockRefs[i-1] }}</td>
|
||||
<td style="padding:10px 14px"><span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'11px',fontWeight:500,background:i===3?'#fef0f0':'#E6F7EE',color:i===3?'#F56C6C':'#1A7A3A',border:'1px solid '+(i===3?'#fbc4c4':'#A8E6C1')}">{{ i===3?'已吊销':'活跃' }}</span></td>
|
||||
<td style="padding:10px 14px;color:#909399;font-size:12px">2026-05-{{ (15-i*2).toString().padStart(2,'0') }}</td>
|
||||
<td style="padding:10px 14px"><span style="color:#2C3E6B;cursor:pointer;margin-right:12px">详情</span><span style="color:#F56C6C;cursor:pointer">吊销</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<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;align-items:center;gap:8px;font-size:12px;color:#909399;border-top:1px solid #F2F5FC">
|
||||
<span>共 128 条</span><span style="cursor:pointer;color:#606266;font-weight:600">‹</span><span style="color:#2C3E6B;font-weight:600">1</span><span style="cursor:pointer;color:#606266">2</span><span>…</span><span style="cursor:pointer;color:#606266">13</span><span style="cursor:pointer;color:#606266;font-weight:600">›</span>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Module: License Management (NEW - selfhosted key page) -->
|
||||
<template v-else-if="activeModule==='licenses'">
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;margin-bottom:12px">
|
||||
<div style="padding:12px 16px;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px">
|
||||
<!-- ====== 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 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>
|
||||
<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><option>活跃</option><option>已吊销</option><option>已过期</option></select>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:6px 14px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500">查询</button>
|
||||
<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>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:7px 16px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500;box-shadow:0 2px 6px rgba(44,62,107,.2)">+ 签发许可证</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow:hidden">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600">
|
||||
<th style="padding:10px 14px;text-align:left;font-size:12px">许可证 ID</th><th style="padding:10px 14px;text-align:left;font-size:12px">租户</th><th style="padding:10px 14px;text-align:left;font-size:12px">产品</th><th style="padding:10px 14px;text-align:left;font-size:12px">类型</th><th style="padding:10px 14px;text-align:left;font-size:12px">终端</th><th style="padding:10px 14px;text-align:left;font-size:12px">宽限</th><th style="padding:10px 14px;text-align:left;font-size:12px">状态</th><th style="padding:10px 14px;text-align:left;font-size:12px">签发时间</th><th style="padding:10px 14px;text-align:left;font-size:12px;width:120px">操作</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="l in licenseData" :key="l.id" style="border-bottom:1px solid #F2F5FC">
|
||||
<td style="padding:10px 14px;font-family:monospace;color:#303133;font-size:12px">{{ l.id }}</td>
|
||||
<td style="padding:10px 14px;color:#303133">{{ l.tenant }}</td>
|
||||
<td style="padding:10px 14px;color:#606266">{{ l.product }}</td>
|
||||
<td style="padding:10px 14px">{{ l.type }}</td>
|
||||
<td style="padding:10px 14px;color:#606266;text-align:center">{{ l.devices }}</td>
|
||||
<td style="padding:10px 14px;color:#606266;text-align:center">{{ l.grace }}天</td>
|
||||
<td style="padding:10px 14px"><span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'11px',fontWeight:500,background:l.status==='active'?'#E6F7EE':l.status==='revoked'?'#fef0f0':'#f4f4f5',color:l.status==='active'?'#1A7A3A':l.status==='revoked'?'#F56C6C':'#909399',border:'1px solid '+(l.status==='active'?'#A8E6C1':l.status==='revoked'?'#fbc4c4':'#e9e9eb')}">{{ {active:'活跃',revoked:'已吊销',expired:'已过期'}[l.status] }}</span></td>
|
||||
<td style="padding:10px 14px;color:#909399;font-size:12px">{{ l.issued }}</td>
|
||||
<td style="padding:10px 14px"><span style="color:#2C3E6B;cursor:pointer;margin-right:12px">详情</span><span v-if="l.status==='active'" style="color:#F56C6C;cursor:pointer">吊销</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<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;align-items:center;gap:8px;font-size:12px;color:#909399;border-top:1px solid #F2F5FC">
|
||||
<span>共 47 条</span><span style="cursor:pointer;color:#606266;font-weight:600">‹</span><span style="color:#2C3E6B;font-weight:600">1</span><span style="cursor:pointer;color:#606266">2</span><span>…</span><span style="cursor:pointer;color:#606266">5</span><span style="cursor:pointer;color:#606266;font-weight:600">›</span>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Module: Callback Inbox -->
|
||||
<template v-else-if="activeModule==='callbacks'">
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;margin-bottom:12px">
|
||||
<div style="padding:12px 16px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<select v-for="f in callbackFilters" :key="f.label" style="border:1px solid #E0E3E8;border-radius:4px;padding:5px 10px;font-size:13px;color:#606266;background:#fff"><option>{{ f.label }}</option></select>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:6px 14px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500">查询</button>
|
||||
<!-- ====== 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>
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow:hidden">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600"><th style="padding:10px 14px;text-align:left;font-size:12px">来源</th><th style="padding:10px 14px;text-align:left;font-size:12px">事件类型</th><th style="padding:10px 14px;text-align:left;font-size:12px">SN</th><th style="padding:10px 14px;text-align:left;font-size:12px">状态</th><th style="padding:10px 14px;text-align:left;font-size:12px">收件时间</th><th style="padding:10px 14px;text-align:left;font-size:12px;width:120px">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="c in callbackData" :key="c.id" style="border-bottom:1px solid #F2F5FC">
|
||||
<td style="padding:10px 14px;color:#303133">{{ c.source }}</td><td style="padding:10px 14px;font-family:monospace;font-size:12px;color:#606266">{{ c.event }}</td>
|
||||
<td style="padding:10px 14px;font-family:monospace;font-size:12px;color:#303133">{{ c.sn }}</td>
|
||||
<td style="padding:10px 14px"><span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'11px',fontWeight:500,background:c.status==='PENDING'?'#FDF6EC':c.status==='PROCESSED'?'#E6F7EE':'#f4f4f5',color:c.status==='PENDING'?'#E6A23C':c.status==='PROCESSED'?'#1A7A3A':'#909399'}">{{ {PENDING:'待处理',PROCESSED:'已处理',IGNORED:'忽略'}[c.status] }}</span></td>
|
||||
<td style="padding:10px 14px;color:#909399;font-size:12px">{{ c.time }}</td>
|
||||
<td style="padding:10px 14px"><span style="color:#2C3E6B;cursor:pointer">详情</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<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>
|
||||
|
||||
<!-- Module: Integration -->
|
||||
<template v-else-if="activeModule==='integration-envs'||activeModule==='integration-plines'">
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow:hidden">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600"><th style="padding:10px 14px;text-align:left;font-size:12px">名称</th><th style="padding:10px 14px;text-align:left;font-size:12px">标识</th><th style="padding:10px 14px;text-align:left;font-size:12px">说明</th><th style="padding:10px 14px;text-align:left;font-size:12px">状态</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="i in 4" :key="i" style="border-bottom:1px solid #F2F5FC"><td style="padding:10px 14px;color:#303133;font-weight:500">{{ ['生产环境','预发布','测试环境','开发环境'][i-1] }}</td><td style="padding:10px 14px;font-family:monospace;font-size:12px;color:#606266">{{ ['prod','staging','test','dev'][i-1] }}</td><td style="padding:10px 14px;color:#909399">—</td><td style="padding:10px 14px"><span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'11px',fontWeight:500,background:i===1?'#E6F7EE':'#f4f4f5',color:i===1?'#1A7A3A':'#909399'}">{{ i===1?'启用':'维护中' }}</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- ====== 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>
|
||||
|
||||
<!-- Module: Settings -->
|
||||
<template v-else-if="activeModule==='settings-keys'">
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;margin-bottom:12px;display:flex;align-items:center;justify-content:space-between;padding:12px 16px">
|
||||
<span style="font-weight:600;color:#303133">RSA 密钥对管理</span>
|
||||
<button style="border:none;background:#2C3E6B;color:#fff;padding:7px 16px;border-radius:4px;font-size:13px;cursor:pointer;font-weight:500">+ 生成密钥对</button>
|
||||
</div>
|
||||
<div class="card" style="background:#fff;border-radius:6px;border:1px solid #E8ECF1;overflow:hidden">
|
||||
<table style="width:100%;border-collapse:collapse;font-size:13px">
|
||||
<thead><tr style="background:#F2F5FC;color:#2C3E6B;font-weight:600"><th style="padding:10px 14px;text-align:left;font-size:12px">Key ID</th><th style="padding:10px 14px;text-align:left;font-size:12px">算法</th><th style="padding:10px 14px;text-align:left;font-size:12px">状态</th><th style="padding:10px 14px;text-align:left;font-size:12px">创建时间</th><th style="padding:10px 14px;text-align:left;font-size:12px;width:120px">操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="k in keyData" :key="k.id" style="border-bottom:1px solid #F2F5FC"><td style="padding:10px 14px;font-family:monospace;font-size:12px;color:#303133">{{ k.id }}</td><td style="padding:10px 14px;color:#606266">{{ k.algo }}</td><td style="padding:10px 14px"><span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'11px',fontWeight:500,background:k.status==='active'?'#E6F7EE':'#f4f4f5',color:k.status==='active'?'#1A7A3A':'#909399'}">{{ k.status==='active'?'活跃':'已轮换' }}</span></td><td style="padding:10px 14px;color:#909399;font-size:12px">{{ k.time }}</td><td style="padding:10px 14px"><span style="color:#2C3E6B;cursor:pointer;margin-right:12px">查看公钥</span><span v-if="k.status==='active'" style="color:#E6A23C;cursor:pointer">轮换</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- ====== 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>
|
||||
|
||||
<!-- ====== DIALOG OVERLAY ====== -->
|
||||
<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()">
|
||||
<!-- ISSUE LICENSE DIALOG 520px -->
|
||||
<div v-if="dialogType==='issue'" class="dlg" style="width:560px;max-height:85vh;overflow-y:auto">
|
||||
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">签发许可证</span><span class="dlg-close" @click="closeAllDialogs()">✕</span></div>
|
||||
<div class="dlg-body">
|
||||
<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 class="form-g"><label class="form-l">产品名称</label><input class="form-i" placeholder="如 wharf-inspection-v2"></div>
|
||||
<div class="form-g" style="display:flex;gap:12px">
|
||||
<div style="flex:1"><label class="form-l" style="display:block;margin-bottom:4px">授权类型</label>
|
||||
<select class="form-i" style="width:100%"><option>订阅 (subscription)</option><option>永久 (perpetual)</option><option>试用 (trial)</option></select>
|
||||
</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>
|
||||
<div class="form-g" style="display:flex;gap:12px">
|
||||
<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 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 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 style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px">
|
||||
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div>
|
||||
<div style="font-size:12px;color:#606266;line-height:1.8">
|
||||
<div>• 弹窗宽度: <b>560px</b>(表单类弹窗标准宽度)</div>
|
||||
<div>• 表单项间距: <b>16px</b></div>
|
||||
<div>• 标签宽度: <b>自适应</b>(左侧标签 + 右侧控件)</div>
|
||||
<div>• Footer: <b>右对齐</b>,取消在左/确认在右</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>
|
||||
|
||||
<!-- CREATE/EDIT DIALOG 480px -->
|
||||
<div v-if="dialogType==='create'" class="dlg" style="width:480px">
|
||||
<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>
|
||||
<div class="dlg-body">
|
||||
<div class="form-g"><label class="form-l">名称 <span style="color:#F56C6C">*</span></label><input class="form-i" placeholder="请输入名称"></div>
|
||||
<div class="form-g"><label class="form-l">备注</label><input class="form-i" placeholder="选填"></div>
|
||||
<div style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px">
|
||||
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</div>
|
||||
<div style="font-size:12px;color:#606266;line-height:1.8"><div>• 弹窗宽度: <b>480px</b>(简单表单)</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>
|
||||
|
||||
<!-- DETAIL DIALOG 480px -->
|
||||
<div v-if="dialogType==='detail'" class="dlg" style="width:480px">
|
||||
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">详情 - {{ selectedItem }}</span><span class="dlg-close" @click="closeAllDialogs()">✕</span></div>
|
||||
<div class="dlg-body">
|
||||
<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 style="background:#F8F9FB;border:1px solid #E8ECF1;border-radius:6px;padding:12px;margin-top:12px">
|
||||
<div style="font-size:12px;color:#909399;margin-bottom:6px">📐 布局说明</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>
|
||||
</div>
|
||||
<div class="dlg-footer"><button class="btn-primary" style="padding:8px 20px;font-size:14px" @click="closeAllDialogs()">关闭</button></div>
|
||||
</div>
|
||||
|
||||
<!-- DETAIL-LICENSE DIALOG 520px -->
|
||||
<div v-if="dialogType==='detail-license'" class="dlg" style="width:520px">
|
||||
<div class="dlg-header"><span style="font-size:16px;font-weight:600;color:#303133">许可证详情</span><span class="dlg-close" @click="closeAllDialogs()">✕</span></div>
|
||||
<div class="dlg-body">
|
||||
<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>
|
||||
<div style="background:#F2F5FC;border:1px dashed #D6DFF0;border-radius:6px;padding:12px;margin-top:12px">
|
||||
<div style="font-size:12px;color:#2C3E6B;font-weight:600;margin-bottom:6px">📐 许可证详情布局</div>
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<!-- CONFIRM / REVOKE DIALOG 420px -->
|
||||
<div v-if="dialogType==='confirm'" class="dlg" style="width:420px;text-align:center">
|
||||
<div class="dlg-body" style="padding:32px 24px 20px">
|
||||
<div style="font-size:48px;margin-bottom:16px">⚠️</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>
|
||||
|
||||
<!-- REVOKE LICENSE DIALOG -->
|
||||
<div v-if="dialogType==='revoke'" class="dlg" style="width:420px;text-align:center">
|
||||
<div class="dlg-body" style="padding:32px 24px 20px">
|
||||
<div style="font-size:48px;margin-bottom:16px">🛑</div>
|
||||
<div style="font-size:16px;font-weight:600;color:#303133;margin-bottom:8px">确认吊销许可证</div>
|
||||
<div style="font-size:13px;color:#909399">确定要吊销许可证<br><b style="color:#303133;font-family:monospace">「{{ selectedLicense?.id }}」</b><br>吗?吊销后该许可证立即失效。</div>
|
||||
<div style="background:#FEF0F0;border:1px solid #FBC4C4;border-radius:6px;padding:12px;margin-top:16px;text-align:left">
|
||||
<div style="font-size:12px;color:#F56C6C;font-weight:600;margin-bottom:6px">📐 危险操作布局</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 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>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
|
||||
const activeModule = ref('dashboard')
|
||||
const activeModule = ref('licenses')
|
||||
const activeTopNav = ref('授权平台')
|
||||
const globalSearch = ref('')
|
||||
const hoverIcon = ref('')
|
||||
const hoverUser = ref(false)
|
||||
const rowHover = ref(0)
|
||||
const dialogType = ref('')
|
||||
const selectedItem = ref('')
|
||||
const selectedLicense = ref(null)
|
||||
|
||||
const topNav = ['授权平台', '运营分析', '系统设置']
|
||||
|
||||
const sidebarGroups = [
|
||||
{ label:'', items:[
|
||||
{ key:'dashboard', icon:'📊', name:'工作台概览' },
|
||||
]},
|
||||
{ label:'业务管理', items:[
|
||||
{ key:'customers', icon:'👥', name:'客户管理' },
|
||||
{ key:'contracts', icon:'📋', name:'合同管理' },
|
||||
{ key:'deliveries', icon:'📦', name:'交付管理' },
|
||||
{ key:'license-sns', icon:'🔑', name:'许可 SN' },
|
||||
]},
|
||||
{ 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 modulePageNames = {
|
||||
dashboard:'工作台概览', customers:'客户管理', contracts:'合同管理',
|
||||
deliveries:'交付管理', 'license-sns':'许可 SN', licenses:'许可证管理',
|
||||
callbacks:'Callback 收件箱', 'integration-envs':'集成环境',
|
||||
'integration-plines':'产品线', 'settings-keys':'密钥管理'
|
||||
}
|
||||
|
||||
const activePageName = computed(() => modulePageNames[activeModule.value] || '—')
|
||||
const pageNames = { dashboard:'工作台概览', customers:'客户管理', contracts:'合同管理', deliveries:'交付管理', 'license-sns':'许可 SN', licenses:'许可证管理', callbacks:'Callback 收件箱', 'integration-envs':'集成环境', 'integration-plines':'产品线', 'settings-keys':'密钥管理' }
|
||||
const showListPage = computed(() => ['customers','contracts','deliveries','license-sns'].includes(activeModule.value))
|
||||
const showTree = computed(() => ['licenses','customers','contracts'].includes(activeModule.value))
|
||||
|
||||
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 },
|
||||
])
|
||||
|
||||
const dashboardStats = [
|
||||
{ label:'总许可证', value:47, trend:12 },
|
||||
{ label:'活跃终端', value:183, trend:8 },
|
||||
{ label:'待处理 Callback', value:3, trend:-25 },
|
||||
{ label:'本月签发', value:15, trend:20 },
|
||||
const sidebarGroups = [
|
||||
{ label:'', items:[{ key:'dashboard', icon:'📊', name:'工作台概览' }] },
|
||||
{ label:'业务管理', items:[{ key:'customers', icon:'👥', name:'客户管理' },{ key:'contracts', icon:'📋', name:'合同管理' },{ key:'deliveries', icon:'📦', name:'交付管理' },{ key:'license-sns', icon:'🔑', name:'许可 SN' }] },
|
||||
{ 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 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 opens = (t, item) => {
|
||||
if (activeModule.value !== 'licenses' && t === 'issue') return
|
||||
dialogType.value = t
|
||||
selectedItem.value = typeof item === 'string' ? item : ''
|
||||
if (t === 'revoke' || t === 'detail-license') selectedLicense.value = item
|
||||
}
|
||||
|
||||
const mockNames = ['广州创飞 · 码头检测合同', '深圳教育局 · 学校合同', '实验室设备授权 v2', '内部测试许可', '流动人口项目 042']
|
||||
const mockRefs = ['码头南沙二期', '深圳南山校区', '实验室 A 区', '内部', '项目 042']
|
||||
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 }])
|
||||
|
||||
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' },
|
||||
@@ -326,34 +271,76 @@ const licenseData = [
|
||||
{ id:'01JQPB...i9j0', tenant:'internal-test', product:'test-license', type:'永久', devices:1, grace:0, status:'active', issued:'2026-05-15' },
|
||||
]
|
||||
|
||||
const licenseDetailRows = computed(() => {
|
||||
const l = selectedLicense.value || licenseData[0]
|
||||
return [
|
||||
{ label:'许可证 ID', value:l.id, code:true },
|
||||
{ label:'租户', value:l.tenant },
|
||||
{ label:'产品', value:l.product },
|
||||
{ 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' },
|
||||
{ source:'BitAnswer', event:'sn:post_activate', sn:'SN-2026-G5H6I', status:'IGNORED', time:'2026-05-17 16:45' },
|
||||
]
|
||||
|
||||
const callbackFilters = [
|
||||
{ label:'全部状态' }, { label:'全部事件类型' }, { label:'全部来源' }
|
||||
]
|
||||
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 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 statusLabels = { active:'活跃', revoked:'已吊销', expired:'已过期' }
|
||||
const featureNames = { advanced_analytics:'高级分析', real_time_monitor:'实时监控', api_export:'API导出', multi_tenant:'多租户' }
|
||||
|
||||
function closeAllDialogs() { dialogType.value=''; selectedItem.value=''; selectedLicense.value=null }
|
||||
function openDialog(t, item) { opens(t, item) }
|
||||
function openViewList() {}
|
||||
function tagStyle(s) {
|
||||
if (s==='revoked') return { background:'#fef0f0', color:'#F56C6C', border:'1px solid #fbc4c4' }
|
||||
return { background:'#E6F7EE', color:'#1A7A3A', border:'1px solid #A8E6C1' }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden}
|
||||
input{font-family:inherit} select{font-family:inherit} button{font-family:inherit}
|
||||
input{font-family:inherit}select{font-family:inherit}button{font-family:inherit}
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
.nav-item:hover{color:#2C3E6B}
|
||||
.menu-item:hover{background:#F2F5FC}
|
||||
.tree-hover{background:#F2F5FC}
|
||||
.stat-card{transition:transform .15s,box-shadow .15s}
|
||||
.stat-card:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
|
||||
input::placeholder{color:#C0C4CC}
|
||||
.card{background:#fff;border-radius:6px;border:1px solid #E8ECF1;box-shadow:0 1px 2px rgba(0,0,0,.03)}
|
||||
.card-hover{transition:transform .15s,box-shadow .15s}
|
||||
.card-hover:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.06)}
|
||||
.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>
|
||||
|
||||
Reference in New Issue
Block a user