325 lines
6.6 KiB
Markdown
325 lines
6.6 KiB
Markdown
# web
|
||
|
||
Vblog 前端项目
|
||
|
||
## 清理模版代码
|
||
|
||
- 清理组件
|
||
- 清理样式
|
||
- 清理js文件
|
||
|
||
## 添加登录页面
|
||
|
||
- LoginPage.vue
|
||
|
||
```vue
|
||
<template>
|
||
<div>登录页面</div>
|
||
</template>
|
||
|
||
<script setup></script>
|
||
|
||
<style lang="css" scoped></style>
|
||
```
|
||
|
||
- 添加路有
|
||
|
||
```js
|
||
const router = createRouter({
|
||
history: createWebHistory(import.meta.env.BASE_URL),
|
||
routes: [
|
||
{
|
||
path: '/login',
|
||
name: 'login',
|
||
component: () => import('../views/common/LoginPage.vue'),
|
||
},
|
||
],
|
||
})
|
||
```
|
||
|
||
- App组件,添加路由视图入口
|
||
|
||
```vue
|
||
<script setup></script>
|
||
|
||
<template>
|
||
<RouterView></RouterView>
|
||
</template>
|
||
|
||
<style scoped></style>
|
||
```
|
||
|
||
## 安装UI插件
|
||
|
||
https://gitee.com/infraboard/go-course/blob/master/day20/vue3-ui.md
|
||
|
||
```js
|
||
// 加载UI组件
|
||
import ArcoVue from '@arco-design/web-vue'
|
||
import '@arco-design/web-vue/dist/arco.css'
|
||
// 额外引入图标库
|
||
import ArcoVueIcon from '@arco-design/web-vue/es/icon'
|
||
app.use(ArcoVue)
|
||
app.use(ArcoVueIcon)
|
||
```
|
||
|
||
## Login Page开发
|
||
|
||
```vue
|
||
<template>
|
||
<div class="content">
|
||
<div class="login-container">
|
||
<div class="login-title">欢迎登录博客系统</div>
|
||
<div class="login-form">
|
||
<a-form size="large" :model="loginForm" @submit="handleSubmit">
|
||
<a-form-item
|
||
field="username"
|
||
hide-label
|
||
hide-asterisk
|
||
:rules="{ required: true, message: '请输入用户名' }"
|
||
>
|
||
<a-input
|
||
style="background-color: #fff;"
|
||
v-model="loginForm.username"
|
||
placeholder="请输入用户名"
|
||
>
|
||
<template #prefix>
|
||
<icon-user />
|
||
</template>
|
||
</a-input>
|
||
</a-form-item>
|
||
<a-form-item
|
||
style="margin-top: 12px"
|
||
field="password"
|
||
hide-label
|
||
hide-asterisk
|
||
:rules="{ required: true, message: '请输入密码' }"
|
||
>
|
||
<a-input-password
|
||
style="background-color: #fff;"
|
||
v-model="loginForm.password"
|
||
placeholder="请输入密码"
|
||
allow-clear
|
||
>
|
||
<template #prefix>
|
||
<icon-lock />
|
||
</template>
|
||
</a-input-password>
|
||
</a-form-item>
|
||
<a-form-item field="remember_me" hide-label hide-asterisk>
|
||
<a-checkbox style="margin-left: auto;" v-model="loginForm.remember_me" value="boolean"
|
||
>记住</a-checkbox
|
||
>
|
||
</a-form-item>
|
||
<a-form-item hide-label hide-asterisk>
|
||
<a-button type="primary" style="width: 100%;" html-type="submit">登录</a-button>
|
||
</a-form-item>
|
||
</a-form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive } from 'vue'
|
||
|
||
const loginForm = reactive({
|
||
username: '',
|
||
password: '',
|
||
remember_me: false,
|
||
})
|
||
const handleSubmit = (data) => {
|
||
if (data.errors === undefined) {
|
||
console.log(data.values)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="css" scoped>
|
||
.content {
|
||
height: 100%;
|
||
width: 100%;
|
||
justify-content: center;
|
||
align-items: center;
|
||
display: flex;
|
||
background-color: rgb(243, 238, 238);
|
||
}
|
||
|
||
.login-container {
|
||
width: 460px;
|
||
height: 320px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
background: rgba(255, 255, 255, 0.3);
|
||
/* 增加透明度 */
|
||
border-radius: 15px;
|
||
backdrop-filter: blur(15px);
|
||
/* 增加模糊程度 */
|
||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.login-title {
|
||
margin-top: 12px;
|
||
margin-bottom: 12px;
|
||
font-size: 20px;
|
||
font-weight: 500;
|
||
color: var(--color-neutral-8);
|
||
}
|
||
|
||
.login-form {
|
||
padding: 20px;
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
## 对接后端
|
||
|
||
调用后端的Restful接口, 需要一个http客户端
|
||
|
||
- XMLHTTPRequest
|
||
- fetch
|
||
- axios: https://www.axios-http.cn/docs/intro
|
||
|
||
```shell
|
||
npm install axios
|
||
```
|
||
|
||
### 后端API
|
||
|
||
```js
|
||
export const client = axios.create({
|
||
baseURL: 'http://127.0.0.1:8080',
|
||
timeout: 3000,
|
||
})
|
||
```
|
||
|
||
```js
|
||
import { client } from './client'
|
||
|
||
export const LOGIN = (data) =>
|
||
client({
|
||
url: '/api/vblog/v1/tokens',
|
||
method: 'POST',
|
||
data: data,
|
||
})
|
||
```
|
||
|
||
### 页面使用
|
||
|
||
```js
|
||
const summitLoading = ref(false)
|
||
const handleSubmit = async (data) => {
|
||
if (data.errors === undefined) {
|
||
try {
|
||
summitLoading.value = true
|
||
await LOGIN(data.values)
|
||
} catch (error) {
|
||
console.log(error)
|
||
} finally {
|
||
summitLoading.value = false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```sh
|
||
Access to XMLHttpRequest at 'http://127.0.0.1:8080/api/vblog/v1/tokens' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
|
||
```
|
||
|
||
### 如何处理CORS
|
||
|
||
- 1. 服务端 允许跨越访问, 服务端需要配置一个CROS 中间件,返回 允许的 origin列表, 允许 origin localhost 访问
|
||
- 2. 前端使用代码, 前端先访问 -- proxy -- backend
|
||
|
||
推荐: 前端使用代理 , 最终的效果是 前端和后端部署在一起
|
||
|
||
前端如何配置代理: (vite)
|
||
|
||
```js
|
||
// https://vite.dev/config/
|
||
export default defineConfig({
|
||
plugins: [vue(), vueJsx(), vueDevTools()],
|
||
resolve: {
|
||
alias: {
|
||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||
},
|
||
},
|
||
server: {
|
||
proxy: {
|
||
'/api': 'http://localhost:8080',
|
||
},
|
||
},
|
||
})
|
||
```
|
||
|
||
http client在请求的时候,不能指定baseURL: http://127.0.0.1:8080/api/vblog/v1/tokens,
|
||
|
||
```
|
||
http://localhost:5173/api/vblog/v1/tokens --> http://localhost:8080/vblog/v1/tokens
|
||
```
|
||
|
||
移除baseURL配置
|
||
|
||
```js
|
||
export const client = axios.create({
|
||
baseURL: '',
|
||
timeout: 3000,
|
||
})
|
||
```
|
||
|
||
### API 访问报错处理
|
||
|
||
添加一个响应拦截器来进行响应的处理
|
||
|
||
```js
|
||
// http 客户端配置一个拦截器
|
||
client.interceptors.response.use(
|
||
// 请求成功的拦截器
|
||
(value) => {
|
||
return value
|
||
},
|
||
// 请求失败
|
||
(error) => {
|
||
let msg = error.message
|
||
try {
|
||
msg = error.response.data.message
|
||
} catch (error) {
|
||
console.log(error)
|
||
}
|
||
Message.error(msg)
|
||
},
|
||
)
|
||
```
|
||
|
||
## 路由嵌套与布局
|
||
|
||
https://router.vuejs.org/zh/guide/essentials/nested-routes.html
|
||
|
||
```js
|
||
const routes = [
|
||
{
|
||
path: '/user/:id',
|
||
component: User,
|
||
children: [
|
||
{
|
||
// 当 /user/:id/profile 匹配成功
|
||
// UserProfile 将被渲染到 User 的 <router-view> 内部
|
||
path: 'profile',
|
||
component: UserProfile,
|
||
},
|
||
{
|
||
// 当 /user/:id/posts 匹配成功
|
||
// UserPosts 将被渲染到 User 的 <router-view> 内部
|
||
path: 'posts',
|
||
component: UserPosts,
|
||
},
|
||
],
|
||
},
|
||
]
|
||
```
|