chore: prepare production release package
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
VITE_API_BASE_URL=http://127.0.0.1:8787
|
||||
VITE_APP_ENV=development
|
||||
VITE_API_BASE_URL=https://test.api.anxinjianyan.com
|
||||
VITE_APP_ENV=test
|
||||
VITE_APP_TITLE=安心验
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"build:mp-harmony": "uni build -p mp-harmony",
|
||||
"sync:mp-config": "php ../server-api/tools/sync_client_configs.php",
|
||||
"build:mp-weixin": "npm run sync:mp-config && uni build -p mp-weixin",
|
||||
"build:mp-weixin:test": "npm run sync:mp-config && uni build --mode test -p mp-weixin",
|
||||
"build:mp-xhs": "uni build -p mp-xhs",
|
||||
"build:quickapp-webview": "uni build -p quickapp-webview",
|
||||
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
|
||||
|
||||
@@ -51,6 +51,12 @@ export interface MiniProgramBindResult {
|
||||
unionid: string;
|
||||
}
|
||||
|
||||
export interface MiniProgramExchangeResult extends WechatExchangeResult {}
|
||||
|
||||
export interface MiniProgramBindMobileResult extends LoginResult {
|
||||
status: "logged_in";
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
sendLoginCode(mobile: string) {
|
||||
return request<SendLoginCodeResult>("/api/app/auth/send-code", {
|
||||
@@ -95,6 +101,22 @@ export const authApi = {
|
||||
data: { code },
|
||||
});
|
||||
},
|
||||
exchangeMiniProgramCode(code: string) {
|
||||
return request<MiniProgramExchangeResult>("/api/app/auth/mini-program/exchange", {
|
||||
method: "POST",
|
||||
data: { code },
|
||||
});
|
||||
},
|
||||
bindMiniProgramMobile(payload: {
|
||||
bind_ticket: string;
|
||||
mobile: string;
|
||||
code: string;
|
||||
}) {
|
||||
return request<MiniProgramBindMobileResult>("/api/app/auth/mini-program/bind-mobile", {
|
||||
method: "POST",
|
||||
data: payload,
|
||||
});
|
||||
},
|
||||
getMe() {
|
||||
return request<{ user_info: AuthUserInfo }>("/api/app/auth/me");
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ 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 { resolveErrorMessage, showErrorToast, showInfoToast, withLoading } from "../../utils/feedback";
|
||||
import {
|
||||
clearWechatBindSession,
|
||||
clearWechatOAuthState,
|
||||
@@ -26,6 +26,8 @@ const sending = ref(false);
|
||||
const submitting = ref(false);
|
||||
const wechatProcessing = ref(false);
|
||||
const wechatMessage = ref("");
|
||||
const miniProgramProcessing = ref(false);
|
||||
const miniProgramMessage = ref("");
|
||||
const countdown = ref(0);
|
||||
const redirect = ref("");
|
||||
const sendCodeErrorMessage = ref("");
|
||||
@@ -44,6 +46,15 @@ const sendButtonText = computed(() => (countdown.value > 0 ? `${countdown.value}
|
||||
const countdownHint = computed(() =>
|
||||
countdown.value > 0 ? `${countdown.value} 秒后可重新发送验证码` : "验证码有效期 5 分钟,请注意查收短信。",
|
||||
);
|
||||
const authorizationStatusVisible = computed(() =>
|
||||
wechatProcessing.value || miniProgramProcessing.value || wechatMessage.value || miniProgramMessage.value,
|
||||
);
|
||||
const authorizationStatusTitle = computed(() =>
|
||||
wechatProcessing.value || miniProgramProcessing.value ? "微信授权登录" : "微信授权提示",
|
||||
);
|
||||
const authorizationStatusDesc = computed(() =>
|
||||
miniProgramMessage.value || wechatMessage.value || "正在打开微信授权",
|
||||
);
|
||||
|
||||
function openAgreement(keyword: "privacy" | "service") {
|
||||
const query = encodeURIComponent(keyword === "privacy" ? "隐私政策" : "服务协议");
|
||||
@@ -275,6 +286,66 @@ async function handleWechatCallback() {
|
||||
}
|
||||
}
|
||||
|
||||
function getMiniProgramLoginCode() {
|
||||
// #ifdef MP-WEIXIN
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
uni.login({
|
||||
provider: "weixin",
|
||||
success: (result) => {
|
||||
const code = String(result.code || "");
|
||||
if (!code) {
|
||||
reject(new Error("小程序登录 code 为空"));
|
||||
return;
|
||||
}
|
||||
resolve(code);
|
||||
},
|
||||
fail: (error) => reject(error),
|
||||
});
|
||||
});
|
||||
// #endif
|
||||
return Promise.reject(new Error("当前环境不支持小程序授权登录"));
|
||||
}
|
||||
|
||||
async function handleMiniProgramAuthorizeLogin() {
|
||||
if (miniProgramProcessing.value || isLoggedIn()) {
|
||||
return;
|
||||
}
|
||||
if (!agreementAccepted.value) {
|
||||
showInfoToast("请先阅读并同意隐私权政策和用户协议");
|
||||
return;
|
||||
}
|
||||
|
||||
miniProgramProcessing.value = true;
|
||||
miniProgramMessage.value = "正在获取小程序授权";
|
||||
try {
|
||||
const code = await getMiniProgramLoginCode();
|
||||
const result = await withLoading("正在授权登录", async () => authApi.exchangeMiniProgramCode(code));
|
||||
|
||||
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?source=mini-program${redirect.value ? `&redirect=${encodeURIComponent(redirect.value)}` : ""}`;
|
||||
uni.redirectTo({ url: bindUrl });
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("小程序授权结果异常,请使用手机号登录");
|
||||
} catch (error) {
|
||||
miniProgramMessage.value = resolveErrorMessage(error, "授权登录失败,可使用手机号登录");
|
||||
showErrorToast(error, "授权登录失败");
|
||||
} finally {
|
||||
miniProgramProcessing.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSendCode() {
|
||||
if (sending.value || countdown.value > 0) return;
|
||||
if (!validateMobile()) return;
|
||||
@@ -387,11 +458,11 @@ onUnmounted(clearCountdown);
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="wechatProcessing || wechatMessage" class="auth-wechat-status">
|
||||
<view v-if="authorizationStatusVisible" 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 class="auth-wechat-status__title">{{ authorizationStatusTitle }}</view>
|
||||
<view class="auth-wechat-status__desc">{{ authorizationStatusDesc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -427,6 +498,16 @@ onUnmounted(clearCountdown);
|
||||
{{ submitting ? "登录中..." : "登录" }}
|
||||
</view>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view
|
||||
:class="['auth-mini-login', miniProgramProcessing ? 'auth-mini-login--disabled' : '']"
|
||||
@click="handleMiniProgramAuthorizeLogin"
|
||||
>
|
||||
<view class="auth-mini-login__icon">微</view>
|
||||
<view class="auth-mini-login__text">{{ miniProgramProcessing ? "授权中..." : "微信授权登录" }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<view class="auth-agreement" @click="toggleAgreement">
|
||||
<view :class="['auth-agreement__check', agreementAccepted ? 'auth-agreement__check--active' : '']"></view>
|
||||
<view class="auth-agreement__text">
|
||||
@@ -718,6 +799,43 @@ onUnmounted(clearCountdown);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.auth-mini-login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
height: 68rpx;
|
||||
margin-top: 22rpx;
|
||||
border: 1rpx solid rgba(47, 107, 79, 0.28);
|
||||
border-radius: 14rpx;
|
||||
background: #f5fbf7;
|
||||
color: #2f6b4f;
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
line-height: 68rpx;
|
||||
}
|
||||
|
||||
.auth-mini-login__icon {
|
||||
flex-shrink: 0;
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #2f6b4f;
|
||||
color: #ffffff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 900;
|
||||
line-height: 38rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.auth-mini-login__text {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.auth-mini-login--disabled {
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.auth-agreement {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -727,6 +845,10 @@ onUnmounted(clearCountdown);
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.auth-mini-login + .auth-agreement {
|
||||
margin-top: 96rpx;
|
||||
}
|
||||
|
||||
.auth-agreement__check {
|
||||
flex-shrink: 0;
|
||||
width: 22rpx;
|
||||
@@ -790,5 +912,9 @@ onUnmounted(clearCountdown);
|
||||
.auth-agreement {
|
||||
margin-top: 116rpx;
|
||||
}
|
||||
|
||||
.auth-mini-login + .auth-agreement {
|
||||
margin-top: 72rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,9 +13,11 @@ import {
|
||||
suppressNextWechatOAuth,
|
||||
} from "../../utils/auth";
|
||||
|
||||
type BindSource = "wechat-h5" | "mini-program";
|
||||
const COUNTDOWN_STORAGE_KEY = "anxinyan_wechat_bind_code_countdown_expire_at";
|
||||
|
||||
const redirect = ref("");
|
||||
const source = ref<BindSource>("wechat-h5");
|
||||
const sending = ref(false);
|
||||
const submitting = ref(false);
|
||||
const countdown = ref(0);
|
||||
@@ -34,6 +36,12 @@ 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 || "");
|
||||
const brandSubtitle = computed(() =>
|
||||
source.value === "mini-program" ? "绑定手机号后即可完成小程序授权登录" : "绑定手机号后即可完成微信登录",
|
||||
);
|
||||
const profileDesc = computed(() =>
|
||||
source.value === "mini-program" ? "首次小程序授权登录需验证手机号" : "首次微信登录需验证手机号",
|
||||
);
|
||||
|
||||
function resolveSendCodeError(error: unknown) {
|
||||
const message = error instanceof Error ? error.message : String(error || "");
|
||||
@@ -144,13 +152,22 @@ async function handleSubmit() {
|
||||
|
||||
submitting.value = true;
|
||||
try {
|
||||
const result = await withLoading("正在绑定", async () =>
|
||||
authApi.bindWechatMobile({
|
||||
const payload = {
|
||||
bind_ticket: bindTicket.value,
|
||||
mobile: form.mobile.trim(),
|
||||
code: form.code.trim(),
|
||||
};
|
||||
const result = await withLoading("正在绑定", async () => {
|
||||
if (source.value === "mini-program") {
|
||||
return authApi.bindMiniProgramMobile(payload);
|
||||
}
|
||||
|
||||
return authApi.bindWechatMobile({
|
||||
bind_ticket: bindTicket.value,
|
||||
mobile: form.mobile.trim(),
|
||||
code: form.code.trim(),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
setUserToken(result.token);
|
||||
clearWechatBindSession();
|
||||
appraisalStore.resetForNewFlow();
|
||||
@@ -171,6 +188,7 @@ function useMobileLogin() {
|
||||
|
||||
onLoad((options) => {
|
||||
redirect.value = String(options?.redirect || "");
|
||||
source.value = String(options?.source || "") === "mini-program" ? "mini-program" : "wechat-h5";
|
||||
bindTicket.value = getWechatBindTicket();
|
||||
profile.value = getWechatBindProfile();
|
||||
restoreCountdown();
|
||||
@@ -199,7 +217,7 @@ onUnmounted(clearCountdown);
|
||||
<view class="bind-brand-mark">安</view>
|
||||
<view>
|
||||
<view class="bind-brand-title">安心验</view>
|
||||
<view class="bind-brand-subtitle">绑定手机号后即可完成微信登录</view>
|
||||
<view class="bind-brand-subtitle">{{ brandSubtitle }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -208,7 +226,7 @@ onUnmounted(clearCountdown);
|
||||
<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 class="bind-profile__desc">{{ profileDesc }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -62,7 +62,6 @@ const productItems = computed(() => {
|
||||
{ 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) {
|
||||
@@ -97,8 +96,7 @@ const productSpecItems = computed(() => {
|
||||
{ label: "品牌", value: detail.value.product_info.brand_name || "-", remark: "" },
|
||||
{ label: "颜色", value: detail.value.product_info.color || "-", remark: "" },
|
||||
{ label: "规格/尺寸", value: detail.value.product_info.size_spec || "-", remark: "" },
|
||||
{ label: "序列号/编码", value: detail.value.product_info.serial_no || "-", remark: "" },
|
||||
].filter((item) => item.value && item.value !== "-");
|
||||
].filter((item) => !isHiddenProductItemLabel(item.label) && item.value && item.value !== "-");
|
||||
});
|
||||
const traceInfoVisible = computed(() => Boolean(detail.value.trace_info?.visible || detail.value.report_header.trace_info_visible));
|
||||
const centerTabVisible = computed(() => {
|
||||
@@ -128,7 +126,13 @@ function appendProductItem(items: ProductDisplayItem[], label: unknown, value: u
|
||||
const labelText = textValue(label);
|
||||
const valueText = textValue(value);
|
||||
const remarkText = textValue(remark);
|
||||
if (labelText === "鉴定师" || !labelText || (!valueText && !remarkText) || items.some((item) => item.label === labelText)) return;
|
||||
if (
|
||||
labelText === "鉴定师"
|
||||
|| isHiddenProductItemLabel(labelText)
|
||||
|| !labelText
|
||||
|| (!valueText && !remarkText)
|
||||
|| items.some((item) => item.label === labelText)
|
||||
) return;
|
||||
items.push({
|
||||
label: labelText,
|
||||
value: valueText || "-",
|
||||
@@ -140,6 +144,11 @@ function textValue(value: unknown) {
|
||||
return String(value ?? "").trim();
|
||||
}
|
||||
|
||||
function isHiddenProductItemLabel(label: string) {
|
||||
const normalized = label.replace(/\s+/g, "");
|
||||
return normalized === "序列号" || normalized === "序列号/编码";
|
||||
}
|
||||
|
||||
function serviceProviderText(serviceProvider: string) {
|
||||
return serviceProvider === "zhongjian" ? "中检鉴定" : "实物鉴定";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user