yangg преди 5 месеца
родител
ревизия
a106143b37

+ 14 - 0
api/index.js

@@ -0,0 +1,14 @@
+/**
+ * API 接口统一出口
+ */
+import * as user from './user.js';
+// 可以导入其他模块的 API
+// import * as order from './order.js';
+// import * as product from './product.js';
+
+// 导出所有 API
+export default {
+  user,
+  // order,
+  // product,
+}; 

+ 116 - 0
api/user.js

@@ -0,0 +1,116 @@
+/**
+ * 用户相关接口
+ */
+import http from '../utils/request.js';
+
+/**
+ * 微信登录
+ * @param {Object} loginParams - 登录参数
+ * @returns {Promise} - 返回登录结果
+ */
+export function wxLogin(loginParams) {
+  // 获取 CSRF token
+  const csrfToken = uni.getStorageSync('csrfToken');
+  
+  // 确保参数格式正确,完全匹配后端期望的格式
+  const requestData = {
+    code: loginParams.code,
+    userInfo: {
+      nickname: loginParams.userInfo?.nickName || '微信用户',
+      avatarUrl: loginParams.userInfo?.avatarUrl || '',
+      gender: loginParams.userInfo?.gender || 0,
+      province: loginParams.userInfo?.province || '',
+      city: loginParams.userInfo?.city || '',
+      country: loginParams.userInfo?.country || ''
+    },
+    signature: loginParams.signature || '',
+    rawData: loginParams.rawData || '',
+    encryptedData: loginParams.encryptedData || '',
+    iv: loginParams.iv || '',
+    _csrf: csrfToken // 添加 CSRF token
+  };
+  
+  return http.post('/wechat/wechatLogin', requestData, {
+    header: {
+      'X-CSRF-Token': csrfToken
+    }
+  });
+}
+
+/**
+ * 获取用户详细信息
+ * @param {String} userId - 用户ID,可选
+ * @returns {Promise} - 返回用户详细信息
+ */
+export const getUserInfo = (userId) => {
+  // 如果提供了userId,则获取指定用户信息,否则获取当前登录用户信息
+  const url = userId ? `/wechat/getUserDetail?id=${userId}` : '/wechat/getUserDetail';
+  return http.get(url);
+};
+
+/**
+ * 获取用户手机号
+ * @param {Object} params - 包含code、encryptedData和iv的对象
+ * @returns {Promise} - 返回用户手机号信息
+ */
+export const getUserPhoneNumber = (params) => {
+  return http.post('/wechat/getUserPhoneNumber', params);
+};
+
+/**
+ * 更新用户信息
+ * @param {Object} userData - 用户数据
+ * @returns {Promise} - 返回更新结果
+ */
+export const updateUserInfo = (userData) => {
+  return http.put('/api/user/update', userData);
+};
+
+/**
+ * 上传用户头像
+ * @param {String} filePath - 文件路径
+ * @returns {Promise} - 返回上传结果
+ */
+export const uploadAvatar = (filePath) => {
+  return http.upload('/api/upload/avatar', filePath, 'avatar');
+};
+
+/**
+ * 退出登录
+ * @returns {Promise} - 返回退出结果
+ */
+export const logout = () => {
+  return http.post('/api/user/logout');
+}; 
+
+/**
+ * 获取职位列表
+ * @param {Object} params - 查询参数
+ * @param {Number} params.page - 页码
+ * @param {Number} params.pageSize - 每页数量
+ * @param {String} params.searchTerms - 搜索关键词
+ * @param {String} params.status - 职位状态
+ * @param {Number} params.tenant_id - 租户ID
+ * @returns {Promise} - 返回职位列表
+ */
+export const getJobList = (params = {}) => {
+  // 设置默认参数
+  const defaultParams = {
+    page: 1,
+    pageSize: 10,
+    searchTerms: '',
+    status: '',
+    tenant_id: 1
+  };
+  
+  // 合并默认参数和传入参数
+  const queryParams = { ...defaultParams, ...params };
+  
+  return http.get('/api/job/list', defaultParams);
+};
+
+/* 填写用户信息 */
+export const fillUserInfo = (params) => {
+  return http.post('/api/system/wechat/save_user_info', params);
+};
+

+ 97 - 0
composables/useUserApi.js

@@ -0,0 +1,97 @@
+import { wxLogin, getUserInfo, updateUserInfo, uploadAvatar, logout } from '../api/user.js';
+import { ref } from 'vue';
+
+export function useUserApi() {
+  const loading = ref(false);
+  const error = ref(null);
+  
+  // 微信登录
+  const login = async (code, userInfo = {}) => {
+    loading.value = true;
+    error.value = null;
+    
+    try {
+      const result = await wxLogin(code, userInfo);
+      loading.value = false;
+      return result;
+    } catch (err) {
+      loading.value = false;
+      error.value = err;
+      throw err;
+    }
+  };
+  
+  // 获取用户信息
+  const fetchUserInfo = async () => {
+    loading.value = true;
+    error.value = null;
+    
+    try {
+      const result = await getUserInfo();
+      loading.value = false;
+      return result;
+    } catch (err) {
+      loading.value = false;
+      error.value = err;
+      throw err;
+    }
+  };
+  
+  // 更新用户信息
+  const updateUser = async (userData) => {
+    loading.value = true;
+    error.value = null;
+    
+    try {
+      const result = await updateUserInfo(userData);
+      loading.value = false;
+      return result;
+    } catch (err) {
+      loading.value = false;
+      error.value = err;
+      throw err;
+    }
+  };
+  
+  // 上传头像
+  const uploadUserAvatar = async (filePath) => {
+    loading.value = true;
+    error.value = null;
+    
+    try {
+      const result = await uploadAvatar(filePath);
+      loading.value = false;
+      return result;
+    } catch (err) {
+      loading.value = false;
+      error.value = err;
+      throw err;
+    }
+  };
+  
+  // 退出登录
+  const logoutUser = async () => {
+    loading.value = true;
+    error.value = null;
+    
+    try {
+      const result = await logout();
+      loading.value = false;
+      return result;
+    } catch (err) {
+      loading.value = false;
+      error.value = err;
+      throw err;
+    }
+  };
+  
+  return {
+    loading,
+    error,
+    login,
+    fetchUserInfo,
+    updateUser,
+    uploadUserAvatar,
+    logoutUser
+  };
+} 

+ 5 - 0
main.js

@@ -1,9 +1,12 @@
 import App from './App'
+import http from './utils/request.js'
 
 // #ifndef VUE3
 import Vue from 'vue'
 import './uni.promisify.adaptor'
 Vue.config.productionTip = false
+// 全局挂载请求工具
+Vue.prototype.$http = http
 App.mpType = 'app'
 const app = new Vue({
   ...App
@@ -15,6 +18,8 @@ app.$mount()
 import { createSSRApp } from 'vue'
 export function createApp() {
   const app = createSSRApp(App)
+  // 全局挂载请求工具
+  app.config.globalProperties.$http = http
   return {
     app
   }

+ 41 - 5
pages/camera/camera.vue

@@ -16,7 +16,8 @@
 		<view class="content">
 			<!-- 数字人头像 -->
 			<view class="digital-avatar">
-				<image src="/static/avatar.png" mode="aspectFill" class="avatar-image"></image>
+				<web-view v-if="digitalHumanUrl" :src="digitalHumanUrl" class="digital-human-webview"></web-view>
+				<image v-else src="/static/avatar.png" mode="aspectFill" class="avatar-image"></image>
 			</view>
 
 			<!-- 进度指示器 -->
@@ -160,7 +161,8 @@
 				timerInterval: null,
 				score: 0,
 				totalQuestions: 0,
-				interviewCompleted: false
+				interviewCompleted: false,
+				digitalHumanUrl: '' // 数字人URL
 			}
 		},
 		computed: {
@@ -179,6 +181,9 @@
 			this.startTimer();
 			// 设置总题目数
 			this.totalQuestions = this.questions.length;
+			
+			// 初始化数字人
+			this.initDigitalHuman();
 		},
 		methods: {
 			startTimer() {
@@ -333,6 +338,13 @@
 				if (this.useVideo && this.aiVideoContext) {
 					this.aiVideoContext.play();
 				}
+				
+				// 触发数字人说话动画
+				if (this.digitalHumanUrl) {
+					// 假设当前问题的文本作为数字人要说的内容
+					const speakText = this.currentQuestion ? this.currentQuestion.text : '';
+					this.interactWithDigitalHuman(speakText);
+				}
 			},
 
 			pauseAiSpeaking() {
@@ -355,6 +367,25 @@
 			testEndScreen() {
 				this.interviewCompleted = true;
 				this.showEndModal = false;
+			},
+
+			// 初始化数字人
+			initDigitalHuman() {
+				// 这里可以根据实际情况设置数字人的URL
+				// 例如,可以是一个第三方数字人服务的URL,或者是本地的HTML页面
+				this.digitalHumanUrl = 'https://your-digital-human-service.com/avatar?id=123';
+				
+				// 如果使用本地HTML,可以这样设置
+				// this.digitalHumanUrl = '/hybrid/html/digital-human.html';
+			},
+			
+			// 与数字人交互的方法
+			interactWithDigitalHuman(message) {
+				// 如果数字人是通过web-view加载的,可以使用postMessage与其通信
+				const webview = this.$mp.page.$getAppWebview().children()[0];
+				if (webview) {
+					webview.evalJS(`receiveMessage('${message}')`);
+				}
 			}
 		},
 		// 添加生命周期钩子,确保在组件销毁时清除计时器
@@ -698,12 +729,10 @@
 	}
 
 	.digital-avatar {
-		/* position: absolute;
-		top: 20rpx;
-		right: 20rpx; */
 		width: 120rpx;
 		height: 120rpx;
 		z-index: 10;
+		overflow: hidden;
 	}
 	
 	.avatar-image {
@@ -713,6 +742,13 @@
 		border: 2rpx solid #e0e0e0;
 	}
 
+	.digital-human-webview {
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 20rpx;
+		border: 2rpx solid #e0e0e0;
+	}
+
 	.interview-complete-screen {
 		position: fixed;
 		top: 0;

+ 670 - 209
pages/index/index.vue

@@ -1,8 +1,8 @@
 <template>
-  <view class="interview-container">
-    <!-- 顶部导航栏 -->
-    <!-- 面试信息 -->
-    <view class="interview-info">
+	<view class="interview-container">
+		<!-- 顶部导航栏 -->
+		<!-- 面试信息 -->
+		<!-- <view class="interview-info">
       <view class="info-item">
         <text class="label">面试岗位:</text>
         <text class="value">前端岗位</text>
@@ -18,214 +18,675 @@
         <text class="label">公司全称:</text>
         <text class="value">敏龙科技集团有限公司</text>
       </view>
-    </view>
-    
-    <!-- 表单信息 -->
-    <view class="form-container">
-      <view class="form-title">填写报名信息</view>
-      <view class="form-subtitle">如确认报名(此次面试),请填写以下信息</view>
-      
-      <view class="form-item">
-        <text class="form-label">姓名 <text class="required">*</text></text>
-        <input type="text" v-model="formData.name" placeholder="请输入姓名" />
-      </view>
-      
-      <view class="form-item">
-        <text class="form-label">手机号 <text class="required">*</text></text>
-        <input type="number" v-model="formData.phone" placeholder="请输入手机号" maxlength="11" />
-      </view>
-      
-      <view class="form-item">
-        <text class="form-label">邮箱</text>
-        <input type="text" v-model="formData.email" placeholder="请输入邮箱" />
-      </view>
-      
-      <button class="submit-btn" @click="submitForm">提交</button>
-    </view>
-  </view>
+    </view> -->
+
+		<!-- 职位列表 -->
+		<view class="job-list-container" v-if="!userInfoFilled">
+			<view class="job-list-title">可选职位列表</view>
+			<view class="job-list">
+				<view v-for="(job, index) in jobList" :key="index" class="job-item"
+					:class="{'job-selected': selectedJobId === job.id}" @click="selectJob(job)">
+					<view class="job-name">{{job.title}}</view>
+					<view class="job-details">
+						<text class="job-salary">{{job.publish_date}}</text>
+						<text class="job-location">{{job.location}}</text>
+					</view>
+				</view>
+			</view>
+
+			<button class="apply-btn" :disabled="!selectedJobId" @click="applyForJob">申请面试</button>
+		</view>
+
+		<!-- 表单信息 -->
+		<view class="form-container" v-if="userInfoFilled">
+			<view class="form-title">填写报名信息</view>
+			<view class="form-subtitle">如确认报名(此次面试),请填写以下信息</view>
+
+			<view class="form-item">
+				<text class="form-label">姓名 <text class="required">*</text></text>
+				<input type="text" v-model="formData.name" placeholder="请输入姓名" />
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">性别 <text class="required">*</text></text>
+				<view class="radio-group">
+					<view class="radio-item" @click="formData.gender = '男'">
+						<view class="radio-circle" :class="{'radio-selected': formData.gender === '男'}"></view>
+						<text class="radio-text">男</text>
+					</view>
+					<view class="radio-item" @click="formData.gender = '女'">
+						<view class="radio-circle" :class="{'radio-selected': formData.gender === '女'}"></view>
+						<text class="radio-text">女</text>
+					</view>
+				</view>
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">身份证号 <text class="required">*</text></text>
+				<input type="text" v-model="formData.idCard" placeholder="请输入有效身份证号" maxlength="18" />
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">手机号 <text class="required">*</text></text>
+				<input type="number" v-model="formData.phone" placeholder="请输入手机号" maxlength="11" />
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">紧急联系人 <text class="required">*</text></text>
+				<input type="text" v-model="formData.emergencyContact" placeholder="请输入紧急联系人姓名" />
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">紧急联系人电话 <text class="required">*</text></text>
+				<input type="number" v-model="formData.emergencyPhone" placeholder="请输入紧急联系人电话" maxlength="11" />
+			</view>
+
+			<view class="form-item">
+				<text class="form-label">与紧急联系人关系 <text class="required">*</text></text>
+				<picker @change="relationChange" :value="relationIndex" :range="relationOptions">
+					<view class="picker-view">
+						<text>{{formData.relation || '请选择关系'}}</text>
+						<text class="picker-arrow">▼</text>
+					</view>
+				</picker>
+			</view>
+
+			<view class="agreement">
+				<checkbox :checked="isAgreed" @tap="toggleAgreement" color="#6c5ce7" />
+				<text class="agreement-text">
+					我已阅读并同意
+					<text class="agreement-link">《身份验证服务协议》</text>
+					<text class="agreement-link">《隐私保护政策》</text>
+					<text class="agreement-link">《网络安全协议》</text>
+				</text>
+			</view>
+
+			<button class="submit-btn" :disabled="!canSubmit" @click="submitForm">提交</button>
+		</view>
+	</view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      formData: {
-        name: '',
-        phone: '',
-        email: ''
-      }
-    }
-  },
-  methods: {
-    goHome() {
-      uni.navigateBack({
-        delta: 1
-      });
-    },
-    submitForm() {
-      // 表单验证
-      if (!this.formData.name.trim()) {
-        uni.showToast({
-          title: '请输入姓名',
-          icon: 'none'
-        });
-        return;
-      }
-      
-      if (!this.formData.phone.trim()) {
-        uni.showToast({
-          title: '请输入手机号',
-          icon: 'none'
-        });
-        return;
-      }
-      
-      if (!/^1\d{10}$/.test(this.formData.phone)) {
-        uni.showToast({
-          title: '请输入正确的手机号',
-          icon: 'none'
-        });
-        return;
-      }
-      
-      if (this.formData.email && !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.formData.email)) {
-        uni.showToast({
-          title: '请输入正确的邮箱',
-          icon: 'none'
-        });
-        return;
-      }
-      
-      // 提交表单数据
-      console.log('提交的表单数据:', this.formData);
-      
-      // 模拟提交成功
-      uni.showLoading({
-        title: '提交中...'
-      });
-      
-      setTimeout(() => {
-        uni.hideLoading();
-        uni.showToast({
-          title: '提交成功',
-          icon: 'success',
-          duration: 1500,
-          success: () => {
-            // 修改为跳转到成功页面
-            setTimeout(() => {
-              uni.navigateTo({
-                url: '/pages/success/success',  // 修改为成功页面的路径
-                fail: (err) => {
-                  console.error('页面跳转失败:', err);
-                  uni.showToast({
-                    title: '页面跳转失败',
-                    icon: 'none'
-                  });
-                }
-              });
-            }, 1500);
-          }
-        });
-      }, 1000);
-    }
-  }
-}
+	import {
+		fillUserInfo,
+		getUserInfo,
+		getJobList
+	} from '@/api/user';
+	export default {
+		data() {
+			return {
+				formData: {
+					name: '',
+					gender: '',
+					phone: '',
+					email: '',
+					idCard: '',
+					emergencyContact: '',
+					emergencyPhone: '',
+					relation: ''
+				},
+				relationOptions: ['父母', '配偶', '子女', '兄弟姐妹', '朋友', '其他'],
+				relationIndex: 0,
+				isAgreed: false,
+				userInfoFilled: false,
+				jobList: [],
+				selectedJobId: null,
+				selectedJob: null
+			}
+		},
+		onLoad() {
+			this.checkUserInfo();
+			this.fetchJobList();
+		},
+		computed: {
+			canSubmit() {
+				return this.formData.name.trim() &&
+					this.formData.gender &&
+					this.formData.phone.trim() &&
+					(/^1\d{10}$/.test(this.formData.phone)) &&
+					(!this.formData.email || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.formData.email)) &&
+					this.formData.idCard.trim() &&
+					this.formData.emergencyContact.trim() &&
+					this.formData.emergencyPhone.trim() &&
+					(/^1\d{10}$/.test(this.formData.emergencyPhone)) &&
+					this.formData.relation &&
+					this.isAgreed;
+			}
+		},
+		methods: {
+			goHome() {
+				uni.navigateBack({
+					delta: 1
+				});
+			},
+			toggleAgreement() {
+				this.isAgreed = !this.isAgreed;
+			},
+			relationChange(e) {
+				this.relationIndex = e.detail.value;
+				this.formData.relation = this.relationOptions[this.relationIndex];
+			},
+			checkUserInfo() {
+				uni.showLoading({
+					title: '加载中...'
+				});
+				console.log('id:', JSON.parse(uni.getStorageSync('userInfo')).id);
+				getUserInfo(JSON.parse(uni.getStorageSync('userInfo')).id)
+					.then(res => {
+						uni.hideLoading();
+
+						if (res.code === 200 && res.data) {
+							const userData = res.data;
+
+							if (userData.name && userData.phone) {
+								this.userInfoFilled = true;
+
+								this.formData.name = userData.name || '';
+								this.formData.gender = userData.gender || '';
+								this.formData.phone = userData.phone || '';
+								this.formData.idCard = userData.id_card || '';
+								this.formData.emergencyContact = userData.emergency_contact || '';
+								this.formData.emergencyPhone = userData.emergency_phone || '';
+								this.formData.relation = userData.relation || '';
+
+								if (userData.relation) {
+									const index = this.relationOptions.findIndex(item => item === userData.relation);
+									if (index !== -1) {
+										this.relationIndex = index;
+									}
+								}
+
+								uni.navigateTo({
+									url: '/pages/success/success'
+								});
+							}
+						}
+					})
+					.catch(err => {
+						uni.hideLoading();
+						console.error('获取用户信息失败:', err);
+						uni.showToast({
+							title: '获取用户信息失败',
+							icon: 'none'
+						});
+					});
+			},
+			fetchJobList() {
+				uni.showLoading({
+					title: '加载职位列表...'
+				});
+
+				getJobList()
+					.then(res => {
+						uni.hideLoading();
+						console.log(res);
+						this.jobList = res;
+
+					})
+					.catch(err => {
+						uni.hideLoading();
+						console.error('获取职位列表失败:', err);
+						uni.showToast({
+							title: '网络错误,请稍后重试',
+							icon: 'none'
+						});
+					});
+			},
+			selectJob(job) {
+				this.selectedJobId = job.id;
+				this.selectedJob = job;
+			},
+			applyForJob() {
+				if (!this.selectedJobId) {
+					uni.showToast({
+						title: '请选择一个职位',
+						icon: 'none'
+					});
+					return;
+				}
+
+				// 保存所选职位信息
+				uni.setStorageSync('selectedJob', JSON.stringify(this.selectedJob));
+				uni.navigateTo({
+				  url: '/pages/interview-notice/interview-notice',
+				  fail: (err) => {
+				    console.error('页面跳转失败:', err);
+				    uni.showToast({
+				      title: '页面跳转失败',
+				      icon: 'none'
+				    });
+				  }
+				});
+				// 显示填写个人信息表单
+				// this.userInfoFilled = true;
+			},
+			submitForm() {
+				if (!this.formData.name.trim()) {
+					uni.showToast({
+						title: '请输入姓名',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.gender) {
+					uni.showToast({
+						title: '请选择性别',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.phone.trim()) {
+					uni.showToast({
+						title: '请输入手机号',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!/^1\d{10}$/.test(this.formData.phone)) {
+					uni.showToast({
+						title: '请输入正确的手机号',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (this.formData.email && !/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.formData.email)) {
+					uni.showToast({
+						title: '请输入正确的邮箱',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.idCard.trim()) {
+					uni.showToast({
+						title: '请输入身份证号',
+						icon: 'none'
+					});
+					return;
+				}
+
+				const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
+				if (!idCardReg.test(this.formData.idCard)) {
+					uni.showToast({
+						title: '请输入正确的身份证号',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.emergencyContact.trim()) {
+					uni.showToast({
+						title: '请输入紧急联系人',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.emergencyPhone.trim()) {
+					uni.showToast({
+						title: '请输入紧急联系人电话',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!/^1\d{10}$/.test(this.formData.emergencyPhone)) {
+					uni.showToast({
+						title: '请输入正确的紧急联系人电话',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.formData.relation) {
+					uni.showToast({
+						title: '请选择与紧急联系人关系',
+						icon: 'none'
+					});
+					return;
+				}
+
+				if (!this.isAgreed) {
+					uni.showToast({
+						title: '请阅读并同意相关协议',
+						icon: 'none'
+					});
+					return;
+				}
+
+				const submitData = {
+					openid: JSON.parse(uni.getStorageSync('userInfo')).openid || '',
+					name: this.formData.name,
+					phone: this.formData.phone,
+					id_card: this.formData.idCard,
+					status: 1,
+					source: 'mini',
+					examine: 0,
+					tenant_id: '1',
+					emergency_contact: this.formData.emergencyContact,
+					emergency_phone: this.formData.emergencyPhone,
+					relation: this.formData.relation,
+					age: '20',
+					job_id: this.selectedJobId
+				};
+
+				uni.showLoading({
+					title: '提交中...'
+				});
+
+				fillUserInfo(submitData)
+					.then(res => {
+						uni.hideLoading();
+							this.updateLocalUserInfo();
+
+							uni.showToast({
+								title: '提交成功',
+								icon: 'success',
+								duration: 1500,
+								success: () => {
+									setTimeout(() => {
+										uni.navigateTo({
+											url: '/pages/success/success',
+											fail: (err) => {
+												console.error('页面跳转失败:', err);
+												uni.showToast({
+													title: '页面跳转失败',
+													icon: 'none'
+												});
+											}
+										});
+									}, 1500);
+								}
+							});
+						// } else {
+						// 	uni.showToast({
+						// 		title: res.msg || '提交失败,请重试',
+						// 		icon: 'none'
+						// 	});
+						// }
+					})
+					.catch(err => {
+						uni.hideLoading();
+						console.error('提交表单失败:', err);
+						uni.showToast({
+							title: '网络错误,请稍后重试',
+							icon: 'none'
+						});
+					});
+			},
+			updateLocalUserInfo() {
+				getUserInfo()
+					.then(res => {
+						if (res.code === 200 && res.data) {
+							let userInfo = {};
+							try {
+								userInfo = JSON.parse(uni.getStorageSync('userInfo') || '{}');
+							} catch (e) {
+								console.error('解析本地存储用户信息失败:', e);
+								userInfo = {};
+							}
+
+							const updatedUserInfo = {
+								...userInfo,
+								...res.data
+							};
+
+							uni.setStorageSync('userInfo', JSON.stringify(updatedUserInfo));
+						}
+					})
+					.catch(err => {
+						console.error('更新本地用户信息失败:', err);
+					});
+			}
+		}
+	}
 </script>
 
 <style>
-.interview-container {
-  display: flex;
-  flex-direction: column;
-  min-height: 100vh;
-  background-color: #f5f7fa;
-}
-
-.nav-bar {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 20rpx 30rpx;
-  background-color: #6c5ce7;
-  color: #fff;
-}
-
-.scan-btn {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  background-color: #ff9f43;
-  padding: 10rpx 30rpx;
-  border-radius: 30rpx;
-  font-size: 24rpx;
-}
-
-.interview-info {
-  background-color: #6c5ce7;
-  color: #fff;
-  padding: 20rpx 30rpx 40rpx;
-}
-
-.info-item {
-  margin: 10rpx 0;
-  font-size: 28rpx;
-}
-
-.label {
-  margin-right: 10rpx;
-}
-
-.form-container {
-  flex: 1;
-  background-color: #fff;
-  border-radius: 20rpx 20rpx 0 0;
-  margin-top: -20rpx;
-  padding: 40rpx 30rpx;
-}
-
-.form-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-bottom: 10rpx;
-}
-
-.form-subtitle {
-  font-size: 24rpx;
-  color: #999;
-  margin-bottom: 40rpx;
-}
-
-.form-item {
-  margin-bottom: 30rpx;
-}
-
-.form-label {
-  display: block;
-  font-size: 28rpx;
-  margin-bottom: 10rpx;
-}
-
-.required {
-  color: #ff4757;
-}
-
-input {
-  width: 100%;
-  height: 80rpx;
-  border: 1px solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  box-sizing: border-box;
-}
-
-.submit-btn {
-  width: 100%;
-  height: 90rpx;
-  line-height: 90rpx;
-  background-color: #6c5ce7;
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  margin-top: 60rpx;
-}
-</style> 
+	.interview-container {
+		display: flex;
+		flex-direction: column;
+		min-height: 100vh;
+		background-color: #f5f7fa;
+	}
+
+	.nav-bar {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20rpx 30rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+	}
+
+	.scan-btn {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		background-color: #ff9f43;
+		padding: 10rpx 30rpx;
+		border-radius: 30rpx;
+		font-size: 24rpx;
+	}
+
+	.interview-info {
+		background-color: #6c5ce7;
+		color: #fff;
+		padding: 20rpx 30rpx 40rpx;
+	}
+
+	.info-item {
+		margin: 10rpx 0;
+		font-size: 28rpx;
+	}
+
+	.label {
+		margin-right: 10rpx;
+	}
+
+	.form-container {
+		flex: 1;
+		background-color: #fff;
+		border-radius: 20rpx 20rpx 0 0;
+		margin-top: -20rpx;
+		padding: 40rpx 30rpx;
+	}
+
+	.form-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+	}
+
+	.form-subtitle {
+		font-size: 24rpx;
+		color: #999;
+		margin-bottom: 40rpx;
+	}
+
+	.form-item {
+		margin-bottom: 30rpx;
+	}
+
+	.form-label {
+		display: block;
+		font-size: 28rpx;
+		margin-bottom: 10rpx;
+	}
+
+	.required {
+		color: #ff4757;
+	}
+
+	input {
+		width: 100%;
+		height: 80rpx;
+		border: 1px solid #eee;
+		border-radius: 8rpx;
+		padding: 0 20rpx;
+		font-size: 28rpx;
+		box-sizing: border-box;
+	}
+
+	.submit-btn {
+		width: 100%;
+		height: 90rpx;
+		line-height: 90rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 32rpx;
+		margin-top: 60rpx;
+	}
+
+	.agreement {
+		display: flex;
+		align-items: flex-start;
+		margin-top: 20rpx;
+	}
+
+	.agreement-text {
+		font-size: 24rpx;
+		color: #666;
+		line-height: 1.5;
+		margin-left: 10rpx;
+	}
+
+	.agreement-link {
+		color: #6c5ce7;
+	}
+
+	.submit-btn[disabled] {
+		background-color: #b2b2b2;
+		color: #fff;
+	}
+
+	.radio-group {
+		display: flex;
+		flex-direction: row;
+		margin-top: 10rpx;
+	}
+
+	.radio-item {
+		display: flex;
+		align-items: center;
+		margin-right: 60rpx;
+	}
+
+	.radio-circle {
+		width: 36rpx;
+		height: 36rpx;
+		border-radius: 50%;
+		border: 2rpx solid #999;
+		margin-right: 10rpx;
+		position: relative;
+	}
+
+	.radio-selected {
+		border-color: #6c5ce7;
+	}
+
+	.radio-selected:after {
+		content: '';
+		position: absolute;
+		width: 24rpx;
+		height: 24rpx;
+		background-color: #6c5ce7;
+		border-radius: 50%;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+	}
+
+	.radio-text {
+		font-size: 28rpx;
+	}
+
+	.picker-view {
+		width: 100%;
+		height: 80rpx;
+		border: 1px solid #eee;
+		border-radius: 8rpx;
+		padding: 0 20rpx;
+		font-size: 28rpx;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+	}
+
+	.picker-arrow {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	/* 职位列表样式 */
+	.job-list-container {
+		flex: 1;
+		background-color: #fff;
+		border-radius: 20rpx 20rpx 0 0;
+		margin-top: -20rpx;
+		padding: 40rpx 30rpx;
+	}
+
+	.job-list-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 30rpx;
+	}
+
+	.job-list {
+		max-height: 800rpx;
+		overflow-y: auto;
+	}
+
+	.job-item {
+		padding: 30rpx;
+		border-radius: 12rpx;
+		background-color: #f8f9fa;
+		margin-bottom: 20rpx;
+		border: 2rpx solid transparent;
+	}
+
+	.job-selected {
+		border-color: #6c5ce7;
+		background-color: rgba(108, 92, 231, 0.1);
+	}
+
+	.job-name {
+		font-size: 30rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+	}
+
+	.job-details {
+		display: flex;
+		justify-content: space-between;
+		font-size: 24rpx;
+		color: #666;
+	}
+
+	.job-salary {
+		color: #ff6b6b;
+	}
+
+	.apply-btn {
+		width: 100%;
+		height: 90rpx;
+		line-height: 90rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 32rpx;
+		margin-top: 40rpx;
+	}
+
+	.apply-btn[disabled] {
+		background-color: #b2b2b2;
+		color: #fff;
+	}
+</style>

+ 1 - 1
pages/interview-notice/interview-notice.vue

@@ -94,7 +94,7 @@ export default {
       }
       
       uni.navigateTo({
-        url: '/pages/identity-verify/identity-verify',
+        url: '/pages/face-photo/face-photo',
         fail: (err) => {
           console.error('页面跳转失败:', err);
           uni.showToast({

+ 665 - 221
pages/my/my.vue

@@ -4,11 +4,12 @@
 			<image class="avatar" :src="userInfo.avatar || '/static/avatar.png'"></image>
 			<view class="user-details">
 				<text class="username">{{ userInfo.username }}</text>
-				<text class="user-id">ID: {{ userInfo.userId }}</text>
+				<!-- <text class="user-id">ID: {{ userInfo.userId }}</text> -->
+				<text class="user-phone" v-if="userInfo.phone">手机: {{ formatPhone(userInfo.phone) }}</text>
 			</view>
 		</view>
 		
-		<view class="user-info" v-else @click="goLogin">
+		<view class="user-info" v-else @click="wxLogin">
 			<image class="avatar" src="/static/avatar.png"></image>
 			<view class="user-details">
 				<text class="username">点击登录</text>
@@ -29,269 +30,640 @@
 		<view class="logout-btn" @click="handleLogout" v-if="isLogin">
 			<text>退出登录</text>
 		</view>
+		
+		<!-- 添加一个隐藏的授权按钮,在需要时显示 -->
+		<view class="auth-modal" v-if="showAuthModal">
+			<view class="auth-content">
+				<text class="auth-title">微信授权登录</text>
+				<text class="auth-desc">请授权获取您的微信头像和昵称</text>
+				<button class="auth-btn" @tap="getUserProfile">确认授权</button>
+				<button class="auth-cancel" @tap="cancelAuth">取消</button>
+			</view>
+		</view>
+		
+		<!-- 添加获取手机号按钮 -->
+		<button 
+			v-if="isLogin && !userInfo.phone" 
+			open-type="getPhoneNumber" 
+			@getphonenumber="getPhoneNumber" 
+			class="get-phone-btn">
+			绑定手机号
+		</button>
 	</view>
 </template>
 
 <script>
-	export default {
-		data() {
-			return {
-				isLogin: false,
-				userInfo: {
-					username: '',
-					userId: '',
-					avatar: ''
-				},
-				menuItems: [
-					{ title: '个人资料', icon: 'icon-user', action: 'profile' },
-					// { title: '我的订单', icon: 'icon-order', action: 'orders' },
-					// { title: '我的收藏', icon: 'icon-star', action: 'favorites' },
-					{ title: '设置', icon: 'icon-settings', action: 'settings' },
-					{ title: '帮助中心', icon: 'icon-help', action: 'help' }
-				]
-			}
-		},
-		onShow() {
-			// 每次显示页面时检查登录状态
-			this.checkLoginStatus();
-		},
-		methods: {
-			// 检查登录状态
-			checkLoginStatus() {
-				try {
-					const token = uni.getStorageSync('token');
-					const userInfo = uni.getStorageSync('userInfo');
+// 直接导入所有需要的 API 函数
+import { log } from 'util';
+import { wxLogin, getUserInfo, getUserPhoneNumber, logout } from '../../api/user.js';
+
+export default {
+	data() {
+		return {
+			isLogin: false,
+			userInfo: {
+				username: '',
+				userId: '',
+				avatar: ''
+			},
+			menuItems: [
+				{ title: '个人资料', icon: 'icon-user', action: 'profile' },
+				// { title: '我的订单', icon: 'icon-order', action: 'orders' },
+				// { title: '我的收藏', icon: 'icon-star', action: 'favorites' },
+				{ title: '设置', icon: 'icon-settings', action: 'settings' },
+				{ title: '帮助中心', icon: 'icon-help', action: 'help' }
+			],
+			showAuthModal: false,
+			wxLoginCode: '' // 存储微信登录code
+		}
+	},
+	onShow() {
+		// 每次显示页面时检查登录状态
+		this.checkLoginStatus();
+	},
+	methods: {
+		// 检查登录状态
+		checkLoginStatus() {
+			try {
+				const token = uni.getStorageSync('token');
+				const userInfoStr = uni.getStorageSync('userInfo');
+				
+				if (token && userInfoStr) {
+					const userInfo = JSON.parse(userInfoStr);
 					
-					if (token && userInfo) {
-						this.isLogin = true;
-						this.userInfo = JSON.parse(userInfo);
-					} else {
-						this.isLogin = false;
-						this.userInfo = {
-							username: '',
-							userId: '',
-							avatar: ''
-						};
+					// 检查登录是否过期(可选:如果需要登录有效期)
+					const loginTime = userInfo.loginTime || 0;
+					const currentTime = new Date().getTime();
+					const loginExpireTime = 7 * 24 * 60 * 60 * 1000; // 例如7天过期
+					
+					if (currentTime - loginTime > loginExpireTime) {
+						console.log('登录已过期,需要重新登录');
+						this.handleLogout(false); // 静默登出,不显示提示
+						return;
 					}
-				} catch (e) {
-					console.error('获取登录状态失败', e);
+					
+					// 设置登录状态
+					this.isLogin = true;
+					this.userInfo = userInfo;
+					
+					// 可选:每次打开页面时刷新用户信息
+					this.fetchUserDetail();
+				} else {
+					// 未登录状态
 					this.isLogin = false;
+					this.userInfo = {
+						username: '',
+						userId: '',
+						avatar: '',
+						phone: ''
+					};
 				}
-			},
+			} catch (e) {
+				console.error('获取登录状态失败', e);
+				this.isLogin = false;
+			}
+		},
+		
+		// 前往登录页
+		goLogin() {
+			// 显示登录方式选择
+			uni.showActionSheet({
+				itemList: ['账号密码登录', '微信登录'],
+				success: (res) => {
+					if (res.tapIndex === 0) {
+						// 账号密码登录
+						uni.navigateTo({
+							url: '/pages/login/login'
+						});
+					} else if (res.tapIndex === 1) {
+						// 微信登录
+						this.wxLogin();
+					}
+				}
+			});
+		},
+		
+		// 微信登录
+		wxLogin() {
+			// #ifdef MP-WEIXIN
+			this.getWxLoginCode();
+			// #endif
 			
-			// 前往登录页
-			goLogin() {
-				// 显示登录方式选择
-				uni.showActionSheet({
-					itemList: ['账号密码登录', '微信登录'],
-					success: (res) => {
-						if (res.tapIndex === 0) {
-							// 账号密码登录
-							uni.navigateTo({
-								url: '/pages/login/login'
-							});
-						} else if (res.tapIndex === 1) {
-							// 微信登录
-							this.wxLogin();
+			// #ifndef MP-WEIXIN
+			uni.showToast({
+				title: '请在微信环境中使用微信登录',
+				icon: 'none'
+			});
+			// #endif
+		},
+		
+		// 获取微信登录 code
+		getWxLoginCode() {
+			uni.showLoading({ title: '登录中...' });
+			
+			// 每次都重新获取 code
+			uni.login({
+				provider: 'weixin',
+				success: (loginRes) => {
+					console.log('获取微信登录code成功:', loginRes.code);
+					// 保存code,并显示授权弹窗
+					this.wxLoginCode = loginRes.code;
+					uni.hideLoading();
+					this.showAuthModal = true;
+				},
+				fail: (err) => {
+					uni.hideLoading();
+					console.error('获取微信登录code失败:', err);
+					uni.showToast({
+						title: '微信登录失败',
+						icon: 'none'
+					});
+				}
+			});
+		},
+		
+		// 用户点击授权按钮时调用
+		getUserProfile() {
+			// 这个方法直接绑定到按钮的点击事件
+			wx.getUserProfile({
+				desc: '用于完善用户资料',
+				success: (profileRes) => {
+					console.log('获取用户信息成功:', profileRes);
+					
+					// 准备完整的登录参数
+					const loginParams = {
+						code: this.wxLoginCode,
+						userInfo: profileRes.userInfo,
+						signature: profileRes.signature,
+						rawData: profileRes.rawData,
+						encryptedData: profileRes.encryptedData,
+						iv: profileRes.iv
+					};
+					
+					// 隐藏授权弹窗
+					this.showAuthModal = false;
+					
+					// 发送登录请求
+					this.wxLoginRequest(loginParams);
+				},
+				fail: (err) => {
+					console.error('获取用户信息失败:', err);
+					this.showAuthModal = false;
+					
+					// 如果用户拒绝授权,仍然可以使用code登录,但没有用户信息
+					this.wxLoginRequest({ 
+						code: this.wxLoginCode,
+						userInfo: {
+							nickName: '微信用户',
+							avatarUrl: '',
+							gender: 0,
+							province: '',
+							city: '',
+							country: ''
 						}
-					}
-				});
-			},
+					});
+				}
+			});
+		},
+		
+		// 取消授权
+		cancelAuth() {
+			this.showAuthModal = false;
+			// 使用code进行静默登录
+			this.wxLoginRequest({ 
+				code: this.wxLoginCode,
+				userInfo: {
+					nickName: '微信用户',
+					avatarUrl: '',
+					gender: 0,
+					province: '',
+					city: '',
+					country: ''
+				}
+			});
+		},
+		
+		// 头像选择回调
+		onChooseAvatar(e) {
+			const { avatarUrl } = e.detail;
+			// 这里可以将头像上传到服务器或进行其他处理
+			console.log('用户选择的头像:', avatarUrl);
+		},
+		
+		// 发送微信登录请求并处理结果
+		async wxLoginRequest(loginParams) {
+			try {
+				uni.showLoading({ title: '登录中...' });
+				
+				console.log('发送登录请求,参数:', loginParams);
+				
+				// 发送登录请求
+				const data = await wxLogin(loginParams);
+				console.log('登录成功,返回数据:', data);
+				
+				// 构建用户信息对象
+				const userData = this.buildUserData(data, loginParams);
+				
+				// 保存登录状态
+				this.saveLoginState(data, userData);
+				
+				// 步骤6: 获取用户详细信息 - 如果需要的话
+				if (userData.userId) {
+					this.fetchUserDetail();
+				} else {
+					console.log('无法获取用户ID,跳过获取详细信息');
+				}
+				
+				// 检查是否需要获取手机号
+				if (!userData.phone) {
+					console.log('用户未绑定手机号,可以提示用户绑定');
+					// 这里可以显示提示或自动弹出获取手机号的按钮
+				}
+			} catch (error) {
+				this.handleLoginError(error, loginParams);
+			}
+		},
+		
+		/**
+		 * 构建用户数据对象
+		 * @param {Object} data - 后端返回的数据
+		 * @param {Object} loginParams - 登录参数
+		 * @returns {Object} - 构建的用户数据
+		 */
+		buildUserData(data, loginParams) {
+			console.log('构建用户数据,服务器返回:', data);
+			
+			// 处理不同的返回格式
+			let userData = {
+				// 用户基本信息 - 优先使用服务器返回的数据
+				username: data.username || data.nickName || 
+						 (loginParams.userInfo ? loginParams.userInfo.nickName : '微信用户'),
+				
+				// 用户ID可能在不同字段
+				userId: data.userId || data.user_id || data.id || data.openid || '',
+				
+				// 头像可能在不同字段
+				avatar: data.avatar || data.avatarUrl || 
+					   (loginParams.userInfo ? loginParams.userInfo.avatarUrl : '/static/avatar.png'),
+				
+				// 用户详细信息
+				phone: data.phone || data.mobile || '',
+				gender: data.gender || (loginParams.userInfo ? loginParams.userInfo.gender : 0),
+				
+				// 微信相关信息
+				openid: data.openid || '',
+				unionid: data.unionid || '',
+				session_key: data.session_key || '',
+				
+				// 是否新用户
+				is_new_user: data.is_new_user || false,
+				
+				// 登录时间
+				loginTime: new Date().getTime()
+			};
+			
+			console.log('构建的用户数据:', userData);
+			return userData;
+		},
+		
+		/**
+		 * 保存登录状态和用户信息
+		 * @param {Object} data - 后端返回的数据
+		 * @param {Object} userData - 构建的用户数据
+		 */
+		saveLoginState(data, userData) {
+			// 保存token - 可能在不同字段
+			const token = data.token || data.session_key || '';
+			if (token) {
+				uni.setStorageSync('token', token);
+				console.log('Token已保存:', token);
+			}
+			
+			// 保存用户信息
+			uni.setStorageSync('userInfo', JSON.stringify(userData));
+			console.log('用户信息已保存');
 			
-			// 微信登录
-			wxLogin() {
-				// 判断是否在微信环境中
-				// #ifdef MP-WEIXIN
-				uni.showLoading({
-					title: '登录中...'
+			// 更新页面状态
+			this.isLogin = true;
+			this.userInfo = userData;
+			
+			// 显示成功提示
+			uni.hideLoading();
+			uni.showToast({
+				title: '登录成功',
+				icon: 'success'
+			});
+		},
+		
+		/**
+		 * 处理登录错误
+		 * @param {Error} error - 错误对象
+		 * @param {Object} loginParams - 登录参数
+		 */
+		handleLoginError(error, loginParams) {
+			uni.hideLoading();
+			
+			// 检查是否是 code 无效错误
+			if (error.message && (
+					error.message.includes('code无效') || 
+					error.message.includes('已过期') || 
+					error.message.includes('已被使用') ||
+					error.status === 999)) {
+				console.error('微信登录code无效,重新获取:', error);
+				uni.showToast({
+					title: 'code已过期,请重试',
+					icon: 'none',
+					duration: 2000
 				});
 				
-				// 直接使用 uni.login 获取 code
-				uni.login({
-					provider: 'weixin',
-					success: (loginRes) => {
-						// 获取登录凭证成功后,直接请求后端
-						this.wxLoginRequest(loginRes.code);
-						
-						// 如果需要用户头像和昵称,可以使用新的接口
-						this.getUserProfileNew();
-					},
-					fail: (err) => {
-						uni.hideLoading();
-						uni.showToast({
-							title: '微信登录失败',
-							icon: 'none'
-						});
-						console.error('微信登录失败', err);
-					}
+				// 延迟一下再重新获取code,避免频繁调用
+				setTimeout(() => {
+					this.getWxLoginCode();
+				}, 1000);
+				return;
+			}
+			
+			// 其他错误处理保持不变
+			if (error.code === 10001) {
+				// 微信未绑定账号,跳转到绑定页面
+				uni.navigateTo({
+					url: '/pages/bind/bind?code=' + (loginParams.code || '')
 				});
-				// #endif
+			} else if (error.message && error.message.includes('CSRF')) {
+				// CSRF 错误处理
+				console.error('CSRF验证失败:', error);
+				uni.showToast({
+					title: 'CSRF验证失败,请刷新页面重试',
+					icon: 'none',
+					duration: 2000
+				});
+				setTimeout(() => this.refreshCSRFToken(), 2000);
+			} else {
+				// 其他错误
+				console.error('微信登录失败:', error);
+				uni.showToast({
+					title: error.message || '登录失败',
+					icon: 'none'
+				});
+			}
+		},
+		
+		/**
+		 * 获取用户详细信息
+		 * 步骤6: 获取并更新用户详细信息
+		 */
+		async fetchUserDetail() {
+			try {
+				if (!this.isLogin) return;
+				
+				// 获取用户ID
+				const userId = this.userInfo.userId;
+				
+				// 调用获取用户详情API
+				const userDetail = await getUserInfo(userId);
+				console.log('获取用户详细信息成功:', userDetail);
 				
-				// 非微信环境提示
-				// #ifndef MP-WEIXIN
+				if (userDetail) {
+					// 更新用户信息
+					this.updateUserInfo(userDetail);
+				}
+			} catch (error) {
+				console.error('获取用户详细信息失败:', error);
+			}
+		},
+		
+		/**
+		 * 更新用户信息
+		 * @param {Object} userDetail - 用户详细信息
+		 */
+		updateUserInfo(userDetail) {
+			// 合并现有信息和新获取的详细信息
+			const updatedUserInfo = {
+				...this.userInfo,
+				// 更新基本信息
+				username: userDetail.username || userDetail.nickName || this.userInfo.username,
+				avatar: userDetail.avatar || userDetail.avatarUrl || this.userInfo.avatar,
+				phone: userDetail.phone || userDetail.mobile || this.userInfo.phone,
+				
+				// 更新详细信息
+				email: userDetail.email || '',
+				address: userDetail.address || '',
+				birthday: userDetail.birthday || '',
+				
+				// 其他字段
+				...userDetail
+			};
+			
+			// 更新状态和存储
+			this.userInfo = updatedUserInfo;
+			uni.setStorageSync('userInfo', JSON.stringify(updatedUserInfo));
+			console.log('用户详细信息已更新');
+		},
+		
+		/**
+		 * 获取用户手机号
+		 * @param {Object} e - 事件对象
+		 */
+		async getPhoneNumber(e) {
+			console.log('获取手机号事件:', e);
+			
+			// 检查是否成功获取
+			if (e.detail.errMsg !== 'getPhoneNumber:ok') {
+				console.error('用户拒绝授权手机号');
 				uni.showToast({
-					title: '请在微信环境中使用微信登录',
+					title: '获取手机号失败',
 					icon: 'none'
 				});
-				// #endif
-			},
+				return;
+			}
 			
-			// 使用新的方式获取用户头像和昵称
-			getUserProfileNew() {
-				// 获取头像
-				wx.getUserInfo({
-					desc: '用于完善用户资料',
-					success: (res) => {
-						console.log('获取用户信息成功', res);
-						// 可以在这里更新用户头像
-					},
-					fail: (err) => {
-						console.error('获取用户信息失败', err);
-					}
+			try {
+				uni.showLoading({ title: '获取手机号中...' });
+				
+				// 需要重新获取登录code
+				const loginResult = await new Promise((resolve, reject) => {
+					uni.login({
+						provider: 'weixin',
+						success: resolve,
+						fail: reject
+					});
 				});
 				
-				// 如果需要获取头像,可以使用 button 组件的开放能力
-				// 在模板中添加:
-				// <button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">获取头像</button>
-			},
+				// 准备请求参数
+				const params = {
+					code: loginResult.code,
+					encryptedData: e.detail.encryptedData,
+					iv: e.detail.iv,
+					openid: JSON.parse(uni.getStorageSync('userInfo')).openid
+				};
+				console.log('获取手机号请求参数:', JSON.parse(uni.getStorageSync('userInfo')).openid);
+				// 调用获取手机号API
+				const phoneData = await getUserPhoneNumber(params);
+				console.log('获取手机号成功:', phoneData);
+				
+				// 更新用户信息
+				if (phoneData && phoneData.phoneNumber) {
+					const updatedUserInfo = {
+						...this.userInfo,
+						phone: phoneData.phoneNumber
+					};
+					
+					// 更新状态和存储
+					this.userInfo = updatedUserInfo;
+					uni.setStorageSync('userInfo', JSON.stringify(updatedUserInfo));
+					
+					uni.showToast({
+						title: '手机号绑定成功',
+						icon: 'success'
+					});
+				} else {
+					throw new Error('未能获取到手机号');
+				}
+			} catch (error) {
+				console.error('获取手机号失败:', error);
+				uni.showToast({
+					title: error.message || '获取手机号失败',
+					icon: 'none'
+				});
+			} finally {
+				uni.hideLoading();
+			}
+		},
+		
+		// 添加刷新 CSRF token 的方法
+		refreshCSRFToken() {
+			// 这里实现获取新的 CSRF token 的逻辑
+			// 可能需要调用特定的 API 或刷新页面
+			uni.request({
+				url: 'your-api-endpoint/csrf-token', // 替换为获取 CSRF token 的 API
+				method: 'GET',
+				success: (res) => {
+					if (res.data && res.data.csrfToken) {
+						uni.setStorageSync('csrfToken', res.data.csrfToken);
+						console.log('CSRF token 已刷新');
+					}
+				},
+				fail: (err) => {
+					console.error('获取 CSRF token 失败', err);
+				}
+			});
+		},
+		
+		handleMenuClick(item) {
+			// 检查是否需要登录
+			const needLogin = ['profile', 'orders', 'favorites'];
 			
-			// 头像选择回调
-			onChooseAvatar(e) {
-				const { avatarUrl } = e.detail;
-				// 这里可以将头像上传到服务器或进行其他处理
-				console.log('用户选择的头像:', avatarUrl);
-			},
+			if (needLogin.includes(item.action) && !this.isLogin) {
+				uni.showToast({
+					title: '请先登录',
+					icon: 'none'
+				});
+				setTimeout(() => {
+					this.wxLogin()
+					//this.goLogin();
+				}, 1500);
+				return;
+			}
 			
-			// 发送微信登录请求到服务器
-			wxLoginRequest(code, userInfo = {}) {
-				// 这里替换为您的实际接口地址
-				uni.request({
-					url: 'https://your-api-domain.com/api/wx/login',
-					method: 'POST',
-					data: {
-						code: code,
-						// 不再依赖 getUserProfile 获取的信息
-						// 如果有用户选择的头像,可以在这里传递
-					},
-					success: (res) => {
-						uni.hideLoading();
-						if (res.data.code === 0) {
-							// 登录成功,保存用户信息和token
-							const userData = {
-								username: res.data.data.username || '微信用户',
-								userId: res.data.data.userId,
-								avatar: res.data.data.avatar || '/static/avatar.png'
-							};
-							
-							try {
-								uni.setStorageSync('token', res.data.data.token);
-								uni.setStorageSync('userInfo', JSON.stringify(userData));
-								
-								// 更新状态
-								this.isLogin = true;
-								this.userInfo = userData;
-								
-								uni.showToast({
-									title: '登录成功',
-									icon: 'success'
-								});
-							} catch (e) {
-								console.error('保存登录信息失败', e);
-							}
-						} else {
+			// 根据action跳转到对应页面
+			switch(item.action) {
+				case 'profile':
+					uni.navigateTo({ url: '/pages/profile/profile' });
+					break;
+				case 'orders':
+					uni.navigateTo({ url: '/pages/orders/orders' });
+					break;
+				case 'favorites':
+					uni.navigateTo({ url: '/pages/favorites/favorites' });
+					break;
+				case 'settings':
+					uni.navigateTo({ url: '/pages/settings/settings' });
+					break;
+				case 'help':
+					uni.navigateTo({ url: '/pages/help/help' });
+					break;
+				default:
+					uni.showToast({
+						title: `点击了${item.title}`,
+						icon: 'none'
+					});
+			}
+		},
+		
+		// 处理退出登录 - 增强版
+		handleLogout(showConfirm = true) {
+			const doLogout = () => {
+				// 调用登出 API
+				logout().then(() => {
+					// 清除登录信息
+					try {
+						uni.removeStorageSync('token');
+						uni.removeStorageSync('userInfo');
+						
+						// 更新状态
+						this.isLogin = false;
+						this.userInfo = {
+							username: '',
+							userId: '',
+							avatar: '',
+							phone: ''
+						};
+						
+						// 显示提示(如果需要)
+						if (showConfirm) {
+							uni.showToast({
+								title: '已退出登录',
+								icon: 'success'
+							});
+						}
+					} catch (e) {
+						console.error('退出登录失败', e);
+						if (showConfirm) {
 							uni.showToast({
-								title: res.data.msg || '登录失败',
+								title: '退出登录失败',
 								icon: 'none'
 							});
 						}
-					},
-					fail: (err) => {
-						uni.hideLoading();
+					}
+				}).catch(err => {
+					console.error('退出登录请求失败', err);
+					// 即使 API 请求失败,也清除本地登录状态
+					uni.removeStorageSync('token');
+					uni.removeStorageSync('userInfo');
+					this.isLogin = false;
+					
+					if (showConfirm) {
 						uni.showToast({
-							title: '网络请求失败',
+							title: err.message || '退出登录失败',
 							icon: 'none'
 						});
-						console.error('微信登录请求失败', err);
 					}
 				});
-			},
-			
-			handleMenuClick(item) {
-				// 检查是否需要登录
-				const needLogin = ['profile', 'orders', 'favorites'];
-				
-				if (needLogin.includes(item.action) && !this.isLogin) {
-					uni.showToast({
-						title: '请先登录',
-						icon: 'none'
-					});
-					setTimeout(() => {
-						this.goLogin();
-					}, 1500);
-					return;
-				}
-				
-				// 根据action跳转到对应页面
-				switch(item.action) {
-					case 'profile':
-						uni.navigateTo({ url: '/pages/profile/profile' });
-						break;
-					case 'orders':
-						uni.navigateTo({ url: '/pages/orders/orders' });
-						break;
-					case 'favorites':
-						uni.navigateTo({ url: '/pages/favorites/favorites' });
-						break;
-					case 'settings':
-						uni.navigateTo({ url: '/pages/settings/settings' });
-						break;
-					case 'help':
-						uni.navigateTo({ url: '/pages/help/help' });
-						break;
-					default:
-						uni.showToast({
-							title: `点击了${item.title}`,
-							icon: 'none'
-						});
-				}
-			},
+			};
 			
-			handleLogout() {
+			// 是否需要显示确认对话框
+			if (showConfirm) {
 				uni.showModal({
 					title: '提示',
 					content: '确定要退出登录吗?',
 					success: (res) => {
 						if (res.confirm) {
-							// 清除登录信息
-							try {
-								uni.removeStorageSync('token');
-								uni.removeStorageSync('userInfo');
-								
-								// 更新状态
-								this.isLogin = false;
-								this.userInfo = {
-									username: '',
-									userId: '',
-									avatar: ''
-								};
-								
-								uni.showToast({
-									title: '已退出登录',
-									icon: 'success'
-								});
-							} catch (e) {
-								console.error('退出登录失败', e);
-								uni.showToast({
-									title: '退出登录失败',
-									icon: 'none'
-								});
-							}
+							doLogout();
 						}
 					}
 				});
+			} else {
+				// 直接登出,不显示确认
+				doLogout();
 			}
+		},
+		// 格式化手机号,例如:188****8888
+		formatPhone(phone) {
+			if (!phone || phone.length < 11) return phone;
+			return phone.substring(0, 3) + '****' + phone.substring(7);
 		}
 	}
+}
 </script>
 
 <style>
@@ -333,6 +705,12 @@
 		color: #999;
 	}
 	
+	.user-phone {
+		font-size: 24rpx;
+		color: #666;
+		margin-top: 6rpx;
+	}
+	
 	.menu-list {
 		background-color: #ffffff;
 		border-radius: 12rpx;
@@ -379,4 +757,70 @@
 	.icon-right {
 		color: #cccccc;
 	}
+	
+	/* 授权弹窗样式 */
+	.auth-modal {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.5);
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		z-index: 999;
+	}
+	
+	.auth-content {
+		width: 80%;
+		background-color: #fff;
+		border-radius: 12rpx;
+		padding: 40rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+	
+	.auth-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		margin-bottom: 20rpx;
+	}
+	
+	.auth-desc {
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 40rpx;
+		text-align: center;
+	}
+	
+	.auth-btn {
+		width: 100%;
+		height: 80rpx;
+		line-height: 80rpx;
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 8rpx;
+		margin-bottom: 20rpx;
+	}
+	
+	.auth-cancel {
+		width: 100%;
+		height: 80rpx;
+		line-height: 80rpx;
+		background-color: #f5f5f5;
+		color: #333;
+		border-radius: 8rpx;
+	}
+	
+	/* 添加获取手机号按钮样式 */
+	.get-phone-btn {
+		margin-top: 20rpx;
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 8rpx;
+		font-size: 28rpx;
+		padding: 16rpx 0;
+	}
 </style> 

+ 97 - 0
services/ApiService.js

@@ -0,0 +1,97 @@
+/**
+ * API 服务类
+ * 提供按需引入的方式使用 API
+ */
+import * as userApi from '../api/user.js';
+// 导入其他 API 模块
+// import * as orderApi from '../api/order.js';
+// import * as productApi from '../api/product.js';
+import errorHandler from '../utils/errorHandler.js';
+
+class ApiService {
+  constructor() {
+    this.apis = {
+      user: userApi,
+      // order: orderApi,
+      // product: productApi
+    };
+    
+    // 为每个API方法添加错误处理包装
+    this._wrapApiWithErrorHandling();
+  }
+  
+  /**
+   * 为所有API方法添加统一的错误处理
+   * @private
+   */
+  _wrapApiWithErrorHandling() {
+    // 遍历所有API模块
+    Object.keys(this.apis).forEach(moduleName => {
+      const moduleApi = this.apis[moduleName];
+      
+      // 遍历模块中的所有方法
+      Object.keys(moduleApi).forEach(methodName => {
+        const originalMethod = moduleApi[methodName];
+        
+        // 如果是函数,则包装它
+        if (typeof originalMethod === 'function') {
+          moduleApi[methodName] = async (...args) => {
+            try {
+              return await originalMethod(...args);
+            } catch (error) {
+              // 统一处理错误
+              this._handleApiError(error, `${moduleName}.${methodName}`);
+              // 继续抛出错误,让调用者可以进行自定义处理
+              throw error;
+            }
+          };
+        }
+      });
+    });
+  }
+  
+  /**
+   * 统一处理API错误
+   * @param {Error} error - 错误对象
+   * @param {string} apiName - API名称
+   * @private
+   */
+  _handleApiError(error, apiName) {
+    errorHandler.logError(error, apiName);
+    // 错误已经在 request.js 中处理过提示,这里不需要重复提示
+  }
+  
+  /**
+   * 获取指定模块的 API
+   * @param {string} module - API 模块名称
+   * @returns {Object} - 对应模块的 API 对象
+   */
+  get(module) {
+    if (!this.apis[module]) {
+      console.error(`API 模块 "${module}" 不存在`);
+      return {};
+    }
+    return this.apis[module];
+  }
+  
+  /**
+   * 获取用户相关 API
+   * @returns {Object} - 用户相关 API 对象
+   */
+  get user() {
+    return this.apis.user;
+  }
+  
+  // 可以添加其他模块的 getter
+  // get order() {
+  //   return this.apis.order;
+  // }
+  
+  // get product() {
+  //   return this.apis.product;
+  // }
+}
+
+// 创建单例
+const apiService = new ApiService();
+export default apiService; 

+ 59 - 0
unpackage/dist/dev/mp-weixin/api/user.js

@@ -0,0 +1,59 @@
+"use strict";
+const common_vendor = require("../common/vendor.js");
+const utils_request = require("../utils/request.js");
+function wxLogin(loginParams) {
+  var _a, _b, _c, _d, _e, _f;
+  const csrfToken = common_vendor.index.getStorageSync("csrfToken");
+  const requestData = {
+    code: loginParams.code,
+    userInfo: {
+      nickname: ((_a = loginParams.userInfo) == null ? void 0 : _a.nickName) || "微信用户",
+      avatarUrl: ((_b = loginParams.userInfo) == null ? void 0 : _b.avatarUrl) || "",
+      gender: ((_c = loginParams.userInfo) == null ? void 0 : _c.gender) || 0,
+      province: ((_d = loginParams.userInfo) == null ? void 0 : _d.province) || "",
+      city: ((_e = loginParams.userInfo) == null ? void 0 : _e.city) || "",
+      country: ((_f = loginParams.userInfo) == null ? void 0 : _f.country) || ""
+    },
+    signature: loginParams.signature || "",
+    rawData: loginParams.rawData || "",
+    encryptedData: loginParams.encryptedData || "",
+    iv: loginParams.iv || "",
+    _csrf: csrfToken
+    // 添加 CSRF token
+  };
+  return utils_request.http.post("/wechat/wechatLogin", requestData, {
+    header: {
+      "X-CSRF-Token": csrfToken
+    }
+  });
+}
+const getUserInfo = (userId) => {
+  const url = userId ? `/wechat/getUserDetail?id=${userId}` : "/wechat/getUserDetail";
+  return utils_request.http.get(url);
+};
+const getUserPhoneNumber = (params) => {
+  return utils_request.http.post("/wechat/getUserPhoneNumber", params);
+};
+const logout = () => {
+  return utils_request.http.post("/api/user/logout");
+};
+const getJobList = (params = {}) => {
+  const defaultParams = {
+    page: 1,
+    pageSize: 10,
+    searchTerms: "",
+    status: "",
+    tenant_id: 1
+  };
+  ({ ...defaultParams, ...params });
+  return utils_request.http.get("/api/job/list", defaultParams);
+};
+const fillUserInfo = (params) => {
+  return utils_request.http.post("/api/system/wechat/save_user_info", params);
+};
+exports.fillUserInfo = fillUserInfo;
+exports.getJobList = getJobList;
+exports.getUserInfo = getUserInfo;
+exports.getUserPhoneNumber = getUserPhoneNumber;
+exports.logout = logout;
+exports.wxLogin = wxLogin;

+ 2 - 0
unpackage/dist/dev/mp-weixin/app.js

@@ -1,6 +1,7 @@
 "use strict";
 Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
 const common_vendor = require("./common/vendor.js");
+const utils_request = require("./utils/request.js");
 if (!Math) {
   "./pages/index/index.js";
   "./pages/success/success.js";
@@ -23,6 +24,7 @@ const _sfc_main = {
 };
 function createApp() {
   const app = common_vendor.createSSRApp(_sfc_main);
+  app.config.globalProperties.$http = utils_request.http;
   return {
     app
   };

+ 42 - 19
unpackage/dist/dev/mp-weixin/pages/camera/camera.js

@@ -61,7 +61,9 @@ const _sfc_main = {
       timerInterval: null,
       score: 0,
       totalQuestions: 0,
-      interviewCompleted: false
+      interviewCompleted: false,
+      digitalHumanUrl: ""
+      // 数字人URL
     };
   },
   computed: {
@@ -76,6 +78,7 @@ const _sfc_main = {
     }
     this.startTimer();
     this.totalQuestions = this.questions.length;
+    this.initDigitalHuman();
   },
   methods: {
     startTimer() {
@@ -185,6 +188,10 @@ const _sfc_main = {
       if (this.useVideo && this.aiVideoContext) {
         this.aiVideoContext.play();
       }
+      if (this.digitalHumanUrl) {
+        const speakText = this.currentQuestion ? this.currentQuestion.text : "";
+        this.interactWithDigitalHuman(speakText);
+      }
     },
     pauseAiSpeaking() {
       if (this.useVideo && this.aiVideoContext) {
@@ -204,6 +211,17 @@ const _sfc_main = {
     testEndScreen() {
       this.interviewCompleted = true;
       this.showEndModal = false;
+    },
+    // 初始化数字人
+    initDigitalHuman() {
+      this.digitalHumanUrl = "https://your-digital-human-service.com/avatar?id=123";
+    },
+    // 与数字人交互的方法
+    interactWithDigitalHuman(message) {
+      const webview = this.$mp.page.$getAppWebview().children()[0];
+      if (webview) {
+        webview.evalJS(`receiveMessage('${message}')`);
+      }
     }
   },
   // 添加生命周期钩子,确保在组件销毁时清除计时器
@@ -215,14 +233,19 @@ const _sfc_main = {
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   return common_vendor.e({
-    a: common_assets._imports_0,
-    b: common_vendor.t($data.currentQuestionIndex + 1),
-    c: common_vendor.t($options.currentQuestion.id),
-    d: common_vendor.t($data.questions.length),
-    e: $options.currentQuestion.isImportant
+    a: $data.digitalHumanUrl
+  }, $data.digitalHumanUrl ? {
+    b: $data.digitalHumanUrl
+  } : {
+    c: common_assets._imports_0
+  }, {
+    d: common_vendor.t($data.currentQuestionIndex + 1),
+    e: common_vendor.t($options.currentQuestion.id),
+    f: common_vendor.t($data.questions.length),
+    g: $options.currentQuestion.isImportant
   }, $options.currentQuestion.isImportant ? {} : {}, {
-    f: common_vendor.t($options.currentQuestion.text),
-    g: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
+    h: common_vendor.t($options.currentQuestion.text),
+    i: common_vendor.f($options.currentQuestion.options, (option, index, i0) => {
       return {
         a: common_vendor.t(option),
         b: index,
@@ -232,22 +255,22 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
         f: common_vendor.o(($event) => $options.selectOption(index), index)
       };
     }),
-    h: common_vendor.t($data.remainingTime),
-    i: common_vendor.o((...args) => $options.nextQuestion && $options.nextQuestion(...args)),
-    j: $data.selectedOption === null,
-    k: $data.showEndModal
+    j: common_vendor.t($data.remainingTime),
+    k: common_vendor.o((...args) => $options.nextQuestion && $options.nextQuestion(...args)),
+    l: $data.selectedOption === null,
+    m: $data.showEndModal
   }, $data.showEndModal ? {
-    l: common_vendor.t($data.score),
-    m: common_vendor.t($data.totalQuestions),
     n: common_vendor.t($data.score),
     o: common_vendor.t($data.totalQuestions),
-    p: common_vendor.o((...args) => $options.restartTest && $options.restartTest(...args)),
-    q: common_vendor.o((...args) => $options.back && $options.back(...args))
+    p: common_vendor.t($data.score),
+    q: common_vendor.t($data.totalQuestions),
+    r: common_vendor.o((...args) => $options.restartTest && $options.restartTest(...args)),
+    s: common_vendor.o((...args) => $options.back && $options.back(...args))
   } : {}, {
-    r: $data.interviewCompleted
+    t: $data.interviewCompleted
   }, $data.interviewCompleted ? {
-    s: common_assets._imports_0,
-    t: common_vendor.o((...args) => $options.back && $options.back(...args))
+    v: common_assets._imports_0,
+    w: common_vendor.o((...args) => $options.back && $options.back(...args))
   } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/camera/camera.wxml

@@ -1 +1 @@
-<view class="camera-container"><view class="content"><view class="digital-avatar"><image src="{{a}}" mode="aspectFill" class="avatar-image"></image></view><view class="interview-content"><view class="question-number"><text>{{b}}-{{c}}/{{d}}</text></view><view class="question-area"><view class="question-importance"><text wx:if="{{e}}">重点题</text> {{f}}</view><view class="options"><view wx:for="{{g}}" wx:for-item="option" wx:key="b" class="{{['option-item', option.c && 'option-selected', option.d && 'option-correct', option.e && 'option-wrong']}}" bindtap="{{option.f}}"><text class="option-text">{{option.a}}</text></view></view><view class="timer-container"><text class="timer-text">本题剩余时间 {{h}}</text></view><button class="next-button" bindtap="{{i}}" disabled="{{j}}"> 下一题 </button></view></view></view><view wx:if="{{k}}" class="interview-end-modal"><view class="modal-content"><view class="modal-title">测试已完成</view><view class="score-display">{{l}}/{{m}}</view><view class="modal-message">您在本次中国传统文化测试中答对了{{n}}道题目,共{{o}}道题目。</view><view class="modal-buttons"><button type="default" class="modal-button" bindtap="{{p}}">重新测试</button><button type="primary" class="modal-button" bindtap="{{q}}">返回首页</button></view></view></view><view wx:if="{{r}}" class="interview-complete-screen"><view class="digital-avatar-large"><image src="{{s}}" mode="aspectFill" class="avatar-image-large"></image></view><view class="complete-message">AI面试已结束</view><view class="complete-description">本次面试的全部环节已结束。非常感谢您的参与。</view><button class="complete-button" bindtap="{{t}}">我知道了</button></view></view>
+<view class="camera-container"><view class="content"><view class="digital-avatar"><web-view wx:if="{{a}}" src="{{b}}" class="digital-human-webview"></web-view><image wx:else src="{{c}}" mode="aspectFill" class="avatar-image"></image></view><view class="interview-content"><view class="question-number"><text>{{d}}-{{e}}/{{f}}</text></view><view class="question-area"><view class="question-importance"><text wx:if="{{g}}">重点题</text> {{h}}</view><view class="options"><view wx:for="{{i}}" wx:for-item="option" wx:key="b" class="{{['option-item', option.c && 'option-selected', option.d && 'option-correct', option.e && 'option-wrong']}}" bindtap="{{option.f}}"><text class="option-text">{{option.a}}</text></view></view><view class="timer-container"><text class="timer-text">本题剩余时间 {{j}}</text></view><button class="next-button" bindtap="{{k}}" disabled="{{l}}"> 下一题 </button></view></view></view><view wx:if="{{m}}" class="interview-end-modal"><view class="modal-content"><view class="modal-title">测试已完成</view><view class="score-display">{{n}}/{{o}}</view><view class="modal-message">您在本次中国传统文化测试中答对了{{p}}道题目,共{{q}}道题目。</view><view class="modal-buttons"><button type="default" class="modal-button" bindtap="{{r}}">重新测试</button><button type="primary" class="modal-button" bindtap="{{s}}">返回首页</button></view></view></view><view wx:if="{{t}}" class="interview-complete-screen"><view class="digital-avatar-large"><image src="{{v}}" mode="aspectFill" class="avatar-image-large"></image></view><view class="complete-message">AI面试已结束</view><view class="complete-description">本次面试的全部环节已结束。非常感谢您的参与。</view><button class="complete-button" bindtap="{{w}}">我知道了</button></view></view>

+ 7 - 3
unpackage/dist/dev/mp-weixin/pages/camera/camera.wxss

@@ -281,12 +281,10 @@
 		animation: speaking 1.5s infinite;
 }
 .digital-avatar {
-		/* position: absolute;
-		top: 20rpx;
-		right: 20rpx; */
 		width: 120rpx;
 		height: 120rpx;
 		z-index: 10;
+		overflow: hidden;
 }
 .avatar-image {
 		width: 120rpx;
@@ -294,6 +292,12 @@
 		border-radius: 20rpx;
 		border: 2rpx solid #e0e0e0;
 }
+.digital-human-webview {
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 20rpx;
+		border: 2rpx solid #e0e0e0;
+}
 .interview-complete-screen {
 		position: fixed;
 		top: 0;

+ 253 - 15
unpackage/dist/dev/mp-weixin/pages/index/index.js

@@ -1,21 +1,129 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
+const api_user = require("../../api/user.js");
 const _sfc_main = {
   data() {
     return {
       formData: {
         name: "",
+        gender: "",
         phone: "",
-        email: ""
-      }
+        email: "",
+        idCard: "",
+        emergencyContact: "",
+        emergencyPhone: "",
+        relation: ""
+      },
+      relationOptions: ["父母", "配偶", "子女", "兄弟姐妹", "朋友", "其他"],
+      relationIndex: 0,
+      isAgreed: false,
+      userInfoFilled: false,
+      jobList: [],
+      selectedJobId: null,
+      selectedJob: null
     };
   },
+  onLoad() {
+    this.checkUserInfo();
+    this.fetchJobList();
+  },
+  computed: {
+    canSubmit() {
+      return this.formData.name.trim() && this.formData.gender && this.formData.phone.trim() && /^1\d{10}$/.test(this.formData.phone) && (!this.formData.email || /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/.test(this.formData.email)) && this.formData.idCard.trim() && this.formData.emergencyContact.trim() && this.formData.emergencyPhone.trim() && /^1\d{10}$/.test(this.formData.emergencyPhone) && this.formData.relation && this.isAgreed;
+    }
+  },
   methods: {
     goHome() {
       common_vendor.index.navigateBack({
         delta: 1
       });
     },
+    toggleAgreement() {
+      this.isAgreed = !this.isAgreed;
+    },
+    relationChange(e) {
+      this.relationIndex = e.detail.value;
+      this.formData.relation = this.relationOptions[this.relationIndex];
+    },
+    checkUserInfo() {
+      common_vendor.index.showLoading({
+        title: "加载中..."
+      });
+      console.log("id:", JSON.parse(common_vendor.index.getStorageSync("userInfo")).id);
+      api_user.getUserInfo(JSON.parse(common_vendor.index.getStorageSync("userInfo")).id).then((res) => {
+        common_vendor.index.hideLoading();
+        if (res.code === 200 && res.data) {
+          const userData = res.data;
+          if (userData.name && userData.phone) {
+            this.userInfoFilled = true;
+            this.formData.name = userData.name || "";
+            this.formData.gender = userData.gender || "";
+            this.formData.phone = userData.phone || "";
+            this.formData.idCard = userData.id_card || "";
+            this.formData.emergencyContact = userData.emergency_contact || "";
+            this.formData.emergencyPhone = userData.emergency_phone || "";
+            this.formData.relation = userData.relation || "";
+            if (userData.relation) {
+              const index = this.relationOptions.findIndex((item) => item === userData.relation);
+              if (index !== -1) {
+                this.relationIndex = index;
+              }
+            }
+            common_vendor.index.navigateTo({
+              url: "/pages/success/success"
+            });
+          }
+        }
+      }).catch((err) => {
+        common_vendor.index.hideLoading();
+        console.error("获取用户信息失败:", err);
+        common_vendor.index.showToast({
+          title: "获取用户信息失败",
+          icon: "none"
+        });
+      });
+    },
+    fetchJobList() {
+      common_vendor.index.showLoading({
+        title: "加载职位列表..."
+      });
+      api_user.getJobList().then((res) => {
+        common_vendor.index.hideLoading();
+        console.log(res);
+        this.jobList = res;
+      }).catch((err) => {
+        common_vendor.index.hideLoading();
+        console.error("获取职位列表失败:", err);
+        common_vendor.index.showToast({
+          title: "网络错误,请稍后重试",
+          icon: "none"
+        });
+      });
+    },
+    selectJob(job) {
+      this.selectedJobId = job.id;
+      this.selectedJob = job;
+    },
+    applyForJob() {
+      if (!this.selectedJobId) {
+        common_vendor.index.showToast({
+          title: "请选择一个职位",
+          icon: "none"
+        });
+        return;
+      }
+      common_vendor.index.setStorageSync("selectedJob", JSON.stringify(this.selectedJob));
+      common_vendor.index.navigateTo({
+        url: "/pages/interview-notice/interview-notice",
+        fail: (err) => {
+          console.error("页面跳转失败:", err);
+          common_vendor.index.showToast({
+            title: "页面跳转失败",
+            icon: "none"
+          });
+        }
+      });
+    },
     submitForm() {
       if (!this.formData.name.trim()) {
         common_vendor.index.showToast({
@@ -24,6 +132,13 @@ const _sfc_main = {
         });
         return;
       }
+      if (!this.formData.gender) {
+        common_vendor.index.showToast({
+          title: "请选择性别",
+          icon: "none"
+        });
+        return;
+      }
       if (!this.formData.phone.trim()) {
         common_vendor.index.showToast({
           title: "请输入手机号",
@@ -45,12 +160,77 @@ const _sfc_main = {
         });
         return;
       }
-      console.log("提交的表单数据:", this.formData);
+      if (!this.formData.idCard.trim()) {
+        common_vendor.index.showToast({
+          title: "请输入身份证号",
+          icon: "none"
+        });
+        return;
+      }
+      const idCardReg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/;
+      if (!idCardReg.test(this.formData.idCard)) {
+        common_vendor.index.showToast({
+          title: "请输入正确的身份证号",
+          icon: "none"
+        });
+        return;
+      }
+      if (!this.formData.emergencyContact.trim()) {
+        common_vendor.index.showToast({
+          title: "请输入紧急联系人",
+          icon: "none"
+        });
+        return;
+      }
+      if (!this.formData.emergencyPhone.trim()) {
+        common_vendor.index.showToast({
+          title: "请输入紧急联系人电话",
+          icon: "none"
+        });
+        return;
+      }
+      if (!/^1\d{10}$/.test(this.formData.emergencyPhone)) {
+        common_vendor.index.showToast({
+          title: "请输入正确的紧急联系人电话",
+          icon: "none"
+        });
+        return;
+      }
+      if (!this.formData.relation) {
+        common_vendor.index.showToast({
+          title: "请选择与紧急联系人关系",
+          icon: "none"
+        });
+        return;
+      }
+      if (!this.isAgreed) {
+        common_vendor.index.showToast({
+          title: "请阅读并同意相关协议",
+          icon: "none"
+        });
+        return;
+      }
+      const submitData = {
+        openid: JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid || "",
+        name: this.formData.name,
+        phone: this.formData.phone,
+        id_card: this.formData.idCard,
+        status: 1,
+        source: "mini",
+        examine: 0,
+        tenant_id: "1",
+        emergency_contact: this.formData.emergencyContact,
+        emergency_phone: this.formData.emergencyPhone,
+        relation: this.formData.relation,
+        age: "20",
+        job_id: this.selectedJobId
+      };
       common_vendor.index.showLoading({
         title: "提交中..."
       });
-      setTimeout(() => {
+      api_user.fillUserInfo(submitData).then((res) => {
         common_vendor.index.hideLoading();
+        this.updateLocalUserInfo();
         common_vendor.index.showToast({
           title: "提交成功",
           icon: "success",
@@ -59,7 +239,6 @@ const _sfc_main = {
             setTimeout(() => {
               common_vendor.index.navigateTo({
                 url: "/pages/success/success",
-                // 修改为成功页面的路径
                 fail: (err) => {
                   console.error("页面跳转失败:", err);
                   common_vendor.index.showToast({
@@ -71,20 +250,79 @@ const _sfc_main = {
             }, 1500);
           }
         });
-      }, 1e3);
+      }).catch((err) => {
+        common_vendor.index.hideLoading();
+        console.error("提交表单失败:", err);
+        common_vendor.index.showToast({
+          title: "网络错误,请稍后重试",
+          icon: "none"
+        });
+      });
+    },
+    updateLocalUserInfo() {
+      api_user.getUserInfo().then((res) => {
+        if (res.code === 200 && res.data) {
+          let userInfo = {};
+          try {
+            userInfo = JSON.parse(common_vendor.index.getStorageSync("userInfo") || "{}");
+          } catch (e) {
+            console.error("解析本地存储用户信息失败:", e);
+            userInfo = {};
+          }
+          const updatedUserInfo = {
+            ...userInfo,
+            ...res.data
+          };
+          common_vendor.index.setStorageSync("userInfo", JSON.stringify(updatedUserInfo));
+        }
+      }).catch((err) => {
+        console.error("更新本地用户信息失败:", err);
+      });
     }
   }
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
-  return {
-    a: $data.formData.name,
-    b: common_vendor.o(($event) => $data.formData.name = $event.detail.value),
-    c: $data.formData.phone,
-    d: common_vendor.o(($event) => $data.formData.phone = $event.detail.value),
-    e: $data.formData.email,
-    f: common_vendor.o(($event) => $data.formData.email = $event.detail.value),
-    g: common_vendor.o((...args) => $options.submitForm && $options.submitForm(...args))
-  };
+  return common_vendor.e({
+    a: !$data.userInfoFilled
+  }, !$data.userInfoFilled ? {
+    b: common_vendor.f($data.jobList, (job, index, i0) => {
+      return {
+        a: common_vendor.t(job.title),
+        b: common_vendor.t(job.publish_date),
+        c: common_vendor.t(job.location),
+        d: index,
+        e: $data.selectedJobId === job.id ? 1 : "",
+        f: common_vendor.o(($event) => $options.selectJob(job), index)
+      };
+    }),
+    c: !$data.selectedJobId,
+    d: common_vendor.o((...args) => $options.applyForJob && $options.applyForJob(...args))
+  } : {}, {
+    e: $data.userInfoFilled
+  }, $data.userInfoFilled ? {
+    f: $data.formData.name,
+    g: common_vendor.o(($event) => $data.formData.name = $event.detail.value),
+    h: $data.formData.gender === "男" ? 1 : "",
+    i: common_vendor.o(($event) => $data.formData.gender = "男"),
+    j: $data.formData.gender === "女" ? 1 : "",
+    k: common_vendor.o(($event) => $data.formData.gender = "女"),
+    l: $data.formData.idCard,
+    m: common_vendor.o(($event) => $data.formData.idCard = $event.detail.value),
+    n: $data.formData.phone,
+    o: common_vendor.o(($event) => $data.formData.phone = $event.detail.value),
+    p: $data.formData.emergencyContact,
+    q: common_vendor.o(($event) => $data.formData.emergencyContact = $event.detail.value),
+    r: $data.formData.emergencyPhone,
+    s: common_vendor.o(($event) => $data.formData.emergencyPhone = $event.detail.value),
+    t: common_vendor.t($data.formData.relation || "请选择关系"),
+    v: common_vendor.o((...args) => $options.relationChange && $options.relationChange(...args)),
+    w: $data.relationIndex,
+    x: $data.relationOptions,
+    y: $data.isAgreed,
+    z: common_vendor.o((...args) => $options.toggleAgreement && $options.toggleAgreement(...args)),
+    A: !$options.canSubmit,
+    B: common_vendor.o((...args) => $options.submitForm && $options.submitForm(...args))
+  } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);
 wx.createPage(MiniProgramPage);

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
unpackage/dist/dev/mp-weixin/pages/index/index.wxml


+ 180 - 54
unpackage/dist/dev/mp-weixin/pages/index/index.wxss

@@ -1,83 +1,209 @@
 
 .interview-container {
-  display: flex;
-  flex-direction: column;
-  min-height: 100vh;
-  background-color: #f5f7fa;
+		display: flex;
+		flex-direction: column;
+		min-height: 100vh;
+		background-color: #f5f7fa;
 }
 .nav-bar {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 20rpx 30rpx;
-  background-color: #6c5ce7;
-  color: #fff;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20rpx 30rpx;
+		background-color: #6c5ce7;
+		color: #fff;
 }
 .scan-btn {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  background-color: #ff9f43;
-  padding: 10rpx 30rpx;
-  border-radius: 30rpx;
-  font-size: 24rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		background-color: #ff9f43;
+		padding: 10rpx 30rpx;
+		border-radius: 30rpx;
+		font-size: 24rpx;
 }
 .interview-info {
-  background-color: #6c5ce7;
-  color: #fff;
-  padding: 20rpx 30rpx 40rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+		padding: 20rpx 30rpx 40rpx;
 }
 .info-item {
-  margin: 10rpx 0;
-  font-size: 28rpx;
+		margin: 10rpx 0;
+		font-size: 28rpx;
 }
 .label {
-  margin-right: 10rpx;
+		margin-right: 10rpx;
 }
 .form-container {
-  flex: 1;
-  background-color: #fff;
-  border-radius: 20rpx 20rpx 0 0;
-  margin-top: -20rpx;
-  padding: 40rpx 30rpx;
+		flex: 1;
+		background-color: #fff;
+		border-radius: 20rpx 20rpx 0 0;
+		margin-top: -20rpx;
+		padding: 40rpx 30rpx;
 }
 .form-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-bottom: 10rpx;
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
 }
 .form-subtitle {
-  font-size: 24rpx;
-  color: #999;
-  margin-bottom: 40rpx;
+		font-size: 24rpx;
+		color: #999;
+		margin-bottom: 40rpx;
 }
 .form-item {
-  margin-bottom: 30rpx;
+		margin-bottom: 30rpx;
 }
 .form-label {
-  display: block;
-  font-size: 28rpx;
-  margin-bottom: 10rpx;
+		display: block;
+		font-size: 28rpx;
+		margin-bottom: 10rpx;
 }
 .required {
-  color: #ff4757;
+		color: #ff4757;
 }
 input {
-  width: 100%;
-  height: 80rpx;
-  border: 1px solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  box-sizing: border-box;
+		width: 100%;
+		height: 80rpx;
+		border: 1px solid #eee;
+		border-radius: 8rpx;
+		padding: 0 20rpx;
+		font-size: 28rpx;
+		box-sizing: border-box;
 }
 .submit-btn {
-  width: 100%;
-  height: 90rpx;
-  line-height: 90rpx;
-  background-color: #6c5ce7;
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  margin-top: 60rpx;
+		width: 100%;
+		height: 90rpx;
+		line-height: 90rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 32rpx;
+		margin-top: 60rpx;
+}
+.agreement {
+		display: flex;
+		align-items: flex-start;
+		margin-top: 20rpx;
+}
+.agreement-text {
+		font-size: 24rpx;
+		color: #666;
+		line-height: 1.5;
+		margin-left: 10rpx;
+}
+.agreement-link {
+		color: #6c5ce7;
+}
+.submit-btn[disabled] {
+		background-color: #b2b2b2;
+		color: #fff;
+}
+.radio-group {
+		display: flex;
+		flex-direction: row;
+		margin-top: 10rpx;
+}
+.radio-item {
+		display: flex;
+		align-items: center;
+		margin-right: 60rpx;
+}
+.radio-circle {
+		width: 36rpx;
+		height: 36rpx;
+		border-radius: 50%;
+		border: 2rpx solid #999;
+		margin-right: 10rpx;
+		position: relative;
+}
+.radio-selected {
+		border-color: #6c5ce7;
+}
+.radio-selected:after {
+		content: '';
+		position: absolute;
+		width: 24rpx;
+		height: 24rpx;
+		background-color: #6c5ce7;
+		border-radius: 50%;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+}
+.radio-text {
+		font-size: 28rpx;
+}
+.picker-view {
+		width: 100%;
+		height: 80rpx;
+		border: 1px solid #eee;
+		border-radius: 8rpx;
+		padding: 0 20rpx;
+		font-size: 28rpx;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+}
+.picker-arrow {
+		font-size: 24rpx;
+		color: #999;
+}
+
+	/* 职位列表样式 */
+.job-list-container {
+		flex: 1;
+		background-color: #fff;
+		border-radius: 20rpx 20rpx 0 0;
+		margin-top: -20rpx;
+		padding: 40rpx 30rpx;
+}
+.job-list-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 30rpx;
+}
+.job-list {
+		max-height: 800rpx;
+		overflow-y: auto;
+}
+.job-item {
+		padding: 30rpx;
+		border-radius: 12rpx;
+		background-color: #f8f9fa;
+		margin-bottom: 20rpx;
+		border: 2rpx solid transparent;
+}
+.job-selected {
+		border-color: #6c5ce7;
+		background-color: rgba(108, 92, 231, 0.1);
+}
+.job-name {
+		font-size: 30rpx;
+		font-weight: bold;
+		margin-bottom: 10rpx;
+}
+.job-details {
+		display: flex;
+		justify-content: space-between;
+		font-size: 24rpx;
+		color: #666;
+}
+.job-salary {
+		color: #ff6b6b;
+}
+.apply-btn {
+		width: 100%;
+		height: 90rpx;
+		line-height: 90rpx;
+		background-color: #6c5ce7;
+		color: #fff;
+		border-radius: 45rpx;
+		font-size: 32rpx;
+		margin-top: 40rpx;
+}
+.apply-btn[disabled] {
+		background-color: #b2b2b2;
+		color: #fff;
 }

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/interview-notice/interview-notice.js

@@ -19,7 +19,7 @@ const _sfc_main = {
         return;
       }
       common_vendor.index.navigateTo({
-        url: "/pages/identity-verify/identity-verify",
+        url: "/pages/face-photo/face-photo",
         fail: (err) => {
           console.error("页面跳转失败:", err);
           common_vendor.index.showToast({

+ 365 - 84
unpackage/dist/dev/mp-weixin/pages/my/my.js

@@ -1,6 +1,12 @@
 "use strict";
 const common_vendor = require("../../common/vendor.js");
+const api_user = require("../../api/user.js");
 const common_assets = require("../../common/assets.js");
+new Proxy({}, {
+  get(_, key) {
+    throw new Error(`Module "util" has been externalized for browser compatibility. Cannot access "util.${key}" in client code.  See https://vitejs.dev/guide/troubleshooting.html#module-externalized-for-browser-compatibility for more details.`);
+  }
+});
 const _sfc_main = {
   data() {
     return {
@@ -16,7 +22,10 @@ const _sfc_main = {
         // { title: '我的收藏', icon: 'icon-star', action: 'favorites' },
         { title: "设置", icon: "icon-settings", action: "settings" },
         { title: "帮助中心", icon: "icon-help", action: "help" }
-      ]
+      ],
+      showAuthModal: false,
+      wxLoginCode: ""
+      // 存储微信登录code
     };
   },
   onShow() {
@@ -27,16 +36,27 @@ const _sfc_main = {
     checkLoginStatus() {
       try {
         const token = common_vendor.index.getStorageSync("token");
-        const userInfo = common_vendor.index.getStorageSync("userInfo");
-        if (token && userInfo) {
+        const userInfoStr = common_vendor.index.getStorageSync("userInfo");
+        if (token && userInfoStr) {
+          const userInfo = JSON.parse(userInfoStr);
+          const loginTime = userInfo.loginTime || 0;
+          const currentTime = (/* @__PURE__ */ new Date()).getTime();
+          const loginExpireTime = 7 * 24 * 60 * 60 * 1e3;
+          if (currentTime - loginTime > loginExpireTime) {
+            console.log("登录已过期,需要重新登录");
+            this.handleLogout(false);
+            return;
+          }
           this.isLogin = true;
-          this.userInfo = JSON.parse(userInfo);
+          this.userInfo = userInfo;
+          this.fetchUserDetail();
         } else {
           this.isLogin = false;
           this.userInfo = {
             username: "",
             userId: "",
-            avatar: ""
+            avatar: "",
+            phone: ""
           };
         }
       } catch (e) {
@@ -61,34 +81,75 @@ const _sfc_main = {
     },
     // 微信登录
     wxLogin() {
-      common_vendor.index.showLoading({
-        title: "登录中..."
-      });
+      this.getWxLoginCode();
+    },
+    // 获取微信登录 code
+    getWxLoginCode() {
+      common_vendor.index.showLoading({ title: "登录中..." });
       common_vendor.index.login({
         provider: "weixin",
         success: (loginRes) => {
-          this.wxLoginRequest(loginRes.code);
-          this.getUserProfileNew();
+          console.log("获取微信登录code成功:", loginRes.code);
+          this.wxLoginCode = loginRes.code;
+          common_vendor.index.hideLoading();
+          this.showAuthModal = true;
         },
         fail: (err) => {
           common_vendor.index.hideLoading();
+          console.error("获取微信登录code失败:", err);
           common_vendor.index.showToast({
             title: "微信登录失败",
             icon: "none"
           });
-          console.error("微信登录失败", err);
         }
       });
     },
-    // 使用新的方式获取用户头像和昵称
-    getUserProfileNew() {
-      common_vendor.wx$1.getUserInfo({
+    // 用户点击授权按钮时调用
+    getUserProfile() {
+      common_vendor.wx$1.getUserProfile({
         desc: "用于完善用户资料",
-        success: (res) => {
-          console.log("获取用户信息成功", res);
+        success: (profileRes) => {
+          console.log("获取用户信息成功:", profileRes);
+          const loginParams = {
+            code: this.wxLoginCode,
+            userInfo: profileRes.userInfo,
+            signature: profileRes.signature,
+            rawData: profileRes.rawData,
+            encryptedData: profileRes.encryptedData,
+            iv: profileRes.iv
+          };
+          this.showAuthModal = false;
+          this.wxLoginRequest(loginParams);
         },
         fail: (err) => {
-          console.error("获取用户信息失败", err);
+          console.error("获取用户信息失败:", err);
+          this.showAuthModal = false;
+          this.wxLoginRequest({
+            code: this.wxLoginCode,
+            userInfo: {
+              nickName: "微信用户",
+              avatarUrl: "",
+              gender: 0,
+              province: "",
+              city: "",
+              country: ""
+            }
+          });
+        }
+      });
+    },
+    // 取消授权
+    cancelAuth() {
+      this.showAuthModal = false;
+      this.wxLoginRequest({
+        code: this.wxLoginCode,
+        userInfo: {
+          nickName: "微信用户",
+          avatarUrl: "",
+          gender: 0,
+          province: "",
+          city: "",
+          country: ""
         }
       });
     },
@@ -97,50 +158,227 @@ const _sfc_main = {
       const { avatarUrl } = e.detail;
       console.log("用户选择的头像:", avatarUrl);
     },
-    // 发送微信登录请求到服务器
-    wxLoginRequest(code, userInfo = {}) {
+    // 发送微信登录请求并处理结果
+    async wxLoginRequest(loginParams) {
+      try {
+        common_vendor.index.showLoading({ title: "登录中..." });
+        console.log("发送登录请求,参数:", loginParams);
+        const data = await api_user.wxLogin(loginParams);
+        console.log("登录成功,返回数据:", data);
+        const userData = this.buildUserData(data, loginParams);
+        this.saveLoginState(data, userData);
+        if (userData.userId) {
+          this.fetchUserDetail();
+        } else {
+          console.log("无法获取用户ID,跳过获取详细信息");
+        }
+        if (!userData.phone) {
+          console.log("用户未绑定手机号,可以提示用户绑定");
+        }
+      } catch (error) {
+        this.handleLoginError(error, loginParams);
+      }
+    },
+    /**
+     * 构建用户数据对象
+     * @param {Object} data - 后端返回的数据
+     * @param {Object} loginParams - 登录参数
+     * @returns {Object} - 构建的用户数据
+     */
+    buildUserData(data, loginParams) {
+      console.log("构建用户数据,服务器返回:", data);
+      let userData = {
+        // 用户基本信息 - 优先使用服务器返回的数据
+        username: data.username || data.nickName || (loginParams.userInfo ? loginParams.userInfo.nickName : "微信用户"),
+        // 用户ID可能在不同字段
+        userId: data.userId || data.user_id || data.id || data.openid || "",
+        // 头像可能在不同字段
+        avatar: data.avatar || data.avatarUrl || (loginParams.userInfo ? loginParams.userInfo.avatarUrl : "/static/avatar.png"),
+        // 用户详细信息
+        phone: data.phone || data.mobile || "",
+        gender: data.gender || (loginParams.userInfo ? loginParams.userInfo.gender : 0),
+        // 微信相关信息
+        openid: data.openid || "",
+        unionid: data.unionid || "",
+        session_key: data.session_key || "",
+        // 是否新用户
+        is_new_user: data.is_new_user || false,
+        // 登录时间
+        loginTime: (/* @__PURE__ */ new Date()).getTime()
+      };
+      console.log("构建的用户数据:", userData);
+      return userData;
+    },
+    /**
+     * 保存登录状态和用户信息
+     * @param {Object} data - 后端返回的数据
+     * @param {Object} userData - 构建的用户数据
+     */
+    saveLoginState(data, userData) {
+      const token = data.token || data.session_key || "";
+      if (token) {
+        common_vendor.index.setStorageSync("token", token);
+        console.log("Token已保存:", token);
+      }
+      common_vendor.index.setStorageSync("userInfo", JSON.stringify(userData));
+      console.log("用户信息已保存");
+      this.isLogin = true;
+      this.userInfo = userData;
+      common_vendor.index.hideLoading();
+      common_vendor.index.showToast({
+        title: "登录成功",
+        icon: "success"
+      });
+    },
+    /**
+     * 处理登录错误
+     * @param {Error} error - 错误对象
+     * @param {Object} loginParams - 登录参数
+     */
+    handleLoginError(error, loginParams) {
+      common_vendor.index.hideLoading();
+      if (error.message && (error.message.includes("code无效") || error.message.includes("已过期") || error.message.includes("已被使用") || error.status === 999)) {
+        console.error("微信登录code无效,重新获取:", error);
+        common_vendor.index.showToast({
+          title: "code已过期,请重试",
+          icon: "none",
+          duration: 2e3
+        });
+        setTimeout(() => {
+          this.getWxLoginCode();
+        }, 1e3);
+        return;
+      }
+      if (error.code === 10001) {
+        common_vendor.index.navigateTo({
+          url: "/pages/bind/bind?code=" + (loginParams.code || "")
+        });
+      } else if (error.message && error.message.includes("CSRF")) {
+        console.error("CSRF验证失败:", error);
+        common_vendor.index.showToast({
+          title: "CSRF验证失败,请刷新页面重试",
+          icon: "none",
+          duration: 2e3
+        });
+        setTimeout(() => this.refreshCSRFToken(), 2e3);
+      } else {
+        console.error("微信登录失败:", error);
+        common_vendor.index.showToast({
+          title: error.message || "登录失败",
+          icon: "none"
+        });
+      }
+    },
+    /**
+     * 获取用户详细信息
+     * 步骤6: 获取并更新用户详细信息
+     */
+    async fetchUserDetail() {
+      try {
+        if (!this.isLogin)
+          return;
+        const userId = this.userInfo.userId;
+        const userDetail = await api_user.getUserInfo(userId);
+        console.log("获取用户详细信息成功:", userDetail);
+        if (userDetail) {
+          this.updateUserInfo(userDetail);
+        }
+      } catch (error) {
+        console.error("获取用户详细信息失败:", error);
+      }
+    },
+    /**
+     * 更新用户信息
+     * @param {Object} userDetail - 用户详细信息
+     */
+    updateUserInfo(userDetail) {
+      const updatedUserInfo = {
+        ...this.userInfo,
+        // 更新基本信息
+        username: userDetail.username || userDetail.nickName || this.userInfo.username,
+        avatar: userDetail.avatar || userDetail.avatarUrl || this.userInfo.avatar,
+        phone: userDetail.phone || userDetail.mobile || this.userInfo.phone,
+        // 更新详细信息
+        email: userDetail.email || "",
+        address: userDetail.address || "",
+        birthday: userDetail.birthday || "",
+        // 其他字段
+        ...userDetail
+      };
+      this.userInfo = updatedUserInfo;
+      common_vendor.index.setStorageSync("userInfo", JSON.stringify(updatedUserInfo));
+      console.log("用户详细信息已更新");
+    },
+    /**
+     * 获取用户手机号
+     * @param {Object} e - 事件对象
+     */
+    async getPhoneNumber(e) {
+      console.log("获取手机号事件:", e);
+      if (e.detail.errMsg !== "getPhoneNumber:ok") {
+        console.error("用户拒绝授权手机号");
+        common_vendor.index.showToast({
+          title: "获取手机号失败",
+          icon: "none"
+        });
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "获取手机号中..." });
+        const loginResult = await new Promise((resolve, reject) => {
+          common_vendor.index.login({
+            provider: "weixin",
+            success: resolve,
+            fail: reject
+          });
+        });
+        const params = {
+          code: loginResult.code,
+          encryptedData: e.detail.encryptedData,
+          iv: e.detail.iv,
+          openid: JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid
+        };
+        console.log("获取手机号请求参数:", JSON.parse(common_vendor.index.getStorageSync("userInfo")).openid);
+        const phoneData = await api_user.getUserPhoneNumber(params);
+        console.log("获取手机号成功:", phoneData);
+        if (phoneData && phoneData.phoneNumber) {
+          const updatedUserInfo = {
+            ...this.userInfo,
+            phone: phoneData.phoneNumber
+          };
+          this.userInfo = updatedUserInfo;
+          common_vendor.index.setStorageSync("userInfo", JSON.stringify(updatedUserInfo));
+          common_vendor.index.showToast({
+            title: "手机号绑定成功",
+            icon: "success"
+          });
+        } else {
+          throw new Error("未能获取到手机号");
+        }
+      } catch (error) {
+        console.error("获取手机号失败:", error);
+        common_vendor.index.showToast({
+          title: error.message || "获取手机号失败",
+          icon: "none"
+        });
+      } finally {
+        common_vendor.index.hideLoading();
+      }
+    },
+    // 添加刷新 CSRF token 的方法
+    refreshCSRFToken() {
       common_vendor.index.request({
-        url: "https://your-api-domain.com/api/wx/login",
-        method: "POST",
-        data: {
-          code
-          // 不再依赖 getUserProfile 获取的信息
-          // 如果有用户选择的头像,可以在这里传递
-        },
+        url: "your-api-endpoint/csrf-token",
+        // 替换为获取 CSRF token 的 API
+        method: "GET",
         success: (res) => {
-          common_vendor.index.hideLoading();
-          if (res.data.code === 0) {
-            const userData = {
-              username: res.data.data.username || "微信用户",
-              userId: res.data.data.userId,
-              avatar: res.data.data.avatar || "/static/avatar.png"
-            };
-            try {
-              common_vendor.index.setStorageSync("token", res.data.data.token);
-              common_vendor.index.setStorageSync("userInfo", JSON.stringify(userData));
-              this.isLogin = true;
-              this.userInfo = userData;
-              common_vendor.index.showToast({
-                title: "登录成功",
-                icon: "success"
-              });
-            } catch (e) {
-              console.error("保存登录信息失败", e);
-            }
-          } else {
-            common_vendor.index.showToast({
-              title: res.data.msg || "登录失败",
-              icon: "none"
-            });
+          if (res.data && res.data.csrfToken) {
+            common_vendor.index.setStorageSync("csrfToken", res.data.csrfToken);
+            console.log("CSRF token 已刷新");
           }
         },
         fail: (err) => {
-          common_vendor.index.hideLoading();
-          common_vendor.index.showToast({
-            title: "网络请求失败",
-            icon: "none"
-          });
-          console.error("微信登录请求失败", err);
+          console.error("获取 CSRF token 失败", err);
         }
       });
     },
@@ -152,7 +390,7 @@ const _sfc_main = {
           icon: "none"
         });
         setTimeout(() => {
-          this.goLogin();
+          this.wxLogin();
         }, 1500);
         return;
       }
@@ -179,50 +417,84 @@ const _sfc_main = {
           });
       }
     },
-    handleLogout() {
-      common_vendor.index.showModal({
-        title: "提示",
-        content: "确定要退出登录吗?",
-        success: (res) => {
-          if (res.confirm) {
-            try {
-              common_vendor.index.removeStorageSync("token");
-              common_vendor.index.removeStorageSync("userInfo");
-              this.isLogin = false;
-              this.userInfo = {
-                username: "",
-                userId: "",
-                avatar: ""
-              };
+    // 处理退出登录 - 增强版
+    handleLogout(showConfirm = true) {
+      const doLogout = () => {
+        api_user.logout().then(() => {
+          try {
+            common_vendor.index.removeStorageSync("token");
+            common_vendor.index.removeStorageSync("userInfo");
+            this.isLogin = false;
+            this.userInfo = {
+              username: "",
+              userId: "",
+              avatar: "",
+              phone: ""
+            };
+            if (showConfirm) {
               common_vendor.index.showToast({
                 title: "已退出登录",
                 icon: "success"
               });
-            } catch (e) {
-              console.error("退出登录失败", e);
+            }
+          } catch (e) {
+            console.error("退出登录失败", e);
+            if (showConfirm) {
               common_vendor.index.showToast({
                 title: "退出登录失败",
                 icon: "none"
               });
             }
           }
-        }
-      });
+        }).catch((err) => {
+          console.error("退出登录请求失败", err);
+          common_vendor.index.removeStorageSync("token");
+          common_vendor.index.removeStorageSync("userInfo");
+          this.isLogin = false;
+          if (showConfirm) {
+            common_vendor.index.showToast({
+              title: err.message || "退出登录失败",
+              icon: "none"
+            });
+          }
+        });
+      };
+      if (showConfirm) {
+        common_vendor.index.showModal({
+          title: "提示",
+          content: "确定要退出登录吗?",
+          success: (res) => {
+            if (res.confirm) {
+              doLogout();
+            }
+          }
+        });
+      } else {
+        doLogout();
+      }
+    },
+    // 格式化手机号,例如:188****8888
+    formatPhone(phone) {
+      if (!phone || phone.length < 11)
+        return phone;
+      return phone.substring(0, 3) + "****" + phone.substring(7);
     }
   }
 };
 function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
   return common_vendor.e({
     a: $data.isLogin
-  }, $data.isLogin ? {
+  }, $data.isLogin ? common_vendor.e({
     b: $data.userInfo.avatar || "/static/avatar.png",
     c: common_vendor.t($data.userInfo.username),
-    d: common_vendor.t($data.userInfo.userId)
-  } : {
-    e: common_assets._imports_0,
-    f: common_vendor.o((...args) => $options.goLogin && $options.goLogin(...args))
+    d: $data.userInfo.phone
+  }, $data.userInfo.phone ? {
+    e: common_vendor.t($options.formatPhone($data.userInfo.phone))
+  } : {}) : {
+    f: common_assets._imports_0,
+    g: common_vendor.o((...args) => $options.wxLogin && $options.wxLogin(...args))
   }, {
-    g: common_vendor.f($data.menuItems, (item, index, i0) => {
+    h: common_vendor.f($data.menuItems, (item, index, i0) => {
       return {
         a: common_vendor.n(item.icon),
         b: common_vendor.t(item.title),
@@ -230,9 +502,18 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
         d: common_vendor.o(($event) => $options.handleMenuClick(item), index)
       };
     }),
-    h: $data.isLogin
+    i: $data.isLogin
   }, $data.isLogin ? {
-    i: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args))
+    j: common_vendor.o((...args) => $options.handleLogout && $options.handleLogout(...args))
+  } : {}, {
+    k: $data.showAuthModal
+  }, $data.showAuthModal ? {
+    l: common_vendor.o((...args) => $options.getUserProfile && $options.getUserProfile(...args)),
+    m: common_vendor.o((...args) => $options.cancelAuth && $options.cancelAuth(...args))
+  } : {}, {
+    n: $data.isLogin && !$data.userInfo.phone
+  }, $data.isLogin && !$data.userInfo.phone ? {
+    o: common_vendor.o((...args) => $options.getPhoneNumber && $options.getPhoneNumber(...args))
   } : {});
 }
 const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render]]);

+ 1 - 1
unpackage/dist/dev/mp-weixin/pages/my/my.wxml

@@ -1 +1 @@
-<view class="my-container"><view wx:if="{{a}}" class="user-info"><image class="avatar" src="{{b}}"></image><view class="user-details"><text class="username">{{c}}</text><text class="user-id">ID: {{d}}</text></view></view><view wx:else class="user-info" bindtap="{{f}}"><image class="avatar" src="{{e}}"></image><view class="user-details"><text class="username">点击登录</text><text class="user-id">登录后查看更多信息</text></view></view><view class="menu-list"><view wx:for="{{g}}" wx:for-item="item" wx:key="c" class="menu-item" bindtap="{{item.d}}"><view class="menu-item-left"><text class="{{['iconfont', item.a]}}"></text><text class="menu-text">{{item.b}}</text></view><text class="iconfont icon-right"></text></view></view><view wx:if="{{h}}" class="logout-btn" bindtap="{{i}}"><text>退出登录</text></view></view>
+<view class="my-container"><view wx:if="{{a}}" class="user-info"><image class="avatar" src="{{b}}"></image><view class="user-details"><text class="username">{{c}}</text><text wx:if="{{d}}" class="user-phone">手机: {{e}}</text></view></view><view wx:else class="user-info" bindtap="{{g}}"><image class="avatar" src="{{f}}"></image><view class="user-details"><text class="username">点击登录</text><text class="user-id">登录后查看更多信息</text></view></view><view class="menu-list"><view wx:for="{{h}}" wx:for-item="item" wx:key="c" class="menu-item" bindtap="{{item.d}}"><view class="menu-item-left"><text class="{{['iconfont', item.a]}}"></text><text class="menu-text">{{item.b}}</text></view><text class="iconfont icon-right"></text></view></view><view wx:if="{{i}}" class="logout-btn" bindtap="{{j}}"><text>退出登录</text></view><view wx:if="{{k}}" class="auth-modal"><view class="auth-content"><text class="auth-title">微信授权登录</text><text class="auth-desc">请授权获取您的微信头像和昵称</text><button class="auth-btn" bindtap="{{l}}">确认授权</button><button class="auth-cancel" bindtap="{{m}}">取消</button></view></view><button wx:if="{{n}}" open-type="getPhoneNumber" bindgetphonenumber="{{o}}" class="get-phone-btn"> 绑定手机号 </button></view>

+ 66 - 0
unpackage/dist/dev/mp-weixin/pages/my/my.wxss

@@ -31,6 +31,11 @@
 		font-size: 24rpx;
 		color: #999;
 }
+.user-phone {
+		font-size: 24rpx;
+		color: #666;
+		margin-top: 6rpx;
+}
 .menu-list {
 		background-color: #ffffff;
 		border-radius: 12rpx;
@@ -71,3 +76,64 @@
 .icon-right {
 		color: #cccccc;
 }
+	
+	/* 授权弹窗样式 */
+.auth-modal {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.5);
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		z-index: 999;
+}
+.auth-content {
+		width: 80%;
+		background-color: #fff;
+		border-radius: 12rpx;
+		padding: 40rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+}
+.auth-title {
+		font-size: 36rpx;
+		font-weight: bold;
+		margin-bottom: 20rpx;
+}
+.auth-desc {
+		font-size: 28rpx;
+		color: #666;
+		margin-bottom: 40rpx;
+		text-align: center;
+}
+.auth-btn {
+		width: 100%;
+		height: 80rpx;
+		line-height: 80rpx;
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 8rpx;
+		margin-bottom: 20rpx;
+}
+.auth-cancel {
+		width: 100%;
+		height: 80rpx;
+		line-height: 80rpx;
+		background-color: #f5f5f5;
+		color: #333;
+		border-radius: 8rpx;
+}
+	
+	/* 添加获取手机号按钮样式 */
+.get-phone-btn {
+		margin-top: 20rpx;
+		background-color: #07c160;
+		color: #fff;
+		border-radius: 8rpx;
+		font-size: 28rpx;
+		padding: 16rpx 0;
+}

+ 115 - 0
unpackage/dist/dev/mp-weixin/utils/errorHandler.js

@@ -0,0 +1,115 @@
+"use strict";
+const common_vendor = require("../common/vendor.js");
+const ERROR_CODE_MAP = {
+  400: "请求参数错误",
+  401: "登录已过期,请重新登录",
+  403: "没有权限执行此操作",
+  404: "请求的资源不存在",
+  500: "服务器错误,请稍后重试",
+  502: "网关错误",
+  503: "服务不可用,请稍后重试",
+  504: "网关超时"
+};
+const BUSINESS_ERROR_CODE_MAP = {
+  10001: "用户名或密码错误",
+  10002: "账号已被禁用",
+  10003: "验证码错误",
+  10004: "操作过于频繁,请稍后再试"
+  // 添加更多业务错误码...
+};
+class ErrorHandler {
+  constructor() {
+    this.lastMessage = "";
+    this.lastTime = 0;
+  }
+  /**
+   * 显示错误提示
+   * @param {string} message - 错误信息
+   * @param {number} duration - 显示时长
+   */
+  showError(message, duration = 2e3) {
+    if (this.lastMessage === message && Date.now() - this.lastTime < 3e3) {
+      return;
+    }
+    common_vendor.index.showToast({
+      title: message,
+      icon: "none",
+      duration
+    });
+    this.lastMessage = message;
+    this.lastTime = Date.now();
+  }
+  /**
+   * 处理HTTP错误
+   * @param {number} statusCode - HTTP状态码
+   * @param {Object} response - 响应对象
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleHttpError(statusCode, response) {
+    const errorMsg = ERROR_CODE_MAP[statusCode] || `网络请求错误:${statusCode}`;
+    this.showError(errorMsg);
+    const error = new Error(errorMsg);
+    error.code = statusCode;
+    error.response = response;
+    if (statusCode === 401) {
+      this.handleUnauthorized();
+    }
+    return error;
+  }
+  /**
+   * 处理业务错误
+   * @param {Object} data - 业务响应数据
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleBusinessError(data) {
+    const errorMsg = BUSINESS_ERROR_CODE_MAP[data.code] || data.msg || "请求失败";
+    this.showError(errorMsg);
+    const error = new Error(errorMsg);
+    error.code = data.code;
+    error.data = data;
+    return error;
+  }
+  /**
+   * 处理网络请求失败
+   * @param {Object} err - 原始错误对象
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleRequestFail(err) {
+    let errorMsg = "网络请求失败,请检查网络连接";
+    if (err.errMsg) {
+      if (err.errMsg.includes("timeout")) {
+        errorMsg = "请求超时,请检查网络连接";
+      } else if (err.errMsg.includes("abort")) {
+        errorMsg = "请求已取消";
+      } else if (err.errMsg.includes("SSL")) {
+        errorMsg = "网络安全证书错误";
+      }
+    }
+    this.showError(errorMsg);
+    const error = new Error(errorMsg);
+    error.original = err;
+    return error;
+  }
+  /**
+   * 处理未授权错误(401)
+   */
+  handleUnauthorized() {
+    common_vendor.index.removeStorageSync("token");
+    common_vendor.index.removeStorageSync("userInfo");
+    setTimeout(() => {
+      common_vendor.index.navigateTo({
+        url: "/pages/login/login"
+      });
+    }, 1500);
+  }
+  /**
+   * 记录错误日志
+   * @param {Error} error - 错误对象
+   * @param {string} source - 错误来源
+   */
+  logError(error, source = "unknown") {
+    console.error(`[${source}] Error:`, error);
+  }
+}
+const errorHandler = new ErrorHandler();
+exports.errorHandler = errorHandler;

+ 165 - 0
unpackage/dist/dev/mp-weixin/utils/request.js

@@ -0,0 +1,165 @@
+"use strict";
+const common_vendor = require("../common/vendor.js");
+const utils_errorHandler = require("./errorHandler.js");
+const BASE_URL = "http://192.168.66.187:8083";
+const TIMEOUT = 6e4;
+const requestInterceptor = (config) => {
+  const token = common_vendor.index.getStorageSync("token");
+  const csrfToken = common_vendor.index.getStorageSync("csrfToken");
+  if (!config.header) {
+    config.header = {};
+  }
+  if (token) {
+    config.header["Authorization"] = `Bearer ${token}`;
+  }
+  if (csrfToken) {
+    config.header["X-CSRF-Token"] = csrfToken;
+  }
+  if (!config.header["Content-Type"]) {
+    config.header["Content-Type"] = "application/json";
+  }
+  if (!config.url.startsWith("http")) {
+    config.url = BASE_URL + config.url;
+  }
+  if (config.method === "POST" && config.data) {
+    config.data._csrf = csrfToken;
+  }
+  return config;
+};
+const showError = (message, duration = 2e3) => {
+  if (showError.lastMessage === message && Date.now() - showError.lastTime < 3e3) {
+    return;
+  }
+  common_vendor.index.showToast({
+    title: message,
+    icon: "none",
+    duration
+  });
+  showError.lastMessage = message;
+  showError.lastTime = Date.now();
+};
+showError.lastMessage = "";
+showError.lastTime = 0;
+const responseInterceptor = (response) => {
+  if (response.statusCode === 200) {
+    const { data } = response;
+    if (data.status === 999 || data.code === 999) {
+      const error = new Error(data.message || "微信登录失败: code无效");
+      error.status = 999;
+      return Promise.reject(error);
+    }
+    if (data.status === 2e3 || data.code === 0 || data.code === 2e3) {
+      if (data.data) {
+        return data.data;
+      }
+      if (data.openid || data.userId || data.user_id) {
+        return data;
+      }
+      return data;
+    } else {
+      const error = utils_errorHandler.errorHandler.handleBusinessError(data);
+      return Promise.reject(error);
+    }
+  } else {
+    const error = utils_errorHandler.errorHandler.handleHttpError(response.statusCode, response);
+    return Promise.reject(error);
+  }
+};
+const request = (options = {}) => {
+  return new Promise((resolve, reject) => {
+    options = requestInterceptor(options);
+    if (!options.timeout) {
+      options.timeout = TIMEOUT;
+    }
+    common_vendor.index.request({
+      ...options,
+      success: (res) => {
+        try {
+          const data = responseInterceptor(res);
+          resolve(data);
+        } catch (error) {
+          utils_errorHandler.errorHandler.logError(error, "request.responseInterceptor");
+          reject(error);
+        }
+      },
+      fail: (err) => {
+        const error = utils_errorHandler.errorHandler.handleRequestFail(err);
+        utils_errorHandler.errorHandler.logError(error, "request.fail");
+        reject(error);
+      }
+    });
+  });
+};
+const http = {
+  get(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: "GET",
+      ...options
+    });
+  },
+  post(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: "POST",
+      ...options
+    });
+  },
+  put(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: "PUT",
+      ...options
+    });
+  },
+  delete(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: "DELETE",
+      ...options
+    });
+  },
+  // 上传文件
+  upload(url, filePath, name = "file", formData = {}, options = {}) {
+    return new Promise((resolve, reject) => {
+      const token = common_vendor.index.getStorageSync("token");
+      const header = options.header || {};
+      if (token) {
+        header["Authorization"] = `Bearer ${token}`;
+      }
+      if (!url.startsWith("http")) {
+        url = BASE_URL + url;
+      }
+      common_vendor.index.uploadFile({
+        url,
+        filePath,
+        name,
+        formData,
+        header,
+        success: (res) => {
+          try {
+            if (typeof res.data === "string") {
+              res.data = JSON.parse(res.data);
+            }
+            const data = responseInterceptor({
+              statusCode: res.statusCode,
+              data: res.data
+            });
+            resolve(data);
+          } catch (error) {
+            reject(error);
+          }
+        },
+        fail: (err) => {
+          showError("文件上传失败");
+          reject(err);
+        }
+      });
+    });
+  }
+};
+exports.http = http;

+ 150 - 0
utils/errorHandler.js

@@ -0,0 +1,150 @@
+/**
+ * 错误处理工具类
+ */
+
+// 错误码映射表
+const ERROR_CODE_MAP = {
+  400: '请求参数错误',
+  401: '登录已过期,请重新登录',
+  403: '没有权限执行此操作',
+  404: '请求的资源不存在',
+  500: '服务器错误,请稍后重试',
+  502: '网关错误',
+  503: '服务不可用,请稍后重试',
+  504: '网关超时'
+};
+
+// 业务错误码映射表(根据实际业务定义)
+const BUSINESS_ERROR_CODE_MAP = {
+  10001: '用户名或密码错误',
+  10002: '账号已被禁用',
+  10003: '验证码错误',
+  10004: '操作过于频繁,请稍后再试',
+  // 添加更多业务错误码...
+};
+
+class ErrorHandler {
+  constructor() {
+    // 记录最后显示的错误信息和时间,避免重复显示
+    this.lastMessage = '';
+    this.lastTime = 0;
+  }
+  
+  /**
+   * 显示错误提示
+   * @param {string} message - 错误信息
+   * @param {number} duration - 显示时长
+   */
+  showError(message, duration = 2000) {
+    // 避免重复显示相同的错误提示
+    if (this.lastMessage === message && Date.now() - this.lastTime < 3000) {
+      return;
+    }
+    
+    uni.showToast({
+      title: message,
+      icon: 'none',
+      duration: duration
+    });
+    
+    // 记录最后显示的错误信息和时间
+    this.lastMessage = message;
+    this.lastTime = Date.now();
+  }
+  
+  /**
+   * 处理HTTP错误
+   * @param {number} statusCode - HTTP状态码
+   * @param {Object} response - 响应对象
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleHttpError(statusCode, response) {
+    const errorMsg = ERROR_CODE_MAP[statusCode] || `网络请求错误:${statusCode}`;
+    this.showError(errorMsg);
+    
+    const error = new Error(errorMsg);
+    error.code = statusCode;
+    error.response = response;
+    
+    // 特殊处理401未授权错误
+    if (statusCode === 401) {
+      this.handleUnauthorized();
+    }
+    
+    return error;
+  }
+  
+  /**
+   * 处理业务错误
+   * @param {Object} data - 业务响应数据
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleBusinessError(data) {
+    const errorMsg = BUSINESS_ERROR_CODE_MAP[data.code] || data.msg || '请求失败';
+    this.showError(errorMsg);
+    
+    const error = new Error(errorMsg);
+    error.code = data.code;
+    error.data = data;
+    
+    return error;
+  }
+  
+  /**
+   * 处理网络请求失败
+   * @param {Object} err - 原始错误对象
+   * @returns {Error} - 格式化的错误对象
+   */
+  handleRequestFail(err) {
+    let errorMsg = '网络请求失败,请检查网络连接';
+    
+    // 根据错误类型提供更具体的错误信息
+    if (err.errMsg) {
+      if (err.errMsg.includes('timeout')) {
+        errorMsg = '请求超时,请检查网络连接';
+      } else if (err.errMsg.includes('abort')) {
+        errorMsg = '请求已取消';
+      } else if (err.errMsg.includes('SSL')) {
+        errorMsg = '网络安全证书错误';
+      }
+    }
+    
+    this.showError(errorMsg);
+    
+    const error = new Error(errorMsg);
+    error.original = err;
+    return error;
+  }
+  
+  /**
+   * 处理未授权错误(401)
+   */
+  handleUnauthorized() {
+    // 清除登录信息
+    uni.removeStorageSync('token');
+    uni.removeStorageSync('userInfo');
+    
+    // 跳转到登录页
+    setTimeout(() => {
+      uni.navigateTo({
+        url: '/pages/login/login'
+      });
+    }, 1500);
+  }
+  
+  /**
+   * 记录错误日志
+   * @param {Error} error - 错误对象
+   * @param {string} source - 错误来源
+   */
+  logError(error, source = 'unknown') {
+    console.error(`[${source}] Error:`, error);
+    
+    // 这里可以添加错误上报逻辑
+    // 例如发送到服务器或第三方监控平台
+  }
+}
+
+// 创建单例
+const errorHandler = new ErrorHandler();
+export default errorHandler; 

+ 247 - 0
utils/request.js

@@ -0,0 +1,247 @@
+/**
+ * 网络请求工具类
+ * 封装uni-app的request方法
+ */
+
+import errorHandler from './errorHandler.js';
+
+// 基础URL,可以根据环境变量等动态设置
+const BASE_URL = 'http://192.168.66.187:8083';
+
+// 请求超时时间
+const TIMEOUT = 60000;
+
+// 请求拦截器
+const requestInterceptor = (config) => {
+  // 获取token
+  const token = uni.getStorageSync('token');
+  
+  // 获取 CSRF token
+  const csrfToken = uni.getStorageSync('csrfToken');
+  
+  // 设置请求头
+  if (!config.header) {
+    config.header = {};
+  }
+  
+  // 添加token到请求头
+  if (token) {
+    config.header['Authorization'] = `Bearer ${token}`;
+  }
+  
+  // 添加 CSRF token 到请求头
+  if (csrfToken) {
+    config.header['X-CSRF-Token'] = csrfToken;
+  }
+  
+  // 添加内容类型
+  if (!config.header['Content-Type']) {
+    config.header['Content-Type'] = 'application/json';
+  }
+  
+  // 添加基础URL
+  if (!config.url.startsWith('http')) {
+    config.url = BASE_URL + config.url;
+  }
+  
+  // 如果是 POST 请求,也可以添加到请求体
+  if (config.method === 'POST' && config.data) {
+    config.data._csrf = csrfToken;
+  }
+  
+  return config;
+};
+
+// 错误提示
+const showError = (message, duration = 2000) => {
+  // 避免重复显示相同的错误提示
+  if (showError.lastMessage === message && Date.now() - showError.lastTime < 3000) {
+    return;
+  }
+  
+  uni.showToast({
+    title: message,
+    icon: 'none',
+    duration: duration
+  });
+  
+  // 记录最后显示的错误信息和时间
+  showError.lastMessage = message;
+  showError.lastTime = Date.now();
+};
+
+// 初始化错误提示状态
+showError.lastMessage = '';
+showError.lastTime = 0;
+
+// 错误码映射表
+const ERROR_CODE_MAP = {
+  400: '请求参数错误',
+  401: '登录已过期,请重新登录',
+  403: '没有权限执行此操作',
+  404: '请求的资源不存在',
+  500: '服务器错误,请稍后重试',
+  502: '网关错误',
+  503: '服务不可用,请稍后重试',
+  504: '网关超时'
+};
+
+// 响应拦截器
+const responseInterceptor = (response) => {
+  // 这里可以对响应数据做统一处理
+  if (response.statusCode === 200) {
+    // 服务器正常响应
+    const { data } = response;
+    
+    // 检查是否有特定错误码
+    if (data.status === 999 || data.code === 999) {
+      // 微信 code 无效错误
+      const error = new Error(data.message || '微信登录失败: code无效');
+      error.status = 999;
+      return Promise.reject(error);
+    }
+    // 处理成功响应 - 适配不同的返回格式
+    if (data.status === 2000 || data.code === 0 || data.code === 2000) {
+      // 如果返回的是完整数据对象,直接返回
+      if (data.data) {
+        return data.data;
+      }
+      // 如果没有data字段,但有其他用户信息字段,返回整个对象
+      if (data.openid || data.userId || data.user_id) {
+        return data;
+      }
+      // 默认返回
+      return data;
+    } else {
+      // 业务错误
+      const error = errorHandler.handleBusinessError(data);
+      return Promise.reject(error);
+    }
+  } else {
+    // HTTP错误
+    const error = errorHandler.handleHttpError(response.statusCode, response);
+    return Promise.reject(error);
+  }
+};
+
+// 请求方法
+const request = (options = {}) => {
+  return new Promise((resolve, reject) => {
+    // 应用请求拦截器
+    options = requestInterceptor(options);
+    
+    // 设置超时时间
+    if (!options.timeout) {
+      options.timeout = TIMEOUT;
+    }
+    
+    // 发起请求
+    uni.request({
+      ...options,
+      success: (res) => {
+        try {
+          // 应用响应拦截器
+          const data = responseInterceptor(res);
+          resolve(data);
+        } catch (error) {
+          errorHandler.logError(error, 'request.responseInterceptor');
+          reject(error);
+        }
+      },
+      fail: (err) => {
+        const error = errorHandler.handleRequestFail(err);
+        errorHandler.logError(error, 'request.fail');
+        reject(error);
+      }
+    });
+  });
+};
+
+// 封装常用请求方法
+const http = {
+  get(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: 'GET',
+      ...options
+    });
+  },
+  
+  post(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: 'POST',
+      ...options
+    });
+  },
+  
+  put(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: 'PUT',
+      ...options
+    });
+  },
+  
+  delete(url, data = {}, options = {}) {
+    return request({
+      url,
+      data,
+      method: 'DELETE',
+      ...options
+    });
+  },
+  
+  // 上传文件
+  upload(url, filePath, name = 'file', formData = {}, options = {}) {
+    return new Promise((resolve, reject) => {
+      // 获取token
+      const token = uni.getStorageSync('token');
+      
+      // 设置请求头
+      const header = options.header || {};
+      if (token) {
+        header['Authorization'] = `Bearer ${token}`;
+      }
+      
+      // 添加基础URL
+      if (!url.startsWith('http')) {
+        url = BASE_URL + url;
+      }
+      
+      uni.uploadFile({
+        url,
+        filePath,
+        name,
+        formData,
+        header,
+        success: (res) => {
+          try {
+            // 上传接口可能返回的是字符串
+            if (typeof res.data === 'string') {
+              res.data = JSON.parse(res.data);
+            }
+            
+            // 应用响应拦截器
+            const data = responseInterceptor({
+              statusCode: res.statusCode,
+              data: res.data
+            });
+            resolve(data);
+          } catch (error) {
+            reject(error);
+          }
+        },
+        fail: (err) => {
+          showError('文件上传失败');
+          reject(err);
+        }
+      });
+    });
+  }
+};
+
+export default http; 

Някои файлове не бяха показани, защото твърде много файлове са промени