feat:登录注册完成
This commit is contained in:
91
src/pages/add-device/add-device.scss
Normal file
91
src/pages/add-device/add-device.scss
Normal file
@@ -0,0 +1,91 @@
|
||||
.add-device-page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.add-device-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.add-device-page__body {
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.add-device-page__title {
|
||||
display: block;
|
||||
padding: 0 40rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text;
|
||||
margin-bottom: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add-device-page__desc {
|
||||
display: block;
|
||||
padding: 0 48rpx;
|
||||
font-size: 26rpx;
|
||||
color: #98A2B3;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.add-device-page__footer {
|
||||
padding: 40rpx 16rpx 0;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.add-device-page__btn {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
border-radius: 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 40rpx;
|
||||
background: linear-gradient(98deg, #45d685 0%, #21c25e 100%);
|
||||
box-shadow: 0 8rpx 28rpx rgba(33, 194, 94, 0.28);
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.add-device-page__btn-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.add-device-page__btn-badge {
|
||||
min-width: 44rpx;
|
||||
height: 44rpx;
|
||||
padding: 0 12rpx;
|
||||
background: #ffc940;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3rpx solid #fff;
|
||||
}
|
||||
|
||||
.add-device-page__btn-badge-num {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.add-device-page__later-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 16rpx 0 8rpx;
|
||||
}
|
||||
|
||||
.add-device-page__later {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
}
|
||||
11
src/pages/add-device/add-device.types.ts
Normal file
11
src/pages/add-device/add-device.types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface BindStepItem {
|
||||
id: number
|
||||
label: string
|
||||
}
|
||||
|
||||
export interface BindFeatureItem {
|
||||
id: string
|
||||
title: string
|
||||
desc: string
|
||||
icon: string
|
||||
}
|
||||
219
src/pages/add-device/complete/complete.scss
Normal file
219
src/pages/add-device/complete/complete.scss
Normal file
@@ -0,0 +1,219 @@
|
||||
.complete-page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.complete-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.complete-page__body {
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.complete-page__status {
|
||||
padding: 0 40rpx;
|
||||
margin-bottom: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.complete-page__title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.complete-page__desc {
|
||||
font-size: 26rpx;
|
||||
color: #98a2b3;
|
||||
line-height: 1.65;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.complete-page__device-card {
|
||||
margin: 0 32rpx 24rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.complete-page__thumb {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f0f2f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.complete-page__device-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.complete-page__device-name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.complete-page__device-serial {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.complete-page__bt-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.complete-page__bt-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: $color-primary;
|
||||
}
|
||||
|
||||
.complete-page__bt-text {
|
||||
font-size: 24rpx;
|
||||
color: $color-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.complete-page__form {
|
||||
margin: 0 32rpx 24rpx;
|
||||
padding: 8rpx 24rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.complete-page__field {
|
||||
padding-top: 24rpx;
|
||||
}
|
||||
|
||||
.complete-page__label {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: $color-text;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.complete-page__input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 16rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.complete-page__input {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: $color-text;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.complete-page__edit-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.complete-page__room-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 88rpx;
|
||||
padding: 0 24rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 16rpx;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.complete-page__room-icon {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.complete-page__room-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: $color-text;
|
||||
}
|
||||
|
||||
.complete-page__room-arrow {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.complete-page__notice {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12rpx;
|
||||
margin: 0 32rpx 24rpx;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.complete-page__notice-icon {
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background: #4a9eff;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
.complete-page__notice-text {
|
||||
flex: 1;
|
||||
font-size: 24rpx;
|
||||
color: #98a2b3;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.complete-page__footer {
|
||||
padding: 16rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.complete-page__submit {
|
||||
height: 96rpx;
|
||||
border-radius: 24rpx;
|
||||
background: linear-gradient(98deg, #45d685 0%, #21c25e 100%);
|
||||
box-shadow: 0 8rpx 28rpx rgba(33, 194, 94, 0.28);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.complete-page__submit-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<view class="complete-success-hero">
|
||||
<view class="complete-success-hero__visual">
|
||||
<view class="complete-success-hero__glow" aria-hidden="true" />
|
||||
<image
|
||||
class="complete-success-hero__illus"
|
||||
:src="staticAssets.iconBindComplete"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.complete-success-hero {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.complete-success-hero__visual {
|
||||
position: relative;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.complete-success-hero__glow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(201, 239, 217, 0.4) 0%,
|
||||
rgba(201, 239, 217, 0.15) 45%,
|
||||
transparent 70%
|
||||
);
|
||||
animation: complete-success-glow 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.complete-success-hero__illus {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes complete-success-glow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.08);
|
||||
opacity: 0.88;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/pages/add-device/complete/index.vue
Normal file
112
src/pages/add-device/complete/index.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<view class="complete-page">
|
||||
<app-navbar title="设备配置" />
|
||||
<scroll-view class="complete-page__scroll" scroll-y>
|
||||
<view class="complete-page__body">
|
||||
<bind-steps :steps="BIND_STEPS" :current="currentStep" />
|
||||
<complete-success-hero />
|
||||
<view class="complete-page__status">
|
||||
<text class="complete-page__title">配对成功!</text>
|
||||
<text class="complete-page__desc">请确认设备信息后完成添加</text>
|
||||
</view>
|
||||
|
||||
<view class="complete-page__device-card">
|
||||
<image
|
||||
class="complete-page__thumb"
|
||||
:src="staticAssets.logo"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="complete-page__device-info">
|
||||
<text class="complete-page__device-name">{{ deviceName }}</text>
|
||||
<text class="complete-page__device-serial">
|
||||
设备编号:{{ serialMasked }}
|
||||
</text>
|
||||
<view class="complete-page__bt-status">
|
||||
<view class="complete-page__bt-dot" />
|
||||
<text class="complete-page__bt-text">蓝牙已连接</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="complete-page__form">
|
||||
<view class="complete-page__field">
|
||||
<text class="complete-page__label">设备名称</text>
|
||||
<view class="complete-page__input-wrap">
|
||||
<input
|
||||
v-model="deviceName"
|
||||
class="complete-page__input"
|
||||
type="text"
|
||||
maxlength="32"
|
||||
placeholder="请输入设备名称"
|
||||
/>
|
||||
<image
|
||||
class="complete-page__edit-icon"
|
||||
:src="staticAssets.iconDeviceNameEdit"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="complete-page__field">
|
||||
<text class="complete-page__label">所在房间</text>
|
||||
<view class="complete-page__room-row" @tap="onPickRoom">
|
||||
<image
|
||||
class="complete-page__room-icon"
|
||||
:src="staticAssets.iconHomeSelector"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="complete-page__room-text">{{ roomName }}</text>
|
||||
<image
|
||||
class="complete-page__room-arrow"
|
||||
:src="staticAssets.iconHomeArrow"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="complete-page__notice">
|
||||
<text class="complete-page__notice-icon">i</text>
|
||||
<text class="complete-page__notice-text">
|
||||
设备名称和房间可随时在设备详情中修改。添加后设备将出现在首页设备列表中。
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="complete-page__footer">
|
||||
<view class="complete-page__submit" @tap="onFinishAdd">
|
||||
<text class="complete-page__submit-text">完成添加</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
import BindSteps from '../components/bind-steps/index.vue'
|
||||
import { BIND_STEPS } from '../use-add-device'
|
||||
import CompleteSuccessHero from './components/complete-success-hero/index.vue'
|
||||
import { useCompleteDevice } from './use-complete-device'
|
||||
|
||||
const {
|
||||
currentStep,
|
||||
deviceName,
|
||||
serialMasked,
|
||||
roomName,
|
||||
onPickRoom,
|
||||
onFinishAdd,
|
||||
} = useCompleteDevice()
|
||||
|
||||
onLoad((query) => {
|
||||
if (query?.name) {
|
||||
deviceName.value = decodeURIComponent(String(query.name))
|
||||
}
|
||||
if (query?.serial) {
|
||||
serialMasked.value = decodeURIComponent(String(query.serial))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './complete.scss';
|
||||
</style>
|
||||
42
src/pages/add-device/complete/use-complete-device.ts
Normal file
42
src/pages/add-device/complete/use-complete-device.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
const ROOM_OPTIONS = ['客厅', '主卧', '卧室', '厨房', '卫生间']
|
||||
|
||||
export function useCompleteDevice() {
|
||||
const currentStep = 4
|
||||
const deviceName = ref('Z2 空气净化器')
|
||||
const serialMasked = ref('****A3B2')
|
||||
const roomName = ref('客厅')
|
||||
const roomOptions = ROOM_OPTIONS
|
||||
|
||||
function onPickRoom() {
|
||||
uni.showActionSheet({
|
||||
itemList: roomOptions,
|
||||
success: (res) => {
|
||||
const picked = roomOptions[res.tapIndex]
|
||||
if (picked) roomName.value = picked
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function onFinishAdd() {
|
||||
const name = deviceName.value.trim()
|
||||
if (!name) {
|
||||
uni.showToast({ title: '请输入设备名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '添加成功', icon: 'success' })
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url: '/pages/device/index' })
|
||||
}, 400)
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
deviceName,
|
||||
serialMasked,
|
||||
roomName,
|
||||
onPickRoom,
|
||||
onFinishAdd,
|
||||
}
|
||||
}
|
||||
127
src/pages/add-device/components/bind-bluetooth-hero/index.vue
Normal file
127
src/pages/add-device/components/bind-bluetooth-hero/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<template>
|
||||
<view class="bind-bluetooth-hero">
|
||||
<view class="bind-bluetooth-hero__visual">
|
||||
<view class="bind-bluetooth-hero__glow" aria-hidden="true" />
|
||||
<view class="bind-bluetooth-hero__pulses" aria-hidden="true">
|
||||
<view
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="bind-bluetooth-hero__pulse"
|
||||
:style="{ animationDelay: `${(i - 1) * 0.9}s` }"
|
||||
/>
|
||||
</view>
|
||||
<image
|
||||
class="bind-bluetooth-hero__icon"
|
||||
:src="staticAssets.iconBluetooth"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bind-bluetooth-hero {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 16rpx 0 40rpx;
|
||||
}
|
||||
|
||||
.bind-bluetooth-hero__visual {
|
||||
position: relative;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bind-bluetooth-hero__glow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(201, 239, 217, 0.45) 0%,
|
||||
rgba(201, 239, 217, 0.18) 42%,
|
||||
transparent 72%
|
||||
);
|
||||
animation: bind-bt-glow 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.bind-bluetooth-hero__pulses {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bind-bluetooth-hero__pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid rgba(33, 194, 94, 0.28);
|
||||
box-sizing: border-box;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
animation: bind-bt-pulse 2.7s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
|
||||
}
|
||||
|
||||
.bind-bluetooth-hero__icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
flex-shrink: 0;
|
||||
animation: bind-bt-icon-breathe 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bind-bt-pulse {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
70% {
|
||||
opacity: 0.08;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(2.65);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bind-bt-glow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.08);
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bind-bt-icon-breathe {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/pages/add-device/components/bind-feature-list/index.vue
Normal file
75
src/pages/add-device/components/bind-feature-list/index.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<view class="bind-feature-list">
|
||||
<view
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
class="bind-feature-list__row"
|
||||
:class="{ 'bind-feature-list__row--border': index < items.length - 1 }"
|
||||
>
|
||||
<image
|
||||
class="bind-feature-list__icon"
|
||||
:src="item.icon"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="bind-feature-list__text">
|
||||
<text class="bind-feature-list__title">{{ item.title }}</text>
|
||||
<text class="bind-feature-list__desc">{{ item.desc }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { BindFeatureItem } from '../../add-device.types'
|
||||
|
||||
defineProps<{
|
||||
items: BindFeatureItem[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bind-feature-list {
|
||||
margin: 0 32rpx;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
padding: 8rpx 24rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.bind-feature-list__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
padding: 28rpx 0;
|
||||
}
|
||||
|
||||
.bind-feature-list__row--border {
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.bind-feature-list__icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bind-feature-list__text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bind-feature-list__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bind-feature-list__desc {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
131
src/pages/add-device/components/bind-steps/index.vue
Normal file
131
src/pages/add-device/components/bind-steps/index.vue
Normal file
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<view class="bind-steps">
|
||||
<view
|
||||
v-for="(step, index) in steps"
|
||||
:key="step.id"
|
||||
class="bind-steps__item"
|
||||
>
|
||||
<view class="bind-steps__top">
|
||||
<view
|
||||
v-if="index > 0"
|
||||
class="bind-steps__line bind-steps__line--left"
|
||||
:class="{ 'bind-steps__line--active': current >= step.id - 1 }"
|
||||
/>
|
||||
<view
|
||||
class="bind-steps__dot"
|
||||
:class="{ 'bind-steps__dot--active': step.id <= current }"
|
||||
>
|
||||
<text class="bind-steps__num">{{ step.id }}</text>
|
||||
</view>
|
||||
<view
|
||||
v-if="index < steps.length - 1"
|
||||
class="bind-steps__line bind-steps__line--right"
|
||||
:class="{ 'bind-steps__line--active': current >= step.id }"
|
||||
/>
|
||||
</view>
|
||||
<text
|
||||
class="bind-steps__label"
|
||||
:class="{ 'bind-steps__label--active': step.id === current }"
|
||||
>
|
||||
{{ step.label }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { BindStepItem } from '../../add-device.types'
|
||||
|
||||
defineProps<{
|
||||
steps: BindStepItem[]
|
||||
current: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bind-steps {
|
||||
display: flex;
|
||||
padding: 24rpx 16rpx 32rpx;
|
||||
}
|
||||
|
||||
.bind-steps__item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bind-steps__top {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
height: 48rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.bind-steps__dot {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: #e8e8e8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bind-steps__dot--active {
|
||||
background: $color-primary;
|
||||
}
|
||||
|
||||
.bind-steps__num {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bind-steps__dot--active .bind-steps__num {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.bind-steps__line {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
height: 4rpx;
|
||||
background: #e8e8e8;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.bind-steps__line--left {
|
||||
left: 0;
|
||||
right: 50%;
|
||||
margin-right: 24rpx;
|
||||
}
|
||||
|
||||
.bind-steps__line--right {
|
||||
left: 50%;
|
||||
right: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.bind-steps__line--active {
|
||||
background: $color-primary;
|
||||
}
|
||||
|
||||
.bind-steps__label {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
.bind-steps__label--active {
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
37
src/pages/add-device/index.vue
Normal file
37
src/pages/add-device/index.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<view class="add-device-page">
|
||||
<app-navbar title="添加设备" />
|
||||
<scroll-view class="add-device-page__scroll" scroll-y>
|
||||
<view class="add-device-page__body">
|
||||
<bind-steps :steps="BIND_STEPS" :current="currentStep" />
|
||||
<bind-bluetooth-hero />
|
||||
<text class="add-device-page__title">需要开启蓝牙权限</text>
|
||||
<text class="add-device-page__desc">
|
||||
电子智能家需要使用蓝牙来发现和连接附近的智能设备,实现设备的快速配置和本地控制。
|
||||
</text>
|
||||
<bind-feature-list :items="BIND_FEATURES" />
|
||||
<view class="add-device-page__footer">
|
||||
<view class="add-device-page__btn" @tap="onOpenBluetooth">
|
||||
<text class="add-device-page__btn-text">去开启蓝牙</text>
|
||||
</view>
|
||||
<view class="add-device-page__later-wrap" @tap="onBindLater">
|
||||
<text class="add-device-page__later">稍后绑定</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BindBluetoothHero from './components/bind-bluetooth-hero/index.vue'
|
||||
import BindFeatureList from './components/bind-feature-list/index.vue'
|
||||
import BindSteps from './components/bind-steps/index.vue'
|
||||
import { BIND_FEATURES, BIND_STEPS, useAddDevice } from './use-add-device'
|
||||
|
||||
const { currentStep, onOpenBluetooth, onBindLater } = useAddDevice()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './add-device.scss';
|
||||
</style>
|
||||
132
src/pages/add-device/pair/components/pair-check-list/index.vue
Normal file
132
src/pages/add-device/pair/components/pair-check-list/index.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<view class="pair-check-list">
|
||||
<text class="pair-check-list__title">配对自检</text>
|
||||
<view
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
class="pair-check-list__row"
|
||||
:class="{ 'pair-check-list__row--border': index < items.length - 1 }"
|
||||
>
|
||||
<view
|
||||
class="pair-check-list__status"
|
||||
:class="`pair-check-list__status--${item.status}`"
|
||||
>
|
||||
<text v-if="item.status === 'done'" class="pair-check-list__check">✓</text>
|
||||
</view>
|
||||
<text class="pair-check-list__label">{{ item.title }}</text>
|
||||
<view
|
||||
class="pair-check-list__tag"
|
||||
:class="`pair-check-list__tag--${item.status}`"
|
||||
>
|
||||
<text class="pair-check-list__tag-text">{{ item.tag }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PairCheckItem } from '../../pair.types'
|
||||
|
||||
defineProps<{
|
||||
items: PairCheckItem[]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pair-check-list {
|
||||
margin: 0 32rpx 24rpx;
|
||||
padding: 28rpx 24rpx 8rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.pair-check-list__title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.pair-check-list__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
padding: 24rpx 0;
|
||||
}
|
||||
|
||||
.pair-check-list__row--border {
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.pair-check-list__status {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pair-check-list__status--done {
|
||||
background: $color-primary;
|
||||
}
|
||||
|
||||
.pair-check-list__status--syncing {
|
||||
background: #ff9f43;
|
||||
}
|
||||
|
||||
.pair-check-list__status--pending {
|
||||
background: #e8eaed;
|
||||
}
|
||||
|
||||
.pair-check-list__check {
|
||||
font-size: 22rpx;
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.pair-check-list__label {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: $color-text;
|
||||
}
|
||||
|
||||
.pair-check-list__tag {
|
||||
padding: 8rpx 20rpx;
|
||||
border-radius: 999rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--done {
|
||||
background: #effbf4;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--syncing {
|
||||
background: #fff4e8;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--pending {
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.pair-check-list__tag-text {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--done .pair-check-list__tag-text {
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--syncing .pair-check-list__tag-text {
|
||||
color: #e67a2e;
|
||||
}
|
||||
|
||||
.pair-check-list__tag--pending .pair-check-list__tag-text {
|
||||
color: #98a2b3;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<view class="pair-device-summary">
|
||||
<image class="pair-device-summary__thumb" :src="thumbSrc" mode="aspectFill" />
|
||||
<view class="pair-device-summary__info">
|
||||
<text class="pair-device-summary__name">{{ device.name }}</text>
|
||||
<text class="pair-device-summary__serial">设备编号:{{ device.serialMasked }}</text>
|
||||
</view>
|
||||
<view class="pair-device-summary__status">
|
||||
<view class="pair-device-summary__status-dot" />
|
||||
<text class="pair-device-summary__status-text">连接中</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
import type { PairDeviceInfo } from '../../pair.types'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
device: PairDeviceInfo
|
||||
thumbSrc?: string
|
||||
}>(),
|
||||
{ thumbSrc: staticAssets.logo },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pair-device-summary {
|
||||
margin: 0 32rpx;
|
||||
padding: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.pair-device-summary__thumb {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f0f2f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pair-device-summary__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.pair-device-summary__name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.pair-device-summary__serial {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.pair-device-summary__status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 10rpx 16rpx;
|
||||
background: #effbf4;
|
||||
border-radius: 999rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.pair-device-summary__status-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid $color-primary;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pair-device-summary__status-text {
|
||||
font-size: 24rpx;
|
||||
color: $color-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
123
src/pages/add-device/pair/components/pair-hero/index.vue
Normal file
123
src/pages/add-device/pair/components/pair-hero/index.vue
Normal file
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<view class="pair-hero">
|
||||
<view class="pair-hero__visual">
|
||||
<view class="pair-hero__glow" aria-hidden="true" />
|
||||
<view class="pair-hero__pulses" aria-hidden="true">
|
||||
<view
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="pair-hero__pulse"
|
||||
:style="{ animationDelay: `${(i - 1) * 0.9}s` }"
|
||||
/>
|
||||
</view>
|
||||
<image class="pair-hero__avatar" :src="thumbSrc" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
thumbSrc?: string
|
||||
}>(),
|
||||
{ thumbSrc: staticAssets.logo },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.pair-hero {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8rpx 0 24rpx;
|
||||
}
|
||||
|
||||
.pair-hero__visual {
|
||||
position: relative;
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pair-hero__glow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 220rpx;
|
||||
height: 220rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(201, 239, 217, 0.45) 0%,
|
||||
rgba(201, 239, 217, 0.18) 42%,
|
||||
transparent 72%
|
||||
);
|
||||
animation: pair-hero-glow 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.pair-hero__pulses {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.pair-hero__pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid rgba(33, 194, 94, 0.28);
|
||||
box-sizing: border-box;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
animation: pair-hero-pulse 2.7s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
|
||||
}
|
||||
|
||||
.pair-hero__avatar {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid #fff;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||
flex-shrink: 0;
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
@keyframes pair-hero-pulse {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
70% {
|
||||
opacity: 0.08;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(2.65);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pair-hero-glow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.08);
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
65
src/pages/add-device/pair/index.vue
Normal file
65
src/pages/add-device/pair/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<view class="pair-page">
|
||||
<app-navbar title="设备配置" />
|
||||
<scroll-view class="pair-page__scroll" scroll-y>
|
||||
<view class="pair-page__body">
|
||||
<bind-steps :steps="BIND_STEPS" :current="currentStep" />
|
||||
<pair-hero />
|
||||
<view class="pair-page__status">
|
||||
<text class="pair-page__title">配对中...</text>
|
||||
<text class="pair-page__desc">正在与设备建立安全连接,请保持手机靠近</text>
|
||||
</view>
|
||||
|
||||
<view class="pair-page__progress-block">
|
||||
<view class="pair-page__progress-head">
|
||||
<text class="pair-page__progress-label">正在同步设备配置...</text>
|
||||
<text class="pair-page__progress-percent">{{ progress }}%</text>
|
||||
</view>
|
||||
<view class="pair-page__progress-track">
|
||||
<view
|
||||
class="pair-page__progress-fill"
|
||||
:style="{ width: `${progress}%` }"
|
||||
/>
|
||||
</view>
|
||||
<text class="pair-page__progress-tip">
|
||||
{{ progress >= 100 ? '配置同步完成' : `预计还需约 ${etaSeconds} 秒,请勿离开当前页面` }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<pair-check-list :items="checkItems" />
|
||||
<pair-device-summary :device="device" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="pair-page__footer">
|
||||
<view class="pair-page__cancel" @tap="onCancelPair">
|
||||
<text class="pair-page__cancel-text">取消配对</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import BindSteps from '../components/bind-steps/index.vue'
|
||||
import { BIND_STEPS } from '../use-add-device'
|
||||
import PairCheckList from './components/pair-check-list/index.vue'
|
||||
import PairDeviceSummary from './components/pair-device-summary/index.vue'
|
||||
import PairHero from './components/pair-hero/index.vue'
|
||||
import { usePairDevice } from './use-pair-device'
|
||||
|
||||
const { currentStep, device, progress, etaSeconds, checkItems, onCancelPair } =
|
||||
usePairDevice()
|
||||
|
||||
onLoad((query) => {
|
||||
if (query?.name) {
|
||||
device.value.name = decodeURIComponent(String(query.name))
|
||||
}
|
||||
if (query?.serial) {
|
||||
device.value.serialMasked = decodeURIComponent(String(query.serial))
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './pair.scss';
|
||||
</style>
|
||||
109
src/pages/add-device/pair/pair.scss
Normal file
109
src/pages/add-device/pair/pair.scss
Normal file
@@ -0,0 +1,109 @@
|
||||
.pair-page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.pair-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.pair-page__body {
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.pair-page__status {
|
||||
padding: 0 40rpx;
|
||||
margin-bottom: 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pair-page__title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text;
|
||||
margin-bottom: 16rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pair-page__desc {
|
||||
font-size: 26rpx;
|
||||
color: #98a2b3;
|
||||
line-height: 1.65;
|
||||
text-align: center;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.pair-page__progress-block {
|
||||
margin: 0 32rpx 24rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.pair-page__progress-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.pair-page__progress-label {
|
||||
font-size: 28rpx;
|
||||
color: $color-text;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.pair-page__progress-percent {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.pair-page__progress-track {
|
||||
height: 16rpx;
|
||||
border-radius: 999rpx;
|
||||
background: #eef1f4;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.pair-page__progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 999rpx;
|
||||
background: linear-gradient(90deg, #45d685 0%, #21c25e 100%);
|
||||
transition: width 0.4s ease;
|
||||
}
|
||||
|
||||
.pair-page__progress-tip {
|
||||
font-size: 24rpx;
|
||||
color: #98a2b3;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.pair-page__footer {
|
||||
padding: 16rpx 32rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
background: $color-bg-page;
|
||||
}
|
||||
|
||||
.pair-page__cancel {
|
||||
height: 96rpx;
|
||||
border-radius: 24rpx;
|
||||
background: #fff;
|
||||
border: 2rpx solid #e8eaed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.pair-page__cancel-text {
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
13
src/pages/add-device/pair/pair.types.ts
Normal file
13
src/pages/add-device/pair/pair.types.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export type PairCheckStatus = 'done' | 'syncing' | 'pending'
|
||||
|
||||
export interface PairCheckItem {
|
||||
id: string
|
||||
title: string
|
||||
status: PairCheckStatus
|
||||
tag: string
|
||||
}
|
||||
|
||||
export interface PairDeviceInfo {
|
||||
name: string
|
||||
serialMasked: string
|
||||
}
|
||||
81
src/pages/add-device/pair/use-pair-device.ts
Normal file
81
src/pages/add-device/pair/use-pair-device.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
import type { PairCheckItem, PairDeviceInfo } from './pair.types'
|
||||
|
||||
const DEFAULT_DEVICE: PairDeviceInfo = {
|
||||
name: 'Z2 空气净化器',
|
||||
serialMasked: '****A3B2',
|
||||
}
|
||||
|
||||
export function usePairDevice(initial?: Partial<PairDeviceInfo>) {
|
||||
const currentStep = 3
|
||||
const device = ref<PairDeviceInfo>({
|
||||
...DEFAULT_DEVICE,
|
||||
...initial,
|
||||
})
|
||||
|
||||
const progress = ref(65)
|
||||
const etaSeconds = ref(15)
|
||||
const checkItems = ref<PairCheckItem[]>([
|
||||
{ id: 'signal', title: '蓝牙信号强度', status: 'done', tag: '良好' },
|
||||
{ id: 'credential', title: '设备凭证校验', status: 'done', tag: '通过' },
|
||||
{ id: 'network', title: '网络配置下发', status: 'syncing', tag: '同步中' },
|
||||
])
|
||||
|
||||
let etaTick = 0
|
||||
const tick = setInterval(() => {
|
||||
if (progress.value < 100) {
|
||||
progress.value = Math.min(100, progress.value + 1)
|
||||
}
|
||||
etaTick += 1
|
||||
if (etaTick % 2 === 0 && etaSeconds.value > 0 && progress.value < 100) {
|
||||
etaSeconds.value = Math.max(0, etaSeconds.value - 1)
|
||||
}
|
||||
if (progress.value >= 100) {
|
||||
checkItems.value = checkItems.value.map((item) =>
|
||||
item.id === 'network'
|
||||
? { ...item, status: 'done', tag: '完成' }
|
||||
: item,
|
||||
)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
let navigated = false
|
||||
|
||||
watch(progress, (value) => {
|
||||
if (value < 100 || navigated) return
|
||||
navigated = true
|
||||
const d = device.value
|
||||
const q = [
|
||||
`name=${encodeURIComponent(d.name)}`,
|
||||
`serial=${encodeURIComponent(d.serialMasked)}`,
|
||||
].join('&')
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: `/pages/add-device/complete/index?${q}` })
|
||||
}, 600)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(tick)
|
||||
})
|
||||
|
||||
function onCancelPair() {
|
||||
uni.showModal({
|
||||
title: '取消配对',
|
||||
content: '确定要取消当前设备的配对吗?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.navigateBack()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
device,
|
||||
progress,
|
||||
etaSeconds,
|
||||
checkItems,
|
||||
onCancelPair,
|
||||
}
|
||||
}
|
||||
102
src/pages/add-device/scan/components/scan-device-item/index.vue
Normal file
102
src/pages/add-device/scan/components/scan-device-item/index.vue
Normal file
@@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<view
|
||||
class="scan-device-item"
|
||||
:class="{
|
||||
'scan-device-item--selected': selected,
|
||||
'scan-device-item--disabled': !device.canAdd,
|
||||
}"
|
||||
@tap="emit('select', device.id)"
|
||||
>
|
||||
<image
|
||||
class="scan-device-item__thumb"
|
||||
:src="thumbSrc"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
<view class="scan-device-item__info">
|
||||
<text class="scan-device-item__name">{{ device.name }}</text>
|
||||
<text class="scan-device-item__serial">设备编号:{{ device.serialMasked }}</text>
|
||||
</view>
|
||||
<image
|
||||
class="scan-device-item__action"
|
||||
:src="selected ? staticAssets.iconScanDeviceAction : staticAssets.iconScanDeviceActionDisabled"
|
||||
mode="aspectFit"
|
||||
@tap.stop="emit('action', device)"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
import type { ScannedDeviceItem } from '../../scan.types'
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
device: ScannedDeviceItem
|
||||
selected: boolean
|
||||
thumbSrc?: string
|
||||
}>(),
|
||||
{ thumbSrc: staticAssets.logo },
|
||||
)
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [id: string]
|
||||
action: [device: ScannedDeviceItem]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scan-device-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid transparent;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-device-item--selected {
|
||||
border-color: $color-primary;
|
||||
box-shadow: 0 4rpx 24rpx rgba(30, 198, 113, 0.12);
|
||||
}
|
||||
|
||||
.scan-device-item--disabled {
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.scan-device-item__thumb {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #f0f2f5;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scan-device-item__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.scan-device-item__name {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.scan-device-item__serial {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.scan-device-item__action {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
76
src/pages/add-device/scan/components/scan-help-tip/index.vue
Normal file
76
src/pages/add-device/scan/components/scan-help-tip/index.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<view class="scan-help-tip">
|
||||
<view class="scan-help-tip__icon-wrap">
|
||||
<view class="scan-help-tip__bulb" />
|
||||
</view>
|
||||
<view class="scan-help-tip__content">
|
||||
<text class="scan-help-tip__title">扫描不到设备?</text>
|
||||
<text class="scan-help-tip__line">1. 确认目标设备已开机</text>
|
||||
<text class="scan-help-tip__line">2. 确保设备处于配对/可发现模式</text>
|
||||
<text class="scan-help-tip__line">3. 检查手机蓝牙已开启</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scan-help-tip {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin: 8rpx 32rpx 0;
|
||||
padding: 28rpx 24rpx;
|
||||
background: #fff8f0;
|
||||
border-radius: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-help-tip__icon-wrap {
|
||||
flex-shrink: 0;
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding-top: 4rpx;
|
||||
}
|
||||
|
||||
.scan-help-tip__bulb {
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 50% 50% 45% 45%;
|
||||
background: #ffc940;
|
||||
position: relative;
|
||||
box-shadow: 0 0 0 6rpx rgba(255, 201, 64, 0.25);
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -8rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 20rpx;
|
||||
height: 8rpx;
|
||||
background: #e8a82a;
|
||||
border-radius: 0 0 4rpx 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.scan-help-tip__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.scan-help-tip__title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #e67a2e;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.scan-help-tip__line {
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
color: #c4845a;
|
||||
line-height: 1.55;
|
||||
}
|
||||
</style>
|
||||
116
src/pages/add-device/scan/components/scan-hero/index.vue
Normal file
116
src/pages/add-device/scan/components/scan-hero/index.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<view class="scan-hero">
|
||||
<view class="scan-hero__visual">
|
||||
<view class="scan-hero__glow" aria-hidden="true" />
|
||||
<view class="scan-hero__pulses" aria-hidden="true">
|
||||
<view
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="scan-hero__pulse"
|
||||
:style="{ animationDelay: `${(i - 1) * 0.9}s` }"
|
||||
/>
|
||||
</view>
|
||||
<image
|
||||
class="scan-hero__illus"
|
||||
:src="staticAssets.iconScanDevice"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.scan-hero {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 8rpx 0 28rpx;
|
||||
}
|
||||
|
||||
.scan-hero__visual {
|
||||
position: relative;
|
||||
width: 320rpx;
|
||||
height: 320rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.scan-hero__glow {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 240rpx;
|
||||
height: 240rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
background: radial-gradient(
|
||||
circle,
|
||||
rgba(201, 239, 217, 0.35) 0%,
|
||||
rgba(201, 239, 217, 0.12) 45%,
|
||||
transparent 70%
|
||||
);
|
||||
animation: scan-hero-glow 2.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.scan-hero__pulses {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.scan-hero__pulse {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
border-radius: 50%;
|
||||
border: 3rpx solid rgba(33, 194, 94, 0.28);
|
||||
box-sizing: border-box;
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
animation: scan-hero-pulse 2.7s cubic-bezier(0.22, 0.61, 0.36, 1) infinite;
|
||||
}
|
||||
|
||||
.scan-hero__illus {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 240rpx;
|
||||
height: 280rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes scan-hero-pulse {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
70% {
|
||||
opacity: 0.08;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(2.65);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scan-hero-glow {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: translate(-50%, -50%) scale(1.08);
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
src/pages/add-device/scan/index.vue
Normal file
70
src/pages/add-device/scan/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<view class="scan-page">
|
||||
<app-navbar title="扫描设备" />
|
||||
<scroll-view class="scan-page__scroll" scroll-y>
|
||||
<view class="scan-page__body">
|
||||
<bind-steps :steps="BIND_STEPS" :current="currentStep" />
|
||||
<scan-hero />
|
||||
<view class="scan-page__status">
|
||||
<text class="scan-page__title">正在扫描附近设备...</text>
|
||||
<view class="scan-page__hint">
|
||||
<view class="scan-page__hint-dot" />
|
||||
<text class="scan-page__hint-text">{{ statusHint }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scan-page__section-head">
|
||||
<text class="scan-page__section-title">发现的设备</text>
|
||||
<text class="scan-page__section-timer">
|
||||
{{ scanning ? `${secondsLeft}秒后停止` : '扫描已停止' }}
|
||||
</text>
|
||||
</view>
|
||||
|
||||
<view class="scan-page__list">
|
||||
<scan-device-item
|
||||
v-for="item in devices"
|
||||
:key="item.id"
|
||||
:device="item"
|
||||
:selected="selectedId === item.id"
|
||||
@select="onSelectDevice"
|
||||
@action="onDeviceAction"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<scan-help-tip />
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import BindSteps from '../components/bind-steps/index.vue'
|
||||
import { BIND_STEPS } from '../use-add-device'
|
||||
import ScanDeviceItem from './components/scan-device-item/index.vue'
|
||||
import ScanHelpTip from './components/scan-help-tip/index.vue'
|
||||
import ScanHero from './components/scan-hero/index.vue'
|
||||
import { useScanDevice } from './use-scan-device'
|
||||
|
||||
const {
|
||||
currentStep,
|
||||
devices,
|
||||
selectedId,
|
||||
secondsLeft,
|
||||
scanning,
|
||||
onSelectDevice,
|
||||
onDeviceAction,
|
||||
} = useScanDevice()
|
||||
|
||||
const statusHint = computed(() => {
|
||||
const count = devices.value.length
|
||||
if (scanning.value) {
|
||||
return `已发现 ${count} 台设备,继续扫描中`
|
||||
}
|
||||
return `已发现 ${count} 台设备,扫描已停止`
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './scan.scss';
|
||||
</style>
|
||||
78
src/pages/add-device/scan/scan.scss
Normal file
78
src/pages/add-device/scan/scan.scss
Normal file
@@ -0,0 +1,78 @@
|
||||
.scan-page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg-page;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.scan-page__scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.scan-page__body {
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.scan-page__status {
|
||||
padding: 0 40rpx;
|
||||
margin-bottom: 36rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scan-page__title {
|
||||
display: block;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: $color-text;
|
||||
margin-bottom: 16rpx;
|
||||
line-height: 1.4;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scan-page__hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.scan-page__hint-dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #4a9eff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.scan-page__hint-text {
|
||||
font-size: 26rpx;
|
||||
color: #98a2b3;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scan-page__section-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 32rpx 16rpx;
|
||||
}
|
||||
|
||||
.scan-page__section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
}
|
||||
|
||||
.scan-page__section-timer {
|
||||
font-size: 24rpx;
|
||||
color: #98a2b3;
|
||||
}
|
||||
|
||||
.scan-page__list {
|
||||
padding: 0 32rpx;
|
||||
}
|
||||
7
src/pages/add-device/scan/scan.types.ts
Normal file
7
src/pages/add-device/scan/scan.types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface ScannedDeviceItem {
|
||||
id: string
|
||||
name: string
|
||||
serialMasked: string
|
||||
/** 是否可添加(未知设备为 false 时箭头置灰) */
|
||||
canAdd: boolean
|
||||
}
|
||||
69
src/pages/add-device/scan/use-scan-device.ts
Normal file
69
src/pages/add-device/scan/use-scan-device.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { onUnmounted, ref } from 'vue'
|
||||
import type { ScannedDeviceItem } from './scan.types'
|
||||
|
||||
const MOCK_DEVICES: ScannedDeviceItem[] = [
|
||||
{
|
||||
id: 'z2-purifier',
|
||||
name: 'Z2 空气净化器',
|
||||
serialMasked: '****A3B2',
|
||||
canAdd: true,
|
||||
},
|
||||
{
|
||||
id: 'unknown',
|
||||
name: '未知设备',
|
||||
serialMasked: '****7C91',
|
||||
canAdd: false,
|
||||
},
|
||||
]
|
||||
|
||||
const SCAN_SECONDS = 20
|
||||
|
||||
export function useScanDevice() {
|
||||
const currentStep = 2
|
||||
const devices = ref<ScannedDeviceItem[]>(MOCK_DEVICES)
|
||||
const selectedId = ref(MOCK_DEVICES[0]?.id ?? '')
|
||||
const secondsLeft = ref(SCAN_SECONDS)
|
||||
const scanning = ref(true)
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (secondsLeft.value <= 0) {
|
||||
scanning.value = false
|
||||
return
|
||||
}
|
||||
secondsLeft.value -= 1
|
||||
if (secondsLeft.value === 0) {
|
||||
scanning.value = false
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
function onSelectDevice(id: string) {
|
||||
selectedId.value = id
|
||||
}
|
||||
|
||||
function onDeviceAction(device: ScannedDeviceItem) {
|
||||
selectedId.value = device.id
|
||||
if (!device.canAdd) {
|
||||
uni.showToast({ title: '暂无法添加该设备', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const q = [
|
||||
`name=${encodeURIComponent(device.name)}`,
|
||||
`serial=${encodeURIComponent(device.serialMasked)}`,
|
||||
].join('&')
|
||||
uni.navigateTo({ url: `/pages/add-device/pair/index?${q}` })
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
devices,
|
||||
selectedId,
|
||||
secondsLeft,
|
||||
scanning,
|
||||
onSelectDevice,
|
||||
onDeviceAction,
|
||||
}
|
||||
}
|
||||
48
src/pages/add-device/use-add-device.ts
Normal file
48
src/pages/add-device/use-add-device.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { staticAssets } from '@/utils/static-assets'
|
||||
import type { BindFeatureItem, BindStepItem } from './add-device.types'
|
||||
|
||||
export const BIND_STEPS: BindStepItem[] = [
|
||||
{ id: 1, label: '开启蓝牙' },
|
||||
{ id: 2, label: '扫描设备' },
|
||||
{ id: 3, label: '配对连接' },
|
||||
{ id: 4, label: '完成配置' },
|
||||
]
|
||||
|
||||
export const BIND_FEATURES: BindFeatureItem[] = [
|
||||
{
|
||||
id: 'discover',
|
||||
title: '发现附近设备',
|
||||
desc: '通过蓝牙扫描发现您周围的电子智能设备',
|
||||
icon: staticAssets.iconBindDiscover,
|
||||
},
|
||||
{
|
||||
id: 'pair',
|
||||
title: '快速配对连接',
|
||||
desc: '一键完成设备配对,无需复杂的网络配置',
|
||||
icon: staticAssets.iconBindPair,
|
||||
},
|
||||
{
|
||||
id: 'secure',
|
||||
title: '本地安全控制',
|
||||
desc: '蓝牙直连控制,数据加密传输,保护您的隐私',
|
||||
icon: staticAssets.iconBindSecure,
|
||||
},
|
||||
]
|
||||
|
||||
export function useAddDevice() {
|
||||
const currentStep = 1
|
||||
|
||||
function onOpenBluetooth() {
|
||||
uni.navigateTo({ url: '/pages/add-device/scan/index' })
|
||||
}
|
||||
|
||||
function onBindLater() {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
onOpenBluetooth,
|
||||
onBindLater,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user