first commit
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Temp
|
||||||
|
dist/
|
||||||
|
.vite/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
|
||||||
12
README.md
Normal file
12
README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Vue 3 + Vite + Element Plus + Axios
|
||||||
|
|
||||||
|
最小可用的 Vue3 脚手架,集成:
|
||||||
|
- 组件库:Element Plus
|
||||||
|
- 网络请求:Axios(含基础拦截器)
|
||||||
|
|
||||||
|
快速使用:
|
||||||
|
- 安装依赖:`npm install`
|
||||||
|
- 开发:`npm run dev`
|
||||||
|
- 构建:`npm run build`;预览:`npm run preview`
|
||||||
|
|
||||||
|
详细文档请见:`docs/开发指南.md`
|
||||||
323
docs/开发指南.md
Normal file
323
docs/开发指南.md
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
# 项目开发指南(Vue 3 + Vite + Element Plus + Axios)
|
||||||
|
|
||||||
|
> 本文档面向日常开发与维护,覆盖环境准备、目录结构、编码规范、网络请求、UI 使用、构建部署与常见问题等。
|
||||||
|
|
||||||
|
## 一、项目概览
|
||||||
|
|
||||||
|
- 框架:Vue 3(Composition API)
|
||||||
|
- 构建:Vite 5
|
||||||
|
- 组件库:Element Plus
|
||||||
|
- 网络:Axios(已内置实例与拦截器)
|
||||||
|
- 运行脚本:`npm run dev | build | preview`
|
||||||
|
|
||||||
|
## 二、环境要求
|
||||||
|
|
||||||
|
- Node.js:建议 18+(可用 `node -v` 检查)
|
||||||
|
- 包管理器:npm(内置于 Node)或 pnpm/yarn(任选其一)
|
||||||
|
- 操作系统:Windows / macOS / Linux
|
||||||
|
|
||||||
|
## 三、目录结构
|
||||||
|
|
||||||
|
当前主要目录:
|
||||||
|
|
||||||
|
```
|
||||||
|
├─ index.html # 应用入口 HTML
|
||||||
|
├─ vite.config.js # Vite 配置
|
||||||
|
├─ package.json # 脚本与依赖
|
||||||
|
├─ src/
|
||||||
|
│ ├─ main.js # 应用入口,注册 Element Plus
|
||||||
|
│ ├─ App.vue # 示例页面(按钮 + 请求演示)
|
||||||
|
│ └─ plugins/
|
||||||
|
│ └─ http.js # Axios 实例与拦截器
|
||||||
|
└─ docs/
|
||||||
|
└─ 开发指南.md # 本文档
|
||||||
|
```
|
||||||
|
|
||||||
|
推荐扩展(按需新增):
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├─ api/ # 接口定义与请求函数(封装到 http 实例上)
|
||||||
|
├─ assets/ # 静态资源(图片、字体等)
|
||||||
|
├─ components/ # 通用基础组件
|
||||||
|
├─ views/ # 页面级组件
|
||||||
|
├─ router/ # 路由(如使用 Vue Router)
|
||||||
|
├─ store/ # 状态管理(如使用 Pinia)
|
||||||
|
├─ styles/ # 全局样式、变量、主题定制
|
||||||
|
└─ utils/ # 工具方法(格式化、校验、下载等)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、快速开始
|
||||||
|
|
||||||
|
- 安装依赖:`npm install`
|
||||||
|
- 启动开发:`npm run dev`(默认 http://localhost:5173)
|
||||||
|
- 构建产物:`npm run build`(输出至 `dist/`)
|
||||||
|
- 本地预览:`npm run preview`
|
||||||
|
|
||||||
|
## 五、配置说明
|
||||||
|
|
||||||
|
### 1. 环境变量
|
||||||
|
|
||||||
|
建议使用 Vite 的环境文件管理不同环境:
|
||||||
|
|
||||||
|
- `.env.development`(开发)
|
||||||
|
- `.env.production`(生产)
|
||||||
|
|
||||||
|
示例内容:
|
||||||
|
|
||||||
|
```
|
||||||
|
# .env.development
|
||||||
|
VITE_API_BASE=https://dev-api.example.com
|
||||||
|
|
||||||
|
# .env.production
|
||||||
|
VITE_API_BASE=https://api.example.com
|
||||||
|
```
|
||||||
|
|
||||||
|
在代码中使用:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const baseURL = import.meta.env.VITE_API_BASE
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Vite 配置(`vite.config.js`)
|
||||||
|
|
||||||
|
常用配置要点:
|
||||||
|
- 别名:为 `src` 设置 `@` 方便导入。
|
||||||
|
- 代理:开发阶段转发 API,避免 CORS。
|
||||||
|
|
||||||
|
示例(按需修改后替换到 `vite.config.js`):
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
resolve: {
|
||||||
|
alias: { '@': path.resolve(__dirname, 'src') },
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'https://dev-api.example.com',
|
||||||
|
changeOrigin: true,
|
||||||
|
rewrite: (p) => p.replace(/^\/api/, ''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Axios 配置(`src/plugins/http.js`)
|
||||||
|
|
||||||
|
当前实现:
|
||||||
|
- `baseURL: '/'`(建议改为 `import.meta.env.VITE_API_BASE`)
|
||||||
|
- 超时 15s
|
||||||
|
- 预留请求拦截器(注入 Token)与响应拦截器(统一错误处理)
|
||||||
|
|
||||||
|
建议修改:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const http = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE || '/',
|
||||||
|
timeout: 15000,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
统一错误处理示例(可在响应拦截器中加入):
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
http.interceptors.response.use(
|
||||||
|
(res) => res,
|
||||||
|
(error) => {
|
||||||
|
const msg = error?.response?.data?.message || error.message || '请求失败'
|
||||||
|
ElMessage.error(msg)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、代码与组件规范
|
||||||
|
|
||||||
|
- 组件目录:一个组件一个目录(可含 `index.vue` / `index.ts`)。
|
||||||
|
- 命名规范:
|
||||||
|
- 组件名:`PascalCase`(如 `UserCard.vue`)。
|
||||||
|
- 变量/函数:`camelCase`。
|
||||||
|
- 文件:`kebab-case`(工具类/样式)。
|
||||||
|
- 组件结构:`<template>` / `<script setup>` / `<style scoped>` 顺序。
|
||||||
|
- 导入顺序:Vue/第三方 → 别名 `@` → 相对路径。
|
||||||
|
- 提交信息:语义化(feat/fix/docs/chore/refactor/style/test/build)。
|
||||||
|
|
||||||
|
## 七、UI 与交互(Element Plus)
|
||||||
|
|
||||||
|
- 全量引入已配置于 `src/main.js`:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
app.use(ElementPlus)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 主题定制:
|
||||||
|
- 推荐在 `src/styles/variables.scss` 定义主题变量,按需覆盖 Element Plus 变量。
|
||||||
|
- 也可使用暗色主题与自定义主题(参考官方文档)。
|
||||||
|
|
||||||
|
- 常用交互:
|
||||||
|
- 消息:`ElMessage.success('保存成功')`
|
||||||
|
- 弹窗:`ElMessageBox.confirm('确定操作吗?', '提示')`
|
||||||
|
- 加载:`ElLoading.service({ text: '加载中...' })`
|
||||||
|
|
||||||
|
## 八、网络请求规范
|
||||||
|
|
||||||
|
- 统一通过 `src/plugins/http.js` 导出的 `http` 发起请求:
|
||||||
|
|
||||||
|
```js
|
||||||
|
import http from '@/plugins/http'
|
||||||
|
|
||||||
|
export function fetchUser(id) {
|
||||||
|
return http.get(`/users/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUser(payload) {
|
||||||
|
return http.post('/users', payload)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 结果解构与错误处理:
|
||||||
|
|
||||||
|
```js
|
||||||
|
try {
|
||||||
|
const { data } = await fetchUser(1)
|
||||||
|
// 根据后端约定判断 code / success
|
||||||
|
} catch (e) {
|
||||||
|
// 统一拦截器会提示,这里可按需记录或兜底
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 可选:取消请求(例如在切换路由或重复点击时):
|
||||||
|
|
||||||
|
```js
|
||||||
|
const controller = new AbortController()
|
||||||
|
http.get('/path', { signal: controller.signal })
|
||||||
|
// 需要时取消
|
||||||
|
controller.abort()
|
||||||
|
```
|
||||||
|
|
||||||
|
## 九、常见开发食谱
|
||||||
|
|
||||||
|
- 新增页面:
|
||||||
|
- 在 `src/views/` 新建页面组件,如 `UserList.vue`。
|
||||||
|
- 如使用路由,在 `router` 中注册路由并在入口挂载。
|
||||||
|
|
||||||
|
- 调用接口 + 加载态示例(参考 `App.vue`):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import http from '@/plugins/http'
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const data = ref(null)
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res = await http.get('/example')
|
||||||
|
data.value = res.data
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
- 表单校验(Element Plus):
|
||||||
|
- 使用 `el-form` + `rules` + `el-form-item`,提交前 `formRef.validate()`。
|
||||||
|
|
||||||
|
- 消息提示:
|
||||||
|
- 成功:`ElMessage.success('操作成功')`
|
||||||
|
- 失败:`ElMessage.error('操作失败')`
|
||||||
|
|
||||||
|
## 十、路由与状态(可选集成)
|
||||||
|
|
||||||
|
- 安装:
|
||||||
|
- Vue Router:`npm i vue-router`
|
||||||
|
- Pinia:`npm i pinia`
|
||||||
|
|
||||||
|
- 快速示例(Router):
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/router/index.js
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ path: '/', component: () => import('@/views/Home.vue') },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default createRouter({ history: createWebHistory(), routes })
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import router from '@/router'
|
||||||
|
app.use(router)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 快速示例(Pinia):
|
||||||
|
|
||||||
|
```js
|
||||||
|
// src/store/index.js
|
||||||
|
import { createPinia, defineStore } from 'pinia'
|
||||||
|
export const pinia = createPinia()
|
||||||
|
export const useCounter = defineStore('counter', {
|
||||||
|
state: () => ({ count: 0 }),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
// main.js
|
||||||
|
import { pinia } from '@/store'
|
||||||
|
app.use(pinia)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十一、构建与部署
|
||||||
|
|
||||||
|
- 构建命令:`npm run build`,产物在 `dist/`
|
||||||
|
- 预览构建:`npm run preview`
|
||||||
|
- 静态资源路径:如需部署到子路径,设置 `base`(`vite.config.js`)或 `--base`。
|
||||||
|
- Nginx 示例:
|
||||||
|
|
||||||
|
```
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass https://api.example.com/;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 十二、质量保障(可选)
|
||||||
|
|
||||||
|
- 代码格式化:建议接入 Prettier + ESLint。
|
||||||
|
- 提交规范:建议使用 Commitlint + Husky 规范提交信息。
|
||||||
|
- 单元测试:可引入 Vitest 进行组件与函数测试。
|
||||||
|
|
||||||
|
## 十三、常见问题(FAQ)
|
||||||
|
|
||||||
|
- 无法请求跨域?
|
||||||
|
- 开发环境配置 Vite 代理(见上文 server.proxy)。
|
||||||
|
- 生产环境由网关或 Nginx 统一转发。
|
||||||
|
|
||||||
|
- 接口报错但页面无提示?
|
||||||
|
- 确认是否已在响应拦截器中调用 `ElMessage.error`。
|
||||||
|
|
||||||
|
- Element Plus 主题不生效?
|
||||||
|
- 确保样式覆盖顺序正确,且未被 `scoped` 限制。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
如需我帮你接入 Router/Pinia、主题定制或代码规范工具链,请告诉我具体偏好与要求。
|
||||||
21
docs/需求文档.md
Normal file
21
docs/需求文档.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
明白✅
|
||||||
|
|
||||||
|
1. 二界面 URL 模板
|
||||||
|
我已把配置改成:**`域名/{codeNo}`**(文档已更新为 `https://你的域名/{codeNo}`,按运行环境替换域名即可)。
|
||||||
|
|
||||||
|
2. 关于 `http://36.138.184.60:12345/编号/二维码.png`
|
||||||
|
这个是“**脚本端**”暴露出来的**静态图片**地址——**正常情况下会直接返回 PNG 图片**。如果你在页面里看不到图,常见原因有:
|
||||||
|
|
||||||
|
* **混合内容被拦截**:你的前端如果是 **HTTPS**,而这张图是 **HTTP**,浏览器会拦截(安全策略)。
|
||||||
|
解决:给 `36.138.184.60:12345` 配 **HTTPS**,或让后端提供一个 **HTTPS 代理端点**(例如:`GET /api/link/{token}/qr.png`)去转发这张图片,前端只请求自己的 HTTPS 域名即可。(我已在文档里加了这条注意事项)
|
||||||
|
* **二维码尚未生成 / 已过期**:脚本端还没把 `二维码.png` 写到对应 `编号/` 目录,或已被轮转清理 → 会 404 或显示旧图。
|
||||||
|
解决:确保先触发“生成二维码”动作,并用返回的 `createdAt/expireAt` 做本地倒计时;必要时加 `?t=时间戳` 防缓存。
|
||||||
|
* **编号不匹配**:前端用的 `编号` 与实际分配的机器编号不同。
|
||||||
|
解决:以后端返回的 `machineId/编号` 为准。
|
||||||
|
* **端口/防火墙**:12345 端口未对外放通或临时不可达。
|
||||||
|
解决:主机/网关放行该端口并做健康检查。
|
||||||
|
* **跨域非问题**:`<img>` 加载图片不受 CORS 限制,但若被混合内容拦截,同样会显示不出。
|
||||||
|
|
||||||
|
如果你愿意最省心的做法:我们在后端新增一个**图片代理接口**(HTTPS),前端统一拿这个接口的 URL,当脚本端换 IP/端口或做 HTTPS,这边都不用改前端。
|
||||||
|
|
||||||
|
需要的话我可以顺手把这个代理端点的 Spring Boot 代码骨架也给你(带缓存/超时/错误降级)。
|
||||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vue3 App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
1655
package-lock.json
generated
Normal file
1655
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "vue3-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.7.7",
|
||||||
|
"element-plus": "^2.8.8",
|
||||||
|
"vue": "^3.4.38"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
|
"vite": "^5.4.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
11
src/App.vue
Normal file
11
src/App.vue
Normal 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
9
src/api/auth.js
Normal 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
10
src/main.js
Normal 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
29
src/plugins/http.js
Normal 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
141
src/views/Login.vue
Normal 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>
|
||||||
|
|
||||||
8
vite.config.js
Normal file
8
vite.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
})
|
||||||
|
|
||||||
Reference in New Issue
Block a user