Zhangbw 3 месяцев назад
Родитель
Сommit
df799c0c1c

+ 1 - 0
dist/dev/mp-weixin/app.js

@@ -8,6 +8,7 @@ if (!Math) {
   "./pages/rank/rank.js";
   "./pages/mine/mine.js";
   "./pages/login/login.js";
+  "./pages/profile/edit.js";
 }
 const _sfc_main = {
   globalData: {

+ 2 - 1
dist/dev/mp-weixin/app.json

@@ -5,7 +5,8 @@
     "pages/strong/strong",
     "pages/rank/rank",
     "pages/mine/mine",
-    "pages/login/login"
+    "pages/login/login",
+    "pages/profile/edit"
   ],
   "window": {
     "navigationBarBackgroundColor": "#ffffff",

+ 23 - 27
dist/dev/mp-weixin/pages/login/login.js

@@ -25,9 +25,8 @@ const _sfc_main = {
   methods: {
     /**
      * 处理微信一键登录
-     * @param {object} e - 事件对象,包含加密的手机号信息
      */
-    async handleWxLogin(e) {
+    async handleWxLogin() {
       if (!this.agreed) {
         common_vendor.index.showToast({
           title: "请先同意用户协议和隐私政策",
@@ -35,31 +34,29 @@ const _sfc_main = {
         });
         return;
       }
-      if (e.detail.errMsg === "getPhoneNumber:ok") {
-        try {
-          common_vendor.index.showLoading({ title: "登录中..." });
-          const loginRes = await common_vendor.index.login();
-          const result = await utils_api.wxLogin(loginRes.code);
-          utils_auth.setToken(result.data.token);
-          utils_auth.setUserInfo(result.data.userInfo);
-          common_vendor.index.hideLoading();
-          common_vendor.index.showToast({
-            title: "登录成功",
-            icon: "success"
-          });
-          setTimeout(() => {
-            this.navigateBack();
-          }, 1500);
-        } catch (error) {
-          common_vendor.index.hideLoading();
-          common_vendor.index.showToast({
-            title: error.message || "登录失败",
-            icon: "none"
-          });
-        }
-      } else {
+      try {
+        common_vendor.index.showLoading({ title: "登录中..." });
+        const loginRes = await common_vendor.index.login();
+        console.log("获取登录code成功:", loginRes.code);
+        const result = await utils_api.wxLogin({
+          code: loginRes.code
+        });
+        console.log("登录成功:", result);
+        utils_auth.setToken(result.data.token);
+        utils_auth.setUserInfo(result.data.userInfo);
+        common_vendor.index.hideLoading();
         common_vendor.index.showToast({
-          title: "获取手机号失败",
+          title: "登录成功",
+          icon: "success"
+        });
+        setTimeout(() => {
+          this.navigateBack();
+        }, 1500);
+      } catch (error) {
+        console.error("登录失败:", error);
+        common_vendor.index.hideLoading();
+        common_vendor.index.showToast({
+          title: error.message || "登录失败",
           icon: "none"
         });
       }
@@ -151,7 +148,6 @@ const _sfc_main = {
         console.log("登录成功,result:", result);
         utils_auth.setToken(result.data.token);
         utils_auth.setUserInfo(result.data.userInfo);
-        utils_auth.setUserInfo(result.data.userInfo);
         common_vendor.index.hideLoading();
         common_vendor.index.showToast({
           title: "登录成功",

+ 1 - 1
dist/dev/mp-weixin/pages/login/login.wxml

@@ -1 +1 @@
-<view class="login-container data-v-cdfe2409"><view class="login-header data-v-cdfe2409"><text class="app-name data-v-cdfe2409">量化选股大师</text><text class="welcome-text data-v-cdfe2409">欢迎登录</text></view><view class="login-form data-v-cdfe2409"><button wx:if="{{a}}" class="wx-login-btn data-v-cdfe2409" open-type="getPhoneNumber" bindgetphonenumber="{{b}}"><image class="wx-icon data-v-cdfe2409" src="/static/images/wechat.png" mode="aspectFit"></image><text class="data-v-cdfe2409">微信一键登录</text></button><view class="divider data-v-cdfe2409"><view class="divider-line data-v-cdfe2409"></view><text class="divider-text data-v-cdfe2409">或</text><view class="divider-line data-v-cdfe2409"></view></view><view class="phone-login data-v-cdfe2409"><view class="input-group data-v-cdfe2409"><input class="input-field data-v-cdfe2409" type="number" placeholder="请输入手机号" maxlength="11" value="{{c}}" bindinput="{{d}}"/></view><view class="input-group code-group data-v-cdfe2409"><input class="input-field code-input data-v-cdfe2409" type="number" placeholder="请输入验证码" maxlength="6" value="{{e}}" bindinput="{{f}}"/><button class="code-btn data-v-cdfe2409" bindtap="{{h}}" disabled="{{i}}">{{g}}</button></view><button class="login-btn data-v-cdfe2409" bindtap="{{j}}">登录</button></view><view class="agreement data-v-cdfe2409"><checkbox-group class="data-v-cdfe2409" bindchange="{{l}}"><label class="data-v-cdfe2409"><checkbox class="data-v-cdfe2409" checked="{{k}}" color="#5d55e8"/><text class="agreement-text data-v-cdfe2409">我已阅读并同意</text><text class="agreement-link data-v-cdfe2409">《用户协议》</text><text class="agreement-text data-v-cdfe2409">和</text><text class="agreement-link data-v-cdfe2409">《隐私政策》</text></label></checkbox-group></view></view></view>
+<view class="login-container data-v-cdfe2409"><view class="login-header data-v-cdfe2409"><text class="app-name data-v-cdfe2409">量化选股大师</text><text class="welcome-text data-v-cdfe2409">欢迎登录</text></view><view class="login-form data-v-cdfe2409"><button wx:if="{{a}}" class="wx-login-btn data-v-cdfe2409" bindtap="{{b}}"><image class="wx-icon data-v-cdfe2409" src="/static/images/wechat.png" mode="aspectFit"></image><text class="data-v-cdfe2409">微信一键登录</text></button><view class="divider data-v-cdfe2409"><view class="divider-line data-v-cdfe2409"></view><text class="divider-text data-v-cdfe2409">或</text><view class="divider-line data-v-cdfe2409"></view></view><view class="phone-login data-v-cdfe2409"><view class="input-group data-v-cdfe2409"><input class="input-field data-v-cdfe2409" type="number" placeholder="请输入手机号" maxlength="11" value="{{c}}" bindinput="{{d}}"/></view><view class="input-group code-group data-v-cdfe2409"><input class="input-field code-input data-v-cdfe2409" type="number" placeholder="请输入验证码" maxlength="6" value="{{e}}" bindinput="{{f}}"/><button class="code-btn data-v-cdfe2409" bindtap="{{h}}" disabled="{{i}}">{{g}}</button></view><button class="login-btn data-v-cdfe2409" bindtap="{{j}}">登录</button></view><view class="agreement data-v-cdfe2409"><checkbox-group class="data-v-cdfe2409" bindchange="{{l}}"><label class="data-v-cdfe2409"><checkbox class="data-v-cdfe2409" checked="{{k}}" color="#5d55e8"/><text class="agreement-text data-v-cdfe2409">我已阅读并同意</text><text class="agreement-link data-v-cdfe2409">《用户协议》</text><text class="agreement-text data-v-cdfe2409">和</text><text class="agreement-link data-v-cdfe2409">《隐私政策》</text></label></checkbox-group></view></view></view>

+ 24 - 9
dist/dev/mp-weixin/pages/mine/mine.js

@@ -14,15 +14,29 @@ const _sfc_main = {
     common_vendor.onMounted(() => {
       loadUserInfo();
     });
+    common_vendor.onShow(() => {
+      loadUserInfo();
+    });
     const loadUserInfo = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
       if (isLoggedIn.value) {
         const storedInfo = utils_auth.getUserInfo();
         if (storedInfo) {
           userInfo.value = storedInfo;
+          console.log("加载用户信息:", userInfo.value);
         }
       }
     };
+    const handleEditProfile = () => {
+      if (!utils_auth.checkLogin(() => {
+        handleEditProfile();
+      })) {
+        return;
+      }
+      common_vendor.index.navigateTo({
+        url: "/pages/profile/edit"
+      });
+    };
     const toggleLogin = () => {
       if (isLoggedIn.value) {
         common_vendor.index.showModal({
@@ -61,17 +75,18 @@ const _sfc_main = {
     };
     return (_ctx, _cache) => {
       return common_vendor.e({
-        a: isLoggedIn.value && userInfo.value.avatar ? userInfo.value.avatar : "/static/images/tab_mine_active.png",
-        b: common_vendor.t(isLoggedIn.value ? userInfo.value.nickname || "微信用户" : "游客"),
-        c: isLoggedIn.value && userInfo.value.phone
+        a: isLoggedIn.value && userInfo.value.avatar ? userInfo.value.avatar : "/static/images/head.png",
+        b: common_vendor.o(handleEditProfile),
+        c: common_vendor.t(isLoggedIn.value ? userInfo.value.nickname || "微信用户" : "游客"),
+        d: isLoggedIn.value && userInfo.value.phone
       }, isLoggedIn.value && userInfo.value.phone ? {
-        d: common_vendor.t(userInfo.value.phone)
+        e: common_vendor.t(userInfo.value.phone)
       } : {}, {
-        e: common_vendor.t(isLoggedIn.value ? points.value : 0),
-        f: common_vendor.o(handleRecharge),
-        g: common_vendor.o(handleHistory),
-        h: common_vendor.t(isLoggedIn.value ? "退出登录" : "登录"),
-        i: common_vendor.o(toggleLogin)
+        f: common_vendor.t(isLoggedIn.value ? points.value : 0),
+        g: common_vendor.o(handleRecharge),
+        h: common_vendor.o(handleHistory),
+        i: common_vendor.t(isLoggedIn.value ? "退出登录" : "登录"),
+        j: common_vendor.o(toggleLogin)
       });
     };
   }

+ 1 - 1
dist/dev/mp-weixin/pages/mine/mine.wxml

@@ -1 +1 @@
-<view class="page-mine"><view class="user-card"><view class="avatar-container"><image class="avatar" src="{{a}}" mode="aspectFill"></image></view><view class="user-info"><text class="username">{{b}}</text><text wx:if="{{c}}" class="phone">{{d}}</text></view></view><view class="section-card"><view class="card-header"><view class="header-icon-box"><text class="header-icon-text">🪙</text></view><text class="header-title">积分值</text></view><view class="card-content points-content"><text class="points-value">积分值:{{e}}</text><view class="points-actions"><button class="mini-btn" bindtap="{{f}}">充值</button><button class="mini-btn" bindtap="{{g}}">积分记录</button></view></view></view><view class="section-card"><view class="card-header"><view class="header-icon-box"><text class="header-icon-text">📝</text></view><text class="header-title">交易记录</text></view><view class="card-content transaction-content"><text class="empty-text">暂无交易记录</text></view></view><view class="action-area"><button class="main-btn" bindtap="{{i}}">{{h}}</button></view></view>
+<view class="page-mine"><view class="user-card"><view class="avatar-container" bindtap="{{b}}"><image class="avatar" src="{{a}}" mode="aspectFill"></image><view class="edit-icon">✏️</view></view><view class="user-info"><text class="username">{{c}}</text><text wx:if="{{d}}" class="phone">{{e}}</text></view></view><view class="section-card"><view class="card-header"><view class="header-icon-box"><text class="header-icon-text">🪙</text></view><text class="header-title">积分值</text></view><view class="card-content points-content"><text class="points-value">积分值:{{f}}</text><view class="points-actions"><button class="mini-btn" bindtap="{{g}}">充值</button><button class="mini-btn" bindtap="{{h}}">积分记录</button></view></view></view><view class="section-card"><view class="card-header"><view class="header-icon-box"><text class="header-icon-text">📝</text></view><text class="header-title">交易记录</text></view><view class="card-content transaction-content"><text class="empty-text">暂无交易记录</text></view></view><view class="action-area"><button class="main-btn" bindtap="{{j}}">{{i}}</button></view></view>

+ 16 - 0
dist/dev/mp-weixin/pages/mine/mine.wxss

@@ -25,6 +25,22 @@
   overflow: hidden;
   background-color: #eee;
   flex-shrink: 0;
+  position: relative;
+  cursor: pointer;
+}
+.edit-icon {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  width: 36rpx;
+  height: 36rpx;
+  background: #5d55e8;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 20rpx;
+  border: 2rpx solid #fff;
 }
 .avatar {
   width: 100%;

+ 106 - 0
dist/dev/mp-weixin/pages/profile/edit.js

@@ -0,0 +1,106 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const utils_auth = require("../../utils/auth.js");
+const utils_api = require("../../utils/api.js");
+const _sfc_main = {
+  data() {
+    return {
+      avatarUrl: "/static/images/head.png",
+      nickname: "",
+      phone: "",
+      originalAvatar: "",
+      originalNickname: ""
+    };
+  },
+  onLoad() {
+    this.loadUserInfo();
+  },
+  methods: {
+    /**
+     * 加载用户信息
+     */
+    loadUserInfo() {
+      const userInfo = utils_auth.getUserInfo();
+      if (userInfo) {
+        this.avatarUrl = userInfo.avatar || "/static/images/head.png";
+        this.nickname = userInfo.nickname || "";
+        this.phone = userInfo.phone || "";
+        this.originalAvatar = this.avatarUrl;
+        this.originalNickname = this.nickname;
+      }
+    },
+    /**
+     * 选择头像
+     */
+    onChooseAvatar(e) {
+      const { avatarUrl } = e.detail;
+      this.avatarUrl = avatarUrl;
+      console.log("选择头像:", avatarUrl);
+    },
+    /**
+     * 保存资料
+     */
+    async handleSave() {
+      if (!this.nickname || this.nickname.trim() === "") {
+        common_vendor.index.showToast({
+          title: "请输入昵称",
+          icon: "none"
+        });
+        return;
+      }
+      if (this.avatarUrl === this.originalAvatar && this.nickname === this.originalNickname) {
+        common_vendor.index.showToast({
+          title: "没有修改",
+          icon: "none"
+        });
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "保存中..." });
+        let uploadedAvatarUrl = this.avatarUrl;
+        if (this.avatarUrl !== this.originalAvatar && !this.avatarUrl.startsWith("/static/")) {
+          uploadedAvatarUrl = this.avatarUrl;
+        }
+        const result = await utils_api.updateUserProfile({
+          nickname: this.nickname,
+          avatar: uploadedAvatarUrl
+        });
+        console.log("更新成功:", result);
+        const userInfo = utils_auth.getUserInfo();
+        userInfo.nickname = this.nickname;
+        userInfo.avatar = uploadedAvatarUrl;
+        utils_auth.setUserInfo(userInfo);
+        common_vendor.index.hideLoading();
+        common_vendor.index.showToast({
+          title: "保存成功",
+          icon: "success"
+        });
+        setTimeout(() => {
+          common_vendor.index.navigateBack();
+        }, 1500);
+      } catch (error) {
+        console.error("保存失败:", error);
+        common_vendor.index.hideLoading();
+        common_vendor.index.showToast({
+          title: error.message || "保存失败",
+          icon: "none"
+        });
+      }
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return common_vendor.e({
+    a: $data.avatarUrl,
+    b: common_vendor.o((...args) => $options.onChooseAvatar && $options.onChooseAvatar(...args)),
+    c: $data.nickname,
+    d: common_vendor.o(($event) => $data.nickname = $event.detail.value),
+    e: $data.phone
+  }, $data.phone ? {
+    f: common_vendor.t($data.phone)
+  } : {}, {
+    g: common_vendor.o((...args) => $options.handleSave && $options.handleSave(...args))
+  });
+}
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-7e5a80f3"], ["__file", "D:/program/gupiao-wx/src/pages/profile/edit.vue"]]);
+wx.createPage(MiniProgramPage);

+ 4 - 0
dist/dev/mp-weixin/pages/profile/edit.json

@@ -0,0 +1,4 @@
+{
+  "navigationBarTitleText": "编辑资料",
+  "usingComponents": {}
+}

+ 1 - 0
dist/dev/mp-weixin/pages/profile/edit.wxml

@@ -0,0 +1 @@
+<view class="edit-profile-container data-v-7e5a80f3"><view class="edit-header data-v-7e5a80f3"><text class="header-title data-v-7e5a80f3">编辑资料</text></view><view class="edit-form data-v-7e5a80f3"><view class="form-item data-v-7e5a80f3"><text class="item-label data-v-7e5a80f3">头像</text><button class="avatar-btn data-v-7e5a80f3" open-type="chooseAvatar" bindchooseavatar="{{b}}"><image class="avatar-preview data-v-7e5a80f3" src="{{a}}" mode="aspectFill"></image><text class="change-text data-v-7e5a80f3">点击更换</text></button></view><view class="form-item data-v-7e5a80f3"><text class="item-label data-v-7e5a80f3">昵称</text><input class="item-input data-v-7e5a80f3" type="nickname" placeholder="请输入昵称" maxlength="20" value="{{c}}" bindinput="{{d}}"/></view><view wx:if="{{e}}" class="form-item data-v-7e5a80f3"><text class="item-label data-v-7e5a80f3">手机号</text><text class="item-value data-v-7e5a80f3">{{f}}</text></view></view><view class="action-area data-v-7e5a80f3"><button class="save-btn data-v-7e5a80f3" bindtap="{{g}}">保存</button></view></view>

+ 90 - 0
dist/dev/mp-weixin/pages/profile/edit.wxss

@@ -0,0 +1,90 @@
+
+.edit-profile-container.data-v-7e5a80f3 {
+  min-height: 100vh;
+  background: #f5f6fb;
+  padding: 30rpx;
+}
+.edit-header.data-v-7e5a80f3 {
+  text-align: center;
+  padding: 40rpx 0;
+}
+.header-title.data-v-7e5a80f3 {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #222;
+}
+.edit-form.data-v-7e5a80f3 {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 40rpx;
+}
+.form-item.data-v-7e5a80f3 {
+  display: flex;
+  align-items: center;
+  padding: 30rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.form-item.data-v-7e5a80f3:last-child {
+  border-bottom: none;
+}
+.item-label.data-v-7e5a80f3 {
+  width: 150rpx;
+  font-size: 32rpx;
+  color: #333;
+  font-weight: 500;
+}
+.avatar-btn.data-v-7e5a80f3 {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: transparent;
+  border: none;
+  padding: 0;
+  margin: 0;
+  line-height: normal;
+}
+.avatar-btn.data-v-7e5a80f3::after {
+  border: none;
+}
+.avatar-preview.data-v-7e5a80f3 {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 50%;
+  background: #f0f0f0;
+}
+.change-text.data-v-7e5a80f3 {
+  font-size: 28rpx;
+  color: #5d55e8;
+}
+.item-input.data-v-7e5a80f3 {
+  flex: 1;
+  height: 80rpx;
+  font-size: 32rpx;
+  color: #333;
+  text-align: right;
+}
+.item-value.data-v-7e5a80f3 {
+  flex: 1;
+  font-size: 32rpx;
+  color: #999;
+  text-align: right;
+}
+.action-area.data-v-7e5a80f3 {
+  padding: 0 40rpx;
+}
+.save-btn.data-v-7e5a80f3 {
+  width: 100%;
+  height: 90rpx;
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 45rpx;
+  border: none;
+  box-shadow: 0 12rpx 24rpx rgba(93, 85, 232, 0.4);
+  line-height: 90rpx;
+}
+.save-btn.data-v-7e5a80f3:active {
+  opacity: 0.9;
+}

BIN
dist/dev/mp-weixin/static/images/head.png


+ 13 - 2
dist/dev/mp-weixin/utils/api.js

@@ -37,14 +37,14 @@ const request = (options) => {
     });
   });
 };
-const wxLogin = (code) => {
+const wxLogin = (params) => {
   return request({
     url: "/v1/auth/wxLogin",
     method: "POST",
     header: {
       "content-type": "application/json"
     },
-    data: { code }
+    data: params
   });
 };
 const phoneLogin = (phone, verifyCode) => {
@@ -68,6 +68,16 @@ const sendSmsCode = (phone) => {
     data: { phone }
   });
 };
+const updateUserProfile = (data) => {
+  return request({
+    url: "/v1/user/profile",
+    method: "PUT",
+    header: {
+      "content-type": "application/json"
+    },
+    data
+  });
+};
 const getSuggestions = (keyword) => {
   return request({
     url: "/v1/stock/suggestion",
@@ -103,4 +113,5 @@ exports.getUserPortfolio = getUserPortfolio;
 exports.phoneLogin = phoneLogin;
 exports.searchStocks = searchStocks;
 exports.sendSmsCode = sendSmsCode;
+exports.updateUserProfile = updateUserProfile;
 exports.wxLogin = wxLogin;

+ 6 - 0
src/pages.json

@@ -37,6 +37,12 @@
         "navigationBarBackgroundColor": "#667eea",
         "navigationBarTextStyle": "white"
       }
+    },
+    {
+      "path": "pages/profile/edit",
+      "style": {
+        "navigationBarTitleText": "编辑资料"
+      }
     }
   ],
   "globalStyle": {

+ 36 - 40
src/pages/login/login.vue

@@ -9,8 +9,7 @@
       <!-- 微信一键登录 -->
       <button 
         class="wx-login-btn" 
-        open-type="getPhoneNumber" 
-        @getphonenumber="handleWxLogin"
+        @click="handleWxLogin"
         v-if="canUseWxLogin"
       >
         <image class="wx-icon" src="/static/images/wechat.png" mode="aspectFit"></image>
@@ -107,9 +106,8 @@ export default {
   methods: {
     /**
      * 处理微信一键登录
-     * @param {object} e - 事件对象,包含加密的手机号信息
      */
-    async handleWxLogin(e) {
+    async handleWxLogin() {
       if (!this.agreed) {
         uni.showToast({
           title: '请先同意用户协议和隐私政策',
@@ -118,41 +116,40 @@ export default {
         return
       }
 
-      if (e.detail.errMsg === 'getPhoneNumber:ok') {
-        try {
-          uni.showLoading({ title: '登录中...' })
-          
-          // 获取微信登录code
-          const loginRes = await uni.login()
-          
-          // 调用后端登录接口
-          const result = await wxLogin(loginRes.code)
-          
-          // 保存token和用户信息
-          setToken(result.data.token)
-          setUserInfo(result.data.userInfo)
-          
-          uni.hideLoading()
-          uni.showToast({
-            title: '登录成功',
-            icon: 'success'
-          })
-          
-          // 延迟跳转,让用户看到成功提示
-          setTimeout(() => {
-            this.navigateBack()
-          }, 1500)
-          
-        } catch (error) {
-          uni.hideLoading()
-          uni.showToast({
-            title: error.message || '登录失败',
-            icon: 'none'
-          })
-        }
-      } else {
+      try {
+        uni.showLoading({ title: '登录中...' })
+        
+        // 获取微信登录code
+        const loginRes = await uni.login()
+        console.log('获取登录code成功:', loginRes.code)
+        
+        // 调用后端登录接口(不传递昵称和头像,后端使用默认值)
+        const result = await wxLogin({
+          code: loginRes.code
+        })
+        
+        console.log('登录成功:', result)
+        
+        // 保存token和用户信息
+        setToken(result.data.token)
+        setUserInfo(result.data.userInfo)
+        
+        uni.hideLoading()
         uni.showToast({
-          title: '获取手机号失败',
+          title: '登录成功',
+          icon: 'success'
+        })
+        
+        // 延迟跳转,让用户看到成功提示
+        setTimeout(() => {
+          this.navigateBack()
+        }, 1500)
+        
+      } catch (error) {
+        console.error('登录失败:', error)
+        uni.hideLoading()
+        uni.showToast({
+          title: error.message || '登录失败',
           icon: 'none'
         })
       }
@@ -255,7 +252,7 @@ export default {
         
         console.log('准备登录,phone:', this.phone, 'code:', this.code)
         
-        // 调用后端登录接口(注意:phoneLogin的第二个参数是verifyCode
+        // 调用后端登录接口(不传递昵称和头像,后端使用默认值
         const result = await phoneLogin(this.phone, this.code)
         
         console.log('登录成功,result:', result)
@@ -263,7 +260,6 @@ export default {
         // 保存token和用户信息
         setToken(result.data.token)
         setUserInfo(result.data.userInfo)
-        setUserInfo(result.data.userInfo)
         
         uni.hideLoading()
         uni.showToast({

+ 45 - 2
src/pages/mine/mine.vue

@@ -2,13 +2,14 @@
   <view class="page-mine">
     <!-- User Info Card -->
     <view class="user-card">
-      <view class="avatar-container">
+      <view class="avatar-container" @click="handleEditProfile">
         <!-- Default avatar if not logged in or no avatar -->
         <image 
           class="avatar" 
-          :src="isLoggedIn && userInfo.avatar ? userInfo.avatar : '/static/images/tab_mine_active.png'" 
+          :src="isLoggedIn && userInfo.avatar ? userInfo.avatar : '/static/images/head.png'" 
           mode="aspectFill"
         ></image>
+        <view class="edit-icon">✏️</view>
       </view>
       <view class="user-info">
         <text class="username">{{ isLoggedIn ? (userInfo.nickname || '微信用户') : '游客' }}</text>
@@ -58,6 +59,7 @@
 
 <script setup>
 import { ref, onMounted } from 'vue'
+import { onShow } from '@dcloudio/uni-app'
 import { isLoggedIn as checkLogin, getUserInfo as getStoredUserInfo, logout, checkLogin as requireLogin } from '@/utils/auth.js'
 
 const isLoggedIn = ref(false)
@@ -75,6 +77,13 @@ onMounted(() => {
   loadUserInfo()
 })
 
+/**
+ * 页面显示时刷新用户信息(从登录页返回时会触发)
+ */
+onShow(() => {
+  loadUserInfo()
+})
+
 /**
  * 加载用户信息
  */
@@ -84,10 +93,27 @@ const loadUserInfo = () => {
     const storedInfo = getStoredUserInfo()
     if (storedInfo) {
       userInfo.value = storedInfo
+      console.log('加载用户信息:', userInfo.value)
     }
   }
 }
 
+/**
+ * 编辑个人资料
+ */
+const handleEditProfile = () => {
+  if (!requireLogin(() => {
+    handleEditProfile()
+  })) {
+    return
+  }
+  
+  // 跳转到编辑资料页面
+  uni.navigateTo({
+    url: '/pages/profile/edit'
+  })
+}
+
 /**
  * 切换登录状态
  */
@@ -173,6 +199,23 @@ const handleHistory = () => {
   overflow: hidden;
   background-color: #eee;
   flex-shrink: 0;
+  position: relative;
+  cursor: pointer;
+}
+
+.edit-icon {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  width: 36rpx;
+  height: 36rpx;
+  background: #5d55e8;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 20rpx;
+  border: 2rpx solid #fff;
 }
 
 .avatar {

+ 261 - 0
src/pages/profile/edit.vue

@@ -0,0 +1,261 @@
+<template>
+  <view class="edit-profile-container">
+    <view class="edit-header">
+      <text class="header-title">编辑资料</text>
+    </view>
+
+    <view class="edit-form">
+      <!-- 头像 -->
+      <view class="form-item">
+        <text class="item-label">头像</text>
+        <button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
+          <image class="avatar-preview" :src="avatarUrl" mode="aspectFill"></image>
+          <text class="change-text">点击更换</text>
+        </button>
+      </view>
+
+      <!-- 昵称 -->
+      <view class="form-item">
+        <text class="item-label">昵称</text>
+        <input 
+          class="item-input" 
+          type="nickname" 
+          v-model="nickname" 
+          placeholder="请输入昵称"
+          maxlength="20"
+        />
+      </view>
+
+      <!-- 手机号(只读) -->
+      <view class="form-item" v-if="phone">
+        <text class="item-label">手机号</text>
+        <text class="item-value">{{ phone }}</text>
+      </view>
+    </view>
+
+    <!-- 保存按钮 -->
+    <view class="action-area">
+      <button class="save-btn" @click="handleSave">保存</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getUserInfo, setUserInfo } from '@/utils/auth.js'
+import { updateUserProfile } from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      avatarUrl: '/static/images/head.png',
+      nickname: '',
+      phone: '',
+      originalAvatar: '',
+      originalNickname: ''
+    }
+  },
+
+  onLoad() {
+    this.loadUserInfo()
+  },
+
+  methods: {
+    /**
+     * 加载用户信息
+     */
+    loadUserInfo() {
+      const userInfo = getUserInfo()
+      if (userInfo) {
+        this.avatarUrl = userInfo.avatar || '/static/images/head.png'
+        this.nickname = userInfo.nickname || ''
+        this.phone = userInfo.phone || ''
+        this.originalAvatar = this.avatarUrl
+        this.originalNickname = this.nickname
+      }
+    },
+
+    /**
+     * 选择头像
+     */
+    onChooseAvatar(e) {
+      const { avatarUrl } = e.detail
+      this.avatarUrl = avatarUrl
+      console.log('选择头像:', avatarUrl)
+    },
+
+    /**
+     * 保存资料
+     */
+    async handleSave() {
+      if (!this.nickname || this.nickname.trim() === '') {
+        uni.showToast({
+          title: '请输入昵称',
+          icon: 'none'
+        })
+        return
+      }
+
+      // 检查是否有修改
+      if (this.avatarUrl === this.originalAvatar && this.nickname === this.originalNickname) {
+        uni.showToast({
+          title: '没有修改',
+          icon: 'none'
+        })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '保存中...' })
+
+        // 上传头像(如果更换了头像且不是默认头像)
+        let uploadedAvatarUrl = this.avatarUrl
+        if (this.avatarUrl !== this.originalAvatar && !this.avatarUrl.startsWith('/static/')) {
+          // TODO: 这里需要上传头像到服务器
+          // 暂时直接使用微信临时路径
+          uploadedAvatarUrl = this.avatarUrl
+        }
+
+        // 调用后端接口更新用户信息
+        const result = await updateUserProfile({
+          nickname: this.nickname,
+          avatar: uploadedAvatarUrl
+        })
+
+        console.log('更新成功:', result)
+
+        // 更新本地存储的用户信息
+        const userInfo = getUserInfo()
+        userInfo.nickname = this.nickname
+        userInfo.avatar = uploadedAvatarUrl
+        setUserInfo(userInfo)
+
+        uni.hideLoading()
+        uni.showToast({
+          title: '保存成功',
+          icon: 'success'
+        })
+
+        // 延迟返回
+        setTimeout(() => {
+          uni.navigateBack()
+        }, 1500)
+
+      } catch (error) {
+        console.error('保存失败:', error)
+        uni.hideLoading()
+        uni.showToast({
+          title: error.message || '保存失败',
+          icon: 'none'
+        })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.edit-profile-container {
+  min-height: 100vh;
+  background: #f5f6fb;
+  padding: 30rpx;
+}
+
+.edit-header {
+  text-align: center;
+  padding: 40rpx 0;
+}
+
+.header-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #222;
+}
+
+.edit-form {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 40rpx;
+}
+
+.form-item {
+  display: flex;
+  align-items: center;
+  padding: 30rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+
+.form-item:last-child {
+  border-bottom: none;
+}
+
+.item-label {
+  width: 150rpx;
+  font-size: 32rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.avatar-btn {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background: transparent;
+  border: none;
+  padding: 0;
+  margin: 0;
+  line-height: normal;
+}
+
+.avatar-btn::after {
+  border: none;
+}
+
+.avatar-preview {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 50%;
+  background: #f0f0f0;
+}
+
+.change-text {
+  font-size: 28rpx;
+  color: #5d55e8;
+}
+
+.item-input {
+  flex: 1;
+  height: 80rpx;
+  font-size: 32rpx;
+  color: #333;
+  text-align: right;
+}
+
+.item-value {
+  flex: 1;
+  font-size: 32rpx;
+  color: #999;
+  text-align: right;
+}
+
+.action-area {
+  padding: 0 40rpx;
+}
+
+.save-btn {
+  width: 100%;
+  height: 90rpx;
+  background: linear-gradient(135deg, #5d55e8, #7568ff);
+  color: #fff;
+  font-size: 32rpx;
+  border-radius: 45rpx;
+  border: none;
+  box-shadow: 0 12rpx 24rpx rgba(93, 85, 232, 0.4);
+  line-height: 90rpx;
+}
+
+.save-btn:active {
+  opacity: 0.9;
+}
+</style>

BIN
src/static/images/head.png


+ 22 - 3
src/utils/api.js

@@ -51,17 +51,18 @@ const request = (options) => {
 
 /**
  * 微信登录接口
- * @param {string} code - 微信登录code
+ * @param {object} params - 登录参数
+ * @param {string} params.code - 微信登录code
  * @returns {Promise} 返回登录结果,包含token和用户信息
  */
-export const wxLogin = (code) => {
+export const wxLogin = (params) => {
   return request({
     url: '/v1/auth/wxLogin',
     method: 'POST',
     header: {
       'content-type': 'application/json'
     },
-    data: { code }
+    data: params
   })
 }
 
@@ -110,6 +111,24 @@ export const getUserInfo = () => {
   })
 }
 
+/**
+ * 更新用户资料
+ * @param {object} data - 用户资料
+ * @param {string} data.nickname - 昵称
+ * @param {string} data.avatar - 头像URL
+ * @returns {Promise} 返回更新结果
+ */
+export const updateUserProfile = (data) => {
+  return request({
+    url: '/v1/user/profile',
+    method: 'PUT',
+    header: {
+      'content-type': 'application/json'
+    },
+    data: data
+  })
+}
+
 /**
  * 模糊搜索(联想建议)
  * @param {string} keyword - 搜索关键词