# 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 ``` ## 前台的详情页 自己补充上 ```vue ``` 可以在后台的编辑页面,给一个跳转按钮,跳转到详情页进行预览。