chore: prepare anxinyan release
This commit is contained in:
@@ -373,6 +373,7 @@ export interface ReportDetailData {
|
||||
report_title: string;
|
||||
report_status: string;
|
||||
service_provider: string;
|
||||
service_provider_text: string;
|
||||
institution_name: string;
|
||||
publish_time: string;
|
||||
zhongjian_report_no: string;
|
||||
|
||||
@@ -22,6 +22,30 @@ export interface LoginResult {
|
||||
user_info: AuthUserInfo;
|
||||
}
|
||||
|
||||
export interface WechatAuthConfig {
|
||||
appid: string;
|
||||
oauth_redirect_url: string;
|
||||
enabled: boolean;
|
||||
scope: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
export interface WechatExchangeResult {
|
||||
status: "logged_in" | "need_bind";
|
||||
token?: string;
|
||||
user_info?: AuthUserInfo;
|
||||
bind_ticket?: string;
|
||||
expire_seconds?: number;
|
||||
profile?: {
|
||||
nickname: string;
|
||||
avatar: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface WechatBindMobileResult extends LoginResult {
|
||||
status: "logged_in";
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
sendLoginCode(mobile: string) {
|
||||
return request<SendLoginCodeResult>("/api/app/auth/send-code", {
|
||||
@@ -41,6 +65,25 @@ export const authApi = {
|
||||
data: { mobile, password },
|
||||
});
|
||||
},
|
||||
getWechatConfig() {
|
||||
return request<WechatAuthConfig>("/api/app/auth/wechat/config");
|
||||
},
|
||||
exchangeWechatCode(code: string, state: string) {
|
||||
return request<WechatExchangeResult>("/api/app/auth/wechat/exchange", {
|
||||
method: "POST",
|
||||
data: { code, state },
|
||||
});
|
||||
},
|
||||
bindWechatMobile(payload: {
|
||||
bind_ticket: string;
|
||||
mobile: string;
|
||||
code: string;
|
||||
}) {
|
||||
return request<WechatBindMobileResult>("/api/app/auth/wechat/bind-mobile", {
|
||||
method: "POST",
|
||||
data: payload,
|
||||
});
|
||||
},
|
||||
getMe() {
|
||||
return request<{ user_info: AuthUserInfo }>("/api/app/auth/me");
|
||||
},
|
||||
|
||||
@@ -387,6 +387,8 @@ export const reportDetailFallback: ReportDetailData = {
|
||||
{ label: "检测结论", value: "正品", remark: "综合当前送检资料与商品特征判断,符合正品特征。" },
|
||||
{ label: "品牌", value: "Rolex" },
|
||||
{ label: "主体颜色", value: "银盘" },
|
||||
{ label: "服务类型", value: "中检鉴定" },
|
||||
{ label: "鉴定师", value: "张师傅" },
|
||||
],
|
||||
},
|
||||
trace_info: {
|
||||
@@ -422,6 +424,7 @@ export const reportDetailFallback: ReportDetailData = {
|
||||
report_title: "中检鉴定报告",
|
||||
report_status: "published",
|
||||
service_provider: "zhongjian",
|
||||
service_provider_text: "中检鉴定",
|
||||
institution_name: "中检鉴定中心",
|
||||
publish_time: "2026-04-18 18:26:00",
|
||||
zhongjian_report_no: "ZJ-20260418-0001",
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
"navigationBarTitleText": "登录"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/auth/wechat-bind",
|
||||
"style": {
|
||||
"navigationBarTitleText": "绑定手机号"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/home/index",
|
||||
"style": {
|
||||
|
||||
@@ -4,7 +4,19 @@ import { onLoad } from "@dcloudio/uni-app";
|
||||
import { authApi } from "../../api/auth";
|
||||
import { useAppraisalStore } from "../../stores/appraisal";
|
||||
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
import { isLoggedIn, isWechatBrowser, navigateAfterLogin, setUserToken } from "../../utils/auth";
|
||||
import {
|
||||
clearWechatBindSession,
|
||||
clearWechatOAuthState,
|
||||
consumeWechatOAuthSuppression,
|
||||
getWechatOAuthState,
|
||||
isLoggedIn,
|
||||
isWechatBrowser,
|
||||
navigateAfterLogin,
|
||||
rememberLoginRedirect,
|
||||
setUserToken,
|
||||
setWechatBindSession,
|
||||
setWechatOAuthState,
|
||||
} from "../../utils/auth";
|
||||
|
||||
type LoginMode = "code" | "password";
|
||||
const COUNTDOWN_STORAGE_KEY = "anxinyan_login_code_countdown_expire_at";
|
||||
@@ -12,6 +24,8 @@ const COUNTDOWN_STORAGE_KEY = "anxinyan_login_code_countdown_expire_at";
|
||||
const mode = ref<LoginMode>("code");
|
||||
const sending = ref(false);
|
||||
const submitting = ref(false);
|
||||
const wechatProcessing = ref(false);
|
||||
const wechatMessage = ref("");
|
||||
const countdown = ref(0);
|
||||
const redirect = ref("");
|
||||
const sendCodeErrorMessage = ref("");
|
||||
@@ -27,7 +41,7 @@ let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const browserHint = computed(() =>
|
||||
isWechatBrowser()
|
||||
? "微信内也支持手机号快捷登录,后续可继续补充微信授权。"
|
||||
? "微信内将自动使用公众号授权登录;授权失败时也可继续用手机号登录。"
|
||||
: "当前为非微信浏览器环境,可直接使用手机号验证码或密码登录。",
|
||||
);
|
||||
|
||||
@@ -129,6 +143,138 @@ function goHome() {
|
||||
uni.reLaunch({ url: "/pages/home/index" });
|
||||
}
|
||||
|
||||
function currentQueryValue(key: string) {
|
||||
// #ifdef H5
|
||||
const url = new URL(window.location.href);
|
||||
const directValue = url.searchParams.get(key);
|
||||
if (directValue) {
|
||||
return directValue;
|
||||
}
|
||||
|
||||
const hashQuery = window.location.hash.includes("?") ? window.location.hash.split("?")[1] : "";
|
||||
return new URLSearchParams(hashQuery).get(key) || "";
|
||||
// #endif
|
||||
return "";
|
||||
}
|
||||
|
||||
function cleanWechatCallbackQuery() {
|
||||
// #ifdef H5
|
||||
const url = new URL(window.location.href);
|
||||
const hashHasCallback = window.location.hash.includes("?")
|
||||
&& (new URLSearchParams(window.location.hash.split("?")[1]).has("code")
|
||||
|| new URLSearchParams(window.location.hash.split("?")[1]).has("state"));
|
||||
if (!url.searchParams.has("code") && !url.searchParams.has("state") && !hashHasCallback) {
|
||||
return;
|
||||
}
|
||||
url.searchParams.delete("code");
|
||||
url.searchParams.delete("state");
|
||||
if (window.location.hash.includes("?")) {
|
||||
const [hashPath, hashQuery = ""] = window.location.hash.split("?");
|
||||
const hashParams = new URLSearchParams(hashQuery);
|
||||
hashParams.delete("code");
|
||||
hashParams.delete("state");
|
||||
const nextQuery = hashParams.toString();
|
||||
url.hash = `${hashPath}${nextQuery ? `?${nextQuery}` : ""}`;
|
||||
}
|
||||
window.history.replaceState({}, document.title, url.toString());
|
||||
// #endif
|
||||
}
|
||||
|
||||
function buildWechatAuthorizeUrl(config: Awaited<ReturnType<typeof authApi.getWechatConfig>>) {
|
||||
const params = [
|
||||
`appid=${encodeURIComponent(config.appid)}`,
|
||||
`redirect_uri=${encodeURIComponent(config.oauth_redirect_url)}`,
|
||||
"response_type=code",
|
||||
`scope=${encodeURIComponent(config.scope || "snsapi_userinfo")}`,
|
||||
`state=${encodeURIComponent(config.state)}`,
|
||||
].join("&");
|
||||
|
||||
return `https://open.weixin.qq.com/connect/oauth2/authorize?${params}#wechat_redirect`;
|
||||
}
|
||||
|
||||
async function startWechatOAuth() {
|
||||
if (wechatProcessing.value || !isWechatBrowser() || isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wechatProcessing.value = true;
|
||||
wechatMessage.value = "正在准备微信授权";
|
||||
try {
|
||||
const config = await authApi.getWechatConfig();
|
||||
if (!config.enabled) {
|
||||
wechatMessage.value = "微信授权暂未启用,请使用手机号登录";
|
||||
return;
|
||||
}
|
||||
|
||||
setWechatOAuthState(config.state);
|
||||
rememberLoginRedirect(redirect.value || "/pages/mine/index");
|
||||
// #ifdef H5
|
||||
window.location.href = buildWechatAuthorizeUrl(config);
|
||||
// #endif
|
||||
} catch (error) {
|
||||
wechatMessage.value = "微信授权暂不可用,请使用手机号登录";
|
||||
showErrorToast(error, "微信授权失败");
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
wechatProcessing.value = false;
|
||||
}, 800);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleWechatCallback() {
|
||||
const code = currentQueryValue("code");
|
||||
const state = currentQueryValue("state");
|
||||
if (!code) {
|
||||
if (consumeWechatOAuthSuppression()) {
|
||||
wechatMessage.value = "可继续使用手机号登录";
|
||||
return;
|
||||
}
|
||||
await startWechatOAuth();
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedState = getWechatOAuthState();
|
||||
if (expectedState && state !== expectedState) {
|
||||
clearWechatOAuthState();
|
||||
cleanWechatCallbackQuery();
|
||||
showErrorToast(new Error("微信授权状态不匹配,请重新登录"), "微信授权失败");
|
||||
return;
|
||||
}
|
||||
|
||||
wechatProcessing.value = true;
|
||||
wechatMessage.value = "正在完成微信登录";
|
||||
try {
|
||||
const result = await authApi.exchangeWechatCode(code, state);
|
||||
clearWechatOAuthState();
|
||||
cleanWechatCallbackQuery();
|
||||
|
||||
if (result.status === "logged_in" && result.token) {
|
||||
clearWechatBindSession();
|
||||
setUserToken(result.token);
|
||||
appraisalStore.resetForNewFlow();
|
||||
showInfoToast("登录成功");
|
||||
navigateAfterLogin(redirect.value || "/pages/mine/index");
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.status === "need_bind" && result.bind_ticket) {
|
||||
setWechatBindSession(result.bind_ticket, result.profile);
|
||||
const bindUrl = `/pages/auth/wechat-bind${redirect.value ? `?redirect=${encodeURIComponent(redirect.value)}` : ""}`;
|
||||
uni.redirectTo({ url: bindUrl });
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("微信授权结果异常,请使用手机号登录");
|
||||
} catch (error) {
|
||||
clearWechatOAuthState();
|
||||
cleanWechatCallbackQuery();
|
||||
wechatMessage.value = "微信授权失败,可使用手机号登录";
|
||||
showErrorToast(error, "微信授权失败");
|
||||
} finally {
|
||||
wechatProcessing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSendCode() {
|
||||
if (sending.value || countdown.value > 0) return;
|
||||
if (!validateMobile()) return;
|
||||
@@ -191,6 +337,11 @@ onLoad((options) => {
|
||||
restoreCountdown();
|
||||
if (isLoggedIn()) {
|
||||
navigateAfterLogin(redirect.value || "/pages/mine/index");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWechatBrowser()) {
|
||||
handleWechatCallback();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -234,6 +385,14 @@ onUnmounted(clearCountdown);
|
||||
</view>
|
||||
|
||||
<view class="auth-panel">
|
||||
<view v-if="wechatProcessing || wechatMessage" class="auth-wechat-status">
|
||||
<view class="auth-wechat-status__icon">微</view>
|
||||
<view>
|
||||
<view class="auth-wechat-status__title">{{ wechatProcessing ? "微信授权登录" : "微信授权提示" }}</view>
|
||||
<view class="auth-wechat-status__desc">{{ wechatMessage || "正在打开微信授权" }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="auth-switch">
|
||||
<view :class="['auth-switch__item', mode === 'code' ? 'auth-switch__item--active' : '']" @click="mode = 'code'">
|
||||
验证码登录
|
||||
@@ -414,6 +573,43 @@ onUnmounted(clearCountdown);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.auth-wechat-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
margin-bottom: 22rpx;
|
||||
padding: 20rpx 22rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #edf7f0;
|
||||
border: 1px solid rgba(47, 107, 79, 0.14);
|
||||
}
|
||||
|
||||
.auth-wechat-status__icon {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #2f6b4f;
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
line-height: 64rpx;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.auth-wechat-status__title {
|
||||
color: #244f3b;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.auth-wechat-status__desc {
|
||||
margin-top: 6rpx;
|
||||
color: #4f7662;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.auth-switch {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
|
||||
444
user-app/src/pages/auth/wechat-bind.vue
Normal file
444
user-app/src/pages/auth/wechat-bind.vue
Normal file
@@ -0,0 +1,444 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onUnmounted, reactive, ref, watch } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { authApi } from "../../api/auth";
|
||||
import { useAppraisalStore } from "../../stores/appraisal";
|
||||
import { showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
import {
|
||||
clearWechatBindSession,
|
||||
getWechatBindProfile,
|
||||
getWechatBindTicket,
|
||||
navigateAfterLogin,
|
||||
setUserToken,
|
||||
suppressNextWechatOAuth,
|
||||
} from "../../utils/auth";
|
||||
|
||||
const COUNTDOWN_STORAGE_KEY = "anxinyan_wechat_bind_code_countdown_expire_at";
|
||||
|
||||
const redirect = ref("");
|
||||
const sending = ref(false);
|
||||
const submitting = ref(false);
|
||||
const countdown = ref(0);
|
||||
const sendCodeErrorMessage = ref("");
|
||||
const bindTicket = ref("");
|
||||
const profile = ref({ nickname: "", avatar: "" });
|
||||
const appraisalStore = useAppraisalStore();
|
||||
|
||||
const form = reactive({
|
||||
mobile: "",
|
||||
code: "",
|
||||
});
|
||||
|
||||
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
const sendButtonText = computed(() => (countdown.value > 0 ? `${countdown.value}s 后重发` : "发送验证码"));
|
||||
const displayName = computed(() => profile.value.nickname || "微信用户");
|
||||
const displayAvatar = computed(() => profile.value.avatar || "");
|
||||
|
||||
function resolveSendCodeError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error || "");
|
||||
|
||||
if (message.includes("触发号码天级流控")) {
|
||||
return "该手机号今日获取验证码次数已达上限,请明天再试或更换手机号。";
|
||||
}
|
||||
if (message.includes("请") && message.includes("秒后再试")) {
|
||||
return message;
|
||||
}
|
||||
if (message.includes("短信配置未完成")) {
|
||||
return "短信发送配置尚未完成,请联系管理员在后台补全短信参数。";
|
||||
}
|
||||
|
||||
return message || "验证码发送失败,请稍后重试。";
|
||||
}
|
||||
|
||||
function clearCountdown() {
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
uni.removeStorageSync(COUNTDOWN_STORAGE_KEY);
|
||||
}
|
||||
|
||||
function startCountdownByExpireAt(expireAt: number) {
|
||||
if (!Number.isFinite(expireAt) || expireAt <= Date.now()) {
|
||||
clearCountdown();
|
||||
countdown.value = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (countdownTimer) {
|
||||
clearInterval(countdownTimer);
|
||||
}
|
||||
|
||||
uni.setStorageSync(COUNTDOWN_STORAGE_KEY, String(expireAt));
|
||||
const updateCountdown = () => {
|
||||
const left = Math.max(0, Math.ceil((expireAt - Date.now()) / 1000));
|
||||
countdown.value = left;
|
||||
if (left <= 0) {
|
||||
clearCountdown();
|
||||
countdown.value = 0;
|
||||
}
|
||||
};
|
||||
|
||||
updateCountdown();
|
||||
countdownTimer = setInterval(updateCountdown, 1000);
|
||||
}
|
||||
|
||||
function startCountdown(seconds = 60) {
|
||||
startCountdownByExpireAt(Date.now() + seconds * 1000);
|
||||
}
|
||||
|
||||
function restoreCountdown() {
|
||||
const expireAt = Number(uni.getStorageSync(COUNTDOWN_STORAGE_KEY) || 0);
|
||||
if (expireAt) {
|
||||
startCountdownByExpireAt(expireAt);
|
||||
}
|
||||
}
|
||||
|
||||
function validateMobile() {
|
||||
if (!/^1\d{10}$/.test(form.mobile.trim())) {
|
||||
showInfoToast("请输入正确的手机号");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleSendCode() {
|
||||
if (sending.value || countdown.value > 0) return;
|
||||
if (!validateMobile()) return;
|
||||
|
||||
sendCodeErrorMessage.value = "";
|
||||
sending.value = true;
|
||||
try {
|
||||
const data = await withLoading("正在发送验证码", async () => authApi.sendLoginCode(form.mobile.trim()));
|
||||
startCountdown(data.retry_after_seconds || 60);
|
||||
if (data.debug_code) {
|
||||
showInfoToast(`调试验证码:${data.debug_code}`);
|
||||
} else {
|
||||
showInfoToast("验证码已发送");
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error || "");
|
||||
const retryMatch = message.match(/(\d+)\s*秒后再试/);
|
||||
if (retryMatch) {
|
||||
startCountdown(Number(retryMatch[1]));
|
||||
}
|
||||
sendCodeErrorMessage.value = resolveSendCodeError(error);
|
||||
} finally {
|
||||
sending.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (submitting.value) return;
|
||||
if (!bindTicket.value) {
|
||||
showInfoToast("微信绑定凭证已失效,请重新授权");
|
||||
uni.redirectTo({ url: `/pages/auth/login${redirect.value ? `?redirect=${encodeURIComponent(redirect.value)}` : ""}` });
|
||||
return;
|
||||
}
|
||||
if (!validateMobile()) return;
|
||||
if (!/^\d{6}$/.test(form.code.trim())) {
|
||||
showInfoToast("请输入 6 位验证码");
|
||||
return;
|
||||
}
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
const result = await withLoading("正在绑定", async () =>
|
||||
authApi.bindWechatMobile({
|
||||
bind_ticket: bindTicket.value,
|
||||
mobile: form.mobile.trim(),
|
||||
code: form.code.trim(),
|
||||
}),
|
||||
);
|
||||
setUserToken(result.token);
|
||||
clearWechatBindSession();
|
||||
appraisalStore.resetForNewFlow();
|
||||
showInfoToast("绑定成功");
|
||||
navigateAfterLogin(redirect.value || "/pages/mine/index");
|
||||
} catch (error) {
|
||||
showErrorToast(error, "绑定失败");
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function useMobileLogin() {
|
||||
clearWechatBindSession();
|
||||
suppressNextWechatOAuth();
|
||||
uni.redirectTo({ url: `/pages/auth/login${redirect.value ? `?redirect=${encodeURIComponent(redirect.value)}` : ""}` });
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
redirect.value = String(options?.redirect || "");
|
||||
bindTicket.value = getWechatBindTicket();
|
||||
profile.value = getWechatBindProfile();
|
||||
restoreCountdown();
|
||||
|
||||
if (!bindTicket.value) {
|
||||
showInfoToast("微信绑定凭证已失效,请重新授权");
|
||||
uni.redirectTo({ url: `/pages/auth/login${redirect.value ? `?redirect=${encodeURIComponent(redirect.value)}` : ""}` });
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => form.mobile,
|
||||
() => {
|
||||
sendCodeErrorMessage.value = "";
|
||||
},
|
||||
);
|
||||
|
||||
onUnmounted(clearCountdown);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="bind-page">
|
||||
<view class="bind-shell">
|
||||
<view class="bind-hero">
|
||||
<view class="bind-brand-row">
|
||||
<view class="bind-brand-mark">安</view>
|
||||
<view>
|
||||
<view class="bind-brand-title">安心验</view>
|
||||
<view class="bind-brand-subtitle">绑定手机号后即可完成微信登录</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-profile">
|
||||
<image v-if="displayAvatar" class="bind-profile__avatar" :src="displayAvatar" mode="aspectFill" />
|
||||
<view v-else class="bind-profile__avatar bind-profile__avatar--text">微</view>
|
||||
<view>
|
||||
<view class="bind-profile__name">{{ displayName }}</view>
|
||||
<view class="bind-profile__desc">首次微信登录需验证手机号</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-panel">
|
||||
<view class="bind-title">绑定手机号</view>
|
||||
<view class="bind-desc">同一手机号已存在账号时,将直接关联到原账号,不会创建重复账号。</view>
|
||||
|
||||
<view class="bind-form">
|
||||
<view class="bind-field">
|
||||
<view class="bind-field__label">手机号</view>
|
||||
<view class="bind-input-wrap">
|
||||
<input v-model="form.mobile" class="bind-input" maxlength="11" type="number" placeholder="请输入手机号" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-field">
|
||||
<view class="bind-field__label">验证码</view>
|
||||
<view class="bind-code-row">
|
||||
<view class="bind-input-wrap bind-code-row__input">
|
||||
<input v-model="form.code" class="bind-input" maxlength="6" type="number" placeholder="请输入 6 位验证码" />
|
||||
</view>
|
||||
<view :class="['bind-code-btn', countdown > 0 ? 'bind-code-btn--disabled' : '']" @click="handleSendCode">
|
||||
{{ sending ? "发送中..." : sendButtonText }}
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="sendCodeErrorMessage" class="bind-error-banner">{{ sendCodeErrorMessage }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="bind-actions">
|
||||
<view class="btn btn--secondary bind-actions__button" @click="useMobileLogin">手机号登录</view>
|
||||
<view :class="['btn', 'btn--primary', 'bind-actions__button', submitting ? 'btn--disabled' : '']" @click="handleSubmit">
|
||||
{{ submitting ? "绑定中..." : "完成绑定" }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bind-page {
|
||||
min-height: 100vh;
|
||||
padding: 36rpx 28rpx 48rpx;
|
||||
background: #f2f2f4;
|
||||
}
|
||||
|
||||
.bind-shell {
|
||||
display: grid;
|
||||
gap: 28rpx;
|
||||
}
|
||||
|
||||
.bind-hero,
|
||||
.bind-panel {
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 16rpx;
|
||||
background: #ffffff;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.bind-hero {
|
||||
padding: 36rpx 34rpx;
|
||||
}
|
||||
|
||||
.bind-brand-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.bind-brand-mark {
|
||||
width: 78rpx;
|
||||
height: 78rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #edbd00;
|
||||
color: #ffffff;
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
line-height: 78rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bind-brand-title {
|
||||
color: #252527;
|
||||
font-size: 38rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.bind-brand-subtitle {
|
||||
margin-top: 8rpx;
|
||||
color: #777;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bind-profile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
margin-top: 34rpx;
|
||||
padding: 22rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #edf7f0;
|
||||
border: 1px solid rgba(47, 107, 79, 0.14);
|
||||
}
|
||||
|
||||
.bind-profile__avatar {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border-radius: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bind-profile__avatar--text {
|
||||
background: #2f6b4f;
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
line-height: 76rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bind-profile__name {
|
||||
color: #244f3b;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.bind-profile__desc {
|
||||
margin-top: 6rpx;
|
||||
color: #4f7662;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.bind-panel {
|
||||
padding: 30rpx 28rpx;
|
||||
}
|
||||
|
||||
.bind-title {
|
||||
color: #252527;
|
||||
font-size: 44rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.16;
|
||||
}
|
||||
|
||||
.bind-desc {
|
||||
margin-top: 14rpx;
|
||||
color: #666;
|
||||
font-size: 26rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.bind-form {
|
||||
display: grid;
|
||||
gap: 24rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.bind-field__label {
|
||||
margin-bottom: 12rpx;
|
||||
color: #252527;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bind-input-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 92rpx;
|
||||
padding: 0 24rpx;
|
||||
border: 1px solid #ededf0;
|
||||
border-radius: 16rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.bind-input {
|
||||
width: 100%;
|
||||
color: #252527;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.bind-code-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 212rpx;
|
||||
gap: 14rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bind-code-row__input {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bind-code-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 92rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(237, 189, 0, 0.12);
|
||||
color: #c89b00;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
border: 1px solid rgba(237, 189, 0, 0.24);
|
||||
}
|
||||
|
||||
.bind-code-btn--disabled {
|
||||
opacity: 0.52;
|
||||
}
|
||||
|
||||
.bind-error-banner {
|
||||
margin-top: 14rpx;
|
||||
padding: 18rpx 20rpx;
|
||||
border-radius: 16rpx;
|
||||
background: rgba(159, 59, 50, 0.08);
|
||||
border: 1px solid rgba(159, 59, 50, 0.16);
|
||||
color: #9f3b32;
|
||||
font-size: 22rpx;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.bind-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
|
||||
.bind-actions__button {
|
||||
min-width: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,7 @@ import { reportDetailFallback } from "../../mocks/app";
|
||||
import { resolveErrorMessage } from "../../utils/feedback";
|
||||
|
||||
type ReportTab = "product" | "trace";
|
||||
type ProductDisplayItem = ReportDetailData["product_display"]["items"][number];
|
||||
|
||||
const detail = ref<ReportDetailData>(reportDetailFallback);
|
||||
const downloading = ref(false);
|
||||
@@ -37,12 +38,35 @@ const institutionName = computed(() =>
|
||||
|| "-",
|
||||
);
|
||||
const productItems = computed(() => {
|
||||
const items = detail.value.product_display?.items || [];
|
||||
if (items.length) return items;
|
||||
return [
|
||||
{ label: "检测结论", value: detail.value.result_info.result_text || "-", remark: detail.value.result_info.result_desc || "" },
|
||||
{ label: "品牌", value: detail.value.product_info.brand_name || "-" },
|
||||
];
|
||||
const items: ProductDisplayItem[] = [];
|
||||
const displayItems = detail.value.product_display?.items || [];
|
||||
const baseItems = displayItems.length
|
||||
? displayItems
|
||||
: [
|
||||
{ label: "检测结论", value: detail.value.result_info.result_text || "-", remark: detail.value.result_info.result_desc || "" },
|
||||
{ label: "品类", value: detail.value.product_info.category_name || "" },
|
||||
{ label: "品牌", value: detail.value.product_info.brand_name || "" },
|
||||
{ label: "颜色", value: detail.value.product_info.color || "" },
|
||||
{ label: "规格/尺寸", value: detail.value.product_info.size_spec || "" },
|
||||
{ label: "序列号/编码", value: detail.value.product_info.serial_no || "" },
|
||||
];
|
||||
|
||||
for (const item of baseItems) {
|
||||
appendProductItem(items, item.label, item.value, item.remark);
|
||||
}
|
||||
|
||||
appendProductItem(
|
||||
items,
|
||||
"服务类型",
|
||||
detail.value.report_header.service_provider_text || serviceProviderText(detail.value.report_header.service_provider),
|
||||
);
|
||||
|
||||
const appraiserName = textValue(detail.value.appraisal_info?.appraiser_name)
|
||||
|| textValue(detail.value.appraisal_info?.reviewer_name)
|
||||
|| textValue(detail.value.report_header.report_entry_admin_name);
|
||||
appendProductItem(items, "鉴定师", appraiserName);
|
||||
|
||||
return items;
|
||||
});
|
||||
const publishTime = computed(() => detail.value.report_header.publish_time || "-");
|
||||
const resultItem = computed(() => {
|
||||
@@ -74,6 +98,26 @@ const zhongjianImageFiles = computed(() => zhongjianReportFiles.value.filter((it
|
||||
const zhongjianOtherFiles = computed(() => zhongjianReportFiles.value.filter((item) => item.file_type !== "image"));
|
||||
const reportNo = computed(() => detail.value.report_header.report_no || "");
|
||||
|
||||
function appendProductItem(items: ProductDisplayItem[], label: unknown, value: unknown, remark: unknown = "") {
|
||||
const labelText = textValue(label);
|
||||
const valueText = textValue(value);
|
||||
const remarkText = textValue(remark);
|
||||
if (!labelText || (!valueText && !remarkText) || items.some((item) => item.label === labelText)) return;
|
||||
items.push({
|
||||
label: labelText,
|
||||
value: valueText || "-",
|
||||
remark: remarkText,
|
||||
});
|
||||
}
|
||||
|
||||
function textValue(value: unknown) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function serviceProviderText(serviceProvider: string) {
|
||||
return serviceProvider === "zhongjian" ? "中检鉴定" : "实物鉴定";
|
||||
}
|
||||
|
||||
function evidenceTypeText(fileType: string) {
|
||||
if (fileType === "video") return "视频";
|
||||
if (fileType === "pdf") return "PDF";
|
||||
@@ -294,6 +338,7 @@ onLoad(async (options) => {
|
||||
|
||||
<template v-else>
|
||||
<view class="report-shell">
|
||||
<view class="report-shell__watermark" aria-hidden="true"></view>
|
||||
<view class="report-cover">
|
||||
<swiper v-if="reportImages.length" class="report-cover__swiper" indicator-dots circular>
|
||||
<swiper-item v-for="item in reportImages" :key="item.file_url || item.file_id">
|
||||
@@ -334,8 +379,6 @@ onLoad(async (options) => {
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab === 'product'" class="report-panel">
|
||||
<view class="report-watermark" aria-hidden="true"></view>
|
||||
|
||||
<view class="report-result">
|
||||
<view class="report-result__content">
|
||||
<view class="report-result__label">{{ resultItem.label }}</view>
|
||||
@@ -343,8 +386,8 @@ onLoad(async (options) => {
|
||||
<view v-if="resultItem.remark" class="report-result__desc">{{ resultItem.remark }}</view>
|
||||
</view>
|
||||
<view class="report-seal">
|
||||
<text class="report-seal__brand">ANXINYAN</text>
|
||||
<text class="report-seal__main">可信</text>
|
||||
<text class="report-seal__brand">安心验</text>
|
||||
<text class="report-seal__main">鉴定</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -490,6 +533,26 @@ onLoad(async (options) => {
|
||||
box-shadow: 0 18rpx 48rpx rgba(31, 36, 48, 0.08);
|
||||
}
|
||||
|
||||
.report-shell__watermark {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
top: 374rpx;
|
||||
left: 28rpx;
|
||||
right: 28rpx;
|
||||
height: 560rpx;
|
||||
background: url("../../static/report/report-watermark.svg") center / 100% 100% no-repeat;
|
||||
opacity: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.report-cover,
|
||||
.report-meta,
|
||||
.report-tabs,
|
||||
.report-panel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.report-cover {
|
||||
height: 356rpx;
|
||||
margin: 28rpx 28rpx 0;
|
||||
@@ -614,28 +677,14 @@ onLoad(async (options) => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.report-watermark {
|
||||
position: absolute;
|
||||
top: -6rpx;
|
||||
left: 50%;
|
||||
width: 520rpx;
|
||||
height: 430rpx;
|
||||
border-radius: 50%;
|
||||
opacity: 0.46;
|
||||
transform: translateX(-50%);
|
||||
background:
|
||||
repeating-radial-gradient(ellipse at center, rgba(230, 195, 79, 0.2) 0, rgba(230, 195, 79, 0.2) 2rpx, transparent 3rpx, transparent 17rpx),
|
||||
repeating-conic-gradient(from 0deg, rgba(230, 195, 79, 0.12) 0deg 8deg, transparent 8deg 16deg);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.report-result {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 24rpx;
|
||||
padding: 10rpx 0 30rpx;
|
||||
min-height: 132rpx;
|
||||
padding: 10rpx 136rpx 30rpx 0;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
border-radius: 0;
|
||||
@@ -672,29 +721,57 @@ onLoad(async (options) => {
|
||||
}
|
||||
|
||||
.report-seal {
|
||||
flex: 0 0 auto;
|
||||
position: absolute;
|
||||
right: 2rpx;
|
||||
bottom: 22rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 104rpx;
|
||||
height: 104rpx;
|
||||
margin-top: 2rpx;
|
||||
border: 4rpx solid rgba(56, 164, 73, 0.8);
|
||||
width: 106rpx;
|
||||
height: 106rpx;
|
||||
border: 4rpx solid rgba(40, 151, 73, 0.82);
|
||||
border-radius: 999rpx;
|
||||
color: #39a54b;
|
||||
transform: rotate(-10deg);
|
||||
background: rgba(255, 255, 255, 0.42);
|
||||
color: #239245;
|
||||
box-shadow: inset 0 0 0 4rpx rgba(40, 151, 73, 0.1);
|
||||
transform: rotate(-9deg);
|
||||
}
|
||||
|
||||
.report-seal::before {
|
||||
position: absolute;
|
||||
inset: 10rpx;
|
||||
border: 2rpx solid rgba(40, 151, 73, 0.58);
|
||||
border-radius: inherit;
|
||||
content: "";
|
||||
}
|
||||
|
||||
.report-seal::after {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 12rpx;
|
||||
width: 34rpx;
|
||||
height: 5rpx;
|
||||
border-radius: 999rpx;
|
||||
background: currentColor;
|
||||
content: "";
|
||||
opacity: 0.7;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.report-seal__brand {
|
||||
font-size: 16rpx;
|
||||
font-weight: 800;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
font-size: 18rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.report-seal__main {
|
||||
margin-top: 8rpx;
|
||||
font-size: 28rpx;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 9rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 75 KiB |
36
user-app/src/static/report/report-watermark.svg
Normal file
36
user-app/src/static/report/report-watermark.svg
Normal file
@@ -0,0 +1,36 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 584">
|
||||
<g fill="none" stroke="#E5BE39" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="320" cy="292" r="248" opacity=".16" stroke-width="2"/>
|
||||
<circle cx="320" cy="292" r="210" opacity=".14" stroke-width="2"/>
|
||||
<circle cx="320" cy="292" r="170" opacity=".12" stroke-width="2"/>
|
||||
<circle cx="320" cy="292" r="118" opacity=".11" stroke-width="2"/>
|
||||
<circle cx="320" cy="292" r="74" opacity=".12" stroke-width="2"/>
|
||||
|
||||
<g opacity=".11" stroke-width="1.6">
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(15 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(30 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(45 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(60 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(75 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(90 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(105 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(120 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(135 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(150 320 292)"/>
|
||||
<ellipse cx="320" cy="292" rx="58" ry="234" transform="rotate(165 320 292)"/>
|
||||
</g>
|
||||
|
||||
<g opacity=".18" stroke-width="2">
|
||||
<path d="M320 88c34 54 69 86 128 106-59 20-94 52-128 106-34-54-69-86-128-106 59-20 94-52 128-106Z"/>
|
||||
<path d="M320 284c28 45 58 73 107 90-49 17-79 45-107 90-28-45-58-73-107-90 49-17 79-45 107-90Z"/>
|
||||
</g>
|
||||
|
||||
<g opacity=".1" stroke-width="1.4">
|
||||
<path d="M96 292h448"/>
|
||||
<path d="M320 68v448"/>
|
||||
<path d="M160 132l320 320"/>
|
||||
<path d="M480 132 160 452"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1,5 +1,9 @@
|
||||
const TOKEN_KEY = "anxinyan_user_token";
|
||||
const LOGIN_REDIRECT_KEY = "anxinyan_user_login_redirect";
|
||||
const WECHAT_BIND_TICKET_KEY = "anxinyan_wechat_bind_ticket";
|
||||
const WECHAT_BIND_PROFILE_KEY = "anxinyan_wechat_bind_profile";
|
||||
const WECHAT_OAUTH_STATE_KEY = "anxinyan_wechat_oauth_state";
|
||||
const WECHAT_OAUTH_SUPPRESS_KEY = "anxinyan_wechat_oauth_suppress_once";
|
||||
|
||||
const TABBAR_PAGES = new Set([
|
||||
"/pages/home/index",
|
||||
@@ -16,6 +20,7 @@ const PUBLIC_PAGES = new Set([
|
||||
"/pages/verify/result",
|
||||
"/pages/material-tag/detail",
|
||||
"/pages/auth/login",
|
||||
"/pages/auth/wechat-bind",
|
||||
]);
|
||||
|
||||
let redirecting = false;
|
||||
@@ -56,6 +61,12 @@ export function isLoggedIn() {
|
||||
return getUserToken() !== "";
|
||||
}
|
||||
|
||||
export function rememberLoginRedirect(targetUrl: string) {
|
||||
if (targetUrl) {
|
||||
uni.setStorageSync(LOGIN_REDIRECT_KEY, targetUrl);
|
||||
}
|
||||
}
|
||||
|
||||
export function buildAuthHeaders(headers: Record<string, string> = {}) {
|
||||
const token = getUserToken();
|
||||
if (!token) {
|
||||
@@ -75,6 +86,61 @@ export function isWechatBrowser() {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getWechatOAuthState() {
|
||||
return String(uni.getStorageSync(WECHAT_OAUTH_STATE_KEY) || "");
|
||||
}
|
||||
|
||||
export function setWechatOAuthState(state: string) {
|
||||
uni.setStorageSync(WECHAT_OAUTH_STATE_KEY, state);
|
||||
}
|
||||
|
||||
export function clearWechatOAuthState() {
|
||||
uni.removeStorageSync(WECHAT_OAUTH_STATE_KEY);
|
||||
}
|
||||
|
||||
export function suppressNextWechatOAuth() {
|
||||
uni.setStorageSync(WECHAT_OAUTH_SUPPRESS_KEY, "1");
|
||||
}
|
||||
|
||||
export function consumeWechatOAuthSuppression() {
|
||||
const suppressed = String(uni.getStorageSync(WECHAT_OAUTH_SUPPRESS_KEY) || "") === "1";
|
||||
if (suppressed) {
|
||||
uni.removeStorageSync(WECHAT_OAUTH_SUPPRESS_KEY);
|
||||
}
|
||||
return suppressed;
|
||||
}
|
||||
|
||||
export function setWechatBindSession(bindTicket: string, profile?: { nickname?: string; avatar?: string }) {
|
||||
uni.setStorageSync(WECHAT_BIND_TICKET_KEY, bindTicket);
|
||||
uni.setStorageSync(WECHAT_BIND_PROFILE_KEY, JSON.stringify(profile || {}));
|
||||
}
|
||||
|
||||
export function getWechatBindTicket() {
|
||||
return String(uni.getStorageSync(WECHAT_BIND_TICKET_KEY) || "");
|
||||
}
|
||||
|
||||
export function getWechatBindProfile() {
|
||||
const raw = String(uni.getStorageSync(WECHAT_BIND_PROFILE_KEY) || "");
|
||||
if (!raw) {
|
||||
return { nickname: "", avatar: "" };
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as { nickname?: string; avatar?: string };
|
||||
return {
|
||||
nickname: String(parsed.nickname || ""),
|
||||
avatar: String(parsed.avatar || ""),
|
||||
};
|
||||
} catch {
|
||||
return { nickname: "", avatar: "" };
|
||||
}
|
||||
}
|
||||
|
||||
export function clearWechatBindSession() {
|
||||
uni.removeStorageSync(WECHAT_BIND_TICKET_KEY);
|
||||
uni.removeStorageSync(WECHAT_BIND_PROFILE_KEY);
|
||||
}
|
||||
|
||||
export function isPublicPage(urlOrPath: string) {
|
||||
const { path } = splitUrl(urlOrPath);
|
||||
return PUBLIC_PAGES.has(path);
|
||||
@@ -100,9 +166,7 @@ export function redirectToLogin(targetUrl?: string) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentUrl) {
|
||||
uni.setStorageSync(LOGIN_REDIRECT_KEY, currentUrl);
|
||||
}
|
||||
rememberLoginRedirect(currentUrl);
|
||||
|
||||
redirecting = true;
|
||||
uni.navigateTo({
|
||||
|
||||
Reference in New Issue
Block a user