278 lines
11 KiB
Vue
278 lines
11 KiB
Vue
<!--
|
||
* 特征码对接详情 — 方案B/A双轨、验证流程、对比表
|
||
-->
|
||
<template>
|
||
<view class="sub-page">
|
||
<nav-bar title="特征码对接" :show-back="true" @back="closeSub()" />
|
||
|
||
<scroll-view scroll-y class="page-content">
|
||
<!-- 状态卡 -->
|
||
<view class="status-banner" :class="verified ? 'success' : ''">
|
||
<text class="sb-icon">{{ verified ? '✓' : '!' }}</text>
|
||
<view class="sb-texts">
|
||
<text class="sb-title">{{ verified ? '已验证(方案B)' : '待验证' }}</text>
|
||
<text class="sb-desc">Agent本地生成特征码,基于硬件指纹绑定</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 硬件指纹信息 -->
|
||
<view class="info-card">
|
||
<text class="info-title">硬件指纹信息</text>
|
||
<view class="info-rows">
|
||
<view class="ir-item">
|
||
<text class="ir-label">设备名称</text>
|
||
<text class="ir-value">{{ deviceName }}</text>
|
||
</view>
|
||
<view class="ir-item">
|
||
<text class="ir-label">对接方式</text>
|
||
<text class="ir-value">{{ dockMethod }}</text>
|
||
</view>
|
||
<view class="ir-item">
|
||
<text class="ir-label">特征码</text>
|
||
<text class="ir-value mono">{{ featureCode }}</text>
|
||
</view>
|
||
<view class="ir-item">
|
||
<text class="ir-label">生成方式</text>
|
||
<text class="ir-value">本地Agent生成</text>
|
||
</view>
|
||
<view class="ir-item">
|
||
<text class="ir-label">绑定时间</text>
|
||
<text class="ir-value">{{ bindTime }}</text>
|
||
</view>
|
||
<view class="ir-item">
|
||
<text class="ir-label">验证状态</text>
|
||
<text class="ir-value" :class="verified ? 'status-success' : ''">{{ verified ? '已验证通过' : '未验证' }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 双轨策略 -->
|
||
<view class="section-block">
|
||
<text class="section-title">双轨策略</text>
|
||
|
||
<!-- 方案B -->
|
||
<view class="strategy-card active">
|
||
<view class="sc-badge">当前使用</view>
|
||
<view class="sc-header">
|
||
<text class="sc-name">方案B</text>
|
||
<text class="sc-secure">安全等级 ★★★★★</text>
|
||
</view>
|
||
<view class="sc-steps">
|
||
<view v-for="(s, i) in planBSteps" :key="i" class="scs-item">
|
||
<text class="scs-num">{{ i + 1 }}</text>
|
||
<text class="scs-text">{{ s }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 方案A -->
|
||
<view class="strategy-card disabled">
|
||
<view class="sc-badge" style="background:#f5f7fa;color:#BCC2CE;">二期上线</view>
|
||
<view class="sc-header">
|
||
<text class="sc-name" style="color:#BCC2CE;">方案A</text>
|
||
<text class="sc-secure" style="color:#d9d9d9;">安全等级 ★★★★</text>
|
||
</view>
|
||
<view class="sc-steps">
|
||
<view v-for="(s, i) in planASteps" :key="i" class="scs-item" style="opacity:.5">
|
||
<text class="scs-num">{{ i + 1 }}</text>
|
||
<text class="scs-text">{{ s }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 通用验证流程 -->
|
||
<view class="section-block">
|
||
<text class="section-title">对接验证流程</text>
|
||
<view class="flow-list">
|
||
<view v-for="(f, i) in flowSteps" :key="i" class="fl-item">
|
||
<view class="fl-num">{{ f.step }}</view>
|
||
<view class="fl-body">
|
||
<text class="fl-title">{{ f.title }}</text>
|
||
<text class="fl-desc">{{ f.desc }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 对比表 -->
|
||
<view class="section-block">
|
||
<text class="section-title">方案对比</text>
|
||
<view class="compare-table">
|
||
<view class="ct-row ct-header">
|
||
<text class="ct-cell ct-col-item">对比项</text>
|
||
<text class="ct-cell ct-col-b">方案B</text>
|
||
<text class="ct-cell ct-col-a">方案A</text>
|
||
</view>
|
||
<view v-for="(row, i) in compareRows" :key="i" class="ct-row">
|
||
<text class="ct-cell ct-col-item">{{ row.item }}</text>
|
||
<text class="ct-cell ct-col-b highlight">{{ row.planB }}</text>
|
||
<text class="ct-cell ct-col-a dim">{{ row.planA }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import NavBar from '@/components/NavBar.vue'
|
||
import request from '@/utils/request'
|
||
import { ref } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import { closeSub, parseSubParams } from '@/composables/useNavigation'
|
||
|
||
const deviceName = ref('--')
|
||
const featureCode = ref('--')
|
||
const dockMethod = ref('方案B(Agent生成)')
|
||
const bindTime = ref('--')
|
||
const verified = ref(false)
|
||
|
||
onLoad(async (options) => {
|
||
const params = parseSubParams(options)
|
||
if (params.id) {
|
||
try {
|
||
const res = await request.get('/c/devices/' + params.id, { showLoading: false })
|
||
const d = res.data
|
||
deviceName.value = d.name || d.device_id || '--'
|
||
dockMethod.value = d.source === 'own' ? '方案B(Agent生成)' : '平台设备'
|
||
bindTime.value = d.created_at || '--'
|
||
verified.value = d.status === 'running'
|
||
// 特征码用 device_id 脱敏展示
|
||
if (d.device_id) {
|
||
featureCode.value = d.device_id.substring(0, 4) + '****'
|
||
}
|
||
} catch (e) { /* 忽略 */ }
|
||
}
|
||
})
|
||
|
||
const goBack = () => closeSub()
|
||
|
||
const planBSteps = ref([
|
||
'Agent安装后本地生成特征码',
|
||
'特征码与硬件指纹绑定',
|
||
'加密传输至服务器',
|
||
'服务器端验证并激活',
|
||
'绑定成功,进入运行态'
|
||
])
|
||
|
||
const planASteps = ref([
|
||
'手动录入硬件信息',
|
||
'服务器生成特征码',
|
||
'人工审核并下发',
|
||
'设备端确认绑定',
|
||
'激活上线'
|
||
])
|
||
|
||
const flowSteps = ref([
|
||
{ step: '01', title: '特征码生成', desc: '系统根据设备硬件信息生成唯一特征码' },
|
||
{ step: '02', title: '本地上传', desc: 'Agent将特征码加密上传至服务器' },
|
||
{ step: '03', title: '服务器验证', desc: '服务器对比硬件指纹与特征码一致性' },
|
||
{ step: '04', title: '绑定确认', desc: '验证通过后,特征码与设备完成绑定' },
|
||
{ step: '05', title: '激活上线', desc: '绑定成功,设备进入运行中状态' }
|
||
])
|
||
|
||
const compareRows = ref([
|
||
{ item: '特征码生成', planB: 'Agent端本地生成', planA: '服务器端生成' },
|
||
{ item: '硬件绑定方式', planB: '硬件指纹自动绑定', planA: '手动录入信息' },
|
||
{ item: '部署复杂度', planB: '低(自动完成)', planA: '高(需人工参与)' },
|
||
{ item: '安全性', planB: '高(端到端加密)', planA: '中(人工审核)' },
|
||
{ item: '上线速度', planB: '分钟级', planA: '小时级' },
|
||
{ item: '适用场景', planB: '自有设备/新部署', planA: '存量设备迁移' },
|
||
{ item: '二期支持', planB: '-', planA: '二期上线后支持' }
|
||
])
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.sub-page { min-height: 100vh; background: #F2F4F8; }
|
||
.page-content { height: calc(100vh - 88rpx); padding: 24rpx 24rpx 60rpx; }
|
||
|
||
// Status banner
|
||
.status-banner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 24rpx;
|
||
border-radius: 16rpx;
|
||
margin-bottom: 20rpx;
|
||
&.success { background: linear-gradient(135deg, #e6faf5, #d4f5ed); }
|
||
}
|
||
.sb-icon {
|
||
width: 56rpx; height: 56rpx; border-radius: 50%; background: #00C4A7;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 28rpx; color: #fff; flex-shrink: 0;
|
||
}
|
||
.sb-title { font-size: 28rpx; font-weight: 700; color: #0F1226; display: block; }
|
||
.sb-desc { font-size: 22rpx; color: #5A6072; display: block; margin-top: 4rpx; }
|
||
|
||
// Info card
|
||
.info-card {
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(15,18,38,.04);
|
||
}
|
||
.info-title { font-size: 28rpx; font-weight: 700; color: #0F1226; display: block; margin-bottom: 20rpx; }
|
||
.ir-item { display: flex; padding: 16rpx 0; border-bottom: 1rpx solid #f5f7fa; &:last-child { border: none; } }
|
||
.ir-label { font-size: 24rpx; color: #8B91A1; width: 160rpx; flex-shrink: 0; }
|
||
.ir-value { font-size: 24rpx; color: #0F1226; flex: 1;
|
||
&.mono { font-family: 'Courier New', monospace; }
|
||
&.status-success { color: #00C4A7; font-weight: 600; }
|
||
}
|
||
|
||
// Section
|
||
.section-block { margin-bottom: 24rpx; }
|
||
.section-title {
|
||
font-size: 28rpx; font-weight: 700; color: #0F1226; margin-bottom: 16rpx;
|
||
display: flex; align-items: center; gap: 10rpx;
|
||
&::before { content: ''; width: 6rpx; height: 26rpx; background: linear-gradient(180deg, #1677FF, #4096FF); border-radius: 3rpx; }
|
||
}
|
||
|
||
// Strategy card
|
||
.strategy-card {
|
||
background: #fff;
|
||
border-radius: 14rpx;
|
||
padding: 24rpx;
|
||
margin-bottom: 16rpx;
|
||
box-shadow: 0 2rpx 8rpx rgba(15,18,38,.04);
|
||
position: relative;
|
||
&.active { border-left: 6rpx solid #00C4A7; }
|
||
&.disabled { border-left: 6rpx solid #e0e0e0; opacity: .7; }
|
||
}
|
||
.sc-badge { position: absolute; top: 16rpx; right: 16rpx; font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 6rpx; background: #e6faf5; color: #00C4A7; }
|
||
.sc-header { display: flex; align-items: center; gap: 16rpx; margin-bottom: 16rpx; }
|
||
.sc-name { font-size: 30rpx; font-weight: 700; color: #0F1226; }
|
||
.sc-secure { font-size: 20rpx; color: #FF8800; }
|
||
.sc-steps { display: flex; flex-direction: column; gap: 8rpx; }
|
||
.scs-item { display: flex; align-items: center; gap: 12rpx; }
|
||
.scs-num { width: 32rpx; height: 32rpx; border-radius: 50%; background: #e6faf5; color: #00C4A7; font-size: 20rpx; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
||
.scs-text { font-size: 24rpx; color: #5A6072; }
|
||
|
||
// Flow list
|
||
.flow-list { display: flex; flex-direction: column; gap: 12rpx; }
|
||
.fl-item { display: flex; gap: 16rpx; background: #fff; border-radius: 14rpx; padding: 20rpx; box-shadow: 0 2rpx 8rpx rgba(15,18,38,.04); }
|
||
.fl-num { width: 48rpx; height: 48rpx; background: linear-gradient(135deg, #1677FF, #4096FF); border-radius: 10rpx; display: flex; align-items: center; justify-content: center; font-size: 22rpx; color: #fff; font-weight: 700; flex-shrink: 0; }
|
||
.fl-body { flex: 1; }
|
||
.fl-title { font-size: 26rpx; font-weight: 600; color: #0F1226; display: block; }
|
||
.fl-desc { font-size: 22rpx; color: #8B91A1; display: block; margin-top: 4rpx; }
|
||
|
||
// Compare table
|
||
.compare-table {
|
||
background: #fff;
|
||
border-radius: 14rpx;
|
||
overflow: hidden;
|
||
box-shadow: 0 2rpx 8rpx rgba(15,18,38,.04);
|
||
}
|
||
.ct-row { display: flex; padding: 18rpx 16rpx; border-bottom: 1rpx solid #f5f7fa; &:last-child { border: none; } }
|
||
.ct-header { background: #fafafa; }
|
||
.ct-cell { font-size: 22rpx; color: #5A6072; text-align: center;
|
||
.ct-header & { font-weight: 600; color: #0F1226; }
|
||
}
|
||
.ct-col-item { flex: 1.2; text-align: left; }
|
||
.ct-col-b { flex: 1; }
|
||
.ct-col-a { flex: 1; }
|
||
.highlight { color: #00C4A7; font-weight: 500; }
|
||
.dim { color: #BCC2CE; }
|
||
</style>
|