feat(web): add Figma vs Element Plus license management theme comparison page

This commit is contained in:
2026-05-18 23:14:39 +08:00
parent be772db94b
commit 9cb2fda66a
5 changed files with 194 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
{
"name": "安徽地质博物馆v2.0",
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
"nodeId": "0-38499",
"url": "https://www.figma.com/design/TdU1qb5xVYDLOssDOQxQqv/",
"fetchedAt": "2026-05-18T00:00:00Z"
}
+56
View File
@@ -0,0 +1,56 @@
{
"file": {
"name": "安徽地质博物馆v2.0",
"fileId": "TdU1qb5xVYDLOssDOQxQqv",
"lastModified": "2026-05-18T14:45:48Z",
"frame": "数字资源系统-资源管理",
"frameSize": "1920x1080"
},
"colors": {
"pageBackground": "rgba(234,239,250,1.0)",
"cardBackground": "rgba(255,255,255,1.0)",
"textPrimary": "rgba(0,0,0,1.0)",
"textSecondary": "rgba(49,49,49,1.0)",
"textOnPrimary": "rgba(255,255,255,1.0)",
"badgeRed": "rgba(213,73,65,1.0)",
"decorativeBlue": "rgba(207,209,255,1.0)",
"decorativeTeal": "rgba(217,248,255,1.0)"
},
"typography": {
"body": { "fontSize": "14px", "color": "rgba(0,0,0,1.0)" },
"badge": { "fontSize": "12px", "color": "rgba(255,255,255,1.0)" },
"placeholder": { "fontSize": "14px", "color": "rgba(49,49,49,1.0)" }
},
"layout": {
"frameWidth": 1920,
"frameHeight": 1080,
"headerHeight": 60,
"sidebarWidth": 232,
"contentWidth": 1688,
"contentPaddingX": 20,
"treePanelWidth": 280,
"mainPanelWidth": 1368,
"breadcrumbHeight": 46
},
"components": {
"header": "headerMenu 顶部菜单导航",
"sidebar": "Menu - 侧边菜单",
"breadcrumb": "Breadcrumb 面包屑 (数字资源 > 资源管理)",
"tree": "Tree 树结构 - 资源分类树",
"search": "search 搜索框",
"menuItems": [
"item/menuLogo/baseLogo-light",
"item/normalMenu/1st-light (x11 菜单项)",
"Button"
],
"headerItems": [
"icon-search-w/text - 资源快速搜索",
"icon-internet",
"icon-view-module",
"icon-mail + Badge (红点通知 2)",
"logo-github",
"icon-user w/ TD Admin",
"icon-setting"
]
}
}
File diff suppressed because one or more lines are too long
@@ -104,9 +104,16 @@ const routes = [
component: () => import("../views/ContractsView.vue"), component: () => import("../views/ContractsView.vue"),
meta: { roles: ["SYS_ADMIN", "DEVELOPER"], title: "合同管理" }, meta: { roles: ["SYS_ADMIN", "DEVELOPER"], title: "合同管理" },
}, },
{
path: "license-compare",
name: "license-compare",
component: () => import("../views/LicenseCompareView.vue"),
meta: { roles: ["SYS_ADMIN", "DEVELOPER"] },
},
], ],
}, },
{ path: "/403", name: "forbidden", component: () => import("../views/ForbiddenView.vue") }, { path: "/403", name: "forbidden", component: () => import("../views/ForbiddenView.vue") },
{ path: "/license-compare-public", name: "license-compare-public", component: () => import("../views/LicenseCompareView.vue") },
{ path: "/:pathMatch(.*)*", name: "notfound", component: () => import("../views/NotFoundView.vue") }, { path: "/:pathMatch(.*)*", name: "notfound", component: () => import("../views/NotFoundView.vue") },
]; ];
@@ -0,0 +1,123 @@
<template>
<div style="display:flex; height:100vh; overflow:hidden; min-width:1400px">
<div v-for="theme in themes" :key="theme.name" :class="['half', theme.cssClass]" :style="{background:theme.bg}">
<div :style="{position:'sticky',top:0,zIndex:10,padding:'6px 16px',fontSize:'13px',fontWeight:600,textAlign:'center',color:'#fff',background:theme.brand}">{{ theme.label }}</div>
<div style="padding:14px 20px; display:flex; flex-direction:column; gap:12px">
<!-- 统计卡片行 -->
<div style="display:flex; gap:12px">
<div v-for="s in stats" :key="s.label" :style="{flex:1,background:'#fff',borderRadius:theme.radius+'px',padding:'16px',textAlign:'center',boxShadow:'0 1px 3px rgba(0,0,0,.06)',border:theme.border}">
<div style="font-size:28px; font-weight:700; margin-bottom:4px">{{ s.value }}</div>
<div style="font-size:13px; color:#909399">{{ s.label }}</div>
</div>
</div>
<!-- 搜索 + 操作栏 -->
<div :style="{background:'#fff',borderRadius:theme.radius+'px',padding:'14px 16px',boxShadow:'0 1px 3px rgba(0,0,0,.06)',border:theme.border}">
<div style="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 #dcdfe6',borderRadius:'4px',padding:'6px 12px',width:'200px',display:'flex',alignItems:'center',gap:'6px'}">
<span style="color:#c0c4cc; font-size:14px">🔍</span>
<input v-model="keyword" placeholder="搜索许可证ID" style="border:none;outline:none;flex:1;font-size:14px;color:#606266" />
</div>
<select v-model="filterStatus" :style="{border:'1px solid #dcdfe6',borderRadius:'4px',padding:'6px 10px',fontSize:'14px',color:'#606266',background:'#fff'}">
<option value="">全部状态</option>
<option value="active">活跃</option>
<option value="revoked">已吊销</option>
<option value="expired">已过期</option>
</select>
<button :style="{background:theme.brand,color:'#fff',border:'none',borderRadius:'4px',padding:'7px 16px',fontSize:'14px',cursor:'pointer',fontWeight:500}" @click="search">{{ theme.btnText }}</button>
</div>
<button :style="{background:theme.brand,color:'#fff',border:'none',borderRadius:'4px',padding:'7px 16px',fontSize:'14px',cursor:'pointer'}" @click="create"> 签发许可证</button>
</div>
</div>
<!-- 表格 -->
<div :style="{background:'#fff',borderRadius:theme.radius+'px',padding:'0',overflow:'hidden',boxShadow:'0 1px 3px rgba(0,0,0,.06)',border:theme.border}">
<table style="width:100%; border-collapse:collapse; font-size:14px">
<thead>
<tr :style="{background:theme.thBg, color:theme.thColor}">
<th style="padding:10px 12px; text-align:left; font-weight:600; min-width:150px">许可证 ID</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; min-width:100px">租户</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; width:80px">类型</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; width:70px">终端</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; width:80px">宽限</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; width:80px">状态</th>
<th style="padding:10px 12px; text-align:left; font-weight:600; width:120px">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="r in list" :key="r.licenseId" style="border-bottom:1px solid #ebeef5">
<td style="padding:10px 12px; color:#303133">{{ r.licenseId }}</td>
<td style="padding:10px 12px; color:#606266">{{ r.tenantId }}</td>
<td style="padding:10px 12px">{{ typeLabel(r.grantType) }}</td>
<td style="padding:10px 12px; color:#606266">{{ r.maxDevices }}</td>
<td style="padding:10px 12px; color:#606266">{{ r.offlineGraceDays }}</td>
<td style="padding:10px 12px">
<span :style="{display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'12px',fontWeight:500,...statusStyle(r.status,theme)}">{{ r.status }}</span>
</td>
<td style="padding:10px 12px">
<span :style="{color:theme.brand,cursor:'pointer',marginRight:12}" @click="detail(r)">详情</span>
<span v-if="r.status==='active'" style="color:#f56c6c;cursor:pointer" @click="revoke(r)">吊销</span>
</td>
</tr>
</tbody>
</table>
<div style="padding:10px 16px; display:flex; justify-content:flex-end; align-items:center; gap:8px; font-size:13px; color:#909399; border-top:1px solid #ebeef5">
<span> {{ total }} </span>
<span style="cursor:pointer" @click="page = Math.max(1, page-1)"></span>
<span>{{ page }}</span>
<span style="cursor:pointer" @click="page = Math.min(Math.ceil(total/10), page+1)"></span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, reactive } from 'vue'
const themes = [
{ name:'figma', cssClass:'figma', label:'Figama · 安徽地质博物馆 色彩', bg:'#EAEFFA', brand:'#2C3E6B', radius:6, border:'1px solid #D6DFF0', thBg:'#F2F5FC', thColor:'#2C3E6B', btnText:'查询' },
{ name:'elplus', cssClass:'elplus', label:'Element Plus 默认主题', bg:'#f0f2f5', brand:'#409EFF', radius:4, border:'none', thBg:'#f5f7fa', thColor:'#303133', btnText:'查询' }
]
const MOCK = [
{ licenseId:'01JQNX...a1b2', tenantId:'craftlabs-wharf-prod', grantType:'perpetual', maxDevices:5, offlineGraceDays:7, status:'active' },
{ licenseId:'01JQNY...c3d4', tenantId:'school-district-west', grantType:'subscription', maxDevices:20, offlineGraceDays:3, status:'active' },
{ licenseId:'01JQNZ...e5f6', tenantId:'floating-project-042', grantType:'trial', maxDevices:2, offlineGraceDays:1, status:'expired' },
{ licenseId:'01JQPA...g7h8', tenantId:'craftlabs-demo', grantType:'subscription', maxDevices:10, offlineGraceDays:7, status:'revoked' },
{ licenseId:'01JQPB...i9j0', tenantId:'internal-test', grantType:'perpetual', maxDevices:1, offlineGraceDays:0, status:'active' },
]
const rows = ref([...MOCK])
const total = ref(5), page = ref(1)
const keyword = ref(''), filterStatus = ref('')
const stats = reactive([{label:'总许可证',value:5},{label:'活跃',value:3},{label:'已吊销',value:1},{label:'已过期',value:1}])
const list = computed(() => {
let r = [...rows.value]
if (keyword.value) r = r.filter(x=>x.licenseId.includes(keyword.value)||x.tenantId.includes(keyword.value))
if (filterStatus.value) r = r.filter(x=>x.status===filterStatus.value)
return r
})
function search() {}
function create() { alert('Demo: 签发许可证\n\n此对比页不含后端交互,仅展示 UI 主题差异。') }
function detail(r) { alert(`许可证详情\nID: ${r.licenseId}\n租户: ${r.tenantId}\n状态: ${r.status}`) }
function revoke(r) { if(confirm(`确定吊销 ${r.licenseId}`)){ r.status='revoked' } }
function typeLabel(t) { return ({subscription:'订阅',perpetual:'永久',trial:'试用'})[t]||t }
function statusStyle(s, t) {
const m = { active:{bg:t.name==='figma'?'#E6F7EE':'#f0f9eb',color:t.name==='figma'?'#1A7A3A':'#67c23a',border:'1px solid '+ (t.name==='figma'?'#A8E6C1':'#e1f3d8')},
revoked:{bg:'#fef0f0',color:'#f56c6c',border:'1px solid #fbc4c4'},
expired:{bg:'#f4f4f5',color:'#909399',border:'1px solid #e9e9eb'} }
return m[s]||{bg:'#f4f4f5',color:'#909399'}
}
</script>
<style scoped>
.half { flex:1; overflow-y:auto; position:relative; min-width:680px }
input:focus, select:focus { border-color: #409EFF !important; outline: none }
.figma input:focus, .figma select:focus { border-color: #2C3E6B !important }
</style>