From a22fcafea9f38240a4214cbe00f8bd8ac1b9da03 Mon Sep 17 00:00:00 2001 From: HycJack <772403255@qq.com> Date: Mon, 13 Apr 2026 01:54:57 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=B0=8F=E7=A8=8B=E5=BA=8F?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E4=BB=A3=E7=A0=81=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=BA=93=E3=80=81=E8=B7=AF=E7=94=B1=E3=80=81?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=B1=82=E7=AD=89=E3=80=82=20=E5=B0=8F?= =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=89=8D=E5=90=8E=E7=AB=AF=E6=89=93=E9=80=9A?= =?UTF-8?q?=EF=BC=8C=E5=8C=85=E6=8B=AC=E7=99=BB=E5=BD=95=E3=80=81=E6=B3=A8?= =?UTF-8?q?=E5=86=8C=E3=80=81=E7=BB=91=E5=AE=9A=E8=AE=BE=E5=A4=87=E3=80=81?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=BB=91=E5=AE=9A=E4=BF=A1=E6=81=AF=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=B0=8F=E6=9C=8B=E5=8F=8B=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E7=AD=89=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 0 -> 6148 bytes banban-mini/config/index.js | 45 +- banban-mini/package-lock.json | 20 +- banban-mini/src/pages/bind/index.scss | 61 +++ banban-mini/src/pages/bind/index.tsx | 208 +++++---- banban-mini/src/pages/device/index.tsx | 71 +++- banban-mini/src/pages/login/index.tsx | 130 ++++-- banban-mini/src/pages/sleep/index.scss | 147 +++++++ banban-mini/src/pages/sleep/index.tsx | 290 +++++++++---- banban-mini/src/services/api.ts | 56 +++ banban-mini/src/services/auth.ts | 75 ++++ banban-mini/src/services/binding.ts | 105 +++++ banban-mini/src/services/child.ts | 55 +++ mini-program/.DS_Store | Bin 0 -> 8196 bytes mini-program/.env.example | 2 + mini-program/app/.DS_Store | Bin 0 -> 8196 bytes mini-program/app/dao/.DS_Store | Bin 0 -> 6148 bytes mini-program/app/dao/__init__.py | 13 + mini-program/app/dao/binding.py | 226 ++++++++++ mini-program/app/dao/child.py | 95 +++++ mini-program/app/dao/parent.py | 100 +++++ mini-program/app/db.py | 43 +- mini-program/app/main.py | 20 +- mini-program/app/middleware/auth.py | 25 +- mini-program/app/models/__init__.py | 37 ++ mini-program/app/models/entities.py | 258 +++++++++++ mini-program/app/routers/.DS_Store | Bin 0 -> 6148 bytes mini-program/app/routers/bindings.py | 164 +++++++ mini-program/app/routers/children.py | 102 +++++ mini-program/app/routers/parents.py | 63 +++ mini-program/app/routers/wechat_auth.py | 87 ++++ mini-program/app/service/.DS_Store | Bin 0 -> 6148 bytes mini-program/app/service/__init__.py | 12 + mini-program/app/service/binding.py | 45 ++ mini-program/app/service/child.py | 42 ++ mini-program/app/service/parent.py | 33 ++ mini-program/app/settings.py | 15 + mini-program/data.db | Bin 0 -> 172032 bytes mini-program/database/.DS_Store | Bin 0 -> 6148 bytes mini-program/database/sql/002_new_db.sql | 227 ++++++++++ mini-program/database/sql/003_new_tables.sql | 214 ++++++++++ mini-program/design.md | 379 +++++++++++++++++ mini-program/requirements.txt | 7 +- mini-program/tests/__init__.py | 0 mini-program/tests/test_dao.py | 85 ++++ talkingq-url/utils/s3storage.py | 424 +++++++++++++++++++ 46 files changed, 3713 insertions(+), 268 deletions(-) create mode 100644 .DS_Store create mode 100644 banban-mini/src/services/api.ts create mode 100644 banban-mini/src/services/auth.ts create mode 100644 banban-mini/src/services/binding.ts create mode 100644 banban-mini/src/services/child.ts create mode 100644 mini-program/.DS_Store create mode 100644 mini-program/app/.DS_Store create mode 100644 mini-program/app/dao/.DS_Store create mode 100644 mini-program/app/dao/__init__.py create mode 100644 mini-program/app/dao/binding.py create mode 100644 mini-program/app/dao/child.py create mode 100644 mini-program/app/dao/parent.py create mode 100644 mini-program/app/models/__init__.py create mode 100644 mini-program/app/models/entities.py create mode 100644 mini-program/app/routers/.DS_Store create mode 100644 mini-program/app/routers/bindings.py create mode 100644 mini-program/app/routers/children.py create mode 100644 mini-program/app/routers/parents.py create mode 100644 mini-program/app/routers/wechat_auth.py create mode 100644 mini-program/app/service/.DS_Store create mode 100644 mini-program/app/service/__init__.py create mode 100644 mini-program/app/service/binding.py create mode 100644 mini-program/app/service/child.py create mode 100644 mini-program/app/service/parent.py create mode 100644 mini-program/data.db create mode 100644 mini-program/database/.DS_Store create mode 100644 mini-program/database/sql/002_new_db.sql create mode 100644 mini-program/database/sql/003_new_tables.sql create mode 100644 mini-program/design.md create mode 100644 mini-program/tests/__init__.py create mode 100644 mini-program/tests/test_dao.py create mode 100644 talkingq-url/utils/s3storage.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a9b2e23d68e6fcbd653f0c0d79c1db60d62a403a GIT binary patch literal 6148 zcmeHK&x_MQ6n@jqy6JX>f{NlPuosn9H=fc-n{_RBE#+-MC4nCaG0QAs257 zf)~L*!mD}^QPk_6Jb3hP*o!yc{3tu6X5GCkvdn{-Z!+`VebyusMRR?X1$UH>IgNGy|G}|B(Uq?HX_zy5K`P@%^5>yrPbU zwApk6>SCXH;^Vc=H($RmPmX)rj`!A-z2VeLG=&nl(1u9XRS40C2z=FxqpyFy%GXqV zdOG*Shi}}coOwgK<>9}zRevozWBx9TJ%lRsAO?!8#TY^84w9ba=7ZY{{3Kmk`auda z#_WMP(=O>me$*amKsP&$iQ5Q@8PO@ohn*E_ZGHr_g9Vv8_hToH0vdHuN5q$6QUh4$%drcOFx`DjbfCru%fCZDXO6BK-}}uQ^S{3E zrMUpP`df>0C;Do9aQzdyvU`y$!7T0GR9k2UGz0sb0k%I_SQyqNW&-)vffc_3AR5@s z1>5W{!E7vvb%~ik96=$%6j7KGdBq^Y9Q!S)SC^Ox6y`wW<%7sK6L~`+{Ovfu#npk- z1?pNepc$BCU`9Na+4+Ap|NcK|q@Oecnt^@A05O_Qvw([]) + const [newChildName, setNewChildName] = useState('') + const [saving, setSaving] = useState(false) useEffect(() => { - // 检查是否已登录 - const isLoggedIn = Taro.getStorageSync('isLoggedIn') - if (!isLoggedIn) { - // 未登录,跳转到登录页面 + checkAuth() + }, []) + + const checkAuth = async () => { + const token = getToken() + if (!token) { Taro.redirectTo({ url: '/pages/login/index' }) return } - // 检查是否已绑定设备 - const hasDevice = Taro.getStorageSync('hasDevice') - if (hasDevice) { - // 已绑定设备,跳转到首页 - Taro.reLaunch({ url: '/pages/device/index' }) + try { + const childRes = await getChildren() + setChildren(childRes.items || []) + + if (childRes.items?.length === 0) { + setLoading(false) + return + } + } catch (err) { + console.error('Get children failed:', err) } - }, []) + + try { + const binding = await getCurrentBinding() + if (binding) { + Taro.showToast({ title: '已有设备绑定', icon: 'none' }) + setTimeout(() => { + Taro.reLaunch({ url: '/pages/device/index' }) + }, 1000) + return + } + } catch (err) { + } + setLoading(false) + } const handleScanCode = () => { setIsScanning(true) @@ -29,56 +61,57 @@ export default function Bind() { Taro.scanCode({ onlyFromCamera: true, scanType: ['qrCode', 'barCode'], - success: (res) => { + success: async (res) => { setIsScanning(false) - console.log('扫码结果:', res.result) + const deviceId = res.result - // 模拟绑定设备 - Taro.showLoading({ title: '绑定中...' }) - - setTimeout(() => { - Taro.hideLoading() - - // 保存设备信息 - Taro.setStorageSync('hasDevice', true) - Taro.setStorageSync('deviceInfo', { - id: res.result || 'DEVICE_' + Date.now(), - name: '小夏的伴伴', - status: 'online', - battery: 82, - bindTime: new Date().toLocaleString() - }) - - Taro.showToast({ - title: '设备绑定成功', - icon: 'success', - duration: 2000, - complete: () => { - // 绑定成功,跳转到首页 - setTimeout(() => { - Taro.reLaunch({ url: '/pages/device/index' }) - }, 500) - } - }) - }, 1500) + if (!deviceId) { + Taro.showToast({ title: '无效的设备码', icon: 'none' }) + return + } + + await doBind(deviceId) }, fail: (err) => { setIsScanning(false) - console.error('扫码失败:', err) - - // 用户取消扫码,不显示错误 if (err.errMsg && err.errMsg.includes('cancel')) { return } - - Taro.showToast({ - title: '扫码失败,请重试', - icon: 'none' - }) + Taro.showToast({ title: '扫码失败', icon: 'none' }) } }) } + const doBind = async (deviceId: string) => { + if (children.length === 0) { + Taro.showToast({ title: '请先添加儿童', icon: 'none' }) + return + } + + Taro.showLoading({ title: '绑定中...' }) + + try { + await directBind({ + device_id: deviceId, + child_id: children[0].child_id + }) + Taro.hideLoading() + Taro.showToast({ + title: '设备绑定成功', + icon: 'success', + duration: 1500, + complete: () => { + setTimeout(() => { + Taro.reLaunch({ url: '/pages/device/index' }) + }, 500) + } + }) + } catch (err: any) { + Taro.hideLoading() + Taro.showToast({ title: err.message || '绑定失败', icon: 'none' }) + } + } + const handleManualInput = () => { Taro.showModal({ title: '手动输入设备码', @@ -86,37 +119,60 @@ export default function Bind() { placeholderText: '请输入设备底部的设备码', success: (res) => { if (res.confirm && res.content) { - // 模拟绑定设备 - Taro.showLoading({ title: '绑定中...' }) - - setTimeout(() => { - Taro.hideLoading() - - Taro.setStorageSync('hasDevice', true) - Taro.setStorageSync('deviceInfo', { - id: res.content, - name: '小夏的伴伴', - status: 'online', - battery: 82, - bindTime: new Date().toLocaleString() - }) - - Taro.showToast({ - title: '设备绑定成功', - icon: 'success', - duration: 2000, - complete: () => { - setTimeout(() => { - Taro.reLaunch({ url: '/pages/device/index' }) - }, 500) - } - }) - }, 1500) + doBind(res.content) } } }) } + const handleAddChild = async () => { + if (!newChildName.trim()) { + Taro.showToast({ title: '请输入儿童姓名', icon: 'none' }) + return + } + setSaving(true) + try { + await createChild({ child_name: newChildName.trim() }) + Taro.showToast({ title: '添加成功', icon: 'success' }) + const childRes = await getChildren() + setChildren(childRes.items || []) + } catch (err) { + Taro.showToast({ title: '添加失败', icon: 'none' }) + } finally { + setSaving(false) + } + } + + if (loading) { + return ( + + 加载中... + + ) + } + + if (children.length === 0) { + return ( + + + 请先添加儿童 + 绑定设备前需要先添加儿童信息 + + setNewChildName(e.detail.value)} + /> + + + {saving ? '保存中...' : '确认添加'} + + + + ) + } + return ( @@ -170,4 +226,4 @@ export default function Bind() { ) -} +} \ No newline at end of file diff --git a/banban-mini/src/pages/device/index.tsx b/banban-mini/src/pages/device/index.tsx index df306d2..210aff6 100644 --- a/banban-mini/src/pages/device/index.tsx +++ b/banban-mini/src/pages/device/index.tsx @@ -1,6 +1,7 @@ import { View, Text, Switch, Slider, Image } from '@tarojs/components' import { useState, useEffect } from 'react' import Taro from '@tarojs/taro' +import { getCurrentBinding } from '@/services/binding' import './index.scss' interface DeviceData { @@ -20,35 +21,59 @@ export default function Device() { const [hasDevice, setHasDevice] = useState(false) useEffect(() => { - // 检查登录状态 - const isLoggedIn = Taro.getStorageSync('isLoggedIn') - if (!isLoggedIn) { + console.log('[Device] Checking login status...') + const token = Taro.getStorageSync('token') + console.log('[Device] Token exists:', !!token) + + if (!token) { + console.log('[Device] No token, redirecting to login') Taro.redirectTo({ url: '/pages/login/index' }) return } - // 检查设备绑定状态 - const deviceInfo = Taro.getStorageSync('deviceInfo') - const hasDeviceFlag = Taro.getStorageSync('hasDevice') - - setHasDevice(!!hasDeviceFlag) - - if (deviceInfo && hasDeviceFlag) { - setDevice({ - id: deviceInfo.id, - name: deviceInfo.name || '小夏的伴伴', - status: deviceInfo.status || 'online', - battery: deviceInfo.battery || 82, - daysLeft: 4, - sleepEnabled: true, - sleepTime: '每天 22:00 - 07:00', - volume: 50 - }) - } - - setIsLoading(false) + loadDeviceInfo() }, []) + const loadDeviceInfo = async () => { + try { + const binding = await getCurrentBinding() + console.log('[Device] Got binding:', binding) + + if (binding) { + setHasDevice(true) + Taro.setStorageSync('hasDevice', '1') + Taro.setStorageSync('deviceInfo', { + id: binding.device_id, + name: '伴伴设备', + status: 'online', + battery: 82 + }) + setDevice({ + id: binding.device_id, + name: '伴伴设备', + status: 'online', + battery: 82, + daysLeft: 4, + sleepEnabled: true, + sleepTime: '每天 22:00 - 07:00', + volume: 50 + }) + } else { + setHasDevice(false) + Taro.removeStorageSync('hasDevice') + Taro.removeStorageSync('deviceInfo') + } + } catch (err: any) { + console.log('[Device] No binding or error:', err?.message || err) + if (err?.status === 404) { + setHasDevice(false) + Taro.removeStorageSync('hasDevice') + Taro.removeStorageSync('deviceInfo') + } + } + setIsLoading(false) + } + const handleSleepToggle = (e: any) => { if (!device) return setDevice({ ...device, sleepEnabled: e.detail.value }) diff --git a/banban-mini/src/pages/login/index.tsx b/banban-mini/src/pages/login/index.tsx index b4b638b..694eee3 100644 --- a/banban-mini/src/pages/login/index.tsx +++ b/banban-mini/src/pages/login/index.tsx @@ -1,51 +1,118 @@ import { View, Text, Button } from '@tarojs/components' import { useEffect } from 'react' import Taro from '@tarojs/taro' +import { BASE_URL, request } from '@/services/api' import './index.scss' export default function Login() { useEffect(() => { - // 检查是否已登录 - const isLoggedIn = Taro.getStorageSync('isLoggedIn') - - if (isLoggedIn) { - // 已登录,跳转到首页 + console.log('[Login] Page mounted, checking token...') + const token = Taro.getStorageSync('token') + console.log('[Login] Token exists:', !!token) + if (token) { + console.log('[Login] Already logged in, redirecting to device page') Taro.reLaunch({ url: '/pages/device/index' }) } }, []) - const handleLogin = () => { - Taro.showLoading({ title: '登录中...' }) - - // 调用微信登录 - Taro.login({ - success: (loginRes) => { - console.log('微信登录成功:', loginRes) - - // 模拟后端验证 - 实际项目中需要将code发送到服务器 - // 这里假设登录成功 - Taro.setStorageSync('isLoggedIn', true) - Taro.setStorageSync('userInfo', { - nickName: '微信家长', - avatarUrl: '', - isAdmin: true, - openid: loginRes.code + const doWeChatLogin = async () => { + try { + console.log('[Login] Starting WeChat login...') + Taro.showLoading({ title: '登录中...' }) + + // 1. 调用微信登录获取 code + console.log('[Login] Step 1: Calling Taro.login()...') + const loginRes = await Taro.login() + const code = loginRes.code + console.log('[Login] Got code:', code ? 'yes' : 'no') + + // 2. 获取用户头像信息 (可选) + let userInfo = null + try { + console.log('[Login] Step 2: Calling Taro.getUserProfile()...') + const profileRes = await Taro.getUserProfile({ + desc: '用于完善用户资料' }) - - Taro.hideLoading() - - // 登录成功后跳转到首页 + userInfo = profileRes.userInfo + console.log('[Login] Got userInfo:', !!userInfo) + } catch (e) { + console.log('[Login] getUserProfile failed (expected if user denied):', e) + } + + // 3. 调用后端登录接口 (微信登录) + console.log('[Login] Step 3: Calling backend /auth/login...') + const res = await request<{ access_token: string; expires_in: number; user_id: number }>('/auth/login', { + method: 'POST', + data: { + code: code, // 微信 code + nickname: userInfo?.nickName, + avatar_url: userInfo?.avatarUrl + } + }) + console.log('[Login] Backend login success, user_id:', res.user_id) + + // 4. 保存 token 和用户信息 + console.log('[Login] Step 4: Saving token and user info...') + Taro.setStorageSync('token', res.access_token) + Taro.setStorageSync('user_id', res.user_id) + Taro.setStorageSync('expires_in', res.expires_in) + + // 保存用户信息用于显示 + if (userInfo) { + Taro.setStorageSync('userInfo', { + nickname: userInfo.nickName, + avatar_url: userInfo.avatarUrl + }) + } + + // 5. 如果获取到了用户信息,发送到后端更新 + if (userInfo) { + console.log('[Login] Step 5: Updating user profile...') + try { + await request(`/parents/${res.user_id}`, { + method: 'PATCH', + data: { + nickname: userInfo.nickName, + avatar_url: userInfo.avatarUrl + } + }) + } catch (e) { + console.log('[Login] Update user profile failed:', e) + } + } + + Taro.hideLoading() + console.log('[Login] Step 6: Login complete, showing success toast') + Taro.showToast({ title: '登录成功', icon: 'success' }) + + // 6. 跳转到首页 + console.log('[Login] Redirecting to /pages/device/index...') + setTimeout(() => { Taro.reLaunch({ url: '/pages/device/index' }) - }, - fail: (err) => { - Taro.hideLoading() - console.error('微信登录失败:', err) + }, 500) + + } catch (err: any) { + Taro.hideLoading() + console.error('[Login] Login failed:', err) + + // 如果是 401 错误,尝试通过创建新用户登录 + if (err.status === 401) { Taro.showToast({ title: '登录失败,请重试', icon: 'none' }) + } else { + Taro.showToast({ + title: err.message || '网络错误,请重试', + icon: 'none' + }) } - }) + } + } + + const handleLogin = () => { + console.log('Button clicked, starting login...') + doWeChatLogin() } return ( @@ -77,8 +144,7 @@ export default function Login() {