Browse Source

refactor(api): 统一管理网络请求并迁移至 src/api

- 新增 src/api/physical.ts 文件,封装体格相关接口(listPhysical、addPhysical、deletePhysical)
- 扩展 src/api/user.ts,添加用户信息、登录、头像上传下载及地理位置相关函数
- 迁移所有页面中对 uni.request、uni.uploadFile 和 uni.downloadFile 的直接调用
- 替换页面内重复的用户信息获取逻辑为统一的 fetchUserInfo 函数
- 封装头像上传下载功能并替换原有实现,提升代码复用性与维护性
- 更新医生端、患者端及其家属端多个页面以使用新的 API 封装方法
- 删除页面中的原始网络请求代码,保留 UI 控制与数据处理逻辑
- 优化上传头像逻辑,增强错误处理与用户体验反馈
- 完成地理编码接口 geocodeNearest 的封装与调用替换
- 提供清晰的接口文档说明和后续迁移建议,便于团队协作开发
mcbaiyun 1 month ago
parent
commit
527f01972f

+ 154 - 0
docs/接口迁移进度.md

@@ -0,0 +1,154 @@
+# 接口迁移进度(自动扫描结果)
+
+说明:此文档列出当前代码库中发现的直接发起网络请求(`uni.request`)位置,建议将这些请求迁移到 `src/api` 下统一管理。每一项包含:文件路径、发现的后端路径/用途、建议目标 `api` 文件、当前状态与建议操作。
+
+---
+
+## 概览
+- 扫描方式:在 `src/**` 中查找 `uni.request(...)`。
+- 发现的页面(不含已在 `src/api` 中的文件):
+  - `src/pages/patient/profile/index.vue`
+  - `src/pages/patient/profile/infos/base-info.vue`
+  - `src/pages/patient/health/details/physical.vue`
+  - `src/pages/patient/index/index.vue`
+  - `src/pages/patient-family/profile/infos/base-info.vue`
+  - `src/pages/patient-family/profile/index.vue`
+  - `src/pages/patient-family/index/index.vue`
+  - `src/pages/doctor/profile/infos/base-info.vue`
+  - `src/pages/public/profile/index.vue`
+  - `src/pages/public/profile/qr/index.vue`
+  - `src/pages/public/login/index.vue`
+
+> 注意:`src/api` 目录内的文件(例如 `bloodGlucose.ts`、`bloodPressure.ts`)本身已经在封装请求,这里不再重复列出。
+
+---
+
+## 逐文件详情与建议
+
+### 1. `src/pages/patient/profile/infos/base-info.vue`
+- 发现的后端接口(在文件中直接使用):
+  - `POST https://wx.baiyun.work/user_info`
+  - `POST https://wx.baiyun.work/update_user_info`
+  - `POST https://wx.baiyun.work/user/avatar/upload`(`uni.uploadFile`)
+  - `GET https://wx.baiyun.work/user/avatar/{userId}`(头像下载)
+  - `GET https://wx.baiyun.work/geo/nearest?latitude=...&longitude=...`(地理编码/附近定位)
+- 建议目标 `api` 文件:`src/api/user.ts`(新增或扩展函数)
+- 当前状态:已迁移(页面已改为调用 `src/api/user.ts` 中的 `fetchUserInfo`)
+- 建议操作:
+  1. 在 `src/api/user.ts` 中添加 `fetchUserInfo()`、`updateUserInfo(payload)`、`uploadAvatar(filePath)`(或 `uploadAvatarFormData`)、`downloadAvatar(userId)`、`geocodeNearest(lat, lon)`。
+  2. 将页面内的 `uni.request`/`uni.uploadFile`/`uni.downloadFile` 调用替换为导入的 API 函数,并仅在页面内保留 UI 逻辑(loading、提示、表单校验等)。
+  
+  - 当前状态:已迁移(头像上传/下载、地理编码与更新请求均已替换为 `src/api/user.ts` 中的封装;页面保留 UI 逻辑)。
+
+---
+
+### 2. `src/pages/doctor/profile/infos/base-info.vue`
+- 与患者 `base-info.vue` 内容基本相同,使用的接口也与用户信息相关(`user_info`、头像上传、geo 等)。
+- 建议目标:`src/api/user.ts`
+- 当前状态:已迁移(页面已改为调用 `src/api/user.ts` 中的 `fetchUserInfo`)
+- 建议操作:与第1项相同,统一使用 `user.ts` 中导出的函数。
+
+---
+
+### 3. `src/pages/patient-family/profile/infos/base-info.vue`
+- 与上面类似,调用 `user_info`、头像上传、geo。建议目标 `src/api/user.ts`。
+ - 当前状态:已迁移(头像上传/下载、地理编码与更新请求均已替换为 `src/api/user.ts` 中的封装;页面保留 UI 逻辑)。
+
+---
+
+### 4. `src/pages/public/profile/index.vue` 和 `src/pages/public/profile/qr/index.vue`
+- 主要使用 `POST /user_info` 拉取用户信息;`qr` 页面也会拉取 `user_info` 用于生成二维码。
+- 建议目标:`src/api/user.ts`(函数名例如 `fetchUserInfo`)
+- 当前状态:已迁移(页面已改为调用 `src/api/user.ts` 中的 `fetchUserInfo`)
+
+---
+
+### 5. `src/pages/public/login/index.vue`
+- 使用登录流程并调用:`POST https://wx.baiyun.work/get_openid`(后端返回 token/openid)
+- 建议目标:`src/api/user.ts`(函数名例如 `loginWithWx(code, role)` 或 `getOpenId(code, role)`)
+- 当前状态:已迁移(已抽离至 `src/api/user.ts`,新增函数 `loginWithWx(code, role)`;`src/pages/public/login/index.vue` 已替换为调用该函数)
+
+---
+
+### 6. `src/pages/patient/index/index.vue`
+- 页面内调用 `POST /user_info` 并在缺少头像时下载 `GET /user/avatar/{userId}`。
+- 建议目标:`src/api/user.ts`(`fetchUserInfo`, `downloadAvatar`)
+ - 当前状态:已迁移(页面已改为调用 `src/api/user.ts` 中的 `fetchUserInfo`,并在需要时使用 `downloadAvatar` 封装下载头像)
+
+---
+
+### 7. `src/pages/patient/health/details/physical.vue`
+- 发现接口:
+  - `POST https://wx.baiyun.work/physical/list`
+  - `POST https://wx.baiyun.work/physical/add`
+  - `POST https://wx.baiyun.work/physical/delete`
+- 建议目标:新增 `src/api/physical.ts`,包含 `listPhysical`、`addPhysical`、`deletePhysical` 等函数。
+- 当前状态:未迁移
+- 当前状态:已迁移(已添加 `src/api/physical.ts` 并替换页面中的 `list/add/delete` 请求)
+
+---
+
+### 8. `src/pages/patient-family/index/index.vue`、`src/pages/patient-family/profile/index.vue`、其它首页和列表页
+- grep 显示这些文件中也存在 `uni.request` 调用(多数为 `user_info` 或与列表/分页相关的后端接口)。
+- 建议目标:根据具体接口归类到 `user.ts`、`doctor.ts`、`bloodGlucose.ts`、`bloodPressure.ts`、或新增模块(如 `physical.ts`、`notification.ts` 等)
+ - 当前状态:已迁移(涉及 `user_info` 的调用已改为使用 `src/api/user.ts::fetchUserInfo`;其它接口视情况待迁移)
+
+---
+
+## 推荐新增/调整的 `src/api` 接口函数(示例)
+- `src/api/user.ts`(扩展)
+  - `export async function fetchUserInfo()`
+  - `export async function updateUserInfo(payload)`
+  - `export async function uploadAvatar(filePath)`
+  - `export async function downloadAvatar(userId)`
+  - `export async function getOpenId(code, role)`
+  - `(保留或兼容现有的 getUserInfo(userId))`
+
+- `src/api/physical.ts`(新建)
+  - `export async function listPhysical(params)`
+  - `export async function addPhysical(payload)`
+  - `export async function deletePhysical(id)`
+
+
+## 迁移示例(把页面内的请求提取到 `src/api/user.ts`)
+```ts
+// src/api/user.ts
+export async function fetchUserInfo() {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/user_info',
+    method: 'POST',
+    header: { 'content-type': 'application/json', Authorization: `Bearer ${token}` },
+    data: {}
+  })
+  return res
+}
+```
+
+页面中的调用替换为:
+```ts
+import { fetchUserInfo } from '@/api/user'
+
+const resp = await fetchUserInfo()
+// 只在页面处理 UI(loading、跳转、toast)与数据映射
+```
+
+## 建议迁移流程(小规模、安全)
+- 优先迁移全局或复用率高的用户相关接口(`user_info`、登录、头像等),因为多个页面复用了这些调用。
+- 为每个被迁移的接口:
+  1. 在 `src/api` 中添加函数并导出,写好入参/返回类型注释(TypeScript)。
+  2. 在页面中导入新函数,替换原来的 `uni.request` 调用,保留页面的 UI/错误处理逻辑。
+  3. 运行项目(或编译/类型检查)验证不会破坏页面行为。
+- 对于上传/下载文件(`uni.uploadFile`/`uni.downloadFile`),建议把真正的网络操作封装在 `api` 中,但可以选择让页面传递 `filePath` 并由 API 返回解析后的结果。
+
+## 后续建议/可选项
+- 我可以为你:
+  - (1)自动在 `src/api/user.ts` 中添加上面列出的函数骨架并替换某些页面的调用(逐文件进行,先做 `base-info.vue` 或 `login.vue`);
+  - (2)生成一个更详细的迁移计划(含优先级与变更 PR 模板);
+  - (3)写一个简单的 codemod(大量替换时谨慎使用)。
+
+---
+
+> 注:本次扫描仅基于 `uni.request` 的静态文本搜索,未检测动态构造的请求函数、第三方库封装(如 `axios`、自定义 `request` helper)或服务端渲染调用。若项目中存在其他请求模式,请告知我以扩展扫描策略。
+
+

+ 42 - 0
src/api/physical.ts

@@ -0,0 +1,42 @@
+// 体格(physical)相关接口封装
+export async function listPhysical(params: { pageNum: number; pageSize: number; startTime?: string; endTime?: string }) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/physical/list',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json',
+      Authorization: `Bearer ${token}`
+    },
+    data: params
+  })
+  return res
+}
+
+export async function addPhysical(payload: { height: number; weight: number; measureTime: string }) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/physical/add',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json',
+      Authorization: `Bearer ${token}`
+    },
+    data: payload
+  })
+  return res
+}
+
+export async function deletePhysical(id: string) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/physical/delete',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json',
+      Authorization: `Bearer ${token}`
+    },
+    data: { id }
+  })
+  return res
+}

+ 84 - 0
src/api/user.ts

@@ -10,4 +10,88 @@ export async function getUserInfo(userId: number) {
     }
   })
   return res
+}
+
+// 使用微信 code 登录并获取 token/openid
+export async function loginWithWx(code: string, role: number) {
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/get_openid',
+    method: 'POST',
+    header: { 'Content-Type': 'application/json' },
+    data: { code, role }
+  })
+  return res
+}
+
+// 拉取当前登录用户信息(统一包装 POST /user_info)
+export async function fetchUserInfo() {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/user_info',
+    method: 'POST',
+    header: {
+      'Content-Type': 'application/json',
+      Authorization: `Bearer ${token}`
+    },
+    data: {}
+  })
+  return res
+}
+
+// 更新用户信息 POST /update_user_info
+export async function updateUserInfo(payload: any) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: 'https://wx.baiyun.work/update_user_info',
+    method: 'POST',
+    header: {
+      'Content-Type': 'application/json',
+      Authorization: `Bearer ${token}`
+    },
+    data: payload
+  })
+  return res
+}
+
+// 上传头像(封装 uni.uploadFile)
+export function uploadAvatar(filePath: string) {
+  return new Promise<any>((resolve, reject) => {
+    const token = uni.getStorageSync('token')
+    uni.uploadFile({
+      url: 'https://wx.baiyun.work/user/avatar/upload',
+      filePath,
+      name: 'file',
+      header: { Authorization: `Bearer ${token}` },
+      success: (uploadRes: any) => {
+        try {
+          const parsed = JSON.parse(uploadRes.data || '{}')
+          resolve(parsed)
+        } catch (e) {
+          resolve({ data: uploadRes.data })
+        }
+      },
+      fail: (err: any) => reject(err)
+    })
+  })
+}
+
+// 下载头像(封装 uni.downloadFile)
+export async function downloadAvatar(userId: string | number) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.downloadFile({
+    url: `https://wx.baiyun.work/user/avatar/${userId}`,
+    header: { Authorization: `Bearer ${token}` }
+  })
+  return res
+}
+
+// 地理编码/最近位置查询
+export async function geocodeNearest(latitude: number, longitude: number) {
+  const token = uni.getStorageSync('token')
+  const res: any = await uni.request({
+    url: `https://wx.baiyun.work/geo/nearest?latitude=${latitude}&longitude=${longitude}`,
+    method: 'GET',
+    header: { Authorization: `Bearer ${token}` }
+  })
+  return res
 }

+ 2 - 10
src/pages/doctor/index/index.vue

@@ -98,6 +98,7 @@ import { ref, computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
+import { fetchUserInfo as fetchUserInfoApi } from '@/api/user'
 
 const user = ref<{ avatar?: string; nickname?: string; title?: string }>({})
 
@@ -156,16 +157,7 @@ const fetchUserInfo = async () => {
   try {
     const token = uni.getStorageSync('token')
     if (!token) return
-    uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any

+ 3 - 15
src/pages/doctor/profile/index.vue

@@ -44,6 +44,7 @@ import { ref } from 'vue'
 import { computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi } from '@/api/user'
 import TabBar from '@/components/tab-bar.vue'
 import { isLoggedIn as checkLogin, getRole } from '@/composables/useAuth'
 
@@ -87,15 +88,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any
@@ -113,12 +106,7 @@ const fetchUserInfo = async () => {
         const userId = resp.data.id || resp.data.userId
         if (userId) {
           try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
-              }
-            })
+            const downloadRes = await downloadAvatarApi(userId)
             if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
               resp.data.avatar = downloadRes.tempFilePath
             }

+ 29 - 83
src/pages/doctor/profile/infos/base-info.vue

@@ -76,6 +76,7 @@ import { ref } from 'vue'
 const loading = ref(false)
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi, uploadAvatar as uploadAvatarApi, updateUserInfo as updateUserInfoApi, geocodeNearest as geocodeNearestApi } from '@/api/user'
 
 const form = ref({
   avatar: '',
@@ -103,15 +104,7 @@ const fetchUserInfo = async () => {
     loading.value = true
     // 使用 mask: true 阻止用户在加载/下载时继续交互
     uni.showLoading({ title: '加载中...', mask: true })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     
     console.log('fetchUserInfo response:', response)
     if (response.statusCode === 401) {
@@ -129,12 +122,7 @@ const fetchUserInfo = async () => {
         const userId = d.id || d.userId
         if (userId) {
           try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
-              }
-            })
+            const downloadRes = await downloadAvatarApi(userId)
             if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
               d.avatar = downloadRes.tempFilePath
             }
@@ -190,59 +178,32 @@ const setSex = (s: number) => {
 }
 
 /**
- * 上传头像到 `user/avatar/upload`,返回上传后的 URL(如果服务器返回)
+ * 使用 `src/api/user.ts` 中的 `uploadAvatar` 封装上传并返回上传后的 URL
  */
-const uploadAvatar = (filePath: string) => {
-  return new Promise<string>((resolve, reject) => {
-    if (!filePath) {
-      reject(new Error('no filePath'))
-      return
+const uploadAvatar = async (filePath: string) => {
+  if (!filePath) throw new Error('no filePath')
+  avatarUploading.value = true
+  uni.showLoading({ title: '上传头像中...' })
+  try {
+    const resp: any = await uploadAvatarApi(filePath)
+    uni.hideLoading()
+    avatarUploading.value = false
+    if (resp && resp.code === 200) {
+      const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
+      if (uploadedUrl) form.value.avatar = uploadedUrl
+      avatarNeedsUpload.value = false
+      uni.showToast({ title: '头像上传成功', icon: 'success' })
+      return uploadedUrl || ''
     }
-    avatarUploading.value = true
-    uni.showLoading({ title: '上传头像中...' })
-    const token = uni.getStorageSync('token')
-    uni.uploadFile({
-      url: 'https://wx.baiyun.work/user/avatar/upload',
-      filePath,
-      name: 'file',
-      header: {
-        Authorization: `Bearer ${token}`
-      },
-      success: (uploadRes: any) => {
-        uni.hideLoading()
-        try {
-          const resp = JSON.parse(uploadRes.data || '{}') as any
-          if (resp && resp.code === 200) {
-            // 尝试从响应中取出 URL 字段(后端可能返回 data.url 或 data.path 等)
-            const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
-            if (uploadedUrl) {
-              form.value.avatar = uploadedUrl
-            }
-            // 标记为已上传
-            avatarNeedsUpload.value = false
-            uni.showToast({ title: '头像上传成功', icon: 'success' })
-            resolve(uploadedUrl || '')
-          } else {
-            uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
-            reject(new Error(resp?.message || 'upload failed'))
-          }
-        } catch (err) {
-          console.error('upload parse error', err)
-          uni.showToast({ title: '头像上传失败', icon: 'none' })
-          reject(err)
-        }
-      },
-      fail: (err: any) => {
-        uni.hideLoading()
-        console.error('upload fail', err)
-        uni.showToast({ title: '头像上传失败', icon: 'none' })
-        reject(err)
-      },
-      complete: () => {
-        avatarUploading.value = false
-      }
-    })
-  })
+    uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
+    throw new Error(resp?.message || 'upload failed')
+  } catch (err) {
+    uni.hideLoading()
+    avatarUploading.value = false
+    console.error('upload error', err)
+    uni.showToast({ title: '头像上传失败', icon: 'none' })
+    throw err
+  }
 }
 
 /**
@@ -358,14 +319,7 @@ const getCurrentLocation = async () => {
 
     // 调用用户自己的地理编码接口
     const token = uni.getStorageSync('token')
-    const geocodeRes = await uni.request({
-      url: `https://wx.baiyun.work/geo/nearest?latitude=${latitude}&longitude=${longitude}`,
-      method: 'GET',
-      header: {
-        'Authorization': `Bearer ${token}`
-      }
-    })
-
+    const geocodeRes = await geocodeNearestApi(latitude, longitude)
     const data = geocodeRes.data as any
     if (data && data.code === 200 && data.data) {
       // 解析 data 字段中的 JSON 字符串
@@ -459,15 +413,7 @@ const onSubmit = async () => {
     const payload: any = JSON.parse(JSON.stringify(form.value))
     if (payload.avatar !== undefined) delete payload.avatar
 
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/update_user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: payload
-    })
+    const response = await updateUserInfoApi(payload)
     console.log('Update response:', response)
     const resp = response.data as any
     if (resp && resp.code === 200) {

+ 2 - 9
src/pages/patient-family/index/index.vue

@@ -117,6 +117,7 @@
 import { ref, computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi } from '@/api/user'
 import TabBar from '@/components/tab-bar.vue'
 
 const user = ref<{ avatar?: string; nickname?: string }>({})
@@ -177,15 +178,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any

+ 2 - 9
src/pages/patient-family/profile/index.vue

@@ -44,6 +44,7 @@ import { ref } from 'vue'
 import { computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi } from '@/api/user'
 import TabBar from '@/components/tab-bar.vue'
 import { isLoggedIn as checkLogin, getRole } from '@/composables/useAuth'
 
@@ -87,15 +88,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any

+ 29 - 83
src/pages/patient-family/profile/infos/base-info.vue

@@ -76,6 +76,7 @@ import { ref } from 'vue'
 const loading = ref(false)
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi, uploadAvatar as uploadAvatarApi, updateUserInfo as updateUserInfoApi, geocodeNearest as geocodeNearestApi } from '@/api/user'
 
 const form = ref({
   avatar: '',
@@ -103,15 +104,7 @@ const fetchUserInfo = async () => {
     loading.value = true
     // 使用 mask: true 阻止用户在加载/下载时继续交互
     uni.showLoading({ title: '加载中...', mask: true })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     
     console.log('fetchUserInfo response:', response)
     if (response.statusCode === 401) {
@@ -129,12 +122,7 @@ const fetchUserInfo = async () => {
         const userId = d.id || d.userId
         if (userId) {
           try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
-              }
-            })
+            const downloadRes = await downloadAvatarApi(userId)
             if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
               d.avatar = downloadRes.tempFilePath
             }
@@ -190,59 +178,32 @@ const setSex = (s: number) => {
 }
 
 /**
- * 上传头像到 `user/avatar/upload`,返回上传后的 URL(如果服务器返回)
+ * 使用 `src/api/user.ts` 中的 `uploadAvatar` 封装上传并返回上传后的 URL
  */
-const uploadAvatar = (filePath: string) => {
-  return new Promise<string>((resolve, reject) => {
-    if (!filePath) {
-      reject(new Error('no filePath'))
-      return
+const uploadAvatar = async (filePath: string) => {
+  if (!filePath) throw new Error('no filePath')
+  avatarUploading.value = true
+  uni.showLoading({ title: '上传头像中...' })
+  try {
+    const resp: any = await uploadAvatarApi(filePath)
+    uni.hideLoading()
+    avatarUploading.value = false
+    if (resp && resp.code === 200) {
+      const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
+      if (uploadedUrl) form.value.avatar = uploadedUrl
+      avatarNeedsUpload.value = false
+      uni.showToast({ title: '头像上传成功', icon: 'success' })
+      return uploadedUrl || ''
     }
-    avatarUploading.value = true
-    uni.showLoading({ title: '上传头像中...' })
-    const token = uni.getStorageSync('token')
-    uni.uploadFile({
-      url: 'https://wx.baiyun.work/user/avatar/upload',
-      filePath,
-      name: 'file',
-      header: {
-        Authorization: `Bearer ${token}`
-      },
-      success: (uploadRes: any) => {
-        uni.hideLoading()
-        try {
-          const resp = JSON.parse(uploadRes.data || '{}') as any
-          if (resp && resp.code === 200) {
-            // 尝试从响应中取出 URL 字段(后端可能返回 data.url 或 data.path 等)
-            const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
-            if (uploadedUrl) {
-              form.value.avatar = uploadedUrl
-            }
-            // 标记为已上传
-            avatarNeedsUpload.value = false
-            uni.showToast({ title: '头像上传成功', icon: 'success' })
-            resolve(uploadedUrl || '')
-          } else {
-            uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
-            reject(new Error(resp?.message || 'upload failed'))
-          }
-        } catch (err) {
-          console.error('upload parse error', err)
-          uni.showToast({ title: '头像上传失败', icon: 'none' })
-          reject(err)
-        }
-      },
-      fail: (err: any) => {
-        uni.hideLoading()
-        console.error('upload fail', err)
-        uni.showToast({ title: '头像上传失败', icon: 'none' })
-        reject(err)
-      },
-      complete: () => {
-        avatarUploading.value = false
-      }
-    })
-  })
+    uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
+    throw new Error(resp?.message || 'upload failed')
+  } catch (err) {
+    uni.hideLoading()
+    avatarUploading.value = false
+    console.error('upload error', err)
+    uni.showToast({ title: '头像上传失败', icon: 'none' })
+    throw err
+  }
 }
 
 /**
@@ -358,14 +319,7 @@ const getCurrentLocation = async () => {
 
     // 调用用户自己的地理编码接口
     const token = uni.getStorageSync('token')
-    const geocodeRes = await uni.request({
-      url: `https://wx.baiyun.work/geo/nearest?latitude=${latitude}&longitude=${longitude}`,
-      method: 'GET',
-      header: {
-        'Authorization': `Bearer ${token}`
-      }
-    })
-
+    const geocodeRes = await geocodeNearestApi(latitude, longitude)
     const data = geocodeRes.data as any
     if (data && data.code === 200 && data.data) {
       // 解析 data 字段中的 JSON 字符串
@@ -459,15 +413,7 @@ const onSubmit = async () => {
     const payload: any = JSON.parse(JSON.stringify(form.value))
     if (payload.avatar !== undefined) delete payload.avatar
 
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/update_user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: payload
-    })
+    const response = await updateUserInfoApi(payload)
     console.log('Update response:', response)
     const resp = response.data as any
     if (resp && resp.code === 200) {

+ 4 - 36
src/pages/patient/health/details/physical.vue

@@ -105,6 +105,7 @@ import CustomNav from '@/components/custom-nav.vue'
 import ScaleRuler from '@/components/scale-ruler.vue'
 import { getWeekStart, getWeekEnd, formatDisplayDate, formatPickerDate, daysInMonth, getTodayStart, isAfterTodayDate, isMonthAfterToday, isWeekAfterToday } from '@/utils/date'
 import { getWindowWidth, rpxToPx } from '@/utils/platform'
+import { listPhysical, addPhysical, deletePhysical } from '@/api/physical'
 
 type RecordItem = { id: string; date: string; h: number; w: number; bmi: number }
 
@@ -205,20 +206,7 @@ async function fetchRecords() {
 
   try {
     const token = uni.getStorageSync('token')
-    const res = await uni.request({
-      url: 'https://wx.baiyun.work/physical/list',
-      method: 'POST',
-      data: {
-        pageNum: 1,
-        pageSize: 100,
-        startTime,
-        endTime
-      },
-      header: {
-        'content-type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      }
-    })
+    const res = await listPhysical({ pageNum: 1, pageSize: 100, startTime, endTime })
     if (res.statusCode === 401) {
       // Token 无效,清除并跳转登录
       uni.removeStorageSync('token')
@@ -535,19 +523,7 @@ async function confirmAdd() {
   }
   try {
     const token = uni.getStorageSync('token')
-    const res = await uni.request({
-      url: 'https://wx.baiyun.work/physical/add',
-      method: 'POST',
-      data: {
-        height: addHeight.value,
-        weight: addWeight.value,
-        measureTime: new Date(addDate.value).toISOString()
-      },
-      header: {
-        'content-type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      }
-    })
+    const res = await addPhysical({ height: addHeight.value!, weight: addWeight.value!, measureTime: new Date(addDate.value).toISOString() })
     if (res.statusCode === 401) {
       // Token 无效,清除并跳转登录
       uni.removeStorageSync('token')
@@ -583,15 +559,7 @@ async function confirmDeleteRecord(id: string) {
         if (res.confirm) {
           try {
             const token = uni.getStorageSync('token')
-            const delRes = await uni.request({
-              url: 'https://wx.baiyun.work/physical/delete',
-              method: 'POST',
-              data: { id },
-              header: {
-                'content-type': 'application/json',
-                'Authorization': `Bearer ${token}`
-              }
-            })
+            const delRes = await deletePhysical(id)
             if (delRes.statusCode === 401) {
               // Token 无效,清除并跳转登录
               uni.removeStorageSync('token')

+ 8 - 20
src/pages/patient/index/index.vue

@@ -93,6 +93,7 @@ import { ref, computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi } from '@/api/user'
 
 const user = ref<{ avatar?: string; nickname?: string; age?: number }>({})
 
@@ -153,15 +154,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any
@@ -178,20 +171,15 @@ const fetchUserInfo = async () => {
       if (!resp.data.avatar || !resp.data.avatar.startsWith('http')) {
         const userId = resp.data.id || resp.data.userId
         if (userId) {
-          try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
+            try {
+              const downloadRes = await downloadAvatarApi(userId)
+              if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
+                resp.data.avatar = downloadRes.tempFilePath
               }
-            })
-            if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
-              resp.data.avatar = downloadRes.tempFilePath
+            } catch (e) {
+              console.error('Download avatar error:', e)
             }
-          } catch (e) {
-            console.error('Download avatar error:', e)
           }
-        }
       }
       user.value = resp.data
       uni.setStorageSync('user_info', resp.data)

+ 3 - 15
src/pages/patient/profile/index.vue

@@ -49,6 +49,7 @@ import { computed } from 'vue'
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi } from '@/api/user'
 
 const title = ref('个人中心')
 
@@ -90,15 +91,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any
@@ -116,12 +109,7 @@ const fetchUserInfo = async () => {
         const userId = resp.data.id || resp.data.userId
         if (userId) {
           try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
-              }
-            })
+            const downloadRes = await downloadAvatarApi(userId)
             if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
               resp.data.avatar = downloadRes.tempFilePath
             }

+ 29 - 78
src/pages/patient/profile/infos/base-info.vue

@@ -76,6 +76,7 @@ import { ref } from 'vue'
 const loading = ref(false)
 import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi, uploadAvatar as uploadAvatarApi, updateUserInfo as updateUserInfoApi, geocodeNearest as geocodeNearestApi } from '@/api/user'
 
 const form = ref({
   avatar: '',
@@ -103,16 +104,7 @@ const fetchUserInfo = async () => {
     loading.value = true
     // 使用 mask: true 阻止用户在加载/下载时继续交互
     uni.showLoading({ title: '加载中...', mask: true })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
-    
+    const response = await fetchUserInfoApi()
     console.log('fetchUserInfo response:', response)
     if (response.statusCode === 401) {
       // 未授权,跳转登录
@@ -190,59 +182,33 @@ const setSex = (s: number) => {
 }
 
 /**
- * 上传头像到 `user/avatar/upload`,返回上传后的 URL(如果服务器返回)
+ * 使用 `src/api/user.ts` 中的 `uploadAvatar` 封装上传并返回上传后的 URL
  */
-const uploadAvatar = (filePath: string) => {
-  return new Promise<string>((resolve, reject) => {
-    if (!filePath) {
-      reject(new Error('no filePath'))
-      return
+const uploadAvatar = async (filePath: string) => {
+  if (!filePath) throw new Error('no filePath')
+  avatarUploading.value = true
+  uni.showLoading({ title: '上传头像中...' })
+  try {
+    const resp: any = await uploadAvatarApi(filePath)
+    uni.hideLoading()
+    avatarUploading.value = false
+    // uploadAvatarApi 返回解析后的对象(参照 api 实现)
+    if (resp && resp.code === 200) {
+      const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
+      if (uploadedUrl) form.value.avatar = uploadedUrl
+      avatarNeedsUpload.value = false
+      uni.showToast({ title: '头像上传成功', icon: 'success' })
+      return uploadedUrl || ''
     }
-    avatarUploading.value = true
-    uni.showLoading({ title: '上传头像中...' })
-    const token = uni.getStorageSync('token')
-    uni.uploadFile({
-      url: 'https://wx.baiyun.work/user/avatar/upload',
-      filePath,
-      name: 'file',
-      header: {
-        Authorization: `Bearer ${token}`
-      },
-      success: (uploadRes: any) => {
-        uni.hideLoading()
-        try {
-          const resp = JSON.parse(uploadRes.data || '{}') as any
-          if (resp && resp.code === 200) {
-            // 尝试从响应中取出 URL 字段(后端可能返回 data.url 或 data.path 等)
-            const uploadedUrl = resp.data?.url || resp.data?.path || resp.data?.fileUrl || ''
-            if (uploadedUrl) {
-              form.value.avatar = uploadedUrl
-            }
-            // 标记为已上传
-            avatarNeedsUpload.value = false
-            uni.showToast({ title: '头像上传成功', icon: 'success' })
-            resolve(uploadedUrl || '')
-          } else {
-            uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
-            reject(new Error(resp?.message || 'upload failed'))
-          }
-        } catch (err) {
-          console.error('upload parse error', err)
-          uni.showToast({ title: '头像上传失败', icon: 'none' })
-          reject(err)
-        }
-      },
-      fail: (err: any) => {
-        uni.hideLoading()
-        console.error('upload fail', err)
-        uni.showToast({ title: '头像上传失败', icon: 'none' })
-        reject(err)
-      },
-      complete: () => {
-        avatarUploading.value = false
-      }
-    })
-  })
+    uni.showToast({ title: resp?.message || '上传失败', icon: 'none' })
+    throw new Error(resp?.message || 'upload failed')
+  } catch (err) {
+    uni.hideLoading()
+    avatarUploading.value = false
+    console.error('upload error', err)
+    uni.showToast({ title: '头像上传失败', icon: 'none' })
+    throw err
+  }
 }
 
 /**
@@ -358,14 +324,7 @@ const getCurrentLocation = async () => {
 
     // 调用用户自己的地理编码接口
     const token = uni.getStorageSync('token')
-    const geocodeRes = await uni.request({
-      url: `https://wx.baiyun.work/geo/nearest?latitude=${latitude}&longitude=${longitude}`,
-      method: 'GET',
-      header: {
-        'Authorization': `Bearer ${token}`
-      }
-    })
-
+    const geocodeRes = await geocodeNearestApi(latitude, longitude)
     const data = geocodeRes.data as any
     if (data && data.code === 200 && data.data) {
       // 解析 data 字段中的 JSON 字符串
@@ -459,15 +418,7 @@ const onSubmit = async () => {
     const payload: any = JSON.parse(JSON.stringify(form.value))
     if (payload.avatar !== undefined) delete payload.avatar
 
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/update_user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: payload
-    })
+    const response = await updateUserInfoApi(payload)
     console.log('Update response:', response)
     const resp = response.data as any
     if (resp && resp.code === 200) {

+ 2 - 6
src/pages/public/login/index.vue

@@ -25,6 +25,7 @@
 <script setup lang="ts">
 import { ref } from 'vue'
 import CustomNav from '@/components/custom-nav.vue'
+import { loginWithWx } from '@/api/user'
 
 const isLogging = ref(false)
 
@@ -47,12 +48,7 @@ async function onSelectRole(role: number) {
     })
     console.log('uni.login success:', loginRes)
     const code = loginRes.code
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/get_openid',
-      method: 'POST',
-      header: { 'Content-Type': 'application/json' },
-      data: { code: code, role: role }
-    })
+    const response = await loginWithWx(code, role)
     console.log('Backend response:', response)
     // 兼容后端返回格式,提取 token 并打印
     const resp = response.data as any

+ 3 - 15
src/pages/public/profile/index.vue

@@ -42,6 +42,7 @@ import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 import TabBar from '@/components/tab-bar.vue'
 import { isLoggedIn as checkLogin, getRole } from '@/composables/useAuth'
+import { fetchUserInfo as fetchUserInfoApi, downloadAvatar as downloadAvatarApi } from '@/api/user'
 
 const title = ref('个人中心')
 
@@ -83,15 +84,7 @@ const fetchUserInfo = async () => {
     const token = uni.getStorageSync('token')
     if (!token) return
     uni.showLoading({ title: '加载中...' })
-    const response = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any
@@ -109,12 +102,7 @@ const fetchUserInfo = async () => {
         const userId = resp.data.id || resp.data.userId
         if (userId) {
           try {
-            const downloadRes = await uni.downloadFile({
-              url: `https://wx.baiyun.work/user/avatar/${userId}`,
-              header: {
-                Authorization: `Bearer ${token}`
-              }
-            })
+            const downloadRes = await downloadAvatarApi(userId)
             if (downloadRes.statusCode === 200 && downloadRes.tempFilePath) {
               resp.data.avatar = downloadRes.tempFilePath
             }

+ 2 - 9
src/pages/public/profile/qr/index.vue

@@ -33,6 +33,7 @@ import { onShow } from '@dcloudio/uni-app'
 import CustomNav from '@/components/custom-nav.vue'
 // @ts-ignore
 import drawQrcode from 'weapp-qrcode'
+import { fetchUserInfo as fetchUserInfoApi } from '@/api/user'
 
 const user = ref<{ nickname?: string; role?: string | number; openid?: string; wx_openid?: string }>({})
 const qrData = ref('')
@@ -86,15 +87,7 @@ const fetchUserInfo = async () => {
     if (isFetching.value) return
     isFetching.value = true
     uni.showLoading({ title: '加载中...' })
-    const response: any = await uni.request({
-      url: 'https://wx.baiyun.work/user_info',
-      method: 'POST',
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': `Bearer ${token}`
-      },
-      data: {}
-    })
+    const response: any = await fetchUserInfoApi()
     uni.hideLoading()
     console.log('User info response:', response)
     const resp = response.data as any