feat(voice): upload device audio to cos and enable playback
This commit is contained in:
@@ -5,13 +5,11 @@ import { clearToken, getCurrentUserId, getToken } from '@/services/auth'
|
||||
import {
|
||||
BindingListItem,
|
||||
clearSelectedBindingDeviceId,
|
||||
getBindings,
|
||||
resolveBindingSelection,
|
||||
setBindingChild,
|
||||
loadCurrentChildBindingContext,
|
||||
setSelectedBindingDeviceId,
|
||||
unbindDevice,
|
||||
} from '@/services/binding'
|
||||
import { Child, createChild, getChildren, updateChild } from '@/services/child'
|
||||
import { Child, clearSelectedChildId, createChild, setSelectedChildId, updateChild } from '@/services/child'
|
||||
import './index.scss'
|
||||
|
||||
interface MenuItem {
|
||||
@@ -28,8 +26,9 @@ export default function Sleep() {
|
||||
const [children, setChildren] = useState<Child[]>([])
|
||||
const [bindings, setBindings] = useState<BindingListItem[]>([])
|
||||
const [binding, setBinding] = useState<BindingListItem | null>(null)
|
||||
const [currentChild, setCurrentChild] = useState<Child | null>(null)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [showDeviceModal, setShowDeviceModal] = useState(false)
|
||||
const [showChildModal, setShowChildModal] = useState(false)
|
||||
const [modalType, setModalType] = useState<'add' | 'edit'>('add')
|
||||
const [childName, setChildName] = useState('')
|
||||
const [editingChildId, setEditingChildId] = useState<number | null>(null)
|
||||
@@ -47,13 +46,11 @@ export default function Sleep() {
|
||||
|
||||
setLoading(true)
|
||||
try {
|
||||
const [childResponse, bindingResponse] = await Promise.all([getChildren(), getBindings(undefined, 100)])
|
||||
const bindingItems = bindingResponse.items || []
|
||||
const activeBinding = resolveBindingSelection(bindingItems)
|
||||
|
||||
setChildren(childResponse.items || [])
|
||||
setBindings(bindingItems)
|
||||
setBinding(activeBinding)
|
||||
const context = await loadCurrentChildBindingContext()
|
||||
setChildren(context.children)
|
||||
setBindings(context.bindings as BindingListItem[])
|
||||
setCurrentChild(context.currentChild)
|
||||
setBinding((context.currentBinding as BindingListItem | null) || null)
|
||||
setParentInfo(Taro.getStorageSync('userInfo') || {})
|
||||
} catch (error: any) {
|
||||
console.error('[manage] load failed:', error)
|
||||
@@ -66,8 +63,7 @@ export default function Sleep() {
|
||||
}
|
||||
}
|
||||
|
||||
const currentChild = binding?.child_id ? children.find((item) => item.child_id === binding.child_id) || null : null
|
||||
const currentChildName = currentChild?.child_name || binding?.child_name || (binding && !binding.child_id ? '待关联' : '未设置')
|
||||
const currentChildName = currentChild?.child_name || '未设置'
|
||||
|
||||
const handleOpenModal = (type: 'add' | 'edit', child?: Child) => {
|
||||
setModalType(type)
|
||||
@@ -82,12 +78,19 @@ export default function Sleep() {
|
||||
setEditingChildId(null)
|
||||
}
|
||||
|
||||
const handleSelectBinding = (targetBinding: BindingListItem) => {
|
||||
setSelectedBindingDeviceId(targetBinding.device_id)
|
||||
setBinding(targetBinding)
|
||||
setShowDeviceModal(false)
|
||||
const handleSelectChild = (child: Child) => {
|
||||
setSelectedChildId(child.child_id)
|
||||
const nextBinding = bindings.find((item) => item.child_id === child.child_id) || null
|
||||
if (nextBinding?.device_id) {
|
||||
setSelectedBindingDeviceId(nextBinding.device_id)
|
||||
} else {
|
||||
clearSelectedBindingDeviceId()
|
||||
}
|
||||
setCurrentChild(child)
|
||||
setBinding(nextBinding)
|
||||
setShowChildModal(false)
|
||||
Taro.showToast({
|
||||
title: '已切换设备',
|
||||
title: '已切换当前孩子',
|
||||
icon: 'success',
|
||||
})
|
||||
}
|
||||
@@ -102,12 +105,8 @@ export default function Sleep() {
|
||||
try {
|
||||
if (modalType === 'add') {
|
||||
const createdChild = await createChild({ child_name: normalizedName })
|
||||
if (binding && !binding.child_id) {
|
||||
await setBindingChild(binding.device_id, { child_id: createdChild.child_id })
|
||||
Taro.showToast({ title: '已创建并关联', icon: 'success' })
|
||||
} else {
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' })
|
||||
}
|
||||
setSelectedChildId(createdChild.child_id)
|
||||
Taro.showToast({ title: '创建成功', icon: 'success' })
|
||||
} else if (editingChildId) {
|
||||
await updateChild(editingChildId, { child_name: normalizedName })
|
||||
Taro.showToast({ title: '修改成功', icon: 'success' })
|
||||
@@ -124,54 +123,19 @@ export default function Sleep() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleChildMenu = () => {
|
||||
if (binding && !binding.child_id && children.length > 0) {
|
||||
Taro.showActionSheet({
|
||||
itemList: [...children.map((item) => item.child_name), '新建儿童资料'],
|
||||
success: async (result) => {
|
||||
if (result.tapIndex === children.length) {
|
||||
handleOpenModal('add')
|
||||
return
|
||||
}
|
||||
|
||||
const targetChild = children[result.tapIndex]
|
||||
if (!targetChild) return
|
||||
|
||||
try {
|
||||
await setBindingChild(binding.device_id, { child_id: targetChild.child_id })
|
||||
Taro.showToast({ title: '关联成功', icon: 'success' })
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
Taro.showToast({
|
||||
title: error?.message || '关联失败,请重试',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (currentChild) {
|
||||
handleOpenModal('edit', currentChild)
|
||||
return
|
||||
}
|
||||
|
||||
handleOpenModal('add')
|
||||
}
|
||||
|
||||
const handleUnbind = () => {
|
||||
if (!binding) return
|
||||
|
||||
Taro.showModal({
|
||||
title: '解除设备绑定',
|
||||
content: '确定要解除当前设备绑定吗?',
|
||||
content: `确定要解除 ${currentChildName} 当前绑定的设备吗?`,
|
||||
confirmColor: '#FF8C42',
|
||||
success: async (result) => {
|
||||
if (!result.confirm) return
|
||||
|
||||
try {
|
||||
await unbindDevice(binding.device_id)
|
||||
clearSelectedBindingDeviceId()
|
||||
Taro.showToast({ title: '已解绑', icon: 'success' })
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
@@ -193,6 +157,7 @@ export default function Sleep() {
|
||||
if (!result.confirm) return
|
||||
clearToken()
|
||||
clearSelectedBindingDeviceId()
|
||||
clearSelectedChildId()
|
||||
Taro.removeStorageSync('userInfo')
|
||||
Taro.reLaunch({ url: '/pages/login/index' })
|
||||
},
|
||||
@@ -202,21 +167,33 @@ export default function Sleep() {
|
||||
const handleMenuClick = (item: MenuItem) => {
|
||||
if (item.disabled) return
|
||||
|
||||
if (item.name === '儿童资料 (用于称呼)') {
|
||||
handleChildMenu()
|
||||
if (item.name === '当前孩子') {
|
||||
setShowChildModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '绑定新设备') {
|
||||
if (item.name === '编辑当前孩子') {
|
||||
if (currentChild) {
|
||||
handleOpenModal('edit', currentChild)
|
||||
} else {
|
||||
handleOpenModal('add')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '绑定设备') {
|
||||
if (!currentChild) {
|
||||
handleOpenModal('add')
|
||||
Taro.showToast({
|
||||
title: '请先添加儿童资料',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
Taro.navigateTo({ url: '/pages/bind/index' })
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '切换设备') {
|
||||
setShowDeviceModal(true)
|
||||
return
|
||||
}
|
||||
|
||||
if (item.name === '解除设备绑定') {
|
||||
handleUnbind()
|
||||
}
|
||||
@@ -237,24 +214,24 @@ export default function Sleep() {
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
iconBgClass: 'orange',
|
||||
name: '儿童资料 (用于称呼)',
|
||||
name: '当前孩子',
|
||||
value: currentChildName,
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '切换设备',
|
||||
value: bindings.length > 0 ? `共 ${bindings.length} 台` : '暂无设备',
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/orange-robot.png'),
|
||||
iconBgClass: 'orange',
|
||||
name: '绑定新设备',
|
||||
name: '编辑当前孩子',
|
||||
value: '',
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/rings.png'),
|
||||
iconBgClass: 'green',
|
||||
name: '绑定设备',
|
||||
value: binding?.device_id ? `当前: ${binding.device_id}` : '未绑定',
|
||||
arrow: true,
|
||||
},
|
||||
{
|
||||
icon: require('../../assets/tab-icons/broken-rings.png'),
|
||||
iconBgClass: 'red',
|
||||
@@ -284,23 +261,23 @@ export default function Sleep() {
|
||||
<Text className='user-role'>用户 ID: {userId || '--'}</Text>
|
||||
</View>
|
||||
<View className='verified-badge'>
|
||||
<Text>{bindings.length > 0 ? `已绑定 ${bindings.length} 台` : '未绑定设备'}</Text>
|
||||
<Text>{currentChild ? '已选择当前孩子' : '未选择孩子'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='summary-card'>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>已绑定设备数</Text>
|
||||
<Text className='summary-value'>{bindings.length} 台</Text>
|
||||
</View>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>当前设备</Text>
|
||||
<Text className='summary-value'>{binding?.device_id || '未绑定'}</Text>
|
||||
<Text className='summary-label'>儿童资料数</Text>
|
||||
<Text className='summary-value'>{children.length} 个</Text>
|
||||
</View>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>当前儿童</Text>
|
||||
<Text className='summary-value'>{currentChildName}</Text>
|
||||
</View>
|
||||
<View className='summary-row'>
|
||||
<Text className='summary-label'>当前设备</Text>
|
||||
<Text className='summary-value'>{binding?.device_id || '未绑定'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='menu-card'>
|
||||
@@ -333,39 +310,40 @@ export default function Sleep() {
|
||||
<Text className='version-text'>伴伴 Companion V1.1.0</Text>
|
||||
</View>
|
||||
|
||||
{showDeviceModal && (
|
||||
<View className='modal-mask' onClick={() => setShowDeviceModal(false)}>
|
||||
{showChildModal && (
|
||||
<View className='modal-mask' onClick={() => setShowChildModal(false)}>
|
||||
<View
|
||||
className='modal-card device-switch-card'
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<Text className='modal-title'>切换设备</Text>
|
||||
{bindings.length === 0 ? (
|
||||
<Text className='device-switch-empty'>当前还没有已绑定设备</Text>
|
||||
<Text className='modal-title'>切换当前孩子</Text>
|
||||
{children.length === 0 ? (
|
||||
<Text className='device-switch-empty'>当前还没有儿童资料</Text>
|
||||
) : (
|
||||
<View className='device-switch-list'>
|
||||
{bindings.map((item) => {
|
||||
const isActive = binding?.device_id === item.device_id
|
||||
{children.map((item) => {
|
||||
const isActive = currentChild?.child_id === item.child_id
|
||||
const itemBinding = bindings.find((bindingItem) => bindingItem.child_id === item.child_id) || null
|
||||
return (
|
||||
<View
|
||||
key={item.device_id}
|
||||
key={item.child_id}
|
||||
className={`device-switch-item ${isActive ? 'active' : ''}`}
|
||||
onClick={() => handleSelectBinding(item)}
|
||||
onClick={() => handleSelectChild(item)}
|
||||
>
|
||||
<View className='device-switch-head'>
|
||||
<Text className='device-switch-id'>{item.device_id}</Text>
|
||||
<Text className='device-switch-id'>{item.child_name}</Text>
|
||||
{isActive && <Text className='device-switch-tag'>当前</Text>}
|
||||
</View>
|
||||
<Text className='device-switch-name'>{item.child_name || '待关联儿童'}</Text>
|
||||
<Text className='device-switch-name'>{itemBinding?.device_id || '未绑定设备'}</Text>
|
||||
</View>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
<View className='modal-actions'>
|
||||
<Text className='modal-action cancel' onClick={() => setShowDeviceModal(false)}>
|
||||
<Text className='modal-action cancel' onClick={() => setShowChildModal(false)}>
|
||||
关闭
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user