From 6377e53ecb9f661d0d5e34776ea8452d0964a226 Mon Sep 17 00:00:00 2001 From: ozh Date: Thu, 17 Sep 2026 14:44:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(room):=20=E6=88=BF=E9=97=B4=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=85=A5=E5=8F=82=E7=B1=BB=E5=9E=8B=E5=8C=96=E5=B9=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=BD=BF=E7=94=A8=E5=9C=BA=E6=99=AF=E9=80=89?= =?UTF-8?q?=E6=8B=A9=EF=BC=88add/update=20=E5=B8=A6=20sence=20=E6=9E=9A?= =?UTF-8?q?=E4=B8=BE=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/homeRoom.ts | 47 ++++++- src/pages/scene/select.module.scss | 167 ++++++++++++++++++++-- src/pages/scene/select.tsx | 217 +++++++++++++++++++---------- 3 files changed, 345 insertions(+), 86 deletions(-) diff --git a/src/api/homeRoom.ts b/src/api/homeRoom.ts index 1ea37b6..f91ae22 100644 --- a/src/api/homeRoom.ts +++ b/src/api/homeRoom.ts @@ -1,10 +1,45 @@ import { request } from '@/http' +// 房间使用场景枚举(apifox YlHomeRoomAddReq.sence 推荐值) +export const ROOM_SENCE_MAP: Record = { + 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 { return request.post('/yanglao/app/ylHomeRoom/getList', { homeId }) } -export function addRoom(params: Record): Promise { - return request.post('/yanglao/app/ylHomeRoom/add', params) +export function addRoom(params: HomeRoomAddReq): Promise { + return request.post('/yanglao/app/ylHomeRoom/add', params as unknown as Record) } -export function updateRoom(params: Record): Promise { - return request.post('/yanglao/app/ylHomeRoom/update', params) +export function updateRoom(params: HomeRoomUpdateReq): Promise { + return request.post('/yanglao/app/ylHomeRoom/update', params as unknown as Record) } -export function deleteRoom(params: Record): Promise { - return request.post('/yanglao/app/ylHomeRoom/delete', params) +export function deleteRoom(params: HomeRoomDeleteReq): Promise { + return request.post('/yanglao/app/ylHomeRoom/delete', params as unknown as Record) } diff --git a/src/pages/scene/select.module.scss b/src/pages/scene/select.module.scss index df01e99..dde5bb7 100644 --- a/src/pages/scene/select.module.scss +++ b/src/pages/scene/select.module.scss @@ -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; +} diff --git a/src/pages/scene/select.tsx b/src/pages/scene/select.tsx index 90b52d5..d3735db 100644 --- a/src/pages/scene/select.tsx +++ b/src/pages/scene/select.tsx @@ -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 - -// 房间操作菜单:编辑名称 / 删除 -const showActionSheet = () => - Taro.showActionSheet({ itemList: ['编辑名称', '删除房间'] }) as Promise +const confirmModal = (option: Taro.showModal.Option): Promise => + Taro.showModal(option) as Promise export default function SceneSelect() { const [loading, setLoading] = useState(true) @@ -33,6 +31,15 @@ export default function SceneSelect() { const [home, setHome] = useState(null) const [list, setList] = useState([]) + // 表单弹窗 + const [formOpen, setFormOpen] = useState(false) + const [editingRoom, setEditingRoom] = useState(null) + const [form, setForm] = useState({ name: '', sence: '' }) + const [saving, setSaving] = useState(false) + + // 删除确认弹窗 + const [deletingRoom, setDeletingRoom] = useState(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() { ) : ( {list.map((room) => ( - onRoomAction(room)} - > - {room.roomName} - onRoomAction(room)}>编辑 + + openEditForm(room)}> + {room.roomName} + {room.sence && ROOM_SENCE_MAP[room.sence] && ( + {ROOM_SENCE_MAP[room.sence]} + )} + + setDeletingRoom(room)}>删除 ))} {/* 空列表时也保留添加入口 */} {list.length === 0 && ( )} - + + 添加房间 )} + + {/* 添加/编辑房间弹窗:名称输入 + 场景九宫格 */} + + + + 房间名称 + setForm((f) => ({ ...f, name: e.detail.value }))} + /> + + + 使用场景(可选) + + {Object.entries(ROOM_SENCE_MAP).map(([key, label]) => ( + setForm((f) => ({ ...f, sence: f.sence === key ? '' : key }))} + > + {label} + + ))} + + + + {saving ? '保存中...' : '保存'} + + + + + {/* 删除确认 */} + {deletingRoom && ( + !saving && setDeletingRoom(null)} catchMove> + e.stopPropagation()}> + 删除房间 + + 确定删除「{deletingRoom.roomName}」吗?该房间下的设备需重新分配。 + + + setDeletingRoom(null)}> + 取消 + + + {saving ? '删除中...' : '删除'} + + + + + )} ) }