login.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. "use strict";
  2. const common_vendor = require("../../common/vendor.js");
  3. const utils_auth = require("../../utils/auth.js");
  4. const utils_api = require("../../utils/api.js");
  5. const UserInfoPopup = () => "../../components/UserInfoPopup.js";
  6. const _sfc_main = {
  7. components: {
  8. UserInfoPopup
  9. },
  10. data() {
  11. return {
  12. showOneClickLogin: true,
  13. // 是否显示一键登录按钮
  14. showPhoneAuth: false,
  15. // 是否显示手机号授权按钮(新用户第二步)
  16. agreedToTerms: false,
  17. // 是否同意协议
  18. loginCode: "",
  19. // 微信登录code
  20. tempUserData: null,
  21. // 临时存储的用户数据(openid, unionid等)
  22. tempUserProfile: null
  23. // 临时存储的用户头像昵称信息
  24. };
  25. },
  26. methods: {
  27. /**
  28. * 返回上一页
  29. */
  30. handleBack() {
  31. const pages = getCurrentPages();
  32. if (pages.length > 1) {
  33. common_vendor.index.navigateBack();
  34. } else {
  35. common_vendor.index.switchTab({
  36. url: "/pages/index/index"
  37. });
  38. }
  39. },
  40. /**
  41. * 第一步:微信一键登录(老用户静默登录)
  42. */
  43. async handleWxLogin() {
  44. if (!this.agreedToTerms) {
  45. common_vendor.index.showToast({
  46. title: "请先阅读并同意用户协议",
  47. icon: "none",
  48. duration: 2e3
  49. });
  50. return;
  51. }
  52. try {
  53. common_vendor.index.showLoading({ title: "登录中..." });
  54. const loginRes = await this.wxLoginAsync();
  55. this.loginCode = loginRes.code;
  56. console.log("[登录] 获取到微信code:", this.loginCode);
  57. const result = await utils_auth.wxSilentLogin(this.loginCode);
  58. common_vendor.index.hideLoading();
  59. if (result && result.isSign === "true") {
  60. console.log("[登录] 老用户登录成功");
  61. this.handleLoginSuccess();
  62. } else if (result && result.isSign === "false") {
  63. console.log("[登录] 新用户,需要先获取头像昵称");
  64. this.showOneClickLogin = false;
  65. this.showPhoneAuth = false;
  66. } else if (result && result.code === 103) {
  67. common_vendor.index.showModal({
  68. title: "账号异常",
  69. content: "您的账号已被禁用,如有疑问请联系客服",
  70. showCancel: false
  71. });
  72. } else {
  73. throw new Error("登录接口返回数据异常");
  74. }
  75. } catch (error) {
  76. common_vendor.index.hideLoading();
  77. console.error("[登录] 微信登录失败:", error);
  78. common_vendor.index.showToast({
  79. title: error.message || "登录失败,请重试",
  80. icon: "none",
  81. duration: 2e3
  82. });
  83. }
  84. },
  85. /**
  86. * 第二步:获取用户头像昵称(新用户)
  87. */
  88. handleGetUserProfile() {
  89. if (!this.agreedToTerms) {
  90. common_vendor.index.showToast({
  91. title: "请先阅读并同意用户协议",
  92. icon: "none",
  93. duration: 2e3
  94. });
  95. return;
  96. }
  97. console.log("[登录] 打开用户信息弹窗");
  98. this.$refs.userInfoPopup.open();
  99. },
  100. /**
  101. * 用户信息弹窗确认回调(新用户第二步完成后)
  102. */
  103. handleUserInfoConfirm(userInfo) {
  104. console.log("[登录] 用户信息已获取:", userInfo);
  105. this.tempUserProfile = {
  106. nickname: userInfo.nickname,
  107. avatarUrl: userInfo.avatarUrl,
  108. tempAvatarPath: userInfo.tempAvatarPath
  109. };
  110. this.showPhoneAuth = true;
  111. common_vendor.index.showToast({
  112. title: "请继续授权手机号",
  113. icon: "none",
  114. duration: 2e3
  115. });
  116. },
  117. /**
  118. * 第三步:获取手机号授权(新用户)
  119. */
  120. async handleGetPhoneNumber(e) {
  121. console.log("[登录] 手机号授权回调:", e);
  122. if (e.detail.errMsg !== "getPhoneNumber:ok") {
  123. if (e.detail.errMsg.includes("no permission")) {
  124. common_vendor.index.showModal({
  125. title: "权限不足",
  126. content: '获取手机号功能需要:\n1. 小程序企业认证"权限 \n2. 在真机上测试',
  127. showCancel: false
  128. });
  129. } else {
  130. common_vendor.index.showToast({
  131. title: "授权失败,请重试",
  132. icon: "none"
  133. });
  134. }
  135. return;
  136. }
  137. try {
  138. common_vendor.index.showLoading({ title: "验证中..." });
  139. const loginRes = await this.wxLoginAsync();
  140. const newLoginCode = loginRes.code;
  141. const params = {
  142. loginCode: newLoginCode,
  143. phoneCode: e.detail.code,
  144. encryptedData: e.detail.encryptedData,
  145. iv: e.detail.iv
  146. };
  147. console.log("[登录] 发送手机号验证请求");
  148. const result = await utils_auth.wxPhoneLogin(params);
  149. common_vendor.index.hideLoading();
  150. if (result && result.isSign === "false") {
  151. console.log("[登录] 需要完成注册");
  152. this.tempUserData = {
  153. openid: result.openid,
  154. unionid: result.unionid,
  155. phoneNumber: result.phoneNumber
  156. };
  157. await this.submitRegistration();
  158. } else if (result && result.isSign === "true") {
  159. console.log("[登录] 已注册用户,登录成功");
  160. this.handleLoginSuccess();
  161. } else {
  162. throw new Error("验证接口返回数据异常");
  163. }
  164. } catch (error) {
  165. common_vendor.index.hideLoading();
  166. console.error("[登录] 手机号验证失败:", error);
  167. let errorMsg = "验证失败,请重试";
  168. if (error.message) {
  169. if (error.message.includes("48001") || error.message.includes("未开通手机号快速验证组件")) {
  170. common_vendor.index.showModal({
  171. title: "权限未开通",
  172. content: '小程序未开通"手机号快速验证组件"权限\n\n请前往微信公众平台:\n开发 → 开发管理 → 接口设置\n开通"手机号快速验证组件"',
  173. showCancel: false
  174. });
  175. return;
  176. } else if (error.message.includes("40029") || error.message.includes("code无效")) {
  177. errorMsg = "phoneCode已失效,请重新授权";
  178. } else if (error.message.includes("40001") || error.message.includes("access_token")) {
  179. errorMsg = "access_token无效,请重试";
  180. } else if (error.message.includes("45011")) {
  181. errorMsg = "操作过于频繁,请稍后重试";
  182. } else {
  183. errorMsg = error.message;
  184. }
  185. }
  186. common_vendor.index.showToast({
  187. title: errorMsg,
  188. icon: "none",
  189. duration: 3e3
  190. });
  191. }
  192. },
  193. /**
  194. * 第四步:提交注册(新用户完成所有授权后)
  195. */
  196. async submitRegistration() {
  197. try {
  198. common_vendor.index.showLoading({ title: "注册中..." });
  199. const completeInfo = {
  200. openid: this.tempUserData.openid,
  201. unionid: this.tempUserData.unionid,
  202. phoneNumber: this.tempUserData.phoneNumber,
  203. nickname: this.tempUserProfile.nickname,
  204. avatarUrl: this.tempUserProfile.avatarUrl
  205. };
  206. console.log("[登录] 提交用户信息");
  207. await utils_auth.wxCompleteUserInfo(completeInfo);
  208. if (this.tempUserProfile.tempAvatarPath) {
  209. console.log("[登录] 开始上传头像到服务器");
  210. try {
  211. const uploadedUrl = await this.uploadAvatarWithToken(this.tempUserProfile.tempAvatarPath);
  212. console.log("[登录] 头像上传成功:", uploadedUrl);
  213. if (uploadedUrl) {
  214. const updateResult = await utils_api.updateUserProfile({ avatar: uploadedUrl });
  215. console.log("[登录] updateUserProfile返回:", updateResult);
  216. const userInfoLocal = common_vendor.index.getStorageSync("user_info");
  217. if (userInfoLocal) {
  218. const parsed = JSON.parse(userInfoLocal);
  219. parsed.avatar = uploadedUrl;
  220. common_vendor.index.setStorageSync("user_info", JSON.stringify(parsed));
  221. }
  222. }
  223. } catch (uploadErr) {
  224. console.warn("[登录] 头像上传失败:", uploadErr);
  225. }
  226. }
  227. common_vendor.index.hideLoading();
  228. console.log("[登录] 注册成功");
  229. this.handleLoginSuccess();
  230. } catch (error) {
  231. common_vendor.index.hideLoading();
  232. console.error("[登录] 注册失败:", error);
  233. common_vendor.index.showToast({
  234. title: error.message || "注册失败,请重试",
  235. icon: "none",
  236. duration: 2e3
  237. });
  238. }
  239. },
  240. /**
  241. * 带token上传头像
  242. */
  243. uploadAvatarWithToken(filePath) {
  244. return new Promise((resolve, reject) => {
  245. common_vendor.index.uploadFile({
  246. url: utils_api.uploadFile.url,
  247. filePath,
  248. name: "file",
  249. header: {
  250. "Authorization": `Bearer ${common_vendor.index.getStorageSync("user_token") || ""}`
  251. },
  252. success: (res) => {
  253. const data = JSON.parse(res.data);
  254. if (data.code === 200 && data.data && data.data.url) {
  255. resolve(data.data.url);
  256. } else {
  257. reject(new Error(data.message || "上传失败"));
  258. }
  259. },
  260. fail: (err) => {
  261. reject(err);
  262. }
  263. });
  264. });
  265. },
  266. /**
  267. * 登录成功后的处理
  268. */
  269. handleLoginSuccess() {
  270. common_vendor.index.showToast({
  271. title: "登录成功",
  272. icon: "success",
  273. duration: 1500
  274. });
  275. setTimeout(() => {
  276. const pages = getCurrentPages();
  277. if (pages.length > 1) {
  278. common_vendor.index.navigateBack();
  279. } else {
  280. common_vendor.index.switchTab({
  281. url: "/pages/index/index"
  282. });
  283. }
  284. }, 1500);
  285. },
  286. /**
  287. * 协议勾选变化
  288. */
  289. handleAgreementChange(e) {
  290. this.agreedToTerms = e.detail.value.length > 0;
  291. },
  292. /**
  293. * 显示协议内容
  294. */
  295. showAgreement(type) {
  296. const title = type === "user" ? "用户协议" : "隐私政策";
  297. common_vendor.index.showModal({
  298. title,
  299. content: "这里显示协议内容...",
  300. showCancel: false
  301. });
  302. },
  303. /**
  304. * 封装 wx.login 为 Promise
  305. */
  306. wxLoginAsync() {
  307. return new Promise((resolve, reject) => {
  308. common_vendor.index.login({
  309. provider: "weixin",
  310. success: (res) => {
  311. resolve(res);
  312. },
  313. fail: (err) => {
  314. reject(err);
  315. }
  316. });
  317. });
  318. }
  319. }
  320. };
  321. if (!Array) {
  322. const _component_user_info_popup = common_vendor.resolveComponent("user-info-popup");
  323. _component_user_info_popup();
  324. }
  325. function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
  326. return common_vendor.e({
  327. a: common_vendor.o((...args) => $options.handleBack && $options.handleBack(...args)),
  328. b: $data.showOneClickLogin
  329. }, $data.showOneClickLogin ? {
  330. c: common_vendor.o((...args) => $options.handleWxLogin && $options.handleWxLogin(...args))
  331. } : $data.showPhoneAuth ? {
  332. e: common_vendor.o((...args) => $options.handleGetPhoneNumber && $options.handleGetPhoneNumber(...args))
  333. } : {
  334. f: common_vendor.o((...args) => $options.handleGetUserProfile && $options.handleGetUserProfile(...args))
  335. }, {
  336. d: $data.showPhoneAuth,
  337. g: $data.agreedToTerms,
  338. h: common_vendor.o(($event) => $options.showAgreement("user")),
  339. i: common_vendor.o(($event) => $options.showAgreement("privacy")),
  340. j: common_vendor.o((...args) => $options.handleAgreementChange && $options.handleAgreementChange(...args)),
  341. k: common_vendor.sr("userInfoPopup", "cdfe2409-0"),
  342. l: common_vendor.o($options.handleUserInfoConfirm)
  343. });
  344. }
  345. 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"]]);
  346. wx.createPage(MiniProgramPage);