yangg 1 місяць тому
батько
коміт
76f986f8f1

+ 303 - 0
README.md

@@ -0,0 +1,303 @@
+# 敏龙面试小程序 (Minlong Interview Mini Program)
+
+这是一个基于 uni-app 框架开发的跨平台面试系统应用,支持 iOS、Android 和微信小程序等多个平台。
+
+## 项目简介
+
+本项目是一个完整的面试管理平台,提供从面试报名、身份验证、视频录制到简历评估等全流程功能。
+
+## 技术栈
+
+- **框架**: uni-app
+- **Vue 版本**: Vue 3
+- **开发语言**: JavaScript
+- **UI 框架**: uni-ui 组件库
+
+## 项目结构
+
+```
+interview_uni/
+├── api/                    # API 接口定义
+│   ├── index.js           # 通用接口
+│   └── user.js            # 用户相关接口
+├── common/                # 公共配置
+│   ├── config.js          # 配置文件
+│   └── faceunity/         # 面容SDK相关
+├── components/            # 组件
+│   └── voice-check-modal.vue  # 语音检查弹窗
+├── composables/           # Vue 3 Composition API
+│   └── useUserApi.js      # 用户API组合式函数
+├── docs/                  # 文档目录
+├── pages/                 # 页面文件
+│   ├── index/             # 首页/面试报名
+│   ├── login/             # 登录页
+│   ├── identity-verify/   # 身份验证
+│   ├── face-photo/        # 人脸照片拍摄
+│   ├── camera/            # 相机功能
+│   ├── posture-guide/     # 体态评估指导
+│   ├── interview/         # 手部照片采集
+│   ├── interview-question/# 面试问题
+│   ├── ResumeEvaluation/  # 简历评估
+│   ├── my/                # 个人中心
+│   ├── Personal/          # 个人信息
+│   ├── job-detail/        # 职位详情
+│   └── ...                # 其他功能页面
+├── services/              # 服务层
+│   └── ApiService.js      # API服务
+├── utils/                 # 工具函数
+│   ├── request.js         # 请求封装
+│   └── errorHandler.js    # 错误处理
+├── static/                # 静态资源
+├── pages.json             # 页面配置
+└── manifest.json          # 应用配置
+```
+
+## 主要功能模块
+
+### 1. 用户系统
+- 用户登录/注册
+- 身份验证
+- 个人信息管理
+
+### 2. 面试流程
+- 面试报名
+- 面试注意事项查看
+- 身份验证
+- 人脸照片拍摄
+- 体态评估指导(手部姿势指导)
+- 手部照片采集
+- 面试问题录制
+- 简历上传与评估
+
+### 3. 核心功能
+- **身份验证**: 人脸识别验证
+- **体态评估**: 指导用户完成手部姿势拍摄
+- **视频录制**: 面试视频录制功能
+- **简历管理**: 简历上传、预览和评估
+
+## 页面说明
+
+### 主要页面
+
+| 页面路径 | 说明 | 导航栏标题 |
+|---------|------|-----------|
+| `pages/index/index` | 首页/面试报名 | 面试报名 |
+| `pages/login/login` | 用户登录 | 登录 |
+| `pages/identity-verify/identity-verify` | 身份验证 | - |
+| `pages/face-photo/face-photo` | 人脸照片拍摄 | 拍摄照片 |
+| `pages/posture-guide/posture-guide` | 体态评估指导 | 体态评估指导 |
+| `pages/interview/interview` | 手部照片采集 | 手部照片采集 |
+| `pages/interview-question/interview-question` | 面试问题 | - |
+| `pages/ResumeEvaluation/ResumeEvaluation` | 简历评估 | - |
+| `pages/my/my` | 个人中心 | 我的 |
+| `pages/Personal/Personal` | 个人信息 | - |
+| `pages/job-detail/job-detail` | 职位详情 | - |
+| `pages/interview-notice/interview-notice` | 面试注意事项 | 面试注意事项 |
+| `pages/video-briefing/video-briefing` | 宣讲视频 | 宣讲视频 |
+| `pages/interview_retake/interview_retake` | 体态照片补拍 | 体态照片补拍 |
+
+### 页面流程
+
+```
+登录 → 身份验证 → 人脸拍摄 → 体态指导 → 手部采集 → 面试问题 → 完成
+  ↓
+首页(职位列表) → 职位详情 → 申请职位 → 简历上传
+  ↓
+个人中心 → 个人信息 → 我的申请
+```
+
+详细的页面文档:
+- [体态评估指导页面](./docs/posture-guide.md)
+
+## 开发指南
+
+详细的开发指南请参考:[开发指南](./docs/development.md)
+
+### 快速开始
+
+#### 环境要求
+
+- Node.js >= 12
+- HBuilderX 或 Visual Studio Code
+- uni-app 开发环境
+- 微信开发者工具(开发小程序时)
+
+#### 安装依赖
+
+```bash
+npm install
+# 或
+yarn install
+```
+
+#### 运行项目
+
+```bash
+# H5 开发
+npm run dev:h5
+# 访问 http://localhost:8080
+
+# 微信小程序开发
+npm run dev:mp-weixin
+# 在微信开发者工具中打开
+
+# App 开发(需在 HBuilderX 中运行)
+npm run dev:app-plus
+```
+
+#### 构建项目
+
+```bash
+# 构建 H5
+npm run build:h5
+
+# 构建微信小程序
+npm run build:mp-weixin
+
+# 构建 App
+npm run build:app-plus
+```
+
+## 配置说明
+
+### manifest.json
+
+- 应用基本信息配置
+- Android/iOS 打包配置
+- 微信小程序配置(AppID: wxc9655eeaa3223b75)
+- 权限配置(相机、麦克风等)
+
+### pages.json
+
+- 页面路由配置
+- 导航栏设置
+- TabBar 配置
+
+## API 接口
+
+### 接口文档
+
+详细的 API 文档请参考:[API 文档](./docs/api.md)
+
+### 快速开始
+
+```javascript
+// 导入 API 方法
+import { wxLogin, getUserInfo } from '@/api/user.js';
+
+// 使用 API
+const result = await wxLogin({
+  code: 'code',
+  userInfo: {}
+});
+```
+
+### 接口模块
+
+#### 用户相关接口
+- `wxLogin()` - 微信登录
+- `getUserInfo()` - 获取用户信息
+- `getUserPhoneNumber()` - 获取手机号
+- `updateUserInfo()` - 更新用户信息
+- `uploadAvatar()` - 上传头像
+- `logout()` - 退出登录
+- `fillUserInfo()` - 填写用户信息
+
+#### 职位相关接口
+- `getJobList()` - 获取职位列表
+- `applyJob()` - 申请职位
+- `getApplicationDetail()` - 获取申请详情
+
+#### 面试相关接口
+- `getInterviewList()` - 获取面试列表
+- `getInterviewDetail()` - 获取面试详情
+- `submitAnswer()` - 提交答案
+- `getQuestions()` - 获取问题列表
+
+#### 文件上传接口
+- `uploadPhoto()` - 上传照片
+
+### 请求配置
+
+- **基础URL**: `https://backend.qicai321.com`
+- **超时时间**: 60秒
+- **认证方式**: Bearer Token + CSRF Token
+- **数据格式**: JSON
+
+详细的接口文档、参数说明和示例代码请查看 [API 文档](./docs/api.md)。
+
+## 注意事项
+
+### 开发注意事项
+
+1. **权限要求**: 需要获取相机、麦克风等权限
+   - Android 端需要在 manifest.json 中配置权限
+   - iOS 端需要配置 info.plist
+   - 小程序需要在小程序后台配置权限
+
+2. **网络配置**: 确保 API 接口地址正确配置
+   - 修改 `common/config.js` 中的 `apiBaseUrl`
+   - 注意区分测试和生产环境
+
+3. **平台差异**: 不同平台可能存在一些差异,注意测试
+   - H5/小程序/App 的 API 可能有差异
+   - 使用 `#ifdef` 条件编译处理平台差异
+
+4. **图片资源**: 部分图片资源存储在 CDN 上
+   - CDN 地址: `https://data.qicai321.com/minlong/`
+   - 注意网络加载状态处理
+
+5. **Token 管理**: 
+   - Token 存储在本地存储
+   - 注意处理 token 过期情况
+   - 使用 CSRF token 防止跨站请求伪造
+
+6. **错误处理**: 
+   - 所有请求都包含统一的错误处理
+   - 特殊错误码 (999) 需要手动处理
+   - 注意避免重复的错误提示
+
+7. **并发控制**: 
+   - 某些接口有防重入保护
+   - 注意处理并发请求的情况
+
+### 调试技巧
+
+1. **开启调试**: 在 HBuilderX 中开启调试模式
+2. **查看日志**: 使用 `console.log` 输出调试信息
+3. **网络抓包**: 使用 Charles/Fiddler 抓包分析
+4. **真机调试**: 注意真机与模拟器的差异
+
+## 版本信息
+
+- 版本号: 1.0.0
+- 版本代码: 100
+
+## 开发团队
+
+面试系统开发团队
+
+## License
+
+[MIT License]
+
+---
+
+## 详细文档
+
+### 功能文档
+
+- [体态评估指导页面](./docs/posture-guide.md) - 手部照片拍摄指导页面详细说明
+
+### 技术文档
+
+- [API 文档](./docs/api.md) - 完整的 API 接口文档,包含所有接口的详细说明、参数和示例
+- [开发指南](./docs/development.md) - 开发环境搭建、开发流程、调试技巧和常见问题
+
+### 快速链接
+
+- [快速开始](#开发指南)
+- [API 使用](#api-接口)
+- [页面说明](#页面说明)
+- [常见问题](#注意事项)
+

+ 81 - 0
docs/README.md

@@ -0,0 +1,81 @@
+# 文档索引
+
+欢迎查看面试系统项目的完整文档!
+
+## 📚 文档导航
+
+### 功能文档
+
+- **[体态评估指导页面](./posture-guide.md)**
+  - 手部照片拍摄指导页面的详细说明
+  - 功能特性介绍
+  - 代码结构和实现细节
+  - 用户交互流程
+
+### 技术文档
+
+- **[API 文档](./api.md)**
+  - 完整的 API 接口文档
+  - 所有接口的详细说明
+  - 请求参数和响应格式
+  - 使用示例和代码片段
+  - 错误处理指南
+
+- **[开发指南](./development.md)**
+  - 环境搭建和配置
+  - 开发流程和最佳实践
+  - 调试技巧和工具
+  - 常见问题解答
+  - 性能优化建议
+
+## 📋 快速查找
+
+### 我是新开发者
+
+如果你想快速开始开发:
+
+1. 📖 阅读主项目 [README](../README.md) 了解项目概况
+2. 🛠️ 查看 [开发指南](./development.md) 搭建开发环境
+3. 📱 查看 [API 文档](./api.md) 了解如何调用接口
+4. 🎨 参考 [体态评估页面](./posture-guide.md) 了解页面开发模式
+
+### 我想了解某个功能
+
+- **用户相关功能**: 查看 [API 文档](./api.md#用户相关接口)
+- **面试流程**: 查看 [体态评估页面](./posture-guide.md)
+- **页面开发**: 查看 [开发指南](./development.md#页面开发)
+- **接口调用**: 查看 [API 文档](./api.md#使用示例)
+
+### 我遇到问题了
+
+- **开发环境问题**: 查看 [开发指南](./development.md#环境搭建)
+- **API 调用失败**: 查看 [API 文档](./api.md#错误处理) 和 [开发指南](./development.md#常见问题)
+- **样式不生效**: 查看 [开发指南](./development.md#样式开发)
+- **页面跳转失败**: 查看 [开发指南](./development.md#常见问题)
+
+## 📝 文档更新日志
+
+### 2024 - v1.0.0
+
+- ✅ 创建项目主 README
+- ✅ 创建 API 完整文档
+- ✅ 创建开发指南
+- ✅ 创建体态评估页面文档
+
+## 🔗 外部链接
+
+- [uni-app 官方文档](https://uniapp.dcloud.net.cn/)
+- [Vue 3 官方文档](https://cn.vuejs.org/)
+- [uni-ui 组件库](https://uniapp.dcloud.net.cn/component/uniui/uni-ui.html)
+
+## 📞 获取帮助
+
+如果你在使用过程中遇到问题:
+
+1. 查看 [常见问题](./development.md#常见问题)
+2. 查看 [API 错误处理](./api.md#错误处理)
+3. 联系开发团队
+
+---
+
+

+ 612 - 0
docs/api.md

@@ -0,0 +1,612 @@
+# API 文档
+
+## 目录
+
+- [基础配置](#基础配置)
+- [请求封装](#请求封装)
+- [用户相关接口](#用户相关接口)
+- [职位相关接口](#职位相关接口)
+- [面试相关接口](#面试相关接口)
+- [文件上传接口](#文件上传接口)
+- [错误处理](#错误处理)
+
+## 基础配置
+
+### API 基础地址
+
+- **生产环境**: `https://backend.qicai321.com`
+- **测试环境**: `http://192.168.66.187:8083`
+
+### 请求配置
+
+- **请求超时**: 60000ms (60秒)
+- **认证方式**: Bearer Token + CSRF Token
+- **数据格式**: JSON
+
+### 请求头
+
+所有请求会自动添加以下请求头:
+
+```javascript
+{
+  'Authorization': 'Bearer {token}',
+  'X-CSRF-Token': '{csrfToken}',
+  'Content-Type': 'application/json'
+}
+```
+
+## 请求封装
+
+### HTTP 方法
+
+项目使用封装的 `http` 对象提供以下方法:
+
+```javascript
+import http from '@/utils/request.js';
+
+// GET 请求
+http.get(url, data, options)
+
+// POST 请求
+http.post(url, data, options)
+
+// PUT 请求
+http.put(url, data, options)
+
+// DELETE 请求
+http.delete(url, data, options)
+
+// 文件上传
+http.upload(url, filePath, name, formData, options)
+```
+
+### 请求拦截器
+
+自动添加的功能:
+
+1. **Token 认证**: 自动从存储中获取 token 并添加到请求头
+2. **CSRF 保护**: 自动添加 CSRF token
+3. **URL 处理**: 自动拼接基础 URL
+4. **错误处理**: 统一的错误处理和提示
+
+### 响应拦截器
+
+自动处理的功能:
+
+1. **状态码检查**: HTTP 状态码检查
+2. **业务状态**: 检查业务返回码 (code/status)
+3. **数据提取**: 自动提取 response.data.data
+4. **错误映射**: 统一的错误码映射
+
+## 用户相关接口
+
+### 微信登录
+
+```javascript
+import { wxLogin } from '@/api/user.js';
+
+// 请求参数
+const params = {
+  code: '微信登录code',
+  userInfo: {
+    nickname: '用户昵称',
+    avatarUrl: '头像URL',
+    gender: 0, // 性别
+    province: '省份',
+    city: '城市',
+    country: '国家',
+    tenant_id: 1
+  },
+  signature: '签名',
+  rawData: '原始数据',
+  encryptedData: '加密数据',
+  iv: '初始向量',
+  _csrf: 'CSRF token'
+};
+
+// 调用
+const result = await wxLogin(params);
+```
+
+**接口地址**: `POST /wechat/wechatLogin`
+
+**返回数据**:
+
+```javascript
+{
+  status: 2000,
+  code: 0,
+  data: {
+    token: 'jwt_token',
+    userInfo: {
+      openid: 'openid',
+      nickname: '昵称',
+      avatarUrl: '头像',
+      tenant_id: 1
+    }
+  }
+}
+```
+
+### 获取用户信息
+
+```javascript
+import { getUserInfo } from '@/api/user.js';
+
+// 获取当前用户信息
+const userInfo = await getUserInfo();
+
+// 获取指定用户信息
+const otherUserInfo = await getUserInfo(userId, openid);
+```
+
+**接口地址**: `GET /wechat/getUserDetail` 或 `GET /api/wechat/get_user_info`
+
+### 获取用户手机号
+
+```javascript
+import { getUserPhoneNumber } from '@/api/user.js';
+
+const params = {
+  code: '微信授权code',
+  encryptedData: '加密数据',
+  iv: '初始向量'
+};
+
+const phoneInfo = await getUserPhoneNumber(params);
+```
+
+**接口地址**: `POST /wechat/getUserPhoneNumber`
+
+### 更新用户信息
+
+```javascript
+import { updateUserInfo } from '@/api/user.js';
+
+const userData = {
+  nickname: '新昵称',
+  phone: '手机号',
+  // ...其他用户信息
+};
+
+await updateUserInfo(userData);
+```
+
+**接口地址**: `PUT /api/user/update`
+
+### 上传头像
+
+```javascript
+import { uploadAvatar } from '@/api/user.js';
+
+const filePath = '/path/to/image.jpg';
+const result = await uploadAvatar(filePath);
+```
+
+**接口地址**: `POST /api/upload/avatar`
+
+### 退出登录
+
+```javascript
+import { logout } from '@/api/user.js';
+
+await logout();
+```
+
+**接口地址**: `POST /wechat/wechatLogout`
+
+### 填写用户信息
+
+```javascript
+import { fillUserInfo } from '@/api/user.js';
+
+const userInfo = {
+  name: '姓名',
+  phone: '手机号',
+  email: '邮箱',
+  // ...其他信息
+};
+
+await fillUserInfo(userInfo);
+```
+
+**接口地址**: `POST /api/wechat/save_user_info`
+
+## 职位相关接口
+
+### 获取职位列表
+
+```javascript
+import { getJobList } from '@/api/user.js';
+
+const params = {
+  page: 1,
+  limit: 50,
+  searchTerms: '关键词',
+  status: '状态',
+  tenant_id: 1
+};
+
+const jobList = await getJobList(params);
+```
+
+**接口地址**: `GET /api/system/job/list`
+
+**请求参数**:
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| page | Number | 否 | 页码,默认1 |
+| limit | Number | 否 | 每页数量,默认50 |
+| searchTerms | String | 否 | 搜索关键词 |
+| status | String | 否 | 职位状态 |
+| tenant_id | Number | 否 | 租户ID |
+
+### 申请职位
+
+```javascript
+import { applyJob } from '@/api/user.js';
+
+const params = {
+  job_id: 123,
+  user_id: 456,
+  resume_id: 789
+};
+
+await applyJob(params);
+```
+
+**接口地址**: `POST /api/job/apply`
+
+### 获取职位申请详情
+
+```javascript
+import { getApplicationDetail } from '@/api/user.js';
+
+const params = {
+  id: 123,
+  application_id: 456
+};
+
+const detail = await getApplicationDetail(params);
+```
+
+**接口地址**: `GET /api/job/application_detail`
+
+**请求参数**:
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| id | Number | 是 | 用户ID |
+| tenant_id | Number | 是 | 租户ID |
+| application_id | Number | 是 | 申请ID |
+
+## 面试相关接口
+
+### 获取面试列表
+
+```javascript
+import { getInterviewList } from '@/api/user.js';
+
+const params = {
+  user_id: 123,
+  job_id: 456
+};
+
+const interviewList = await getInterviewList(params);
+```
+
+**接口地址**: `GET /job/questions`
+
+### 获取面试详情
+
+```javascript
+import { getInterviewDetail } from '@/api/user.js';
+
+const params = {
+  interview_id: 123
+};
+
+const detail = await getInterviewDetail(params);
+```
+
+**接口地址**: `GET /interview_question/detail`
+
+### 提交面试答案
+
+```javascript
+import { submitAnswer } from '@/api/user.js';
+
+const params = {
+  question_id: 123,
+  answer: '答案内容',
+  video_url: '视频URL',
+  audio_url: '音频URL'
+};
+
+await submitAnswer(params);
+```
+
+**接口地址**: `POST /api/job/submit_answer`
+
+### 获取问题数据
+
+```javascript
+import { getQuestions } from '@/api/user.js';
+
+const params = {
+  job_id: 123,
+  tenant_id: 1
+};
+
+const questions = await getQuestions(params);
+```
+
+**接口地址**: `GET /api/wechat/choice_questions/`
+
+**请求参数**:
+
+| 参数 | 类型 | 必填 | 说明 |
+|------|------|------|------|
+| job_id | Number | 是 | 职位ID |
+| tenant_id | Number | 是 | 租户ID |
+
+### 语音面试互动
+
+```javascript
+// 直接调用 uni.request
+const res = await uni.request({
+  url: `${apiBaseUrl}/api/voice_interview_interaction/`,
+  method: 'POST',
+  data: {
+    tenant_id: 1,
+    question_id: 123,
+    position_config_id: 456,
+    application_id: 789
+  }
+});
+```
+
+**接口地址**: `POST /api/voice_interview_interaction/`
+
+## 文件上传接口
+
+### 上传照片
+
+```javascript
+import { uploadPhoto } from '@/api/user.js';
+
+const params = {
+  file: '文件路径',
+  type: 'photo',
+  user_id: 123
+};
+
+const result = await uploadPhoto(params);
+```
+
+**接口地址**: `POST /api/upload/`
+
+**上传文件示例**:
+
+```javascript
+import http from '@/utils/request.js';
+
+const result = await http.upload(
+  '/api/upload/',
+  filePath,
+  'file', // 文件字段名
+  {
+    type: 'photo',
+    tenant_id: 1
+  }
+);
+```
+
+## 错误处理
+
+### 错误码说明
+
+| HTTP 状态码 | 业务码 | 说明 | 处理方式 |
+|------------|--------|------|---------|
+| 200 | 2000 | 请求成功 | 正常处理 |
+| 400 | - | 请求参数错误 | 检查请求参数 |
+| 401 | - | 未登录或token过期 | 跳转登录页面 |
+| 403 | - | 无权限 | 提示用户 |
+| 500 | - | 服务器错误 | 提示用户稍后重试 |
+| 400 | 999 | 业务错误(如职位已截止) | 显示错误信息 |
+
+### 错误处理示例
+
+```javascript
+import http from '@/utils/request.js';
+
+try {
+  const result = await http.post('/api/example', data);
+  // 处理成功结果
+} catch (error) {
+  // 错误已在拦截器中处理并提示用户
+  // 这里可以做额外的处理
+  if (error.status === 401) {
+    // 跳转到登录页面
+    uni.reLaunch({ url: '/pages/login/login' });
+  }
+}
+```
+
+### 特殊错误码 999
+
+当返回 `status: 999` 或 `code: 999` 时,表示业务逻辑错误(如职位申请已截止),不会自动提示,需要手动处理:
+
+```javascript
+try {
+  await applyJob(params);
+} catch (error) {
+  if (error.status === 999) {
+    uni.showToast({
+      title: error.message || '申请已截止',
+      icon: 'none'
+    });
+  }
+}
+```
+
+## 使用示例
+
+### 方式一:使用 API 模块
+
+```javascript
+// 在页面中导入
+import { wxLogin, getUserInfo } from '@/api/user.js';
+
+// 使用
+export default {
+  methods: {
+    async handleLogin() {
+      try {
+        const result = await wxLogin(params);
+        // 处理登录结果
+      } catch (error) {
+        // 错误处理
+      }
+    }
+  }
+}
+```
+
+### 方式二:使用 API 服务
+
+```javascript
+import apiService from '@/services/ApiService.js';
+
+// 获取用户API
+const userApi = apiService.get('user');
+const result = await userApi.getUserInfo();
+
+// 或使用简写
+const userApi = apiService.user;
+const result = await userApi.getUserInfo();
+```
+
+### 方式三:使用 Composition API
+
+```javascript
+import { useUserApi } from '@/composables/useUserApi.js';
+
+export default {
+  setup() {
+    const { login, loading, error } = useUserApi();
+    
+    const handleLogin = async () => {
+      try {
+        const result = await login(code, userInfo);
+        return result;
+      } catch (err) {
+        console.error('Login failed:', error.value);
+      }
+    };
+    
+    return {
+      handleLogin,
+      loading,
+      error
+    };
+  }
+}
+```
+
+### 方式四:直接使用 uni.request
+
+```javascript
+import { apiBaseUrl } from '@/common/config.js';
+
+const res = await uni.request({
+  url: `${apiBaseUrl}/api/wechat/user/get_full_info`,
+  method: 'GET',
+  data: {
+    tenant_id: 1,
+    openid: 'openid'
+  }
+});
+```
+
+## 注意事项
+
+### Token 管理
+
+- Token 存储在本地存储中: `uni.getStorageSync('token')`
+- 建议在 app.onLaunch 时检查 token 有效性
+- Token 过期后需要重新登录
+
+### CSRF Token
+
+- CSRF Token 存储在: `uni.getStorageSync('csrfToken')`
+- 自动添加到请求头: `X-CSRF-Token`
+- 建议在首次加载时获取 CSRF token
+
+### 请求超时
+
+- 默认超时时间: 60秒
+- 长请求可能需要调整超时时间
+- 使用 `options.timeout` 参数自定义超时时间
+
+### 并发请求
+
+- 多个请求并发时可使用 `Promise.all()`
+- 注意避免重复请求(使用防抖/节流)
+- 某些接口有防重入保护(如面试互动接口)
+
+### 测试环境切换
+
+修改 `common/config.js` 中的 `apiBaseUrl`:
+
+```javascript
+// 生产环境
+export const apiBaseUrl = 'https://backend.qicai321.com';
+
+// 测试环境
+export const apiBaseUrl = 'http://192.168.66.187:8083';
+```
+
+## API 列表汇总
+
+### 用户相关
+
+- 微信登录: `POST /wechat/wechatLogin`
+- 获取用户信息: `GET /wechat/getUserDetail`
+- 获取手机号: `POST /wechat/getUserPhoneNumber`
+- 更新用户信息: `PUT /api/user/update`
+- 上传头像: `POST /api/upload/avatar`
+- 退出登录: `POST /wechat/wechatLogout`
+- 填写用户信息: `POST /api/wechat/save_user_info`
+
+### 职位相关
+
+- 获取职位列表: `GET /api/system/job/list`
+- 申请职位: `POST /api/job/apply`
+- 获取申请详情: `GET /api/job/application_detail`
+
+### 面试相关
+
+- 获取面试列表: `GET /job/questions`
+- 获取面试详情: `GET /interview_question/detail`
+- 提交答案: `POST /api/job/submit_answer`
+- 获取问题: `GET /api/wechat/choice_questions/`
+- 面试互动: `POST /api/voice_interview_interaction/`
+
+### 文件相关
+
+- 上传文件: `POST /api/upload/`
+
+### 公共相关
+
+- 用户协议: `GET /api/public/agreements/terms_of_service/`
+
+## 更新日志
+
+- **v1.0.0** (2024): 初始版本
+  - 基础API封装
+  - 用户、职位、面试相关接口
+  - 统一的错误处理
+  - CSRF 防护机制
+

+ 489 - 0
docs/development.md

@@ -0,0 +1,489 @@
+# 开发指南
+
+## 环境搭建
+
+### 必需软件
+
+1. **HBuilderX** 或 **Visual Studio Code**
+2. **微信开发者工具** (开发微信小程序时)
+3. **Android Studio** (开发 App 时)
+4. **Xcode** (开发 iOS App 时,仅 macOS)
+
+### Node.js 环境
+
+```bash
+# 检查 Node.js 版本
+node -v  # 需要 >= 12
+
+# 检查 npm 版本
+npm -v
+
+# 安装依赖
+npm install
+# 或使用 yarn
+yarn install
+```
+
+## 项目配置
+
+### 环境配置
+
+修改 `common/config.js` 配置不同环境:
+
+```javascript
+// 生产环境
+export const apiBaseUrl = 'https://backend.qicai321.com';
+
+// 测试环境
+export const apiBaseUrl = 'http://192.168.66.187:8083';
+```
+
+### 微信小程序配置
+
+在 `manifest.json` 中配置小程序 AppID:
+
+```json
+{
+  "mp-weixin": {
+    "appid": "wxc9655eeaa3223b75",
+    "setting": {
+      "urlCheck": false
+    }
+  }
+}
+```
+
+## 开发流程
+
+### 1. 启动开发服务器
+
+```bash
+# H5 开发
+npm run dev:h5
+
+# 微信小程序开发
+npm run dev:mp-weixin
+
+# App 开发 (需要在 HBuilderX 中运行)
+```
+
+### 2. 页面开发
+
+创建新页面的步骤:
+
+1. **创建页面文件**
+   
+   在 `pages` 目录下创建新的目录和 .vue 文件:
+   ```
+   pages/
+     new-page/
+       new-page.vue
+   ```
+
+2. **配置页面路由**
+   
+   在 `pages.json` 中添加页面配置:
+   ```json
+   {
+     "path": "pages/new-page/new-page",
+     "style": {
+       "navigationBarTitleText": "新页面"
+     }
+   }
+   ```
+
+3. **页面跳转**
+   
+   ```javascript
+   // 保留当前页面,跳转到应用内的某个页面
+   uni.navigateTo({
+     url: '/pages/new-page/new-page'
+   });
+   
+   // 关闭当前页面,跳转到应用内的某个页面
+   uni.redirectTo({
+     url: '/pages/new-page/new-page'
+   });
+   
+   // 跳转到 tabBar 页面
+   uni.switchTab({
+     url: '/pages/index/index'
+   });
+   ```
+
+### 3. API 调用
+
+#### 使用封装的 API
+
+```javascript
+// 方式一:直接导入
+import { getUserInfo } from '@/api/user.js';
+
+export default {
+  methods: {
+    async fetchData() {
+      try {
+        const userInfo = await getUserInfo();
+        console.log(userInfo);
+      } catch (error) {
+        console.error('获取用户信息失败:', error);
+      }
+    }
+  }
+}
+```
+
+#### 使用 Composition API
+
+```javascript
+import { useUserApi } from '@/composables/useUserApi.js';
+
+export default {
+  setup() {
+    const { fetchUserInfo, loading, error } = useUserApi();
+    
+    const loadData = async () => {
+      try {
+        const data = await fetchUserInfo();
+        console.log(data);
+      } catch (err) {
+        console.error(error.value);
+      }
+    };
+    
+    return {
+      loadData,
+      loading,
+      error
+    };
+  }
+}
+```
+
+### 4. 样式开发
+
+#### 使用 rpx 单位
+
+uni-app 使用 rpx 作为响应式单位:
+
+```css
+/* 750rpx 等于屏幕宽度,自适应不同屏幕 */
+.container {
+  width: 750rpx;
+  height: 200rpx;
+  padding: 20rpx;
+}
+```
+
+#### 常用样式类
+
+```css
+/* 弹性布局 */
+.flex {
+  display: flex;
+}
+
+.flex-column {
+  flex-direction: column;
+}
+
+/* 居中对齐 */
+.center {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+/* 按钮样式 */
+.btn-primary {
+  background-color: #0039b3;
+  color: #fff;
+  border-radius: 44rpx;
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+}
+```
+
+## 调试技巧
+
+### 1. 控制台调试
+
+```javascript
+// 在页面中使用
+console.log('调试信息', data);
+console.error('错误信息', error);
+console.warn('警告信息', warning);
+```
+
+### 2. 条件编译
+
+针对不同平台开发:
+
+```javascript
+// #ifdef APP-PLUS
+console.log('App 平台');
+// #endif
+
+// #ifdef MP-WEIXIN
+console.log('微信小程序');
+// #endif
+
+// #ifdef H5
+console.log('H5 平台');
+// #endif
+```
+
+### 3. 真机调试
+
+App 真机调试:
+
+1. 连接手机到电脑
+2. 在 HBuilderX 中选择 "运行" → "运行到手机或模拟器"
+3. 选择设备并运行
+
+微信小程序调试:
+
+1. 在微信开发者工具中打开项目
+2. 点击 "预览" 或 "真机调试"
+3. 使用微信扫码在手机上查看
+
+### 4. 网络请求调试
+
+查看网络请求:
+
+```javascript
+// 在 utils/request.js 中已有 console.log
+console.log('request:', options);
+console.log('response:', response);
+```
+
+使用抓包工具:
+
+- Charles/Fiddler: 抓取 HTTP/HTTPS 请求
+- 微信开发者工具: 查看小程序网络请求
+
+## 构建部署
+
+### 构建 H5
+
+```bash
+npm run build:h5
+```
+
+构建产物在 `unpackage/dist/build/h5`
+
+### 构建微信小程序
+
+```bash
+npm run build:mp-weixin
+```
+
+构建产物在 `unpackage/dist/build/mp-weixin`
+
+### 构建 App
+
+1. 在 HBuilderX 中选择 "发行" → "原生 App-云打包"
+2. 配置打包参数
+3. 提交打包
+
+## 常见问题
+
+### 1. 页面跳转失败
+
+**问题**: 跳转后白屏或报错
+
+**解决方案**:
+- 检查页面路径是否正确
+- 检查 `pages.json` 配置是否正确
+- 检查页面文件是否存在语法错误
+
+### 2. API 请求失败
+
+**问题**: 网络请求返回 404 或 500
+
+**解决方案**:
+- 检查 API 地址是否正确
+- 检查网络连接
+- 检查后端服务是否正常
+- 查看控制台错误信息
+
+### 3. 样式不生效
+
+**问题**: CSS 样式显示不正确
+
+**解决方案**:
+- 检查选择器是否正确
+- 使用 `!important` 提高优先级
+- 检查 scoped 是否影响样式
+- 使用 rpx 而非 px
+
+### 4. 真机调试问题
+
+**问题**: 真机上无法正常运行
+
+**解决方案**:
+- 检查手机是否允许 USB 调试
+- 检查防火墙设置
+- 尝试重启调试工具
+- 查看真机运行日志
+
+### 5. Token 过期
+
+**问题**: 请求返回 401 未授权
+
+**解决方案**:
+- 清除本地 token: `uni.removeStorageSync('token')`
+- 重新登录获取新 token
+- 添加全局请求拦截器处理 token 刷新
+
+## 代码规范
+
+### 命名规范
+
+1. **变量命名**: 使用驼峰命名法
+   ```javascript
+   const userName = 'John';
+   const isLogin = true;
+   ```
+
+2. **方法命名**: 使用动词开头
+   ```javascript
+   function getUserInfo() {}
+   function handleLogin() {}
+   function fetchData() {}
+   ```
+
+3. **组件命名**: 使用 PascalCase
+   ```vue
+   <UserInfo />
+   <VoiceCheckModal />
+   ```
+
+### 文件结构
+
+```
+pages/
+  page-name/
+    page-name.vue     # 页面文件
+    page-name.js      # 页面逻辑(如果复杂)
+    page-name.scss    # 页面样式(如果复杂)
+```
+
+### 注释规范
+
+```javascript
+/**
+ * 获取用户信息
+ * @param {Number} userId - 用户ID
+ * @returns {Promise} - 用户信息
+ */
+export function getUserInfo(userId) {
+  // 实现代码
+}
+```
+
+## 版本管理
+
+### Git 工作流
+
+```bash
+# 创建新分支
+git checkout -b feature/new-feature
+
+# 提交代码
+git add .
+git commit -m "feat: 添加新功能"
+
+# 推送分支
+git push origin feature/new-feature
+```
+
+### 提交信息规范
+
+- `feat`: 新功能
+- `fix`: 修复 bug
+- `docs`: 文档更新
+- `style`: 代码格式调整
+- `refactor`: 代码重构
+- `test`: 添加测试
+- `chore`: 构建过程或辅助工具的变动
+
+## 性能优化
+
+### 1. 图片优化
+
+- 使用压缩后的图片
+- 使用 WebP 格式(H5 平台)
+- 懒加载图片
+
+### 2. 代码分割
+
+```javascript
+// 按需加载组件
+const Component = () => import('@/components/Component.vue');
+```
+
+### 3. 数据缓存
+
+```javascript
+// 缓存 API 数据
+const cachedData = uni.getStorageSync('cachedData');
+if (cachedData) {
+  // 使用缓存数据
+} else {
+  // 请求新数据
+}
+```
+
+### 4. 防抖和节流
+
+```javascript
+// 防抖
+function debounce(func, wait) {
+  let timeout;
+  return function() {
+    clearTimeout(timeout);
+    timeout = setTimeout(func, wait);
+  };
+}
+
+// 节流
+function throttle(func, limit) {
+  let inThrottle;
+  return function() {
+    if (!inThrottle) {
+      func.apply(this, arguments);
+      inThrottle = true;
+      setTimeout(() => inThrottle = false, limit);
+    }
+  };
+}
+```
+
+## 测试
+
+### 单元测试
+
+```javascript
+// 测试 API 方法
+import { getUserInfo } from '@/api/user.js';
+
+test('getUserInfo should return user data', async () => {
+  const userInfo = await getUserInfo();
+  expect(userInfo).toHaveProperty('id');
+});
+```
+
+### 集成测试
+
+- 测试完整的用户流程
+- 测试页面跳转
+- 测试表单提交
+
+### 真机测试
+
+- 在不同设备上测试
+- 测试不同网络环境
+- 测试边界情况
+

+ 179 - 0
docs/posture-guide.md

@@ -0,0 +1,179 @@
+# 体态评估指导页面文档
+
+## 页面概述
+
+体态评估指导页面(`pages/posture-guide/posture-guide.vue`)用于向用户展示手部照片拍摄的指导说明,帮助用户了解如何正确拍摄手部照片。
+
+## 页面功能
+
+该页面通过轮播图的方式,向用户展示6张不同的手部姿势示意图:
+- 左手手掌
+- 左手手背
+- 左手握拳
+- 右手手掌
+- 右手手背
+- 右手握拳
+
+## 功能特性
+
+### 1. 轮播图展示
+- 使用 uni-app 的 `swiper` 组件展示多张指导图片
+- 支持滑动切换图片
+- 底部显示指示点(dots)
+- 自动跟踪当前图片索引
+
+### 2. 按钮控制
+- **确认按钮**: 仅在查看完所有图片后启用
+- **禁用状态**: 未看完所有说明时,按钮呈灰色禁用状态
+- **文案切换**: 
+  - 未看完: "请查看完所有说明"
+  - 已看完: "我知道了,开始采集"
+
+### 3. 页面跳转
+- 确认后跳转到手部照片采集页面(`/pages/interview/interview`)
+- 使用 `uni.navigateTo` 进行页面跳转
+- 包含错误处理和降级方案
+
+## 代码结构
+
+### Template 部分
+
+```vue
+<template>
+  <view class="posture-guide">
+    <view class="guide-content">
+      <!-- 标题和副标题 -->
+      <view class="guide-title">体态评估环节</view>
+      <view class="guide-subtitle">请仔细阅读下方说明,完整展示手臂,向右滑动查看更多</view>
+      
+      <!-- 轮播图 -->
+      <swiper>
+        <!-- 多张指导图片 -->
+      </swiper>
+      
+      <!-- 确认按钮 -->
+      <button @click="handleConfirm">我知道了,开始采集</button>
+    </view>
+  </view>
+</template>
+```
+
+### Script 部分
+
+#### 数据结构
+
+```javascript
+data() {
+  return {
+    currentIndex: 0,        // 当前显示的图片索引
+    isLastSlide: false,    // 是否是最后一张图片
+    guideImages: [         // 指导图片数组
+      {
+        url: '图片URL',
+        description: '图片描述'
+      }
+    ]
+  }
+}
+```
+
+#### 方法说明
+
+**handleSwiperChange(e)**
+- 监听轮播图切换事件
+- 更新当前图片索引
+- 判断是否是最后一张图片
+
+**handleConfirm()**
+- 处理确认按钮点击事件
+- 验证是否已看完所有图片
+- 执行页面跳转或提示用户
+
+### Style 部分
+
+主要样式类:
+- `.posture-guide`: 页面容器
+- `.guide-title`: 标题样式
+- `.guide-subtitle`: 副标题样式
+- `.guide-swiper`: 轮播图容器
+- `.guide-image`: 图片样式
+- `.confirm-button`: 确认按钮样式
+- `.button-disabled`: 禁用状态按钮样式
+
+## 使用场景
+
+该页面在面试流程中处于以下位置:
+
+```
+用户登录 
+  → 身份验证 
+    → 人脸照片拍摄 
+      → **体态评估指导** 
+        → 手部照片采集 
+          → 面试问题录制
+```
+
+## 数据流程
+
+1. **页面加载**: 显示第一张指导图片
+2. **用户滑动**: 切换图片,更新 `currentIndex`
+3. **到达最后一张**: `isLastSlide` 置为 `true`
+4. **点击确认**: 跳转到手部照片采集页面
+
+## 图片资源
+
+图片通过 CDN 加载:
+
+```
+https://data.qicai321.com/minlong/
+```
+
+包含以下图片:
+1. 85fbafbd-1b80-48cd-bcd6-fc4911e9ff54.jpg - 左手手掌
+2. 17d141e0-8f99-4a54-a534-f6b954c600f8.png - 左手手背
+3. 5c7d7d6f-5e14-4cd6-bb9f-11509473a8bb.png - 左手握拳
+4. 148eea00-21b4-49e1-a3b6-712fff08a5a8.png - 右手手掌
+5. c67c303e-91c0-4e79-8e82-84096435481f.png - 右手手背
+6. 5a093f70-d397-4a36-9539-a8b4d11e0a13.png - 右手握拳
+
+## 用户体验优化
+
+1. **渐进式揭示**: 要求用户查看完所有图片才能进入下一步
+2. **视觉反馈**: 
+   - 禁用状态的按钮样式变化
+   - 按钮文案动态变化
+3. **错误处理**: 包含跳转失败的处理逻辑
+4. **指示点**: 清晰显示当前图片位置
+
+## 注意事项
+
+### 开发注意
+- 轮播图使用 `aspectFit` 模式确保图片比例正确
+- 指示点位置需要调整以适应图片高度
+- 响应式布局适配不同屏幕尺寸
+
+### 测试要点
+- 测试图片加载失败情况
+- 测试快速滑动情况
+- 测试不同设备的兼容性
+- 测试按钮禁用/启用逻辑
+
+### 可能的改进
+1. 添加图片加载失败的回退处理
+2. 添加图片加载状态指示
+3. 支持跳过功能(可选)
+4. 添加动画过渡效果
+
+## 相关页面
+
+- **前置页面**: `pages/face-photo/face-photo.vue` - 人脸照片拍摄
+- **后置页面**: `pages/interview/interview.vue` - 手部照片采集
+
+## 版本历史
+
+- **v1.0.0** (2024): 初始版本
+  - 基本轮播图功能
+  - 按钮禁用/启用逻辑
+  - 页面跳转功能
+
+

+ 112 - 62
pages/Personal/Personal.vue

@@ -336,23 +336,23 @@
 						<view class="form-item" v-if="showEducationTimeField">
 							<text class="label required">起止时间</text>
 							<view class="date-range">
-								<picker 
-									mode="date" 
-									:value="educationForm.startTime" 
-									@change="bindStartTimeChange"
-									:class="{'input-error': educationErrors.startTime}"
-								>
-									<view class="picker-text">{{ educationForm.startTime || '开始时间' }}</view>
-								</picker>
-								<text class="date-separator">至</text>
-								<picker 
-									mode="date" 
-									:value="educationForm.endTime" 
-									@change="bindEndTimeChange"
-									:class="{'input-error': educationErrors.endTime}"
-								>
-									<view class="picker-text">{{ educationForm.endTime || '结束时间' }}</view>
-								</picker>
+					<picker 
+						mode="date" 
+						:value="defaultEducationStartTime" 
+						@change="bindStartTimeChange"
+						:class="{'input-error': educationErrors.startTime}"
+					>
+						<view class="picker-text">{{ educationForm.startTime || '开始时间' }}</view>
+					</picker>
+					<text class="date-separator">至</text>
+					<picker 
+						mode="date" 
+						:value="defaultEducationEndTime" 
+						@change="bindEndTimeChange"
+						:class="{'input-error': educationErrors.endTime}"
+					>
+						<view class="picker-text">{{ educationForm.endTime || '结束时间' }}</view>
+					</picker>
 							</view>
 							<text v-if="educationErrors.startTime" class="error-message">{{educationErrors.startTime}}</text>
 							<text v-if="educationErrors.endTime" class="error-message">{{educationErrors.endTime}}</text>
@@ -496,23 +496,23 @@
 						<view class="form-item" v-if="showWorkTimeField">
 							<text class="label required">起止时间</text>
 							<view class="date-range">
-								<picker 
-									mode="date" 
-									:value="workForm.startTime" 
-									@change="bindWorkStartTimeChange"
-									:class="{'input-error': workErrors.startTime}"
-								>
-									<view class="picker-text">{{ workForm.startTime || '开始时间' }}</view>
-								</picker>
-								<text class="date-separator">至</text>
-								<picker 
-									mode="date" 
-									:value="workForm.endTime" 
-									@change="bindWorkEndTimeChange"
-									:class="{'input-error': workErrors.endTime}"
-								>
-									<view class="picker-text">{{ workForm.endTime || '结束时间' }}</view>
-								</picker>
+							<picker 
+								mode="date" 
+								:value="defaultWorkStartTime" 
+								@change="bindWorkStartTimeChange"
+								:class="{'input-error': workErrors.startTime}"
+							>
+								<view class="picker-text">{{ workForm.startTime || '开始时间' }}</view>
+							</picker>
+							<text class="date-separator">至</text>
+							<picker 
+								mode="date" 
+								:value="defaultWorkEndTime" 
+								@change="bindWorkEndTimeChange"
+								:class="{'input-error': workErrors.endTime}"
+							>
+								<view class="picker-text">{{ workForm.endTime || '结束时间' }}</view>
+							</picker>
 							</view>
 							<text v-if="workErrors.startTime" class="error-message">{{workErrors.startTime}}</text>
 							<text v-if="workErrors.endTime" class="error-message">{{workErrors.endTime}}</text>
@@ -1086,11 +1086,59 @@ import { apiBaseUrl } from '@/common/config.js';
 				if (Object.keys(this.safeWorkFieldsConfig).length === 0) {
 					return true;
 				}
-				// 检查所有家庭成员字段是否都被隐藏
-				const fields = ['start_date', 'end_date', 'company_name', 'department', 'company_size','position','monthly_salary','direct_supervisor','supervisor_phone'];
-				return fields.some(field => this.safeWorkFieldsConfig[field]?.visible !== false);
-			},
+			// 检查所有家庭成员字段是否都被隐藏
+			const fields = ['start_date', 'end_date', 'company_name', 'department', 'company_size','position','monthly_salary','direct_supervisor','supervisor_phone'];
+			return fields.some(field => this.safeWorkFieldsConfig[field]?.visible !== false);
+		},
+		// 获取教育经历开始时间的初始值(默认为当前月份1号)
+		defaultEducationStartTime() {
+			// 如果有值,返回当前值;否则返回当前月份的1号作为picker的初始值
+			if (this.educationForm.startTime) {
+				return this.educationForm.startTime;
+			}
+			// 返回当前月份的1号
+			const now = new Date();
+			const year = now.getFullYear();
+			const month = String(now.getMonth() + 1).padStart(2, '0');
+			return `${year}-${month}-01`;
+		},
+		// 获取教育经历结束时间的初始值(默认为当前月份1号)
+		defaultEducationEndTime() {
+			// 如果有值,返回当前值;否则返回当前月份的1号作为picker的初始值
+			if (this.educationForm.endTime) {
+				return this.educationForm.endTime;
+			}
+			// 返回当前月份的1号
+			const now = new Date();
+			const year = now.getFullYear();
+			const month = String(now.getMonth() + 1).padStart(2, '0');
+			return `${year}-${month}-01`;
+		},
+		// 获取工作经历开始时间的初始值(默认为当前月份1号)
+		defaultWorkStartTime() {
+			// 如果有值,返回当前值;否则返回当前月份的1号作为picker的初始值
+			if (this.workForm.startTime) {
+				return this.workForm.startTime;
+			}
+			// 返回当前月份的1号
+			const now = new Date();
+			const year = now.getFullYear();
+			const month = String(now.getMonth() + 1).padStart(2, '0');
+			return `${year}-${month}-01`;
 		},
+		// 获取工作经历结束时间的初始值(默认为当前月份1号)
+		defaultWorkEndTime() {
+			// 如果有值,返回当前值;否则返回当前月份的1号作为picker的初始值
+			if (this.workForm.endTime) {
+				return this.workForm.endTime;
+			}
+			// 返回当前月份的1号
+			const now = new Date();
+			const year = now.getFullYear();
+			const month = String(now.getMonth() + 1).padStart(2, '0');
+			return `${year}-${month}-01`;
+		},
+	},
 		methods: {
 			// 确保当前步骤是有效的(如果需要显示的话)
 			ensureValidStep() {
@@ -1463,30 +1511,32 @@ import { apiBaseUrl } from '@/common/config.js';
 			},
 			
 			// 教育经历相关方法
-			bindStartTimeChange(e) {
-				const startTime = e.detail.value;
-				if (this.educationForm.endTime && startTime > this.educationForm.endTime) {
-					uni.showToast({
-						title: '开始时间不能大于结束时间',
-						icon: 'none'
-					});
-					return;
-				}
-				this.educationForm.startTime = startTime;
-				this.educationErrors.startTime = false;
-			},
-			bindEndTimeChange(e) {
-				const endTime = e.detail.value;
-				if (this.educationForm.startTime && endTime < this.educationForm.startTime) {
-					uni.showToast({
-						title: '结束时间不能小于开始时间',
-						icon: 'none'
-					});
-					return;
-				}
-				this.educationForm.endTime = endTime;
-				this.educationErrors.endTime = false;
-			},
+		bindStartTimeChange(e) {
+			const startTime = e.detail.value;
+			
+			if (this.educationForm.endTime && startTime > this.educationForm.endTime) {
+				uni.showToast({
+					title: '开始时间不能大于结束时间',
+					icon: 'none'
+				});
+				return;
+			}
+			this.educationForm.startTime = startTime;
+			this.educationErrors.startTime = false;
+		},
+		bindEndTimeChange(e) {
+			const endTime = e.detail.value;
+			
+			if (this.educationForm.startTime && endTime < this.educationForm.startTime) {
+				uni.showToast({
+					title: '结束时间不能小于开始时间',
+					icon: 'none'
+				});
+				return;
+			}
+			this.educationForm.endTime = endTime;
+			this.educationErrors.endTime = false;
+		},
 			bindDegreeChange(e) {
 				this.degreeIndex = e.detail.value;
 				this.educationForm.degree = this.degreeOptions[this.degreeIndex];

+ 44 - 4
unpackage/dist/dev/mp-weixin/pages/Personal/Personal.js

@@ -477,6 +477,46 @@ const _sfc_main = {
         var _a;
         return ((_a = this.safeWorkFieldsConfig[field]) == null ? void 0 : _a.visible) !== false;
       });
+    },
+    // 获取教育经历开始时间的初始值(默认为当前月份1号)
+    defaultEducationStartTime() {
+      if (this.educationForm.startTime) {
+        return this.educationForm.startTime;
+      }
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, "0");
+      return `${year}-${month}-01`;
+    },
+    // 获取教育经历结束时间的初始值(默认为当前月份1号)
+    defaultEducationEndTime() {
+      if (this.educationForm.endTime) {
+        return this.educationForm.endTime;
+      }
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, "0");
+      return `${year}-${month}-01`;
+    },
+    // 获取工作经历开始时间的初始值(默认为当前月份1号)
+    defaultWorkStartTime() {
+      if (this.workForm.startTime) {
+        return this.workForm.startTime;
+      }
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, "0");
+      return `${year}-${month}-01`;
+    },
+    // 获取工作经历结束时间的初始值(默认为当前月份1号)
+    defaultWorkEndTime() {
+      if (this.workForm.endTime) {
+        return this.workForm.endTime;
+      }
+      const now = /* @__PURE__ */ new Date();
+      const year = now.getFullYear();
+      const month = String(now.getMonth() + 1).padStart(2, "0");
+      return `${year}-${month}-01`;
     }
   },
   methods: {
@@ -2322,11 +2362,11 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     bD: $options.showEducationTimeField
   }, $options.showEducationTimeField ? common_vendor.e({
     bE: common_vendor.t($data.educationForm.startTime || "开始时间"),
-    bF: $data.educationForm.startTime,
+    bF: $options.defaultEducationStartTime,
     bG: common_vendor.o((...args) => $options.bindStartTimeChange && $options.bindStartTimeChange(...args)),
     bH: $data.educationErrors.startTime ? 1 : "",
     bI: common_vendor.t($data.educationForm.endTime || "结束时间"),
-    bJ: $data.educationForm.endTime,
+    bJ: $options.defaultEducationEndTime,
     bK: common_vendor.o((...args) => $options.bindEndTimeChange && $options.bindEndTimeChange(...args)),
     bL: $data.educationErrors.endTime ? 1 : "",
     bM: $data.educationErrors.startTime
@@ -2443,11 +2483,11 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     cS: $options.showWorkTimeField
   }, $options.showWorkTimeField ? common_vendor.e({
     cT: common_vendor.t($data.workForm.startTime || "开始时间"),
-    cU: $data.workForm.startTime,
+    cU: $options.defaultWorkStartTime,
     cV: common_vendor.o((...args) => $options.bindWorkStartTimeChange && $options.bindWorkStartTimeChange(...args)),
     cW: $data.workErrors.startTime ? 1 : "",
     cX: common_vendor.t($data.workForm.endTime || "结束时间"),
-    cY: $data.workForm.endTime,
+    cY: $options.defaultWorkEndTime,
     cZ: common_vendor.o((...args) => $options.bindWorkEndTimeChange && $options.bindWorkEndTimeChange(...args)),
     da: $data.workErrors.endTime ? 1 : "",
     db: $data.workErrors.startTime

+ 1 - 1
unpackage/dist/dev/mp-weixin/project.config.json

@@ -8,7 +8,7 @@
     "urlCheck": false,
     "es6": true,
     "postcss": false,
-    "minified": true,
+    "minified": false,
     "newFeature": true,
     "bigPackageSizeSupport": true,
     "babelSetting": {