Преглед на файлове

添加 useWx 方法,支持微信小程序登录等

icssoa преди 7 месеца
родител
ревизия
6c691c7653
променени са 5 файла, в които са добавени 292 реда и са изтрити 25 реда
  1. 9 0
      config/index.ts
  2. 4 3
      cool/hooks/index.ts
  3. 239 0
      cool/hooks/wx.ts
  4. 37 19
      pages/user/components/login/wx.uvue
  5. 3 3
      pages/user/login.uvue

+ 9 - 0
config/index.ts

@@ -8,6 +8,11 @@ export const isDev = process.env.NODE_ENV == "development";
 // 忽略 token 校验的接口路径
 export const ignoreTokens: string[] = [];
 
+// 微信配置
+type WxConfig = {
+	debug: boolean;
+};
+
 // 配置类型定义
 type Config = {
 	name: string; // 应用名称
@@ -19,6 +24,7 @@ type Config = {
 	showDarkButton: boolean; // 是否显示暗色模式切换按钮
 	isCustomTabBar: boolean; // 是否自定义 tabBar
 	backTop: boolean; // 是否显示回到顶部按钮
+	wx: WxConfig; // 微信配置
 };
 
 // 根据环境导出最终配置
@@ -30,6 +36,9 @@ export const config = {
 	showDarkButton: isMp() ? false : true,
 	isCustomTabBar: true,
 	backTop: true,
+	wx: {
+		debug: false
+	},
 	...(isDev ? dev() : prod())
 } as Config;
 

+ 4 - 3
cool/hooks/index.ts

@@ -1,5 +1,6 @@
-export * from "./refs";
-export * from "./pager";
-export * from "./long-press";
 export * from "./cache";
+export * from "./long-press";
+export * from "./pager";
 export * from "./parent";
+export * from "./refs";
+export * from "./wx";

+ 239 - 0
cool/hooks/wx.ts

@@ -0,0 +1,239 @@
+import { ref } from "vue";
+import { assign, getUrlParam, storage } from "../utils";
+import { service } from "../service";
+import { t } from "@/locale";
+import { config } from "@/config";
+
+// #ifdef H5
+import wx from "weixin-js-sdk";
+// #endif
+
+// 微信配置类型
+type WxConfig = {
+	appId: string;
+};
+
+// 微信相关功能封装类
+export class Wx {
+	// 微信登录code
+	code = ref("");
+
+	/**
+	 * 获取微信登录code
+	 */
+	async getCode(): Promise<string> {
+		return new Promise((resolve) => {
+			// #ifdef MP-WEIXIN
+			uni.login({
+				provider: "weixin",
+				success: (res) => {
+					this.code.value = res.code;
+					resolve(res.code);
+				}
+			});
+			// #endif
+
+			// #ifndef MP-WEIXIN
+			resolve("");
+			// #endif
+		});
+	}
+
+	// #ifdef H5
+	// 公众号配置
+	mpConfig: WxConfig = {
+		appId: ""
+	};
+
+	/**
+	 * 判断当前是否为微信浏览器
+	 */
+	isWxBrowser() {
+		const ua: string = window.navigator.userAgent.toLowerCase();
+		if (ua.match(/MicroMessenger/i) != null) {
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	/**
+	 * 获取公众号配置信息,并初始化微信JS-SDK
+	 */
+	getMpConfig() {
+		if (this.isWxBrowser()) {
+			service.user.comm
+				.wxMpConfig({
+					url: `${location.origin}${location.pathname}`
+				})
+				.then((res) => {
+					wx.config({
+						debug: config.wx.debug,
+						jsApiList: ["chooseWXPay"],
+						...res
+					});
+
+					// 合并配置到mpConfig
+					assign(this.mpConfig, res);
+				});
+		}
+	}
+
+	/**
+	 * 跳转到微信授权页面
+	 */
+	mpAuth() {
+		const { appId } = this.mpConfig;
+
+		const redirect_uri = encodeURIComponent(
+			`${location.origin}${location.pathname}#/pages/user/login`
+		);
+		const response_type = "code";
+		const scope = "snsapi_userinfo";
+		const state = "STATE";
+
+		const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=${response_type}&scope=${scope}&state=${state}#wechat_redirect`;
+
+		location.href = url;
+	}
+
+	/**
+	 * 公众号登录,获取code
+	 */
+	mpLogin() {
+		return new Promise((resolve) => {
+			const code = getUrlParam("code");
+			const mpCode = storage.get("mpCode");
+
+			// 去除url中的code参数,避免重复
+			const url = window.location.href.replace(/(\?[^#]*)#/, "#");
+			window.history.replaceState({}, "", url);
+
+			if (code != mpCode) {
+				storage.set("mpCode", code, 1000 * 60 * 5);
+				resolve(code);
+			} else {
+				resolve(null);
+			}
+		});
+	}
+
+	/**
+	 * 公众号微信支付
+	 * @param params 支付参数
+	 */
+	mpPay(params: wx.IchooseWXPay & { timeStamp: number }): Promise<void> {
+		return new Promise((resolve, reject) => {
+			if (!this.isWxBrowser()) {
+				return reject({ message: t("请在微信浏览器中打开") });
+			}
+
+			wx.chooseWXPay({
+				...params,
+				timestamp: params.timeStamp,
+				success() {
+					resolve();
+				},
+				complete(e: { errMsg: string }) {
+					switch (e.errMsg) {
+						case "chooseWXPay:cancel":
+							reject({ message: t("已取消支付") });
+							break;
+
+						default:
+							reject({ message: t("支付失败") });
+					}
+				}
+			});
+		});
+	}
+	// #endif
+
+	// #ifdef MP
+	/**
+	 * 小程序登录,获取用户信息和code
+	 */
+	miniLogin(): Promise<{
+		code: string;
+		iv: string;
+		encryptedData: string;
+		signature: string;
+		rawData: string;
+	}> {
+		return new Promise((resolve, reject) => {
+			// 兼容 Mac,Mac 端需用 getUserInfo
+			const k = uni.getDeviceInfo().platform === "mac" ? "getUserInfo" : "getUserProfile";
+
+			uni[k]({
+				lang: "zh_CN",
+				desc: t("授权信息仅用于用户登录"),
+				success: ({ iv, encryptedData, signature, rawData }) => {
+					const next = () => {
+						resolve({
+							iv,
+							encryptedData,
+							signature,
+							rawData,
+							code: this.code.value
+						});
+					};
+
+					// 检查登录状态是否过期
+					uni.checkSession({
+						success: () => {
+							next();
+						},
+						fail: () => {
+							this.getCode().then(() => {
+								next();
+							});
+						}
+					});
+				},
+				fail: (err) => {
+					console.error(`[useWx.miniLogin] error`, err);
+					this.getCode();
+
+					reject(t("登录授权失败"));
+				}
+			});
+		});
+	}
+
+	/**
+	 * 小程序微信支付
+	 * @param params 支付参数
+	 */
+	miniPay(params: any): Promise<void> {
+		return new Promise((resolve, reject) => {
+			uni.requestPayment({
+				provider: "wxpay",
+				...params,
+				success() {
+					resolve();
+				},
+				fail() {
+					reject(t("已取消支付"));
+				}
+			});
+		});
+	}
+	// #endif
+}
+
+/**
+ * useWx 钩子函数,后续可扩展
+ */
+export const useWx = (): Wx => {
+	const wx = new Wx();
+
+	onReady(() => {
+		wx.getCode();
+
+		// #ifdef H5
+		wx.getMpConfig();
+		// #endif
+	});
+
+	return wx;
+};

+ 37 - 19
pages/user/components/login/wx.uvue

@@ -74,7 +74,7 @@
 </template>
 
 <script setup lang="ts">
-import { service, upload, useStore, type Response } from "@/cool";
+import { parse, router, service, upload, useStore, useWx, type Response, type Token } from "@/cool";
 import { t } from "@/locale";
 import { useUi } from "@/uni_modules/cool-ui";
 import { reactive, ref } from "vue";
@@ -83,6 +83,7 @@ const emit = defineEmits(["success"]);
 
 const { user } = useStore();
 const ui = useUi();
+const wx = useWx();
 
 // 是否显示编辑
 const editVisible = ref(false);
@@ -142,6 +143,9 @@ async function editSave() {
 
 			// 关闭弹窗
 			editClose();
+
+			// 跳转首页
+			router.nextLogin();
 		})
 		.catch((err) => {
 			// 上传失败,提示错误信息
@@ -168,16 +172,38 @@ function onEditClose() {
 
 // 微信小程序登录
 async function miniLogin() {
-	await service.user.login
-		.mini({})
-		.then((res) => {
-			emit("success", res);
-		})
-		.catch((err) => {
-			ui.showToast({
-				message: (err as Response).message!
+	// #ifdef MP
+	ui.showLoading(t("登录中"));
+
+	await wx.miniLogin().then(async (data) => {
+		await service.user.login
+			.mini(data)
+			.then(async (res) => {
+				// 设置token
+				user.setToken(parse<Token>(res)!);
+
+				// 获取用户信息
+				await user.get();
+
+				// 是否首次注册,根据业务情况调整判断逻辑
+				if (user.info.nickName == "微信用户") {
+					// 打开编辑弹窗
+					editOpen();
+				} else {
+					// 跳转首页
+					router.nextLogin();
+				}
+			})
+			.catch((err) => {
+				ui.showToast({
+					message: (err as Response).message!
+				});
 			});
-		});
+	});
+
+	ui.hideLoading();
+
+	// #endif
 }
 
 // 微信APP登录
@@ -185,21 +211,13 @@ function appLogin() {}
 
 // 微信登录
 async function login() {
-	ui.showToast({
-		message: t("开发中,敬请期待")
-	});
-
-	return;
-
-	// #ifdef MP-WEIXIN
+	// #ifdef MP
 	miniLogin();
 	// #endif
 
 	// #ifdef APP
 	appLogin();
 	// #endif
-
-	emit("success");
 }
 
 defineExpose({

+ 3 - 3
pages/user/login.uvue

@@ -21,7 +21,7 @@
 			<login-phone :form="form" @success="toLogin"></login-phone>
 
 			<!-- 微信登录 -->
-			<login-wx :ref="refs.set('loginWx')" @success="toLogin"></login-wx>
+			<login-wx :ref="refs.set('loginWx')"></login-wx>
 
 			<!-- 协议 -->
 			<view class="mt-6 flex flex-row flex-wrap items-center justify-center">
@@ -94,9 +94,9 @@ const form = reactive<LoginForm>({
 const agree = ref(false);
 
 // 登录成功
-function toLogin(res: any) {
+async function toLogin(res: any) {
 	user.setToken(parse<Token>(res)!);
-	router.home();
+	router.nextLogin();
 }
 
 // 跳转文档