Răsfoiți Sursa

登录功能实现

Zhangbw 3 luni în urmă
părinte
comite
8b9eb7b470
38 a modificat fișierele cu 2030 adăugiri și 795 ștergeri
  1. 1 3
      dist/dev/mp-weixin/app.js
  2. 1 0
      dist/dev/mp-weixin/app.json
  3. 55 8
      dist/dev/mp-weixin/common/vendor.js
  4. 145 0
      dist/dev/mp-weixin/components/UserInfoPopup.js
  5. 4 0
      dist/dev/mp-weixin/components/UserInfoPopup.json
  6. 1 0
      dist/dev/mp-weixin/components/UserInfoPopup.wxml
  7. 115 0
      dist/dev/mp-weixin/components/UserInfoPopup.wxss
  8. 15 54
      dist/dev/mp-weixin/pages/index/index.js
  9. 0 0
      dist/dev/mp-weixin/pages/index/index.wxml
  10. 0 59
      dist/dev/mp-weixin/pages/index/index.wxss
  11. 257 0
      dist/dev/mp-weixin/pages/login/login.js
  12. 7 0
      dist/dev/mp-weixin/pages/login/login.json
  13. 1 0
      dist/dev/mp-weixin/pages/login/login.wxml
  14. 138 0
      dist/dev/mp-weixin/pages/login/login.wxss
  15. 3 27
      dist/dev/mp-weixin/pages/mine/mine.js
  16. 16 43
      dist/dev/mp-weixin/pages/pool/pool.js
  17. 6 34
      dist/dev/mp-weixin/pages/rank/rank.js
  18. 0 0
      dist/dev/mp-weixin/pages/rank/rank.wxml
  19. 5 33
      dist/dev/mp-weixin/pages/strong/strong.js
  20. 0 0
      dist/dev/mp-weixin/pages/strong/strong.wxml
  21. BIN
      dist/dev/mp-weixin/static/images/logo.png
  22. 36 3
      dist/dev/mp-weixin/utils/api.js
  23. 54 41
      dist/dev/mp-weixin/utils/auth.js
  24. 1 1
      project.config.json
  25. 0 4
      src/App.vue
  26. 100 0
      src/components/LoginGuide.vue
  27. 322 0
      src/components/UserInfoPopup.vue
  28. 7 0
      src/pages.json
  29. 15 143
      src/pages/index/index.vue
  30. 508 0
      src/pages/login/login.vue
  31. 0 45
      src/pages/logs/logs.vue
  32. 4 82
      src/pages/mine/mine.vue
  33. 15 54
      src/pages/pool/pool.vue
  34. 10 49
      src/pages/rank/rank.vue
  35. 9 49
      src/pages/strong/strong.vue
  36. BIN
      src/static/images/logo.png
  37. 77 0
      src/utils/api.js
  38. 102 63
      src/utils/auth.js

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

@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
 const common_vendor = require("./common/vendor.js");
 if (!Math) {
   "./pages/index/index.js";
+  "./pages/login/login.js";
   "./pages/pool/pool.js";
   "./pages/strong/strong.js";
   "./pages/rank/rank.js";
@@ -15,9 +16,6 @@ const _sfc_main = {
   },
   onLaunch: function() {
     console.log("App Launch");
-    const logs = common_vendor.index.getStorageSync("logs") || [];
-    logs.unshift(Date.now());
-    common_vendor.index.setStorageSync("logs", logs);
   },
   onShow: function() {
     console.log("App Show");

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

@@ -1,6 +1,7 @@
 {
   "pages": [
     "pages/index/index",
+    "pages/login/login",
     "pages/pool/pool",
     "pages/strong/strong",
     "pages/rank/rank",

+ 55 - 8
dist/dev/mp-weixin/common/vendor.js

@@ -707,8 +707,8 @@ function promisify$1(name, fn) {
     if (hasCallback(args)) {
       return wrapperReturnValue(name, invokeApi(name, fn, args, rest));
     }
-    return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
-      invokeApi(name, fn, extend(args, { success: resolve, fail: reject }), rest);
+    return wrapperReturnValue(name, handlePromise(new Promise((resolve2, reject) => {
+      invokeApi(name, fn, extend(args, { success: resolve2, fail: reject }), rest);
     })));
   };
 }
@@ -1000,7 +1000,7 @@ function invokeGetPushCidCallbacks(cid2, errMsg) {
   getPushCidCallbacks.length = 0;
 }
 const API_GET_PUSH_CLIENT_ID = "getPushClientId";
-const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve, reject }) => {
+const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve: resolve2, reject }) => {
   Promise.resolve().then(() => {
     if (typeof enabled === "undefined") {
       enabled = false;
@@ -1009,7 +1009,7 @@ const getPushClientId = defineAsyncApi(API_GET_PUSH_CLIENT_ID, (_, { resolve, re
     }
     getPushCidCallbacks.push((cid2, errMsg) => {
       if (cid2) {
-        resolve({ cid: cid2 });
+        resolve2({ cid: cid2 });
       } else {
         reject(errMsg);
       }
@@ -1074,9 +1074,9 @@ function promisify(name, api) {
     if (isFunction(options.success) || isFunction(options.fail) || isFunction(options.complete)) {
       return wrapperReturnValue(name, invokeApi(name, api, options, rest));
     }
-    return wrapperReturnValue(name, handlePromise(new Promise((resolve, reject) => {
+    return wrapperReturnValue(name, handlePromise(new Promise((resolve2, reject) => {
       invokeApi(name, api, extend({}, options, {
-        success: resolve,
+        success: resolve2,
         fail: reject
       }), rest);
     })));
@@ -3560,6 +3560,46 @@ function validateDirectiveName(name) {
     warn("Do not use built-in directive ids as custom directive id: " + name);
   }
 }
+const COMPONENTS = "components";
+function resolveComponent(name, maybeSelfReference) {
+  return resolveAsset(COMPONENTS, name, true, maybeSelfReference) || name;
+}
+function resolveAsset(type, name, warnMissing = true, maybeSelfReference = false) {
+  const instance = currentRenderingInstance || currentInstance;
+  if (instance) {
+    const Component2 = instance.type;
+    if (type === COMPONENTS) {
+      const selfName = getComponentName(
+        Component2,
+        false
+        /* do not include inferred name to avoid breaking existing code */
+      );
+      if (selfName && (selfName === name || selfName === camelize(name) || selfName === capitalize(camelize(name)))) {
+        return Component2;
+      }
+    }
+    const res = (
+      // local registration
+      // check instance[type] first which is resolved for options API
+      resolve(instance[type] || Component2[type], name) || // global registration
+      resolve(instance.appContext[type], name)
+    );
+    if (!res && maybeSelfReference) {
+      return Component2;
+    }
+    if (warnMissing && !res) {
+      const extra = type === COMPONENTS ? `
+If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement.` : ``;
+      warn(`Failed to resolve ${type.slice(0, -1)}: ${name}${extra}`);
+    }
+    return res;
+  } else {
+    warn(`resolve${capitalize(type.slice(0, -1))} can only be used in render() or setup().`);
+  }
+}
+function resolve(registry, name) {
+  return registry && (registry[name] || registry[camelize(name)] || registry[capitalize(camelize(name))]);
+}
 const getPublicInstance = (i) => {
   if (!i)
     return null;
@@ -5143,8 +5183,8 @@ function nextTick(instance, fn) {
       _resolve(instance.proxy);
     }
   });
-  return new Promise((resolve) => {
-    _resolve = resolve;
+  return new Promise((resolve2) => {
+    _resolve = resolve2;
   });
 }
 function clone(src, seen) {
@@ -5907,11 +5947,16 @@ function vFor(source, renderItem) {
   }
   return ret;
 }
+function setRef(ref2, id, opts = {}) {
+  const { $templateRefs } = getCurrentInstance();
+  $templateRefs.push({ i: id, r: ref2, k: opts.k, f: opts.f });
+}
 const o = (value, key) => vOn(value, key);
 const f = (source, renderItem) => vFor(source, renderItem);
 const e = (target, ...sources) => extend(target, ...sources);
 const n = (value) => normalizeClass(value);
 const t = (val) => toDisplayString(val);
+const sr = (ref2, id, opts) => setRef(ref2, id, opts);
 function createApp$1(rootComponent, rootProps = null) {
   rootComponent && (rootComponent.mpType = "app");
   return createVueApp(rootComponent, rootProps).use(plugin);
@@ -6758,5 +6803,7 @@ exports.onLoad = onLoad;
 exports.onMounted = onMounted;
 exports.onShow = onShow;
 exports.ref = ref;
+exports.resolveComponent = resolveComponent;
+exports.sr = sr;
 exports.t = t;
 exports.unref = unref;

+ 145 - 0
dist/dev/mp-weixin/components/UserInfoPopup.js

@@ -0,0 +1,145 @@
+"use strict";
+const common_vendor = require("../common/vendor.js");
+const utils_api = require("../utils/api.js");
+const _sfc_main = {
+  data() {
+    return {
+      visible: false,
+      nickname: "",
+      avatarUrl: "",
+      tempAvatarPath: "",
+      // 临时头像路径
+      userData: null
+      // 后端返回的用户数据
+    };
+  },
+  methods: {
+    /**
+     * 打开弹窗
+     */
+    open(userData) {
+      this.visible = true;
+      this.userData = userData;
+      this.nickname = "";
+      this.avatarUrl = "";
+      this.tempAvatarPath = "";
+    },
+    /**
+     * 关闭弹窗
+     */
+    close() {
+      this.visible = false;
+    },
+    /**
+     * 选择头像
+     */
+    handleChooseAvatar(e) {
+      console.log("[用户信息] 选择头像:", e);
+      const { avatarUrl } = e.detail;
+      this.tempAvatarPath = avatarUrl;
+      this.avatarUrl = avatarUrl;
+    },
+    /**
+     * 点击遮罩层
+     */
+    handleMaskClick() {
+    },
+    /**
+     * 取消
+     */
+    handleCancel() {
+      common_vendor.index.showModal({
+        title: "提示",
+        content: "取消后将无法完成登录,确定要取消吗?",
+        success: (res) => {
+          if (res.confirm) {
+            this.close();
+          }
+        }
+      });
+    },
+    /**
+     * 确定提交
+     */
+    async handleConfirm() {
+      if (!this.nickname || this.nickname.trim() === "") {
+        common_vendor.index.showToast({
+          title: "请输入昵称",
+          icon: "none"
+        });
+        return;
+      }
+      if (!this.avatarUrl) {
+        common_vendor.index.showToast({
+          title: "请选择头像",
+          icon: "none"
+        });
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "上传中..." });
+        let uploadedAvatarUrl = this.avatarUrl;
+        if (this.tempAvatarPath) {
+          uploadedAvatarUrl = await this.uploadAvatar(this.tempAvatarPath);
+        }
+        common_vendor.index.hideLoading();
+        this.$emit("confirm", {
+          nickname: this.nickname.trim(),
+          avatarUrl: uploadedAvatarUrl
+        });
+        this.close();
+      } catch (error) {
+        common_vendor.index.hideLoading();
+        console.error("[用户信息] 上传头像失败:", error);
+        common_vendor.index.showToast({
+          title: "头像上传失败,请重试",
+          icon: "none"
+        });
+      }
+    },
+    /**
+     * 上传头像到OSS
+     */
+    uploadAvatar(filePath) {
+      return new Promise((resolve, reject) => {
+        common_vendor.index.uploadFile({
+          url: utils_api.uploadFile.url,
+          // 从api.js导入
+          filePath,
+          name: "file",
+          header: {
+            "Authorization": `Bearer ${common_vendor.index.getStorageSync("user_token") || ""}`
+          },
+          success: (res) => {
+            const data = JSON.parse(res.data);
+            if (data.code === 200 && data.data && data.data.url) {
+              resolve(data.data.url);
+            } else {
+              reject(new Error(data.message || "上传失败"));
+            }
+          },
+          fail: (err) => {
+            reject(err);
+          }
+        });
+      });
+    }
+  }
+};
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return common_vendor.e({
+    a: $data.visible
+  }, $data.visible ? {
+    b: $data.avatarUrl || "/static/images/default-avatar.png",
+    c: common_vendor.o((...args) => $options.handleChooseAvatar && $options.handleChooseAvatar(...args)),
+    d: $data.nickname,
+    e: common_vendor.o(($event) => $data.nickname = $event.detail.value),
+    f: common_vendor.o((...args) => $options.handleCancel && $options.handleCancel(...args)),
+    g: common_vendor.o((...args) => $options.handleConfirm && $options.handleConfirm(...args)),
+    h: common_vendor.o(() => {
+    }),
+    i: common_vendor.o((...args) => $options.handleMaskClick && $options.handleMaskClick(...args))
+  } : {});
+}
+const Component = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-a5b292f7"], ["__file", "D:/program/gupiao-wx/src/components/UserInfoPopup.vue"]]);
+wx.createComponent(Component);

+ 4 - 0
dist/dev/mp-weixin/components/UserInfoPopup.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 1 - 0
dist/dev/mp-weixin/components/UserInfoPopup.wxml

@@ -0,0 +1 @@
+<view wx:if="{{a}}" class="popup-mask data-v-a5b292f7" bindtap="{{i}}"><view class="popup-content data-v-a5b292f7" catchtap="{{h}}"><view class="popup-header data-v-a5b292f7"><text class="popup-title data-v-a5b292f7">完善个人信息</text><text class="popup-subtitle data-v-a5b292f7">首次登录需要完善以下信息</text></view><view class="form-container data-v-a5b292f7"><view class="form-item data-v-a5b292f7"><text class="form-label data-v-a5b292f7">头像</text><button class="avatar-btn data-v-a5b292f7" open-type="chooseAvatar" bindchooseavatar="{{c}}"><image class="avatar-img data-v-a5b292f7" src="{{b}}" mode="aspectFill"></image><view class="avatar-tip data-v-a5b292f7">点击选择头像</view></button></view><view class="form-item data-v-a5b292f7"><text class="form-label data-v-a5b292f7">昵称</text><input class="nickname-input data-v-a5b292f7" type="nickname" placeholder="请输入昵称" placeholder-class="input-placeholder" maxlength="20" value="{{d}}" bindinput="{{e}}"/></view></view><view class="popup-actions data-v-a5b292f7"><button class="action-btn cancel-btn data-v-a5b292f7" bindtap="{{f}}">取消</button><button class="action-btn confirm-btn data-v-a5b292f7" bindtap="{{g}}">确定</button></view></view></view>

+ 115 - 0
dist/dev/mp-weixin/components/UserInfoPopup.wxss

@@ -0,0 +1,115 @@
+
+.popup-mask.data-v-a5b292f7 {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+}
+.popup-content.data-v-a5b292f7 {
+  width: 600rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 48rpx 40rpx;
+}
+.popup-header.data-v-a5b292f7 {
+  text-align: center;
+  margin-bottom: 48rpx;
+}
+.popup-title.data-v-a5b292f7 {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.popup-subtitle.data-v-a5b292f7 {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+}
+.form-container.data-v-a5b292f7 {
+  margin-bottom: 48rpx;
+}
+.form-item.data-v-a5b292f7 {
+  margin-bottom: 40rpx;
+}
+.form-label.data-v-a5b292f7 {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+/* 头像选择 */
+.avatar-btn.data-v-a5b292f7 {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 80rpx;
+  overflow: hidden;
+  position: relative;
+  background: #f5f6fb;
+  border: 2rpx dashed #ddd;
+  padding: 0;
+  margin: 0 auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.avatar-img.data-v-a5b292f7 {
+  width: 100%;
+  height: 100%;
+}
+.avatar-tip.data-v-a5b292f7 {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgba(0, 0, 0, 0.5);
+  color: #fff;
+  font-size: 20rpx;
+  text-align: center;
+  padding: 8rpx 0;
+}
+
+/* 昵称输入 */
+.nickname-input.data-v-a5b292f7 {
+  width: 100%;
+  height: 88rpx;
+  background: #f5f6fb;
+  border-radius: 12rpx;
+  padding: 0 24rpx;
+  font-size: 28rpx;
+  color: #333;
+}
+.input-placeholder.data-v-a5b292f7 {
+  color: #999;
+}
+
+/* 按钮 */
+.popup-actions.data-v-a5b292f7 {
+  display: flex;
+  gap: 24rpx;
+}
+.action-btn.data-v-a5b292f7 {
+  flex: 1;
+  height: 88rpx;
+  border-radius: 44rpx;
+  font-size: 30rpx;
+  font-weight: 500;
+  border: none;
+}
+.cancel-btn.data-v-a5b292f7 {
+  background: #f5f6fb;
+  color: #666;
+}
+.confirm-btn.data-v-a5b292f7 {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+}

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

@@ -13,7 +13,6 @@ const _sfc_main = {
     const suggestions = common_vendor.ref([]);
     const showDropdown = common_vendor.ref(false);
     const isLoggedIn = common_vendor.ref(false);
-    const showPhoneAuth = common_vendor.ref(false);
     let timer = null;
     common_vendor.onMounted(() => {
       isLoggedIn.value = utils_auth.isLoggedIn();
@@ -28,56 +27,25 @@ const _sfc_main = {
       console.log("=== 点击搜索按钮 ===");
       console.log("当前登录状态:", isLoggedIn.value);
       if (!isLoggedIn.value) {
-        console.log("未登录,直接调用微信授权");
-        handleLogin();
+        console.log("未登录,跳转到登录页");
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({
+                url: "/pages/login/login"
+              });
+            }
+          }
+        });
         return;
       }
       console.log("已登录,执行搜索");
       onSearch();
     };
-    const handleLogin = () => {
-      console.log("[首页] 显示手机号授权弹窗");
-      showPhoneAuth.value = true;
-    };
-    const onGetPhoneNumber = async (e) => {
-      console.log("[首页] 获取手机号回调:", e.detail);
-      if (e.detail.errMsg === "getPhoneNumber:ok") {
-        const phoneCode = e.detail.code;
-        console.log("[首页] phoneCode:", phoneCode);
-        common_vendor.index.showLoading({
-          title: "登录中...",
-          mask: true
-        });
-        try {
-          const loginRes = await common_vendor.index.login();
-          console.log("[首页] uni.login完整响应:", loginRes);
-          console.log("[首页] 微信登录code:", loginRes.code);
-          if (!loginRes.code) {
-            throw new Error("获取微信登录code失败");
-          }
-          const result2 = await utils_auth.wxAuthLogin(loginRes.code, phoneCode);
-          common_vendor.index.hideLoading();
-          if (result2) {
-            showPhoneAuth.value = false;
-            isLoggedIn.value = utils_auth.isLoggedIn();
-            onSearch();
-          }
-        } catch (error) {
-          common_vendor.index.hideLoading();
-          console.error("[首页] 登录失败:", error);
-        }
-      } else {
-        showPhoneAuth.value = false;
-        common_vendor.index.showToast({
-          title: "需要授权手机号才能完成登录",
-          icon: "none",
-          duration: 2e3
-        });
-      }
-    };
-    const closePhoneAuth = () => {
-      showPhoneAuth.value = false;
-    };
     const onKeywordChange = (e) => {
       const value = e.detail.value;
       keyword.value = value;
@@ -202,14 +170,7 @@ const _sfc_main = {
       } : {}, {
         k: errorMsg.value,
         m: result.value
-      }) : {}, {
-        s: showPhoneAuth.value
-      }, showPhoneAuth.value ? {
-        t: common_vendor.o(onGetPhoneNumber),
-        v: common_vendor.o(() => {
-        }),
-        w: common_vendor.o(closePhoneAuth)
-      } : {});
+      }) : {});
     };
   }
 };

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
dist/dev/mp-weixin/pages/index/index.wxml


+ 0 - 59
dist/dev/mp-weixin/pages/index/index.wxss

@@ -286,62 +286,3 @@
 .bottom-safe-area {
   height: 80rpx;
 }
-
-/* 手机号授权弹窗 */
-.phone-auth-mask {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.6);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 9999;
-}
-.phone-auth-prompt {
-  width: 560rpx;
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 50rpx 40rpx;
-  text-align: center;
-  box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
-}
-.auth-icon {
-  font-size: 72rpx;
-  margin-bottom: 20rpx;
-}
-.auth-title {
-  display: block;
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #222222;
-  margin-bottom: 12rpx;
-}
-.auth-desc {
-  display: block;
-  font-size: 24rpx;
-  color: #999999;
-  line-height: 1.5;
-  margin-bottom: 32rpx;
-}
-.auth-button {
-  width: 100%;
-  height: 80rpx;
-  background: linear-gradient(135deg, #FF9800, #FFA726);
-  color: #ffffff;
-  border-radius: 40rpx;
-  font-size: 30rpx;
-  font-weight: 600;
-  box-shadow: 0 8rpx 24rpx rgba(255, 152, 0, 0.4);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: none;
-  padding: 0;
-  line-height: 80rpx;
-}
-.auth-button::after {
-  border: none;
-}

+ 257 - 0
dist/dev/mp-weixin/pages/login/login.js

@@ -0,0 +1,257 @@
+"use strict";
+const common_vendor = require("../../common/vendor.js");
+const utils_auth = require("../../utils/auth.js");
+require("../../utils/api.js");
+const UserInfoPopup = () => "../../components/UserInfoPopup.js";
+const _sfc_main = {
+  components: {
+    UserInfoPopup
+  },
+  data() {
+    return {
+      showOneClickLogin: true,
+      // 是否显示一键登录按钮
+      agreedToTerms: false,
+      // 是否同意协议
+      loginCode: "",
+      // 微信登录code
+      tempUserData: null
+      // 临时存储的用户数据
+    };
+  },
+  methods: {
+    /**
+     * 返回上一页
+     */
+    handleBack() {
+      const pages = getCurrentPages();
+      if (pages.length > 1) {
+        common_vendor.index.navigateBack();
+      } else {
+        common_vendor.index.switchTab({
+          url: "/pages/index/index"
+        });
+      }
+    },
+    /**
+     * 第一步:微信一键登录(老用户静默登录)
+     */
+    async handleWxLogin() {
+      if (!this.agreedToTerms) {
+        common_vendor.index.showToast({
+          title: "请先阅读并同意用户协议",
+          icon: "none",
+          duration: 2e3
+        });
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "登录中..." });
+        const loginRes = await this.wxLoginAsync();
+        this.loginCode = loginRes.code;
+        console.log("[登录] 获取到微信code:", this.loginCode);
+        const result = await utils_auth.wxSilentLogin(this.loginCode);
+        common_vendor.index.hideLoading();
+        if (result && result.isSign === "true") {
+          console.log("[登录] 老用户登录成功");
+          this.handleLoginSuccess();
+        } else if (result && result.isSign === "false") {
+          console.log("[登录] 新用户,需要授权手机号");
+          this.showOneClickLogin = false;
+        } else if (result && result.code === 103) {
+          common_vendor.index.showModal({
+            title: "账号异常",
+            content: "您的账号已被禁用,如有疑问请联系客服",
+            showCancel: false
+          });
+        } else {
+          throw new Error("登录接口返回数据异常");
+        }
+      } catch (error) {
+        common_vendor.index.hideLoading();
+        console.error("[登录] 微信登录失败:", error);
+        common_vendor.index.showToast({
+          title: error.message || "登录失败,请重试",
+          icon: "none",
+          duration: 2e3
+        });
+      }
+    },
+    /**
+     * 第二步:获取手机号授权(新用户)
+     */
+    async handleGetPhoneNumber(e) {
+      console.log("[登录] 手机号授权回调:", e);
+      if (e.detail.errMsg !== "getPhoneNumber:ok") {
+        if (e.detail.errMsg.includes("no permission")) {
+          common_vendor.index.showModal({
+            title: "权限不足",
+            content: '获取手机号功能需要:\n1. 小程序企业认证\n2. 开通"手机号快速验证组件"权限\n3. 在真机上测试',
+            showCancel: false
+          });
+        } else {
+          common_vendor.index.showToast({
+            title: "授权失败,请重试",
+            icon: "none"
+          });
+        }
+        return;
+      }
+      try {
+        common_vendor.index.showLoading({ title: "验证中..." });
+        const loginRes = await this.wxLoginAsync();
+        const newLoginCode = loginRes.code;
+        const params = {
+          loginCode: newLoginCode,
+          phoneCode: e.detail.code,
+          encryptedData: e.detail.encryptedData,
+          iv: e.detail.iv
+        };
+        console.log("[登录] 发送手机号验证请求");
+        const result = await utils_auth.wxPhoneLogin(params);
+        common_vendor.index.hideLoading();
+        if (result && result.isSign === "false") {
+          console.log("[登录] 需要完善用户信息");
+          this.tempUserData = result;
+          this.$refs.userInfoPopup.open(result);
+        } else if (result && result.isSign === "true") {
+          console.log("[登录] 已注册用户,登录成功");
+          this.handleLoginSuccess();
+        } else {
+          throw new Error("验证接口返回数据异常");
+        }
+      } catch (error) {
+        common_vendor.index.hideLoading();
+        console.error("[登录] 手机号验证失败:", error);
+        let errorMsg = "验证失败,请重试";
+        if (error.message) {
+          if (error.message.includes("48001") || error.message.includes("未开通手机号快速验证组件")) {
+            common_vendor.index.showModal({
+              title: "权限未开通",
+              content: '小程序未开通"手机号快速验证组件"权限\n\n请前往微信公众平台:\n开发 → 开发管理 → 接口设置\n开通"手机号快速验证组件"',
+              showCancel: false
+            });
+            return;
+          } else if (error.message.includes("40029") || error.message.includes("code无效")) {
+            errorMsg = "phoneCode已失效,请重新授权";
+          } else if (error.message.includes("40001") || error.message.includes("access_token")) {
+            errorMsg = "access_token无效,请重试";
+          } else if (error.message.includes("45011")) {
+            errorMsg = "操作过于频繁,请稍后重试";
+          } else {
+            errorMsg = error.message;
+          }
+        }
+        common_vendor.index.showToast({
+          title: errorMsg,
+          icon: "none",
+          duration: 3e3
+        });
+      }
+    },
+    /**
+     * 第三步:完善用户信息(首次登录)
+     */
+    async handleUserInfoConfirm(userInfo) {
+      try {
+        common_vendor.index.showLoading({ title: "注册中..." });
+        const completeInfo = {
+          openid: this.tempUserData.openid,
+          unionid: this.tempUserData.unionid,
+          phoneNumber: this.tempUserData.phoneNumber,
+          nickname: userInfo.nickname,
+          avatarUrl: userInfo.avatarUrl
+        };
+        console.log("[登录] 提交完整用户信息");
+        await utils_auth.wxCompleteUserInfo(completeInfo);
+        common_vendor.index.hideLoading();
+        console.log("[登录] 注册成功");
+        this.handleLoginSuccess();
+      } catch (error) {
+        common_vendor.index.hideLoading();
+        console.error("[登录] 注册失败:", error);
+        common_vendor.index.showToast({
+          title: error.message || "注册失败,请重试",
+          icon: "none",
+          duration: 2e3
+        });
+      }
+    },
+    /**
+     * 登录成功后的处理
+     */
+    handleLoginSuccess() {
+      common_vendor.index.showToast({
+        title: "登录成功",
+        icon: "success",
+        duration: 1500
+      });
+      setTimeout(() => {
+        const pages = getCurrentPages();
+        if (pages.length > 1) {
+          common_vendor.index.navigateBack();
+        } else {
+          common_vendor.index.switchTab({
+            url: "/pages/index/index"
+          });
+        }
+      }, 1500);
+    },
+    /**
+     * 协议勾选变化
+     */
+    handleAgreementChange(e) {
+      this.agreedToTerms = e.detail.value.length > 0;
+    },
+    /**
+     * 显示协议内容
+     */
+    showAgreement(type) {
+      const title = type === "user" ? "用户协议" : "隐私政策";
+      common_vendor.index.showModal({
+        title,
+        content: "这里显示协议内容...",
+        showCancel: false
+      });
+    },
+    /**
+     * 封装 wx.login 为 Promise
+     */
+    wxLoginAsync() {
+      return new Promise((resolve, reject) => {
+        common_vendor.index.login({
+          provider: "weixin",
+          success: (res) => {
+            resolve(res);
+          },
+          fail: (err) => {
+            reject(err);
+          }
+        });
+      });
+    }
+  }
+};
+if (!Array) {
+  const _component_user_info_popup = common_vendor.resolveComponent("user-info-popup");
+  _component_user_info_popup();
+}
+function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
+  return common_vendor.e({
+    a: common_vendor.o((...args) => $options.handleBack && $options.handleBack(...args)),
+    b: $data.showOneClickLogin
+  }, $data.showOneClickLogin ? {
+    c: common_vendor.o((...args) => $options.handleWxLogin && $options.handleWxLogin(...args))
+  } : {
+    d: common_vendor.o((...args) => $options.handleGetPhoneNumber && $options.handleGetPhoneNumber(...args))
+  }, {
+    e: $data.agreedToTerms,
+    f: common_vendor.o(($event) => $options.showAgreement("user")),
+    g: common_vendor.o(($event) => $options.showAgreement("privacy")),
+    h: common_vendor.o((...args) => $options.handleAgreementChange && $options.handleAgreementChange(...args)),
+    i: common_vendor.sr("userInfoPopup", "cdfe2409-0"),
+    j: common_vendor.o($options.handleUserInfoConfirm)
+  });
+}
+const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-cdfe2409"], ["__file", "D:/program/gupiao-wx/src/pages/login/login.vue"]]);
+wx.createPage(MiniProgramPage);

+ 7 - 0
dist/dev/mp-weixin/pages/login/login.json

@@ -0,0 +1,7 @@
+{
+  "navigationBarTitleText": "登录",
+  "navigationStyle": "custom",
+  "usingComponents": {
+    "user-info-popup": "../../components/UserInfoPopup"
+  }
+}

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

@@ -0,0 +1 @@
+<view class="login-container data-v-cdfe2409"><view class="back-button data-v-cdfe2409" bindtap="{{a}}"><text class="back-icon data-v-cdfe2409">←</text><text class="back-text data-v-cdfe2409">返回</text></view><view class="bg-decoration data-v-cdfe2409"><view class="circle circle-1 data-v-cdfe2409"></view><view class="circle circle-2 data-v-cdfe2409"></view></view><view class="header data-v-cdfe2409"><image class="logo data-v-cdfe2409" src="/static/images/logo.png" mode="aspectFit"></image><text class="title data-v-cdfe2409">量化选股大师</text><text class="subtitle data-v-cdfe2409">专业的股票量化分析工具</text></view><view class="login-actions data-v-cdfe2409"><button wx:if="{{b}}" class="login-btn primary-btn data-v-cdfe2409" bindtap="{{c}}"><text class="btn-icon data-v-cdfe2409">📱</text><text class="btn-text data-v-cdfe2409">微信一键登录</text></button><button wx:else class="login-btn primary-btn data-v-cdfe2409" open-type="getPhoneNumber" bindgetphonenumber="{{d}}"><text class="btn-icon data-v-cdfe2409">🔐</text><text class="btn-text data-v-cdfe2409">授权手机号登录</text></button><view class="agreement data-v-cdfe2409"><checkbox-group class="data-v-cdfe2409" bindchange="{{h}}"><label class="agreement-label data-v-cdfe2409"><checkbox class="data-v-cdfe2409" checked="{{e}}" color="#5d55e8"/><text class="agreement-text data-v-cdfe2409"> 我已阅读并同意 <text class="link data-v-cdfe2409" catchtap="{{f}}">《用户协议》</text> 和 <text class="link data-v-cdfe2409" catchtap="{{g}}">《隐私政策》</text></text></label></checkbox-group></view></view><user-info-popup class="r data-v-cdfe2409" u-r="userInfoPopup" bindconfirm="{{j}}" u-i="cdfe2409-0" bind:__l="__l"/></view>

+ 138 - 0
dist/dev/mp-weixin/pages/login/login.wxss

@@ -0,0 +1,138 @@
+
+/* 返回按钮 */
+.back-button.data-v-cdfe2409 {
+  position: absolute;
+  top: 40rpx;
+  left: 30rpx;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  padding: 12rpx 24rpx;
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 50rpx;
+  backdrop-filter: blur(10rpx);
+}
+.back-icon.data-v-cdfe2409 {
+  font-size: 32rpx;
+  color: #fff;
+  margin-right: 8rpx;
+  font-weight: bold;
+}
+.back-text.data-v-cdfe2409 {
+  font-size: 28rpx;
+  color: #fff;
+}
+.login-container.data-v-cdfe2409 {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40rpx;
+  position: relative;
+  overflow: hidden;
+}
+
+/* 背景装饰 */
+.bg-decoration.data-v-cdfe2409 {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 0;
+}
+.circle.data-v-cdfe2409 {
+  position: absolute;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.1);
+}
+.circle-1.data-v-cdfe2409 {
+  width: 400rpx;
+  height: 400rpx;
+  top: -100rpx;
+  right: -100rpx;
+}
+.circle-2.data-v-cdfe2409 {
+  width: 300rpx;
+  height: 300rpx;
+  bottom: -50rpx;
+  left: -50rpx;
+}
+
+/* 头部 */
+.header.data-v-cdfe2409 {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 120rpx;
+  z-index: 1;
+}
+.logo.data-v-cdfe2409 {
+  width: 160rpx;
+  height: 160rpx;
+  margin-bottom: 40rpx;
+  border-radius: 30rpx;
+  background: #fff;
+  box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1);
+}
+.title.data-v-cdfe2409 {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 16rpx;
+}
+.subtitle.data-v-cdfe2409 {
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+/* 登录按钮区域 */
+.login-actions.data-v-cdfe2409 {
+  width: 100%;
+  z-index: 1;
+}
+.login-btn.data-v-cdfe2409 {
+  width: 100%;
+  height: 96rpx;
+  border-radius: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-bottom: 40rpx;
+  border: none;
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
+}
+.primary-btn.data-v-cdfe2409 {
+  background: #fff;
+  color: #667eea;
+}
+.btn-icon.data-v-cdfe2409 {
+  font-size: 40rpx;
+  margin-right: 16rpx;
+}
+.btn-text.data-v-cdfe2409 {
+  font-size: 32rpx;
+}
+
+/* 用户协议 */
+.agreement.data-v-cdfe2409 {
+  margin-top: 60rpx;
+}
+.agreement-label.data-v-cdfe2409 {
+  display: flex;
+  align-items: flex-start;
+}
+.agreement-text.data-v-cdfe2409 {
+  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.9);
+  line-height: 1.6;
+  margin-left: 12rpx;
+}
+.link.data-v-cdfe2409 {
+  color: #fff;
+  text-decoration: underline;
+}

+ 3 - 27
dist/dev/mp-weixin/pages/mine/mine.js

@@ -6,7 +6,7 @@ const _sfc_main = {
   __name: "mine",
   setup(__props) {
     const isLoggedIn = common_vendor.ref(false);
-    const showPhoneAuth = common_vendor.ref(false);
+    common_vendor.ref(false);
     const userInfo = common_vendor.ref({
       nickname: "",
       avatar: "",
@@ -37,33 +37,9 @@ const _sfc_main = {
     };
     const handleLogin = async () => {
       console.log("[我的] 开始登录流程");
-      common_vendor.index.showLoading({
-        title: "登录中...",
-        mask: true
+      common_vendor.index.navigateTo({
+        url: "/pages/login/login"
       });
-      try {
-        const loginRes = await common_vendor.index.login();
-        console.log("[我的] uni.login完整响应:", loginRes);
-        if (!loginRes.code) {
-          throw new Error("获取微信登录code失败");
-        }
-        common_vendor.index.hideLoading();
-        showPhoneAuth.value = true;
-        common_vendor.index.showModal({
-          title: "授权提示",
-          content: "需要授权手机号才能完成登录",
-          showCancel: false,
-          success: () => {
-          }
-        });
-      } catch (error) {
-        common_vendor.index.hideLoading();
-        console.error("[我的] 登录失败:", error);
-        common_vendor.index.showToast({
-          title: "登录失败,请重试",
-          icon: "none"
-        });
-      }
     };
     const handleLogout = () => {
       common_vendor.index.showModal({

+ 16 - 43
dist/dev/mp-weixin/pages/pool/pool.js

@@ -39,52 +39,25 @@ const _sfc_main = {
     const showPurchaseModal = () => {
       console.log("点击立即解锁");
       if (!checkLogin()) {
-        console.log("未登录,显示手机号授权弹窗");
-        showPhoneAuth.value = true;
+        console.log("未登录,跳转到登录页");
+        common_vendor.index.showModal({
+          title: "登录提示",
+          content: "此功能需要登录后使用,是否前往登录?",
+          confirmText: "去登录",
+          cancelText: "取消",
+          success: (res) => {
+            if (res.confirm) {
+              common_vendor.index.navigateTo({
+                url: "/pages/login/login"
+              });
+            }
+          }
+        });
         return;
       }
       console.log("已登录,显示购买弹窗");
       showModal.value = true;
     };
-    const onGetPhoneNumber = async (e) => {
-      console.log("[超短池] 获取手机号回调:", e.detail);
-      if (e.detail.errMsg === "getPhoneNumber:ok") {
-        const phoneCode = e.detail.code;
-        console.log("[超短池] phoneCode:", phoneCode);
-        common_vendor.index.showLoading({
-          title: "登录中...",
-          mask: true
-        });
-        try {
-          const loginRes = await common_vendor.index.login();
-          console.log("[超短池] uni.login完整响应:", loginRes);
-          console.log("[超短池] 微信登录code:", loginRes.code);
-          if (!loginRes.code) {
-            throw new Error("获取微信登录code失败");
-          }
-          const result = await utils_auth.wxAuthLogin(loginRes.code, phoneCode);
-          common_vendor.index.hideLoading();
-          if (result) {
-            showPhoneAuth.value = false;
-            checkLogin();
-            showModal.value = true;
-          }
-        } catch (error) {
-          common_vendor.index.hideLoading();
-          console.error("[超短池] 登录失败:", error);
-        }
-      } else {
-        showPhoneAuth.value = false;
-        common_vendor.index.showToast({
-          title: "需要授权手机号才能完成登录",
-          icon: "none",
-          duration: 2e3
-        });
-      }
-    };
-    const closePhoneAuth = () => {
-      showPhoneAuth.value = false;
-    };
     const closePurchaseModal = () => {
       showModal.value = false;
     };
@@ -143,10 +116,10 @@ const _sfc_main = {
         e: common_vendor.o(onHistorySearch),
         f: showPhoneAuth.value
       }, showPhoneAuth.value ? {
-        g: common_vendor.o(onGetPhoneNumber),
+        g: common_vendor.o((...args) => _ctx.onGetPhoneNumber && _ctx.onGetPhoneNumber(...args)),
         h: common_vendor.o(() => {
         }),
-        i: common_vendor.o(closePhoneAuth)
+        i: common_vendor.o((...args) => _ctx.closePhoneAuth && _ctx.closePhoneAuth(...args))
       } : {}, {
         j: showModal.value
       }, showModal.value ? {

+ 6 - 34
dist/dev/mp-weixin/pages/rank/rank.js

@@ -41,39 +41,6 @@ const _sfc_main = {
         return "rank-third";
       return "";
     };
-    const onGetPhoneNumber = async (e) => {
-      console.log("[模拟排名] 获取手机号回调:", e.detail);
-      if (e.detail.errMsg === "getPhoneNumber:ok") {
-        const phoneCode = e.detail.code;
-        console.log("[模拟排名] phoneCode:", phoneCode);
-        common_vendor.index.showLoading({
-          title: "登录中...",
-          mask: true
-        });
-        try {
-          const loginRes = await common_vendor.index.login();
-          console.log("[模拟排名] uni.login完整响应:", loginRes);
-          console.log("[模拟排名] 微信登录code:", loginRes.code);
-          if (!loginRes.code) {
-            throw new Error("获取微信登录code失败");
-          }
-          const result = await utils_auth.wxAuthLogin(loginRes.code, phoneCode);
-          common_vendor.index.hideLoading();
-          if (result) {
-            checkLoginAndLoadData();
-          }
-        } catch (error) {
-          common_vendor.index.hideLoading();
-          console.error("[模拟排名] 登录失败:", error);
-        }
-      } else {
-        common_vendor.index.showToast({
-          title: "需要授权手机号才能登录",
-          icon: "none",
-          duration: 2e3
-        });
-      }
-    };
     const checkLoginAndLoadData = () => {
       isLoggedIn.value = utils_auth.isLoggedIn();
       console.log("[模拟排名] 登录状态:", isLoggedIn.value);
@@ -83,6 +50,11 @@ const _sfc_main = {
         leaderboard.value = mockLeaderboard;
       }
     };
+    const goToLogin = () => {
+      common_vendor.index.navigateTo({
+        url: "/pages/login/login"
+      });
+    };
     const loadRealData = async () => {
       try {
         const portfolioRes = await utils_api.getUserPortfolio();
@@ -129,7 +101,7 @@ const _sfc_main = {
         j: !isLoggedIn.value ? 1 : "",
         k: !isLoggedIn.value
       }, !isLoggedIn.value ? {
-        l: common_vendor.o(onGetPhoneNumber)
+        l: common_vendor.o(goToLogin)
       } : {});
     };
   }

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
dist/dev/mp-weixin/pages/rank/rank.wxml


+ 5 - 33
dist/dev/mp-weixin/pages/strong/strong.js

@@ -26,38 +26,10 @@ const _sfc_main = {
       isLoggedIn.value = utils_auth.isLoggedIn();
       console.log("[强势池] 登录状态:", isLoggedIn.value);
     };
-    const onGetPhoneNumber = async (e) => {
-      console.log("[强势池] 获取手机号回调:", e.detail);
-      if (e.detail.errMsg === "getPhoneNumber:ok") {
-        const phoneCode = e.detail.code;
-        console.log("[强势池] phoneCode:", phoneCode);
-        common_vendor.index.showLoading({
-          title: "登录中...",
-          mask: true
-        });
-        try {
-          const loginRes = await common_vendor.index.login();
-          console.log("[强势池] uni.login完整响应:", loginRes);
-          console.log("[强势池] 微信登录code:", loginRes.code);
-          if (!loginRes.code) {
-            throw new Error("获取微信登录code失败");
-          }
-          const result = await utils_auth.wxAuthLogin(loginRes.code, phoneCode);
-          common_vendor.index.hideLoading();
-          if (result) {
-            checkLogin();
-          }
-        } catch (error) {
-          common_vendor.index.hideLoading();
-          console.error("[强势池] 登录失败:", error);
-        }
-      } else {
-        common_vendor.index.showToast({
-          title: "需要授权手机号才能登录",
-          icon: "none",
-          duration: 2e3
-        });
-      }
+    const goToLogin = () => {
+      common_vendor.index.navigateTo({
+        url: "/pages/login/login"
+      });
     };
     const buyTotalAmount = common_vendor.computed(() => {
       const qty = parseInt(buyQuantity.value) || 0;
@@ -205,7 +177,7 @@ const _sfc_main = {
         f: !isLoggedIn.value ? 1 : "",
         g: !isLoggedIn.value
       }, !isLoggedIn.value ? {
-        h: common_vendor.o(onGetPhoneNumber)
+        h: common_vendor.o(goToLogin)
       } : {}, {
         i: showBuyModalFlag.value
       }, showBuyModalFlag.value ? {

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
dist/dev/mp-weixin/pages/strong/strong.wxml


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


+ 36 - 3
dist/dev/mp-weixin/utils/api.js

@@ -54,9 +54,9 @@ const request = (options) => {
     });
   });
 };
-const wxLogin = (params) => {
+const wxSilentLoginApi = (params) => {
   return request({
-    url: "/v1/auth/wxLogin",
+    url: "/auth/sys/miniapp/custom/openid",
     method: "POST",
     header: {
       "content-type": "application/json"
@@ -64,6 +64,35 @@ const wxLogin = (params) => {
     data: params
   });
 };
+const wxPhoneLoginApi = (params) => {
+  return request({
+    url: "/auth/sys/miniapp/custom/check",
+    method: "POST",
+    header: {
+      "content-type": "application/json"
+    },
+    data: params
+  });
+};
+const wxCompleteUserInfoApi = (params) => {
+  return request({
+    url: "/auth/sys/miniapp/custom/login",
+    method: "POST",
+    header: {
+      "content-type": "application/json"
+    },
+    data: params
+  });
+};
+const getUserInfoApi = () => {
+  return request({
+    url: "/wd/miniapp-member/bizBigMember/getMemberInfoByToken",
+    method: "GET"
+  });
+};
+const uploadFile = {
+  url: `${BASE_URL}/jeecg-boot/mg/sys/oss/file/upload`
+};
 const updateUserProfile = (data) => {
   return request({
     url: "/v1/user/profile",
@@ -105,7 +134,11 @@ const getLeaderboard = () => {
 };
 exports.getLeaderboard = getLeaderboard;
 exports.getSuggestions = getSuggestions;
+exports.getUserInfoApi = getUserInfoApi;
 exports.getUserPortfolio = getUserPortfolio;
 exports.searchStocks = searchStocks;
 exports.updateUserProfile = updateUserProfile;
-exports.wxLogin = wxLogin;
+exports.uploadFile = uploadFile;
+exports.wxCompleteUserInfoApi = wxCompleteUserInfoApi;
+exports.wxPhoneLoginApi = wxPhoneLoginApi;
+exports.wxSilentLoginApi = wxSilentLoginApi;

+ 54 - 41
dist/dev/mp-weixin/utils/auth.js

@@ -29,54 +29,65 @@ const logout = () => {
   removeToken();
   removeUserInfo();
 };
-const wxAuthLogin = async (code, phoneCode) => {
+const wxSilentLogin = async (loginCode) => {
   try {
-    console.log("[微信登录] ========== 开始登录 ==========");
-    console.log("[微信登录] 接收到的参数:");
-    console.log("  - code:", code);
-    console.log("  - phoneCode:", phoneCode);
-    if (!code) {
-      console.error("[微信登录] 错误:code为空!");
-      throw new Error("微信登录code为空,请重试");
+    console.log("[静默登录] 开始检查用户状态, code:", loginCode);
+    const result = await utils_api.wxSilentLoginApi({ loginCode });
+    console.log("[静默登录] 后端响应:", result);
+    if (result.code === 200 && result.data.isSign === "true" && result.data.token) {
+      setToken(result.data.token);
+      console.log("[静默登录] 老用户登录成功");
+      await fetchAndSaveUserInfo();
     }
-    if (!phoneCode) {
-      console.error("[微信登录] 错误:phoneCode为空!");
-      throw new Error("手机号授权code为空,请重试");
+    return result.data;
+  } catch (error) {
+    console.error("[静默登录] 失败:", error);
+    throw error;
+  }
+};
+const wxPhoneLogin = async (params) => {
+  try {
+    console.log("[手机号登录] 开始验证手机号");
+    const result = await utils_api.wxPhoneLoginApi(params);
+    console.log("[手机号登录] 后端响应:", result);
+    if (result.code === 200 && result.data.isSign === "true" && result.data.token) {
+      setToken(result.data.token);
+      console.log("[手机号登录] 已注册用户登录成功");
+      await fetchAndSaveUserInfo();
     }
-    const loginParams = {
-      code,
-      // 微信登录code,后端用于获取 openid 和 unionid
-      phoneCode
-      // 手机号授权code,后端用于获取手机号、昵称、头像等
-    };
-    console.log("[微信登录] 准备发送到后端的参数:", JSON.stringify(loginParams));
-    const result = await utils_api.wxLogin(loginParams);
-    console.log("[微信登录] 后端响应:", result);
-    if (result.code === 200 && result.data && result.data.token) {
+    return result.data;
+  } catch (error) {
+    console.error("[手机号登录] 失败:", error);
+    throw error;
+  }
+};
+const wxCompleteUserInfo = async (userInfo) => {
+  try {
+    console.log("[完善信息] 提交用户信息");
+    const result = await utils_api.wxCompleteUserInfoApi(userInfo);
+    console.log("[完善信息] 后端响应:", result);
+    if (result.code === 200 && result.data.token) {
       setToken(result.data.token);
-      if (result.data.userInfo) {
-        setUserInfo(result.data.userInfo);
-      }
-      console.log("[微信登录] 登录成功,token已保存");
-      common_vendor.index.showToast({
-        title: "登录成功",
-        icon: "success",
-        duration: 1500
-      });
+      console.log("[完善信息] 注册成功");
+      await fetchAndSaveUserInfo();
       return true;
     } else {
-      console.error("[微信登录] 后端返回格式错误:", result);
-      throw new Error(result.message || "登录返回数据格式错误");
+      throw new Error(result.message || "注册失败");
+    }
+  } catch (error) {
+    console.error("[完善信息] 失败:", error);
+    throw error;
+  }
+};
+const fetchAndSaveUserInfo = async () => {
+  try {
+    const result = await utils_api.getUserInfoApi();
+    if (result.code === 200 && result.data) {
+      setUserInfo(result.data);
+      console.log("[用户信息] 获取成功");
     }
   } catch (error) {
-    console.error("[微信登录] ========== 登录失败 ==========");
-    console.error("[微信登录] 错误详情:", error);
-    common_vendor.index.showToast({
-      title: error.message || "登录失败,请稍后重试",
-      icon: "none",
-      duration: 2e3
-    });
-    return false;
+    console.error("[用户信息] 获取失败:", error);
   }
 };
 const checkLogin = (callback) => {
@@ -96,4 +107,6 @@ exports.getUserInfo = getUserInfo;
 exports.isLoggedIn = isLoggedIn;
 exports.logout = logout;
 exports.setUserInfo = setUserInfo;
-exports.wxAuthLogin = wxAuthLogin;
+exports.wxCompleteUserInfo = wxCompleteUserInfo;
+exports.wxPhoneLogin = wxPhoneLogin;
+exports.wxSilentLogin = wxSilentLogin;

+ 1 - 1
project.config.json

@@ -37,6 +37,6 @@
     "tabIndent": "auto",
     "tabSize": 2
   },
-  "appid": "wx6e0cde280c822198",
+  "appid": "wxcf9eec0da6a6b696",
   "simulatorPluginLibVersion": {}
 }

+ 0 - 4
src/App.vue

@@ -5,10 +5,6 @@ export default {
   },
   onLaunch: function() {
     console.log('App Launch')
-    // 展示本地存储能力
-    const logs = uni.getStorageSync('logs') || []
-    logs.unshift(Date.now())
-    uni.setStorageSync('logs', logs)
   },
   onShow: function() {
     console.log('App Show')

+ 100 - 0
src/components/LoginGuide.vue

@@ -0,0 +1,100 @@
+<template>
+  <view class="login-guide" v-if="!isLoggedIn">
+    <view class="guide-content">
+      <image class="guide-icon" src="/static/images/login-icon.png" mode="aspectFit"></image>
+      <text class="guide-title">{{ title || '登录后查看更多内容' }}</text>
+      <text class="guide-desc">{{ desc || '登录后可以使用完整功能' }}</text>
+      <button class="guide-btn" @click="goToLogin">立即登录</button>
+    </view>
+  </view>
+</template>
+
+<script>
+import { isLoggedIn } from '@/utils/auth.js'
+
+export default {
+  props: {
+    title: {
+      type: String,
+      default: '登录后查看更多内容'
+    },
+    desc: {
+      type: String,
+      default: '登录后可以使用完整功能'
+    }
+  },
+  
+  data() {
+    return {
+      isLoggedIn: false
+    }
+  },
+  
+  onShow() {
+    this.checkLoginStatus()
+  },
+  
+  methods: {
+    checkLoginStatus() {
+      this.isLoggedIn = isLoggedIn()
+    },
+    
+    goToLogin() {
+      uni.navigateTo({
+        url: '/pages/login/login'
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.login-guide {
+  min-height: 100vh;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #f5f6fb;
+}
+
+.guide-content {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx 40rpx;
+}
+
+.guide-icon {
+  width: 200rpx;
+  height: 200rpx;
+  margin-bottom: 40rpx;
+  opacity: 0.6;
+}
+
+.guide-title {
+  font-size: 32rpx;
+  font-weight: 500;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+
+.guide-desc {
+  font-size: 26rpx;
+  color: #999;
+  margin-bottom: 60rpx;
+}
+
+.guide-btn {
+  width: 400rpx;
+  height: 88rpx;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 44rpx;
+  font-size: 30rpx;
+  font-weight: 500;
+  border: none;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 322 - 0
src/components/UserInfoPopup.vue

@@ -0,0 +1,322 @@
+<template>
+  <view class="popup-mask" v-if="visible" @click="handleMaskClick">
+    <view class="popup-content" @click.stop>
+      <view class="popup-header">
+        <text class="popup-title">完善个人信息</text>
+        <text class="popup-subtitle">首次登录需要完善以下信息</text>
+      </view>
+
+      <view class="form-container">
+        <!-- 头像选择 -->
+        <view class="form-item">
+          <text class="form-label">头像</text>
+          <button 
+            class="avatar-btn"
+            open-type="chooseAvatar"
+            @chooseavatar="handleChooseAvatar"
+          >
+            <image 
+              class="avatar-img" 
+              :src="avatarUrl || '/static/images/default-avatar.png'" 
+              mode="aspectFill"
+            ></image>
+            <view class="avatar-tip">点击选择头像</view>
+          </button>
+        </view>
+
+        <!-- 昵称输入 -->
+        <view class="form-item">
+          <text class="form-label">昵称</text>
+          <input 
+            class="nickname-input"
+            type="nickname"
+            v-model="nickname"
+            placeholder="请输入昵称"
+            placeholder-class="input-placeholder"
+            maxlength="20"
+          />
+        </view>
+      </view>
+
+      <view class="popup-actions">
+        <button class="action-btn cancel-btn" @click="handleCancel">取消</button>
+        <button class="action-btn confirm-btn" @click="handleConfirm">确定</button>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { uploadFile } from '@/utils/api.js'
+
+export default {
+  data() {
+    return {
+      visible: false,
+      nickname: '',
+      avatarUrl: '',
+      tempAvatarPath: '',  // 临时头像路径
+      userData: null       // 后端返回的用户数据
+    }
+  },
+
+  methods: {
+    /**
+     * 打开弹窗
+     */
+    open(userData) {
+      this.visible = true
+      this.userData = userData
+      this.nickname = ''
+      this.avatarUrl = ''
+      this.tempAvatarPath = ''
+    },
+
+    /**
+     * 关闭弹窗
+     */
+    close() {
+      this.visible = false
+    },
+
+    /**
+     * 选择头像
+     */
+    handleChooseAvatar(e) {
+      console.log('[用户信息] 选择头像:', e)
+      const { avatarUrl } = e.detail
+      this.tempAvatarPath = avatarUrl
+      this.avatarUrl = avatarUrl
+    },
+
+    /**
+     * 点击遮罩层
+     */
+    handleMaskClick() {
+      // 不允许点击遮罩关闭
+    },
+
+    /**
+     * 取消
+     */
+    handleCancel() {
+      uni.showModal({
+        title: '提示',
+        content: '取消后将无法完成登录,确定要取消吗?',
+        success: (res) => {
+          if (res.confirm) {
+            this.close()
+          }
+        }
+      })
+    },
+
+    /**
+     * 确定提交
+     */
+    async handleConfirm() {
+      // 验证昵称
+      if (!this.nickname || this.nickname.trim() === '') {
+        uni.showToast({
+          title: '请输入昵称',
+          icon: 'none'
+        })
+        return
+      }
+
+      // 验证头像
+      if (!this.avatarUrl) {
+        uni.showToast({
+          title: '请选择头像',
+          icon: 'none'
+        })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '上传中...' })
+
+        // 上传头像到OSS
+        let uploadedAvatarUrl = this.avatarUrl
+        if (this.tempAvatarPath) {
+          uploadedAvatarUrl = await this.uploadAvatar(this.tempAvatarPath)
+        }
+
+        uni.hideLoading()
+
+        // 返回用户信息给父组件
+        this.$emit('confirm', {
+          nickname: this.nickname.trim(),
+          avatarUrl: uploadedAvatarUrl
+        })
+
+        this.close()
+      } catch (error) {
+        uni.hideLoading()
+        console.error('[用户信息] 上传头像失败:', error)
+        uni.showToast({
+          title: '头像上传失败,请重试',
+          icon: 'none'
+        })
+      }
+    },
+
+    /**
+     * 上传头像到OSS
+     */
+    uploadAvatar(filePath) {
+      return new Promise((resolve, reject) => {
+        uni.uploadFile({
+          url: uploadFile.url,  // 从api.js导入
+          filePath: filePath,
+          name: 'file',
+          header: {
+            'Authorization': `Bearer ${uni.getStorageSync('user_token') || ''}`
+          },
+          success: (res) => {
+            const data = JSON.parse(res.data)
+            if (data.code === 200 && data.data && data.data.url) {
+              resolve(data.data.url)
+            } else {
+              reject(new Error(data.message || '上传失败'))
+            }
+          },
+          fail: (err) => {
+            reject(err)
+          }
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.popup-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, 0.6);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 9999;
+}
+
+.popup-content {
+  width: 600rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 48rpx 40rpx;
+}
+
+.popup-header {
+  text-align: center;
+  margin-bottom: 48rpx;
+}
+
+.popup-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 12rpx;
+}
+
+.popup-subtitle {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+}
+
+.form-container {
+  margin-bottom: 48rpx;
+}
+
+.form-item {
+  margin-bottom: 40rpx;
+}
+
+.form-label {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+/* 头像选择 */
+.avatar-btn {
+  width: 160rpx;
+  height: 160rpx;
+  border-radius: 80rpx;
+  overflow: hidden;
+  position: relative;
+  background: #f5f6fb;
+  border: 2rpx dashed #ddd;
+  padding: 0;
+  margin: 0 auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.avatar-img {
+  width: 100%;
+  height: 100%;
+}
+
+.avatar-tip {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgba(0, 0, 0, 0.5);
+  color: #fff;
+  font-size: 20rpx;
+  text-align: center;
+  padding: 8rpx 0;
+}
+
+/* 昵称输入 */
+.nickname-input {
+  width: 100%;
+  height: 88rpx;
+  background: #f5f6fb;
+  border-radius: 12rpx;
+  padding: 0 24rpx;
+  font-size: 28rpx;
+  color: #333;
+}
+
+.input-placeholder {
+  color: #999;
+}
+
+/* 按钮 */
+.popup-actions {
+  display: flex;
+  gap: 24rpx;
+}
+
+.action-btn {
+  flex: 1;
+  height: 88rpx;
+  border-radius: 44rpx;
+  font-size: 30rpx;
+  font-weight: 500;
+  border: none;
+}
+
+.cancel-btn {
+  background: #f5f6fb;
+  color: #666;
+}
+
+.confirm-btn {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+}
+</style>

+ 7 - 0
src/pages.json

@@ -6,6 +6,13 @@
         "navigationBarTitleText": "量化选股大师"
       }
     },
+    {
+      "path": "pages/login/login",
+      "style": {
+        "navigationBarTitleText": "登录",
+        "navigationStyle": "custom"
+      }
+    },
     {
       "path": "pages/pool/pool",
       "style": {

+ 15 - 143
src/pages/index/index.vue

@@ -125,30 +125,13 @@
         <view class="bottom-safe-area"></view>
       </view>
     </scroll-view>
-    
-    <!-- 手机号授权弹窗 -->
-    <view v-if="showPhoneAuth" class="phone-auth-mask" @click="closePhoneAuth">
-      <view class="phone-auth-prompt" @click.stop>
-        <view class="auth-icon">📱</view>
-        <text class="auth-title">授权手机号</text>
-        <text class="auth-desc">为了完成登录,需要获取您的手机号</text>
-        
-        <button 
-          class="auth-button" 
-          open-type="getPhoneNumber" 
-          @getphonenumber="onGetPhoneNumber"
-        >
-          授权手机号
-        </button>
-      </view>
-    </view>
   </view>
 </template>
 
 <script setup>
 import { ref, onMounted } from 'vue'
 import { getSuggestions, searchStocks } from '../../utils/api.js'
-import { isLoggedIn as checkLoginStatus, wxAuthLogin } from '../../utils/auth.js'
+import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
 
 // 引入 onShow 生命周期
 import { onShow } from '@dcloudio/uni-app'
@@ -161,7 +144,6 @@ const result = ref(null)
 const suggestions = ref([])
 const showDropdown = ref(false)
 const isLoggedIn = ref(false)
-const showPhoneAuth = ref(false)  // 是否显示手机号授权按钮
 let timer = null
 
 /**
@@ -190,8 +172,20 @@ const handleSearchClick = async () => {
   
   // 检查登录状态
   if (!isLoggedIn.value) {
-    console.log('未登录,直接调用微信授权')
-    handleLogin()
+    console.log('未登录,跳转到登录页')
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({
+            url: '/pages/login/login'
+          })
+        }
+      }
+    })
     return
   }
   
@@ -200,63 +194,6 @@ const handleSearchClick = async () => {
   onSearch()
 }
 
-// 处理登录 - 直接显示手机号授权
-const handleLogin = () => {
-  console.log('[首页] 显示手机号授权弹窗')
-  showPhoneAuth.value = true
-}
-
-// 处理手机号授权并完成登录
-const onGetPhoneNumber = async (e) => {
-  console.log('[首页] 获取手机号回调:', e.detail)
-  
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    const phoneCode = e.detail.code
-    console.log('[首页] phoneCode:', phoneCode)
-    
-    uni.showLoading({
-      title: '登录中...',
-      mask: true
-    })
-    
-    try {
-      const loginRes = await uni.login()
-      console.log('[首页] uni.login完整响应:', loginRes)
-      console.log('[首页] 微信登录code:', loginRes.code)
-      
-      if (!loginRes.code) {
-        throw new Error('获取微信登录code失败')
-      }
-      
-      const result = await wxAuthLogin(loginRes.code, phoneCode)
-      
-      uni.hideLoading()
-      
-      if (result) {
-        showPhoneAuth.value = false
-        isLoggedIn.value = checkLoginStatus()
-        // 登录成功后执行搜索
-        onSearch()
-      }
-    } catch (error) {
-      uni.hideLoading()
-      console.error('[首页] 登录失败:', error)
-    }
-  } else {
-    showPhoneAuth.value = false
-    uni.showToast({
-      title: '需要授权手机号才能完成登录',
-      icon: 'none',
-      duration: 2000
-    })
-  }
-}
-
-// 关闭手机号授权弹窗
-const closePhoneAuth = () => {
-  showPhoneAuth.value = false
-}
-
 const onKeywordChange = (e) => {
   const value = e.detail.value
   keyword.value = value
@@ -692,69 +629,4 @@ const onInputBlur = () => {
 .bottom-safe-area {
   height: 80rpx;
 }
-
-/* 手机号授权弹窗 */
-.phone-auth-mask {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0, 0, 0, 0.6);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 9999;
-}
-
-.phone-auth-prompt {
-  width: 560rpx;
-  background: #ffffff;
-  border-radius: 24rpx;
-  padding: 50rpx 40rpx;
-  text-align: center;
-  box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);
-}
-
-.auth-icon {
-  font-size: 72rpx;
-  margin-bottom: 20rpx;
-}
-
-.auth-title {
-  display: block;
-  font-size: 32rpx;
-  font-weight: 600;
-  color: #222222;
-  margin-bottom: 12rpx;
-}
-
-.auth-desc {
-  display: block;
-  font-size: 24rpx;
-  color: #999999;
-  line-height: 1.5;
-  margin-bottom: 32rpx;
-}
-
-.auth-button {
-  width: 100%;
-  height: 80rpx;
-  background: linear-gradient(135deg, #FF9800, #FFA726);
-  color: #ffffff;
-  border-radius: 40rpx;
-  font-size: 30rpx;
-  font-weight: 600;
-  box-shadow: 0 8rpx 24rpx rgba(255, 152, 0, 0.4);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: none;
-  padding: 0;
-  line-height: 80rpx;
-}
-
-.auth-button::after {
-  border: none;
-}
 </style>

+ 508 - 0
src/pages/login/login.vue

@@ -0,0 +1,508 @@
+<template>
+  <view class="login-container">
+    <!-- 返回按钮 -->
+    <view class="back-button" @click="handleBack">
+      <text class="back-icon">←</text>
+      <text class="back-text">返回</text>
+    </view>
+
+    <!-- 背景装饰 -->
+    <view class="bg-decoration">
+      <view class="circle circle-1"></view>
+      <view class="circle circle-2"></view>
+    </view>
+
+    <!-- Logo和标题 -->
+    <view class="header">
+      <image class="logo" src="/static/images/logo.png" mode="aspectFit"></image>
+      <text class="title">量化选股大师</text>
+      <text class="subtitle">专业的股票量化分析工具</text>
+    </view>
+
+    <!-- 登录按钮区域 -->
+    <view class="login-actions">
+      <!-- 老用户:微信一键登录 -->
+      <button 
+        v-if="showOneClickLogin"
+        class="login-btn primary-btn"
+        @click="handleWxLogin"
+      >
+        <text class="btn-icon">📱</text>
+        <text class="btn-text">微信一键登录</text>
+      </button>
+
+      <!-- 新用户:授权手机号登录 -->
+      <button 
+        v-else
+        class="login-btn primary-btn"
+        open-type="getPhoneNumber"
+        @getphonenumber="handleGetPhoneNumber"
+      >
+        <text class="btn-icon">🔐</text>
+        <text class="btn-text">授权手机号登录</text>
+      </button>
+
+      <!-- 用户协议 -->
+      <view class="agreement">
+        <checkbox-group @change="handleAgreementChange">
+          <label class="agreement-label">
+            <checkbox :checked="agreedToTerms" color="#5d55e8" />
+            <text class="agreement-text">
+              我已阅读并同意
+              <text class="link" @click.stop="showAgreement('user')">《用户协议》</text>
+              和
+              <text class="link" @click.stop="showAgreement('privacy')">《隐私政策》</text>
+            </text>
+          </label>
+        </checkbox-group>
+      </view>
+    </view>
+
+    <!-- 用户信息完善弹窗 -->
+    <user-info-popup 
+      ref="userInfoPopup"
+      @confirm="handleUserInfoConfirm"
+    />
+  </view>
+</template>
+
+<script>
+import { wxSilentLogin, wxPhoneLogin, wxCompleteUserInfo } from '@/utils/auth.js'
+import UserInfoPopup from '@/components/UserInfoPopup.vue'
+
+export default {
+  components: {
+    UserInfoPopup
+  },
+  
+  data() {
+    return {
+      showOneClickLogin: true,  // 是否显示一键登录按钮
+      agreedToTerms: false,     // 是否同意协议
+      loginCode: '',            // 微信登录code
+      tempUserData: null        // 临时存储的用户数据
+    }
+  },
+
+  methods: {
+    /**
+     * 返回上一页
+     */
+    handleBack() {
+      const pages = getCurrentPages()
+      if (pages.length > 1) {
+        // 有上一页,返回
+        uni.navigateBack()
+      } else {
+        // 没有上一页,跳转到首页
+        uni.switchTab({
+          url: '/pages/index/index'
+        })
+      }
+    },
+
+    /**
+     * 第一步:微信一键登录(老用户静默登录)
+     */
+    async handleWxLogin() {
+      // 检查是否同意协议
+      if (!this.agreedToTerms) {
+        uni.showToast({
+          title: '请先阅读并同意用户协议',
+          icon: 'none',
+          duration: 2000
+        })
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '登录中...' })
+        
+        // 调用 wx.login 获取 code
+        const loginRes = await this.wxLoginAsync()
+        this.loginCode = loginRes.code
+        
+        console.log('[登录] 获取到微信code:', this.loginCode)
+        
+        // 调用后端接口检查是否为老用户
+        const result = await wxSilentLogin(this.loginCode)
+        
+        uni.hideLoading()
+        
+        if (result && result.isSign === 'true') {
+          // 老用户,直接登录成功
+          console.log('[登录] 老用户登录成功')
+          this.handleLoginSuccess()
+        } else if (result && result.isSign === 'false') {
+          // 新用户,需要授权手机号
+          console.log('[登录] 新用户,需要授权手机号')
+          this.showOneClickLogin = false
+        } else if (result && result.code === 103) {
+          // 账号被禁用
+          uni.showModal({
+            title: '账号异常',
+            content: '您的账号已被禁用,如有疑问请联系客服',
+            showCancel: false
+          })
+        } else {
+          // 后端返回异常
+          throw new Error('登录接口返回数据异常')
+        }
+      } catch (error) {
+        uni.hideLoading()
+        console.error('[登录] 微信登录失败:', error)
+        uni.showToast({
+          title: error.message || '登录失败,请重试',
+          icon: 'none',
+          duration: 2000
+        })
+      }
+    },
+
+    /**
+     * 第二步:获取手机号授权(新用户)
+     */
+    async handleGetPhoneNumber(e) {
+      console.log('[登录] 手机号授权回调:', e)
+      
+      if (e.detail.errMsg !== 'getPhoneNumber:ok') {
+        // 检查是否是权限问题
+        if (e.detail.errMsg.includes('no permission')) {
+          uni.showModal({
+            title: '权限不足',
+            content: '获取手机号功能需要:\n1. 小程序企业认证\n2. 开通"手机号快速验证组件"权限\n3. 在真机上测试',
+            showCancel: false
+          })
+        } else {
+          uni.showToast({
+            title: '授权失败,请重试',
+            icon: 'none'
+          })
+        }
+        return
+      }
+
+      try {
+        uni.showLoading({ title: '验证中...' })
+        
+        // 重新获取 code(微信要求)
+        const loginRes = await this.wxLoginAsync()
+        const newLoginCode = loginRes.code
+        
+        // 构建请求参数
+        const params = {
+          loginCode: newLoginCode,
+          phoneCode: e.detail.code,
+          encryptedData: e.detail.encryptedData,
+          iv: e.detail.iv
+        }
+        
+        console.log('[登录] 发送手机号验证请求')
+        
+        // 调用后端接口验证手机号
+        const result = await wxPhoneLogin(params)
+        
+        uni.hideLoading()
+        
+        if (result && result.isSign === 'false') {
+          // 需要完善用户信息
+          console.log('[登录] 需要完善用户信息')
+          this.tempUserData = result
+          this.$refs.userInfoPopup.open(result)
+        } else if (result && result.isSign === 'true') {
+          // 已注册,直接登录成功
+          console.log('[登录] 已注册用户,登录成功')
+          this.handleLoginSuccess()
+        } else {
+          // 后端返回异常
+          throw new Error('验证接口返回数据异常')
+        }
+      } catch (error) {
+        uni.hideLoading()
+        console.error('[登录] 手机号验证失败:', error)
+        
+        // 根据错误信息提供友好提示
+        let errorMsg = '验证失败,请重试'
+        
+        if (error.message) {
+          if (error.message.includes('48001') || error.message.includes('未开通手机号快速验证组件')) {
+            // 未开通权限
+            uni.showModal({
+              title: '权限未开通',
+              content: '小程序未开通"手机号快速验证组件"权限\n\n请前往微信公众平台:\n开发 → 开发管理 → 接口设置\n开通"手机号快速验证组件"',
+              showCancel: false
+            })
+            return
+          } else if (error.message.includes('40029') || error.message.includes('code无效')) {
+            errorMsg = 'phoneCode已失效,请重新授权'
+          } else if (error.message.includes('40001') || error.message.includes('access_token')) {
+            errorMsg = 'access_token无效,请重试'
+          } else if (error.message.includes('45011')) {
+            errorMsg = '操作过于频繁,请稍后重试'
+          } else {
+            errorMsg = error.message
+          }
+        }
+        
+        uni.showToast({
+          title: errorMsg,
+          icon: 'none',
+          duration: 3000
+        })
+      }
+    },
+
+    /**
+     * 第三步:完善用户信息(首次登录)
+     */
+    async handleUserInfoConfirm(userInfo) {
+      try {
+        uni.showLoading({ title: '注册中...' })
+        
+        // 构建完整的用户信息
+        const completeInfo = {
+          openid: this.tempUserData.openid,
+          unionid: this.tempUserData.unionid,
+          phoneNumber: this.tempUserData.phoneNumber,
+          nickname: userInfo.nickname,
+          avatarUrl: userInfo.avatarUrl
+        }
+        
+        console.log('[登录] 提交完整用户信息')
+        
+        // 调用后端接口完成注册
+        await wxCompleteUserInfo(completeInfo)
+        
+        uni.hideLoading()
+        
+        console.log('[登录] 注册成功')
+        this.handleLoginSuccess()
+      } catch (error) {
+        uni.hideLoading()
+        console.error('[登录] 注册失败:', error)
+        uni.showToast({
+          title: error.message || '注册失败,请重试',
+          icon: 'none',
+          duration: 2000
+        })
+      }
+    },
+
+    /**
+     * 登录成功后的处理
+     */
+    handleLoginSuccess() {
+      uni.showToast({
+        title: '登录成功',
+        icon: 'success',
+        duration: 1500
+      })
+      
+      // 延迟跳转,让用户看到成功提示
+      setTimeout(() => {
+        // 跳转到首页或返回上一页
+        const pages = getCurrentPages()
+        if (pages.length > 1) {
+          uni.navigateBack()
+        } else {
+          uni.switchTab({
+            url: '/pages/index/index'
+          })
+        }
+      }, 1500)
+    },
+
+    /**
+     * 协议勾选变化
+     */
+    handleAgreementChange(e) {
+      this.agreedToTerms = e.detail.value.length > 0
+    },
+
+    /**
+     * 显示协议内容
+     */
+    showAgreement(type) {
+      const title = type === 'user' ? '用户协议' : '隐私政策'
+      uni.showModal({
+        title: title,
+        content: '这里显示协议内容...',
+        showCancel: false
+      })
+    },
+
+    /**
+     * 封装 wx.login 为 Promise
+     */
+    wxLoginAsync() {
+      return new Promise((resolve, reject) => {
+        uni.login({
+          provider: 'weixin',
+          success: (res) => {
+            resolve(res)
+          },
+          fail: (err) => {
+            reject(err)
+          }
+        })
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+/* 返回按钮 */
+.back-button {
+  position: absolute;
+  top: 40rpx;
+  left: 30rpx;
+  z-index: 10;
+  display: flex;
+  align-items: center;
+  padding: 12rpx 24rpx;
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 50rpx;
+  backdrop-filter: blur(10rpx);
+}
+
+.back-icon {
+  font-size: 32rpx;
+  color: #fff;
+  margin-right: 8rpx;
+  font-weight: bold;
+}
+
+.back-text {
+  font-size: 28rpx;
+  color: #fff;
+}
+
+.login-container {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 40rpx;
+  position: relative;
+  overflow: hidden;
+}
+
+/* 背景装饰 */
+.bg-decoration {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 0;
+}
+
+.circle {
+  position: absolute;
+  border-radius: 50%;
+  background: rgba(255, 255, 255, 0.1);
+}
+
+.circle-1 {
+  width: 400rpx;
+  height: 400rpx;
+  top: -100rpx;
+  right: -100rpx;
+}
+
+.circle-2 {
+  width: 300rpx;
+  height: 300rpx;
+  bottom: -50rpx;
+  left: -50rpx;
+}
+
+/* 头部 */
+.header {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-bottom: 120rpx;
+  z-index: 1;
+}
+
+.logo {
+  width: 160rpx;
+  height: 160rpx;
+  margin-bottom: 40rpx;
+  border-radius: 30rpx;
+  background: #fff;
+  box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.1);
+}
+
+.title {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 16rpx;
+}
+
+.subtitle {
+  font-size: 28rpx;
+  color: rgba(255, 255, 255, 0.8);
+}
+
+/* 登录按钮区域 */
+.login-actions {
+  width: 100%;
+  z-index: 1;
+}
+
+.login-btn {
+  width: 100%;
+  height: 96rpx;
+  border-radius: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 32rpx;
+  font-weight: 500;
+  margin-bottom: 40rpx;
+  border: none;
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.15);
+}
+
+.primary-btn {
+  background: #fff;
+  color: #667eea;
+}
+
+.btn-icon {
+  font-size: 40rpx;
+  margin-right: 16rpx;
+}
+
+.btn-text {
+  font-size: 32rpx;
+}
+
+/* 用户协议 */
+.agreement {
+  margin-top: 60rpx;
+}
+
+.agreement-label {
+  display: flex;
+  align-items: flex-start;
+}
+
+.agreement-text {
+  font-size: 24rpx;
+  color: rgba(255, 255, 255, 0.9);
+  line-height: 1.6;
+  margin-left: 12rpx;
+}
+
+.link {
+  color: #fff;
+  text-decoration: underline;
+}
+</style>

+ 0 - 45
src/pages/logs/logs.vue

@@ -1,45 +0,0 @@
-<template>
-  <scroll-view class="scrollarea" scroll-y type="list">
-    <block v-for="(log, index) in logs" :key="log.timeStamp">
-      <view class="log-item">{{ index + 1 }}. {{ log.date }}</view>
-    </block>
-  </scroll-view>
-</template>
-
-<script setup>
-import { ref } from 'vue'
-import { onLoad } from '@dcloudio/uni-app'
-import { formatTime } from '../../utils/util.js'
-
-const logs = ref([])
-
-onLoad(() => {
-  console.log('[日志页] 页面加载')
-  const storedLogs = uni.getStorageSync('logs') || []
-  logs.value = storedLogs.map(log => {
-    return {
-      date: formatTime(new Date(log)),
-      timeStamp: log
-    }
-  })
-})
-</script>
-
-<style>
-page {
-  height: 100vh;
-  display: flex;
-  flex-direction: column;
-}
-.scrollarea {
-  flex: 1;
-  overflow-y: hidden;
-}
-.log-item {
-  margin-top: 20rpx;
-  text-align: center;
-}
-.log-item:last-child {
-  padding-bottom: env(safe-area-inset-bottom);
-}
-</style>

+ 4 - 82
src/pages/mine/mine.vue

@@ -83,7 +83,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, wxAuthLogin } from '@/utils/auth.js'
+import { isLoggedIn as checkLogin, getUserInfo as getStoredUserInfo, logout, checkLogin as requireLogin } from '@/utils/auth.js'
 
 const isLoggedIn = ref(false)
 const showPhoneAuth = ref(false)
@@ -138,88 +138,10 @@ const handleUserCardClick = () => {
 const handleLogin = async () => {
   console.log('[我的] 开始登录流程')
   
-  uni.showLoading({
-    title: '登录中...',
-    mask: true
+  // 跳转到登录页面
+  uni.navigateTo({
+    url: '/pages/login/login'
   })
-  
-  try {
-    // 获取微信登录code
-    const loginRes = await uni.login()
-    console.log('[我的] uni.login完整响应:', loginRes)
-    
-    if (!loginRes.code) {
-      throw new Error('获取微信登录code失败')
-    }
-    
-    // 获取手机号授权
-    uni.hideLoading()
-    
-    // 显示手机号授权按钮
-    showPhoneAuth.value = true
-    
-    // 触发手机号授权
-    uni.showModal({
-      title: '授权提示',
-      content: '需要授权手机号才能完成登录',
-      showCancel: false,
-      success: () => {
-        // 用户需要点击授权按钮
-      }
-    })
-  } catch (error) {
-    uni.hideLoading()
-    console.error('[我的] 登录失败:', error)
-    uni.showToast({
-      title: '登录失败,请重试',
-      icon: 'none'
-    })
-  }
-}
-
-/**
- * 处理手机号授权
- */
-const onGetPhoneNumber = async (e) => {
-  console.log('[我的] 获取手机号回调:', e.detail)
-  
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    const phoneCode = e.detail.code
-    console.log('[我的] phoneCode:', phoneCode)
-    
-    uni.showLoading({
-      title: '登录中...',
-      mask: true
-    })
-    
-    try {
-      const loginRes = await uni.login()
-      console.log('[我的] uni.login完整响应:', loginRes)
-      
-      if (!loginRes.code) {
-        throw new Error('获取微信登录code失败')
-      }
-      
-      const result = await wxAuthLogin(loginRes.code, phoneCode)
-      
-      uni.hideLoading()
-      
-      if (result) {
-        showPhoneAuth.value = false
-        loadUserInfo()
-      }
-    } catch (error) {
-      uni.hideLoading()
-      console.error('[我的] 登录失败:', error)
-    }
-  } else {
-    showPhoneAuth.value = false
-    uni.showToast({
-      title: '需要授权手机号才能完成登录',
-      icon: 'none',
-      duration: 2000
-    })
-  }
 }
 
 /**

+ 15 - 54
src/pages/pool/pool.vue

@@ -143,7 +143,7 @@
 <script setup>
 import { ref } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
-import { isLoggedIn as checkLoginStatus, wxAuthLogin } from '../../utils/auth.js'
+import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
 
 const isPurchased = ref(false)
 const showModal = ref(false)
@@ -188,8 +188,20 @@ const showPurchaseModal = () => {
   
   // 检查登录状态
   if (!checkLogin()) {
-    console.log('未登录,显示手机号授权弹窗')
-    showPhoneAuth.value = true
+    console.log('未登录,跳转到登录页')
+    uni.showModal({
+      title: '登录提示',
+      content: '此功能需要登录后使用,是否前往登录?',
+      confirmText: '去登录',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          uni.navigateTo({
+            url: '/pages/login/login'
+          })
+        }
+      }
+    })
     return
   }
   
@@ -197,57 +209,6 @@ const showPurchaseModal = () => {
   showModal.value = true
 }
 
-// 处理手机号授权并完成登录
-const onGetPhoneNumber = async (e) => {
-  console.log('[超短池] 获取手机号回调:', e.detail)
-  
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    const phoneCode = e.detail.code
-    console.log('[超短池] phoneCode:', phoneCode)
-    
-    uni.showLoading({
-      title: '登录中...',
-      mask: true
-    })
-    
-    try {
-      const loginRes = await uni.login()
-      console.log('[超短池] uni.login完整响应:', loginRes)
-      console.log('[超短池] 微信登录code:', loginRes.code)
-      
-      if (!loginRes.code) {
-        throw new Error('获取微信登录code失败')
-      }
-      
-      const result = await wxAuthLogin(loginRes.code, phoneCode)
-      
-      uni.hideLoading()
-      
-      if (result) {
-        showPhoneAuth.value = false
-        checkLogin()
-        // 登录成功后显示购买弹窗
-        showModal.value = true
-      }
-    } catch (error) {
-      uni.hideLoading()
-      console.error('[超短池] 登录失败:', error)
-    }
-  } else {
-    showPhoneAuth.value = false
-    uni.showToast({
-      title: '需要授权手机号才能完成登录',
-      icon: 'none',
-      duration: 2000
-    })
-  }
-}
-
-// 关闭手机号授权弹窗
-const closePhoneAuth = () => {
-  showPhoneAuth.value = false
-}
-
 // 关闭购买弹窗
 const closePurchaseModal = () => {
   showModal.value = false

+ 10 - 49
src/pages/rank/rank.vue

@@ -82,11 +82,10 @@
         <text class="prompt-title">登录后查看完整数据</text>
         <text class="prompt-desc">使用微信授权快速登录</text>
         
-        <!-- 微信授权登录按钮 -->
+        <!-- 跳转到登录页按钮 -->
         <button 
           class="login-button-native" 
-          open-type="getPhoneNumber" 
-          @getphonenumber="onGetPhoneNumber"
+          @click="goToLogin"
         >
           <text class="button-icon">📱</text>
           <text>微信授权登录</text>
@@ -99,7 +98,7 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { getUserPortfolio, getLeaderboard } from '../../utils/api.js'
-import { isLoggedIn as checkIsLoggedIn, wxAuthLogin } from '../../utils/auth.js'
+import { isLoggedIn as checkIsLoggedIn } from '../../utils/auth.js'
 
 // 登录状态
 const isLoggedIn = ref(false)
@@ -145,51 +144,6 @@ const getRankClass = (rank) => {
   return ''
 }
 
-// 处理手机号授权 - 静默登录
-const onGetPhoneNumber = async (e) => {
-  console.log('[模拟排名] 获取手机号回调:', e.detail)
-  
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    const phoneCode = e.detail.code
-    console.log('[模拟排名] phoneCode:', phoneCode)
-    
-    uni.showLoading({
-      title: '登录中...',
-      mask: true
-    })
-    
-    try {
-      // 获取微信登录code
-      const loginRes = await uni.login()
-      console.log('[模拟排名] uni.login完整响应:', loginRes)
-      console.log('[模拟排名] 微信登录code:', loginRes.code)
-      
-      if (!loginRes.code) {
-        throw new Error('获取微信登录code失败')
-      }
-      
-      // 调用后端登录接口(只传code和phoneCode)
-      const result = await wxAuthLogin(loginRes.code, phoneCode)      
-      uni.hideLoading()
-      
-      if (result) {
-        // 登录成功,重新加载数据
-        checkLoginAndLoadData()
-      }
-    } catch (error) {
-      uni.hideLoading()
-      console.error('[模拟排名] 登录失败:', error)
-    }
-  } else {
-    // 用户拒绝授权手机号
-    uni.showToast({
-      title: '需要授权手机号才能登录',
-      icon: 'none',
-      duration: 2000
-    })
-  }
-}
-
 // 检查登录状态并加载数据
 const checkLoginAndLoadData = () => {
   isLoggedIn.value = checkIsLoggedIn()
@@ -203,6 +157,13 @@ const checkLoginAndLoadData = () => {
   }
 }
 
+// 跳转到登录页
+const goToLogin = () => {
+  uni.navigateTo({
+    url: '/pages/login/login'
+  })
+}
+
 // 加载真实数据
 const loadRealData = async () => {
   try {

+ 9 - 49
src/pages/strong/strong.vue

@@ -98,11 +98,10 @@
         <text class="prompt-title">登录后进行模拟交易</text>
         <text class="prompt-desc">使用微信授权快速登录</text>
         
-        <!-- 微信授权登录按钮 -->
+        <!-- 跳转到登录页按钮 -->
         <button 
           class="login-button-native" 
-          open-type="getPhoneNumber" 
-          @getphonenumber="onGetPhoneNumber"
+          @click="goToLogin"
         >
           <text class="button-icon">📱</text>
           <text>微信授权登录</text>
@@ -197,7 +196,7 @@
 <script setup>
 import { ref, computed } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
-import { isLoggedIn as checkLoginStatus, wxAuthLogin } from '../../utils/auth.js'
+import { isLoggedIn as checkLoginStatus } from '../../utils/auth.js'
 
 // 登录状态
 const isLoggedIn = ref(false)
@@ -221,56 +220,17 @@ const stockList = ref([
 const selectedDate = ref('2025年11月20日')
 const showBuyModalFlag = ref(false)
 
-// 检查登录状态
+// 检查登录状态(不弹窗提示)
 const checkLogin = () => {
   isLoggedIn.value = checkLoginStatus()
   console.log('[强势池] 登录状态:', isLoggedIn.value)
 }
 
-// 处理手机号授权 - 静默登录
-const onGetPhoneNumber = async (e) => {
-  console.log('[强势池] 获取手机号回调:', e.detail)
-  
-  if (e.detail.errMsg === 'getPhoneNumber:ok') {
-    const phoneCode = e.detail.code
-    console.log('[强势池] phoneCode:', phoneCode)
-    
-    uni.showLoading({
-      title: '登录中...',
-      mask: true
-    })
-    
-    try {
-      // 获取微信登录code
-      const loginRes = await uni.login()
-      console.log('[强势池] uni.login完整响应:', loginRes)
-      console.log('[强势池] 微信登录code:', loginRes.code)
-      
-      if (!loginRes.code) {
-        throw new Error('获取微信登录code失败')
-      }
-      
-      // 调用后端登录接口(只传code和phoneCode)
-      const result = await wxAuthLogin(loginRes.code, phoneCode)
-      
-      uni.hideLoading()
-      
-      if (result) {
-        // 登录成功,重新检查登录状态
-        checkLogin()
-      }
-    } catch (error) {
-      uni.hideLoading()
-      console.error('[强势池] 登录失败:', error)
-    }
-  } else {
-    // 用户拒绝授权手机号
-    uni.showToast({
-      title: '需要授权手机号才能登录',
-      icon: 'none',
-      duration: 2000
-    })
-  }
+// 跳转到登录页
+const goToLogin = () => {
+  uni.navigateTo({
+    url: '/pages/login/login'
+  })
 }
 
 // 计算买入总金额

BIN
src/static/images/logo.png


+ 77 - 0
src/utils/api.js

@@ -104,6 +104,72 @@ export const wxLogin = (params) => {
   })
 }
 
+/**
+ * 第一步:微信静默登录接口(检查是否为老用户)
+ * @param {object} params - { loginCode }
+ * @returns {Promise} 返回 { isSign, token?, code? }
+ */
+export const wxSilentLoginApi = (params) => {
+  return request({
+    url: '/auth/sys/miniapp/custom/openid',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json'
+    },
+    data: params
+  })
+}
+
+/**
+ * 第二步:手机号授权登录接口(新用户验证手机号)
+ * @param {object} params - { loginCode, phoneCode, encryptedData, iv }
+ * @returns {Promise} 返回 { isSign, token?, openid?, unionid?, phoneNumber? }
+ */
+export const wxPhoneLoginApi = (params) => {
+  return request({
+    url: '/auth/sys/miniapp/custom/check',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json'
+    },
+    data: params
+  })
+}
+
+/**
+ * 第三步:完善用户信息接口(首次登录)
+ * @param {object} params - { openid, unionid, phoneNumber, nickname, avatarUrl, m?, actId? }
+ * @returns {Promise} 返回 { token }
+ */
+export const wxCompleteUserInfoApi = (params) => {
+  return request({
+    url: '/auth/sys/miniapp/custom/login',
+    method: 'POST',
+    header: {
+      'content-type': 'application/json'
+    },
+    data: params
+  })
+}
+
+/**
+ * 第四步:获取用户完整信息接口
+ * @returns {Promise} 返回用户信息
+ */
+export const getUserInfoApi = () => {
+  return request({
+    url: '/wd/miniapp-member/bizBigMember/getMemberInfoByToken',
+    method: 'GET'
+  })
+}
+
+/**
+ * 文件上传配置
+ */
+export const uploadFile = {
+  url: `${BASE_URL}/jeecg-boot/mg/sys/oss/file/upload`
+}
+
 /**
  * 获取用户信息接口
  * @returns {Promise} 返回用户信息
@@ -115,6 +181,17 @@ export const getUserInfo = () => {
   })
 }
 
+/**
+ * 记录登录日志接口
+ * @returns {Promise} 返回记录结果
+ */
+export const recordLoginLog = () => {
+  return request({
+    url: '/wd/miniapp-member/bizBigMember/loginlog',
+    method: 'POST'
+  })
+}
+
 /**
  * 更新用户资料
  * @param {object} data - 用户资料

+ 102 - 63
src/utils/auth.js

@@ -1,14 +1,20 @@
 /**
  * 认证工具类
  * 用于管理用户登录状态、token存储和登录检查
- * 使用微信官方最新登录方式:
- * 1. wx.login() 获取code
- * 2. button open-type="chooseAvatar" 获取头像
- * 3. input type="nickname" 获取昵称
- * 4. button open-type="getPhoneNumber" 获取手机号
+ * 
+ * 完整登录流程:
+ * 1. 老用户静默登录:wx.login() -> /auth/sys/miniapp/custom/openid
+ * 2. 新用户手机号授权:getPhoneNumber -> /auth/sys/miniapp/custom/check
+ * 3. 完善用户信息:头像+昵称 -> /auth/sys/miniapp/custom/login
+ * 4. 获取用户信息:token -> /wd/miniapp-member/bizBigMember/getMemberInfoByToken
  */
 
-import { wxLogin } from './api.js'
+import { 
+  wxSilentLoginApi, 
+  wxPhoneLoginApi, 
+  wxCompleteUserInfoApi,
+  getUserInfoApi 
+} from './api.js'
 
 const TOKEN_KEY = 'user_token'
 const USER_INFO_KEY = 'user_info'
@@ -77,82 +83,115 @@ export const logout = () => {
 }
 
 /**
- * 微信静默登录
- * 流程:
- * 1. 用户点击授权手机号按钮
- * 2. 调用 wx.login() 获取 code
- * 3. 获取手机号授权 phoneCode
- * 4. 将 code 和 phoneCode 发送到后端
- * 5. 后端通过 code 获取 openid 和 unionid
- * 6. 后端通过 phoneCode 获取手机号和用户信息(昵称、头像等)
- * 7. 后端保存所有信息到数据库并返回 token
+ * 第一步:微信静默登录(老用户)
+ * 调用 wx.login() 获取 code,发送到后端检查是否为老用户
  * 
- * @param {string} code - 微信登录code(通过wx.login获取)
- * @param {string} phoneCode - 手机号授权code(通过getPhoneNumber获取)
- * @returns {Promise<boolean>} 登录是否成功
+ * @param {string} loginCode - 微信登录code
+ * @returns {Promise<object>} 返回 { isSign, token?, code? }
  */
-export const wxAuthLogin = async (code, phoneCode) => {
+export const wxSilentLogin = async (loginCode) => {
   try {
-    console.log('[微信登录] ========== 开始登录 ==========')
-    console.log('[微信登录] 接收到的参数:')
-    console.log('  - code:', code)
-    console.log('  - phoneCode:', phoneCode)
+    console.log('[静默登录] 开始检查用户状态, code:', loginCode)
     
-    // 验证必需参数
-    if (!code) {
-      console.error('[微信登录] 错误:code为空!')
-      throw new Error('微信登录code为空,请重试')
-    }
+    const result = await wxSilentLoginApi({ loginCode })
+    
+    console.log('[静默登录] 后端响应:', result)
     
-    if (!phoneCode) {
-      console.error('[微信登录] 错误:phoneCode为空!')
-      throw new Error('手机号授权code为空,请重试')
+    // 老用户直接登录成功
+    if (result.code === 200 && result.data.isSign === 'true' && result.data.token) {
+      setToken(result.data.token)
+      console.log('[静默登录] 老用户登录成功')
+      
+      // 获取用户信息
+      await fetchAndSaveUserInfo()
     }
     
-    // 构建请求参数
-    const loginParams = {
-      code: code,              // 微信登录code,后端用于获取 openid 和 unionid
-      phoneCode: phoneCode     // 手机号授权code,后端用于获取手机号、昵称、头像等
+    return result.data
+  } catch (error) {
+    console.error('[静默登录] 失败:', error)
+    throw error
+  }
+}
+
+/**
+ * 第二步:手机号授权登录(新用户)
+ * 获取手机号授权后,发送到后端验证
+ * 
+ * @param {object} params - 包含 loginCode, phoneCode, encryptedData, iv
+ * @returns {Promise<object>} 返回 { isSign, token?, openid?, unionid?, phoneNumber? }
+ */
+export const wxPhoneLogin = async (params) => {
+  try {
+    console.log('[手机号登录] 开始验证手机号')
+    
+    const result = await wxPhoneLoginApi(params)
+    
+    console.log('[手机号登录] 后端响应:', result)
+    
+    // 已注册用户直接登录成功
+    if (result.code === 200 && result.data.isSign === 'true' && result.data.token) {
+      setToken(result.data.token)
+      console.log('[手机号登录] 已注册用户登录成功')
+      
+      // 获取用户信息
+      await fetchAndSaveUserInfo()
     }
-    console.log('[微信登录] 准备发送到后端的参数:', JSON.stringify(loginParams))
     
-    // 调用后端登录接口
-    const result = await wxLogin(loginParams)
+    return result.data
+  } catch (error) {
+    console.error('[手机号登录] 失败:', error)
+    throw error
+  }
+}
+
+/**
+ * 第三步:完善用户信息(首次登录)
+ * 提交完整的用户信息完成注册
+ * 
+ * @param {object} userInfo - 包含 openid, unionid, phoneNumber, nickname, avatarUrl, m?, actId?
+ * @returns {Promise<boolean>} 注册是否成功
+ */
+export const wxCompleteUserInfo = async (userInfo) => {
+  try {
+    console.log('[完善信息] 提交用户信息')
     
-    console.log('[微信登录] 后端响应:', result)
+    const result = await wxCompleteUserInfoApi(userInfo)
     
-    // 保存token和用户信息
-    if (result.code === 200 && result.data && result.data.token) {
+    console.log('[完善信息] 后端响应:', result)
+    
+    if (result.code === 200 && result.data.token) {
       setToken(result.data.token)
-      if (result.data.userInfo) {
-        setUserInfo(result.data.userInfo)
-      }
+      console.log('[完善信息] 注册成功')
       
-      console.log('[微信登录] 登录成功,token已保存')
+      // 获取用户信息
+      await fetchAndSaveUserInfo()
       
-      // 显示成功提示
-      uni.showToast({
-        title: '登录成功',
-        icon: 'success',
-        duration: 1500
-      })
+      // 记录登录日志(可选)
+      // await recordLoginLog()
       
       return true
     } else {
-      console.error('[微信登录] 后端返回格式错误:', result)
-      throw new Error(result.message || '登录返回数据格式错误')
+      throw new Error(result.message || '注册失败')
     }
   } catch (error) {
-    console.error('[微信登录] ========== 登录失败 ==========')
-    console.error('[微信登录] 错误详情:', error)
-    
-    uni.showToast({
-      title: error.message || '登录失败,请稍后重试',
-      icon: 'none',
-      duration: 2000
-    })
-    
-    return false
+    console.error('[完善信息] 失败:', error)
+    throw error
+  }
+}
+
+/**
+ * 获取并保存用户信息
+ * 使用 token 调用后端接口获取完整用户信息
+ */
+const fetchAndSaveUserInfo = async () => {
+  try {
+    const result = await getUserInfoApi()
+    if (result.code === 200 && result.data) {
+      setUserInfo(result.data)
+      console.log('[用户信息] 获取成功')
+    }
+  } catch (error) {
+    console.error('[用户信息] 获取失败:', error)
   }
 }
 

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff