Files
quanxiyun-app/src/pages-sub/device/docking-detail.vue
T
2026-09-16 15:04:08 +08:00

278 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
* 特征码对接详情 方案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('方案BAgent生成)')
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' ? '方案BAgent生成)' : '平台设备'
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>