add banbanmini
This commit is contained in:
3
banban-mini/src/pages/device/index.config.ts
Normal file
3
banban-mini/src/pages/device/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export default definePageConfig({
|
||||
navigationStyle: 'custom'
|
||||
})
|
||||
291
banban-mini/src/pages/device/index.scss
Normal file
291
banban-mini/src/pages/device/index.scss
Normal file
@@ -0,0 +1,291 @@
|
||||
.device-page {
|
||||
min-height: 100%;
|
||||
background: #F5F7FA;
|
||||
padding-bottom: 160px;
|
||||
padding-bottom: calc(160px + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(160px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.page-header {
|
||||
padding: 80px 32px 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 28px;
|
||||
color: #666666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.status-card {
|
||||
background: linear-gradient(135deg, #FF8C42 0%, #FF6B35 100%);
|
||||
border-radius: 28px;
|
||||
padding: 40px 36px;
|
||||
margin: 0 24px 32px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
border-radius: 20px;
|
||||
padding: 10px 20px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.status-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: #4CD964;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 24px;
|
||||
color: #FFFFFF;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.battery-section {
|
||||
.battery-percent {
|
||||
font-size: 72px;
|
||||
font-weight: 700;
|
||||
color: #FFFFFF;
|
||||
line-height: 1;
|
||||
margin-bottom: 12px;
|
||||
display: block;
|
||||
|
||||
.percent {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.battery-label {
|
||||
font-size: 26px;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.device-avatar {
|
||||
position: absolute;
|
||||
right: 36px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.robot-icon {
|
||||
.robot-img {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1A1A1A;
|
||||
margin: 0 24px 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.control-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 16px;
|
||||
margin: 0 24px 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.control-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24px;
|
||||
|
||||
&.volume-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
padding: 20px 24px 24px;
|
||||
}
|
||||
|
||||
.volume-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.control-left {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.control-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
|
||||
.icon-bg {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
|
||||
&.purple {
|
||||
background: #F0E6FF;
|
||||
}
|
||||
|
||||
&.orange {
|
||||
background: #FFF0E6;
|
||||
}
|
||||
|
||||
.control-icon-img {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.control-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.control-name {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.control-detail {
|
||||
font-size: 26px;
|
||||
color: #FF8C42;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.volume-slider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
|
||||
.volume-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
flex: 1;
|
||||
margin: 0 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: #F0F0F0;
|
||||
margin: 0 24px;
|
||||
}
|
||||
|
||||
.quick-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 24px;
|
||||
margin: 0 24px 24px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quick-grid {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.quick-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-icon {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
border-radius: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 40px;
|
||||
margin-bottom: 12px;
|
||||
|
||||
&.bg-orange {
|
||||
background: #FEF3E8;
|
||||
}
|
||||
|
||||
&.bg-green {
|
||||
background: #E8F8F0;
|
||||
}
|
||||
|
||||
&.bg-blue {
|
||||
background: #E8F0FE;
|
||||
}
|
||||
}
|
||||
|
||||
.quick-text {
|
||||
font-size: 26px;
|
||||
color: #666666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.loading {
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
text {
|
||||
font-size: 32px;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
174
banban-mini/src/pages/device/index.tsx
Normal file
174
banban-mini/src/pages/device/index.tsx
Normal file
@@ -0,0 +1,174 @@
|
||||
import { View, Text, Switch, Slider, Image } from '@tarojs/components'
|
||||
import { useState, useEffect } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import './index.scss'
|
||||
|
||||
interface DeviceData {
|
||||
id: string
|
||||
name: string
|
||||
status: 'online' | 'offline'
|
||||
battery: number
|
||||
daysLeft: number
|
||||
sleepEnabled: boolean
|
||||
sleepTime: string
|
||||
volume: number
|
||||
}
|
||||
|
||||
export default function Device() {
|
||||
const [device, setDevice] = useState<DeviceData | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [hasDevice, setHasDevice] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// 检查登录状态
|
||||
const isLoggedIn = Taro.getStorageSync('isLoggedIn')
|
||||
if (!isLoggedIn) {
|
||||
Taro.redirectTo({ url: '/pages/login/index' })
|
||||
return
|
||||
}
|
||||
|
||||
// 检查设备绑定状态
|
||||
const deviceInfo = Taro.getStorageSync('deviceInfo')
|
||||
const hasDeviceFlag = Taro.getStorageSync('hasDevice')
|
||||
|
||||
setHasDevice(!!hasDeviceFlag)
|
||||
|
||||
if (deviceInfo && hasDeviceFlag) {
|
||||
setDevice({
|
||||
id: deviceInfo.id,
|
||||
name: deviceInfo.name || '小夏的伴伴',
|
||||
status: deviceInfo.status || 'online',
|
||||
battery: deviceInfo.battery || 82,
|
||||
daysLeft: 4,
|
||||
sleepEnabled: true,
|
||||
sleepTime: '每天 22:00 - 07:00',
|
||||
volume: 50
|
||||
})
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
const handleSleepToggle = (e: any) => {
|
||||
if (!device) return
|
||||
setDevice({ ...device, sleepEnabled: e.detail.value })
|
||||
Taro.showToast({
|
||||
title: e.detail.value ? '已开启定时休眠' : '已关闭定时休眠',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
|
||||
const handleVolumeChange = (e: any) => {
|
||||
if (!device) return
|
||||
setDevice({ ...device, volume: e.detail.value })
|
||||
}
|
||||
|
||||
const switchTab = (url: string) => {
|
||||
Taro.switchTab({ url })
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='loading'>
|
||||
<Text>加载中...</Text>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
// 未绑定设备,跳转到绑定页面
|
||||
if (!hasDevice || !device) {
|
||||
Taro.redirectTo({ url: '/pages/bind/index' })
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View className='device-page'>
|
||||
<View className='page-header'>
|
||||
<Text className='page-title'>{device.name}</Text>
|
||||
<Text className='page-subtitle'>设备运行状态良好</Text>
|
||||
</View>
|
||||
|
||||
<View className='status-card'>
|
||||
<View className='status-badge'>
|
||||
<View className='status-dot'></View>
|
||||
<Text className='status-text'>4G 在线</Text>
|
||||
</View>
|
||||
<View className='battery-section'>
|
||||
<Text className='battery-percent'>{device.battery}<Text className='percent'>%</Text></Text>
|
||||
<Text className='battery-label'>剩余电量 (预计续航 {device.daysLeft} 天)</Text>
|
||||
</View>
|
||||
<View className='device-avatar'>
|
||||
<View className='robot-icon'>
|
||||
<Image className='robot-img' src={require('../../assets/tab-icons/robot.png')} mode='aspectFit' />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='section-title'>基础控制</View>
|
||||
<View className='control-card'>
|
||||
<View className='control-item'>
|
||||
<View className='control-left'>
|
||||
<View className='icon-bg purple'>
|
||||
<Image
|
||||
className='control-icon-img'
|
||||
src={require('../../assets/tab-icons/moon.png')}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
</View>
|
||||
<View className='control-info'>
|
||||
<Text className='control-name'>定时休眠</Text>
|
||||
<Text className='control-detail'>{device.sleepTime}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Switch
|
||||
checked={device.sleepEnabled}
|
||||
onChange={handleSleepToggle}
|
||||
color='#FF8C42'
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View className='divider'></View>
|
||||
|
||||
<View className='control-item volume-item'>
|
||||
<View className='volume-header'>
|
||||
<View className='control-left'>
|
||||
<View className='icon-bg orange'>
|
||||
<Image
|
||||
className='control-icon-img'
|
||||
src={require('../../assets/tab-icons/orange-speaker.png')}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
</View>
|
||||
<Text className='control-name'>外放音量</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View className='volume-slider'>
|
||||
<Image
|
||||
className='volume-icon'
|
||||
src={require('../../assets/tab-icons/unspeaker.png')}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
<Slider
|
||||
className='slider'
|
||||
min={0}
|
||||
max={100}
|
||||
value={device.volume}
|
||||
onChange={handleVolumeChange}
|
||||
activeColor='#FF8C42'
|
||||
backgroundColor='#E5E5E5'
|
||||
blockColor='#FF8C42'
|
||||
/>
|
||||
<Image
|
||||
className='volume-icon'
|
||||
src={require('../../assets/tab-icons/orange-speaker.png')}
|
||||
mode='aspectFit'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user