frontend layout and infrastructure

This commit is contained in:
2026-03-18 13:53:36 +08:00
parent 88df42fe52
commit 2b1a2f0c28
26 changed files with 986 additions and 460 deletions
+50 -58
View File
@@ -1,63 +1,55 @@
<script setup>
const router = useRouter()
const goToRegister = () => {
router.push('/register')
}
const goToLogin = () => {
router.push('/login')
}
<script setup lang="ts">
import {
ShoppingCartOutlined,
ShoppingOutlined,
DollarOutlined,
CloudServerOutlined,
} from '@ant-design/icons-vue'
</script>
<template>
<div class="home-container">
<div class="home-content">
<h1 class="home-title">欢迎来到 DataHub</h1>
<p class="home-description">数据管理平台</p>
<div class="home-actions">
<a-button type="primary" size="large" @click="goToLogin"> 登录 </a-button>
<a-button size="large" @click="goToRegister"> 注册 </a-button>
</div>
</div>
<div>
<h2 class="text-xl font-semibold mb-4">Dashboard</h2>
<a-row :gutter="16">
<a-col :span="6">
<a-card :bordered="false">
<a-statistic title="订单总数" value="--">
<template #prefix>
<ShoppingCartOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card :bordered="false">
<a-statistic title="产品总数" value="--">
<template #prefix>
<ShoppingOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card :bordered="false">
<a-statistic title="退款总数" value="--">
<template #prefix>
<DollarOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
<a-col :span="6">
<a-card :bordered="false">
<a-statistic title="队列消息" value="--">
<template #prefix>
<CloudServerOutlined />
</template>
</a-statistic>
</a-card>
</a-col>
</a-row>
<a-card class="mt-4" title="欢迎使用 DataHub 数据管理平台">
<p class="text-gray-500">请从左侧菜单选择功能模块开始使用</p>
</a-card>
</div>
</template>
<style scoped>
.home-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.home-content {
text-align: center;
color: white;
}
.home-title {
font-size: 48px;
font-weight: 700;
margin: 0 0 16px 0;
color: white;
}
.home-description {
font-size: 20px;
margin: 0 0 40px 0;
opacity: 0.9;
}
.home-actions {
display: flex;
gap: 16px;
justify-content: center;
}
.home-actions .ant-btn {
min-width: 120px;
}
</style>
+14 -18
View File
@@ -1,28 +1,29 @@
<script setup>
<script setup lang="ts">
import Brand from '@/components/Brand.vue'
import { UserOutlined, LockOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
// 表单数据
const formState = reactive({
username: '',
password: '',
remember: true,
})
// 表单规则
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请输入用户名', trigger: 'blur' as const },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请输入密码', trigger: 'blur' as const },
],
}
const formRef = ref()
const loading = ref(false)
const router = useRouter()
const route = useRoute()
const userStore = useUserStore()
// 提交登录
const handleSubmit = async () => {
try {
await formRef.value.validate()
@@ -30,9 +31,7 @@ const handleSubmit = async () => {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: formState.username,
password: formState.password,
@@ -42,21 +41,19 @@ const handleSubmit = async () => {
const data = await response.json()
if (data.code === 0) {
// 保存 token
localStorage.setItem('access_token', data.data.access_token)
localStorage.setItem('refresh_token', data.data.refresh_token)
localStorage.setItem('user', JSON.stringify(data.data.user))
userStore.setToken(data.data.access_token, data.data.refresh_token)
userStore.setUser(data.data.user)
message.success('登录成功!')
const redirect = (route.query.redirect as string) || '/'
setTimeout(() => {
router.push('/')
router.push(redirect)
}, 500)
} else {
message.error(data.message || '登录失败,请重试')
}
} catch (error) {
if (error.errorFields) {
// 表单验证错误
} catch (error: unknown) {
if (error && typeof error === 'object' && 'errorFields' in error) {
return
}
console.error('登录错误:', error)
@@ -66,7 +63,6 @@ const handleSubmit = async () => {
}
}
// 跳转到注册页
const goToRegister = () => {
router.push('/register')
}
+24 -20
View File
@@ -1,7 +1,8 @@
<script setup>
<script setup lang="ts">
import Brand from '@/components/Brand.vue'
import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons-vue'
import { useUserStore } from '@/stores/user'
// 表单数据
const formState = reactive({
username: '',
email: '',
@@ -9,30 +10,29 @@ const formState = reactive({
confirmPassword: '',
})
// 表单规则
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度应为 3-20 个字符', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请输入用户名', trigger: 'blur' as const },
{ type: 'string' as const, min: 3, max: 20, message: '用户名长度应为 3-20 个字符', trigger: 'blur' as const },
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请输入邮箱', trigger: 'blur' as const },
{ type: 'email' as const, message: '请输入有效的邮箱地址', trigger: 'blur' as const },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度至少为 6 个字符', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请输入密码', trigger: 'blur' as const },
{ type: 'string' as const, min: 6, message: '密码长度至少为 6 个字符', trigger: 'blur' as const },
],
confirmPassword: [
{ required: true, message: '请确认密码', trigger: 'blur' },
{ type: 'string' as const, required: true, message: '请确认密码', trigger: 'blur' as const },
{
validator: (rule, value) => {
validator: (_rule: unknown, value: string) => {
if (value !== formState.password) {
return Promise.reject('两次输入的密码不一致')
}
return Promise.resolve()
},
trigger: 'blur',
trigger: 'blur' as const,
},
],
}
@@ -40,8 +40,8 @@ const rules = {
const formRef = ref()
const loading = ref(false)
const router = useRouter()
const userStore = useUserStore()
// 提交注册
const handleSubmit = async () => {
try {
await formRef.value.validate()
@@ -49,9 +49,7 @@ const handleSubmit = async () => {
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: formState.username,
email: formState.email,
@@ -62,6 +60,14 @@ const handleSubmit = async () => {
const data = await response.json()
if (data.code === 0) {
// 注册成功后自动设置 token(如果后端返回)
if (data.data?.access_token) {
userStore.setToken(data.data.access_token, data.data.refresh_token)
if (data.data.user) {
userStore.setUser(data.data.user)
}
}
message.success('注册成功!即将跳转到登录页...')
setTimeout(() => {
router.push('/login')
@@ -69,9 +75,8 @@ const handleSubmit = async () => {
} else {
message.error(data.message || '注册失败,请重试')
}
} catch (error) {
if (error.errorFields) {
// 表单验证错误
} catch (error: unknown) {
if (error && typeof error === 'object' && 'errorFields' in error) {
return
}
console.error('注册错误:', error)
@@ -81,7 +86,6 @@ const handleSubmit = async () => {
}
}
// 跳转到登录页
const goToLogin = () => {
router.push('/login')
}