feat(room): 房间管理入参类型化并支持使用场景选择(add/update 带 sence 枚举)
This commit is contained in:
parent
cb2dc3d2aa
commit
6377e53ecb
|
|
@ -1,10 +1,45 @@
|
|||
import { request } from '@/http'
|
||||
|
||||
// 房间使用场景枚举(apifox YlHomeRoomAddReq.sence 推荐值)
|
||||
export const ROOM_SENCE_MAP: Record<string, string> = {
|
||||
LIVING_ROOM: '客厅',
|
||||
BEDROOM: '卧室',
|
||||
MASTER_BEDROOM: '主卧',
|
||||
BATHROOM: '卫生间',
|
||||
KITCHEN: '厨房',
|
||||
DINING_ROOM: '餐厅',
|
||||
STUDY: '书房',
|
||||
BALCONY: '阳台',
|
||||
OTHER: '其他'
|
||||
}
|
||||
|
||||
// 入参对齐 apifox YlHomeRoomAddReq
|
||||
export interface HomeRoomAddReq {
|
||||
homeId: string
|
||||
name: string
|
||||
sence?: string // 使用场景,ROOM_SENCE_MAP 的 key
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// 入参对齐 apifox YlHomeRoomUpdateReq
|
||||
export interface HomeRoomUpdateReq {
|
||||
roomId: string
|
||||
name?: string
|
||||
sence?: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
// 入参对齐 apifox YlHomeRoomDeleteReq
|
||||
export interface HomeRoomDeleteReq {
|
||||
roomId: string
|
||||
}
|
||||
|
||||
// 骨架:本期仅锁定接口路径,字段类型联调时修正(spec D7)
|
||||
export interface HomeRoom {
|
||||
roomId: string
|
||||
homeId: string
|
||||
roomName: string
|
||||
sence?: string | null
|
||||
}
|
||||
|
||||
// 文档路径为 POST + body { homeId }(YlHomeRoomListReq)
|
||||
|
|
@ -12,14 +47,14 @@ export function getRoomList(homeId: string): Promise<HomeRoom[]> {
|
|||
return request.post('/yanglao/app/ylHomeRoom/getList', { homeId })
|
||||
}
|
||||
|
||||
export function addRoom(params: Record<string, unknown>): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/add', params)
|
||||
export function addRoom(params: HomeRoomAddReq): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/add', params as unknown as Record<string, unknown>)
|
||||
}
|
||||
|
||||
export function updateRoom(params: Record<string, unknown>): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/update', params)
|
||||
export function updateRoom(params: HomeRoomUpdateReq): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/update', params as unknown as Record<string, unknown>)
|
||||
}
|
||||
|
||||
export function deleteRoom(params: Record<string, unknown>): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/delete', params)
|
||||
export function deleteRoom(params: HomeRoomDeleteReq): Promise<void> {
|
||||
return request.post('/yanglao/app/ylHomeRoom/delete', params as unknown as Record<string, unknown>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
.page {
|
||||
min-height: 100vh;
|
||||
background: $color-bg;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.stateWrap {
|
||||
|
|
@ -13,8 +14,8 @@
|
|||
.list { margin-top: 20px; }
|
||||
|
||||
.item {
|
||||
height: 104px;
|
||||
padding: 0 32px;
|
||||
min-height: 104px;
|
||||
padding: 16px 32px;
|
||||
margin-bottom: 2px;
|
||||
background: $color-bg-white;
|
||||
display: flex;
|
||||
|
|
@ -22,26 +23,43 @@
|
|||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.itemActive { background: $color-primary-light-bg; }
|
||||
// 名称 + 场景标签组:点击进编辑
|
||||
.itemMain {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 32px;
|
||||
color: $color-text;
|
||||
font-weight: 600;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.itemActive .name { color: $color-primary; }
|
||||
|
||||
.check {
|
||||
font-size: 32px;
|
||||
color: $color-text-placeholder;
|
||||
.senceTag {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 16px;
|
||||
background: $color-primary-light-bg;
|
||||
border-radius: $radius-pill;
|
||||
font-size: 22px;
|
||||
color: $color-primary-deep;
|
||||
}
|
||||
|
||||
.itemActive .check { color: $color-primary; }
|
||||
.del {
|
||||
flex-shrink: 0;
|
||||
margin-left: 24px;
|
||||
font-size: 26px;
|
||||
color: $color-danger;
|
||||
}
|
||||
|
||||
// 添加房间:虚线边框卡,与家庭圈添加卡风格一致
|
||||
.addBtn {
|
||||
margin-top: 24px;
|
||||
margin: 24px 24px 0;
|
||||
height: 104px;
|
||||
border: 1px dashed $color-primary-light-border;
|
||||
border-radius: $radius-md;
|
||||
|
|
@ -64,3 +82,132 @@
|
|||
font-size: 28px;
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
// —— 添加/编辑弹窗表单 ——
|
||||
.formBody {
|
||||
padding: 8px 40px 40px;
|
||||
}
|
||||
|
||||
.formField {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.formLabel {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
color: $color-text-secondary;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.formInput {
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
padding: 0 32px;
|
||||
background: $color-bg-gray;
|
||||
border-radius: $radius-md;
|
||||
font-size: 28px;
|
||||
color: $color-text;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.senceGrid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.senceChip {
|
||||
padding: 14px 32px;
|
||||
background: $color-bg-gray;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $radius-pill;
|
||||
font-size: 26px;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.senceChipActive {
|
||||
background: $color-primary-light-bg;
|
||||
border-color: $color-primary;
|
||||
color: $color-primary-deep;
|
||||
}
|
||||
|
||||
.formSubmit {
|
||||
height: 96px;
|
||||
background: $color-primary-grad;
|
||||
border-radius: $radius-pill;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.formSubmitPressed { opacity: 0.85; }
|
||||
.formSubmitDisabled { opacity: 0.6; }
|
||||
|
||||
.formSubmitText {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
// —— 删除确认弹窗 ——
|
||||
.delMask {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: $color-mask;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.delDialog {
|
||||
width: 600px;
|
||||
background: $color-bg-white;
|
||||
border-radius: $radius-lg;
|
||||
padding: 48px 40px 32px;
|
||||
}
|
||||
|
||||
.delTitle {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 34px;
|
||||
font-weight: 600;
|
||||
color: $color-text;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.delContent {
|
||||
display: block;
|
||||
font-size: 28px;
|
||||
color: $color-text-secondary;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.delBtnRow {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.delCancel,
|
||||
.delConfirm {
|
||||
flex: 1;
|
||||
height: 88px;
|
||||
border-radius: $radius-pill;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.delCancel {
|
||||
background: $color-bg-gray;
|
||||
color: $color-text-secondary;
|
||||
}
|
||||
|
||||
.delConfirm {
|
||||
background: $color-danger;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,29 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { View, Text, Input } from '@tarojs/components'
|
||||
import { Navbar } from '@/components/navbar'
|
||||
import { Loading } from '@/components/loading'
|
||||
import { Empty } from '@/components/empty'
|
||||
import { StateView } from '@/components/state-view'
|
||||
import { BottomSheet } from '@/components/bottom-sheet'
|
||||
import { getHomeList, type HomeInfo } from '@/api/home'
|
||||
import { getRoomList, addRoom, updateRoom, deleteRoom, type HomeRoom } from '@/api/homeRoom'
|
||||
import {
|
||||
getRoomList, addRoom, updateRoom, deleteRoom, ROOM_SENCE_MAP, type HomeRoom
|
||||
} from '@/api/homeRoom'
|
||||
import { showToast } from '@/utils/toast'
|
||||
import styles from './select.module.scss'
|
||||
|
||||
// Taro 类型未跟进 wx.showModal 的 editable 扩展(微信 2.17.1+),运行时真实存在
|
||||
interface EditableModalOption extends Taro.showModal.Option {
|
||||
editable?: boolean
|
||||
placeholderText?: string
|
||||
// 添加/编辑弹窗的表单态:editingRoom 为空是新增,有值是编辑
|
||||
interface RoomFormState {
|
||||
name: string
|
||||
sence: string
|
||||
}
|
||||
|
||||
interface EditableModalResult extends Taro.showModal.SuccessCallbackResult {
|
||||
content?: string
|
||||
}
|
||||
// 消除 Taro.showModal cancel/confirm 分支中的隐式 any
|
||||
type ModalResult = { confirm: boolean; cancel: boolean; content?: string }
|
||||
|
||||
const showEditableModal = (option: EditableModalOption) =>
|
||||
Taro.showModal(option as Taro.showModal.Option) as Promise<EditableModalResult>
|
||||
|
||||
// 房间操作菜单:编辑名称 / 删除
|
||||
const showActionSheet = () =>
|
||||
Taro.showActionSheet({ itemList: ['编辑名称', '删除房间'] }) as Promise<Taro.showActionSheet.SuccessCallbackResult>
|
||||
const confirmModal = (option: Taro.showModal.Option): Promise<ModalResult> =>
|
||||
Taro.showModal(option) as Promise<ModalResult>
|
||||
|
||||
export default function SceneSelect() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
|
@ -33,6 +31,15 @@ export default function SceneSelect() {
|
|||
const [home, setHome] = useState<HomeInfo | null>(null)
|
||||
const [list, setList] = useState<HomeRoom[]>([])
|
||||
|
||||
// 表单弹窗
|
||||
const [formOpen, setFormOpen] = useState(false)
|
||||
const [editingRoom, setEditingRoom] = useState<HomeRoom | null>(null)
|
||||
const [form, setForm] = useState<RoomFormState>({ name: '', sence: '' })
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
// 删除确认弹窗
|
||||
const [deletingRoom, setDeletingRoom] = useState<HomeRoom | null>(null)
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true)
|
||||
setLoadError(false)
|
||||
|
|
@ -55,56 +62,68 @@ export default function SceneSelect() {
|
|||
|
||||
useEffect(() => { load() }, [])
|
||||
|
||||
// 添加房间:可编辑弹窗输入名称,成功后刷新列表
|
||||
const addRoomByName = () => {
|
||||
showEditableModal({
|
||||
title: '添加房间',
|
||||
editable: true,
|
||||
placeholderText: '请输入房间名称(如:阳台)'
|
||||
}).then(async ({ confirm, content }) => {
|
||||
if (!confirm || !home) return
|
||||
try {
|
||||
await addRoom({ homeId: home.homeId, name: (content ?? '').trim() })
|
||||
showToast('添加成功')
|
||||
await load()
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : '添加失败')
|
||||
}
|
||||
})
|
||||
// —— 添加 / 编辑 ——
|
||||
|
||||
const openAddForm = () => {
|
||||
setEditingRoom(null)
|
||||
setForm({ name: '', sence: '' })
|
||||
setFormOpen(true)
|
||||
}
|
||||
|
||||
// 长按/操作:编辑名称或删除
|
||||
const onRoomAction = (room: HomeRoom) => {
|
||||
const openEditForm = (room: HomeRoom) => {
|
||||
setEditingRoom(room)
|
||||
setForm({ name: room.roomName, sence: room.sence ?? '' })
|
||||
setFormOpen(true)
|
||||
}
|
||||
|
||||
const closeForm = () => {
|
||||
if (saving) return
|
||||
setFormOpen(false)
|
||||
setEditingRoom(null)
|
||||
}
|
||||
|
||||
const submitForm = async () => {
|
||||
if (saving) return
|
||||
const name = form.name.trim()
|
||||
if (!name) {
|
||||
showToast('请输入房间名称')
|
||||
return
|
||||
}
|
||||
if (!home) return
|
||||
showActionSheet()
|
||||
.then(async ({ tapIndex }) => {
|
||||
if (tapIndex === 0) {
|
||||
const { confirm, content } = await showEditableModal({
|
||||
title: '编辑房间名称',
|
||||
editable: true,
|
||||
content: room.roomName,
|
||||
placeholderText: '请输入房间名称'
|
||||
})
|
||||
if (!confirm) return
|
||||
await updateRoom({ roomId: room.roomId, name: (content ?? '').trim() })
|
||||
showToast('已修改')
|
||||
await load()
|
||||
} else if (tapIndex === 1) {
|
||||
const { confirm } = await Taro.showModal({
|
||||
title: '删除房间',
|
||||
content: `确定删除「${room.roomName}」吗?`,
|
||||
confirmText: '删除',
|
||||
confirmColor: '#EE4B4B'
|
||||
})
|
||||
if (!confirm) return
|
||||
await deleteRoom({ roomId: room.roomId })
|
||||
showToast('已删除')
|
||||
await load()
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消操作静默
|
||||
})
|
||||
setSaving(true)
|
||||
try {
|
||||
if (editingRoom) {
|
||||
await updateRoom({ roomId: editingRoom.roomId, name, sence: form.sence || undefined })
|
||||
showToast('已修改')
|
||||
} else {
|
||||
await addRoom({ homeId: home.homeId, name, sence: form.sence || undefined })
|
||||
showToast('添加成功')
|
||||
}
|
||||
setFormOpen(false)
|
||||
setEditingRoom(null)
|
||||
await load()
|
||||
} catch (e) {
|
||||
showToast(e instanceof Error ? e.message : '保存失败')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
// —— 删除 ——
|
||||
|
||||
const confirmDelete = async () => {
|
||||
if (!deletingRoom || saving) return
|
||||
setSaving(true)
|
||||
try {
|
||||
await deleteRoom({ roomId: deletingRoom.roomId })
|
||||
showToast('已删除')
|
||||
setDeletingRoom(null)
|
||||
await load()
|
||||
} catch (e) {
|
||||
showToast(e instanceof Error ? e.message : '删除失败')
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -117,25 +136,83 @@ export default function SceneSelect() {
|
|||
) : (
|
||||
<View className={styles.list}>
|
||||
{list.map((room) => (
|
||||
<View
|
||||
key={room.roomId}
|
||||
className={styles.item}
|
||||
onLongPress={() => onRoomAction(room)}
|
||||
>
|
||||
<Text className={styles.name}>{room.roomName}</Text>
|
||||
<Text className={styles.check} onClick={() => onRoomAction(room)}>编辑</Text>
|
||||
<View key={room.roomId} className={styles.item}>
|
||||
<View className={styles.itemMain} onClick={() => openEditForm(room)}>
|
||||
<Text className={styles.name}>{room.roomName}</Text>
|
||||
{room.sence && ROOM_SENCE_MAP[room.sence] && (
|
||||
<Text className={styles.senceTag}>{ROOM_SENCE_MAP[room.sence]}</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text className={styles.del} onClick={() => setDeletingRoom(room)}>删除</Text>
|
||||
</View>
|
||||
))}
|
||||
{/* 空列表时也保留添加入口 */}
|
||||
{list.length === 0 && (
|
||||
<View className={styles.stateWrap}><Empty text="暂无房间,点击下方添加" /></View>
|
||||
)}
|
||||
<View className={styles.addBtn} hoverClass={styles.addBtnPressed} onClick={addRoomByName}>
|
||||
<View className={styles.addBtn} hoverClass={styles.addBtnPressed} onClick={openAddForm}>
|
||||
<Text className={styles.addIcon}>+</Text>
|
||||
<Text className={styles.addText}>添加房间</Text>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 添加/编辑房间弹窗:名称输入 + 场景九宫格 */}
|
||||
<BottomSheet visible={formOpen} title={editingRoom ? '编辑房间' : '添加房间'} onClose={closeForm}>
|
||||
<View className={styles.formBody}>
|
||||
<View className={styles.formField}>
|
||||
<Text className={styles.formLabel}>房间名称</Text>
|
||||
<Input
|
||||
className={styles.formInput}
|
||||
placeholder="请输入房间名称(如:阳台)"
|
||||
maxlength={20}
|
||||
value={form.name}
|
||||
onInput={(e) => setForm((f) => ({ ...f, name: e.detail.value }))}
|
||||
/>
|
||||
</View>
|
||||
<View className={styles.formField}>
|
||||
<Text className={styles.formLabel}>使用场景(可选)</Text>
|
||||
<View className={styles.senceGrid}>
|
||||
{Object.entries(ROOM_SENCE_MAP).map(([key, label]) => (
|
||||
<View
|
||||
key={key}
|
||||
className={`${styles.senceChip} ${form.sence === key ? styles.senceChipActive : ''}`}
|
||||
onClick={() => setForm((f) => ({ ...f, sence: f.sence === key ? '' : key }))}
|
||||
>
|
||||
<Text>{label}</Text>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
<View
|
||||
className={`${styles.formSubmit} ${saving ? styles.formSubmitDisabled : ''}`}
|
||||
hoverClass={styles.formSubmitPressed}
|
||||
onClick={submitForm}
|
||||
>
|
||||
<Text className={styles.formSubmitText}>{saving ? '保存中...' : '保存'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</BottomSheet>
|
||||
|
||||
{/* 删除确认 */}
|
||||
{deletingRoom && (
|
||||
<View className={styles.delMask} onClick={() => !saving && setDeletingRoom(null)} catchMove>
|
||||
<View className={styles.delDialog} onClick={(e) => e.stopPropagation()}>
|
||||
<Text className={styles.delTitle}>删除房间</Text>
|
||||
<Text className={styles.delContent}>
|
||||
确定删除「{deletingRoom.roomName}」吗?该房间下的设备需重新分配。
|
||||
</Text>
|
||||
<View className={styles.delBtnRow}>
|
||||
<View className={styles.delCancel} onClick={() => setDeletingRoom(null)}>
|
||||
<Text>取消</Text>
|
||||
</View>
|
||||
<View className={styles.delConfirm} onClick={confirmDelete}>
|
||||
<Text>{saving ? '删除中...' : '删除'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue