云图库平台(二)前端项目初始化
环境准备:
node.js版本必须>=18.12
,使用vue脚手架来初始化创建项目
目录
- 一、创建项目
- 二、前期准备
- 前端工程化配置
- 组件库的引入
- 开发规范
- 三、页面基本信息
- 基础布局结构
- 全局底部栏
- 动态替换内容弄个
- 全局顶部栏
- 修改GlobalHeader组件
- 四、路由
- 路由跳转
- 代码高亮实现
- 五、Axios请求库
- 全局自定义请求
- 自动生成请求代码
- 测试请求
- 六、全局状态管理
- 定义状态
- 引入状态
- 测试全局状态管理
一、创建项目
使用Vue的脚手架快速创建项目,可以参考Vue官网:https://cn.vuejs.org/
终端执行命令:
npm create vue@latest
项目创建完成后,这里我是使用的WebStorm打开的项目,在终端输入命令安装依赖:
npm install
依赖安装完成后运行
npm run dev
看是否能运行成功。
出现上述界面就说明运行成功了。
二、前期准备
前端工程化配置
刚刚使用vue脚手架创建项目时已经帮我们整合了Prettier代码美化
、ESLint自动校验
、TypeScript类型校验
。但是我们需要在webstorm中开启代码美化插件.
setting->eslint:这里新手推荐选择第一个
Disable ESLint
,因为ESLing自动校验较为严格,对新手不是特别友好.
setting->Prettier:开启代码美化工具,配置如下:
组件库的引入
引入Ant Design Vue组件库
,这里使用的版本是v4.2.6
,可以参考官方文档快速上手:https://antdv.com/docs/vue/getting-started-cn]
- 输入命令引入组件
npm i --save ant-design-vue@4.x
- 改变
main.ts文件
,使用全局注册组件(更加方便,这里参考[官方文档],有重复的,者这里自行进行删除即可(https://antdv.com/docs/vue/getting-started-cn):
import { createApp } from 'vue';
import Antd from 'ant-design-vue';
import App from './App';
import 'ant-design-vue/dist/reset.css';
const app = createApp(App);
app.use(Antd).mount('#app');
然后我们随便引入一个组件来看看是否成功引入组件,这里我以按钮组件为例:
- 引入组件库中的按钮组件:
<a-button type="primary" danger>Primary</a-button>
运行后效果如下:
由此可以知道Ant Design Vue组件库
引入成功了。
开发规范
Vue组件中可以按照该两种不同的风格进行书写:选项式API
和组合式API
。这里为了使项目开发过程中更加高效,所以使用了Vue3中的组合式API,而不是选项式API。
- 这里使用组合式API进行示范,示例代码如下:
<template>
<div id="xxPage">
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
#xxPage {
}
</style>
好了,接下来正式进行前端初始化页面模板的开发。
三、页面基本信息
修改页面搜索引擎优化页面:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能协同云图库</title>
<meta name="description" content="这是一个SpringBoot + Vue3的智能协同云图库平台">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
基础布局结构
在layouts目录下新建BasicLayout.vue
文件,然后在App.vue全局页面入口文件中引入。
App.vue代码如下:
<template>
<div id="app">
<BasicLayout />
</div>
</template>
<script setup lang="ts">
import BasicLayout from '@/layouts/BasicLayout.vue'
</script>
<style scoped>
</style>
然后在Ant Design官网中找到Layout组件,这里以上中下结构布局来进行演示:
不要忘记清楚页面内的默认样式,并且移除assets目录
中的main.css文件和base.css文件
,防止样式污染。
BasicLayout.vue
代码如下:
<template>
<div id="basicLayout">
<a-layout style="min-height: 100vh">
<a-layout-header>Header</a-layout-header>
<a-layout-content>Content</a-layout-content>
<a-layout-footer>Footer</a-layout-footer>
</a-layout>
</div>
</template>
<script setup lang="ts"></script>
目前样式如下:
全局底部栏
全局底部栏展示底部栏信息:
<a-layout-footer class="footer">
<a ref="https://gitee.com/" target="_blank">智能云图库平台</a>
</a-layout-footer>
调整底部栏信息样式:
#basicLayout .footer {
background: #efefef;
padding: 16px;
position: fixed;
bottom: 0;
left: 0;
right: 0;
text-align: center;
}
动态替换内容弄个
Vue脚手架已经帮我们引入了Vue Router路由库,可以在router/intex.ts
中进行路由配置,使得能够根据访问的页面地址找到不同的组件并进行渲染。
首先需要知道哪些内容需要动态替换,很显然肯定是中间content部分:
<a-layout-content class="content">
<router-view />
</a-layout-content>
页面效果如下:
修改content样式,要和底部栏保持一定外边距,否则内容会被遮住
如下:
#basicLayout .content {
background: linear-gradient(to right, #fefefe, #fff);
margin-bottom: 28px;
padding: 20px;
}
全局顶部栏
在components目录中新建GlobalHeader.vue文件
用于实现全局顶部栏组件。
- 使用Ant Design的菜单组件:
在BasicLayout.vue
文件中引入该顶部栏组件:
<a-layout-header class="header">
<GlobalHeader />
</a-layout-header>
下面是引入该组件的代码:
<script setup lang="ts">
import GlobalHeader from '@/components/GlobalHeader.vue'
</script>
然后修改该顶部栏组件的样式(主要是为了清楚默认样式),代码如下:
#basicLayout .header {
padding-inline: 20px;
margin-bottom: 16px;
color: unset;
background: white;
}
修改GlobalHeader组件
- 给该菜单组件外面套一层元素,用于控制整体的样式,代码如下:
<div class="globalHeader">
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
</div>
- 然后修改菜单配置,代码如下:
<script lang="ts" setup>
import { h, ref } from 'vue'
import { HomeOutlined } from '@ant-design/icons-vue'
import { MenuProps } from 'ant-design-vue'
const current = ref<string[]>(['home'])
const items = ref<MenuProps['items']>([
{
key: '/',
icon: () => h(HomeOutlined),
label: '主页',
title: '主页',
},
{
key: '/about',
label: '关于',
title: '关于',
},
{
key: 'others',
label: h('a', { href: 'https://www.codefather.cn', target: '_blank' }, '编程导航'),
title: '编程导航',
},
])
</script>
- 接着继续完善全局顶部栏,在左侧补充网站logo(根据自己需要找一个就行)和标题:
将找到的logo.png图片
放到src/assets目录下
,替换掉原本的默认Logo即可:
代码如下(其中RouterLink组件作用是支持超链接跳转(不刷新页面)
):
<div id="globalHeader">
<RouterLink to="/">
<div class="title-bar">
<img class="logo" src="../assets/logo.png" alt="logo" />
<div class="title">智能协同云图库</div>
</div>
</RouterLink>
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
</div>
css样式如下:
<style scoped>
.title-bar {
display: flex;
align-items: center;
}
.title {
color: black;
font-size: 18px;
margin-left: 16px;
}
.logo {
height: 48px;
}
</style>
目前页面效果如下:
- 接着,实现右侧展示当前用户的登录状态,代码如下:
<div class="user-login-status">
<a-button type="primary" href="/user/login">登录</a-button>
</div>
- 最后一步:优化导航栏的布局,使用Ant Design的栅格组件的自适应布局(左右宽度固定,中间菜单栏自适应),代码如下:
<div id="globalHeader">
<a-row :wrap="false">
<a-col flex="200px">
<RouterLink to="/">
<div class="title-bar">
<img class="logo" src="../assets/logo.png" alt="logo" />
<div class="title">智能协同云图库</div>
</div>
</RouterLink>
</a-col>
<a-col flex="auto">
<a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
</a-col>
<a-col flex="120px">
<div class="user-login-status">
<a-button type="primary" href="/user/login">登录</a-button>
</div>
</a-col>
</a-row>
</div>
最终效果如下图所示:
到这里,全局通用布局结束了,接下来进行路由的配置。
四、路由
现在,要实现点击不同的菜单项从而跳转到不同的页面,并实现菜单项的高亮。
我们可以看到router目录下index.ts文件中的routes提供了两种不同加载组件的方式:第一种方式是直接加载组件(无论是否使用组件均会加载进来);第二种方式是按需加载(懒加载,即用到哪个组件就加载哪个组件,用不到就不加载)。
路由跳转
现在我们来实现菜单项的路由跳转功能,给GlobalHeader组件
的菜单组件绑定跳转事件,代码如下:
import { useRouter } from "vue-router";
const router = useRouter();
// 路由跳转事件
const doMenuClick = ({ key }: { key: string }) => {
router.push({
path: key,
});
};
html代码如下:
<a-menu
v-model:selectedKeys="current"
mode="horizontal"
:items="items"
@click="doMenuClick"
/>
现在,点击对应的菜单项会显示高亮,但是页面说刷新后当前选中的菜单项并不会高亮。
代码高亮实现
代码如下:
const router = useRouter();
// 当前选中菜单
const current = ref<string[]>([]);
// 监听路由变化,更新当前选中菜单
router.afterEach((to, from, next) => {
current.value = [to.path];
});
当我们进入页面的时候,current变量
初始时候是没有的;由于afterEach函数,每次前往新页面时,都会把当前要前往页面的路径设置给current变量。
高亮显示原理:
- 点击菜单时,Ant Design组件已经通过v-model 绑定current变量实现了高亮。
- 每次刷新页面都会获取到当前的url路径,然后修改current变量的值,从而实现同步。
五、Axios请求库
当前端需要获取数据时,前端需要向后端接口发送请求,由后端执行操作并响应数据给前端。这里使用第三方库Axios
来发送请求。
安装请求工具库Axios
(参考官方文档https://axios-http.com/docs/intro):
npm install axios
我们如何来使用Axios库
呢?接下来看全局自定义请求。
全局自定义请求
需要自定义全局请求地址等,可以参照Axios官方文档,编写请求配置文件request.ts
。包括全局接口请求地址、超时时间、自定义请求响应拦截器等。
代码如下:
import axios from 'axios'
import { message } from 'ant-design-vue'
// 创建 Axios 实例
const myAxios = axios.create({
baseURL: 'http://localhost:8126',
timeout: 60000,
withCredentials: true,
})
// 全局请求拦截器
myAxios.interceptors.request.use(
function (config) {
// Do something before request is sent
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
},
)
// 全局响应拦截器
myAxios.interceptors.response.use(
function (response) {
const { data } = response
// 未登录
if (data.code === 40100) {
// 不是获取用户信息的请求,并且用户目前不是已经在用户登录页面,则跳转到登录页面
if (
!response.request.responseURL.includes('user/get/login') &&
!window.location.pathname.includes('/user/login')
) {
message.warning('请先登录')
window.location.href = `/user/login?redirect=${window.location.href}`
}
}
return response
},
function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error)
},
)
export default myAxios
自动生成请求代码
前端请求代码这里使用OpenAPI工具
自动生成(根据后端的swagger接口文档地址生成前端的请求代码),参考官方文档:https://www.npmjs.com/package/@umijs/openapi
- 安装命令:
npm i --save-dev @umijs/openapi
然后在项目根目录中新建openapi.config.js文件
,代码如下:
import { generateService } from '@umijs/openapi'
generateService({
requestLibPath: "import request from '@/request'",
schemaPath: 'http://localhost:8126/api/v2/api-docs',
serversPath: './src',
})
- 在
package.json的script
中添加"openapi": "node openapi.config.js"
。
不要忘记将schemPath
改为自己后端服务提供的Swagger
接口文档的地址。现在执行刚刚添加的"openapi": "node openapi.config.js"
即可生成可执行代码。
如下图所示:
之后每次后端接口发生变更时只需要重新执行生成一遍就好了。
测试请求
现在可以试着在任意页面代码中调用API:
import { healthUsingGet } from '@/api/mainController'
healthUsingGet().then((res) => {
console.log(res)
})
在项目后端初始化阶段,已经添加了全局跨域配置,正常情况下出现如下响应:
如果出现跨域问题,这是因为前端网址地址和后端请求接口地址不一致导致的。
如果后端代码无法修改或者还可以通过前端代理服务器。同时如果项目中使用Vite
,内置了代理服务器。现在通过前端来解决跨域问题,修改vite.config.ts
文件增加代理配置
。
该项目由于整合了vite打包工具,而vite默认给我们提供了一个代理服务器的配置。增加代理配置代码如下(vite.config.ts文件
):
export default defineConfig({
server: {
proxy: {
'/api': 'http://localhost:8126',
}
},
})
还需要修改request.ts文件
:
// 创建 Axios 实例
const myAxios = axios.create({
baseURL: "", //本地开发环境
// baseURL: 'http://localhost:8126',
timeout: 60000,
withCredentials: true,
})
由此,前端发送的请求域名就等同于当前URL的域名,就不会出现跨域。当我们访问到/api
开头的接口时,会被代理到请求8126端口
的后端服务器,从而完成请求。
六、全局状态管理
全局状态管理可以立即为多个页面要共享的数据,适合作为全局状态管理的数据比如说已登录的用户信息。
引入Pinia,最开始创建项目时vue脚手架已经引入了Pinia,所以无需再次引入了。当然如果没有引入Pinia的话可以参照官方文档。
定义状态
在src/stores目录中
创建useLoginUserStore.ts文件
定义user模块:
代码如下:
import { defineStore } from "pinia";
import { ref } from "vue";
export const useLoginUserStore = defineStore("loginUser", () => {
const loginUser = ref<any>({
userName: "未登录",
});
async function fetchLoginUser() {
// todo 由于后端还没提供接口,暂时注释
// const res = await getCurrentUser();
// if (res.data.code === 0 && res.data.data) {
// loginUser.value = res.data.data;
// }
}
function setLoginUser(newLoginUser: any) {
loginUser.value = newLoginUser;
}
return { loginUser, setLoginUser, fetchLoginUser };
});
引入状态
可以直接使用store中导出的状态变量和函数,如下:
一般我们首次进入页面时都会获取登录用户的信息,修改App.vue
文件,添加部分代码来远程获取用户数据,如下:
import { useLoginUserStore } from '@/stores/useLoginUserStore.ts'
const loginUserStore = useLoginUserStore()
loginUserStore.fetchLoginUser()
接下来修改全局顶部栏组件(即GlobalHeader.vue文件
),在顶部栏右侧展示用户状态,代码如下:
<div class="user-login-status">
<div v-if="loginUserStore.loginUser.id">
{{ loginUserStore.loginUser.userName ?? '无名' }}
</div>
<div v-else>
<a-button type="primary" href="/user/login">登录</a-button>
</div>
</div>
测试全局状态管理
在userStore中编写测试代码
async function fetchLoginUser() {
// 测试用户登录,3 秒后登录
setTimeout(() => {
loginUser.value = { userName: '用于展示登录用户信息', id: 1 }
}, 3000)
}
最终效果:进入页面3秒钟后,顶部栏右侧会展示出登录用户信息。
至此,该项目的前端初始化完结散花。