# web
Vblog 前端项目
## 清理模版代码
- 清理组件
- 清理样式
- 清理js文件
## 添加登录页面
- LoginPage.vue
```vue
登录页面
```
- 添加路有
```js
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/common/LoginPage.vue'),
},
],
})
```
- App组件,添加路由视图入口
```vue
```
## 安装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
```
## 对接后端
调用后端的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 的 内部
path: 'profile',
component: UserProfile,
},
{
// 当 /user/:id/posts 匹配成功
// UserPosts 将被渲染到 User 的 内部
path: 'posts',
component: UserPosts,
},
],
},
]
```
```js
// 文章管理
// 标签管理
const handleMenuItemClick = (key) => {
// https://router.vuejs.org/zh/guide/essentials/navigation.html
router.push({ name: key })
}
```
## 登录与退出
```js
const summitLoading = ref(false)
const handleSubmit = async (data) => {
if (data.errors === undefined) {
try {
summitLoading.value = true
const tk = await LOGIN(data.values)
// 保存当前用户的登录状态
token.value.access_token = tk.access_token
token.value.ref_user_name = tk.ref_user_name
token.value.refresh_token = tk.refresh_token
// 需要把用户重定向到Home页面去了
router.push({ name: 'blog_management' })
} catch (error) {
console.log(error)
} finally {
summitLoading.value = false
}
}
}
```
```js
const logout = () => {
// 需要调用Logout接口的, 自己添加下退出接口
// 之前的登录状态给清理掉, store
token.value = undefined
// 需要重定向到登录页面
router.push({ name: 'login' })
}
```
## 导航守卫
https://router.vuejs.org/zh/guide/advanced/navigation-guards.html
```js
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
```
```js
router.beforeEach((to, from) => {
const whiteList = ['login']
if (!whiteList.includes(to.name)) {
// 需要做权限判断
if (isLogin()) {
return true
}
// 返回跳转后的页面
return { name: 'login' }
}
})
```
## 覆盖UI的默认样式
css, :deep(), 用于选择需要覆盖的class进行覆盖
:deep 选择器并不是一个通用的 CSS 选择器,而是特定于某些框架(如 Vue.js 和 Svelte)中用于 Scoped CSS 的一个特性。它的主要目的是允许开发者在使用 Scoped CSS 的情况下,能够针对子组件或外部组件的样式进行覆盖。
```js
```
## 文章的发布
后端如何自定义状态展示
```go
type STAGE int
func (s STAGE) String() string {
return STAGE_MAPPING[s]
}
// 自定义 类型的序列化方式
// "草稿"
func (s STAGE) MarshalJSON() ([]byte, error) {
return []byte(`"` + s.String() + `"`), nil
}
// UnmarshalJSON([]byte) error
func (s *STAGE) UnmarshalJSON(data []byte) error {
str := strings.Trim(string(data), `"`)
switch str {
case "草稿":
*s = STAGE_DRAFT
case "已发布":
*s = STAGE_PUBLISHED
default:
return fmt.Errorf("不支持的发布类型")
}
return nil
}
var STAGE_MAPPING = map[STAGE]string{
STAGE_DRAFT: "草稿",
STAGE_PUBLISHED: "已发布",
}
const (
STAGE_DRAFT STAGE = iota
STAGE_PUBLISHED
)
```
## 前台布局
1. 前台整体的布局, 组件的复用,Login/Logout
```vue
退出
登录
```
前台布局模版:
```vue
```
## 前台的列表页
```vue
83
{{ item.id }}
Reply
```
## 前台的详情页
自己补充上
```vue
文章详情
```
可以在后台的编辑页面,给一个跳转按钮,跳转到详情页进行预览。