上传小程序修改合入
This commit is contained in:
@@ -13,6 +13,7 @@ class ApiError extends Error {
|
||||
|
||||
function getErrorDetail(data: any): string {
|
||||
if (!data) return 'Request failed'
|
||||
if (typeof data.errMsg === 'string') return data.errMsg
|
||||
if (typeof data.detail === 'string') return data.detail
|
||||
if (Array.isArray(data.detail)) {
|
||||
return data.detail.map((item: any) => String(item?.msg || item)).join('; ')
|
||||
@@ -30,6 +31,8 @@ async function request<T>(
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
const token = getToken()
|
||||
const requestUrl = `${BASE_URL}${url}`
|
||||
const requestMethod = options.method || 'GET'
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers,
|
||||
@@ -41,22 +44,19 @@ async function request<T>(
|
||||
let response
|
||||
try {
|
||||
response = await Taro.request({
|
||||
url: `${BASE_URL}${url}`,
|
||||
method: options.method || 'GET',
|
||||
url: requestUrl,
|
||||
method: requestMethod,
|
||||
data: options.data,
|
||||
header: headers,
|
||||
timeout: REQUEST_TIMEOUT_MS,
|
||||
})
|
||||
} catch (error: any) {
|
||||
const message = String(error?.errMsg || error?.message || '')
|
||||
const detail = message.includes('timeout')
|
||||
? '请求超时,请检查后端服务是否可访问'
|
||||
: '网络请求失败,请检查后端地址和网络连接'
|
||||
throw new ApiError(0, detail)
|
||||
const message = String(error?.errMsg || error?.message || 'request:fail')
|
||||
throw new ApiError(0, message)
|
||||
}
|
||||
|
||||
if (response.statusCode === 401) {
|
||||
handleUnauthorized({ redirect: !url.startsWith('/auth/') })
|
||||
handleUnauthorized({ redirect: !url.startsWith('/banban/auth/') })
|
||||
}
|
||||
|
||||
if (response.statusCode >= 400) {
|
||||
|
||||
@@ -30,7 +30,7 @@ export interface Parent {
|
||||
}
|
||||
|
||||
export async function wechatLogin(data: WechatLoginPayload): Promise<LoginSession> {
|
||||
const session = await request<LoginSession>('/auth/login', {
|
||||
const session = await request<LoginSession>('/banban/auth/login', {
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
@@ -39,14 +39,14 @@ export async function wechatLogin(data: WechatLoginPayload): Promise<LoginSessio
|
||||
}
|
||||
|
||||
export async function getParent(userId: number): Promise<Parent> {
|
||||
return request<Parent>(`/parents/${userId}`)
|
||||
return request<Parent>(`/banban/parents/${userId}`)
|
||||
}
|
||||
|
||||
export async function updateParent(
|
||||
userId: number,
|
||||
data: { nickname?: string; avatar_url?: string; phone?: string }
|
||||
): Promise<Parent> {
|
||||
return request<Parent>(`/parents/${userId}`, {
|
||||
return request<Parent>(`/banban/parents/${userId}`, {
|
||||
method: 'PATCH',
|
||||
data,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
import { request } from './api'
|
||||
import { Child, clearSelectedChildId, getChildren, resolveChildSelection, setSelectedChildId } from './child'
|
||||
|
||||
const SELECTED_BINDING_DEVICE_ID_KEY = 'selectedBindingDeviceId'
|
||||
|
||||
@@ -26,12 +28,38 @@ export interface BindingListResponse {
|
||||
next_cursor?: number | null
|
||||
}
|
||||
|
||||
export interface BindingChildContext {
|
||||
currentChild: Child | null
|
||||
currentBinding: Binding | null
|
||||
}
|
||||
|
||||
export interface DirectBindPayload {
|
||||
device_id: string
|
||||
serial_number: string
|
||||
child_id?: number
|
||||
}
|
||||
|
||||
export interface NFCSessionStartPayload {
|
||||
device_id: string
|
||||
serial_number: string
|
||||
child_id?: number
|
||||
}
|
||||
|
||||
export interface NFCSessionStartResponse {
|
||||
bind_token: string
|
||||
expires_at: string
|
||||
status: number
|
||||
}
|
||||
|
||||
export interface NFCSessionStatus {
|
||||
bind_token: string
|
||||
device_id: string
|
||||
child_id: number | null
|
||||
status: number
|
||||
expires_at: string
|
||||
card_uuid?: string | null
|
||||
}
|
||||
|
||||
export function getSelectedBindingDeviceId(): string | null {
|
||||
const value = Taro.getStorageSync(SELECTED_BINDING_DEVICE_ID_KEY)
|
||||
const deviceId = String(value || '').trim()
|
||||
@@ -67,7 +95,7 @@ export function resolveBindingSelection<T extends { device_id: string }>(items:
|
||||
|
||||
export async function getCurrentBinding(): Promise<Binding | null> {
|
||||
try {
|
||||
return await request<Binding>('/bindings/current')
|
||||
return await request<Binding>('/banban/bindings/current')
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return null
|
||||
throw error
|
||||
@@ -75,16 +103,13 @@ export async function getCurrentBinding(): Promise<Binding | null> {
|
||||
}
|
||||
|
||||
export async function getBindings(cursor?: number, limit: number = 20): Promise<BindingListResponse> {
|
||||
const query = buildQuery({
|
||||
cursor,
|
||||
limit,
|
||||
})
|
||||
return request<BindingListResponse>(`/bindings?${query}`)
|
||||
const query = buildQuery({ cursor, limit })
|
||||
return request<BindingListResponse>(`/banban/bindings?${query}`)
|
||||
}
|
||||
|
||||
export async function getBinding(deviceId: string): Promise<Binding | null> {
|
||||
try {
|
||||
return await request<Binding>(`/bindings/${deviceId}`)
|
||||
return await request<Binding>(`/banban/bindings/${deviceId}`)
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return null
|
||||
throw error
|
||||
@@ -100,7 +125,6 @@ export async function resolveActiveBinding(): Promise<Binding | null> {
|
||||
setSelectedBindingDeviceId(selectedBinding.device_id)
|
||||
return selectedBinding
|
||||
}
|
||||
|
||||
clearSelectedBindingDeviceId()
|
||||
}
|
||||
|
||||
@@ -115,24 +139,92 @@ export async function resolveActiveBinding(): Promise<Binding | null> {
|
||||
}
|
||||
|
||||
export async function directBind(data: DirectBindPayload): Promise<{ device_id: string; child_id: number | null }> {
|
||||
return request('/bindings/direct', {
|
||||
return request('/banban/bindings/direct', { method: 'POST', data })
|
||||
}
|
||||
|
||||
export async function startNFCBind(data: NFCSessionStartPayload): Promise<NFCSessionStartResponse> {
|
||||
return request<NFCSessionStartResponse>('/banban/bindings/start', {
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export async function getNFCBindSession(bindToken: string): Promise<NFCSessionStatus> {
|
||||
return request<NFCSessionStatus>(`/banban/bindings/sessions/${bindToken}`)
|
||||
}
|
||||
|
||||
export async function setBindingChild(
|
||||
deviceId: string,
|
||||
data: { child_id: number }
|
||||
): Promise<{ device_id: string; child_id: number | null }> {
|
||||
return request(`/bindings/${deviceId}/child`, {
|
||||
return request(`/banban/bindings/${deviceId}/child`, {
|
||||
method: 'PATCH',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
export async function unbindDevice(deviceId: string): Promise<void> {
|
||||
return request(`/bindings/${deviceId}`, {
|
||||
return request(`/banban/bindings/${deviceId}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
|
||||
export function resolveBindingForChild<T extends { child_id: number | null }>(
|
||||
items: T[],
|
||||
childId?: number | null
|
||||
): T | null {
|
||||
const normalizedChildId = Number(childId || 0)
|
||||
if (!Number.isFinite(normalizedChildId) || normalizedChildId <= 0) return null
|
||||
|
||||
const childBindings = items.filter((item) => item.child_id === normalizedChildId)
|
||||
if (childBindings.length === 0) return null
|
||||
|
||||
const selectedDeviceId = getSelectedBindingDeviceId()
|
||||
if (selectedDeviceId) {
|
||||
const selectedBinding = childBindings.find((item: any) => item.device_id === selectedDeviceId) || null
|
||||
if (selectedBinding) return selectedBinding
|
||||
}
|
||||
|
||||
return childBindings[0] || null
|
||||
}
|
||||
|
||||
export function syncChildAndBindingSelection(children: Child[], bindings: Binding[]): BindingChildContext {
|
||||
const currentChild = resolveChildSelection(children)
|
||||
const currentBinding = currentChild ? resolveBindingForChild(bindings, currentChild.child_id) : null
|
||||
|
||||
if (currentChild) {
|
||||
setSelectedChildId(currentChild.child_id)
|
||||
} else {
|
||||
clearSelectedChildId()
|
||||
}
|
||||
|
||||
if (currentBinding?.device_id) {
|
||||
setSelectedBindingDeviceId(currentBinding.device_id)
|
||||
} else {
|
||||
clearSelectedBindingDeviceId()
|
||||
}
|
||||
|
||||
return {
|
||||
currentChild,
|
||||
currentBinding,
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadCurrentChildBindingContext(): Promise<{
|
||||
children: Child[]
|
||||
bindings: Binding[]
|
||||
currentChild: Child | null
|
||||
currentBinding: Binding | null
|
||||
}> {
|
||||
const [childResponse, bindingResponse] = await Promise.all([getChildren(undefined, 100), getBindings(undefined, 100)])
|
||||
const children = childResponse.items || []
|
||||
const bindings = bindingResponse.items || []
|
||||
const { currentChild, currentBinding } = syncChildAndBindingSelection(children, bindings)
|
||||
|
||||
return {
|
||||
children,
|
||||
bindings,
|
||||
currentChild,
|
||||
currentBinding,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { getCurrentUserId } from './auth'
|
||||
import { request } from './api'
|
||||
import { resolveActiveBinding } from './binding'
|
||||
import { getChild } from './child'
|
||||
import { loadCurrentChildBindingContext } from './binding'
|
||||
|
||||
export type ConversationSource = 'im' | 'ai'
|
||||
export type PeerKind = 'child' | 'parent' | 'ai'
|
||||
@@ -38,6 +37,11 @@ export interface ChatMessage {
|
||||
content: string
|
||||
time: string
|
||||
conversationId: number
|
||||
contentType?: number
|
||||
mediaUrl?: string | null
|
||||
mediaDurationMs?: number | null
|
||||
mediaMimeType?: string | null
|
||||
mediaTranscriptText?: string | null
|
||||
senderType?: string
|
||||
senderId?: string
|
||||
receiverType?: string
|
||||
@@ -240,21 +244,12 @@ function getConversationRank(conversation: ChatConversation): number {
|
||||
}
|
||||
|
||||
async function getBindingContext(): Promise<BindingContext> {
|
||||
const binding = await resolveActiveBinding()
|
||||
let childName = String(binding?.child_name || '').trim()
|
||||
|
||||
if (!childName && binding?.child_id) {
|
||||
try {
|
||||
const child = await getChild(binding.child_id)
|
||||
childName = String(child?.child_name || '').trim()
|
||||
} catch (error) {
|
||||
console.error('[chat] load child name failed:', error)
|
||||
}
|
||||
}
|
||||
const context = await loadCurrentChildBindingContext()
|
||||
const childName = String(context.currentChild?.child_name || context.currentBinding?.child_name || '').trim()
|
||||
|
||||
return {
|
||||
deviceId: binding?.device_id || '',
|
||||
childId: binding?.child_id || null,
|
||||
deviceId: context.currentBinding?.device_id || '',
|
||||
childId: context.currentChild?.child_id || null,
|
||||
childName,
|
||||
currentUserId: getCurrentUserId(),
|
||||
}
|
||||
@@ -265,7 +260,7 @@ async function getDeviceMessages(context?: BindingContext): Promise<DeviceAiMess
|
||||
if (!deviceId) return []
|
||||
|
||||
try {
|
||||
const response = await request<DeviceAiMessageListResponse>(`/devices/${deviceId}/messages?limit=100`)
|
||||
const response = await request<DeviceAiMessageListResponse>(`/banban/devices/${deviceId}/messages?limit=100`)
|
||||
return response.items || []
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return []
|
||||
@@ -278,7 +273,7 @@ async function getChildConversations(context?: BindingContext): Promise<ChildCon
|
||||
if (!childId) return []
|
||||
|
||||
try {
|
||||
const response = await request<ChildConversationListResponse>(`/children/${childId}/conversations?limit=100`)
|
||||
const response = await request<ChildConversationListResponse>(`/banban/children/${childId}/conversations?limit=100`)
|
||||
return response.items || []
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return []
|
||||
@@ -294,7 +289,7 @@ async function getChildConversationMessages(
|
||||
if (!childId || conversationId <= 0) return []
|
||||
|
||||
const response = await request<ChildConversationMessageListResponse>(
|
||||
`/children/${childId}/conversations/${conversationId}/messages?limit=100`
|
||||
`/banban/children/${childId}/conversations/${conversationId}/messages?limit=100`
|
||||
)
|
||||
return response.items || []
|
||||
}
|
||||
@@ -437,6 +432,11 @@ export async function getMessages(
|
||||
content: item.content || '暂无内容',
|
||||
time: formatDetailTime(item.created_at),
|
||||
conversationId: item.conversation_id,
|
||||
contentType: 1,
|
||||
mediaUrl: null,
|
||||
mediaDurationMs: null,
|
||||
mediaMimeType: null,
|
||||
mediaTranscriptText: null,
|
||||
senderType: item.is_user ? 'device' : 'ai',
|
||||
senderId: item.is_user ? context.deviceId : item.role_key,
|
||||
receiverType: item.is_user ? 'ai' : 'device',
|
||||
@@ -462,6 +462,11 @@ export async function getMessages(
|
||||
content: formatImPreview(item),
|
||||
time: formatDetailTime(item.created_at),
|
||||
conversationId: item.conversation_id,
|
||||
contentType: item.content_type,
|
||||
mediaUrl: item.media_file_key || null,
|
||||
mediaDurationMs: item.media_duration_ms || null,
|
||||
mediaMimeType: item.media_mime_type || null,
|
||||
mediaTranscriptText: item.media_transcript_text || null,
|
||||
senderType: item.sender_type,
|
||||
senderId: item.sender_id,
|
||||
receiverType: item.receiver_type,
|
||||
@@ -580,7 +585,7 @@ export async function sendParentMessage(childId: number, content: string): Promi
|
||||
throw new Error('请输入消息内容')
|
||||
}
|
||||
|
||||
return request<ConversationMessageCreateResponse>(`/children/${childId}/messages`, {
|
||||
return request<ConversationMessageCreateResponse>(`/banban/children/${childId}/messages`, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
content_type: 1,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import { request } from './api'
|
||||
|
||||
const SELECTED_CHILD_ID_KEY = 'selectedChildId'
|
||||
|
||||
function buildQuery(params: Record<string, string | number | undefined | null>): string {
|
||||
return Object.entries(params)
|
||||
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
||||
@@ -21,12 +24,44 @@ export interface ChildListResponse {
|
||||
next_cursor?: number | null
|
||||
}
|
||||
|
||||
export function getSelectedChildId(): number | null {
|
||||
const value = Number(Taro.getStorageSync(SELECTED_CHILD_ID_KEY) || 0)
|
||||
return Number.isFinite(value) && value > 0 ? value : null
|
||||
}
|
||||
|
||||
export function setSelectedChildId(childId?: number | null) {
|
||||
const normalizedChildId = Number(childId || 0)
|
||||
if (!Number.isFinite(normalizedChildId) || normalizedChildId <= 0) {
|
||||
Taro.removeStorageSync(SELECTED_CHILD_ID_KEY)
|
||||
return
|
||||
}
|
||||
Taro.setStorageSync(SELECTED_CHILD_ID_KEY, normalizedChildId)
|
||||
}
|
||||
|
||||
export function clearSelectedChildId() {
|
||||
Taro.removeStorageSync(SELECTED_CHILD_ID_KEY)
|
||||
}
|
||||
|
||||
export function resolveChildSelection<T extends { child_id: number }>(items: T[]): T | null {
|
||||
const selectedChildId = getSelectedChildId()
|
||||
const selectedChild = selectedChildId ? items.find((item) => item.child_id === selectedChildId) || null : null
|
||||
const currentChild = selectedChild || items[0] || null
|
||||
|
||||
if (currentChild) {
|
||||
setSelectedChildId(currentChild.child_id)
|
||||
} else {
|
||||
clearSelectedChildId()
|
||||
}
|
||||
|
||||
return currentChild
|
||||
}
|
||||
|
||||
export async function createChild(data: {
|
||||
child_name: string
|
||||
child_gender?: number
|
||||
child_birthday?: string
|
||||
}): Promise<Child> {
|
||||
return request<Child>('/children', {
|
||||
return request<Child>('/banban/children', {
|
||||
method: 'POST',
|
||||
data,
|
||||
})
|
||||
@@ -37,18 +72,18 @@ export async function getChildren(cursor?: number, limit: number = 20): Promise<
|
||||
cursor,
|
||||
limit,
|
||||
})
|
||||
return request<ChildListResponse>(`/children?${query}`)
|
||||
return request<ChildListResponse>(`/banban/children?${query}`)
|
||||
}
|
||||
|
||||
export async function getChild(childId: number): Promise<Child> {
|
||||
return request<Child>(`/children/${childId}`)
|
||||
return request<Child>(`/banban/children/${childId}`)
|
||||
}
|
||||
|
||||
export async function updateChild(
|
||||
childId: number,
|
||||
data: { child_name?: string; child_gender?: number; child_birthday?: string }
|
||||
): Promise<Child> {
|
||||
return request<Child>(`/children/${childId}`, {
|
||||
return request<Child>(`/banban/children/${childId}`, {
|
||||
method: 'PATCH',
|
||||
data,
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { request } from './api'
|
||||
import { resolveActiveBinding } from './binding'
|
||||
import { loadCurrentChildBindingContext } from './binding'
|
||||
|
||||
export interface DeviceLocation {
|
||||
child_id: number
|
||||
@@ -32,11 +32,12 @@ export interface DeviceTrajectoryResponse {
|
||||
}
|
||||
|
||||
export async function getCurrentDeviceLocation(deviceId?: string): Promise<DeviceLocation | null> {
|
||||
const resolvedDeviceId = String(deviceId || '').trim() || (await resolveActiveBinding())?.device_id || ''
|
||||
const resolvedDeviceId =
|
||||
String(deviceId || '').trim() || (await loadCurrentChildBindingContext()).currentBinding?.device_id || ''
|
||||
if (!resolvedDeviceId) return null
|
||||
|
||||
try {
|
||||
return await request<DeviceLocation>(`/devices/${resolvedDeviceId}/location`)
|
||||
return await request<DeviceLocation>(`/banban/devices/${resolvedDeviceId}/location`)
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return null
|
||||
throw error
|
||||
@@ -49,7 +50,8 @@ export async function getDeviceTrajectory(params?: {
|
||||
endAt?: string
|
||||
limit?: number
|
||||
}): Promise<DeviceTrajectoryResponse | null> {
|
||||
const resolvedDeviceId = String(params?.deviceId || '').trim() || (await resolveActiveBinding())?.device_id || ''
|
||||
const resolvedDeviceId =
|
||||
String(params?.deviceId || '').trim() || (await loadCurrentChildBindingContext()).currentBinding?.device_id || ''
|
||||
if (!resolvedDeviceId) return null
|
||||
|
||||
const query = new URLSearchParams()
|
||||
@@ -59,7 +61,7 @@ export async function getDeviceTrajectory(params?: {
|
||||
|
||||
try {
|
||||
const suffix = query.toString() ? `?${query.toString()}` : ''
|
||||
return await request<DeviceTrajectoryResponse>(`/devices/${resolvedDeviceId}/trajectory${suffix}`)
|
||||
return await request<DeviceTrajectoryResponse>(`/banban/devices/${resolvedDeviceId}/trajectory${suffix}`)
|
||||
} catch (error: any) {
|
||||
if (error?.status === 404) return null
|
||||
throw error
|
||||
|
||||
Reference in New Issue
Block a user