合入小程序修改内容
This commit is contained in:
@@ -4,6 +4,10 @@ import { getCurrentUserId, getToken, handleUnauthorized } from './session'
|
||||
|
||||
const BASE_URL = API_BASE_URL
|
||||
const REQUEST_TIMEOUT_MS = 10000
|
||||
const ERROR_DETAIL_MAP: Record<string, string> = {
|
||||
'Request failed': '请求失败',
|
||||
'device is already bound, unbind it before binding again': '设备已绑定,请先解绑后再重新绑定',
|
||||
}
|
||||
|
||||
class ApiError extends Error {
|
||||
constructor(public status: number, message: string) {
|
||||
@@ -12,14 +16,14 @@ class ApiError extends Error {
|
||||
}
|
||||
|
||||
function getErrorDetail(data: any): string {
|
||||
if (!data) return 'Request failed'
|
||||
if (!data) return ERROR_DETAIL_MAP['Request failed']
|
||||
if (typeof data.errMsg === 'string') return data.errMsg
|
||||
if (typeof data.detail === 'string') return data.detail
|
||||
if (typeof data.detail === 'string') return ERROR_DETAIL_MAP[data.detail] || data.detail
|
||||
if (Array.isArray(data.detail)) {
|
||||
return data.detail.map((item: any) => String(item?.msg || item)).join('; ')
|
||||
}
|
||||
if (typeof data.message === 'string') return data.message
|
||||
return 'Request failed'
|
||||
return ERROR_DETAIL_MAP['Request failed']
|
||||
}
|
||||
|
||||
async function request<T>(
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface LoginSession {
|
||||
token_type: string
|
||||
expires_in: number
|
||||
user_id: number
|
||||
nickname?: string
|
||||
}
|
||||
|
||||
export interface Parent {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { getCurrentUserId } from './auth'
|
||||
import { request } from './api'
|
||||
import { ApiError, BASE_URL, getToken, request } from './api'
|
||||
import { loadCurrentChildBindingContext } from './binding'
|
||||
import { handleUnauthorized } from './session'
|
||||
|
||||
export type ConversationSource = 'im' | 'ai'
|
||||
export type PeerKind = 'child' | 'parent' | 'ai'
|
||||
@@ -49,6 +51,7 @@ export interface ChatMessage {
|
||||
senderName?: string
|
||||
receiverName?: string
|
||||
channelLabel?: string
|
||||
extJson?: Record<string, any> | null
|
||||
}
|
||||
|
||||
export interface Character {
|
||||
@@ -201,10 +204,18 @@ function formatDetailTime(value?: string | null): string {
|
||||
}
|
||||
|
||||
function formatImPreview(
|
||||
item: Pick<ChildConversationMessageItem, 'content_type' | 'content_text' | 'media_transcript_text' | 'content_json'>
|
||||
item: Pick<
|
||||
ChildConversationMessageItem,
|
||||
'content_type' | 'content_text' | 'media_transcript_text' | 'content_json' | 'ext_json'
|
||||
>
|
||||
): string {
|
||||
if (item.content_type === 1) return item.content_text || '文本消息'
|
||||
if (item.content_type === 2) return item.media_transcript_text || '[语音]'
|
||||
if (item.content_type === 2) {
|
||||
if (item.ext_json?.message_kind === 'leave_message') {
|
||||
return item.media_transcript_text || '[留言]'
|
||||
}
|
||||
return item.media_transcript_text || '[语音]'
|
||||
}
|
||||
if (item.content_type === 3) return '[图片]'
|
||||
if (item.content_type === 4) {
|
||||
const title = String(item.content_json?.title || item.content_json?.type || '').trim()
|
||||
@@ -361,7 +372,7 @@ function createSyntheticParentConversation(context: BindingContext): ChatConvers
|
||||
avatar: PEER_META.parent.icon,
|
||||
typeLabel: '家长沟通',
|
||||
description: '家长通过微信小程序和孩子直接沟通',
|
||||
lastMessage: '还没有消息,点进去发送第一条',
|
||||
lastMessage: '还没有留言,点进去发送第一条',
|
||||
time: '',
|
||||
sortAt: '',
|
||||
conversationTypeName: PARENT_CHILD_CONVERSATION_TYPE_NAME,
|
||||
@@ -471,6 +482,7 @@ export async function getMessages(
|
||||
senderId: item.sender_id,
|
||||
receiverType: item.receiver_type,
|
||||
receiverId: item.receiver_id,
|
||||
extJson: item.ext_json || null,
|
||||
senderName:
|
||||
item.sender_type === 'parent' && item.sender_id === String(context.currentUserId)
|
||||
? '我'
|
||||
@@ -594,3 +606,68 @@ export async function sendParentMessage(childId: number, content: string): Promi
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function parseUploadResponseData(rawData: any): any {
|
||||
if (typeof rawData !== 'string') return rawData
|
||||
try {
|
||||
return JSON.parse(rawData)
|
||||
} catch {
|
||||
return { detail: rawData }
|
||||
}
|
||||
}
|
||||
|
||||
function getUploadErrorMessage(data: any): string {
|
||||
if (!data) return '留言发送失败'
|
||||
if (typeof data.detail === 'string') return data.detail
|
||||
if (typeof data.message === 'string') return data.message
|
||||
if (typeof data.errMsg === 'string') return data.errMsg
|
||||
return '留言发送失败'
|
||||
}
|
||||
|
||||
export async function sendParentVoiceMessage(
|
||||
childId: number,
|
||||
options: { filePath: string; durationMs: number; transcriptText?: string }
|
||||
): Promise<ConversationMessageCreateResponse> {
|
||||
if (!childId) {
|
||||
throw new Error('当前会话不支持留言')
|
||||
}
|
||||
if (!options.filePath) {
|
||||
throw new Error('录音文件不存在')
|
||||
}
|
||||
|
||||
const token = getToken()
|
||||
const requestUrl = `${BASE_URL}/banban/children/${childId}/voice-message`
|
||||
const uploadResponse: any = await new Promise((resolve, reject) => {
|
||||
Taro.uploadFile({
|
||||
url: requestUrl,
|
||||
filePath: options.filePath,
|
||||
name: 'file',
|
||||
header: token
|
||||
? {
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
: {},
|
||||
formData: {
|
||||
duration_ms: `${Math.max(1, Math.round(options.durationMs || 0))}`,
|
||||
client_msg_id: createClientMessageId(),
|
||||
transcript_text: options.transcriptText || '',
|
||||
},
|
||||
success: resolve,
|
||||
fail: reject,
|
||||
})
|
||||
}).catch((error: any) => {
|
||||
const message = String(error?.errMsg || error?.message || 'request:fail')
|
||||
throw new ApiError(0, message)
|
||||
})
|
||||
|
||||
if (uploadResponse.statusCode === 401) {
|
||||
handleUnauthorized({ redirect: true })
|
||||
}
|
||||
|
||||
const data = parseUploadResponseData(uploadResponse.data)
|
||||
if (uploadResponse.statusCode >= 400) {
|
||||
throw new ApiError(uploadResponse.statusCode, getUploadErrorMessage(data))
|
||||
}
|
||||
|
||||
return data as ConversationMessageCreateResponse
|
||||
}
|
||||
|
||||
62
banban-mini/src/services/device.ts
Normal file
62
banban-mini/src/services/device.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { request } from './api'
|
||||
import { loadCurrentChildBindingContext } from './binding'
|
||||
|
||||
export interface DeviceStatus {
|
||||
device_id: string
|
||||
child_id?: number | null
|
||||
child_name?: string | null
|
||||
power?: number | null
|
||||
volume?: number | null
|
||||
signal?: number | null
|
||||
version?: string | null
|
||||
settings_updated_at?: string | null
|
||||
coord_type?: string | null
|
||||
lat?: number | null
|
||||
lng?: number | null
|
||||
accuracy_m?: number | null
|
||||
altitude_m?: number | null
|
||||
speed_mps?: number | null
|
||||
heading_deg?: number | null
|
||||
source?: number | null
|
||||
battery_pct?: number | null
|
||||
device_time?: string | null
|
||||
server_time?: string | null
|
||||
location_updated_at?: string | null
|
||||
}
|
||||
|
||||
export interface DeviceVolumeUpdateResponse {
|
||||
device_id: string
|
||||
level: number
|
||||
msg_id: string
|
||||
}
|
||||
|
||||
async function resolveDeviceId(deviceId?: string): Promise<string> {
|
||||
const normalizedDeviceId = String(deviceId || '').trim()
|
||||
if (normalizedDeviceId) return normalizedDeviceId
|
||||
const context = await loadCurrentChildBindingContext()
|
||||
return context.currentBinding?.device_id || ''
|
||||
}
|
||||
|
||||
export async function getDeviceStatus(deviceId?: string): Promise<DeviceStatus | null> {
|
||||
const resolvedDeviceId = await resolveDeviceId(deviceId)
|
||||
if (!resolvedDeviceId) return null
|
||||
|
||||
try {
|
||||
return await request<DeviceStatus>(`/banban/devices/${resolvedDeviceId}/status`)
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return null
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function setDeviceVolume(level: number, deviceId?: string): Promise<DeviceVolumeUpdateResponse> {
|
||||
const resolvedDeviceId = await resolveDeviceId(deviceId)
|
||||
if (!resolvedDeviceId) {
|
||||
throw new Error('当前没有可用设备')
|
||||
}
|
||||
|
||||
return request<DeviceVolumeUpdateResponse>(`/banban/devices/${resolvedDeviceId}/volume`, {
|
||||
method: 'POST',
|
||||
data: { level },
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user