mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 01:50:30 +08:00
feat(web): add 3-way theme comparison (Figma / ElementPlus / Hybrid-recommended)
This commit is contained in:
@@ -1,73 +1,31 @@
|
||||
<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 style="display:flex; height:100vh; overflow:hidden; min-width:1950px">
|
||||
<div v-for="t in themes" :key="t.name" :class="['panel', t.css]" :style="{background:t.bg}">
|
||||
<div class="badge" :style="{background:t.badgeBg||t.brand,color:t.badgeColor||'#fff'}">
|
||||
{{ t.label }}
|
||||
<span v-if="t.recommend" class="recommend-tag">推荐</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="stat-row">
|
||||
<div v-for="s in stats" :key="s.label" class="card" :style="{borderRadius:t.radius+'px',border:t.cardBorder}">
|
||||
<div class="stat-num">{{ s.value }}</div><div class="stat-label">{{ 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 class="card" :style="{borderRadius:t.radius+'px',border:t.cardBorder}">
|
||||
<div class="tool-row">
|
||||
<div class="search-group">
|
||||
<div class="input-box"><span class="input-icon">🔍</span><input v-model="keyword" placeholder="搜索许可证ID" class="native-input"></div>
|
||||
<select v-model="filterStatus" class="native-select"><option value="">全部状态</option><option value="active">活跃</option><option value="revoked">已吊销</option><option value="expired">已过期</option></select>
|
||||
<button class="btn" :style="{background:t.brand,color:'#fff',borderRadius:'4px'}" @click="search">查询</button>
|
||||
</div>
|
||||
<button :style="{background:theme.brand,color:'#fff',border:'none',borderRadius:'4px',padding:'7px 16px',fontSize:'14px',cursor:'pointer'}" @click="create">+ 签发许可证</button>
|
||||
<button class="btn btn-create" :style="{background:t.createBg||t.brand,color:'#fff',borderRadius:'4px',boxShadow:t.createShadow||'none'}" @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>
|
||||
<div class="card" :style="{borderRadius:t.radius+'px',border:t.cardBorder,padding:0,overflow:'hidden'}">
|
||||
<table class="dt"><thead><tr :style="{background:t.thBg,color:t.thColor,borderBottom:t.thBorder||'1px solid #ebeef5'}"><th>许可证 ID</th><th>租户</th><th class="c80">类型</th><th class="c60">终端</th><th class="c70">宽限</th><th class="c70">状态</th><th class="c100">操作</th></tr></thead>
|
||||
<tbody><tr v-for="r in list" :key="r.licenseId" :style="{borderBottom:'1px solid '+(t.rowBorder||'#ebeef5')}"><td class="td-id">{{ r.licenseId }}</td><td class="td2">{{ r.tenantId }}</td><td>{{ typeL(r.grantType) }}</td><td class="td2">{{ r.maxDevices }}</td><td class="td2">{{ r.offlineGraceDays }}天</td><td><span :style="statusS(r.status,t)">{{ statusL(r.status) }}</span></td><td><span class="act" :style="{color:t.brand}" @click="detail(r)">详情</span><span v-if="r.status==='active'" class="act" style="color:#f56c6c" @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 class="pager"><span>共 {{ total }} 条</span><span class="pc" @click="page=Math.max(1,page-1)">‹</span><span>{{ page }}</span><span class="pc" @click="page=Math.min(3,page+1)">›</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,8 +36,9 @@
|
||||
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:'查询' }
|
||||
{ name:'figma', css:'p-figma', label:'Figama · 安徽地质博物馆', bg:'#EAEFFA', brand:'#2C3E6B', radius:6, cardBorder:'1px solid #D6DFF0', thBg:'#F2F5FC', thColor:'#2C3E6B', thBorder:'1px solid #D6DFF0', rowBorder:'#eef0f5', createBg:'#2C3E6B' },
|
||||
{ name:'elplus', css:'p-elplus', label:'Element Plus 默认', bg:'#f0f2f5', brand:'#409EFF', radius:4, cardBorder:'none', thBg:'#f5f7fa', thColor:'#303133', thBorder:'1px solid #ebeef5', rowBorder:'#ebeef5', createBg:'#409EFF' },
|
||||
{ name:'hybrid', css:'p-hybrid', label:'推荐 · 混合方案', bg:'#EAEFFA', brand:'#2C3E6B', radius:6, cardBorder:'1px solid #D6DFF0', thBg:'#F2F5FC', thColor:'#2C3E6B', thBorder:'1px solid #D6DFF0', rowBorder:'#eef0f5', createBg:'#2C3E6B', createShadow:'0 2px 8px rgba(44,62,107,.2)', badgeBg:'linear-gradient(135deg, #2C3E6B, #3D5A99)', badgeColor:'#fff', recommend:true }
|
||||
]
|
||||
|
||||
const MOCK = [
|
||||
@@ -90,34 +49,58 @@ const MOCK = [
|
||||
{ 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 rows = ref([...MOCK]), 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)
|
||||
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'}
|
||||
function search(){}
|
||||
function create(){ alert('Demo: 签发许可证对话框') }
|
||||
function detail(r){ alert(`许可证: ${r.licenseId}\n租户: ${r.tenantId}\n状态: ${r.status}`) }
|
||||
function revoke(r){ if(confirm(`吊销 ${r.licenseId}?`)) r.status='revoked' }
|
||||
function typeL(t){ return ({subscription:'订阅',perpetual:'永久',trial:'试用'})[t]||t }
|
||||
function statusL(s){ return ({active:'活跃',revoked:'已吊销',expired:'已过期'})[s]||s }
|
||||
function statusS(s,t){
|
||||
const b={display:'inline-block',padding:'2px 8px',borderRadius:'3px',fontSize:'12px',fontWeight:500}
|
||||
if(s==='active'&&t.name==='elplus') return{...b,background:'#f0f9eb',color:'#67c23a',border:'1px solid #e1f3d8'}
|
||||
if(s==='active') return{...b,background:'#E6F7EE',color:'#1A7A3A',border:'1px solid #A8E6C1'}
|
||||
if(s==='revoked') return{...b,background:'#fef0f0',color:'#f56c6c',border:'1px solid #fbc4c4'}
|
||||
return{...b,background:'#f4f4f5',color:'#909399',border:'1px solid #e9e9eb'}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}</style>
|
||||
<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 }
|
||||
.panel{flex:1;overflow-y:auto;min-width:630px;position:relative}
|
||||
.badge{position:sticky;top:0;z-index:10;padding:7px 14px;font-size:13px;font-weight:600;text-align:center;letter-spacing:.3px}
|
||||
.panel:not(:last-child){border-right:2px solid rgba(0,0,0,.08)}
|
||||
.body{padding:12px 15px;display:flex;flex-direction:column;gap:10px}
|
||||
.stat-row{display:flex;gap:10px}
|
||||
.card{background:#fff;padding:13px 14px;box-shadow:0 1px 3px rgba(0,0,0,.05)}
|
||||
.stat-num{font-size:26px;font-weight:700;margin-bottom:2px;text-align:center}
|
||||
.stat-label{font-size:12px;color:#909399;text-align:center}
|
||||
.tool-row{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}
|
||||
.search-group{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
||||
.input-box{border:1px solid #dcdfe6;border-radius:4px;padding:5px 10px;width:170px;display:flex;align-items:center;gap:5px;background:#fff}
|
||||
.input-icon{color:#c0c4cc;font-size:12px}
|
||||
.native-input{border:none;outline:none;flex:1;font-size:13px;color:#606266;background:transparent}
|
||||
.native-select{border:1px solid #dcdfe6;border-radius:4px;padding:5px 8px;font-size:13px;color:#606266;background:#fff}
|
||||
.btn{border:none;padding:5px 12px;font-size:13px;cursor:pointer;font-weight:500;white-space:nowrap}
|
||||
.btn-create{padding:5px 14px}
|
||||
.dt{width:100%;border-collapse:collapse;font-size:13px}
|
||||
.dt th{padding:9px 8px;text-align:left;font-weight:600;font-size:12px;white-space:nowrap}
|
||||
.dt td{padding:8px}
|
||||
.td-id{color:#303133;font-family:monospace;font-size:12px}
|
||||
.td2{color:#606266}
|
||||
.c80{width:65px}.c60{width:50px}.c70{width:60px}.c100{width:95px}
|
||||
.act{cursor:pointer;margin-right:10px;font-size:13px;user-select:none}
|
||||
.pager{padding:8px 14px;display:flex;justify-content:flex-end;align-items:center;gap:6px;font-size:12px;color:#909399;border-top:1px solid #ebeef5}
|
||||
.pc{cursor:pointer;font-weight:600;color:#606266}
|
||||
.recommend-tag{font-size:10px;margin-left:6px;border:1px solid currentColor;border-radius:3px;padding:0 4px;line-height:1.6;display:inline-block;vertical-align:1px}
|
||||
.p-hybrid .badge{text-shadow:0 1px 2px rgba(0,0,0,.15)}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user