Compare commits

...

7 Commits

6 changed files with 226 additions and 10 deletions

23
api/scene.js Normal file
View File

@ -0,0 +1,23 @@
import request from '@/api/http/client'
// 后端就绪后置为 false 即可恢复真实请求。
const USE_MOCK = true
// 模拟场景列表数据(后端 /scene/list 就绪后删除)。
const MOCK_SCENES = [
{ id: 1, name: '家里' },
{ id: 2, name: '养老院' },
{ id: 3, name: '社区中心' }
]
/**
* 获取场景列表
* 约定data 为数组每项至少含 { id, name }多余字段不影响前端
*/
export function getSceneList() {
if (USE_MOCK) {
// 模拟网络延迟,让 loading 态可见。
return new Promise((resolve) => setTimeout(() => resolve(MOCK_SCENES), 500))
}
return request.get('/scene/list')
}

View File

@ -7,7 +7,8 @@ export const ROUTES = Object.freeze({
EVENT: '/pages/event/index',
MINE: '/pages/mine/index',
DEVICE_ADD: '/pages/device/add',
DEVICE_CONFIG: '/pages/device/config'
DEVICE_CONFIG: '/pages/device/config',
SCENE_SELECT: '/pages/scene/select'
})
/**

View File

@ -35,6 +35,12 @@
"style": {
"navigationStyle": "custom"
}
},
{
"path": "pages/scene/select",
"style": {
"navigationStyle": "custom"
}
}
],
"globalStyle": {

View File

@ -21,10 +21,10 @@
@click="goConfig"
/>
<view class="site-row" @click="goConfig">
<view class="site-row" @click="goSceneSelect">
<view class="site-left">
<up-icon name="list" size="18" color="#26c996" />
<text class="site-text">请选择场所</text>
<text class="site-text">{{ sceneName || '请选择场所' }}</text>
</view>
<up-icon name="arrow-right" size="14" color="#26c996" />
</view>
@ -33,8 +33,8 @@
<up-empty mode="list" text="暂无数据" />
</view>
<view class="fab-wrap">
<up-button type="success" shape="circle" text="添加设备" @click="goAdd" />
<view class="fab-wrap" @click="goAdd">
<up-icon name="plus" color="#ffffff" size="28" />
</view>
</view>
</template>
@ -42,14 +42,28 @@
<script>
import { ROUTES } from '@/constants/routes'
import { navigateTo } from '@/utils/navigation'
import { getCurrentScene } from '@/store/scene'
export default {
data() {
return {
noticeText: '点击选择场所后可进入设备配置流程'
noticeText: '点击选择场所后可进入设备配置流程',
sceneName: ''
}
},
onShow() {
this.refreshScene()
},
methods: {
//
refreshScene() {
const scene = getCurrentScene()
this.sceneName = scene ? scene.name : ''
},
//
goSceneSelect() {
navigateTo(ROUTES.SCENE_SELECT)
},
//
goAdd() {
navigateTo(ROUTES.DEVICE_ADD)
@ -67,7 +81,6 @@ export default {
.page {
position: relative;
padding-bottom: 180rpx;
}
.banner {
@ -139,8 +152,19 @@ export default {
.fab-wrap {
position: fixed;
right: 32rpx;
bottom: 220rpx;
width: 240rpx;
right: 40rpx;
bottom: 60rpx;
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background: linear-gradient(135deg, #1fca97 0%, #2dbf9c 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(31, 202, 151, 0.4);
/* 点击态缩放反馈 */
&:active {
transform: scale(0.92);
}
}
</style>

120
pages/scene/select.vue Normal file
View File

@ -0,0 +1,120 @@
<!-- 场景选择页加载场景列表点选后保存并返回首页 -->
<template>
<view class="page page-base">
<up-navbar title="选择场所" :fixed="false" :safe-area-inset-top="true" :border="false" />
<view v-if="loading" class="state-wrap">
<up-loading-icon mode="circle" size="48" text="加载中..." />
</view>
<view v-else-if="loadError" class="state-wrap">
<up-empty mode="wifi" text="加载失败,请检查网络" />
<up-button type="success" shape="circle" text="重试" class="retry-btn" @click="loadScenes" />
</view>
<view v-else-if="sceneList.length === 0" class="state-wrap">
<up-empty mode="list" text="暂无场景" />
</view>
<view v-else class="scene-list">
<view
v-for="scene in sceneList"
:key="scene.id"
class="scene-item"
:class="{ 'scene-item--active': scene.id === currentSceneId }"
@click="selectScene(scene)"
>
<text class="scene-name">{{ scene.name }}</text>
<up-icon
:name="scene.id === currentSceneId ? 'checkmark' : 'arrow-right'"
:size="scene.id === currentSceneId ? '16' : '14'"
:color="scene.id === currentSceneId ? '#26c996' : '#c0c4cc'"
/>
</view>
</view>
</view>
</template>
<script>
import { getSceneList } from '@/api/scene'
import { getCurrentScene, setCurrentScene } from '@/store/scene'
import { navigateBack } from '@/utils/navigation'
export default {
data() {
return {
loading: true,
loadError: false,
sceneList: [],
currentSceneId: getCurrentScene()?.id ?? null
}
},
onLoad() {
this.loadScenes()
},
methods: {
// toast
async loadScenes() {
this.loading = true
this.loadError = false
try {
const data = await getSceneList()
this.sceneList = Array.isArray(data) ? data : []
} catch (error) {
this.loadError = true
} finally {
this.loading = false
}
},
// onShow
selectScene(scene) {
setCurrentScene(scene)
navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
@import '@/styles/common.css';
.state-wrap {
margin-top: 120rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.retry-btn {
margin-top: 40rpx;
width: 240rpx;
}
.scene-list {
margin-top: 20rpx;
}
.scene-item {
height: 104rpx;
padding: 0 32rpx;
margin-bottom: 2rpx;
background: #ffffff;
display: flex;
align-items: center;
justify-content: space-between;
}
.scene-item--active {
background: #e9fff3;
}
.scene-name {
font-size: 32rpx;
color: #222;
font-weight: 600;
}
.scene-item--active .scene-name {
color: #26c996;
}
</style>

42
store/scene.js Normal file
View File

@ -0,0 +1,42 @@
/**
* 当前场景全局状态内存 + uni.storage 持久化
* 项目未引入 Vuex/Pinia采用轻量模块方案
*/
const STORAGE_KEY = 'current_scene'
let currentScene = null
// 模块加载时从本地存储恢复上次选中的场景。
function initFromStorage() {
const cached = uni.getStorageSync(STORAGE_KEY)
if (cached && cached.id !== undefined) {
currentScene = cached
}
}
initFromStorage()
/**
* 获取当前选中场景
* @returns {{ id: *, name: string } | null}
*/
export function getCurrentScene() {
return currentScene
}
/**
* 设置当前场景并持久化仅保留 id/name避免后端多余字段进缓存
* @param {{ id: *, name: string }} scene
*/
export function setCurrentScene(scene) {
currentScene = { id: scene.id, name: scene.name }
uni.setStorageSync(STORAGE_KEY, currentScene)
}
/**
* 清除当前场景本地与持久化均清除
*/
export function clearCurrentScene() {
currentScene = null
uni.removeStorageSync(STORAGE_KEY)
}