284 lines
9.2 KiB
Vue
284 lines
9.2 KiB
Vue
<script setup lang="ts">
|
||
// 代理登录页:复刻旧系统 WebVue views/login/loginAgent.vue
|
||
// 已登录的管理员在「我的」页输入被代理号(目标用户手机号)+ 自己的密码,LoginAgent 成功后覆盖当前会话
|
||
// 后端 LoginAgent 先 Login(data) 用「当前登录者自己的手机号 + 密码」认证,故手机号预填为本人、只读,杜绝填错他人号造成不一致
|
||
// 加密与登录同算法:RsaUserPassword = RSA( guid + MD5(密码) )
|
||
// 视觉与登录页共用一套语言:主题渐变底 + 叶脉树 Logo + 主题色渐变按钮 + 图标内嵌白底胶囊输入框(强调色全部跟随当前主题)
|
||
import { useLocale } from "uview-pro";
|
||
import { computed, ref } from "vue";
|
||
import { brandGlyph, veinGlyph } from "@/common/brand";
|
||
import { backendLocale } from "@/common/http.interceptor";
|
||
import { encryptLoginPassword, makeGuid } from "@/common/pwd-crypto";
|
||
import { useAuthGuard, useThemeGradient, useThemeTokens } from "@/composables";
|
||
import { LoginData as LoginDataClass } from "@/data/auto/loginData";
|
||
import { pageUrl } from "@/router/routes";
|
||
import authService from "@/service/security/authService";
|
||
import { useLoginProfileStore } from "@/stores";
|
||
|
||
const { t } = useLocale();
|
||
|
||
/* 主题接线:底渐变与强调色跟随当前主题(与登录/注册页同口径),不再写死品牌绿 */
|
||
const { themeBackground } = useThemeGradient();
|
||
const { primary, accentGradient, accentSoft, accentShadow } = useThemeTokens();
|
||
const themeVars = computed(() => ({
|
||
"--fg-primary": primary.value,
|
||
"--fg-gradient": accentGradient.value,
|
||
"--fg-soft": accentSoft.value,
|
||
"--fg-shadow": accentShadow.value,
|
||
}));
|
||
|
||
useAuthGuard();
|
||
|
||
const loginProfileStore = useLoginProfileStore();
|
||
|
||
const agentUserName = ref("");
|
||
// 手机号即「当前登录者本人」的号,后端 Login(data) 用它认证,故预填且只读,不让手改
|
||
const mobile = ref(loginProfileStore.userMobile || "");
|
||
const password = ref("");
|
||
const showPwd = ref(false);
|
||
const loading = ref(false);
|
||
|
||
/** 返回「我的」:代理登录从「我的」跳入,优先回退;深链直达无栈时重进「我的」 */
|
||
function goBack(): void {
|
||
if (getCurrentPages().length > 1) {
|
||
uni.navigateBack();
|
||
}
|
||
else {
|
||
uni.reLaunch({ url: pageUrl("mineTab") });
|
||
}
|
||
}
|
||
|
||
async function handleSubmit() {
|
||
if (!agentUserName.value) {
|
||
uni.showToast({ title: t("LoginAgentPage.agentRequired"), icon: "none" });
|
||
return;
|
||
}
|
||
if (!mobile.value) {
|
||
uni.showToast({ title: t("LoginAgentPage.mobileRequired"), icon: "none" });
|
||
return;
|
||
}
|
||
if (!password.value) {
|
||
uni.showToast({ title: t("LoginAgentPage.passwordRequired"), icon: "none" });
|
||
return;
|
||
}
|
||
loading.value = true;
|
||
try {
|
||
const guid = makeGuid();
|
||
|
||
// 1. 取后端 RSA 公钥
|
||
const key = await authService.GetRsaPublicKey();
|
||
|
||
// 2. RSA( guid + MD5(密码) )
|
||
const rsaUserPassword = encryptLoginPassword(password.value, guid, String(key));
|
||
if (!rsaUserPassword) {
|
||
uni.showToast({ title: "密码加密失败,请重试", icon: "none" });
|
||
return;
|
||
}
|
||
|
||
// 3. 代理登录(成功后覆盖当前会话档案与令牌)
|
||
const data = new LoginDataClass();
|
||
data.AgentUserName = agentUserName.value;
|
||
data.UserMobile = mobile.value;
|
||
data.Guid = guid;
|
||
data.RsaUserPassword = rsaUserPassword;
|
||
data.Culture = backendLocale();
|
||
const profile = await authService.LoginAgent(data);
|
||
loginProfileStore.updateLoginProfile(profile);
|
||
|
||
uni.showToast({ title: t("LoginAgentPage.success"), icon: "none" });
|
||
// reLaunch 清空页面栈,避免返回键退回代理登录页
|
||
setTimeout(() => uni.reLaunch({ url: pageUrl("dashboard") }), 800);
|
||
}
|
||
catch {
|
||
// 错误提示已由 http.interceptor 统一处理
|
||
}
|
||
finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<view class="login-page" :style="[{ background: themeBackground }, themeVars]">
|
||
<!-- 背景右上淡叶脉暗纹(纯装饰,低透明) -->
|
||
<image class="login-vein" :src="veinGlyph" mode="aspectFit" />
|
||
|
||
<!-- 品牌位:叶脉树 Logo + 名称 + 「代理登录」副标题 -->
|
||
<view class="login-brand">
|
||
<view class="login-brand__logo">
|
||
<image class="login-brand__glyph" :src="brandGlyph" mode="aspectFit" />
|
||
</view>
|
||
<view class="login-brand__name">
|
||
{{ t("LoginForm.brandName") }}
|
||
</view>
|
||
<view class="login-brand__sub">
|
||
{{ t("LoginAgentPage.title") }}
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 表单:图标内嵌白底圆角胶囊 -->
|
||
<view class="login-form">
|
||
<view class="login-field">
|
||
<u-icon name="account" size="34" color="#9aa0a6" />
|
||
<input v-model="agentUserName" class="login-field__input" :placeholder="t('LoginAgentPage.agentLabel')"
|
||
placeholder-class="login-field__ph">
|
||
</view>
|
||
<view class="login-field">
|
||
<u-icon name="phone" size="34" color="#9aa0a6" />
|
||
<input v-model="mobile" class="login-field__input login-field__input--locked" type="number" disabled
|
||
:placeholder="t('LoginAgentPage.mobileLabel')" placeholder-class="login-field__ph">
|
||
<u-icon name="lock" size="28" color="#b3b8bd" />
|
||
</view>
|
||
<view class="login-field">
|
||
<u-icon name="lock" size="34" color="#9aa0a6" />
|
||
<input v-model="password" class="login-field__input" :password="!showPwd"
|
||
:placeholder="t('LoginAgentPage.passwordLabel')" placeholder-class="login-field__ph"
|
||
@confirm="handleSubmit">
|
||
<u-icon :name="showPwd ? 'eye-fill' : 'eye-off'" size="34" color="#9aa0a6"
|
||
@click="showPwd = !showPwd" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 按钮:代理登录黛翠绿渐变实心,返回白底绿描边 -->
|
||
<view class="login-btn login-btn--submit" @click="handleSubmit">
|
||
<u-loading v-if="loading" mode="circle" color="#ffffff" :size="34" />
|
||
<text v-else>{{ t("LoginAgentPage.submitBut") }}</text>
|
||
</view>
|
||
<view class="login-btn login-btn--ghost" @click="goBack">
|
||
<text>{{ t("LoginAgentPage.backBut") }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
/* 强调色全部经 --fg-* 变量跟随主题(见 script 的 themeVars);此处仅留中性文字色与柔和投影 */
|
||
$ink: #1f2329;
|
||
$soft-shadow: 0 6rpx 20rpx rgba(31, 35, 41, 0.06);
|
||
|
||
.login-page {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
box-sizing: border-box;
|
||
min-height: 100vh;
|
||
padding: 120rpx 60rpx 60rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 右上淡叶脉暗纹:半隐若现,不拦截点击 */
|
||
.login-vein {
|
||
position: absolute;
|
||
top: -40rpx;
|
||
right: -60rpx;
|
||
width: 460rpx;
|
||
height: 460rpx;
|
||
opacity: 0.08;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* ===== 品牌位 ===== */
|
||
.login-brand {
|
||
display: flex;
|
||
z-index: 1;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-bottom: 70rpx;
|
||
}
|
||
|
||
.login-brand__logo {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 120rpx;
|
||
height: 120rpx;
|
||
margin-bottom: 24rpx;
|
||
border-radius: 34rpx;
|
||
background: var(--fg-gradient, linear-gradient(135deg, #47a678, #1f5f47));
|
||
box-shadow: 0 16rpx 36rpx var(--fg-shadow, rgba(31, 95, 71, 0.32));
|
||
}
|
||
|
||
.login-brand__glyph {
|
||
width: 70rpx;
|
||
height: 70rpx;
|
||
}
|
||
|
||
.login-brand__name {
|
||
font-size: 48rpx;
|
||
font-weight: 700;
|
||
letter-spacing: 4rpx;
|
||
color: $ink;
|
||
}
|
||
|
||
.login-brand__sub {
|
||
margin-top: 10rpx;
|
||
font-size: 26rpx;
|
||
letter-spacing: 6rpx;
|
||
color: var(--fg-primary, #2f7d5c);
|
||
}
|
||
|
||
/* ===== 表单 ===== */
|
||
.login-form {
|
||
display: flex;
|
||
z-index: 1;
|
||
flex-direction: column;
|
||
gap: 28rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.login-field {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
padding: 0 32rpx;
|
||
border-radius: 20rpx;
|
||
background-color: #ffffff;
|
||
box-shadow: $soft-shadow;
|
||
}
|
||
|
||
.login-field__input {
|
||
flex: 1;
|
||
height: 92rpx;
|
||
font-size: 30rpx;
|
||
color: $ink;
|
||
}
|
||
|
||
/* 只读手机号:压掉 disabled 默认灰白,保留可读的中性灰,表明「锁定但正常」 */
|
||
.login-field__input--locked {
|
||
color: #6b7280;
|
||
opacity: 1;
|
||
-webkit-text-fill-color: #6b7280;
|
||
}
|
||
|
||
.login-field__ph {
|
||
color: #b3b8bd;
|
||
}
|
||
|
||
/* ===== 按钮 ===== */
|
||
.login-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 92rpx;
|
||
margin-top: 32rpx;
|
||
border-radius: 46rpx;
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
letter-spacing: 8rpx;
|
||
}
|
||
|
||
.login-btn--submit {
|
||
color: #ffffff;
|
||
background: var(--fg-gradient, linear-gradient(135deg, #47a678, #1f5f47));
|
||
box-shadow: 0 16rpx 32rpx var(--fg-shadow, rgba(31, 95, 71, 0.3));
|
||
}
|
||
|
||
.login-btn--ghost {
|
||
margin-top: 24rpx;
|
||
color: var(--fg-primary, #2f7d5c);
|
||
border: 2rpx solid var(--fg-primary, #2f7d5c);
|
||
background-color: #ffffff;
|
||
}
|
||
</style>
|