Files
yaokongqi/pages/activate-device/activate-device.uvue
T
2026-09-03 22:21:38 +08:00

1120 lines
31 KiB
Plaintext
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.
<template>
<view class="page">
<!-- ============ 顶部栏(Figma #172:3451,1920×104) ============ -->
<view class="header">
<!-- 左侧:头像 + 登录状态 -->
<view class="header-user" @click="onHeaderUserClick">
<image class="header-avatar" src="/static/activate/avatar.png" mode="aspectFill"></image>
<text class="header-name">{{ isLoggedIn ? '已登录' : '未登录' }}</text>
</view>
<view class="header-spacer"></view>
<!-- 右侧:蓝牙 / WiFi / 信号 + 时间(设计稿内绘制的状态栏元素) -->
<view class="header-status">
<image class="status-icon" src="/static/icons/status-bluetooth.png" mode="aspectFit"></image>
<image class="status-icon status-icon-gap" src="/static/icons/status-wifi.png" mode="aspectFit"></image>
<image class="status-icon status-icon-gap" src="/static/icons/status-cellular.png" mode="aspectFit"></image>
<text class="header-time">{{ clockText }}</text>
</view>
</view>
<view class="content">
<!-- ============ 左列:2×2 功能卡 + 预览大图 ============ -->
<view class="left-col">
<view class="card-grid">
<!-- 文件(Figma #11:227) -->
<view class="func-card" @click="onCardClick('文件')">
<text class="card-title">文件</text>
<text class="card-sub">点击查看文件</text>
<view class="card-ring">
<image class="ring-icon" src="/static/activate/icon-file.png" mode="aspectFit"></image>
</view>
</view>
<!-- 空间(Figma #0:826,电量环) -->
<view class="func-card" @click="onCardClick('空间')">
<text class="card-title">空间</text>
<text class="card-sub">点击查看空间</text>
<image class="battery-ring" src="/static/activate/battery-ring.png" mode="aspectFit"></image>
</view>
<!-- 航线(Figma #0:830) -->
<view class="func-card" @click="onCardClick('航线')">
<text class="card-title">航线</text>
<text class="card-sub">点击查看航线</text>
<image class="route-icon" src="/static/activate/icon-route.png" mode="aspectFit"></image>
</view>
<!-- 文件-菜单(Figma #11:226) -->
<view class="func-card" @click="onCardClick('文件')">
<text class="card-title">文件</text>
<text class="card-sub">点击查看文件</text>
<view class="card-ring">
<image class="ring-icon" src="/static/activate/icon-menu.png" mode="aspectFit"></image>
</view>
</view>
</view>
<!-- 预览大图(Figma #9:100,946×532,5 张轮巡:自动轮播 + 左右滑动浏览) -->
<view class="preview-card">
<swiper
class="preview-swiper"
:autoplay="true"
:circular="true"
:interval="4000"
:duration="400"
:indicator-dots="false"
:current="currentPreview"
:disable-touch="false"
@change="onPreviewChange"
>
<swiper-item v-for="(item, index) in previewImages" :key="index" @click="onPreviewClick(index)">
<image class="preview-img" :src="item" mode="aspectFill"></image>
</swiper-item>
</swiper>
<!-- 指示点(Figma #9:91,200×24,透明度 0.7,距右下 23.44rpx) -->
<image class="preview-dots" src="/static/activate/progress-bar.png" mode="aspectFit"></image>
</view>
</view>
<!-- ============ 右列:设备信息 + 激活按钮 ============ -->
<view class="right-col">
<!-- 设备展示区(Figma #12:239,660×380 灰底) -->
<view class="device-area">
<image class="device-img" src="/static/activate/device-view.png" mode="aspectFit"></image>
</view>
<!-- 设备名(Figma #0:812) -->
<text class="device-name">{{ deviceNameDisplay }}</text>
<!-- 统计三项(Figma #12:241 / #12:242 / #12:240) -->
<view class="stats-row">
<view class="stat-item">
<text class="stat-value">720 h</text>
<text class="stat-label">行驶时长</text>
</view>
<view class="stat-item stat-item-mid">
<text class="stat-value">720 km</text>
<text class="stat-label">行驶里程</text>
</view>
<view class="stat-item">
<text class="stat-value">720 次</text>
<text class="stat-label">行驶次数</text>
</view>
</view>
<!-- 激活状态(Figma #0:821):正在激活中时显示 -->
<text class="activating" v-if="activating">正在激活</text>
<view class="right-spacer"></view>
<!-- 按钮(Figma #9:90,800×96):三态切换 — 添加设备 / 激活设备(含激活中) / 开始使用 -->
<view
class="activate-btn"
:class="{ 'activate-btn-activating': activating, 'activate-btn-done': activateBtnState === 'start' }"
@click="onActivateBtnClick"
>
<!-- 激活中:显示 loading spinner + 文案 -->
<view class="activate-loading" v-if="activating">
<view class="spinner"></view>
<text class="activate-text activate-text-inline">正在激活中...</text>
</view>
<text class="activate-text" v-else>
{{ activateBtnState === 'add' ? '添加设备' : (activateBtnState === 'activate' ? '激活设备' : '开始使用') }}
</text>
</view>
</view>
</view>
<!-- ============ 添加设备弹窗(Figma node 18:617,画布 1280×720,1px=0.390625rpx) ============ -->
<view class="modal-mask" v-if="showAddModal" @click="onModalMaskClick">
<view class="add-modal" @click.stop="noop">
<!-- 顶栏 1280×80 -->
<view class="modal-header">
<text class="modal-title">添加设备</text>
<text class="modal-close" @click="onCloseModal">✕</text>
</view>
<!-- 设备类型选择:无人车(636,160) 150×60 / 无人机(810,160) 150×60 -->
<view class="type-tabs">
<view
class="type-tab"
:class="deviceType === 'car' ? 'type-tab-active' : ''"
@click="onTypeTabChange('car')"
>
<text
class="type-tab-text"
:class="deviceType === 'car' ? 'type-tab-text-active' : ''"
>无人车</text>
</view>
<view
class="type-tab"
:class="deviceType === 'uav' ? 'type-tab-active' : ''"
@click="onTypeTabChange('uav')"
>
<text
class="type-tab-text"
:class="deviceType === 'uav' ? 'type-tab-text-active' : ''"
>无人机</text>
</view>
</view>
<!-- 左侧预览图 Mask (36,128) 542×380,内部图 (84,23) 373×334;按设备类型切换展示 -->
<view class="preview-wrap">
<image
class="preview-dev-img"
:src="deviceType === 'car' ? '/static/activate/add_device_car.png' : '/static/activate/add_device_uav.png'"
mode="aspectFill"
></image>
</view>
<!-- 右侧输入区:设备名 (636,244) 570×60;SN码 (636,334) 570×60 -->
<view class="input-group">
<view class="input-box" @click="onInputFocus('name')">
<input
class="input-field"
v-model="deviceName"
placeholder="请填写设备名称"
placeholder-class="input-placeholder"
:focus="inputFocusTarget === 'name'"
@focus="onInputFocus('name')"
/>
</view>
<view class="input-box input-box-sn" @click="onInputFocus('sn')">
<input
class="input-field"
v-model="deviceSn"
placeholder="请填写设备SN码"
placeholder-class="input-placeholder"
:focus="inputFocusTarget === 'sn'"
@focus="onInputFocus('sn')"
/>
</view>
</view>
<!-- 底部按钮:取消(474,544) 150×60;确定(656,544) 150×60(相对弹窗) -->
<view class="modal-btns">
<view class="modal-btn modal-btn-cancel" @click="onModalCancel">
<text class="modal-btn-text">取消</text>
</view>
<view class="modal-btn modal-btn-ok" @click="onModalOk">
<text class="modal-btn-text">确定</text>
</view>
</view>
<!-- ========== 模拟拼音键盘弹窗(点击输入框打开,覆盖弹窗底部区域) ========== -->
<view class="kbd-mask" v-if="showKeyboard" @click="closeKeyboard">
<view class="kbd-panel" @click.stop="noop">
<view class="kbd-title">
<text class="kbd-title-text">拼音键盘</text>
<text class="kbd-close" @click="closeKeyboard">✕</text>
</view>
<!-- 3 行字母 -->
<view class="kbd-row">
<view class="kbd-key" v-for="(k,i) in kbdRow1" :key="'r1-'+i" @click="onKbdKey(k)"><text class="kbd-key-text">{{ k }}</text></view>
</view>
<view class="kbd-row">
<view class="kbd-key" v-for="(k,i) in kbdRow2" :key="'r2-'+i" @click="onKbdKey(k)"><text class="kbd-key-text">{{ k }}</text></view>
</view>
<view class="kbd-row">
<view class="kbd-key" v-for="(k,i) in kbdRow3" :key="'r3-'+i" @click="onKbdKey(k)"><text class="kbd-key-text">{{ k }}</text></view>
</view>
<!-- 数字行 + 空格 + 删除 + 确认 -->
<view class="kbd-row">
<view class="kbd-key kbd-key-num" v-for="(k,i) in kbdNums" :key="'n-'+i" @click="onKbdKey(k)"><text class="kbd-key-text">{{ k }}</text></view>
<view class="kbd-key kbd-key-space" @click="onKbdKey(' ')"><text class="kbd-key-text">空格</text></view>
<view class="kbd-key kbd-key-del" @click="onKbdBackspace"><text class="kbd-key-text">⌫</text></view>
<view class="kbd-key kbd-key-ok" @click="closeKeyboard"><text class="kbd-key-text kbd-key-ok-text">确认</text></view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
// ====== 添加设备弹窗 ======
const showAddModal = ref<boolean>(false)
const deviceType = ref<string>('car') // 'car' | 'uav' 必选单选,默认无人车
const deviceName = ref<string>('')
const deviceSn = ref<string>('')
// ====== 模拟拼音键盘 ======
const showKeyboard = ref<boolean>(false)
// 当前激活的目标字段:'name' 设备名称 | 'sn' 设备SN码
const inputFocusTarget = ref<string>('')
const kbdRow1 = ref<string[]>(['Q','W','E','R','T','Y','U','I','O','P'])
const kbdRow2 = ref<string[]>(['A','S','D','F','G','H','J','K','L'])
const kbdRow3 = ref<string[]>(['Z','X','C','V','B','N','M'])
const kbdNums = ref<string[]>(['1','2','3','4','5','6','7','8','9','0'])
function onInputFocus(target : string) : void {
// 点击【设备名称】或【设备SN码】打开拼音键盘弹窗
inputFocusTarget.value = target
showKeyboard.value = true
}
function closeKeyboard() : void {
showKeyboard.value = false
}
function onKbdKey(ch : string) : void {
const target = inputFocusTarget.value
if (target !== 'name' && target !== 'sn') return
if (target === 'name') {
deviceName.value = deviceName.value + ch
} else {
deviceSn.value = deviceSn.value + ch
}
}
function onKbdBackspace() : void {
const target = inputFocusTarget.value
if (target !== 'name' && target !== 'sn') return
if (target === 'name') {
const s = deviceName.value
if (s.length > 0) deviceName.value = s.substring(0, s.length - 1)
} else {
const s = deviceSn.value
if (s.length > 0) deviceSn.value = s.substring(0, s.length - 1)
}
}
function noop() : void {}
function onModalMaskClick() : void {
// 点击遮罩关闭(按需求可改为不关闭)
showAddModal.value = false
showKeyboard.value = false
}
function onCloseModal() : void {
showAddModal.value = false
showKeyboard.value = false
}
function onTypeTabChange(type : string) : void {
// 类型单选互斥:切换后预览图同步切换(car→无人车图,uav→无人机图)
deviceType.value = type
}
function onModalCancel() : void {
// 取消添加:关闭弹窗并清空所有编辑信息(名称/SN码/类型恢复默认),不保留
showAddModal.value = false
showKeyboard.value = false
deviceName.value = ''
deviceSn.value = ''
deviceType.value = 'car'
}
function onModalOk() : void {
const name = deviceName.value.trim()
const sn = deviceSn.value.trim()
if (name.length == 0) {
// 未填写设备名称
uni.showToast({ title: '请填写设备名称', icon: 'none' })
return
}
if (sn.length == 0) {
// 未填写设备SN码
uni.showToast({ title: '请填写设备SN码', icon: 'none' })
return
}
// 验证成功:绑定所属设备 → 同步到设备位展示;关闭弹窗;toast 提示「添加成功」;按钮切换为「激活设备」
deviceNameDisplay.value = name
activateBtnState.value = 'activate'
uni.showToast({ title: '添加成功', icon: 'none' })
showAddModal.value = false
showKeyboard.value = false
deviceName.value = ''
deviceSn.value = ''
}
// 预览轮播:限定 5 张宣传图片,接入真实素材后替换路径
const previewImages = ref<string[]>([
'/static/activate/preview-main.png',
'/static/activate/preview-main.png',
'/static/activate/preview-main.png',
'/static/activate/preview-main.png',
'/static/activate/preview-main.png'
])
// 轮播当前页(与 swiper current 双向同步,支持左右滑动浏览)
const currentPreview = ref<number>(0)
const isLoggedIn = ref<boolean>(false)
// 设备位显示的设备名:默认「赛德001」,添加后同步为新设备名
const deviceNameDisplay = ref<string>('赛德001')
// ====== 激活按钮状态机:'add' 添加设备 → 'activate' 激活设备 → 'start' 开始使用 ======
// 激活中中间态:activating = true 时按钮显示"正在激活中..."并禁用点击,上方显示"正在激活"文字
const activateBtnState = ref<string>('add')
const activating = ref<boolean>(false)
let _activateTimer : number = -1
// 按钮点击:按状态分发行为
function onActivateBtnClick() : void {
// 激活中禁止重复点击
if (activating.value) return
const state = activateBtnState.value
if (state === 'add') {
// 「添加设备」→ 打开添加设备弹窗
showAddModal.value = true
} else if (state === 'activate') {
// 「激活设备」→ 进入激活流程
startActivating()
} else if (state === 'start') {
// 「开始使用」→ 进入使用阶段(预留:跳转页面/下发控制命令等)
uni.showToast({ title: '开始使用', icon: 'none' })
}
}
// 模拟激活流程:2.5s 后激活完成,切换为「开始使用」
function startActivating() : void {
if (activating.value) return
activating.value = true
// 清理旧计时器,避免连续点击
if (_activateTimer != -1) {
clearTimeout(_activateTimer)
_activateTimer = -1
}
_activateTimer = setTimeout(() : void => {
activating.value = false
activateBtnState.value = 'start'
_activateTimer = -1
}, 2500)
}
// ====== 顶部时间(Figma 19:41,HH:mm 实时刷新) ======
const clockText = ref<string>('19:41')
let _clockTimer : number = -1
function pad2(n : number) : string {
return n < 10 ? '0' + n.toString() : n.toString()
}
function refreshClock() : void {
const d = new Date()
clockText.value = pad2(d.getHours()) + ':' + pad2(d.getMinutes())
}
function startClock() : void {
refreshClock()
_clockTimer = setInterval(() : void => {
refreshClock()
}, 1000)
}
onLoad(() : void => {
startClock()
})
onUnload(() : void => {
if (_clockTimer != -1) {
clearInterval(_clockTimer)
_clockTimer = -1
}
if (_activateTimer != -1) {
clearTimeout(_activateTimer)
_activateTimer = -1
}
})
// 顶部头像入口(预留:接入账号页)
function onHeaderUserClick() : void {
uni.showToast({ title: isLoggedIn.value ? '已登录' : '未登录', icon: 'none' })
}
// 功能卡片点击(预留:接入文件/空间/航线模块)
function onCardClick(name : string) : void {
uni.showToast({ title: name, icon: 'none' })
}
// 预览轮播滑动切换:同步 current
function onPreviewChange(e : any) : void {
currentPreview.value = e.detail.current as number
}
// 预览图点击:查看投放内容(打开图片预览,可查看5张宣传图片)
function onPreviewClick(index : number) : void {
currentPreview.value = index
const urls : string[] = []
for (let i = 0; i < previewImages.value.length; i++) {
urls.push(previewImages.value[i])
}
// #ifdef APP-ANDROID
uni.previewImage({
urls: urls,
current: urls[index]
})
// #endif
// #ifndef APP-ANDROID
// H5 / 其他平台兜底:简单 toast 提示
uni.showToast({ title: '查看投放内容 ' + (index + 1).toString(), icon: 'none' })
// #endif
}
</script>
<style>
/* ==================== 激活设备页(设计稿 1920×1200,1px = 0.390625rpx) ==================== */
.page {
width: 750rpx;
height: 100%;
background-color: #040912;
flex-direction: column;
}
/* ---------- 顶部栏:1920×104 → 750×40.63rpx ---------- */
.header {
width: 100%;
height: 40.63rpx;
flex-direction: row;
align-items: center;
padding-left: 23.44rpx; /* 头像 x=60 */
padding-right: 23.44rpx; /* 时间右边缘 x=1860 */
}
.header-user {
flex-direction: row;
align-items: center;
}
/* 头像 60×60 → 23.44rpx 圆形 */
.header-avatar {
width: 23.44rpx;
height: 23.44rpx;
border-radius: 11.72rpx;
}
/* 「未登录」36px Regular 白,与头像间距 24 → 9.38rpx */
.header-name {
margin-left: 9.38rpx;
font-size: 14.06rpx;
line-height: 18.75rpx;
color: #FFFFFF;
}
.header-spacer {
flex: 1;
}
.header-status {
flex-direction: row;
align-items: center;
}
/* 状态图标 60×60 → 23.44rpx;图标间距 84-60=24 → 9.38rpx */
.status-icon {
width: 23.44rpx;
height: 23.44rpx;
}
.status-icon-gap {
margin-left: 9.38rpx;
}
/* 「19:41」36px Regular 白,与信号图标间距 24 → 9.38rpx */
.header-time {
margin-left: 9.38rpx;
font-size: 14.06rpx;
line-height: 14.06rpx;
color: #FFFFFF;
text-align: right;
}
/* ---------- 内容区:卡片顶边 y=112(header 104 → 间距 8 → 3.13rpx);底边距 90 → 35.16rpx ---------- */
.content {
flex: 1;
flex-direction: row;
padding-left: 23.44rpx; /* x=60 */
padding-right: 23.44rpx; /* 右列右边缘 x=1860 */
padding-top: 3.13rpx;
padding-bottom: 35.16rpx;
}
/* ---------- 左列:宽 946 → 369.53rpx ---------- */
.left-col {
width: 369.53rpx;
flex-shrink: 0;
flex-direction: column;
}
/* 2×2 卡片网格:卡 464×208 → 181.25×81.25rpx;水平/垂直间距 18 → 7.03rpx */
.card-grid {
width: 100%;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
.func-card {
width: 181.25rpx;
height: 81.25rpx;
margin-bottom: 7.03rpx;
background-color: #242738;
border-radius: 7.81rpx; /* 20 → 7.81rpx */
position: relative;
}
/* 标题 36px Bold,相对卡片 (28,46) → (10.94,17.97)rpx */
.card-title {
position: absolute;
left: 10.94rpx;
top: 17.97rpx;
font-size: 14.06rpx;
line-height: 18.75rpx;
font-weight: bold;
color: #FFFFFF;
}
/* 副标题 24px Medium,相对卡片 (30,106) → (11.72,41.41)rpx */
.card-sub {
position: absolute;
left: 11.72rpx;
top: 41.41rpx;
font-size: 9.38rpx;
line-height: 18.75rpx;
font-weight: 500;
color: #FFFFFF;
}
/* 图标圆环 96×96 → 37.5rpx,相对卡片 (338,56) → (132.03,21.88)rpx;描边 2px 白 */
.card-ring {
position: absolute;
left: 132.03rpx;
top: 21.88rpx;
width: 37.5rpx;
height: 37.5rpx;
border-radius: 18.75rpx;
border-width: 0.78rpx;
border-style: solid;
border-color: #FFFFFF;
align-items: center;
justify-content: center;
}
/* 圆环内图标 60×60 → 23.44rpx */
.ring-icon {
width: 23.44rpx;
height: 23.44rpx;
}
/* 电量环(含 80% 文字)96×96 → 37.5rpx */
.battery-ring {
position: absolute;
left: 132.03rpx;
top: 21.88rpx;
width: 37.5rpx;
height: 37.5rpx;
}
/* 航线图标(含圆环)96×96 → 37.5rpx */
.route-icon {
position: absolute;
left: 132.03rpx;
top: 21.88rpx;
width: 37.5rpx;
height: 37.5rpx;
}
/* 预览大图:设计稿 946×532 → 369.53×207.81rpx;与网格底部间距 32 → 12.5rpx;
高度弹性填充(1200 高屏幕下恰为 207.81rpx,矮屏自动收缩) */
.preview-card {
width: 369.53rpx;
flex: 1;
margin-top: 5.47rpx; /* 设计稿间距 32 → 12.5rpx,扣除第二行卡片 margin-bottom 7.03 */
border-radius: 7.81rpx;
overflow: hidden;
position: relative;
}
.preview-swiper {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.preview-img {
width: 100%;
height: 100%;
}
/* 指示点:200×24 → 78.13×9.38rpx,距卡片右下 60px → 23.44rpx */
.preview-dots {
position: absolute;
right: 23.44rpx;
bottom: 23.44rpx;
width: 78.13rpx;
height: 9.38rpx;
opacity: 0.7;
}
/* ---------- 右列:宽 800 → 312.5rpx,与左列间距 54 → 21.09rpx ---------- */
.right-col {
flex: 1;
margin-left: 21.09rpx;
flex-direction: column;
align-items: center;
}
/* 设备展示区:660×380 → 257.81×148.44rpx顶边 y=130 → 距内容顶 7.03rpx */
.device-area {
width: 257.81rpx;
height: 148.44rpx;
margin-top: 7.03rpx;
position: relative;
}
/* 设备图:373×334 → 145.70×130.47rpx,相对展示区 (145,23) → (56.64,8.98)rpx */
.device-img {
position: absolute;
left: 56.64rpx;
top: 8.98rpx;
width: 145.70rpx;
height: 130.47rpx;
}
/* 设备名「赛德001」36px Bold 居中;与展示区间距 30 → 11.72rpx */
.device-name {
margin-top: 11.72rpx;
font-size: 14.06rpx;
line-height: 19.69rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
}
/* 统计行:与设备名底部间距 66 → 25.78rpx;三项居中,间隙 38 → 14.84rpx */
.stats-row {
margin-top: 25.78rpx;
flex-direction: row;
justify-content: center;
align-items: flex-start;
}
.stat-item {
width: 39.06rpx; /* 100px */
flex-direction: column;
align-items: center;
}
.stat-item-mid {
width: 52.34rpx; /* 134px */
margin-left: 38.28rpx; /* 组间隙 98 → 38.28rpx */
margin-right: 38.28rpx;
}
/* 数值 32px Bold,行高 48 → 18.75rpx */
.stat-value {
font-size: 12.5rpx;
line-height: 18.75rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
}
/* 标签 24px Medium,行高 33.6 → 13.13rpx;与数值间距 22 → 8.59rpx;阴影 0 4 5 rgba(0,0,0,0.3) */
.stat-label {
margin-top: 8.59rpx;
font-size: 9.38rpx;
line-height: 13.13rpx;
font-weight: 500;
color: #FFFFFF;
text-align: center;
text-shadow: 0 1.56rpx 1.95rpx rgba(0, 0, 0, 0.3);
}
/* 「正在激活」24px #0093F9;与统计行底部间距 66 → 25.78rpx */
.activating {
margin-top: 25.78rpx;
font-size: 9.38rpx;
line-height: 13.13rpx;
font-weight: 500;
color: #0093F9;
text-align: center;
text-shadow: 0 1.56rpx 1.95rpx rgba(0, 0, 0, 0.3);
}
/* 弹性占位:设计稿中「正在激活」与按钮间距 148 → 57.81rpx,
1200 高屏幕下自动分配约 57.81rpx,矮屏收缩、不溢出 */
.right-spacer {
flex: 1;
}
/* 激活设备按钮:800×96 → 312.5×37.5rpx */
.activate-btn {
width: 312.5rpx;
height: 37.5rpx;
background-color: rgba(36, 39, 56, 0.8);
border-radius: 1.56rpx; /* 4 → 1.56rpx */
border-width: 0.78rpx; /* 2px → 0.78rpx */
border-style: solid;
border-color: rgba(217, 217, 217, 0.8);
align-items: center;
justify-content: center;
}
/* 按钮文字 32px Bold,行高 38 → 14.84rpx */
.activate-text {
font-size: 12.5rpx;
line-height: 14.84rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
}
/* 激活中态:按钮半透明 + 蓝边高亮,禁用点击视觉 */
.activate-btn-activating {
background-color: rgba(0, 147, 249, 0.15);
border-color: #0093F9;
/* 禁用态点击感:降低不透明度 */
opacity: 0.9;
}
/* 完成态(开始使用):蓝底白字,提示可操作 */
.activate-btn-done {
background-color: #0093F9;
border-color: #0093F9;
}
/* 激活中 loading 容器:横向排列 spinner + 文案 */
.activate-loading {
flex-direction: row;
align-items: center;
justify-content: center;
}
.activate-text-inline {
margin-left: 3.91rpx;
font-size: 12.5rpx;
line-height: 14.84rpx;
color: #0093F9;
}
/* spinner:16×16rpx 左右圆环旋转,两色圆弧(蓝+半透明白) */
.spinner {
width: 10.94rpx;
height: 10.94rpx;
border-radius: 5.47rpx;
border-width: 1.56rpx;
border-style: solid;
border-color: rgba(0, 147, 249, 0.2);
border-top-color: #0093F9;
border-right-color: #0093F9;
}
/* ==================== 添加设备弹窗(Figma 1280×720 画布,1px = 0.390625rpx) ==================== */
.modal-mask {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
align-items: center;
justify-content: center;
z-index: 999;
}
/* 弹窗容器:1280×720 → 500×281.25rpx,背景 #030A16,圆角 20 → 7.81rpx */
.add-modal {
width: 500rpx;
height: 281.25rpx;
background-color: #030A16;
border-radius: 7.81rpx;
position: relative;
overflow: hidden;
}
/* 顶栏:1280×80 → 500×31.25rpx */
.modal-header {
position: absolute;
left: 0;
top: 0;
width: 500rpx;
height: 31.25rpx;
flex-direction: row;
align-items: center;
justify-content: center;
}
/* 标题:30×720 居中,30px Bold 白,行高 38 */
.modal-title {
font-size: 11.72rpx;
line-height: 14.84rpx;
font-weight: bold;
color: #FFFFFF;
text-align: center;
}
/* 关闭按钮:✕ 文本,位置 (1230,25),容器 30×30 → 11.72rpx,文字大小约 21px → 8.2rpx,颜色 #636E80 */
.modal-close {
position: absolute;
right: 19.53rpx;
top: 9.77rpx;
width: 11.72rpx;
height: 11.72rpx;
font-size: 8.2rpx;
line-height: 11.72rpx;
color: #636E80;
text-align: center;
}
/* 类型 Tab 区:容器相对弹窗 x=636→248.44rpx, y=160→62.5rpx,宽 (150*2+24)=324→126.56rpx,高 60→23.44rpx,两个tab间距 24→9.38rpx */
.type-tabs {
position: absolute;
left: 248.44rpx;
top: 62.5rpx;
width: 126.56rpx;
height: 23.44rpx;
flex-direction: row;
align-items: center;
}
/* 单个 Tab:150×60 → 58.59×23.44rpx,圆角 6→2.34rpx,背景 #242738 */
.type-tab {
width: 58.59rpx;
height: 23.44rpx;
background-color: #242738;
border-radius: 2.34rpx;
align-items: center;
justify-content: center;
}
/* 第二个 tab 与第一个间距 24 → 9.38rpx */
.type-tabs > .type-tab + .type-tab {
margin-left: 9.38rpx;
}
/* 选中状态:1px(#0093F9)描边,文字蓝 */
.type-tab-active {
border-width: 0.39rpx;
border-style: solid;
border-color: #0093F9;
}
/* Tab 文字:24px Regular(无人车为 150×60 中 72×38 居中 → 宽 28.13 高 14.84rpx) */
.type-tab-text {
font-size: 9.38rpx;
line-height: 14.84rpx;
font-weight: 350;
color: #999999;
text-align: center;
}
.type-tab-text-active {
color: #0093F9;
}
/* 左侧预览图容器 Mask (36,128) → (14.06,50)rpx,542×380 → 211.72×148.44rpx, */
.preview-wrap {
position: absolute;
left: 14.06rpx;
top: 50rpx;
width: 211.72rpx;
height: 148.44rpx;
overflow: hidden;
align-items: center;
justify-content: center;
}
/* 内部图 373×334 → 相对 Mask (84,23) → (32.81,8.98)rpx,145.70×130.47rpx */
.preview-dev-img {
position: absolute;
left: 32.81rpx;
top: 8.98rpx;
width: 145.70rpx;
height: 130.47rpx;
}
/* 右侧输入组:位置 (636,244) → (248.44,95.31)rpx,570×(60+30+60)=570×150 → 222.66×58.59rpx,输入框间距 30→11.72rpx */
.input-group {
position: absolute;
left: 248.44rpx;
top: 95.31rpx;
width: 222.66rpx;
height: 58.59rpx;
flex-direction: column;
}
/* 单个输入框:570×60 → 222.66×23.44rpx,背景 #242738,圆角 4→1.56rpx */
.input-box {
width: 222.66rpx;
height: 23.44rpx;
background-color: #242738;
border-radius: 1.56rpx;
align-items: center;
justify-content: flex-start;
padding-left: 7.03rpx; /* 18 → 7.03rpx */
}
/* 第二个输入框与第一个间距 30 → 11.72rpx */
.input-box-sn {
margin-top: 11.72rpx;
}
/* 输入框字段 */
.input-field {
width: 208.59rpx; /* 222.66 - 7.03*2 ≈ 208.6rpx */
height: 23.44rpx;
font-size: 9.38rpx; /* 24px */
line-height: 14.06rpx; /* 36/38px → 约 14-14.84 */
color: #FFFFFF;
}
.input-placeholder {
color: #999999;
}
/* 底部按钮:容器中心定位,相对弹窗 y=544→212.5rpx(top 544/720*281.25=212.5),
取消 (474,544) → left 474/1280*500=185.16rpx,确定 656/1280*500=256.25rpx,
每个按钮 150×60 → 58.59×23.44rpx,圆角 6→2.34rpx */
.modal-btns {
position: absolute;
left: 185.16rpx;
top: 212.5rpx;
width: 129.69rpx; /* 58.59 + 12.5 + 58.59 = 129.68 */
height: 23.44rpx;
flex-direction: row;
align-items: center;
}
.modal-btn {
width: 58.59rpx;
height: 23.44rpx;
background-color: #242738;
border-radius: 2.34rpx;
align-items: center;
justify-content: center;
}
/* 取消按钮:白边 1px 白字 (#FFFFFF) */
.modal-btn-cancel {
border-width: 0.39rpx;
border-style: solid;
border-color: #FFFFFF;
}
/* 确定按钮:白边 1px 白字(设计稿确认) */
.modal-btn-ok {
border-width: 0.39rpx;
border-style: solid;
border-color: #FFFFFF;
margin-left: 12.5rpx; /* 182-150-474 得:按钮1 474宽150,按钮2 656宽150 → 656-(474+150)=32 → 12.5rpx */
}
/* 按钮文字:24px Regular 白,48×38 → 18.75×14.84rpx 居中 */
.modal-btn-text {
font-size: 9.38rpx;
line-height: 14.84rpx;
font-weight: 350;
color: #FFFFFF;
text-align: center;
}
/* ==================== 模拟拼音键盘(内嵌于添加设备弹窗内,遮罩+键盘面板) ==================== */
.kbd-mask {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
align-items: flex-end;
justify-content: center;
z-index: 10;
}
/* 键盘面板:宽与弹窗一致(500rpx),高约 150rpx,深色系 #141825 与设计稿统一 */
.kbd-panel {
width: 500rpx;
padding-top: 6.25rpx;
padding-bottom: 6.25rpx;
padding-left: 3.91rpx;
padding-right: 3.91rpx;
background-color: #141825;
border-top-width: 0.78rpx;
border-top-style: solid;
border-top-color: #242738;
}
.kbd-title {
width: 100%;
height: 15.63rpx;
flex-direction: row;
align-items: center;
padding-left: 7.81rpx;
padding-right: 7.81rpx;
margin-bottom: 3.91rpx;
}
.kbd-title-text {
flex: 1;
font-size: 9.38rpx;
line-height: 15.63rpx;
font-weight: 500;
color: #FFFFFF;
}
.kbd-close {
width: 11.72rpx;
height: 11.72rpx;
font-size: 8.2rpx;
line-height: 11.72rpx;
color: #636E80;
text-align: center;
}
.kbd-row {
width: 100%;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 2.34rpx;
}
/* 单键:统一 31.25×15.63rpx(约 80×40 Figma px),圆角 2.34rpx,#242738 背景,白字 */
.kbd-key {
width: 31.25rpx;
height: 15.63rpx;
margin-left: 0.78rpx;
margin-right: 0.78rpx;
background-color: #242738;
border-radius: 2.34rpx;
align-items: center;
justify-content: center;
}
.kbd-key-text {
font-size: 8.59rpx;
line-height: 14.06rpx;
font-weight: 500;
color: #FFFFFF;
text-align: center;
}
/* 数字小键(更紧凑) */
.kbd-key-num {
width: 23.44rpx;
height: 15.63rpx;
}
/* 空格键 */
.kbd-key-space {
width: 78.13rpx;
height: 15.63rpx;
}
/* 删除键 */
.kbd-key-del {
width: 39.06rpx;
height: 15.63rpx;
}
/* 确认键:蓝色底,白字 */
.kbd-key-ok {
width: 46.88rpx;
height: 15.63rpx;
background-color: #0093F9;
}
.kbd-key-ok-text {
color: #FFFFFF;
}
</style>