- login: catch补齐toast,修复密码错误静默失败 - detail: formatUserName渲染当前位置,dictVersion驱动响应式重绘 - detail: 留言板改为悬浮按钮+底部抽屉,未读徽标逻辑 - TaskSwipeCards: lanes终极递归算法,SPAWN不限层级开辟新Tab,先序push修复Tab顺序 - TreeCanvas: 父子相对坐标延伸算法,替代旧版奇偶距离递增
80 lines
3.0 KiB
Vue
80 lines
3.0 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="header">
|
||
<text class="logo">🏭</text>
|
||
<text class="title">Track</text>
|
||
<text class="version">{{ appVersion }}</text>
|
||
</view>
|
||
|
||
<view class="form">
|
||
<input v-model="username" class="input" placeholder="用户名" />
|
||
<input v-model="password" class="input" type="password" placeholder="密码" />
|
||
<button class="login-btn" @tap="handleLogin" :disabled="loading">
|
||
{{ loading ? '登录中...' : '登 录' }}
|
||
</button>
|
||
<text v-if="error" class="error">{{ error }}</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from "vue";
|
||
import { post } from "../../utils/request";
|
||
|
||
// 🚀 动态版本号 — 热更新后自动变化(路由由 App.vue onLaunch 统一接管)
|
||
const appVersion = ref("T1.0.2");
|
||
onMounted(() => {
|
||
try {
|
||
const v = uni.getSystemInfoSync().appWgtVersion || uni.getSystemInfoSync().appVersion;
|
||
if (v) appVersion.value = v.startsWith("T") ? v : "T" + v;
|
||
} catch {}
|
||
});
|
||
|
||
const username = ref("");
|
||
const password = ref("");
|
||
const loading = ref(false);
|
||
const error = ref("");
|
||
|
||
async function handleLogin() {
|
||
if (!username.value || !password.value) {
|
||
error.value = "请输入用户名和密码";
|
||
return;
|
||
}
|
||
loading.value = true;
|
||
error.value = "";
|
||
try {
|
||
const res = await post("/auth/login", {
|
||
username: username.value,
|
||
password: password.value,
|
||
});
|
||
// 双 Token 存储
|
||
uni.setStorageSync("access_token", res.access_token);
|
||
uni.setStorageSync("refresh_token", res.refresh_token);
|
||
uni.setStorageSync("user", JSON.stringify(res.user));
|
||
uni.showToast({ title: "登录成功", icon: "success" });
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: "/pages/scan/index" });
|
||
}, 500);
|
||
} catch (e) {
|
||
// request.js 对 /auth/login 的 401 直接 reject 不弹 toast,此处补齐
|
||
const detail = e?.data?.detail || "";
|
||
uni.showToast({ title: detail || "账号或密码错误", icon: "none", duration: 2000 });
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.page { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px; background: #f3f4f6; }
|
||
.header { display: flex; flex-direction: column; align-items: center; margin-bottom: 40px; }
|
||
.logo { font-size: 64px; }
|
||
.title { font-size: 20px; font-weight: 700; color: #1f2937; margin-top: 12px; }
|
||
.version { font-size: 12px; color: #9ca3af; margin-top: 4px; }
|
||
.form { width: 100%; max-width: 320px; }
|
||
.input { width: 100%; height: 48px; padding: 0 16px; border: 1px solid #e5e7eb; border-radius: 10px; font-size: 15px; background: #fff; margin-bottom: 12px; box-sizing: border-box; }
|
||
.login-btn { width: 100%; height: 48px; background: #2563EB; color: #fff; border: none; border-radius: 10px; font-size: 16px; font-weight: 700; line-height: 48px; }
|
||
.login-btn[disabled] { opacity: 0.6; }
|
||
.error { display: block; text-align: center; color: #dc2626; font-size: 13px; margin-top: 12px; }
|
||
</style>
|