Files
Docker/uniapp/youjiala/src/pages/login/login-agent.vue
T
2026-10-17 21:08:10 +08:00

284 lines
9.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>