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
@@ -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>