jingrow 91be186013 refactor: 删除服务器配置检查,注册链接始终显示
- 删除 /jingrow/server-config API 调用
- 移除服务器配置检查逻辑
- 设置注册链接始终显示(showSignupLink 默认为 true)
- 消除 404 错误
2026-01-02 20:27:42 +08:00

267 lines
6.0 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="login-container">
<div class="login-card">
<div class="login-header">
<div class="logo">
<img src="/logo.svg" :alt="appName" width="48" height="48" />
</div>
<h1 class="title">{{ appName }}</h1>
</div>
<n-form
ref="formRef"
:model="formData"
:rules="rules"
size="medium"
:show-label="false"
@keyup.enter="handleLogin"
>
<n-form-item path="username">
<n-input
v-model:value="formData.username"
:placeholder="t('Username')"
:input-props="{ autocomplete: 'username' }"
>
<template #prefix>
<Icon icon="tabler:user" />
</template>
</n-input>
</n-form-item>
<n-form-item path="password">
<n-input
v-model:value="formData.password"
type="password"
:placeholder="t('Password')"
:input-props="{ autocomplete: 'current-password' }"
show-password-on="click"
>
<template #prefix>
<Icon icon="tabler:lock" />
</template>
</n-input>
</n-form-item>
<n-form-item>
<n-button
type="primary"
size="large"
block
:loading="loading"
@click="handleLogin"
class="brand-button"
>
{{ t('Login') }}
</n-button>
</n-form-item>
</n-form>
<div class="login-footer" v-if="showSignupLink">
<n-text depth="3">
{{ t("Don't have an account?") }}
<router-link to="/signup" class="signup-link">
{{ t('Sign up') }}
</router-link>
</n-text>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { NForm, NFormItem, NInput, NButton, NText, useMessage } from 'naive-ui'
import { Icon } from '@iconify/vue'
import { useAuthStore } from '../../shared/stores/auth'
import { t } from '../../shared/i18n'
const router = useRouter()
const message = useMessage()
const authStore = useAuthStore()
const formRef = ref()
const loading = ref(false)
const showSignupLink = ref(true)
const formData = reactive({
username: '',
password: ''
})
const rules = {
username: [
{ required: true, message: t('Please enter username'), trigger: 'blur' }
],
password: [
{ required: true, message: t('Please enter password'), trigger: 'blur' },
{ min: 6, message: t('Password must be at least 6 characters'), trigger: 'blur' }
]
}
const handleLogin = async () => {
try {
await formRef.value?.validate()
loading.value = true
const result = await authStore.login(formData.username, formData.password)
if (result.success) {
message.success(t('Login successful'))
router.push('/')
} else {
message.error(result.error || t('Login failed'))
}
} catch (error) {
console.error('Login error:', error)
message.error(t('Login failed, please check username and password'))
} finally {
loading.value = false
}
}
// 从 localStorage 读取应用名称,默认 Jingrow与侧边栏保持一致
const appName = computed(() => localStorage.getItem('appName') || 'Jingrow')
onMounted(async () => {
// 初始化认证状态
await authStore.initAuth()
// 如果已经登录,直接跳转
if (authStore.isLoggedIn) {
router.push('/')
return
}
})
</script>
<style scoped>
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
padding: 20px;
}
.login-card {
width: 100%;
max-width: 480px;
background: white;
border-radius: 16px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
}
.login-header {
text-align: center;
margin-bottom: 24px;
}
:deep(.n-form-item) {
margin-bottom: 6px !important;
}
:deep(.n-form-item:last-child) {
margin-bottom: 0 !important;
}
:deep(.n-form-item:not(.n-form-item--error) .n-form-item__feedback-wrapper) {
min-height: 0 !important;
margin-top: 0 !important;
padding-top: 0 !important;
}
:deep(.n-form-item--error .n-form-item__feedback-wrapper) {
margin-top: 4px !important;
min-height: auto !important;
}
.logo {
margin-bottom: 16px;
}
.title {
font-size: 32px;
font-weight: 700;
color: #1f2937;
margin: 0 0 8px 0;
}
.subtitle {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.login-footer {
text-align: center;
margin-top: 24px;
}
.signup-link {
color: #1fc76f;
text-decoration: none;
font-weight: 500;
margin-left: 4px;
transition: color 0.2s;
}
.signup-link:hover {
color: #1fc76f;
text-decoration: underline;
}
:deep(.brand-button) {
background: #e6f8f0 !important;
border: 1px solid #1fc76f !important;
color: #0d684b !important;
}
:deep(.brand-button .n-button__border),
:deep(.brand-button .n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
:deep(.brand-button:hover) {
background: #dcfce7 !important;
border-color: #1fc76f !important;
color: #166534 !important;
box-shadow: 0 2px 8px rgba(31, 199, 111, 0.15) !important;
}
:deep(.brand-button:hover .n-button__border),
:deep(.brand-button:hover .n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
:deep(.brand-button:active) {
background: #1fc76f !important;
border-color: #1fc76f !important;
color: white !important;
box-shadow: 0 1px 4px rgba(31, 199, 111, 0.2) !important;
}
:deep(.brand-button:active .n-button__border),
:deep(.brand-button:active .n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
:deep(.brand-button:focus) {
background: #e6f8f0 !important;
border-color: #1fc76f !important;
color: #0d684b !important;
}
:deep(.brand-button:focus .n-button__border),
:deep(.brand-button:focus .n-button__state-border) {
border: none !important;
border-color: transparent !important;
}
</style>