新增 有家相册 uni-app 前端(uniapp/youjiala,H5 / 微信小程序 / App 三端)
从旧 WebVue 移植的地基部分: - 登录页(RSA+MD5 密码加密、redirect 回跳、中英文切换)与登录后落地页 - 401 / 403 / 404 错误页 + error-panel 通用外壳(文案走 ErrorPage 语言包) - 登录守卫双链路:uni.addInterceptor 拦站内跳转,页面级 onShow 复核冷启动深链 - 单一请求链路 common/http.interceptor:JWT 唯一来源、401 并发刷新单例、 业务异常识别、HTTP 403 跳无权限页 - H5 干净短地址:initRouter 前向 __uniRoutes 追加克隆记录(router/h5-clean-url) - 路由登记表 router/routes.ts(PAGE_PATHS / pageUrl / PAGE_PERMISSIONS) 与新增页面四步登记约定(src/md/page-routing.md) - i18n:uView Pro 语言包 + 后端词条合并,双取值(后端 zh-cn/en,包名 zh-CN/en-US) - 后端接口层 service/ 与数据模型 data/auto/ 为生成器产物,不手改 不入库的内容: - 摘除脚手架残留的 Harmony 签名密码(manifest.json signingConfigs 与 harmony-configs/build-profile.json5 的 keyPassword / storePassword 已置空) - 忽略 node_modules、dist、unpackage、.hbuilderx、.agents、pnpm-lock.yaml 及根目录临时截图
This commit is contained in:
1 parent
74757f9eb4
commit
6a8258ac4b
274 files changed
+30291
No files matched your search
@@ -0,0 +1,183 @@
|
||||
<script setup lang="ts">
|
||||
// 最简登录页:复刻旧系统 WebVue 的登录流程,用于联调 CloudBuilder 后端接口
|
||||
// 流程:GetRsaPublicKey -> RsaUserPassword = RSA(Guid + MD5(密码)) -> Login -> 存 JWT
|
||||
// 加密已抽到 @/common/pwd-crypto(wxmp-rsa 纯 JS 实现,H5/小程序/App 全端可用)
|
||||
import type { LoginData } from "@/data/auto/loginData";
|
||||
import type { LoginProfile } from "@/data/auto/loginProfile";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { useLocale } from "uview-pro";
|
||||
import { computed, ref } from "vue";
|
||||
import { backendLocale, baseUrl } from "@/common/http.interceptor";
|
||||
import { encryptLoginPassword } from "@/common/pwd-crypto";
|
||||
import { LoginData as LoginDataClass } from "@/data/auto/loginData";
|
||||
import { reloadLocales } from "@/locale";
|
||||
import { decodeQueryPath, pageUrl, resolvePageName } from "@/router/routes";
|
||||
import authService from "@/service/security/authService";
|
||||
import { useLoginProfileStore } from "@/stores";
|
||||
|
||||
const userMobile = ref("");
|
||||
const password = ref("");
|
||||
const loading = ref(false);
|
||||
const loginProfileStore = useLoginProfileStore();
|
||||
|
||||
/**
|
||||
* 登录成功后的落地地址:被守卫送去登录页时由 ?redirect= 带过来(见 nav-guard 的 goLoginWithRedirect)。
|
||||
* 只接受 PAGE_PATHS 里认得出的地址,避免 ?redirect= 把人送到站外或不存在的路径
|
||||
*/
|
||||
const redirectUrl = ref(pageUrl("dashboard"));
|
||||
onLoad((options) => {
|
||||
const target = decodeQueryPath(options?.redirect);
|
||||
if (resolvePageName(target)) {
|
||||
redirectUrl.value = target;
|
||||
}
|
||||
});
|
||||
|
||||
// 登录页中英文切换:走 uView Pro 语言包(t / setLocale),语言包 name 为 zh-CN / en-US
|
||||
const { t, setLocale, currentLocale } = useLocale();
|
||||
const isZh = computed(() => (currentLocale.value?.name ?? "zh-CN") !== "en-US");
|
||||
function toggleLang() {
|
||||
setLocale(isZh.value ? "en-US" : "zh-CN");
|
||||
// 开发构建顺手重拉数据库翻译:改完 DB 词条点一下切换就能看到新值,无需整页刷新
|
||||
// (生产构建 import.meta.env.DEV 为 false,该分支编译期即被剔除)
|
||||
if (import.meta.env.DEV) {
|
||||
reloadLocales();
|
||||
}
|
||||
}
|
||||
|
||||
/** 取消:清空输入 */
|
||||
function handleCancel() {
|
||||
userMobile.value = "";
|
||||
password.value = "";
|
||||
}
|
||||
|
||||
// 开发构建显示「登录测试」标记,发布构建只显示后端地址(import.meta.env.DEV 编译期确定)
|
||||
const isDev = import.meta.env.DEV;
|
||||
|
||||
/** 生成 v4 风格 Guid(无需引入 uuid 包,与旧系统 uuidv4() 等效) */
|
||||
function makeGuid(): string {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleLogin() {
|
||||
if (!userMobile.value || !password.value) {
|
||||
uni.showToast({ title: "请输入手机号或者账号名和密码", icon: "none" });
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const guid = makeGuid();
|
||||
|
||||
// 1. 取后端 RSA 公钥(GET /Auth/GetRsaPublicKey,免鉴权)
|
||||
const key = await authService.GetRsaPublicKey();
|
||||
|
||||
// 2. 密码先 MD5 再与 Guid 拼接后 RSA 加密(算法与旧系统一致,库换为跨端的 wxmp-rsa)
|
||||
const rsaUserPassword = encryptLoginPassword(password.value, guid, String(key));
|
||||
if (!rsaUserPassword) {
|
||||
uni.showToast({ title: "密码加密失败,请重试", icon: "none" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 登录(失败时拦截器已统一弹窗提示,这里只处理成功路径)
|
||||
const data: LoginData = new LoginDataClass();
|
||||
data.UserMobile = userMobile.value;
|
||||
data.Guid = guid;
|
||||
data.RsaUserPassword = rsaUserPassword;
|
||||
data.Culture = backendLocale();
|
||||
const profile: LoginProfile = await authService.Login(data);
|
||||
|
||||
// 4. 存登录档案:档案字段进 store,Token 交由请求层保管(此后自动带 Bearer 头 + 401 自动续期)
|
||||
loginProfileStore.updateLoginProfile(profile);
|
||||
|
||||
uni.showToast({ title: `欢迎,${profile.UserLocalName || profile.Username || profile.UserMobile}`, icon: "none" });
|
||||
// 回到被拦前的页面(默认 dashboard):reLaunch 会清空页面栈,避免返回键退回登录页
|
||||
setTimeout(() => uni.reLaunch({ url: redirectUrl.value }), 800);
|
||||
}
|
||||
catch {
|
||||
// 错误提示已由 http.interceptor 统一处理
|
||||
}
|
||||
finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="login-page">
|
||||
<view class="login-title">
|
||||
有家相册
|
||||
</view>
|
||||
<view class="login-subtitle">
|
||||
{{ isDev ? `登录测试 · ${baseUrl}` : baseUrl }}
|
||||
</view>
|
||||
|
||||
<view class="login-form">
|
||||
<u-input v-model="userMobile" :placeholder="t('LoginForm.userLabel')" border clearable />
|
||||
<u-input
|
||||
v-model="password" type="password" :placeholder="t('LoginForm.passwordLabel')" border :password-icon="true"
|
||||
@confirm="handleLogin"
|
||||
/>
|
||||
<u-button type="primary" shape="circle" :loading="loading" :text="t('LoginForm.loginBut')" @click="handleLogin" />
|
||||
<u-button shape="circle" :text="t('LoginForm.cancelBut')" @click="handleCancel" />
|
||||
</view>
|
||||
|
||||
<view class="login-lang" @click="toggleLang">
|
||||
<text class="login-lang__label">
|
||||
{{ t('LoginForm.cultureLabel') }}
|
||||
</text>
|
||||
<text class="login-lang__switch">
|
||||
{{ isZh ? 'English' : '中文' }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 200rpx 60rpx 0;
|
||||
background: linear-gradient(160deg, rgba(41, 121, 255, 0.08), rgba(25, 190, 107, 0.06));
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 56rpx;
|
||||
font-weight: 700;
|
||||
color: var(--u-type-primary);
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
margin: 16rpx 0 80rpx;
|
||||
font-size: 24rpx;
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40rpx;
|
||||
}
|
||||
|
||||
.login-lang {
|
||||
margin-top: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.login-lang__label {
|
||||
color: $u-tips-color;
|
||||
}
|
||||
|
||||
.login-lang__switch {
|
||||
color: var(--u-type-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user