Files
shoot-miniprograms/src/pages/device/my-device.vue
T
2026-09-15 17:56:36 +08:00

1153 lines
26 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.
<script setup>
import { computed, onMounted, onUnmounted, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import ScreenHint from "@/components/ScreenHint.vue";
import ModalDialog from "@/components/ModalDialog.vue";
import { laserAimAPI } from "@/apis";
import useStore from "@/store";
import { storeToRefs } from "pinia";
import { useDeviceBinding } from "./composables/useDeviceBinding";
import {
DEVICE_NAME_STORAGE_KEY,
useDeviceStatus,
} from "./composables/useDeviceStatus";
const store = useStore();
const { updateDevice, updateOnline, clearDevice } = store;
const { user, device, online } = storeToRefs(store);
const formatBindingDate = (value) => {
if (!value) return "2026/12/13";
const text = String(value).trim();
const dateParts = text.match(/^(\d{4})[-/.](\d{1,2})[-/.](\d{1,2})/);
if (dateParts) {
return `${dateParts[1]}/${dateParts[2].padStart(2, "0")}/${dateParts[3].padStart(2, "0")}`;
}
const numericValue = Number(value);
const date = new Date(
Number.isFinite(numericValue)
? numericValue < 1e12
? numericValue * 1000
: numericValue
: value
);
if (Number.isNaN(date.getTime())) return text;
return `${date.getFullYear()}/${String(date.getMonth() + 1).padStart(2, "0")}/${String(
date.getDate()
).padStart(2, "0")}`;
};
const showTip = ref(false);
const confirmBindTip = ref(false);
const unbindDialogVisible = ref(false);
const nameEditorVisible = ref(false);
const qrVisible = ref(false);
const qrSaved = ref(false);
const editingName = ref("");
const token = ref("");
const binding = ref(false);
const retryScanOnShow = ref(false);
const calibration = ref(false);
const showDeviceId = ref(false);
const {
batteryText,
deviceDetails,
getDeviceNameOverrides,
isDeviceOnline,
maskedDeviceId,
networkText,
refreshDeviceStatus,
statusClass,
statusText,
syncDeviceBinding,
unbindDevice,
} = useDeviceStatus({
user,
device,
online,
updateDevice,
updateOnline,
clearDevice,
unbindDialogVisible,
});
const wifiStatusText = computed(() =>
String(networkText.value || "").toLowerCase() === "wifi" ? "已连接" : "未连接"
);
const bindingDateText = computed(() =>
formatBindingDate(
deviceDetails.value.bindTime ||
deviceDetails.value.bindingTime ||
deviceDetails.value.createTime
)
);
const deviceIdText = computed(() =>
showDeviceId.value && device.value.deviceId
? device.value.deviceId
: maskedDeviceId.value
);
// 设计稿中的设备信息优先读取接口字段,缺省时使用页面展示默认值。
const designDeviceStats = computed(() => [
{
label: "设备型号",
value: deviceDetails.value.model || deviceDetails.value.deviceModel || "射灵1代",
},
{
label: "剩余电量",
value: batteryText.value === "暂无数据" ? "88%" : batteryText.value,
},
{
label: "累计使用",
value:
deviceDetails.value.totalUseTime ||
deviceDetails.value.usedTime ||
deviceDetails.value.duration ||
"2510小时36分",
},
{
label: "绑定时间",
value: bindingDateText.value,
},
]);
const { confirmBind, handleScan } = useDeviceBinding({
token,
confirmBindTip,
binding,
updateDevice,
deviceDetails,
refreshDeviceStatus,
});
const qrImageUrl = computed(
() =>
deviceDetails.value.qrCode ||
deviceDetails.value.qrcode ||
deviceDetails.value.qrUrl ||
"../../static/device-assets/my-device-qrcode.png"
);
const isScanPage = computed(() => !device.value.deviceId && !qrVisible.value);
// 解绑前展示统一确认弹窗,避免误触解除绑定。
const openUnbindDialog = () => {
unbindDialogVisible.value = true;
};
const closeUnbindDialog = () => {
unbindDialogVisible.value = false;
};
const openNameEditor = () => {
editingName.value = device.value.deviceName || "我的智能弓";
nameEditorVisible.value = true;
};
const closeNameEditor = () => {
nameEditorVisible.value = false;
};
const toggleDeviceId = () => {
if (device.value.deviceId) showDeviceId.value = !showDeviceId.value;
};
// 当前接口列表没有设备改名接口,先更新页面和本地缓存,后端接口接入时可替换为请求。
const confirmName = () => {
const name = String(editingName.value || "").trim();
if (!name) {
uni.showToast({ title: "请输入设备名", icon: "none" });
return;
}
if (name.length > 10 || !/^[\u4e00-\u9fa5A-Za-z0-9_-]+$/.test(name)) {
uni.showToast({ title: "设备名格式不正确", icon: "none" });
return;
}
updateDevice(device.value.deviceId, name);
deviceDetails.value = { ...deviceDetails.value, deviceName: name };
const nameOverrides = getDeviceNameOverrides();
nameOverrides[device.value.deviceId] = name;
uni.setStorageSync(DEVICE_NAME_STORAGE_KEY, nameOverrides);
nameEditorVisible.value = false;
uni.showToast({ title: "设备名已更新", icon: "success" });
};
const toDeviceIntroPage = () => {
uni.navigateTo({ url: "/pages/device/device-intro" });
};
const joinWifi = () => {
uni.navigateTo({ url: "/pages/device/ota-wifi" });
};
const goFirmwareUpdate = () => {
if (!isDeviceOnline.value) {
uni.showToast({ title: "请先开启智能弓", icon: "none" });
return;
}
uni.navigateTo({ url: "/pages/device/ota-wifi" });
};
const goCalibration = async () => {
try {
await laserAimAPI();
uni.navigateTo({ url: "/pages/device/calibration" });
} catch (error) {
uni.showToast({ title: "设备未连接,暂时无法调瞄", icon: "none" });
}
};
const copyEmail = () => {
uni.setClipboardData({
data: "shelingxingqiu@163.com",
success: () => uni.showToast({ title: "邮箱已复制", icon: "success" }),
});
};
const openQr = () => {
qrSaved.value = false;
qrVisible.value = true;
};
const closeTip = () => {
showTip.value = false;
};
const closeConfirmBindTip = () => {
confirmBindTip.value = false;
};
const closeQr = () => {
qrVisible.value = false;
};
// 保存二维码到相册;远程二维码先下载到临时目录,失败时保留长按保存提示。
const saveQrCode = async () => {
let filePath = qrImageUrl.value;
try {
if (/^https?:\/\//.test(filePath)) {
filePath = await new Promise((resolve, reject) => {
uni.downloadFile({
url: filePath,
success: (result) =>
result.statusCode === 200
? resolve(result.tempFilePath)
: reject(new Error("二维码下载失败")),
fail: reject,
});
});
}
await new Promise((resolve, reject) => {
uni.saveImageToPhotosAlbum({ success: resolve, fail: reject, filePath });
});
qrSaved.value = true;
uni.showToast({ title: "已保存至相册", icon: "success" });
} catch (error) {
uni.showToast({ title: "请长按二维码保存", icon: "none" });
}
};
onLoad((options = {}) => {
retryScanOnShow.value = options.retryScan === "1";
});
onMounted(() => {
uni.$on("device-bind-retry-scan", handleScan);
});
onUnmounted(() => {
uni.$off("device-bind-retry-scan", handleScan);
});
onShow(async () => {
calibration.value = uni.getStorageSync("calibration");
await syncDeviceBinding();
if (retryScanOnShow.value) {
retryScanOnShow.value = false;
handleScan();
}
});
</script>
<template>
<view class="my-device-page" :class="{ 'my-device-page--scan': isScanPage }">
<Container
:bgType="12"
:scroll="false"
:usePageScroll="false"
>
<view v-if="qrVisible" class="qr-page" @click="closeQr">
<view class="qr-canvas" @click.stop>
<view class="qr-corner qr-corner--top"></view>
<view class="qr-corner qr-corner--bottom"></view>
<text class="qr-title">射灵智能弓箭二维码</text>
<image class="qr-image" :src="qrImageUrl" mode="aspectFit" show-menu-by-longpress />
<text v-if="qrSaved" class="qr-device-id">设备ID{{ maskedDeviceId }}</text>
<view v-else class="qr-save-button" @click="saveQrCode">
<text>保存至相册</text>
</view>
<text class="qr-description">
该二维码为当前绑定弓箭的二维码你可以截图保存到相册以便当二维码丢失或不在身边时可以扫描二维码进行设备绑定
</text>
<text class="qr-note">解除绑定后将无法查看该二维码</text>
</view>
</view>
<view v-else-if="!device.deviceId" class="scan-code">
<view class="unbound-content">
<button class="scan-entry" hover-class="none" @click="$clickSound(handleScan)">
<image src="../../static/device-assets/my-device-unbound-scan.png" mode="aspectFit" />
</button>
<view class="scan-title">
<text>请扫描</text>
<text class="highlight-text">射灵智能弓箭</text>
<text>设备上的二维码</text>
</view>
<view class="benefit-copy">
<text>新设备首次绑定账号可获赠</text>
<text class="highlight-text">6个月射灵会员礼包</text>
<text></text>
<text>该礼包仅可使用一次请确保当前登录账号为您本人账号</text>
</view>
<button class="help-link" hover-class="none" @click="showTip = true">
<text>没找到二维码或遇到问题&gt;</text>
</button>
<button class="product-link" hover-class="none" @click="toDeviceIntroPage">
<text>还没有射灵智能弓箭点我获取&gt;</text>
</button>
</view>
</view>
<view v-else class="device-page">
<view class="device-visual">
<image
class="device-stage"
src="../../static/home-device/device-platform.png"
mode="widthFix"
/>
<image
class="device-bow"
src="../../static/home-device/device-bow.png"
mode="widthFix"
/>
</view>
<view class="device-heading">
<view class="device-status" :class="statusClass">
<view class="status-dot"></view>
<text>{{ statusText }}</text>
</view>
<view class="device-title-row">
<text class="device-name">{{ device.deviceName || "打弓佬" }}</text>
<view class="edit-name" @click="openNameEditor">
<image src="../../static/home-device/device-edit.png" mode="aspectFit" />
</view>
</view>
<view class="device-id-row">
<text>设备ID{{ deviceIdText }}</text>
<view class="device-id-toggle" @click="toggleDeviceId">
<image
class="device-id-eye"
src="../../static/home-device/device-id-visibility.png"
mode="aspectFit"
/>
</view>
</view>
</view>
<view class="device-stats">
<view
v-for="(item, index) in designDeviceStats"
:key="item.label"
class="device-stat"
:class="{
'device-stat--divider': index > 0,
'device-stat--usage': index === 2,
}"
>
<text class="device-stat-label">{{ item.label }}</text>
<text class="device-stat-value">{{ item.value }}</text>
</view>
</view>
<view class="device-actions">
<view class="action-item" @click="toDeviceIntroPage">
<view class="action-icon">
<image src="../../static/home-device/device-action-intro.png" mode="aspectFit" />
</view>
<text>设备介绍</text>
</view>
<view class="action-item" @click="goCalibration">
<view class="action-icon">
<image src="../../static/home-device/device-action-calibration.png" mode="aspectFit" />
</view>
<text>弓箭调瞄</text>
</view>
<view class="action-item" @click="goFirmwareUpdate">
<view class="action-icon">
<image src="../../static/home-device/device-action-firmware.png" mode="aspectFit" />
</view>
<view class="action-label-wrap">
<text>固件更新</text>
<text class="action-badge action-badge--new">New</text>
</view>
</view>
<view class="action-item" @click="joinWifi">
<view class="action-icon">
<image src="../../static/home-device/device-action-wifi.png" mode="aspectFit" />
</view>
<view class="action-label-wrap">
<text>WIFI设置</text>
<text v-if="wifiStatusText === '未连接'" class="action-badge action-badge--offline">
未连接
</text>
</view>
</view>
<view class="action-item" @click="openQr">
<view class="action-icon">
<image src="../../static/home-device/device-action-qrcode.png" mode="aspectFit" />
</view>
<text>设备二维码</text>
</view>
</view>
<view class="unbind-entry" @click="openUnbindDialog">
<image
class="unbind-icon"
src="../../static/home-device/device-unbind.png"
mode="aspectFit"
/>
<text>解除绑定</text>
</view>
<view v-if="calibration" class="calibration-tip">
<text>如有场地或距离变化请重新校准以保证智能弓射箭精准度</text>
<view @click="goCalibration">重新校准</view>
</view>
</view>
<ScreenHint mode="square" :show="showTip" :onClose="closeTip">
<view class="scan-tips">
<text class="scan-tips-title">扫码绑定射灵弓箭</text>
<text class="scan-tips-subtitle">设备底部二维码样例</text>
<image
class="scan-tips-qr"
src="../../static/device-assets/my-device-unbound-qr-sample.png"
mode="widthFix"
/>
<text class="scan-tips-note">已被绑定的弓箭无法再次绑定</text>
<view class="scan-tips-contact">
<text>如有任何疑问请随时联系</text>
<button hover-class="none" @click="copyEmail">shelingxingqiu@163.com</button>
</view>
</view>
</ScreenHint>
<ScreenHint :show="confirmBindTip" :onClose="closeConfirmBindTip">
<view class="confirm-bind">
<text>智能弓箭和系统账号需一一对应你确定要将当前登录用户账号绑定这把弓箭吗绑定后不可随意更换</text>
<view class="confirm-actions">
<view
class="primary-action"
:class="{ 'primary-action--disabled': binding }"
@click="confirmBind"
>
{{ binding ? "绑定中..." : "确认绑定" }}
</view>
<view class="secondary-action" @click="confirmBindTip = false">取消</view>
</view>
</view>
</ScreenHint>
<ModalDialog
:show="unbindDialogVisible"
title="解除设备绑定"
content="解除绑定后,将无法使用智能弓箭进行对战,确定继续吗?"
cancelText="取消"
confirmText="解除绑定"
:onCancel="closeUnbindDialog"
:onConfirm="unbindDevice"
></ModalDialog>
<view v-if="nameEditorVisible" class="name-mask" @click="closeNameEditor">
<view class="name-sheet" @click.stop>
<view class="name-input-row">
<input
v-model="editingName"
class="name-input"
focus
maxlength="10"
confirm-type="done"
placeholder="请输入设备名(最长10个汉字)"
placeholder-class="name-placeholder"
@confirm="confirmName"
/>
<view class="name-confirm" @click="confirmName">确定</view>
</view>
<text class="name-helper">仅支持中文英文数字下划线减号</text>
</view>
</view>
</Container>
</view>
</template>
<style scoped>
.my-device-page {
position: relative;
min-height: 100vh;
background: transparent;
}
.my-device-page--scan {
height: 100vh;
overflow: hidden;
}
.scan-code,
.device-page {
display: flex;
width: 100%;
min-height: calc(100vh - 100rpx);
box-sizing: border-box;
flex-direction: column;
align-items: center;
}
.scan-code {
position: relative;
overflow: hidden;
justify-content: flex-start;
padding-top: 0;
background: transparent;
}
.unbound-content {
position: relative;
z-index: 1;
display: flex;
width: 100%;
min-height: calc(100vh - 100rpx);
box-sizing: border-box;
flex-direction: column;
align-items: center;
padding: 170rpx 32rpx 160rpx;
}
.scan-entry,
.help-link,
.product-link {
padding: 0;
border: 0;
background: transparent;
line-height: normal;
}
.scan-entry::after,
.help-link::after,
.product-link::after {
border: 0;
}
.scan-entry {
display: flex;
width: 276rpx;
height: 276rpx;
align-items: center;
justify-content: center;
}
.scan-entry image {
width: 276rpx;
height: 276rpx;
}
.scan-title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin-top: 48rpx;
color: #ffffff;
font-size: 28rpx;
font-weight: 500;
line-height: 40rpx;
white-space: nowrap;
}
.benefit-copy {
display: flex;
width: 100%;
flex-wrap: wrap;
align-items: center;
justify-content: center;
margin-top: 20rpx;
color: rgba(255, 255, 255, 0.74);
font-size: 26rpx;
font-weight: 500;
line-height: 44rpx;
text-align: center;
}
.help-link {
margin-top: 72rpx;
color: #ffd947;
font-size: 26rpx;
line-height: 36rpx;
}
.product-link {
position: fixed;
bottom: 104rpx;
bottom: calc(env(safe-area-inset-bottom) + 104rpx);
left: 0;
z-index: 2;
width: 100%;
color: #ffd947;
font-size: 26rpx;
line-height: 36rpx;
}
.highlight-text {
color: #fed847;
}
.device-page {
position: relative;
display: flex;
min-height: calc(100vh - 100rpx);
box-sizing: border-box;
align-items: center;
padding: 8rpx 24rpx 80rpx;
overflow: hidden;
color: #ffffff;
}
.device-visual {
position: absolute;
top: 146rpx;
left: 0;
z-index: 1;
width: 100%;
height: 610rpx;
pointer-events: none;
}
.device-stage {
position: absolute;
top: 214rpx;
left: 50%;
width: 750rpx;
transform: translateX(-50%);
}
.device-bow {
position: absolute;
top: 0;
left: 50%;
width: 562rpx;
transform: translateX(-50%);
}
.device-heading {
position: relative;
z-index: 2;
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
padding-top: 16rpx;
}
.device-status {
display: flex;
align-items: center;
color: rgba(255, 255, 255, 0.9);
font-size: 24rpx;
line-height: 30rpx;
}
.status-dot {
width: 16rpx;
height: 16rpx;
margin-right: 10rpx;
border-radius: 50%;
}
.device-status--online .status-dot {
background: linear-gradient(180deg, #9aff97, #36bb34);
box-shadow: 0 0 16rpx rgba(98, 228, 161, 0.8);
}
.device-status--offline .status-dot {
background: #ff6e6e;
}
.device-title-row {
display: flex;
align-items: center;
margin-top: 536rpx;
height: 52rpx;
line-height: 52rpx;
padding: 0 24rpx;
border-radius: 78rpx 78rpx 78rpx 78rpx;
background: rgba(0,0,0, 0.5);
}
.device-name {
color: #FFD947;
font-size: 28rpx;
font-weight: 500;
line-height: 36rpx;
}
.edit-name {
display: flex;
width: 40rpx;
height: 34rpx;
align-items: center;
justify-content: center;
margin-left: 6rpx;
}
.edit-name image {
width: 40rpx;
height: 34rpx;
}
.device-id-row {
display: flex;
align-items: center;
margin-top: 8rpx;
color: rgba(255, 255, 255, 0.76);
font-size: 24rpx;
line-height: 32rpx;
}
.device-id-toggle {
display: flex;
width: 42rpx;
height: 32rpx;
align-items: center;
justify-content: center;
margin-left: 6rpx;
}
.device-id-eye {
width: 26rpx;
height: 20rpx;
}
.device-stats {
position: relative;
z-index: 2;
display: flex;
width: 704rpx;
align-self: center;
margin-top: 100rpx;
padding: 14rpx 0 18rpx;
}
.device-stat {
display: flex;
width: 168rpx;
box-sizing: border-box;
flex-direction: column;
align-items: center;
flex-shrink: 0;
min-width: 0;
padding: 0 2rpx;
color: #f3e0b9;
}
.device-stat--usage {
width: 200rpx;
}
.device-stat--divider {
border-left: 1rpx solid rgba(255, 255, 255, 0.3);
}
.device-stat-label {
color: rgba(255, 255, 255, 0.7);
font-size: 24rpx;
line-height: 30rpx;
white-space: nowrap;
}
.device-stat-value {
width: auto;
margin-top: 10rpx;
overflow: visible;
color: #f3e0b9;
font-size: 28rpx;
line-height: 36rpx;
letter-spacing: -0.5rpx;
text-align: center;
white-space: nowrap;
}
.device-actions {
position: relative;
z-index: 2;
display: flex;
width: 100%;
justify-content: space-between;
margin-top: 56rpx;
}
.action-item {
display: flex;
width: 20%;
flex-direction: column;
align-items: center;
color: rgba(255, 255, 255, 0.7);
font-size: 22rpx;
line-height: 30rpx;
text-align: center;
}
.action-icon {
display: flex;
width: 88rpx;
height: 88rpx;
align-items: center;
justify-content: center;
margin-bottom: 10rpx;
border: 2rpx solid rgba(243, 224, 185, 0.75);
border-radius: 50%;
color: #f3e0b9;
font-size: 46rpx;
line-height: 1;
}
.action-icon image {
width: 48rpx;
height: 48rpx;
}
.action-label-wrap {
position: relative;
display: flex;
min-height: 30rpx;
align-items: center;
justify-content: center;
}
.action-badge {
position: absolute;
top: -116rpx;
left: calc(50% + 38rpx);
display: flex;
width: 68rpx;
height: 34rpx;
box-sizing: border-box;
align-items: center;
justify-content: center;
padding: 0;
border-radius: 16rpx 4rpx 20rpx 4rpx;
color: #5c421f;
background: linear-gradient(133deg, #ffd19a 0%, #a17636 100%);
font-size: 18rpx;
line-height: 34rpx;
transform: translateX(-50%);
}
.unbind-entry {
position: relative;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
margin-top: 92rpx;
color: #ffd947;
font-size: 28rpx;
line-height: 38rpx;
}
.unbind-icon {
width: 37rpx;
height: 30rpx;
margin-right: 10rpx;
}
.calibration-tip {
display: flex;
width: 100%;
box-sizing: border-box;
align-items: center;
justify-content: space-between;
margin-top: 24rpx;
padding: 20rpx 24rpx;
border-radius: 16rpx;
background: rgba(40, 127, 255, 0.12);
color: rgba(255, 255, 255, 0.65);
font-size: 22rpx;
line-height: 34rpx;
}
.calibration-tip > text {
flex: 1;
}
.calibration-tip > view {
margin-left: 18rpx;
color: #5bbdff;
white-space: nowrap;
}
.primary-action,
.secondary-action {
display: flex;
min-width: 150rpx;
height: 68rpx;
box-sizing: border-box;
align-items: center;
justify-content: center;
border-radius: 34rpx;
font-size: 24rpx;
}
.primary-action {
color: #111111;
background: #fed847;
}
.primary-action--disabled {
opacity: 0.6;
}
.secondary-action {
color: #ffffff;
background: rgba(255, 255, 255, 0.16);
}
.scan-tips {
display: flex;
width: 90%;
box-sizing: border-box;
flex-direction: column;
margin-top: 20%;
color: #ffffff;
font-size: 24rpx;
}
.scan-tips-title {
color: #fed847;
font-size: 32rpx;
line-height: 42rpx;
}
.scan-tips-subtitle {
margin-top: 14rpx;
color: rgba(255, 255, 255, 0.72);
font-size: 26rpx;
line-height: 36rpx;
}
.scan-tips-qr {
width: 100%;
margin-top: 12rpx;
border-radius: 14rpx;
}
.scan-tips-note {
margin-top: 16rpx;
color: #ffffff;
font-size: 24rpx;
line-height: 36rpx;
}
.scan-tips-contact {
display: flex;
flex-direction: column;
align-items: flex-start;
margin-top: 12rpx;
color: #ffffff;
font-size: 24rpx;
line-height: 36rpx;
}
.scan-tips button {
padding: 0;
color: #39a8ff;
background: transparent;
font-size: 30rpx;
line-height: 40rpx;
}
.confirm-bind {
color: rgba(255, 255, 255, 0.7);
font-size: 26rpx;
line-height: 42rpx;
}
.confirm-actions {
display: flex;
justify-content: space-between;
margin: 24rpx 0;
gap: 16rpx;
}
.confirm-actions > view {
flex: 1;
}
.qr-page {
position: relative;
width: 100%;
height: 100%;
min-height: 0;
box-sizing: border-box;
overflow: hidden;
background: transparent;
}
.qr-corner {
position: absolute;
width: 300rpx;
height: 300rpx;
background: #ffeb00;
}
.qr-corner--top {
top: -220rpx;
right: -170rpx;
transform: rotate(42deg);
}
.qr-corner--bottom {
bottom: -220rpx;
left: -170rpx;
transform: rotate(42deg);
}
.qr-canvas {
position: relative;
z-index: 1;
display: flex;
width: 100%;
height: 100%;
min-height: 0;
box-sizing: border-box;
flex-direction: column;
align-items: center;
padding: 260rpx 62rpx 70rpx;
}
.qr-title {
color: #ffe846;
font-size: 30rpx;
font-weight: 600;
}
.qr-image {
width: 432rpx;
height: 432rpx;
margin-top: 48rpx;
box-sizing: border-box;
background: #ffffff;
}
.qr-device-id {
margin-top: 24rpx;
color: rgba(255, 255, 255, 0.62);
font-size: 24rpx;
}
.qr-save-button {
display: flex;
width: 360rpx;
height: 72rpx;
align-items: center;
justify-content: center;
margin-top: 34rpx;
border: 1rpx solid #e8c840;
border-radius: 36rpx;
color: #ffe846;
font-size: 26rpx;
}
.qr-description,
.qr-note {
width: 100%;
color: rgba(255, 255, 255, 0.6);
font-size: 22rpx;
line-height: 36rpx;
text-align: center;
}
.qr-description {
margin-top: 42rpx;
}
.qr-note {
margin-top: 12rpx;
color: rgba(255, 232, 70, 0.7);
}
.name-mask {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
display: flex;
width: 100vw;
height: 100vh;
box-sizing: border-box;
align-items: flex-end;
background: rgba(0, 0, 0, 0.68);
}
.name-sheet {
width: 100%;
box-sizing: border-box;
padding: 34rpx 32rpx 48rpx;
border-top: 1rpx solid rgba(255, 226, 153, 0.4);
border-radius: 28rpx 28rpx 0 0;
background: linear-gradient(180deg, #57462b, #2c241b);
}
.name-input-row {
width: 100%;
justify-content: space-between;
}
.name-input {
flex: 1;
height: 76rpx;
padding: 0 22rpx;
box-sizing: border-box;
border-radius: 14rpx;
color: #ffffff;
background: rgba(0, 0, 0, 0.22);
font-size: 28rpx;
}
.name-placeholder {
color: rgba(255, 255, 255, 0.45);
}
.name-confirm {
display: flex;
width: 132rpx;
height: 76rpx;
align-items: center;
justify-content: center;
margin-left: 18rpx;
border-radius: 14rpx;
color: #1b160d;
background: #fed847;
font-size: 26rpx;
}
.name-helper {
display: block;
margin-top: 18rpx;
color: rgba(255, 255, 255, 0.6);
font-size: 22rpx;
}
</style>