diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index 7e54581..1d93a80 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -20,7 +20,18 @@ "usedAt": 1776163421500, "industryId": "all" } + ], + "95e0ae51ed494505a477abd8137eaf2b": [ + { + "expertId": "BackendArchitect", + "name": "Joy", + "profession": "后端架构师", + "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/BackendArchitect/BackendArchitect.png", + "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/BackendArchitect/BackendArchitect_zh.md", + "usedAt": 1776172649908, + "industryId": "all" + } ] }, - "lastUpdated": 1776167527349 + "lastUpdated": 1776172717113 } \ No newline at end of file diff --git a/DOC/API手册.md b/DOC/API手册.md new file mode 100644 index 0000000..7546097 --- /dev/null +++ b/DOC/API手册.md @@ -0,0 +1,768 @@ +# OPS 后端 API 使用手册 + +## 概述 + +- **基础URL**: `/api` +- **请求格式**: JSON Body(通过 `userCookieValue` 传cookie,`data` 传业务数据) +- **认证方式**: Cookie-based authentication +- **响应格式**: JSON + +## 通用请求格式 + +```json +{ + "userCookieValue": "cookie字符串", + "data": { + // 业务参数 + } +} +``` + +## 通用响应格式 + +```json +{ + "err_code": 0, + "err_msg": "apiOK", + "return": { + // 响应数据 + } +} +``` + +## 错误码 + +| err_code | err_msg | 说明 | +|----------|---------|------| +| 0 | apiOK | 成功 | +| -1 | apiErr | 服务器错误 | +| -2 | postErr | POST请求错误 | +| -3 | jsonErr | JSON解析错误 | +| -31 | jsonErr_1 | 数据格式错误 | +| -4 | userNameDup | 用户名已存在 | +| -41 | userNameNoFund | 用户不存在 | +| -42 | userPassIncorrect | 密码错误 | +| -43 | userEmailFormatError | 邮箱格式错误 | +| -44 | userCookieError | Cookie错误/未登录 | +| -51 | file_mime_err | 文件类型不允许 | +| -52 | file_size_err | 文件大小超限 | +| -53 | file_name_err | 文件名错误 | +| -54 | file_get_err | 文件获取错误 | +| -55 | file_hash_err | 文件哈希计算错误 | +| -56 | file_save_err | 文件保存失败 | +| -57 | file_not_found | 文件不存在 | +| -58 | file_part_err | 文件参数错误 | +| -61 | schedule_event_not_find | 日程不存在 | +| -62 | schedule_permission_denied | 无权限操作 | +| -1001 | order_not_found | 订单不存在 | +| -1002 | invalid_status | 无效的订单状态 | +| -1003 | status_no_change | 状态未变化 | +| -1004 | no_permission | 无权限 | +| -1005 | photo_hash_invalid | 图片哈希无效 | + +--- + +## 用户模块 `/api/users` + +### 用户登录 + +``` +POST /api/users/login +``` + +**请求参数:** +```json +{ + "userCookieValue": "", + "data": { + "username": "用户名", + "password": "密码", + "remember": false + } +} +``` + +**响应数据:** +```json +{ + "cookie": { + "ID": 1, + "Value": "32位随机字符串", + "ExpiresAt": "过期时间" + } +} +``` + +--- + +### 用户注册 + +``` +POST /api/users/register +``` + +**请求参数:** +```json +{ + "userCookieValue": "", + "data": { + "username": "用户名", + "useremail": "邮箱", + "userpass": "密码" + } +} +``` + +--- + +### 获取当前用户信息 + +``` +POST /api/users/getinfo +``` + +**需要认证** + +**响应数据:** +```json +{ + "user": { + "ID": 1, + "Name": "用户名", + "Email": "邮箱" + }, + "userInfo": { + "UserID": 1, + "Username": "显示名", + "FirstName": "名", + "Birthdate": "生日", + "AvatarPath": "头像路径", + "Gender": "M/F/U", + "Region": "地区", + "Language": "语言" + } +} +``` + +--- + +### 获取指定用户信息 + +``` +GET /api/users/getuserinfo/:id +``` + +**需要认证** + +**响应数据:** +```json +{ + "userinfo": { + "UserID": 1, + "Username": "显示名" + } +} +``` + +--- + +### 修改密码 + +``` +POST /api/users/changePassword +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "oldpass": "旧密码", + "newpass": "新密码" + } +} +``` + +--- + +### 修改邮箱 + +``` +POST /api/users/changeEmail +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "newemail": "新邮箱" + } +} +``` + +--- + +### 更新用户信息 + +``` +POST /api/users/updateInfo +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "username": "用户名", + "remark": "备注(作为FirstName)", + "birthday": "2006-01-02" + } +} +``` + +--- + +### 上传头像 + +``` +POST /api/users/updateAvatar +``` + +**需要认证** + +**请求格式:** `multipart/form-data` + +| 字段 | 类型 | 说明 | +|------|------|------| +| cookie | string | 认证cookie | +| file | file | 图片文件(512字节-1MB) | + +--- + +### 测试接口 + +``` +GET /api/users/test +POST /api/users/test +``` + +--- + +## 订单模块 `/api/purchase` + +### 获取订单列表 + +``` +POST /api/purchase/getorders +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "search": "搜索关键词(可选)", + "status": "pending/ordered/arrived/received/lost/returned(可选)", + "entries": 20, + "page": 1 + } +} +``` + +**响应数据:** +```json +{ + "all_count": 100, + "all_orders": [ + { + "ID": 1, + "UserID": 1, + "Title": "订单标题", + "Remark": "备注", + "Link": "链接", + "Styles": "样式", + "OrderStatus": "pending", + "CreatedAt": "创建时间", + "UpdatedAt": "更新时间" + } + ] +} +``` + +--- + +### 获取单个订单详情 + +``` +POST /api/purchase/getorder +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1 + } +} +``` + +**响应数据:** +```json +{ + "order": { + "ID": 1, + "UserID": 1, + "Title": "订单标题", + "Remark": "备注", + "Link": "链接", + "Styles": "样式", + "OrderStatus": "pending" + }, + "canModify": true, + "costs": [ + { + "ID": 1, + "OrderID": 1, + "Price": 1000, + "Quantity": 2, + "CurrencyType": 1, + "CostType": 1 + } + ], + "photos": [ + { + "ID": 1, + "Name": "图片名", + "Sha256": "哈希值" + } + ], + "commits": [ + { + "ID": 1, + "OrderID": 1, + "UserID": 1, + "Action": "create_status", + "Status": "pending", + "OldStatus": "", + "Comment": "状态变更为: pending", + "Photos": [], + "CreatedAt": "时间" + } + ] +} +``` + +--- + +### 创建订单 + +``` +POST /api/purchase/addorder +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "title": "订单标题(必填)", + "remark": "备注", + "link": "链接", + "styles": "样式", + "costs": [ + { + "cost": 1000, + "costt": 2000, + "currencytype": 1, + "int": 2, + "type": 1 + } + ], + "photos": ["图片sha256哈希"] + } +} +``` + +**字段说明:** +- `cost`: 费用(分) +- `currencytype`: 货币类型 (1-CNY, 2-MOP, 3-HKD, 4-USD) +- `int`: 数量 +- `type`: 费用类型 (1-单价, 2-运费) +- `photos`: 图片SHA256哈希数组 + +--- + +### 更新订单 + +``` +POST /api/purchase/updateorder +``` + +**需要认证** (创建者或管理员) + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1, + "title": "订单标题", + "remark": "备注", + "link": "链接", + "styles": "样式", + "costs": [ + { + "cost": 1000, + "currencytype": 1, + "int": 2, + "type": 1 + } + ], + "photos": ["哈希"] + } +} +``` + +--- + +### 更新订单状态 + +``` +POST /api/purchase/updatestatus +``` + +**需要认证** (创建者或管理员) + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1, + "status": "ordered", + "comment": "备注", + "photos": ["变更图片哈希"] + } +} +``` + +**状态值:** +| 值 | 说明 | +|----|------| +| pending | 待处理 | +| ordered | 已下单 | +| arrived | 已到达 | +| received | 已收件 | +| lost | 丢件 | +| returned | 退件 | + +--- + +### 删除订单 + +``` +POST /api/purchase/deleteorder +``` + +**需要认证** (创建者或管理员) + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1 + } +} +``` + +--- + +### 获取订单统计 + +``` +POST /api/purchase/getordercount +``` + +**需要认证** + +**响应数据:** +```json +{ + "pending": 10, + "ordered": 5, + "arrived": 3, + "received": 20, + "lost": 1, + "returned": 2, + "total": 41 +} +``` + +--- + +## 日程模块 `/api/schedule` + +### 获取日程列表 + +``` +POST /api/schedule/getevents +``` + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "start": "2026-01-01", + "end": "2026-12-31" + } +} +``` + +**响应数据:** +```json +{ + "list": [ + { + "ID": 1, + "UserID": 1, + "Title": "日程标题", + "StartDate": "2026-01-01", + "EndDate": "2026-01-01", + "BgColor": "#3788d9", + "Remark": "备注", + "edit": true + } + ] +} +``` + +--- + +### 创建日程 + +``` +POST /api/schedule/addevent +``` + +**需要认证** + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 0, + "title": "日程标题", + "start": "2026-01-01", + "end": "2026-01-01", + "color": "#3788d9" + } +} +``` + +--- + +### 编辑日程 + +``` +POST /api/schedule/editevent +``` + +**需要认证** (创建者或管理员) + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1, + "title": "日程标题", + "start": "2026-01-01", + "end": "2026-01-01", + "color": "#3788d9" + } +} +``` + +--- + +### 删除日程 + +``` +POST /api/schedule/deleevent +``` + +**需要认证** (创建者或管理员) + +**请求参数:** +```json +{ + "userCookieValue": "cookie", + "data": { + "id": 1 + } +} +``` + +--- + +## 文件模块 `/api/files` + +### 上传图片 + +``` +POST /api/files/upload/image +``` + +**需要认证** + +**请求格式:** `multipart/form-data` + +| 字段 | 类型 | 说明 | +|------|------|------| +| cookie | string | 认证cookie | +| file | file | 图片文件 | + +**响应数据:** +```json +{ + "download": "/api/files/download/文件哈希", + "get": "/api/files/get/文件哈希", + "hash": "sha256哈希值" +} +``` + +--- + +### 获取/下载文件 + +``` +GET /api/files/:mode/:hash + +mode: get - 获取(预览) +mode: download - 下载 +``` + +**参数说明:** +- `get`: 返回文件预览 +- `download`: 返回文件下载 + +--- + +## 静态资源 `/api/static` + +### 获取头像 + +``` +GET /api/static/avatar/:filename +``` + +--- + +## 数据模型 + +### TabUser_ (用户表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| Name | string | 用户名(唯一) | +| Email | string | 邮箱 | +| Pass | string | 密码(哈希) | +| Salt | string | 盐值 | +| Type | string | 用户类型 | +| Date | datetime | 创建时间 | + +### TabUserInfo_ (用户信息表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| UserID | uint | 用户ID(唯一) | +| Username | string | 显示名 | +| FirstName | string | 名 | +| Birthdate | datetime | 生日 | +| Gender | char | 性别 M/F/U | +| AvatarPath | string | 头像路径 | +| Region | string | 地区 | +| Language | string | 语言 | + +### TabPurchaseOrder (订单表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| UserID | uint | 创建者ID | +| Title | string | 标题 | +| Remark | text | 备注 | +| Link | string | 链接 | +| Styles | text | 样式 | +| OrderStatus | string | 状态 | +| CreatedAt | datetime | 创建时间 | +| UpdatedAt | datetime | 更新时间 | + +### TabPurchaseCosts (订单费用表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| OrderID | uint | 订单ID | +| UserID | uint | 用户ID | +| Price | int | 单价(分) | +| Quantity | int | 数量 | +| CurrencyType | int | 货币类型 | +| CostType | int | 费用类型 | + +### TabSchedule (日程表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| UserID | uint | 创建者ID | +| Title | string | 标题 | +| StartDate | string | 开始日期 | +| EndDate | string | 结束日期 | +| BgColor | string | 背景颜色 | +| Remark | text | 备注 | + +### TabFileInfo_ (文件表) + +| 字段 | 类型 | 说明 | +|------|------|------| +| ID | uint | 主键 | +| Name | string | 文件名 | +| Size | int64 | 文件大小 | +| Path | string | 存储路径 | +| Sha256 | string | 文件哈希 | +| Mime | string | MIME类型 | +| Type | string | 文件类型 | +| UserID | uint | 上传用户ID | +| Date | datetime | 上传时间 | + +--- + +## 权限说明 + +### 订单权限 +- 创建者:可修改/删除自己的订单 +- purchase_admin 用户组成员:可修改/删除所有订单 + +### 日程权限 +- 创建者:可修改/删除自己的日程 +- schedule_admin 用户组成员:可修改/删除所有日程 diff --git a/frontend/ops_uniapp/.workbuddy/expert-history.json b/frontend/ops_uniapp/.workbuddy/expert-history.json index 85bcbba..1487a03 100644 --- a/frontend/ops_uniapp/.workbuddy/expert-history.json +++ b/frontend/ops_uniapp/.workbuddy/expert-history.json @@ -13,5 +13,5 @@ } ] }, - "lastUpdated": 1776172462417 + "lastUpdated": 1776173744465 } \ No newline at end of file diff --git a/frontend/ops_uniapp/locales/en.js b/frontend/ops_uniapp/locales/en.js index ce7d82b..0624402 100644 --- a/frontend/ops_uniapp/locales/en.js +++ b/frontend/ops_uniapp/locales/en.js @@ -2,6 +2,7 @@ export default { // tabBar tabBar: { home: 'Home', + user: 'Profile', settings: 'Settings', }, // 首页 @@ -109,5 +110,17 @@ export default { failed: 'Operation failed', back: 'Back', networkError: 'Network request failed, please check your connection', + }, + // 用户页 + user: { + profile: 'Profile', + username: 'Username', + firstName: 'Name', + birthday: 'Birthday', + gender: 'Gender', + region: 'Region', + male: 'Male', + female: 'Female', + unknown: 'Unknown', } } diff --git a/frontend/ops_uniapp/locales/index.js b/frontend/ops_uniapp/locales/index.js index 6eb7965..a13c94a 100644 --- a/frontend/ops_uniapp/locales/index.js +++ b/frontend/ops_uniapp/locales/index.js @@ -1,7 +1,10 @@ -import { createI18n } from 'vue-i18n' +import { createI18n, useI18n as _useI18n } from 'vue-i18n' import zh from './zh.js' import en from './en.js' +// 兼容 uni-app 的 useI18n 导出 +export const useI18n = _useI18n + // 获取本地语言设置,默认中文 function getLocale() { // 从本地存储读取 diff --git a/frontend/ops_uniapp/locales/zh.js b/frontend/ops_uniapp/locales/zh.js index 2234012..f8d049b 100644 --- a/frontend/ops_uniapp/locales/zh.js +++ b/frontend/ops_uniapp/locales/zh.js @@ -2,6 +2,7 @@ export default { // tabBar tabBar: { home: '首页', + user: '我的', settings: '设置', }, // 首页 @@ -109,5 +110,17 @@ export default { failed: '操作失败', back: '返回', networkError: '网络请求失败,请检查网络', + }, + // 用户页 + user: { + profile: '个人资料', + username: '用户名', + firstName: '姓名', + birthday: '生日', + gender: '性别', + region: '地区', + male: '男', + female: '女', + unknown: '未知', } } diff --git a/frontend/ops_uniapp/pages.json b/frontend/ops_uniapp/pages.json index 57334ed..eae9291 100644 --- a/frontend/ops_uniapp/pages.json +++ b/frontend/ops_uniapp/pages.json @@ -12,6 +12,12 @@ "navigationBarTitleText": "首页" } }, + { + "path": "pages/user/user", + "style": { + "navigationBarTitleText": "我的" + } + }, { "path": "pages/settings/apiConfig", "style": { @@ -50,6 +56,12 @@ "iconPath": "static/tabbar/home.png", "selectedIconPath": "static/tabbar/home-active.png" }, + { + "pagePath": "pages/user/user", + "text": "我的", + "iconPath": "static/tabbar/settings.png", + "selectedIconPath": "static/tabbar/settings-active.png" + }, { "pagePath": "pages/settings/apiConfig", "text": "设置", diff --git a/frontend/ops_uniapp/pages/login/login.vue b/frontend/ops_uniapp/pages/login/login.vue index 9531854..dae63c1 100644 --- a/frontend/ops_uniapp/pages/login/login.vue +++ b/frontend/ops_uniapp/pages/login/login.vue @@ -135,18 +135,26 @@ const handleLogin = () => { url: getApp().globalData.BASE_URL + '/users/login', method: 'POST', data: { - username: form.username, - password: form.password, - remember: form.remember + userCookieValue: '', + data: { + username: form.username, + password: form.password, + remember: form.remember + } }, header: { 'Content-Type': 'application/json' }, success: (res) => { - if (res.data.code === 0 && res.data.data && res.data.data.cookie) { - const cookie = res.data.data.cookie - uni.setStorageSync('sessionCookie', cookie.Value) - uni.setStorageSync('userInfo', res.data.data) + if (res.data.err_code === 0 && res.data.return && res.data.return.cookie) { + const cookieData = res.data.return.cookie + // 存储 cookie Value 作为 session 标识 + uni.setStorageSync('sessionCookie', cookieData.Value) + uni.setStorageSync('cookieExpires', cookieData.ExpiresAt) + uni.setStorageSync('userInfo', { + userId: cookieData.ID, + username: form.username + }) if (form.remember) { uni.setStorageSync('savedUsername', form.username) @@ -168,13 +176,15 @@ const handleLogin = () => { }) }, 1500) } else { + // 根据 err_code 显示错误信息 + const errCode = res.data.err_code const msgMap = { - userNameNoFund: t('login.usernameNotFound'), - userPassIncorrect: t('login.passwordIncorrect'), - jsonErr: t('login.paramError'), - postErr: t('login.requestFailed') + '-41': t('login.usernameNotFound'), + '-42': t('login.passwordIncorrect'), + '-3': t('login.paramError'), + '-2': t('login.requestFailed') } - errorMsg.value = msgMap[res.data.code] || t('login.loginFailed') + errorMsg.value = msgMap[errCode] || res.data.err_msg || t('login.loginFailed') } }, fail: (err) => { diff --git a/frontend/ops_uniapp/pages/register/register.vue b/frontend/ops_uniapp/pages/register/register.vue index a3355f0..9fd4ee1 100644 --- a/frontend/ops_uniapp/pages/register/register.vue +++ b/frontend/ops_uniapp/pages/register/register.vue @@ -161,15 +161,18 @@ const handleRegister = () => { url: getApp().globalData.BASE_URL + '/users/register', method: 'POST', data: { - username: form.username, - useremail: form.email, - userpass: form.password + userCookieValue: '', + data: { + username: form.username, + useremail: form.email, + userpass: form.password + } }, header: { 'Content-Type': 'application/json' }, success: (res) => { - if (res.data.code === 0) { + if (res.data.err_code === 0) { uni.showToast({ title: t('register.registerSuccess'), icon: 'success', @@ -179,13 +182,15 @@ const handleRegister = () => { uni.navigateBack() }, 1500) } else { + // 根据 err_code 显示错误信息 + const errCode = res.data.err_code const msgMap = { - userNameDup: t('register.usernameExists'), - userEmailDup: t('register.emailUsed'), - jsonErr: t('register.paramError'), - postErr: t('register.requestFailed') + '-4': t('register.usernameExists'), + '-43': t('register.emailInvalid'), + '-3': t('register.paramError'), + '-2': t('register.requestFailed') } - errorMsg.value = msgMap[res.data.code] || t('register.registerFailed') + errorMsg.value = msgMap[errCode] || res.data.err_msg || t('register.registerFailed') } }, fail: (err) => { diff --git a/frontend/ops_uniapp/pages/user/user.vue b/frontend/ops_uniapp/pages/user/user.vue new file mode 100644 index 0000000..156bb68 --- /dev/null +++ b/frontend/ops_uniapp/pages/user/user.vue @@ -0,0 +1,319 @@ + + + + + diff --git a/frontend/ops_uniapp/static/tabbar/user.svg b/frontend/ops_uniapp/static/tabbar/user.svg new file mode 100644 index 0000000..9de78d2 --- /dev/null +++ b/frontend/ops_uniapp/static/tabbar/user.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file