832 lines
20 KiB
Plaintext
832 lines
20 KiB
Plaintext
<template>
|
||
<view class="device-container">
|
||
<!-- 头部导航 -->
|
||
<view class="header">
|
||
<text class="title">设备管理</text>
|
||
<view class="header-actions">
|
||
<button class="scan-btn" @click="scanDevices">扫描设备</button>
|
||
<button class="add-btn" @click="addDevice">添加设备</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 扫描状态 -->
|
||
<view class="scan-status" v-if="isScanning">
|
||
<view class="scan-indicator">
|
||
<text class="scan-text">正在扫描设备...</text>
|
||
<view class="scan-animation"></view>
|
||
</view>
|
||
</view>
|
||
<!-- 已绑定设备列表 -->
|
||
<view class="section">
|
||
<view class="section-header">
|
||
<text class="section-title">已绑定设备 ({{ boundDevices.length }})</text>
|
||
<button class="refresh-btn" @click="forceRefreshDevices" :disabled="isLoading">
|
||
<text class="refresh-text">{{ isLoading ? '加载中...' : '刷新' }}</text>
|
||
</button>
|
||
</view>
|
||
|
||
<!-- 加载状态 -->
|
||
<view class="loading-status" v-if="isLoading">
|
||
<text class="loading-text">正在加载设备列表...</text>
|
||
</view>
|
||
|
||
<view class="device-list" v-else>
|
||
<view class="device-item" v-for="(device, index) in boundDevices" :key="index"
|
||
@click="selectDevice(device)">
|
||
<view class="device-info">
|
||
<view class="device-header">
|
||
<text class="device-name">{{ device.device_name ?? '未知设备' }}</text>
|
||
<view class="device-status">
|
||
<text class="status-text">{{ getStatusText(device.status ?? 'offline') }}</text>
|
||
</view>
|
||
</view>
|
||
<text class="device-mac">MAC: {{ device.device_mac ?? '--' }}</text>
|
||
<text class="device-type">类型: {{ getDeviceTypeLabel(device.device_type ?? '') }}</text>
|
||
<text class="bind-time">绑定时间: {{ formatTime(device.bind_time ?? '') }}</text>
|
||
</view>
|
||
<view class="device-actions">
|
||
<button class="config-btn" @click.stop="configDevice(device)">配置</button>
|
||
<button class="unbind-btn" @click.stop="unbindDeviceById(device)">解绑</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 发现的设备列表 -->
|
||
<view class="section" v-if="discoveredDevices.length > 0">
|
||
<text class="section-title">发现的设备 ({{ discoveredDevices.length }})</text>
|
||
<view class="device-list">
|
||
<view class="discovered-item" v-for="(device, index) in discoveredDevices" :key="index">
|
||
<view class="device-info">
|
||
<text class="device-name">{{ device.name ?? '未知设备' }}</text>
|
||
<text class="device-mac">MAC: {{ device.mac ?? '--' }}</text>
|
||
<text class="device-rssi">信号强度: {{ device.rssi ?? 0 }} dBm</text>
|
||
</view>
|
||
<button class="bind-btn" @click="bindDevice(device)">绑定</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 设备配置弹窗 -->
|
||
<view class="config-modal" v-if="showConfig" @click="closeConfig">
|
||
<view class="modal-content" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">设备配置</text>
|
||
<button class="close-btn" @click="closeConfig">×</button>
|
||
</view>
|
||
<view class="modal-body">
|
||
<view class="config-form" v-if="currentDevice !== null">
|
||
<view class="form-item">
|
||
<text class="form-label">设备名称</text>
|
||
<input class="form-input" :value="configForm.device_name" @input="onDeviceNameInput"
|
||
placeholder="请输入设备名称" />
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">采样频率</text>
|
||
<view class="picker-view" @click="showSampleRatePicker">
|
||
<text>{{ sampleRates[configForm.sample_rate_index] }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</view>
|
||
<view class="form-item">
|
||
<text class="form-label">数据上传间隔</text>
|
||
<view class="picker-view" @click="showUploadIntervalPicker">
|
||
<text>{{ uploadIntervals[configForm.upload_interval_index] }}</text>
|
||
<text class="picker-arrow">▼</text>
|
||
</view>
|
||
</view>
|
||
<view class="form-item">
|
||
<switch :checked="configForm.auto_sync" @change="onAutoSyncChange" />
|
||
<text class="switch-label">自动同步</text>
|
||
</view>
|
||
</view>
|
||
<view class="form-actions">
|
||
<button class="cancel-btn" @click="closeConfig">取消</button>
|
||
<button class="save-btn" @click="saveConfig">保存</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||
import { state, loadDevices, loadDevicesWithDefault, bindNewDevice, unbindDevice, updateDeviceConfig } from '@/utils/store.uts'
|
||
import { DeviceInfo } from './types.uts'
|
||
|
||
// 响应式数据
|
||
const discoveredDevices = ref<Array<UTSJSONObject>>([])
|
||
const isScanning = ref<boolean>(false)
|
||
const showConfig = ref<boolean>(false)
|
||
const currentDevice = ref<DeviceInfo | null>(null)
|
||
|
||
// 从 state 直接获取设备列表 - 使用计算属性保持响应性
|
||
const boundDevices = computed<Array<DeviceInfo>>(() => state.deviceState.devices)
|
||
const isLoading = computed<boolean>(() => state.deviceState.isLoading) // 配置表单类型定义
|
||
type ConfigForm = {
|
||
device_name : string
|
||
sample_rate_index : number
|
||
upload_interval_index : number
|
||
auto_sync : boolean
|
||
}
|
||
|
||
// 配置表单
|
||
const configForm = ref<ConfigForm>({
|
||
device_name: '',
|
||
sample_rate_index: 0,
|
||
upload_interval_index: 0,
|
||
auto_sync: true
|
||
})
|
||
// 选项数据
|
||
const sampleRates = ['1Hz', '5Hz', '10Hz', '25Hz', '50Hz', '100Hz']
|
||
const uploadIntervals = ['实时', '1分钟', '5分钟', '15分钟', '30分钟', '1小时']
|
||
|
||
// 异步解绑设备函数 - 需要在使用前定义
|
||
async function performUnbind(deviceId : string) {
|
||
try {
|
||
const success = await unbindDevice(deviceId)
|
||
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '设备解绑成功',
|
||
icon: 'success'
|
||
})
|
||
} else {
|
||
uni.showToast({
|
||
title: '设备解绑失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.log('解绑设备失败:', e)
|
||
uni.showToast({
|
||
title: '解绑失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
|
||
async function forceRefreshDevices() {
|
||
try {
|
||
const success = await loadDevices(true)
|
||
if (!success) {
|
||
console.log('强制刷新设备列表失败')
|
||
}
|
||
} catch (e) {
|
||
console.log('强制刷新设备失败:', e)
|
||
}
|
||
}
|
||
|
||
async function scanDevices() {
|
||
isScanning.value = true
|
||
discoveredDevices.value = []
|
||
|
||
try {
|
||
// 模拟蓝牙扫描过程
|
||
await new Promise<void>((resolve) => {
|
||
setTimeout(() => {
|
||
// 模拟发现的设备
|
||
const mockDevices : Array<UTSJSONObject> = []
|
||
|
||
const device1 = new UTSJSONObject()
|
||
device1.set('name', 'Smart Watch Pro')
|
||
device1.set('mac', 'AA:BB:CC:DD:EE:01')
|
||
device1.set('rssi', -45)
|
||
device1.set('type', 'smartwatch')
|
||
mockDevices.push(device1)
|
||
|
||
const device2 = new UTSJSONObject()
|
||
device2.set('name', 'Fitness Band X')
|
||
device2.set('mac', 'AA:BB:CC:DD:EE:02')
|
||
device2.set('rssi', -62)
|
||
device2.set('type', 'fitness_band')
|
||
mockDevices.push(device2)
|
||
|
||
discoveredDevices.value = mockDevices
|
||
resolve()
|
||
}, 3000)
|
||
})
|
||
} catch (e) {
|
||
console.log('扫描设备失败:', e)
|
||
} finally {
|
||
isScanning.value = false
|
||
}
|
||
} async function bindDevice(device : UTSJSONObject) {
|
||
try {
|
||
const deviceData = new UTSJSONObject()
|
||
deviceData.set('device_type', device.getString('type') ?? 'unknown')
|
||
deviceData.set('device_name', device.getString('name') ?? '未知设备')
|
||
deviceData.set('device_mac', device.getString('mac') ?? '')
|
||
|
||
const extra = new UTSJSONObject()
|
||
extra.set('rssi', device.getNumber('rssi') ?? 0)
|
||
extra.set('sample_rate', '10Hz')
|
||
extra.set('upload_interval', '5分钟')
|
||
extra.set('auto_sync', true)
|
||
deviceData.set('extra', extra)
|
||
|
||
const success = await bindNewDevice(deviceData)
|
||
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '设备绑定成功',
|
||
icon: 'success'
|
||
})
|
||
|
||
// 从发现列表中移除
|
||
const mac = device.getString('mac') ?? ''
|
||
discoveredDevices.value = discoveredDevices.value.filter(d =>
|
||
d.getString('mac') !== mac
|
||
)
|
||
} else {
|
||
uni.showToast({
|
||
title: '设备绑定失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.log('绑定设备失败:', e)
|
||
uni.showToast({
|
||
title: '绑定失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
|
||
}
|
||
|
||
async function unbindDeviceById(device : DeviceInfo) {
|
||
const deviceId = device.id
|
||
const deviceName = device.device_name
|
||
|
||
uni.showModal({
|
||
title: '确认解绑',
|
||
content: `确定要解绑设备"${deviceName}"吗?`,
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
// 异步操作需要单独处理
|
||
performUnbind(deviceId)
|
||
}
|
||
}
|
||
})
|
||
|
||
}
|
||
function configDevice(device : DeviceInfo) {
|
||
currentDevice.value = device
|
||
|
||
// 初始化配置表单 - 使用 configForm.value 访问
|
||
const form = configForm.value
|
||
form.device_name = device.device_name ?? ''
|
||
|
||
const extra = device.extra
|
||
if (extra !== null) {
|
||
const sampleRate = extra.getString('sample_rate') ?? '10Hz'
|
||
form.sample_rate_index = sampleRates.indexOf(sampleRate)
|
||
if (form.sample_rate_index === -1) {
|
||
form.sample_rate_index = 2 // 默认10Hz
|
||
}
|
||
|
||
const uploadInterval = extra.getString('upload_interval') ?? '5分钟'
|
||
form.upload_interval_index = uploadIntervals.indexOf(uploadInterval)
|
||
if (form.upload_interval_index === -1) {
|
||
form.upload_interval_index = 2 // 默认5分钟
|
||
}
|
||
form.auto_sync = extra.getBoolean('auto_sync') ?? true
|
||
}
|
||
|
||
showConfig.value = true
|
||
} async function saveConfig() {
|
||
const device = currentDevice.value
|
||
if (device === null) return
|
||
|
||
try {
|
||
const deviceId = device.id
|
||
const form = configForm.value
|
||
|
||
const extra = new UTSJSONObject()
|
||
extra.set('sample_rate', sampleRates[form.sample_rate_index])
|
||
extra.set('upload_interval', uploadIntervals[form.upload_interval_index])
|
||
extra.set('auto_sync', form.auto_sync)
|
||
|
||
const updateData = new UTSJSONObject()
|
||
updateData.set('device_name', form.device_name)
|
||
updateData.set('extra', extra)
|
||
|
||
const success = await updateDeviceConfig(deviceId, updateData)
|
||
|
||
if (success) {
|
||
uni.showToast({
|
||
title: '配置保存成功',
|
||
icon: 'success'
|
||
})
|
||
showConfig.value = false
|
||
} else {
|
||
uni.showToast({
|
||
title: '配置保存失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
} catch (e) {
|
||
console.log('保存配置失败:', e)
|
||
uni.showToast({
|
||
title: '保存失败',
|
||
icon: 'error'
|
||
})
|
||
}
|
||
}
|
||
function selectDevice(device : DeviceInfo) {
|
||
const deviceId = device.id
|
||
uni.navigateTo({
|
||
url: `/pages/sense/index?device_id=${deviceId}`
|
||
})
|
||
}
|
||
|
||
function addDevice() {
|
||
scanDevices()
|
||
} function closeConfig() {
|
||
showConfig.value = false
|
||
currentDevice.value = null
|
||
}
|
||
// Event handlers for form controls
|
||
function onDeviceNameInput(event : any) {
|
||
const form = configForm.value
|
||
try {
|
||
const eventObj = event as UTSJSONObject
|
||
const detail = eventObj.get('detail') as UTSJSONObject
|
||
if (detail != null) {
|
||
const value = detail.get('value')
|
||
if (value != null) {
|
||
form.device_name = value.toString()
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('Error in onDeviceNameInput:', e)
|
||
}
|
||
}
|
||
function showSampleRatePicker() {
|
||
uni.showActionSheet({
|
||
itemList: sampleRates,
|
||
success: (res) => {
|
||
if (res.tapIndex >= 0) {
|
||
configForm.value.sample_rate_index = res.tapIndex
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
function showUploadIntervalPicker() {
|
||
uni.showActionSheet({
|
||
itemList: uploadIntervals,
|
||
success: (res) => {
|
||
if (res.tapIndex >= 0) {
|
||
configForm.value.upload_interval_index = res.tapIndex
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
function onSampleRateChange(event : any) {
|
||
const form = configForm.value
|
||
try {
|
||
const eventObj = event as UTSJSONObject
|
||
const detail = eventObj.get('detail') as UTSJSONObject
|
||
if (detail != null) {
|
||
const value = detail.get('value')
|
||
if (value != null) {
|
||
form.sample_rate_index = parseInt(value.toString())
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('Error in onSampleRateChange:', e)
|
||
}
|
||
}
|
||
|
||
function onUploadIntervalChange(event : any) {
|
||
const form = configForm.value
|
||
try {
|
||
const eventObj = event as UTSJSONObject
|
||
const detail = eventObj.get('detail') as UTSJSONObject
|
||
if (detail != null) {
|
||
const value = detail.get('value')
|
||
if (value != null) {
|
||
form.upload_interval_index = parseInt(value.toString())
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('Error in onUploadIntervalChange:', e)
|
||
}
|
||
}
|
||
|
||
function onAutoSyncChange(event : any) {
|
||
const form = configForm.value
|
||
try {
|
||
const eventObj = event as UTSJSONObject
|
||
const detail = eventObj.get('detail') as UTSJSONObject
|
||
if (detail != null) {
|
||
const value = detail.get('value')
|
||
if (value != null) {
|
||
form.auto_sync = value as boolean
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.log('Error in onAutoSyncChange:', e)
|
||
}
|
||
}
|
||
|
||
// 工具函数
|
||
function getStatusText(status : string) : string {
|
||
const statusMap = new Map<string, string>()
|
||
statusMap.set('active', '在线')
|
||
statusMap.set('inactive', '离线')
|
||
statusMap.set('offline', '离线')
|
||
|
||
return statusMap.get(status) ?? '未知'
|
||
}
|
||
|
||
function getStatusClass(status : string) : string {
|
||
if (status === 'active') {
|
||
return 'status-online'
|
||
}
|
||
return 'status-offline'
|
||
}
|
||
|
||
function getDeviceTypeLabel(type : string) : string {
|
||
const typeMap = new Map<string, string>()
|
||
typeMap.set('smartwatch', '智能手表')
|
||
typeMap.set('fitness_band', '健身手环')
|
||
typeMap.set('heart_monitor', '心率监测器')
|
||
typeMap.set('blood_pressure', '血压计')
|
||
typeMap.set('thermometer', '体温计')
|
||
|
||
return typeMap.get(type) ?? '未知设备'
|
||
}
|
||
function formatTime(timeStr : string) : string {
|
||
if (timeStr === '') return '--'
|
||
|
||
const time = new Date(timeStr)
|
||
const year = time.getFullYear()
|
||
const month = (time.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = time.getDate().toString().padStart(2, '0')
|
||
const hour = time.getHours().toString().padStart(2, '0')
|
||
const minute = time.getMinutes().toString().padStart(2, '0')
|
||
|
||
return `${year}-${month}-${day} ${hour}:${minute}`
|
||
}
|
||
async function loadBoundDevices() {
|
||
try {
|
||
const success = await loadDevicesWithDefault()
|
||
if (!success) {
|
||
console.log('加载设备列表失败')
|
||
}
|
||
} catch (e) {
|
||
console.log('加载绑定设备失败:', e)
|
||
}
|
||
}
|
||
onMounted(() => {
|
||
loadBoundDevices()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.device-container {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.header {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
padding: 20rpx;
|
||
background-color: #ffffff;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.header-actions {
|
||
flex-direction: row;
|
||
}
|
||
|
||
.scan-btn,
|
||
.add-btn {
|
||
padding: 16rpx 24rpx;
|
||
margin-left: 16rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
border: none;
|
||
}
|
||
|
||
.scan-btn {
|
||
background-color: #67C23A;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.add-btn {
|
||
background-color: #409EFF;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.scan-status {
|
||
margin-bottom: 20rpx;
|
||
padding: 32rpx;
|
||
background-color: #ffffff;
|
||
border-radius: 12rpx;
|
||
align-items: center;
|
||
}
|
||
|
||
.scan-indicator {
|
||
align-items: center;
|
||
}
|
||
|
||
.scan-text {
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.scan-animation {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border: 4rpx solid #f0f0f0;
|
||
border-top: 4rpx solid #409EFF;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% {
|
||
transform: rotate(0deg);
|
||
}
|
||
|
||
100% {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.section {
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.section-header {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
margin-bottom: 16rpx;
|
||
padding-left: 8rpx;
|
||
}
|
||
|
||
.refresh-btn {
|
||
padding: 12rpx 24rpx;
|
||
background-color: #409EFF;
|
||
color: #ffffff;
|
||
border-radius: 6rpx;
|
||
font-size: 24rpx;
|
||
border: none;
|
||
}
|
||
|
||
.refresh-btn[disabled] {
|
||
background-color: #cccccc;
|
||
color: #999999;
|
||
}
|
||
|
||
.refresh-text {
|
||
color: inherit;
|
||
}
|
||
|
||
.device-list {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.device-item,
|
||
.discovered-item {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
padding: 24rpx;
|
||
background-color: #ffffff;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.device-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.device-header {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 12rpx;
|
||
}
|
||
|
||
.device-name {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.device-status {
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.status-online {
|
||
background-color: #E8F5E8;
|
||
}
|
||
|
||
.status-offline {
|
||
background-color: #FFF1F0;
|
||
}
|
||
|
||
.status-online .status-text {
|
||
color: #52C41A;
|
||
}
|
||
|
||
.status-offline .status-text {
|
||
color: #FF4D4F;
|
||
}
|
||
|
||
.status-text {
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.device-mac,
|
||
.device-type,
|
||
.bind-time,
|
||
.device-rssi {
|
||
font-size: 24rpx;
|
||
color: #666666;
|
||
margin-bottom: 4rpx;
|
||
}
|
||
|
||
.device-actions {
|
||
flex-direction: row;
|
||
}
|
||
|
||
.config-btn,
|
||
.unbind-btn,
|
||
.bind-btn {
|
||
padding: 12rpx 20rpx;
|
||
margin-left: 12rpx;
|
||
border-radius: 6rpx;
|
||
font-size: 24rpx;
|
||
border: none;
|
||
}
|
||
|
||
.config-btn {
|
||
background-color: #E6A23C;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.unbind-btn {
|
||
background-color: #F56C6C;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.bind-btn {
|
||
background-color: #67C23A;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.config-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.modal-content {
|
||
width: 85%;
|
||
max-height: 80%;
|
||
background-color: #ffffff;
|
||
border-radius: 16rpx;
|
||
}
|
||
|
||
.modal-header {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 32rpx;
|
||
border-bottom: 2rpx solid #f0f0f0;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333333;
|
||
}
|
||
|
||
.close-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
border-radius: 30rpx;
|
||
background-color: #f0f0f0;
|
||
color: #666666;
|
||
font-size: 36rpx;
|
||
border: none;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 32rpx;
|
||
}
|
||
|
||
.config-form {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.form-item {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 32rpx;
|
||
}
|
||
|
||
.form-label {
|
||
width: 200rpx;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.form-input {
|
||
flex: 1;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #e0e0e0;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.picker-view {
|
||
flex: 1;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx;
|
||
border: 2rpx solid #e0e0e0;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.picker-arrow {
|
||
color: #999999;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.switch-label {
|
||
margin-left: 16rpx;
|
||
font-size: 28rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.form-actions {
|
||
flex-direction: row;
|
||
justify-content: flex-end;
|
||
margin-top: 32rpx;
|
||
}
|
||
|
||
.cancel-btn,
|
||
.save-btn {
|
||
padding: 20rpx 40rpx;
|
||
margin-left: 16rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
border: none;
|
||
}
|
||
|
||
.cancel-btn {
|
||
background-color: #f0f0f0;
|
||
color: #666666;
|
||
}
|
||
|
||
.save-btn {
|
||
background-color: #409EFF;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.loading-status {
|
||
padding: 32rpx;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 28rpx;
|
||
color: #666666;
|
||
}
|
||
</style> |