Files
hjc-web/app/pages/login.vue
T
weicw1996 32098b69ac fix(hjc-web): 修复刷新后登录态丢一半,以及顶栏企业名取错字段
- useHjcAuth:登录用户信息补持久化到 hjc_user cookie。原先用 useState,只活在内存与
  本次 SSR payload 里,刷新后归 null,顶栏企业名退化成占位的「企业」,登录态看着像丢了
  一半;H5 端把用户信息存本地存储,PC 端用同名 cookie 对齐。它**只用于展示**,鉴权一律
  以 JWT 为准(后端不读用户信息)。顺带抽出 setCredentials() 统一写入 token + 展示信息。
- HjcUserBar:loggedIn 改 computed —— 原来 ref(isLoggedIn()) 只在挂载那一刻求值一次,
  之后别处清凭据(401 处理、另一页签退出)本组件不跟着变;企业名改取 username 而非
  nickname —— 核心实例建号时会把 nickname 无条件覆盖成打码手机号,拿它当企业名是错的,
  username 才是「企业名称(登录账号)」。
- login:验证码图 object-cover 改 object-contain,避免被裁掉。
- tender/orders:改用 useRequestFetch()。SSR 期间裸 $fetch 不会把浏览器 Cookie 转发给
  站内 /api 代理,代理会变成匿名调用并返回 code=401,从而被 401 分支误判为「凭据失效」
  ——刷新页面等于把用户登出。
2026-09-14 19:01:31 +08:00

128 lines
4.6 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.
<template>
<div class="min-h-[60vh] flex items-center justify-center px-4">
<div class="w-full max-w-md rounded-lg border border-gray-200 p-8">
<h1 class="text-2xl font-bold text-center text-gray-900">企业登录</h1>
<form class="mt-6 space-y-4" @submit.prevent="submitForm">
<div>
<label class="text-sm text-gray-600">企业名称</label>
<input v-model="enterpriseName" class="mt-1 w-full rounded-md border border-gray-300 px-4 py-2 text-sm" placeholder="请输入企业名称" />
</div>
<div>
<label class="text-sm text-gray-600">密码</label>
<input v-model="password" type="password" class="mt-1 w-full rounded-md border border-gray-300 px-4 py-2 text-sm" placeholder="请输入密码" />
</div>
<div>
<label class="text-sm text-gray-600">图形验证码</label>
<div class="mt-1 flex items-center gap-2">
<input
v-model="code"
class="w-full rounded-md border border-gray-300 px-4 py-2 text-sm"
placeholder="请输入图片中的验证码"
autocomplete="off"
/>
<img
v-if="captchaImage"
:src="captchaImage"
alt="图形验证码"
title="看不清?点击换一张"
class="h-[38px] w-28 shrink-0 cursor-pointer rounded-md border border-gray-300 object-contain"
@click="refreshCaptcha"
/>
<button
v-else
type="button"
class="h-[38px] w-28 shrink-0 rounded-md border border-gray-300 text-xs text-gray-600 hover:bg-gray-50"
@click="refreshCaptcha"
>
获取验证码
</button>
</div>
</div>
<button type="submit" class="w-full rounded-md bg-blue-600 py-3 text-white font-medium hover:bg-blue-700 disabled:opacity-50" :disabled="loading">
{{ loading ? '登录中...' : '登录' }}
</button>
<p v-if="error" class="text-center text-sm text-red-500">{{ error }}</p>
</form>
<p class="mt-4 text-center text-sm text-gray-500">
还没有账号
<NuxtLink to="/register" class="text-blue-600 hover:underline">立即注册企业</NuxtLink>
</p>
</div>
</div>
</template>
<script setup lang="ts">
const { login, fetchCaptcha } = useHjcAuth()
const route = useRoute()
const enterpriseName = ref('')
const password = ref('')
/** 图形验证码(后端只下发图片,答案由用户肉眼识别后输入) */
const code = ref('')
const captchaImage = ref('')
const loading = ref(false)
const error = ref('')
/** 拉取/刷新图形验证码图片;失败时提示并允许点击重试 */
async function refreshCaptcha() {
code.value = ''
const res = await fetchCaptcha()
if (res.ok && res.image) {
captchaImage.value = res.image
return
}
captchaImage.value = ''
// 不覆盖已有错误(如登录失败 message),仅在无错误时提示验证码本身的问题
if (!error.value) error.value = res.message || '验证码获取失败,请点击重试'
}
/**
* 登录成功后的去向:`redirect` 可能被 encodeURIComponent 过(如 /buy%3Fid%3D3),
* 这里安全解码;仅接受站内路径,避免开放重定向。
*/
function resolveRedirect() {
const raw = (route.query.redirect as string) || ''
if (!raw) return '/tender'
let target = raw
try {
target = decodeURIComponent(raw)
} catch {
target = raw
}
// 必须以单个 '/' 开头:排除 http(s)://、协议相对地址 //evil.com
// 以及反斜杠变体 /\evil.com(部分浏览器把 '\' 归一成 '/',等价于 //evil.com
return target.startsWith('/') && !target.startsWith('//') && !target.includes('\\') ? target : '/tender'
}
onMounted(refreshCaptcha)
async function submitForm() {
error.value = ''
if (!enterpriseName.value.trim()) {
error.value = '请输入企业名称'
return
}
if (!password.value) {
error.value = '请输入密码'
return
}
if (!code.value.trim()) {
error.value = '请输入图形验证码'
return
}
loading.value = true
try {
const res = await login(enterpriseName.value.trim(), password.value, code.value.trim())
if (res.ok) {
return navigateTo(resolveRedirect())
}
error.value = res.message || '登录失败'
// 验证码一次性,失败后换一张,避免用户用同一个码反复提交
await refreshCaptcha()
} catch (e: any) {
error.value = e?.message || '登录失败'
} finally {
loading.value = false
}
}
</script>