前言
开发这个项目的目的是通过实践的方式,学习Vue3和Golang以及GoFrame框架。实践过程中也会通过扩展知识点,了解其中的原理,更加的巩固知识。 当然作为初学者,肯定会出现很多错误,或者不规范的地方,若有大佬发现,可以提出建议,让小弟我多多学习!感谢。
项目介绍
本项目为销售管理后台,主要功能为管理客户、分销员,以及进行审批。是对我以前开发的一个系统进行重构,之前系统使用Vue2作为前端框架,前端组件库使用Element UI,ThinkPHP作为后端框架。
旧系统由于使用了TP框架的模板功能,所以并不是严格意义上的前后端分离(页面是通过PHP来渲染的),Vue部分也没有使用构建工具(脚手架),所以每个页面都是单独的Vue应用。那在本次重构中,将严格进行前后端分离,使用构建工具进行开发和部署应用。
本项目使用组合式API写法。
本项目代码中,可能会忽略部分样式代码和导入代码,若有特殊情况,将单独说明。
开发环境
Node.js 16+
Vue3
Ant Design of Vue 3+(前端组件库)
Golang 1.19
GoFrame 2.2.1
创建应用
Vue3官方推荐使用Vite作为脚手架,那么我们就按照官方的建议,通过Vite来搭建前端框架。
新建一个vue应用:my-vue-app 为项目文件夹名称
#npm 6.x
npm create vite@latest my-vue-app --template vue
# npm 7+
npm create vite@latest my-vue-app -- --template vue
执行后进入项目文件夹,安装模块,然后运行即可。
应用目录
新建vue应用后,我们可以得到以下的目录:
root(根目录)
├─ node_modules 安装的插件都会放到这个文件夹中
├─ public 存放资源文件
├─ src 存放主要开发代码文件
│ ├─ assets 开发过程中使用到的资源文件
│ ├─ components 组件文件夹
│ ├─ App.vue 根组件文件
│ ├─ main.js 入口文件。在这里挂载vue应用,并导入App.vue组件
│ └─ style.css 公共样式文件
└─ index.html 入口文件
本次开发使用单应用模式,所以默认生成的一个入口文件就够用了。
使用Ant Design Vue组件库
安装
npm i --save ant-design-vue
按需加载
使用 unplugin-vue-components 插件来帮助我们完成按需加载组件。但在ant中,此插件不能加载非组件模块(如message),所以这种组件还是需要手动加载。
npm i unplugin-vue-components -D
# -D 表示只安装在开发模式中
// vite.config.js
import Components from 'unplugin-vue-components/vite';
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
export default {
plugins: [
/* ... */
Components({
resolvers: [AntDesignVueResolver()],
}),
],
};
设计后台页面布局
可以在ant文档中的布局组件中找到喜欢的布局样式,这里选择的是侧边布局。
把整个页面分为左右两部分,左边是导航部分,右边再划分上下区域,上为预留的菜单部分以及用户头像昵称等信息。下区域为主要显示的页面内容。
把每个部分作为一个个组件进行开发,然后再导入根组件App.vue中。
App (根组件)
├─ Left 左侧导航组件
└─ Right 右侧内容组件
├─ Top 上方内容组件
└─ Content 主要内容组件
# 后续根据实际情况可能还会细分出更多的组件
划分组件
既然已经设计好主要的布局方式,那么我们就开始新建对应的组件吧。
左侧导航组件
// Left.vue
<script setup>
defineProps(['collapsed'])
const selectedKeys = ref(['1'])
</script>
<template>
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
<div class="logo" />
<a-menu v-model:selectedKeys="selectedKeys" theme="dark" mode="inline">
<a-menu-item key="1">
<user-outlined />
<span><span>nav 1</span></span>
</a-menu-item>
<a-menu-item key="2">
<video-camera-outlined />
<span><span>nav 2</span></span>
</a-menu-item>
</a-menu>
</a-layout-sider>
</template>
右侧内容组件
// Right.vue
<script setup>
defineProps(['collapsed']);
defineEmits(['menuCollapse']);
</script>
<template>
<a-layout>
<a-layout-header style="background: #fff; padding: 0">
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="$emit('menuCollapse')" />
<menu-fold-outlined v-else class="trigger" @click="$emit('menuCollapse')" />
</a-layout-header>
<a-layout-content :style="{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }">
Content
</a-layout-content>
<a-layout-footer style="text-align: center">
Ant Design ©2018 Created by Ant UED
</a-layout-footer>
</a-layout>
</template>
根组件
// App.vue
<script setup>
const collapsed = ref(false)
function menuCollapse(){
collapsed.value = !collapsed.value;
}
</script>
<template>
<a-layout :style="{minHeight: '100%'}">
<Left :collapsed="collapsed" />
<Right :collapsed="collapsed" @menu-collapse="menuCollapse"/>
</a-layout>
</template>
在这里,我们实现了一个小小的功能,在右组件中,添加了一个按钮,用于折叠左侧导航。
由于左右两组件是同级关系,右组件的消息想要传达到左组件,则需要一个中间组件来进行传递,即它们的上级:App.vue。
因为在vue中,数据只能单向传递,子组件不能修改父组件传递的值。所以我们在根组件中定义了一个变量collapsed,并传递到两个子组件中,子组件通过声明props来接受父组件传递的变量。同时也定义了一个方法menuCollapse(),用于修改collapsed的值。该方法主要由右组件通过触发事件的方式$emit(‘menuCollapse’)进行调用。
当点击右组件的折叠按钮时,会触发父组件的menuCollapse()修改collapsed值,该值会自动更新传递到子组件中,从而实现左组件的导航菜单折叠功能。
优化
通过传递参数,监听事件的方式,我们也可以使用v-model来进行优化。
// App.vue
// 去掉 menuCollapse(),我们把这个方法放到 Right.vue 的点击事件中执行,所以Right组件也要去掉监听事件。
<Right v-model:collapsed="collapsed" />
// Right.vue
<script setup>
const props = defineProps(['collapsed']);
defineEmits(['update:collapsed']);
</script>
<template>
...
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="$emit('update:collapsed', !props.collapsed)" />
...
</template>
App组件中,参数collapsed传递方式由:collapsed变为了v-model:collapsed(冒号后为自定义参数名称),此时在Right组件上会自动添加@update:collapsed事件,当组件触发(此刻是click)该事件时,将会修改参数collapsed的值。所以在Right组件中,我们需要声明一个触发事件update:collapsed,以便父组件监听。
v-model也可不使用自定义名称,此时vue会默认使用modelValue作为传递参数名,所以在子组件中需要声明对应的参数名(modelValue)以及update方法名称(update:modelValue)。
在子组件中,父组件传递的值可以直接在template中使用,但是在JS中,只能从defineProps中获取(获取到的值就是父组件参数.value),所以声明了一个变量props以获取defineProps返回值,才能在方法中使用。
在这里,我们直接在Right组件中点击事件调用$emit(),第一个参数为事件名称,第二个参数为默认值。若需要单独使用一个方法,则是这样写:
// 先声明事件变量
const emit = defineEmits(['update:collapsed']);
function a(){ emit('update:collapsed', !props.collapsed) }
因为在这里这个功能比较简单,只是变化collapsed为true或者false,若是比较复杂的功能,还是只能通过触发父组件函数的方式来修改参数值。
使用路由
做到这里,我们的后台页面也渐渐有模有样了,但是有个很重要的部分,就是主要的内容部分应该怎么显示呢?假设今后我们有客户列表、分销员列表,按之前的想法应该分为Customer、Agent组件,然后都导入到Right中,当点击Left中的导航时,切换相应的组件显示。要实现这个功能,我们需要使用路由功能了。
安装
npm install vue-router@4
配置
在src文件夹下新建一个专门存放路由的文件夹router,并新建index.js路由配置文件,路由对应的组件都在这里配置和导入。
import { createRouter, createWebHistory } from "vue-router"
import Index from '../components/index/Index.vue'
import Hellow from '../components/index/Hellow.vue'
const routes = [
{
path: '/',
component: Index ,
},
{
path: '/hellow',
component: Hellow ,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
vue-router有两种路由模式,
- hash:该模式兼容性较好,但是生成的链接为xx.com/#/hellow,这种链接不够优雅,而且对SEO不友好。这里不采用这种模式。
- history:该模式基于浏览器的history功能,所以需要浏览器支持。生成的链接为常见的格式(xx.com/hellow),我们采用这种路由模式。
全局使用
// main.js
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import Router from './router/index'
createApp(App).use(Router).mount('#app')
// Right.vue
<template>
<a-layout>
<a-layout-header style="background: #fff; padding: 0">
<menu-unfold-outlined v-if="collapsed" class="trigger" @click="$emit('update:collapsed', !props.collapsed)" />
<menu-fold-outlined v-else class="trigger" @click="$emit('update:collapsed', !props.collapsed)" />
</a-layout-header>
<a-layout-content :style="{ margin: '24px 16px', padding: '24px', background: '#fff', minHeight: '280px' }">
<router-view></router-view>
</a-layout-content>
<a-layout-footer style="text-align: center">
Ant Design ©2018 Created by Ant UED
</a-layout-footer>
</a-layout>
</template>
// Left.vue
<template>
<a-layout-sider v-model:collapsed="collapsed" :trigger="null" collapsible>
<div class="logo" />
<a-menu v-model:selectedKeys="selectedKeys" theme="dark" mode="inline">
<a-menu-item key="1">
<user-outlined />
<span><router-link to="/">Go to Home</router-link></span>
</a-menu-item>
<a-menu-item key="2">
<video-camera-outlined />
<span><router-link to="/hellow">Hellow</router-link></span>
</a-menu-item>
</a-menu>
</a-layout-sider>
</template>
在入口文件main.js中绑定路由到整个应用中,以便可全局使用。
在Left导航中使用标签router-link跳转路由,在Right内容位置使用标签router-view来渲染对应的组件模板。
成果
现在,我们已经完成了后台页面的雏形,接下来将完善左侧导航菜单。