update:新增扫码登录页
This commit is contained in:
@@ -4,6 +4,10 @@
|
||||
"libVersion": "3.7.7",
|
||||
"packOptions": {
|
||||
"ignore": [
|
||||
{
|
||||
"type": "folder",
|
||||
"value": "static/scan-login"
|
||||
},
|
||||
{
|
||||
"type": "file",
|
||||
"value": "static/common/dialog-light.png"
|
||||
|
||||
@@ -264,6 +264,14 @@ export const tempBindOrgAPI = (scene) => {
|
||||
});
|
||||
};
|
||||
|
||||
// 小程序确认个人账号登录 Pad,未登录时可携带微信登录 code。
|
||||
export const confirmScanLoginAPI = (scene, code) => {
|
||||
return request("POST", "/index/scanLogin/confirm", {
|
||||
scene,
|
||||
...(code ? {code} : {}),
|
||||
}, BASE_URL, [0], {showErrorToast: false});
|
||||
};
|
||||
|
||||
export const bindDeviceAPI = (device) => {
|
||||
return request("POST", "/user/device/bindDevice", {
|
||||
device,
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
"appid" : "wxa8f5989dcd45cc23",
|
||||
"packOptions" : {
|
||||
"ignore" : [
|
||||
{
|
||||
"type" : "folder",
|
||||
"value" : "static/scan-login"
|
||||
},
|
||||
{
|
||||
"type" : "file",
|
||||
"value" : "static/common/dialog-light.png"
|
||||
|
||||
@@ -118,6 +118,14 @@
|
||||
}
|
||||
},
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "pages/scan-login",
|
||||
"pages": [
|
||||
{
|
||||
"path": "index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"root": "pages/coin",
|
||||
"pages": [
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
<script setup>
|
||||
import { computed, ref } from "vue";
|
||||
import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
|
||||
import { storeToRefs } from "pinia";
|
||||
import Container from "@/components/Container.vue";
|
||||
import Signin from "@/components/Signin.vue";
|
||||
import { confirmScanLoginAPI } from "@/apis";
|
||||
import { capsuleHeight, wxLogin } from "@/util";
|
||||
import useStore from "@/store";
|
||||
|
||||
const store = useStore();
|
||||
const { user } = storeToRefs(store);
|
||||
const scene = ref("");
|
||||
const hasToken = ref(false);
|
||||
const showSignin = ref(false);
|
||||
const confirming = ref(false);
|
||||
const authorized = ref(false);
|
||||
const invalidScene = ref(false);
|
||||
const errorMessage = ref("");
|
||||
const authorizedUser = ref({});
|
||||
let pageActive = true;
|
||||
|
||||
const isLoggedIn = computed(() => hasToken.value || authorized.value);
|
||||
const currentUser = computed(() => authorized.value ? authorizedUser.value : user.value);
|
||||
const avatarFailed = ref(false);
|
||||
const avatar = computed(() =>
|
||||
!avatarFailed.value && currentUser.value.avatar
|
||||
? currentUser.value.avatar
|
||||
: "/static/user-icon.png"
|
||||
);
|
||||
const systemInfo = uni.getSystemInfoSync();
|
||||
const unitScale = 750 / systemInfo.windowWidth;
|
||||
const headerHeight = (capsuleHeight + 50) * unitScale;
|
||||
const contentStyle = computed(() => ({
|
||||
minHeight: `${Math.max(0, systemInfo.windowHeight * unitScale - headerHeight)}rpx`,
|
||||
paddingTop: `${Math.max(40, (isLoggedIn.value ? 408 : 444) - headerHeight)}rpx`,
|
||||
}));
|
||||
|
||||
const getTokenKey = () => `${uni.getAccountInfoSync().miniProgram.envVersion}_token`;
|
||||
|
||||
const refreshLoginState = () => {
|
||||
hasToken.value = Boolean(uni.getStorageSync(getTokenKey()));
|
||||
};
|
||||
|
||||
const closeSignin = () => {
|
||||
showSignin.value = false;
|
||||
};
|
||||
|
||||
const goHome = () => {
|
||||
uni.reLaunch({ url: "/pages/index" });
|
||||
};
|
||||
|
||||
const goBack = () => {
|
||||
if (confirming.value) {
|
||||
uni.showToast({ title: "正在授权,请稍候", icon: "none" });
|
||||
return;
|
||||
}
|
||||
if (authorized.value) {
|
||||
goHome();
|
||||
return;
|
||||
}
|
||||
if (getCurrentPages().length > 1) {
|
||||
uni.navigateBack();
|
||||
return;
|
||||
}
|
||||
goHome();
|
||||
};
|
||||
|
||||
const confirmLogin = async () => {
|
||||
if (confirming.value || authorized.value || !pageActive) return;
|
||||
if (!scene.value || invalidScene.value) return;
|
||||
|
||||
confirming.value = true;
|
||||
errorMessage.value = "";
|
||||
try {
|
||||
refreshLoginState();
|
||||
// 仅在用户点击后确认授权,未登录时先尝试通过微信 code 识别账号。
|
||||
const code = hasToken.value ? "" : (await wxLogin())?.code;
|
||||
if (!hasToken.value && !code) {
|
||||
throw new Error("获取微信登录凭证失败,请重试。");
|
||||
}
|
||||
if (!pageActive) return;
|
||||
const account = { ...user.value };
|
||||
const result = await confirmScanLoginAPI(scene.value, code);
|
||||
if (result?.success === true) {
|
||||
authorizedUser.value = account;
|
||||
authorized.value = true;
|
||||
// Pad 已签发新 token,清理本地账号、设备和对局等会话数据。
|
||||
uni.removeStorageSync(getTokenKey());
|
||||
store.clearSessionState();
|
||||
hasToken.value = false;
|
||||
if (pageActive) {
|
||||
uni.showModal({
|
||||
title: "授权成功",
|
||||
content: "已授权登录 iPad",
|
||||
showCancel: false,
|
||||
success: (res) => {
|
||||
if (res.confirm && pageActive) goHome();
|
||||
},
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!pageActive) return;
|
||||
if (result?.needLogin === true) {
|
||||
uni.removeStorageSync(getTokenKey());
|
||||
store.clearSessionState();
|
||||
hasToken.value = false;
|
||||
showSignin.value = true;
|
||||
return;
|
||||
}
|
||||
errorMessage.value = result?.msg || "授权失败,请重新扫描 Pad 端的小程序码。";
|
||||
} catch (error) {
|
||||
if (!pageActive) return;
|
||||
if (error?.type === "AUTH_INVALID") {
|
||||
store.clearSessionState();
|
||||
hasToken.value = false;
|
||||
errorMessage.value = "登录已失效,请重新登录后确认授权。";
|
||||
showSignin.value = true;
|
||||
} else {
|
||||
errorMessage.value = error?.message || "网络异常,请稍后重试。";
|
||||
}
|
||||
} finally {
|
||||
confirming.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrimaryAction = () => {
|
||||
refreshLoginState();
|
||||
if (!isLoggedIn.value) {
|
||||
showSignin.value = true;
|
||||
return;
|
||||
}
|
||||
return confirmLogin();
|
||||
};
|
||||
|
||||
const handleLoginSuccess = async () => {
|
||||
refreshLoginState();
|
||||
avatarFailed.value = false;
|
||||
await confirmLogin();
|
||||
};
|
||||
|
||||
onLoad((options) => {
|
||||
refreshLoginState();
|
||||
try {
|
||||
const value = decodeURIComponent(String(options?.scene || ""));
|
||||
// 只接收当前扫码协议的 scene,不接收 Pad 私有的 pollId。
|
||||
if (!/^sl[a-zA-Z0-9]{1,30}$/.test(value)) {
|
||||
invalidScene.value = true;
|
||||
errorMessage.value = "二维码无效,请重新扫描 Pad 端的小程序码。";
|
||||
return;
|
||||
}
|
||||
scene.value = value;
|
||||
} catch (error) {
|
||||
invalidScene.value = true;
|
||||
errorMessage.value = "二维码无效,请重新扫描 Pad 端的小程序码。";
|
||||
}
|
||||
});
|
||||
|
||||
onShow(refreshLoginState);
|
||||
onUnload(() => {
|
||||
pageActive = false;
|
||||
closeSignin();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="scan-login-page">
|
||||
<Container
|
||||
:bgType="0"
|
||||
:onBack="goBack"
|
||||
:usePageScroll="true"
|
||||
>
|
||||
<view class="scan-login-content" :style="contentStyle">
|
||||
<view v-if="isLoggedIn" class="identity-transfer">
|
||||
<view class="identity">
|
||||
<image
|
||||
class="identity-avatar"
|
||||
:src="avatar"
|
||||
mode="aspectFill"
|
||||
@error="avatarFailed = true"
|
||||
/>
|
||||
<text class="identity-name">{{ currentUser.nickName || "弓箭手" }}</text>
|
||||
</view>
|
||||
<image
|
||||
class="transfer-arrow"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/scan-login/scan-login-transfer-arrow.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view class="identity">
|
||||
<image
|
||||
class="app-icon"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/scan-login/scan-login-app-icon.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<text class="identity-name">射灵星球APP</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="required-ip">
|
||||
<view class="required-ip-shadow"></view>
|
||||
<image
|
||||
class="required-ip-image"
|
||||
src="https://static.shelingxingqiu.com/shootmini/static/scan-login/scan-login-required-ip.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="isLoggedIn" class="login-title">
|
||||
<text>登录 射灵星球APP </text><text class="target-device">Pad端</text>
|
||||
</view>
|
||||
<view v-else class="login-description">
|
||||
<text>你还没有登录射灵星球小程序,</text>
|
||||
<text>无法扫码登录Pad端。</text>
|
||||
</view>
|
||||
|
||||
<button
|
||||
:class="['confirm-button', isLoggedIn ? 'confirm-button--logged-in' : '']"
|
||||
hover-class="none"
|
||||
:disabled="confirming || authorized || (isLoggedIn && invalidScene)"
|
||||
@click="$clickSound(handlePrimaryAction)"
|
||||
>
|
||||
{{ authorized ? "已授权登录" : confirming ? "正在授权..." : isLoggedIn ? "确认登录" : "立即登录" }}
|
||||
</button>
|
||||
<text v-if="isLoggedIn" class="session-tip">
|
||||
登录Pad端后,小程序端将自动退出登录。
|
||||
</text>
|
||||
<text v-if="isLoggedIn && errorMessage" class="error-message">{{ errorMessage }}</text>
|
||||
|
||||
<button
|
||||
v-if="isLoggedIn"
|
||||
class="cancel-button"
|
||||
hover-class="none"
|
||||
:disabled="confirming"
|
||||
@click="$clickSound(goBack)"
|
||||
>
|
||||
取消登录
|
||||
</button>
|
||||
</view>
|
||||
</Container>
|
||||
<Signin
|
||||
:show="showSignin"
|
||||
:onClose="closeSignin"
|
||||
:onSuccess="handleLoginSuccess"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scan-login-page {
|
||||
min-height: 100vh;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.scan-login-content {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-right: 40rpx;
|
||||
padding-left: 40rpx;
|
||||
}
|
||||
|
||||
.identity-transfer {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 488rpx;
|
||||
}
|
||||
|
||||
.identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 160rpx;
|
||||
}
|
||||
|
||||
.identity-avatar,
|
||||
.app-icon {
|
||||
width: 136rpx;
|
||||
height: 136rpx;
|
||||
}
|
||||
|
||||
.identity-avatar {
|
||||
border-radius: 68rpx;
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
border-radius: 36rpx;
|
||||
}
|
||||
|
||||
.identity-name {
|
||||
width: 160rpx;
|
||||
margin-top: 12rpx;
|
||||
overflow: hidden;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.transfer-arrow {
|
||||
flex-shrink: 0;
|
||||
width: 82rpx;
|
||||
height: 50rpx;
|
||||
margin: 38rpx 41rpx 0;
|
||||
}
|
||||
|
||||
.required-ip {
|
||||
position: relative;
|
||||
width: 280rpx;
|
||||
height: 220rpx;
|
||||
}
|
||||
|
||||
.required-ip-shadow {
|
||||
position: absolute;
|
||||
top: 182rpx;
|
||||
left: 53rpx;
|
||||
width: 172rpx;
|
||||
height: 37rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.required-ip-image {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 52rpx;
|
||||
width: 176rpx;
|
||||
height: 208rpx;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
margin-top: 120rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 500;
|
||||
line-height: 48rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.target-device {
|
||||
color: #fed847;
|
||||
}
|
||||
|
||||
.login-description {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 32rpx;
|
||||
font-size: 28rpx;
|
||||
line-height: 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 400rpx;
|
||||
height: 76rpx;
|
||||
margin: 102rpx 0 0;
|
||||
padding: 0;
|
||||
border-radius: 44rpx;
|
||||
background: #fed847;
|
||||
color: #000;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
line-height: 40rpx;
|
||||
}
|
||||
|
||||
.confirm-button--logged-in {
|
||||
margin-top: 120rpx;
|
||||
}
|
||||
|
||||
.confirm-button::after,
|
||||
.cancel-button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.confirm-button[disabled] {
|
||||
background: #fed847;
|
||||
color: #000;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.session-tip {
|
||||
margin-top: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
font-size: 24rpx;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
max-width: 600rpx;
|
||||
margin-top: 24rpx;
|
||||
color: #fed847;
|
||||
font-size: 24rpx;
|
||||
line-height: 36rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cancel-button {
|
||||
flex-shrink: 0;
|
||||
margin: auto 0 134rpx;
|
||||
padding: 28rpx 40rpx;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #83cdff;
|
||||
font-size: 26rpx;
|
||||
line-height: 36rpx;
|
||||
}
|
||||
|
||||
.cancel-button[disabled] {
|
||||
background: transparent;
|
||||
color: #83cdff;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 405 B |
Reference in New Issue
Block a user