环境准备:
node.js版本必须>=18.12,使用vue脚手架来初始化创建项目
目录
一、创建项目
使用Vue的脚手架快速创建项目,可以参考Vue官网:https://cn.vuejs.org/
终端执行命令:
            
            
              shell
              
              
            
          
          npm create vue@latest
        
项目创建完成后,这里我是使用的WebStorm打开的项目,在终端输入命令安装依赖:
            
            
              shell
              
              
            
          
          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]
- 输入命令引入组件
 
            
            
              shell
              
              
            
          
          npm i --save ant-design-vue@4.x
        
- 改变
main.ts文件,使用全局注册组件(更加方便,这里参考[官方文档],有重复的,者这里自行进行删除即可(https://antdv.com/docs/vue/getting-started-cn): 
            
            
              typescript
              
              
            
          
          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');
        
然后我们随便引入一个组件来看看是否成功引入组件,这里我以按钮组件为例:
- 引入组件库中的按钮组件:
 
            
            
              typescript
              
              
            
          
          <a-button type="primary" danger>Primary</a-button>
        运行后效果如下:

由此可以知道Ant Design Vue组件库引入成功了。
开发规范
Vue组件中可以按照该两种不同的风格进行书写:选项式API和组合式API。这里为了使项目开发过程中更加高效,所以使用了Vue3中的组合式API,而不是选项式API。
- 这里使用组合式API进行示范,示例代码如下:
 
            
            
              js
              
              
            
          
          <template>
  <div id="xxPage">
  </div>
</template>
<script setup lang="ts">
</script>
<style scoped>
#xxPage {
}
</style>
        好了,接下来正式进行前端初始化页面模板的开发。
三、页面基本信息
修改页面搜索引擎优化页面:
            
            
              html
              
              
            
          
          <!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代码如下:
            
            
              html
              
              
            
          
          <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代码如下:
            
            
              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>
        目前样式如下:

全局底部栏
全局底部栏展示底部栏信息:
            
            
              vue
              
              
            
          
          <a-layout-footer class="footer">
  <a ref="https://gitee.com/" target="_blank">智能云图库平台</a>
</a-layout-footer>
        调整底部栏信息样式:
            
            
              typescript
              
              
            
          
          #basicLayout .footer {
  background: #efefef;
  padding: 16px;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
}
        动态替换内容弄个
Vue脚手架已经帮我们引入了Vue Router路由库,可以在router/intex.ts中进行路由配置,使得能够根据访问的页面地址找到不同的组件并进行渲染。
首先需要知道哪些内容需要动态替换,很显然肯定是中间content部分:
            
            
              typescript
              
              
            
          
          <a-layout-content class="content">
  <router-view />
</a-layout-content>
        页面效果如下:
修改content样式,要和底部栏保持一定外边距,否则内容会被遮住如下:
            
            
              typescript
              
              
            
          
          #basicLayout .content {
  background: linear-gradient(to right, #fefefe, #fff);
  margin-bottom: 28px;
  padding: 20px;
}
        全局顶部栏
在components目录中新建GlobalHeader.vue文件用于实现全局顶部栏组件。
- 使用Ant Design的菜单组件:

 
在BasicLayout.vue文件中引入该顶部栏组件:
            
            
              typescript
              
              
            
          
          <a-layout-header class="header">
  <GlobalHeader />
</a-layout-header>
        下面是引入该组件的代码:
            
            
              typescript
              
              
            
          
          <script setup lang="ts">
import GlobalHeader from '@/components/GlobalHeader.vue'
</script>
        然后修改该顶部栏组件的样式(主要是为了清楚默认样式),代码如下:
            
            
              typescript
              
              
            
          
          #basicLayout .header {
  padding-inline: 20px;
  margin-bottom: 16px;
  color: unset;
  background: white;
}
        修改GlobalHeader组件
- 给该菜单组件外面套一层元素,用于控制整体的样式,代码如下:
 
            
            
              typescript
              
              
            
          
          <div class="globalHeader">
  <a-menu v-model:selectedKeys="current" mode="horizontal" :items="items" />
</div>
        - 然后修改菜单配置,代码如下:
 
            
            
              typescript
              
              
            
          
          <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组件作用是支持超链接跳转(不刷新页面)):
            
            
              typescript
              
              
            
          
          <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样式如下:
            
            
              typescript
              
              
            
          
          <style scoped>
.title-bar {
  display: flex;
  align-items: center;
}
.title {
  color: black;
  font-size: 18px;
  margin-left: 16px;
}
.logo {
  height: 48px;
}
</style>
        目前页面效果如下:
- 接着,实现右侧展示当前用户的登录状态,代码如下:
 
            
            
              typescript
              
              
            
          
          <div class="user-login-status">
  <a-button type="primary" href="/user/login">登录</a-button>
</div>
        - 最后一步:优化导航栏的布局,使用Ant Design的栅格组件的自适应布局(左右宽度固定,中间菜单栏自适应),代码如下:
 
            
            
              typescript
              
              
            
          
          <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组件的菜单组件绑定跳转事件,代码如下:
            
            
              typescript
              
              
            
          
          import { useRouter } from "vue-router";
const router = useRouter();
// 路由跳转事件
const doMenuClick = ({ key }: { key: string }) => {
  router.push({
    path: key,
  });
};
        html代码如下:
            
            
              typescript
              
              
            
          
          <a-menu
  v-model:selectedKeys="current"
  mode="horizontal"
  :items="items"
  @click="doMenuClick"
/>
        现在,点击对应的菜单项会显示高亮,但是页面说刷新后当前选中的菜单项并不会高亮。
代码高亮实现
代码如下:
            
            
              typescript
              
              
            
          
          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):
            
            
              shell
              
              
            
          
          npm install axios
        
我们如何来使用Axios库呢?接下来看全局自定义请求。
全局自定义请求
需要自定义全局请求地址等,可以参照Axios官方文档,编写请求配置文件request.ts。包括全局接口请求地址、超时时间、自定义请求响应拦截器等。
代码如下:
            
            
              typescript
              
              
            
          
          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
- 安装命令:
 
            
            
              shell
              
              
            
          
          npm i --save-dev @umijs/openapi
        然后在项目根目录中新建openapi.config.js文件,代码如下:
            
            
              typescript
              
              
            
          
          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:
            
            
              typescript
              
              
            
          
          import { healthUsingGet } from '@/api/mainController'
healthUsingGet().then((res) => {
  console.log(res)
})
        在项目后端初始化阶段,已经添加了全局跨域配置,正常情况下出现如下响应:

如果出现跨域问题,这是因为前端网址地址和后端请求接口地址不一致导致的。
如果后端代码无法修改或者还可以通过前端代理服务器。同时如果项目中使用Vite,内置了代理服务器。现在通过前端来解决跨域问题,修改vite.config.ts文件增加代理配置。
该项目由于整合了vite打包工具,而vite默认给我们提供了一个代理服务器的配置。增加代理配置代码如下(vite.config.ts文件):
            
            
              typescript
              
              
            
          
          export default defineConfig({
  server: {
    proxy: {
      '/api': 'http://localhost:8126',
    }
  },
})
        还需要修改request.ts文件:
            
            
              typescript
              
              
            
          
          // 创建 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模块:
代码如下:
            
            
              typescript
              
              
            
          
          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文件,添加部分代码来远程获取用户数据,如下:
            
            
              typescript
              
              
            
          
          import { useLoginUserStore } from '@/stores/useLoginUserStore.ts'
const loginUserStore = useLoginUserStore()
loginUserStore.fetchLoginUser()
        接下来修改全局顶部栏组件(即GlobalHeader.vue文件),在顶部栏右侧展示用户状态,代码如下:
            
            
              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中编写测试代码
            
            
              typescript
              
              
            
          
          async function fetchLoginUser() {
  // 测试用户登录,3 秒后登录
  setTimeout(() => {
    loginUser.value = { userName: '用于展示登录用户信息', id: 1 }
  }, 3000)
}
        最终效果:进入页面3秒钟后,顶部栏右侧会展示出登录用户信息。

至此,该项目的前端初始化完结散花。








