Bläddra i källkod

开发apple、line、谷歌登录

npzzwq99 1 vecka sedan
förälder
incheckning
2f98802d25

+ 255 - 0
features/passport/social-login.js

@@ -0,0 +1,255 @@
+// c:\Users\zhu\Desktop\Practice Project\new-88live1\features\passport\social-login.js
+/**
+ * 社交登录(Social Login)桥接层。
+ *
+ * 封装第三方平台登录逻辑,对页面层暴露统一接口:
+ *   - appleLogin    苹果登录
+ *
+ * 所有接口返回统一格式的会话数据:{ userinfo, statistics, token, ... }
+ */
+
+import { post } from '@/core/http/http-client'
+import { buildRestPath } from '@/core/config/api-paths'
+import { STORAGE } from '@/core/constants/storage-keys'
+//import { googleLogin as pluginGoogleLogin } from '@/uni_modules/uni-oauth-google'
+
+const ROUTE__LOGIN = buildRestPath('user/third')
+// 谷歌登录配置web client_id 335082713048-el1qosjevi6a8elp80rrq77798msfkij.apps.googleusercontent.com
+//const GOOGLE_SERVER_CLIENT_ID = '335082713048-el1qosjevi6a8elp80rrq77798msfkij.apps.googleusercontent.com'
+/**
+ * LINE 登录配置
+ */
+const LINE_CHANNEL_ID = '2010891313'
+const LINE_CALLBACK_URL = 'https://backend.amazewayhk.com/auth/line/callback'
+function readClientHandle() {
+	return uni.getStorageSync(STORAGE.chatClient) || null
+}
+
+/**
+ * 把不同后端响应形状归一为 store 期望的 `{ userinfo, statistics, ... }`。
+ */
+function unwrapSession(raw) {
+	if (!raw || typeof raw !== 'object') return null
+	if (raw.userinfo && typeof raw.userinfo === 'object') return raw
+	if (raw.data && typeof raw.data === 'object' && raw.data.userinfo) return raw.data
+	if (raw.token || raw.id) return { userinfo: raw }
+	return raw
+}
+
+/**
+ * 苹果登录(Sign in with Apple)
+ * 仅在 APP 端可用,H5 端不支持。
+ *
+ * @returns {Promise<Object>} 会话数据 { userinfo, token, ... }
+ */
+export function appleLogin() {
+	return new Promise((resolve, reject) => {
+		// #ifdef APP-PLUS
+		uni.login({
+			provider: 'apple',
+			success: async (loginRes) => {
+				try {
+					const appleInfo = loginRes.appleInfo
+					const identityToken = appleInfo.identityToken
+					const authorizationCode = appleInfo.authorizationCode
+					if (!identityToken && !authorizationCode) {
+						reject(new Error('Apple login: no token received'))
+						return
+					}
+					const { data } = await post(ROUTE__LOGIN, {
+						platform: 'apple',
+						loginData: loginRes,
+						client_id: readClientHandle()
+					})
+					resolve(unwrapSession(data))
+				} catch (err) {
+					reject(err)
+				}
+			},
+			fail: (err) => {
+				const errMsg = err.errMsg || err.errCode || 'Apple login failed'
+				reject(new Error(errMsg))
+			}
+		})
+		// #endif
+
+		// #ifndef APP-PLUS
+		reject(new Error('Apple login is only supported on APP'))
+		// #endif
+	})
+}
+
+/**
+ * 谷歌登录(Sign in with Google)
+ * 仅在 APP 端可用,H5 端不支持。
+ *
+ * @returns {Promise<Object>} 会话数据 { userinfo, token, ... }
+ */
+// export function googleLogin() {
+// 	return new Promise((resolve, reject) => {
+// 		// #ifdef APP-PLUS
+// 		pluginGoogleLogin({
+// 			serverClientId: GOOGLE_SERVER_CLIENT_ID,
+// 			success: (res) => {
+
+// 				const idToken = res.idToken
+// 				if (!idToken) {
+// 					console.error('[Google Login] 未获取到 idToken')
+// 					reject(new Error('Google login: no idToken received'))
+// 					return
+// 				}
+// 				// 发送到后端验证
+// 				post(ROUTE__LOGIN, {
+// 					platform: 'google',
+// 					idToken: idToken,
+// 					userInfo: {
+// 						googleId: res.openId,
+// 						email: res.email,
+// 						name: res.nickname,
+// 						avatar: res.headimgurl
+// 					},
+// 					client_id: readClientHandle()
+// 				}).then(({ data }) => {
+// 					resolve(unwrapSession(data))
+// 				}).catch(err => {
+
+// 					reject(err)
+// 				})
+// 			},
+// 			fail: (err) => {
+// 				const errMsg = err.errMsg || err.errDesc || `Google login failed (code: ${err.errCode})`
+// 				reject(new Error(errMsg))
+// 			},
+// 			complete: (res) => {
+// 				console.log('[Google Login] 登录流程完成', res)
+// 			}
+// 		})
+
+// 		// #endif
+// 		// #ifndef APP-PLUS
+// 		reject(new Error('Google login is only supported on APP'))
+// 		// #endif
+// 	})
+// }
+
+/**
+ * LINE 登录(WebView OAuth 流程)
+ * 兼容 iOS 和 Android 端
+ *
+ * @returns {Promise<Object>} 会话数据 { userinfo, token, ... }
+ */
+export function lineLogin() {
+	return new Promise((resolve, reject) => {
+		// #ifdef APP-PLUS
+
+		console.log('==================== [LINE Login] 开始 ====================')
+		console.log('[LINE Login] 时间:', new Date().toLocaleString())
+		console.log('[LINE Login] Channel ID:', LINE_CHANNEL_ID)
+		console.log('[LINE Login] Callback URL:', LINE_CALLBACK_URL)
+
+		const authUrl = `https://access.line.me/oauth2/v2.1/authorize?` +
+			`response_type=code` +
+			`&client_id=${LINE_CHANNEL_ID}` +
+			`&redirect_uri=${encodeURIComponent(LINE_CALLBACK_URL)}` +
+			`&state=line_login` +
+			`&scope=profile%20openid` +
+			`&bot_prompt=normal`
+
+		console.log('[LINE Login] 授权 URL:', authUrl)
+
+		// 创建 WebView(App 内部窗口,不是外部浏览器)
+		const wv = plus.webview.create(authUrl, 'line-auth', {
+			top: '44px',
+			left: '0px',
+			width: '100%',
+			height: '100%',
+			background: '#ffffff',
+			// WebView 配置
+			//javascriptEnabled: true,
+			//domStorageEnabled: true,
+			//mixedContentMode: 2,  // 允许混合内容
+			//userAgent: 'Mozilla/5.0 (Linux; Android 10; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.120 Mobile Safari/537.36'
+		})
+		// 设置 WebView 样式
+		wv.setStyle({
+			progress: { color: '#00C300' }  // LINE 绿色进度条
+		})
+		let hasResolved = false
+
+		
+		// 监听页面加载
+		wv.addEventListener('loaded', function () {
+			const url = wv.getURL()
+			console.log('[LINE Login] 页面加载:', url)
+
+			// 检查是否跳转到回调 URL 或包含 code 参数
+			if (url.indexOf(LINE_CALLBACK_URL) === 0 || url.indexOf('code=') > -1) {
+				if (hasResolved) return
+				hasResolved = true
+				// 解析 code
+				const code = extractCodeFromUrl(url)
+				// 关闭 WebView
+				wv.close()
+
+				if (!code) {
+					console.error('[LINE Login] ❌ 未获取到 authorization code')
+					console.error('[LINE Login] URL:', url)
+					reject(new Error('LINE login: no code received'))
+					return
+				}
+
+				console.log('[LINE Login] ✅ 获取到 code:', code)
+				console.log('[LINE Login] 准备调用后端')
+
+				// 发送到后端验证
+				post(ROUTE__LOGIN, {
+					platform: 'line',
+					authorizationCode: code,
+					client_id: readClientHandle()
+				}).then(({ data }) => {
+					console.log('[LINE Login] ✅ 后端接口返回成功')
+					console.log('[LINE Login] 后端返回数据:', JSON.stringify(data, null, 2))
+					console.log('==================== [LINE Login] 完成 ====================')
+					resolve(unwrapSession(data))
+				}).catch(err => {
+					console.error('[LINE Login] ❌ 后端请求失败', err)
+					reject(err)
+				})
+			}
+		})
+
+		// 监听错误
+		wv.addEventListener('error', function (error) {
+			console.log('[LINE Login] ❌ WebView 错误:', JSON.stringify(error))
+			console.log('[LINE Login] 错误详情:', error)
+			if (!hasResolved) {
+				hasResolved = true
+				wv.close()
+				reject(new Error('LINE login: WebView error'))
+			}
+		})
+
+		// 显示 WebView
+		wv.show()
+
+		// #endif
+
+		// #ifndef APP-PLUS
+		reject(new Error('LINE login is only supported on APP'))
+		// #endif
+	})
+}
+
+/**
+ * 从 URL 中提取 authorization code
+ */
+function extractCodeFromUrl(url) {
+	try {
+		const urlObj = new URL(url)
+		return urlObj.searchParams.get('code')
+	} catch (e) {
+		// 手动解析
+		const match = url.match(/[?&]code=([^&]+)/)
+		return match ? match[1] : null
+	}
+}

+ 10 - 1
locale/en.json

@@ -133,6 +133,15 @@
   "passport.modal-taken-content": "This number is already linked to an account. You can sign in directly.",
   "passport.modal-taken-confirm": "Sign in",
   "passport.modal-taken-cancel": "Cancel",
+  "passport.apple-login": "Sign in with Apple",
+  "passport.line-login": "Sign in with LINE",
+  "passport.account-login": "Account & Password",
+  "passport.social-logining": "Signing in...",
+  "passport.social-login-failed": "Social login failed",
+  "passport.apple-not-supported": "Apple login is only supported on APP",
+  "passport.line-not-supported": "LINE login coming soon",
+  "passport.google-login": "Sign in with Google",
+  "passport.google-not-supported": "Google login is only supported on Android",
   "user": {
     "loginRegister": "Login / Register",
     "memberCode": "Member Code",
@@ -1581,7 +1590,7 @@
   "details.waitingPlatformProcess": "Waiting for platform processing",
   "details.estimated24HoursReturn": "Estimated return within 24 hours",
   "details.contactPlatformServiceText": "Please contact platform service",
-    "details.recipient": "Recipient",
+  "details.recipient": "Recipient",
   "details.refundInitiated": "Refund request initiated",
   "details.sellerAgreed": "Seller has agreed to refund",
   "details.canModifyRefund": "You can modify refund request",

+ 10 - 1
locale/zh-Hans.json

@@ -133,6 +133,15 @@
   "passport.modal-taken-content": "该手机号已绑定账号,可直接登录",
   "passport.modal-taken-confirm": "去登录",
   "passport.modal-taken-cancel": "取消",
+  "passport.apple-login": "使用 Apple 登录",
+  "passport.line-login": "LINE登入",
+  "passport.account-login": "账号密码登入",
+  "passport.social-logining": "登录中...",
+  "passport.social-login-failed": "第三方登录失败",
+  "passport.apple-not-supported": "Apple 登录仅支持 APP 端",
+  "passport.line-not-supported": "LINE 登录功能开发中",
+  "passport.google-login": "使用 Google 登录",
+  "passport.google-not-supported": "Google 登录仅支持 Android 端",
   "user": {
     "loginRegister": "登录 / 注册",
     "memberCode": "会员码",
@@ -1563,4 +1572,4 @@
   "edit.maxRefundAmountNoFreight": "最多退款{price}元(不含运费{freight}元)",
   "edit.submitting": "提交中",
   "edit.modifyFailed": "修改失败"
-}
+}

+ 9 - 0
locale/zh-Hant.json

@@ -133,6 +133,15 @@
   "passport.modal-taken-content": "該手機號碼已綁定帳號,可直接登入",
   "passport.modal-taken-confirm": "去登入",
   "passport.modal-taken-cancel": "取消",
+  "passport.apple-login": "使用 Apple 登入",
+  "passport.line-login": "LINE登入",
+  "passport.account-login": "賬號密碼登入",
+  "passport.social-logining": "登入中...",
+  "passport.social-login-failed": "第三方登入失敗",
+  "passport.apple-not-supported": "Apple 登入僅支援 APP 端",
+  "passport.line-not-supported": "LINE 登入功能開發中",
+  "passport.google-login": "使用 Google 登入",
+  "passport.google-not-supported": "Google 登入僅支援 Android 端",
   "user": {
     "loginRegister": "登錄 / 註冊",
     "memberCode": "會員碼",

+ 20 - 1
manifest.json

@@ -22,12 +22,14 @@
             "VideoPlayer" : {},
             "Camera" : {},
             "Geolocation" : {},
-            "Payment" : {}
+            "Payment" : {},
+            "OAuth" : {}
         },
         /* 应用发布信息 */
         "distribute" : {
             /* android打包配置 */
             "android" : {
+                "packagename" : "uni.app.UNI05720DF",
                 "permissions" : [
                     "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
                     "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
@@ -87,6 +89,9 @@
                         "returnURL_ios" : "com.eoolive.hklive://paypalpay",
                         "returnURL_android" : "com.eoolive.hklive://paypalpay"
                     }
+                },
+                "oauth" : {
+                    "apple" : {}
                 }
             },
             "icons" : {
@@ -136,6 +141,20 @@
                     "pid" : "1711",
                     "parameters" : {}
                 }
+            },
+            "XTL-IAPKit" : {
+                "__plugin_info__" : {
+                    "name" : "iOS 内购插件",
+                    "description" : "原生代码实现iOS内购,只有两个方法简单操作",
+                    "platforms" : "iOS",
+                    "url" : "https://ext.dcloud.net.cn/plugin?id=16108",
+                    "android_package_name" : "",
+                    "ios_bundle_id" : "com.amazewayhk.zhibo",
+                    "isCloud" : true,
+                    "bought" : 1,
+                    "pid" : "16108",
+                    "parameters" : {}
+                }
             }
         }
     },

+ 240 - 165
pages/passport/lobby.vue

@@ -1,59 +1,69 @@
 <template>
-	<passport-screen :title="copy.title" :subtitle="copy.subtitle" :kicker="copy.kicker" :crumb="copy.crumb" :can-back="canBack">
-		<view class="pp-lobby__brand">
-			<view class="pp-lobby__logo">
-				<image class="pp-lobby__logo-img" :src="logoSrc" mode="aspectFit" />
-			</view>
-			<text class="pp-lobby__brand-name">{{ copy.appName }}</text>
-			<text class="pp-lobby__brand-tag">{{ copy.appTag }}</text>
+	<view class="pp-lobby">
+		<!-- 返回按钮 -->
+		<view class="pp-lobby__back" @tap="goBack">
+			<text class="wlIcon wlIcon-fanhui1"></text>
 		</view>
+		<!-- 背景图 -->
+		<image class="pp-lobby__bg" :src="bgSrc" mode="aspectFill" />
 
-		<view class="pp-lobby__entries">
-			<view
-				v-for="entry in entryList"
-				:key="entry.id"
-				class="pp-lobby-card"
-				:class="`pp-lobby-card--${entry.id}`"
-				@tap="onEntry(entry)"
-			>
-				<view class="pp-lobby-card__icon">{{ entry.icon }}</view>
-				<view class="pp-lobby-card__body">
-					<text class="pp-lobby-card__title">{{ entry.title }}</text>
-					<text class="pp-lobby-card__desc">{{ entry.desc }}</text>
-				</view>
-				<text class="pp-lobby-card__arrow">›</text>
+		<!-- 内容层 -->
+		<view class="pp-lobby__content">
+			<!-- 顶部 Logo -->
+			<view class="pp-lobby__header">
+				<image class="pp-lobby__logo" :src="logoSrc" mode="aspectFit" />
+				<text class="pp-lobby__app-name">{{ copy.appName }}</text>
 			</view>
-		</view>
 
-		<template #footer>
-			<view class="pp-lobby__bottom">
-				<text class="pp-lobby__tip">{{ copy.bottomTip }}</text>
-				<text class="pp-lobby__cta" @tap="goEnroll">{{ copy.enrollCta }}</text>
+			<!-- 底部操作区 -->
+			<view class="pp-lobby__actions">
+				<!-- iOS 苹果登录主按钮 -->
+				<!-- #ifdef APP-PLUS -->
+				<view v-if="isIOS" class="pp-lobby__apple-btn" @tap="onAppleLogin">
+					<image class="pp-lobby__icon" src="/static/imgs/Apple_logo.png" mode="aspectFit" />
+					<text class="pp-lobby_text">{{ copy.appleLogin }}</text>
+				</view>
+				<!-- Android Google 登录主按钮 -->
+				<view v-else class="pp-lobby__google-btn" @tap="onGoogleLogin">
+					<image class="pp-lobby_google_icon" src="/static/imgs/Google_logo.png" mode="aspectFit" />
+					<text class="pp-lobby_text">{{ copy.googleLogin }}</text>
+				</view>
+				<!-- #endif -->
+
+				<!-- 其他登录方式 -->
+				<view class="pp-lobby__others">
+					<text class="pp-lobby__other-link" @tap="onLineLogin">{{ copy.lineLogin }}</text>
+					<text class="pp-lobby__divider">|</text>
+					<text class="pp-lobby__other-link" @tap="goSignin">{{ copy.accountLogin }}</text>
+				</view>
 			</view>
-		</template>
-	</passport-screen>
+		</view>
+	</view>
 </template>
 
 <script>
-import PassportScreen from '@/components/passport-kit/passport-screen.vue'
+import { appleLogin, googleLogin, lineLogin } from '@/features/passport/social-login'
 import { fetchAppBrand } from '@/features/shell/app-config-gateway'
 
 const FALLBACK_LOGO = '/static/logo.png'
+const DEFAULT_BG = '/static/imgs/login-bg.png'
+
 
 export default {
-	components: { PassportScreen },
+	components: {},
 	data() {
 		return {
-			canBack: false,
+			isIOS: false, // 是否为 iOS 
 			logoPath: '',
-			appBrandName: ''
+			appBrandName: '',
+			bgPath: ''
 		}
 	},
 	computed: {
 		displayAppName() {
 			const fromApi = (this.appBrandName || '').trim()
 			if (fromApi) return fromApi
-			return this.$t('passport.app-name')
+			return '88Live'
 		},
 		logoSrc() {
 			const raw = (this.logoPath || '').trim()
@@ -63,60 +73,37 @@ export default {
 			}
 			return raw
 		},
+		bgSrc() {
+			const raw = (this.bgPath || '').trim()
+			if (!raw) return DEFAULT_BG
+			if (this.$appKit && typeof this.$appKit.oss === 'function') {
+				return this.$appKit.oss(raw, 750, 1334)
+			}
+			return raw
+		},
 		copy() {
 			const t = this.$t.bind(this)
-			const name = this.displayAppName
 			return {
-				kicker: t('passport.kicker-welcome'),
-				crumb: t('passport.crumb-signin'),
-				title: t('passport.title-lobby', { name }),
-				subtitle: t('passport.subtitle-lobby'),
-				appName: name,
-				appTag: t('passport.app-tag'),
-				enrollCta: t('passport.foot-go-reg-cta'),
-				bottomTip: t('passport.foot-no-account')
+				appName: this.displayAppName,
+				appleLogin: t('passport.apple-login'),
+				googleLogin: t('passport.google-login'),
+				lineLogin: t('passport.line-login'),
+				accountLogin: t('passport.account-login')
 			}
-		},
-		entryList() {
-			const t = this.$t.bind(this)
-			return [
-				{
-					id: 'passcode',
-					icon: '✦',
-					title: t('passport.entry-passcode-title'),
-					desc: t('passport.entry-passcode-desc'),
-					target: '/pages/passport/signin?mode=passcode'
-				},
-				{
-					id: 'secret',
-					icon: '✱',
-					title: t('passport.entry-secret-title'),
-					desc: t('passport.entry-secret-desc'),
-					target: '/pages/passport/signin?mode=secret'
-				},
-				{
-					id: 'enroll',
-					icon: '✚',
-					title: t('passport.entry-enroll-title'),
-					desc: t('passport.entry-enroll-desc'),
-					target: '/pages/passport/enroll'
-				}
-			]
 		}
 	},
 	onLoad() {
-		this.canBack = getCurrentPages().length > 1
+		// #ifdef APP-PLUS
+		this.isIOS = uni.getSystemInfoSync().platform === 'ios'
+		// #endif
 		this.loadBrand()
 	},
 	onBackPress() {
-		if (!this.canBack) {
-			uni.switchTab({
-				url: '/pages/index/index',
-				fail: () => uni.reLaunch({ url: '/pages/index/index' })
-			})
-			return true
-		}
-		return false
+		uni.switchTab({
+			url: '/pages/index/index',
+			fail: () => uni.reLaunch({ url: '/pages/index/index' })
+		})
+		return true
 	},
 	methods: {
 		async loadBrand() {
@@ -124,145 +111,233 @@ export default {
 				const brand = await fetchAppBrand()
 				this.logoPath = brand.logo || ''
 				this.appBrandName = brand.name || ''
+				this.bgPath = brand.login_bg || ''
 			} catch (_) {
 				this.logoPath = ''
 				this.appBrandName = ''
+				this.bgPath = ''
+			}
+		},
+		async onAppleLogin() {
+			try {
+				// #ifdef APP-PLUS
+				const result = await appleLogin()
+				if (result) {
+					this.$store.dispatch('user/login', result)
+					this.$store.dispatch('cart/sync')
+					const nickname = result.userinfo.nickname
+					const toastTitle = nickname || this.$t('passport.tip-signin-ok')
+					uni.showToast({ title: toastTitle, icon: 'success' ,duration: 2000})
+					this.bounce()
+				}
+				// #endif
+				// #ifndef APP-PLUS
+				uni.showToast({ title: this.$t('passport.apple-not-supported'), icon: 'none' })
+				// #endif
+			} catch (err) {
+				uni.showToast({ title: (err && err.message) || this.$t('passport.social-login-failed'), icon: 'none' })
+			} finally {
+				uni.hideLoading()
+			}
+		},
+		// async onGoogleLogin() {
+		// 	try {
+		// 		// #ifdef APP-PLUS
+		// 		const result = await googleLogin()
+		// 		if (result) {
+		// 			this.$store.dispatch('user/login', result)
+		// 			this.$store.dispatch('cart/sync')
+		// 			uni.showToast({ title: this.$t('passport.tip-signin-ok'), icon: 'success' })
+		// 			this.bounce()
+		// 		}
+		// 		// #endif
+		// 		// #ifndef APP-PLUS
+		// 		uni.showToast({ title: this.$t('passport.google-not-supported'), icon: 'none' })
+		// 		// #endif
+		// 	} catch (err) {
+		// 		uni.showToast({ title: (err && err.message) || this.$t('passport.social-login-failed'), icon: 'none' })
+		// 	} finally {
+		// 	}
+		// },
+		goBack() {
+			uni.navigateBack({
+				fail: () => {
+					uni.switchTab({ url: '/pages/index/index' })
+				}
+			})
+		},
+		async onLineLogin() {
+			try {
+				// #ifdef APP-PLUS
+				const result = await lineLogin()
+				// WebView 打开后立即关闭 loading
+				if (result) {
+					this.$store.dispatch('user/login', result)
+					this.$store.dispatch('cart/sync')
+					uni.showToast({ title: this.$t('passport.tip-signin-ok'), icon: 'success' })
+					this.bounce()
+				}
+				// #endif
+				// #ifndef APP-PLUS
+				uni.showToast({ title: this.$t('passport.line-not-supported'), icon: 'none' })
+				// #endif
+			} catch (err) {
+				console.log('[LINE Login] ❌ 登录失败错误信息:', err)
+				console.log('[LINE Login] ❌ 登录失败标题:', err.message)
+
+				uni.showToast({ title: (err && err.message) || this.$t('passport.social-login-failed'), icon: 'none' })
 			}
 		},
-		onEntry(entry) {
-			uni.navigateTo({ url: entry.target })
+		goSignin() {
+			uni.navigateTo({ url: '/pages/passport/signin' })
 		},
-		goEnroll() {
-			uni.navigateTo({ url: '/pages/passport/enroll' })
+		bounce() {
+			const fallback = '/pages/index/index'
+			uni.switchTab({ url: fallback })
 		}
 	}
 }
 </script>
 
 <style lang="scss">
-.pp-lobby__brand {
+.pp-lobby {
+	position: relative;
+	width: 100%;
+	height: 100vh;
+	overflow: hidden;
+}
+
+.pp-lobby__bg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 1;
+}
+
+.pp-lobby__content {
+	position: relative;
+	z-index: 2;
+	width: 100%;
+	height: 100%;
 	display: flex;
 	flex-direction: column;
-	align-items: center;
-	padding-bottom: 28rpx;
+	justify-content: space-between;
+	padding: 120rpx 60rpx 160rpx;
+	box-sizing: border-box;
 }
 
-.pp-lobby__logo {
-	width: 160rpx;
-	height: 160rpx;
-	border-radius: 48rpx;
-	background: linear-gradient(135deg, #3d5afe 0%, #ff4d6a 100%);
+/* 返回按钮 */
+.pp-lobby__back {
+	position: absolute;
+	top: 80rpx;
+	left: 40rpx;
+	z-index: 10;
+	width: 72rpx;
+	height: 72rpx;
+	border-radius: 50%;
+	background: rgba(0, 0, 0, 0.3);
+	backdrop-filter: blur(10rpx);
 	display: flex;
 	align-items: center;
 	justify-content: center;
-	box-shadow: 0 20rpx 60rpx rgba(61, 90, 254, 0.45);
 }
 
-.pp-lobby__logo-img {
-	width: 104rpx;
-	height: 104rpx;
-	border-radius: 32rpx;
+/* 顶部 Logo 区域 */
+.pp-lobby__header {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	padding-top: 80rpx;
 }
 
-.pp-lobby__brand-name {
+.pp-lobby__logo {
+	width: 160rpx;
+	height: 160rpx;
+	border-radius: 40rpx;
+}
+
+.pp-lobby__app-name {
 	font-size: 48rpx;
 	font-weight: 800;
-	color: #fff;
+	color: #060606;
 	margin-top: 24rpx;
-	letter-spacing: 3rpx;
-}
-
-.pp-lobby__brand-tag {
-	font-size: 24rpx;
-	color: rgba(255, 255, 255, 0.5);
-	margin-top: 8rpx;
+	text-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.3);
 	letter-spacing: 2rpx;
 }
 
-.pp-lobby__entries {
-	margin-top: 28rpx;
-}
-
-.pp-lobby-card {
+/* 底部操作区 */
+.pp-lobby__actions {
 	display: flex;
+	flex-direction: column;
 	align-items: center;
-	padding: 28rpx 28rpx;
-	border-radius: 26rpx;
-	margin-bottom: 22rpx;
-	background: rgba(255, 255, 255, 0.08);
-	border: 1rpx solid rgba(255, 255, 255, 0.15);
-}
-
-.pp-lobby-card--passcode {
-	background: linear-gradient(120deg, rgba(255, 181, 71, 0.22), rgba(255, 77, 106, 0.18));
-	border-color: rgba(255, 181, 71, 0.35);
-}
-
-.pp-lobby-card--secret {
-	background: linear-gradient(120deg, rgba(61, 90, 254, 0.32), rgba(125, 60, 252, 0.2));
-	border-color: rgba(61, 90, 254, 0.4);
-}
-
-.pp-lobby-card--enroll {
-	background: linear-gradient(120deg, rgba(37, 220, 132, 0.2), rgba(255, 181, 71, 0.22));
-	border-color: rgba(37, 220, 132, 0.4);
+	gap: 40rpx;
 }
 
-.pp-lobby-card__icon {
-	width: 92rpx;
-	height: 92rpx;
-	border-radius: 28rpx;
-	background: rgba(255, 255, 255, 0.18);
-	font-size: 40rpx;
-	color: #fff;
+/* 苹果登录主按钮 */
+.pp-lobby__apple-btn {
+	width: 100%;
+	height: 88rpx;
+	background: #FFFFFF;
+	border-radius: 12rpx;
 	display: flex;
 	align-items: center;
 	justify-content: center;
-	margin-right: 24rpx;
+	gap: 16rpx;
+	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
 }
 
-.pp-lobby-card__body {
-	flex: 1;
+.pp-lobby__icon {
+	width: 40rpx;
+	height: 40rpx;
 }
 
-.pp-lobby-card__title {
-	display: block;
-	font-size: 30rpx;
-	color: #fff;
-	font-weight: 700;
+.pp-lobby_google_icon {
+	margin-right: 20rpx;
+	width: 34rpx;
+	height: 34rpx;
 }
 
-.pp-lobby-card__desc {
-	display: block;
-	font-size: 22rpx;
-	color: rgba(255, 255, 255, 0.7);
-	margin-top: 8rpx;
+.pp-lobby_text {
+	font-size: 32rpx;
+	font-weight: 500;
+	color: #000000;
+	letter-spacing: 0.5rpx;
 }
 
-.pp-lobby-card__arrow {
-	color: rgba(255, 255, 255, 0.6);
-	font-size: 40rpx;
-	font-weight: 200;
+
+
+// Google 登录按钮
+.pp-lobby__google-btn {
+	width: 100%;
+	height: 88rpx;
+	background: #FFFFFF;
+	border-radius: 12rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	gap: 16rpx;
+	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
 }
 
-.pp-lobby__bottom {
+/* 其他登录方式 */
+.pp-lobby__others {
 	display: flex;
 	align-items: center;
 	justify-content: center;
+	gap: 32rpx;
 }
 
-.pp-lobby__tip {
-	color: rgba(255, 255, 255, 0.55);
-	font-size: 24rpx;
+.pp-lobby__other-link {
+	font-size: 28rpx;
+	color: rgba(255, 255, 255, 0.9);
+	text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
+	padding: 12rpx 24rpx;
 }
 
-.pp-lobby__cta {
-	color: #ffb547;
-	font-size: 26rpx;
-	font-weight: 700;
-	margin-left: 12rpx;
-	padding: 10rpx 16rpx;
-	border-radius: 24rpx;
-	background: rgba(255, 181, 71, 0.16);
+.pp-lobby__divider {
+	font-size: 30rpx;
+	color: rgba(255, 255, 255, 0.5);
 }
-</style>
+</style>

BIN
static/imgs/Apple_logo.png


BIN
static/imgs/Google_logo.png


BIN
static/imgs/login-bg.png


+ 6 - 0
uni_modules/uni-oauth-google/changelog.md

@@ -0,0 +1,6 @@
+## 1.1.1(2025-09-30)
++ filterByAuthorizedAccounts 默认修改为false,避免部分场景下登录失败
+## 1.1.0(2025-03-05)
+基于Credential Manager实现Google登录
+## 1.0.0(2025-02-27)
+实现Google登录/登出 功能

+ 106 - 0
uni_modules/uni-oauth-google/package.json

@@ -0,0 +1,106 @@
+{
+  "id": "uni-oauth-google",
+  "displayName": "Google登录插件",
+  "version": "1.1.1",
+  "description": "Google登录插件,基于Credential Manager实现",
+  "keywords": [
+    "uni-oauth-google"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^4.43",
+    "uni-app": "^4.75",
+    "uni-app-x": "^4.75"
+  },
+  "dcloudext": {
+    "type": "uts",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "插件不采集任何数据",
+      "permissions": "无"
+    },
+    "npmurl": "",
+    "darkmode": "x",
+    "i18n": "x",
+    "widescreen": "x"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "√",
+        "aliyun": "√",
+        "alipay": "√"
+      },
+      "client": {
+        "uni-app": {
+          "vue": {
+            "vue2": {
+                "extVersion": "1.0.0",
+                "minVersion": ""
+            },
+            "vue3": {
+                "extVersion": "1.0.0",
+                "minVersion": ""
+            }
+          },
+          "web": {
+            "safari": "x",
+            "chrome": "x"
+          },
+          "app": {
+            "vue": "x",
+            "nvue": "x",
+            "android": {
+                "extVersion": "",
+                "minVersion": "21"
+            },
+            "ios": "x",
+            "harmony": "x"
+          },
+          "mp": {
+            "weixin": "x",
+            "alipay": "x",
+            "toutiao": "x",
+            "baidu": "x",
+            "kuaishou": "x",
+            "jd": "x",
+            "harmony": "x",
+            "qq": "x",
+            "lark": "x"
+          },
+          "quickapp": {
+            "huawei": "x",
+            "union": "x"
+          }
+        },
+        "uni-app-x": {
+          "web": {
+            "safari": "-",
+            "chrome": "-"
+          },
+          "app": {
+            "android": "-",
+            "ios": "-",
+            "harmony": "-"
+          },
+          "mp": {
+            "weixin": "-"
+          }
+        }
+      }
+    }
+  }
+}

+ 157 - 0
uni_modules/uni-oauth-google/readme.md

@@ -0,0 +1,157 @@
+# uni-oauth-google
+
+## 使用说明
+
+#### 关于 Credential Manager
+
+[Credential Manager](https://developer.android.com/identity/sign-in/credential-manager?hl=zh-cn) 是Android 最新的登录鉴权方式.本插件基于`Credential Manager`实现了Google登录功能
+
+#### 创建 google cloud 应用
+
+在[google云管理后台](https://console.cloud.google.com/),创建应用,并创建`OAuth 客户端 ID`,重点确保填写正确的包名和证书签名。
+
+创建完成后,保存`客户端ID`用于代码调用。`客户端ID`的格式:`xxxxx.apps.googleusercontent.com`
+
+
+
+#### 调用代码
+
+以正确的包名和证书自定义基座后,可以使用下面的代码进行调用
+
+uvue:
+
+```vue
+<template>
+	<view>
+		<button @tap="loginClick" >google登录</button>
+		<button @tap="logoutClick" >google登出</button>
+	</view>
+</template>
+
+<script>
+	import { googleLogin, GoogleLoginOptions } from "@/uni_modules/uni-oauth-google"
+	import { googleLogout, GoogleLogoutOptions } from "@/uni_modules/uni-oauth-google"
+	
+	export default {
+		data() {
+			return {
+			}
+		},
+		methods: {
+			loginClick:()=>{
+				let options = {
+					serverClientId:"xxxxx.apps.googleusercontent.com",
+					success: (res : any) => {
+					  console.log("success",res)
+					},
+					fail: (res : any) => {
+					  console.log("fail",res)
+					},
+					complete: (res : any) => {
+						console.log("complete",res)
+					}
+				} as GoogleLoginOptions;
+				googleLogin(options);
+			},
+			logoutClick:()=>{
+				let options = {
+				  success: (res : any) => {
+				    console.log("success",res)
+				  },
+				  fail: (res : any) => {
+				    console.log("fail",res)
+				  },
+				  complete: (res : any|null) => {
+				  	console.log("complete",res)
+				  }
+				} as GoogleLogoutOptions;
+				
+				googleLogout(options);
+				
+				
+			}
+		}
+	}
+</script>
+
+<style>
+	
+</style>
+
+
+```
+
+vue:
+
+```vue
+<template>
+	<view>
+		<button @tap="loginClick" >google登录</button>
+		<button @tap="logoutClick" >google登出</button>
+	</view>
+</template>
+
+<script>
+	import { googleLogin } from "@/uni_modules/uni-oauth-google"
+	import { googleLogout } from "@/uni_modules/uni-oauth-google"
+	
+	
+	export default {
+		data() {
+			return {
+			}
+		},
+		methods: {
+			
+			loginClick:()=>{
+				
+				let options = {
+					serverClientId:"xxxxxxxx.apps.googleusercontent.com",
+					success: (res) => {
+					  console.log("success",res)
+					},
+					fail: (res) => {
+					  console.log("fail",res)
+					},
+					complete: (res) => {
+						console.log("complete",res)
+					}
+				} 
+				googleLogin(options);
+			},
+			logoutClick:()=>{
+				
+				let options = {
+				  success: () => {
+				    console.log("success")
+				  },
+				  fail: (res) => {
+				    console.log("fail",res)
+				  },
+				  complete: (res) => {
+				  	console.log("complete",res)
+				  }
+				}
+				googleLogout(options);
+			},
+		}
+	}
+</script>
+
+<style>
+
+</style>
+
+
+```
+
+#### 错误码说明
+
+|代码|说明|
+|:--|:--|
+|9010001|系统环境错误,请检查application/activity状态|
+|9010002|用户参数配置错误,重点检查serverClientId是否正确|
+|9010003|暂不支持的登录凭据|
+|9010004|无法找到对应的凭据,可能是应用签名/包名不匹配或者与Google连接失败|
+|9010005|登录组件缺失,请检查系统是否正确安装GMS|
+|9010000|其他未知错误|

+ 107 - 0
uni_modules/uni-oauth-google/utssdk/app-android/KotlinCode.kt

@@ -0,0 +1,107 @@
+package uts.sdk.modules.uniOauthGoogle
+
+import io.dcloud.uts.UTSAndroid
+import io.dcloud.uts.console
+
+import android.content.Context
+import android.os.SystemClock
+import android.util.Base64
+import android.util.Log
+import androidx.credentials.ClearCredentialStateRequest
+import androidx.credentials.Credential
+import androidx.credentials.CredentialManager
+import androidx.credentials.GetCredentialRequest
+import androidx.credentials.GetCredentialResponse
+import com.google.android.libraries.identity.googleid.GetGoogleIdOption
+import com.google.android.libraries.identity.googleid.GoogleIdTokenCredential
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.json.JSONObject
+
+object NativeCode {
+	/**
+	 * credentialManager 要求必须在协程环境进行调用
+	 */
+	private val coroutineScope = CoroutineScope(Dispatchers.IO)
+	
+	fun requestLogout(credentialManager:CredentialManager,options:CallKotlinLogOutOptions){
+	
+		val request = ClearCredentialStateRequest()
+	
+		coroutineScope.launch {
+			try {
+				credentialManager.clearCredentialState(request)
+				options.complete(null)
+			} catch (e: Exception) {
+			   options.complete(e)
+			}
+		}
+		
+	}
+	
+    fun requestLogin(credentialManager:CredentialManager,request:GetCredentialRequest,options:CallKotlinLoginOptions){
+        coroutineScope.launch {
+			try {
+				val result = credentialManager.getCredential(
+					context = UTSAndroid.getUniActivity()!!,
+					request = request
+				)
+				val userInfo = handleSignIn(result)
+				if(userInfo == null){
+					options.fail(null)
+				}else{
+					options.success(userInfo)
+				}
+			} catch (e : Exception) {
+				options.fail(e)
+			}
+		}
+    }
+	
+	fun handleFailure(e:Exception){
+		console.log(e)
+	}
+
+	fun handleSignIn(result: GetCredentialResponse): GoogleLoginSuccess? {
+		when (val credential = result.credential) {
+			is GoogleIdTokenCredential -> {
+				val idToken = credential.idToken
+				// 验证 Token 并获取用户信息
+				val user = getUserFromToken(idToken)
+				// 更新 UI 或发送到服务器
+				return user
+			}
+			else -> {
+				// 处理其他类型的凭证(如密码)
+				return null
+			}
+		}
+	}
+
+	fun getUserFromToken(idToken: String): GoogleLoginSuccess? {
+		val parts = idToken.split(".")
+		if (parts.size != 3) return null
+
+		return try {
+			val payload = String(Base64.decode(parts[1], Base64.URL_SAFE), Charsets.UTF_8)
+			val json = JSONObject(payload)
+			
+			GoogleLoginSuccess(
+				idToken = idToken,
+				openId = json.optString("sub"),
+				nickname = json.optString("name"),
+				email = json.optString("email"),
+				headimgurl = json.optString("picture")
+			)
+		} catch (e: Exception) {
+			null
+		}
+	}
+	
+	
+}
+
+
+
+

+ 8 - 0
uni_modules/uni-oauth-google/utssdk/app-android/config.json

@@ -0,0 +1,8 @@
+{
+  "minSdkVersion": "21",
+  "dependencies": [
+		"androidx.credentials:credentials:1.3.0",
+		"androidx.credentials:credentials-play-services-auth:1.3.0",
+		"com.google.android.libraries.identity.googleid:googleid:1.1.1"
+	]
+}

+ 150 - 0
uni_modules/uni-oauth-google/utssdk/app-android/index.uts

@@ -0,0 +1,150 @@
+/* 引入 interface.uts 文件中定义的变量 */
+import { GoogleLoginOptions, GoogleLogin, GoogleLogoutOptions, GoogleLogout, GoogleLoginSuccess } from '../interface.uts';
+/* 引入 unierror.uts 文件中定义的变量 */
+import { GoogleLoginFailImpl } from '../unierror';
+
+import CoroutineScope from 'kotlinx.coroutines.CoroutineScope';
+import Dispatchers from 'kotlinx.coroutines.Dispatchers';
+import launch from 'kotlinx.coroutines.launch';
+
+import SystemClock from 'android.os.SystemClock';
+import Base64 from 'android.util.Base64';
+import CredentialManager from 'androidx.credentials.CredentialManager';
+import ClearCredentialStateRequest from 'androidx.credentials.ClearCredentialStateRequest';
+import Credential from 'androidx.credentials.Credential';
+import GetCredentialRequest from 'androidx.credentials.GetCredentialRequest';
+import GetCredentialResponse from 'androidx.credentials.GetCredentialResponse';
+import GetGoogleIdOption from 'com.google.android.libraries.identity.googleid.GetGoogleIdOption';
+import GoogleIdTokenCredential from 'com.google.android.libraries.identity.googleid.GoogleIdTokenCredential';
+
+export type CallKotlinLoginOptions = {
+	success : (data : GoogleLoginSuccess | null) => void
+	fail : (ex : Exception | null) => void
+}
+
+export type CallKotlinLogOutOptions = {
+	complete : (ex : Exception | null) => void
+}
+
+/**
+ * google logout logic
+ */
+export const googleLogout : GoogleLogout = function (option : GoogleLogoutOptions) {
+
+	let options = {
+		complete: (ex : Exception | null) => {
+			console.log("fail", ex)
+			if(ex == null){
+				// 成功
+				option.success?.("")
+				option.complete?.("")
+				option.success = null
+				option.complete = null
+			}else{
+				// 未知错误
+				const err = new GoogleLoginFailImpl(9010099);
+				option.fail?.(err)
+				option.complete?.(err)
+				option.fail = null;
+				option.complete = null;
+			}
+		},
+	} as CallKotlinLogOutOptions
+	
+	let credentialManager = CredentialManager.create(UTSAndroid.getUniActivity()!)
+	NativeCode.requestLogout(credentialManager, options);
+
+}
+
+/**
+ * android google login 实现逻辑
+ */
+export const googleLogin : GoogleLogin = function (options : GoogleLoginOptions) {
+
+	if (UTSAndroid.getUniActivity() == null || UTSAndroid.getUniActivity()!.isFinishing()) {
+		const err = new GoogleLoginFailImpl(9010001);
+		options.fail?.(err)
+		options.complete?.(err)
+		options.fail = null;
+		options.complete = null;
+		return;
+	}
+
+	let credentialManager = CredentialManager.create(UTSAndroid.getUniActivity()!)
+	let googleIdOption = GetGoogleIdOption.Builder()
+		.setServerClientId(options.serverClientId)  // Web 客户端 ID
+		.setFilterByAuthorizedAccounts(false)  // 仅显示已授权账户
+		.setAutoSelectEnabled(true)  // 自动选择唯一账户
+		.setNonce(SystemClock.uptimeMillis().toString())  // 防止重放攻击
+		.build()
+
+	let request = GetCredentialRequest.Builder()
+		.addCredentialOption(googleIdOption)
+		.build()
+	/**
+	 * 调用kotlin代码
+	 */
+	let option = {
+		success: (data : GoogleLoginSuccess | null) => {
+
+			if (data == null) {
+				// 未知错误
+				const err = new GoogleLoginFailImpl(9010099);
+				options.fail?.(err)
+				options.complete?.(err)
+				options.fail = null;
+				options.complete = null;
+				return
+			}
+
+			options.success?.(data)
+			options.complete?.(data)
+			options.success = null;
+			options.complete = null;
+
+		},
+		fail: (ex : Exception | null) => {
+
+			if (ex == null) {
+				// 成功返回了凭证,但是凭证类型不是 google Id token。 暂不支持
+				const err = new GoogleLoginFailImpl(9010003);
+				options.fail?.(err)
+				options.complete?.(err)
+				options.fail = null;
+				options.complete = null;
+				return
+			}
+			
+			let err:GoogleLoginFailImpl
+			if ("androidx.credentials.exceptions.NoCredentialException" == ex.javaClass.name) {
+				/**
+				 * 证书签名错误
+				 */
+				err = new GoogleLoginFailImpl(9010004);
+			}else if("androidx.credentials.exceptions.GetCredentialProviderConfigurationException" == ex.javaClass.name) {
+				/**
+				 * 系统组件缺失
+				 */
+				err = new GoogleLoginFailImpl(9010005);
+			}
+			else if ("androidx.credentials.exceptions.GetCredentialCancellationException" == ex.javaClass.name) {
+				console.log(ex)
+				/**
+				 * 通常是因为serverclient配置错误导致
+				 */
+				err = new GoogleLoginFailImpl(9010002);
+			}else{
+				err = new GoogleLoginFailImpl(9010099);
+			}
+			
+			options.fail?.(err)
+			options.complete?.(err)
+			options.fail = null;
+			options.complete = null;
+			return
+		},
+	} as CallKotlinLoginOptions
+
+	NativeCode.requestLogin(credentialManager, request, option);
+
+}

+ 3 - 0
uni_modules/uni-oauth-google/utssdk/app-ios/config.json

@@ -0,0 +1,3 @@
+{
+  "deploymentTarget": "12"
+}

+ 85 - 0
uni_modules/uni-oauth-google/utssdk/app-ios/index.uts

@@ -0,0 +1,85 @@
+/**
+ * 引用 iOS 系统库,示例如下:
+ * import { UIDevice } from "UIKit";
+ * [可选实现,按需引入]
+ */
+
+/* 引入 interface.uts 文件中定义的变量 */
+import { MyApiOptions, MyApiResult, MyApi, MyApiSync } from '../interface.uts';
+
+/* 引入 unierror.uts 文件中定义的变量 */
+import { MyApiFailImpl } from '../unierror';
+
+/**
+ * 引入三方库
+ * [可选实现,按需引入]
+ *
+ * 在 iOS 平台引入三方库有以下两种方式:
+ * 1、通过引入三方库framework 或者.a 等方式,需要将 .framework 放到 ./Frameworks 目录下,将.a 放到 ./Libs 目录下。更多信息[详见](https://uniapp.dcloud.net.cn/plugin/uts-plugin.html#ios-平台原生配置)
+ * 2、通过 cocoaPods 方式引入,将要引入的 pod 信息配置到 config.json 文件下的 dependencies-pods 字段下。详细配置方式[详见](https://uniapp.dcloud.net.cn/plugin/uts-ios-cocoapods.html)
+ *
+ * 在通过上述任意方式依赖三方库后,使用时需要在文件中 import:
+ * 示例:import { LottieLoopMode	} from 'Lottie'
+ */
+
+/**
+ * UTSiOS 为平台内置对象,不需要 import 可直接调用其API,[详见](https://uniapp.dcloud.net.cn/uts/utsios.html)
+ */
+
+/**
+ * 异步方法
+ *
+ * uni-app项目中(vue/nvue)调用示例:
+ * 1、引入方法声明 import { myApi } from "@/uni_modules/uts-api"
+ * 2、方法调用
+ * myApi({
+ *   paramA: false,
+ *   complete: (res) => {
+ *      console.log(res)
+ *   }
+ * });
+ *
+ */
+export const myApi : MyApi = function (options : MyApiOptions) {
+
+  if (options.paramA == true) {
+    // 返回数据
+    const res : MyApiResult = {
+      fieldA: 85,
+      fieldB: true,
+      fieldC: 'some message'
+    };
+    options.success?.(res);
+    options.complete?.(res);
+
+  } else {
+    // 返回错误
+    let failResult = new MyApiFailImpl(9010001);
+    options.fail?.(failResult)
+    options.complete?.(failResult)
+  }
+
+}
+
+/**
+ * 同步方法
+ *
+ * uni-app项目中(vue/nvue)调用示例:
+ * 1、引入方法声明 import { myApiSync } from "@/uni_modules/uts-api"
+ * 2、方法调用
+ * myApiSync(true);
+ *
+ */
+export const myApiSync : MyApiSync = function (paramA : boolean) : MyApiResult {
+  // 返回数据,根据插件功能获取实际的返回值
+  const res : MyApiResult = {
+    fieldA: 85,
+    fieldB: paramA,
+    fieldC: 'some message'
+  };
+  return res;
+}
+
+/**
+ * 更多插件开发的信息详见:https://uniapp.dcloud.net.cn/plugin/uts-plugin.html
+ */

+ 53 - 0
uni_modules/uni-oauth-google/utssdk/interface.uts

@@ -0,0 +1,53 @@
+/**
+ * 登录参数
+ */
+export type GoogleLoginOptions = {
+	serverClientId:string
+	success ?: (res : GoogleLoginSuccess) => void
+	fail ?: (res : GoogleLoginFail) => void
+	complete ?: (res : any) => void
+}
+/**
+ * 登出参数
+ */
+export type GoogleLogoutOptions = {
+  success ?: (res : any) => void
+  fail ?: (res : GoogleLoginFail) => void
+  complete ?: (res : any) => void
+}
+
+
+export type GoogleLoginSuccess = {
+  headimgurl : string|null,
+  nickname : string|null,
+  email : string|null,
+  openId : string,
+  idToken : string,
+}
+
+/**
+ * 错误码
+ * 根据uni错误码规范要求,建议错误码以90开头,以下是错误码示例:
+ * - 9010001 不具备登录的系统环境,比如当前所在的activity状态异常
+ * - 9010002 用户参数配置错误,重点检查serverClientId/包名/签名是否正确
+ * - 9010003 暂不支持的登录凭证
+ * - 9010004 签名错误
+ * - 9010005 缺少系统组件,比如GMS
+ * - 9010099 其他未知错误
+ */
+export type GoogleLoginErrorCode = 9010001 | 9010002 | 9010003 | 9010004 | 9010005 | 9010099;
+/**
+ * 登录过程中发生错误
+ */
+export interface GoogleLoginFail extends IUniError {
+  errCode : GoogleLoginErrorCode
+};
+
+/**
+ * 实现登录
+ */
+export type GoogleLogin = (options : GoogleLoginOptions) => void
+/**
+ * 取消登录
+ */
+export type GoogleLogout = (options : GoogleLogoutOptions) => void

+ 37 - 0
uni_modules/uni-oauth-google/utssdk/unierror.uts

@@ -0,0 +1,37 @@
+
+import { GoogleLoginErrorCode, GoogleLoginFail } from "./interface.uts"
+export const UniErrorSubject = 'uni-google-login';
+
+
+/**
+ * 错误信息
+ * @UniError
+ */
+export const GoogleLoginErrors : Map<GoogleLoginErrorCode, string> = new Map([
+  /**
+   * 错误码及对应的错误信息
+   */
+  [9010001, '系统环境错误,请检查application/activity状态'],
+  [9010002, '用户参数配置错误,重点检查serverClientId是否正确'],
+  [9010003, '暂不支持的登录凭据'],
+  [9010004, '无法找到对应的凭据,可能是应用签名/包名不匹配或者与Google连接失败'],
+  [9010005, '登录组件缺失,请检查系统是否正确安装GMS'],
+  [9010099, '其他未知错误']
+]);
+
+
+/**
+ * 错误对象实现
+ */
+export class GoogleLoginFailImpl extends UniError implements GoogleLoginFail {
+
+  /**
+   * 错误对象构造函数
+   */
+  constructor(errCode : GoogleLoginErrorCode) {
+    super();
+    this.errSubject = UniErrorSubject;
+    this.errCode = errCode;
+    this.errMsg = GoogleLoginErrors.get(errCode) ?? "";
+  }
+}