first commit

This commit is contained in:
zyh
2025-08-24 15:47:51 +08:00
commit 69bf5500cd
13 changed files with 2262 additions and 0 deletions

11
src/App.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<Login />
</template>
<script setup>
import Login from './views/Login.vue'
</script>
<style scoped>
/* 页面样式由 Login 组件内维护 */
</style>

9
src/api/auth.js Normal file
View File

@@ -0,0 +1,9 @@
import http from '../plugins/http'
// 登录 API 封装
export function login(payload) {
// 约定 payload: { username: string, password: string }
// 根据实际接口调整路径与字段
return http.post('/auth/login', payload)
}

10
src/main.js Normal file
View File

@@ -0,0 +1,10 @@
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementPlus)
app.mount('#app')

29
src/plugins/http.js Normal file
View File

@@ -0,0 +1,29 @@
import axios from 'axios'
const http = axios.create({
baseURL: '/',
timeout: 15000,
})
// 请求拦截器
http.interceptors.request.use(
(config) => {
// 例如:在此添加认证 token
// const token = localStorage.getItem('token')
// if (token) config.headers.Authorization = `Bearer ${token}`
return config
},
(error) => Promise.reject(error)
)
// 响应拦截器
http.interceptors.response.use(
(response) => response,
(error) => {
// 统一错误处理(可以按需要自定义)
return Promise.reject(error)
}
)
export default http

141
src/views/Login.vue Normal file
View File

@@ -0,0 +1,141 @@
<template>
<div class="login-page">
<el-card class="login-card" shadow="hover">
<template #header>
<div class="card-header">
<img class="logo" alt="logo" src="https://vuejs.org/images/logo.png" />
<div class="title">平台登录</div>
</div>
</template>
<el-form ref="formRef" :model="form" :rules="rules" label-width="80px" @keyup.enter.native="onSubmit">
<el-form-item label="用户名" prop="username">
<el-input v-model.trim="form.username" placeholder="请输入用户名" clearable />
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input v-model.trim="form.password" type="password" show-password placeholder="请输入密码" />
</el-form-item>
<div class="actions">
<el-checkbox v-model="remember">记住我</el-checkbox>
<el-link type="primary" :underline="false" @click="onForget">忘记密码</el-link>
</div>
<el-form-item>
<el-button type="primary" :loading="loading" class="submit" @click="onSubmit"> </el-button>
</el-form-item>
</el-form>
<el-alert
v-if="notice"
:title="notice"
type="info"
show-icon
class="notice"
/>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import { login } from '../api/auth'
const formRef = ref()
const loading = ref(false)
const remember = ref(false)
const notice = ref('')
const form = ref({
username: '',
password: '',
})
const rules = {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码至少 6 位', trigger: 'blur' },
],
}
onMounted(() => {
const saved = localStorage.getItem('login-remember')
const savedUser = localStorage.getItem('login-username')
remember.value = saved === '1'
if (remember.value && savedUser) {
form.value.username = savedUser
}
notice.value = import.meta?.env?.VITE_API_BASE
? `当前 API: ${import.meta.env.VITE_API_BASE}`
: '未配置 VITE_API_BASE默认使用 /'
})
function onForget() {
ElMessage.info('请联系管理员重置密码')
}
function persistRemember() {
localStorage.setItem('login-remember', remember.value ? '1' : '0')
if (remember.value) {
localStorage.setItem('login-username', form.value.username || '')
} else {
localStorage.removeItem('login-username')
}
}
async function onSubmit() {
if (!formRef.value) return
await formRef.value.validate(async (valid) => {
if (!valid) return
loading.value = true
try {
const payload = { username: form.value.username, password: form.value.password }
const res = await login(payload)
// 依据后端返回结构处理,这里仅做示例提示
ElMessage.success('登录成功')
persistRemember()
console.debug('login response:', res.data)
// TODO: 登录成功后的跳转(如接入 Router
} catch (e) {
const msg = e?.response?.data?.message || e.message || '登录失败'
ElMessage.error(msg)
} finally {
loading.value = false
}
})
}
</script>
<style scoped>
.login-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f2f6fc 0%, #ffffff 100%);
}
.login-card {
width: 420px;
}
.card-header {
display: flex;
align-items: center;
gap: 12px;
}
.logo { width: 32px; height: 32px; }
.title { font-size: 18px; font-weight: 600; }
.actions {
display: flex;
justify-content: space-between;
align-items: center;
margin: -4px 0 8px;
}
.submit { width: 100%; }
.notice { margin-top: 8px; }
</style>