新增小程序后端代码,包括数据库、路由、服务层等。

小程序前后端打通,包括登录、注册、绑定设备、查询绑定信息,修改小朋友名称等功能。
This commit is contained in:
HycJack
2026-04-13 01:54:57 +08:00
parent 33715373b0
commit a22fcafea9
46 changed files with 3713 additions and 268 deletions

View File

@@ -0,0 +1,56 @@
import Taro from '@tarojs/taro'
// const BASE_URL = 'http://175.24.73.253:8001' // 改为实际后端IP
const BASE_URL = 'http://127.0.0.1:8001'
class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
}
}
async function request<T>(
url: string,
options: {
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
data?: any
headers?: Record<string, string>
} = {}
): Promise<T> {
const token = Taro.getStorageSync('token')
console.log(`[API] ${options.method || 'GET'} ${url}`, token ? 'with token' : 'no token')
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...options.headers,
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const response = await Taro.request({
url: `${BASE_URL}${url}`,
method: options.method || 'GET',
data: options.data,
header: headers,
})
console.log(`[API] Response status:`, response.statusCode)
if (response.statusCode >= 400) {
const detail = response.data?.detail || 'Request failed'
throw new ApiError(response.statusCode, detail)
}
return response.data
}
export { request, ApiError, BASE_URL }
export function getToken(): string {
return Taro.getStorageSync('token') || ''
}
export function getCurrentUserId(): number {
return Taro.getStorageSync('user_id') || 0
}

View File

@@ -0,0 +1,75 @@
import { request, BASE_URL } from './api'
export interface Parent {
user_id: number
openid: string
unionid?: string
nickname?: string
avatar_url?: string
phone?: string
status: number
}
// 微信登录
export async function wechatLogin(code: string): Promise<{ token: string; user_id: number }> {
// TODO: 实现微信登录 - 通过 code 换取 openid然后调用后端
// 这里先返回 mock 数据
const mockParent: Parent = {
user_id: 1,
openid: 'mock_openid_' + code,
nickname: '微信用户',
status: 1,
}
// 保存模拟 token
const token = 'mock_token_' + Date.now()
Taro.setStorageSync('token', token)
Taro.setStorageSync('user_id', mockParent.user_id)
return { token, user_id: mockParent.user_id }
}
// 创建家长
export async function createParent(data: {
openid: string
unionid?: string
nickname?: string
avatar_url?: string
}): Promise<Parent> {
return request<Parent>('/parents', {
method: 'POST',
data,
})
}
// 获取家长信息
export async function getParent(userId: number): Promise<Parent> {
return request<Parent>(`/parents/${userId}`)
}
// 更新家长信息
export async function updateParent(
userId: number,
data: { nickname?: string; avatar_url?: string; phone?: string }
): Promise<Parent> {
return request<Parent>(`/parents/${userId}`, {
method: 'PATCH',
data,
})
}
// 获取 token
export function getToken(): string {
return Taro.getStorageSync('token')
}
// 清除 token
export function clearToken(): void {
Taro.removeStorageSync('token')
Taro.removeStorageSync('user_id')
}
// 获取当前用户ID
export function getCurrentUserId(): number {
return Taro.getStorageSync('user_id') || 0
}

View File

@@ -0,0 +1,105 @@
import { request } from './api'
export interface Binding {
device_id: string
child_id: number
status: number
bound_at: string
}
export interface BindStartResponse {
bind_token: string
expires_at: string
}
export interface BindHistoryItem {
device_id: string
child_id: number
bound_at: string
unbound_at?: string
}
export interface BindHistoryResponse {
items: BindHistoryItem[]
total: number
next_cursor?: string
}
// 获取当前绑定信息
export async function getCurrentBinding(): Promise<Binding | null> {
try {
return await request<Binding>('/bindings/current')
} catch (err: any) {
if (err.status === 404) {
return null
}
throw err
}
}
// 开始绑定设备
export async function startBind(data: {
device_id: string
child_id: number
}): Promise<BindStartResponse> {
return request<BindStartResponse>('/bindings/start', {
method: 'POST',
data,
})
}
// 确认绑定设备
export async function confirmBind(data: {
bind_token: string
challenge_code: string
}): Promise<{ device_id: string; child_id: number }> {
return request('/bindings/confirm', {
method: 'POST',
data,
})
}
// 获取设备绑定信息
export async function getBinding(deviceId: string): Promise<Binding | null> {
// If no deviceId, get the first binding for this user
if (!deviceId) {
try {
// Try to get binding list - for now return null if no specific device
return null
} catch {
return null
}
}
return request<Binding>(`/bindings/${deviceId}`)
}
// 解绑设备
export async function unbindDevice(deviceId: string): Promise<void> {
return request(`/bindings/${deviceId}`, {
method: 'DELETE',
})
}
// 直接绑定设备(手动输入设备码)
export async function directBind(data: {
device_id: string
child_id: number
}): Promise<{ device_id: string; child_id: number }> {
return request('/bindings/direct', {
method: 'POST',
data,
})
}
// 获取绑定历史
export async function getBindHistory(
deviceId: string,
cursor?: string,
limit: number = 20
): Promise<BindHistoryResponse> {
const params = new URLSearchParams()
if (cursor) params.append('cursor', cursor)
params.append('limit', String(limit))
return request<BindHistoryResponse>(`/bindings/history/${deviceId}?${params.toString()}`)
}

View File

@@ -0,0 +1,55 @@
import { request } from './api'
export interface Child {
child_id: number
child_name: string
child_gender: number
child_birthday?: string
status: number
}
export interface ChildListResponse {
items: Child[]
total: number
next_cursor?: number
}
// 创建小孩档案
export async function createChild(data: {
child_name: string
child_gender?: number
child_birthday?: string
}): Promise<Child> {
return request<Child>('/children', {
method: 'POST',
data,
})
}
// 获取小孩列表
export async function getChildren(
cursor?: number,
limit: number = 20
): Promise<ChildListResponse> {
const params = new URLSearchParams()
if (cursor) params.append('cursor', String(cursor))
params.append('limit', String(limit))
return request<ChildListResponse>(`/children?${params.toString()}`)
}
// 获取单个小孩信息
export async function getChild(childId: number): Promise<Child> {
return request<Child>(`/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}`, {
method: 'PATCH',
data,
})
}