【vue3+后台管理系统+项目笔记】
目前刚通过培训转行成功,找到工作,但发现实际干活的时候,不知道如何下手,就重新看了下视频的项目部分,并做下笔记记录。PS:内容属于新手/小白行列,适合0基础,或者刚转行的人看看,了解一下。...
文章目录
开发过程:
1.确认页面结构如何划分
(假设是二层结构) ----> 参考【八、页面结构(封装结构)】
2.初始化搭建页面结构
(创建对应的vue文件结构【主文件和对应组件文件】)
3.实现主文件和组件的基本应用
(导入组件,并能正确展示)
4.开发组件的html结构,css样式
(html+css的东西)
5.实现js交互,以及主文件和组件的数据通信
(用假数据)(props/emit等)----> 【三、搜索栏的二次封装】和【五、表格的二次封装(简单版)】
6.实现真实的请求数据
(发送接口请求)---->【 四、数据获取和保存的逻辑】
一、基础操作
1.1、基础命令
src/components/nav-menu.vue
1.1、Template标签中,图片引用要通过“~@/”方式。 <img src="~@/assets/img/logo.svg" alt="logo" />
1.2、Template标签可以使用v-for循环。<template v-for="item in userMenus" :key="item.id">
1.3、Template标签可以使用v-if判断。<template v-if="item.type === 1">
1.4、v-for+v-if结合,当v-if为真时,渲染某一元素/动态绑定某一属性。<i v-if="item.icon" :class="item.icon"></i>
注意:v-if,也可以写在组件中,或者UI组件库中,控制组件是否显示
src/base-ui/from/src/form.vue
1.16、Template标签使用v-if判断。<template v-else-if="item.type === 'select'">
1.2、store相关
src/components/nav-menu.vue
1.5、引入store。import { useStore } from 'vuex'
1.6、实例store对象。const store = useStore()
1.7、获取store对象的属性(computed计算属性)。const userMenus = store.state.login.userMenus
src/components/page-content.vue
1.19、通过setup(props) +store.dispatch(props.属性)可以动态传入参数。setup(props) {store.dispatch('接口', {pageName: props.pageName})}
export default defineComponent({
props: {
pageName: {
type: String,
required: true
}
},
setup(props) {
const store = useStore()
store.dispatch('system/getPageListAction', {
pageName: props.pageName,
queryInfo:{
offset:0,
size:10
}
})
}
})
1.20、不同页面的数据请求实现(场景应用:初始化请求所有的数据)。(user请求userlist,role请求rolelist)
思路:dispatch传递的不要是固定接口/user/list
,而是页面名称pageName
实现:
1.获取pageUrl
2. 根据pageName的内容,进行switch判断
3. 然后对页面发送请求
4. 再根据传递进来的pageName内容,将数据分别存储到对应的state中
// 在store下的system.ts 的module模块中
actions: {
async getPageListAction({ commit }, payload: any) {
// 1. 获取pageUrl
const pageName = payload.pageName
// 2. 根据pageName的内容,进行switch判断
let pageUrl = ''
switch (pageName) {
case 'users':
pageUrl = 'users/list'
break
case 'role':
pageUrl = 'role/list'
break
}
// console.log(pageUrl)
// 3. 然后对页面发送请求
const pageResult = await getPageListData(pageUrl, payload.queryInfo)
// console.log(pageResult)
// 4. 再根据传递进来的pageName内容,将数据分别存储到对应的state中
const { list, totalCount } = pageResult.data
switch (pageName) {
case 'users':
commit('changeUserList', list)
commit('changeUserCount', totalCount)
break
case 'role':
commit('changeRoleList', list)
commit('changeRoleCount', totalCount)
break
}
},
}
注意:useStore只能在setup里使用,在一般的js/ts文件中没法使用const store = useStore()
。--
1.3、router相关
src/components/nav-menu.vue
1.8、引入useRouter和useRoute。import { useRouter, useRoute } from 'vue-router'
1.9、实例router实例对象,可以使用router对象的方法,,例如router.push。const router = useRouter()
1.10、实例route实例对象,可以使用当前路由对象的方法,例如route.path。const route = useRoute()
1.17、获取所有的路由对象。console.log(router.getRoutes());
1.18、在导航守卫router.beforeEach
中可以获取即将跳转的route对象。
router.beforeEach((to) => {
if (to.path !== '/login') {
const token = LocalCache.getCache('token')
if (!token) {
return '/login'
}
}
if (to.path === '/main') {
return firstMenu.url
}
// 获取所有的路由对象
// console.log(router.getRoutes());
// to其实就是即将跳转的route对象
// console.log(to);
})
1.4、其他类
src/views/main.vue
1.11、动态绑定属性+三元运算符。<el-aside :width="isCollapse ? '60px' : '210px'">
src/utils/map-menus.ts
1.12、调用数组的高阶方法:forEach,将部分数据添加到另一个数组中。
computed(()=> {})
computed返回值是一个ref对象:const userMenus = computed(() => store.state.login.userMenus)
switch 类似于 if 判断
switch可以根据条件判断,返回不同的值。
应用场景:数据的转换功能
例如:
// 如果pageName是users,执行第一个选项,如果是role,执行第一个选项,
switch (pageName) {
case 'users':
commit('changeUserList', list)
commit('changeUserCount', totalCount)
break
case 'role':
commit('changeRoleList', list)
commit('changeRoleCount', totalCount)
break
}
1.5、获取组件实例对象
参考链接:https://www.jb51.net/article/209948.htm
1.13、组件实例中绑定ref<canvas ref="refChart" :height="setHeight"></canvas>
1.14、setup中定义refChart
对应的ref对象
。const refChart = ref();
1.15、通过refChart.value.属性/方法
,可以直接使用。
1.6、Vue3中Setup函数的参数props、context详解
参考链接:
重要:props和context分别在template和setup中使用说明https://www.jianshu.com/p/6e27bee45dcb
了解:props和context相关名词定义解释https://blog.csdn.net/weixin_48789376/article/details/119174911
1.21、子组件的props,如果是template
中使用,可以直接使用props对象中定义的变量名
1.21、子组件的props,如果是setup
中使用,需要setup(props)
,然后通过props.变量名
,获取对应的属性
注意:props:接收传过来的参数必须先进行声明,如果没声明,传过来的参数将出现在attrs中
1.15、context :上下文意思,包括 attrs 、 emit 、slots。
① attrs :在此部分,接收在父组件传递过来的,并且没有在props中声明的参数参数。
——在template
中使用,这些属性都存储在$attrs对象
中,可以通过{{ $attrs.属性名 }}
获取
——在setup
函数中获取,这些属性都存储在setup函数的第二个参数对象context的attrs属性
中,可以通过context.attrs.属性名
获取
② emit:子组件对父组件发送事件
③ slots:是一个 proxy 对象,其中 slots.default() 获取到的是一个数组,数组长度由组件的插槽决定,数组内是插槽内容。
1.16、context,常见的用法:{ emit }、{attrs}、{slots}
二、功能点
2.1、子传父+父传子:传递某一变量
src/components/nav-header.vue
2.1、监听事件。<el-icon @click="handleFoldClick()">
2.2、监听事件函数。const handleFoldClick = () => {}
2.3、添加emits属性。export default defineComponent({ emits: ['foldChange']})
2.4、setup函数中解构{emit}。setup(props, { emit }) {}
2.5、发出事件函数,可以带参数(isFold.value
是参数)。const handleFoldClick = () => {emit('foldChange', isFold.value)}
src/views/main.vue
2.6、接收发出的事件函数,父组件自定义函数。<nav-header @foldChange="handleFoldChange" />
2.7、写父组件的监听函数(有参数记得写形参)。const handleFoldChange = (isFold: boolean) => {isCollapse.value = isFold}
2.8、接收参数,修改父组件中定义好的变量。isCollapse.value = isFold
2.9、另一个子组件动态绑定该属性。<nav-menu :collapse="isCollapse" />
src/components/nav-menu.vue
2.10、另一个子组件内设置props属性。export default defineComponent({props: { collapse:Boolean }})
2.11、另一个子组件内可以动态绑定props属性。<el-menu :collapse="collapse">
2.2、添加not found路由和组件
src/router/index.ts
2.12、注册not-found路由映射关系
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('@/views/not-found/not-found.vue')
}
src/views/not-found/not-found.vue
2.13、创建not-found组件
2.3、动态生成路由
原理:登录–账号(角色)–菜单列表–包含url–对应路由的path–对应component
例如:登录–coderwhy–角色管理–包含/main/role–对应路由的path–对应component
实际:登录–coderwhy–N多菜单–菜单中有url–前端提前配置好所有的url和组件的映射–根据菜单中的url加载对应的组件–形成routes数组–动态加载到main路由下的chilren属性中
注意:提前创建好router文件和view文件,并配置好关系
2.3.1、函数封装说明:
封装调用的逻辑:
1.确认要实现哪个功能,并确认该功能的执行位置
2.估算功能实现的逻辑(要获取什么)
3.将逻辑封装到单独函数文件中,执行封装函数,函数返回值就是要获取的内容
4.案例可结合下面的 2.14,2.15,2.19
// 封装函数的大概流程
export function mapMenusToRoutes(userMenus) {
// 定义一个空的数组类型的返回值
const routes = []
// 中间写代码逻辑
// 1.先去加载默认所有的routes
// 2.根据菜单获取需要添加的routes
// 。。。。
// 返回值
return routes
}
// 调用封装函数
const routes = mapMenusToRoutes(userMenus)
2.3.2、案例实现过程:
(1)动态注册路由
src/store/login/login.ts
2.14、因为账号登录后可以获取菜单列表,并保存进行保存,所以可以在这里操作动态添加路由
2.15、根据菜单,找到对应的router的路由:userMenus --> routes
2.19、调用封装的map-menus.ts里的mapMenusToRoutes函数。const routes = mapMenusToRoutes(userMenus)
src/utils/map-menus.ts
2.16、先去加载默认所有的routes
2.17、根据菜单获取需要添加的routes
2.18、递归函数
src/store/login/login.ts
2.19、调用封装的map-menus.ts里的mapMenusToRoutes函数,获取的就是待添加的routes数组
2.20、通过routes数组的forEach,动态添加路由router.addRoute('main', route)
(2)实现路径和组件的映射关系
src/components/nav-menu.vue
2.21、在菜单中,添加监听事件
2.22、监听事件中,添加路由映射
src/views/main.vue
2.23、因为是在main路由添加子路由,所以需要在main.vue文件下进行<router-view />
占位
2.3.3、BUG:动态路由设置后刷新页面出现notfound,不出现正常的路由
判断:
- 在
router.before
导航守卫中,查看所有的路由对象router.getRoutes()
和即将跳转的路由对象to
是否正确。
方案:
- main.js文件中,
app.use(store)
和app.use(router)或者setupStore()
顺序反了。 - 先执行
store
,再执行setupStore(),最后执行router
app.use(store)
setupStore()
app.use(router)
2.3.4、BUG:刷新页面,菜单栏一直固定选择,无法保留刷新前的选项
原因:
- nav-menu.vue的<el-menu default-active=“2”>是固定值
方案:
- 将default-active属性变为动态获取。
实现:
- 动态绑定属性:
<el-menu :default-active="currentMenuId">
- 拿到当前页面的路径:
const route = useRoute()
和const currentPath = route.path
- 根据路径匹配menu菜单(单独封装一个处理函数):
const menu = pathMapToMenu(userMenus.value, currentPath)
- 拿到menu菜单的id属性作为currentMenuId的值:
const currentMenuId = ref(menu.id + '')
// data,匹配页面刷新时,菜单项固定某一位置的bug
//Template:<el-menu :default-active="currentMenuId">
setup() {
const route = useRoute()
const currentPath = route.path
const menu = pathMapToMenu(userMenus.value, currentPath)
const currentMenuId = ref(menu.id + '')
}
2.3.5、BUG:在首页localhost:8080页面刷新出现空白(默认重定向到/main)
原因:
- router中重定向到/main组件,
- main组件引入了nav-menu组件,
- nav-menu组件有个页面刷新保存当前id的设置
- 问题:当直接进入首页,是没有currentPath属性,就无法匹配到对应的id
方案:
- 当首页重定向到/main 的时候,需要继续重定向到menu 菜单的第一项。
- 当第一次登录时,会动态添加路由,
- 动态添加路由的时候,会根据用户菜单列表获取需要添加的routes。
- 此时根据当前的用户菜单列表,我们可以保存下用户菜单列表中的第一个菜单选项
实现:
- mapMenusToRoutes函数中(动态添加路由时)定义一个空的变量firstMenu:
let firstMenu = null
- 判断当前firstMenu是否有值
- 如果没有值的话,保存当前用户菜单列表中的第一个菜单选项
- 将firstMenu进行导出
- 当发现首页重定向到/main 的时候,在router文件通过router.beforeEach,再重定向到用户菜单第一项就可以了
// map-menu.ts文件
let firstMenu: any = null
export function mapMenusToRoutes(userMenus) {
// ...中间省略
const _recurseGetRoute = (menus) => {
for (const menu of menus) {
if (menu.type === 2) {
const route = allRoutes.find((route) => route.path === menu.url)
if (route) {
routes.push(route)
}
if (!firstMenu) {
firstMenu = menu
}
} else {
_recurseGetRoute(menu.children)
}}}}
export { firstMenu }
//router/index.ts
router.beforeEach((to) => {
if (to.path !== '/login') {
const token = LocalCache.getCache('token')
if (!token) {
return '/login'
}
}
if (to.path === '/main') {
return firstMenu.url
}})
三、搜索栏的二次封装
3.1、基础操作
3.1、Form表单
src/base-ui/from/src/form.vue
3.1、修改UI组件的样式,以ElementPlus为例。<el-select style="width: 100%">
3.2、v-bind绑定对象,是将对象内的所有属性全部动态绑定到组件中。<msz-form v-bind="searchFormConfig">
3.3、props中变量如果是数组
或者对象
类型,需要将default
写成函数
的形式。formItems: { type: Array, default: () => [] },
3.4、逻辑运算符(逻辑与&&、逻辑或||)的使用。
3.5、动态获取数据的另一种方式v-model=“formData[`${item.field}`]”
补充:逻辑运算符在 判断语句 和 赋值语句 使用说明
参考链接: https://www.cnblogs.com/guanghe/p/11157201.html?ivk_sa=1024320u
(1)当出现在条件判断语句中时,例如 i f 语句,返回值Boolean
1.&&
1.1两边条件都为true时,结果才为true;
1.2如果有一个为false,结果就为false;
1.3当第一个条件为false时,就不再判断后面的条件
注意:当数值参与逻辑与运算时,结果为true,那么会返回的会是第二个为真的值;如果结果为false,返回的会是第一个为假的值。
2.||
2.1只要有一个条件为true时,结果就为true;
2.2当两个条件都为false时,结果才为false;
2.3当一个条件为true时,后面的条件不再判断
注意:当数值参与逻辑或运算时,结果为true,会返回第一个为真的值;如果结果为false,会返回第二个为假的值;
(2)当出现在赋值语句中时,例如变量赋值、return结果等,返回值是其中一方的运算结果,会遵循以下规则:
const result = 表达式a && 表达式b :
result的运算结果,
先判断计算表达式a(也可以是函数)的运算结果,
如果为 True, 执行表达式b(或函数),并返回b的结果;
如果为 False,返回a的结果;
const result = 表达式a || 表达式b :
result的运算结果,
先判断计算表达式a(也可以是函数)的运算结果,
如果为 Fasle, 执行表达式b(或函数),并返回b的结果;
如果为 True,返回a的结果;
3.2、Form表单二次封装
3.1、组件结构封装逻辑
- 1.新建一个form.vue文件,内容是UI库的代码
- 2.form.vue文件中的属性,尽量通过props属性接收外部传递进来
- 3.在外部注册Form组件,并通过动态绑定的方式传递数据,
v-bind:属性=‘变量’
或者v-bind=‘对象’
- 进阶
- 将第三步动态绑定的属性,单独抽成一个对象格式的JS文件(配置文件),通过
v-bind=‘对象’
的方式将配置对象动态绑定到组件中。
// form.vue
<template>
<div class="hy-form">
<el-form>
<template v-for="item in formItems" :key="item.label">
<el-form-item :label="item.label" :style="itemStyle">
<template v-if="item.type === 'input' || item.type === 'password'">
<el-input :show-password="item.type === 'password'" />
</template>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
props: {
formItems: {
type: Array,
default: () => ([])
},
itemStyle: {
type: Object,
default: () => ({ padding: '10px 40px' })
},
setup() {return {}}
}
</script>
// user.vue
<template>
<div class="user">
//<hy-form v-bind="searchFormConfig" /> 进阶
<hy-form
:formItems="formItems"
:itemStyle="itemStyle"
/>
</div>
</template>
<script>
import HyForm from '@/base-ui/form'
// import { searchFormConfig } from './config/search.config' 进阶
// 将setup中itemStyle和formItems 属性抽到search.config.js文件中,保存成对象格式。再导入进来
export default {
name: 'user',
components: {
HyForm
},
setup() {
const itemStyle= {
padding: '10px 40px'
}
const formItems = [
{
type: 'input',
label: '用户名',
placeholder: '请输入用户名'
},
{
type: 'password',
label: '密码',
placeholder: '请输入密码'
}]
return {
formItems,
itemStyle,
// searchFormConfig 进阶
}
}
}
</script>
3.2、实现表单数据的双向绑定
1.基础:
所有的表单(例如input表单)都有v-model="name"
的属性,通过v-model可以将在input输入的内容,放入到v-model绑定的name
属性中。<el-input v-model="name" />
2.封装:
2.1 在外部引入的文件中,定义一个formData数据
2.2 在自定义组件上,动态绑定formData数据
2.3 在封装组件文件中,使用props接收formData数据
3.如果只有一个表单:
3.1 在封装组件文件v-model直接绑定接收的formData数据既可以了。<el-input v-model="formData" />
4.如果有多个表单,如何实现一一对应的关系:
4.1 通过在formItem
属性中,添加一个field
字段。
4.2 该field
字段名称要与formData
中定义的属性名保持一致。
4.3 在封装组件文件中,通过 <el-input v-model=“formData[`${item.field}']” /> 实现数据的双向绑定
4.4 如果有eslint报错,在eslintrc.js中关闭检测即可
<!-- 基础-->
<!-- base-ui/form/src/form.vue-->
<el-input v-model="name" />
<!-- 封装-->
<!-- user.vue-->
<template>
<div class="user">
//<hy-form v-bind="searchFormConfig" :formData='formData' /> 进阶
<hy-form
:formItems="formItems"
:itemStyle="itemStyle"
/>
</div>
</template>
<script>
import HyForm from '@/base-ui/form'
// import { searchFormConfig } from './config/search.config' 进阶
// 将setup中itemStyle和formItems 属性抽到search.config.js文件中,保存成对象格式。再导入进来
export default {
name: 'user',
components: {
HyForm
},
setup() {
const itemStyle= {
padding: '10px 40px'
}
const formItems = [
{
field:'name',
type: 'input',
label: '用户名',
placeholder: '请输入用户名'
},
{
field:'password',
type: 'password',
label: '密码',
placeholder: '请输入密码'
}]
const formData = ref({
name:'',
password:''
})
return {
formItems,
itemStyle,
formData,
// searchFormConfig 进阶
}
}
}
</script>
<!-- 封装-->
<!-- form.vue-->
<template>
<div class="hy-form">
<el-form>
<template v-for="item in formItems" :key="item.label">
<el-form-item :label="item.label" :style="itemStyle">
<template v-if="item.type === 'input' || item.type === 'password'">
<el-input :show-password="item.type === 'password'" v-model="formData[`${field}`]" />
</template>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
props: {
formItems: {
type: Array,
default: () => ([])
},
itemStyle: {
type: Object,
default: () => ({ padding: '10px 40px' })
},
formData: {
type: Object,
required:true
}
setup() {return {}}
}
</script>
3.3、组件扩展插槽
1.基础使用
定义插槽
<!-- form.vue-->
<template>
<div class="msz-form">
<div class="header">
<slot name="header"></slot>
</div>
<el-form :label-width="labelWidth">
</el-form>
</div>
</template>
使用插槽
<!-- page-search.vue-->
<template>
<div class="page-search">
<msz-form v-bind="searchFormConfig" v-model="formData">
<template #header>
<h1 class="header">高级检索</h1>
</template>
</msz-form>
</div>
</template>
3.4、说明:关于表单双向绑定的其他方案
可以参考【后台管理系统项目实战(八)–04_(掌握)HyForm实现双向绑定的方案】
四、数据获取和保存的逻辑
4.1、目录结构
|-- service
| |-- index.ts
| |-- main
| | |-- system
| | |-- system.ts
| |-- request
| |-- config.ts
| |-- index.ts
|-- store
| |-- index.ts
| |-- main
| |-- system
| |-- system.ts
|-- views
|-- main
| |-- system
| |-- user
| |-- user.vue
4.2、use.vue中发出dispatch
<template>
//省略了结构内容
</template>
<script>
import { defineComponent, computed } from 'vue'
import { useStore } from 'store'
export default defineComponent({
name: 'user',
setup() {
const store = useStore()
// 简单的方式:参数写死,
// pageUrl:接口的URL,可以直接写死省事
// queryInfo:接口查询的条件,有哪些写哪些
store.dispatch('system/getPageListAction', {
pageUrl: '/users/list',
queryInfo: {
offset: 0,
size: 10
}
})
4.2、store/index.js引入子模块内容store/main/system/system.ts
import { createStore} from 'vuex'
import system from './main/system/system'
const store = createStore({
state() {
return {
name: 'coderwhy',
age: 18
}
},
mutations: {},
getters: {},
actions: {},
modules: {
system
}
})
// 加载本地缓存数据的
export function setupStore() {
store.dispatch('login/loadLocalLogin')
}
export default store
4.3、store子模块内容store/main/system/system.ts
import { Module } from 'vuex'
import { getPageListData } from '@/service/main/system/system'
const systemModule = {
// 命名空间
namespaced: true,
state() {
return {
userList: [],
userCount: 0
}
},
mutations: {
changeUserList(state, userList: any[]) {
state.userList = userList
},
changeUserCount(state, userCount: number) {
state.userCount = userCount
}
},
actions: {
async getPageListAction({ commit }, payload) {
// 打印看下是否可以获取到查询条件
console.log(payload.pageUrl)
console.log(payload.queryInfo)
// 1.对页面发送请求,调用的是service下的请求
const pageResult = await getPageListData(
payload.pageUrl,
payload.queryInfo
)
// 获取到list和totalCount 数据
const { list, totalCount } = pageResult.data
// 提交commit保存到state中
commit('changeUserList', list)
commit('changeUserCount', totalCount)
}
}
}
export default systemModule
4.4、store子模块中使用的服务请求
import hyRequest from '../../index'
export function getPageListData(url: string, queryInfo: any) {
return hyRequest.post<IDataType>({
url: url,
data: queryInfo
})
}
4.5、use.vue使用Vuex中的state数据
<template>
<div class="user">
<hy-table :listData="userList" :propList="propList">
// 中间代码省略
</hy-table>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from 'store'
import HyTable from '@/base-ui/table'
export default defineComponent({
name: 'user',
components: {
HyTable
},
setup() {
const store = useStore()
// 提交请求
store.dispatch('system/getPageListAction', {
pageUrl: '/users/list',
queryInfo: {
offset: 0,
size: 10
}
})
// 获取store中的数据,通过computed来监听,实时改变
const userList = computed(() => store.state.system.userList)
const userCount = computed(() => store.state.system.userCount)
return {
userList,
}
}
})
</script>
<style scoped></style>
五、表格的二次封装(简单版)
5.1、根据数据的属性,v-for循环出表格的表头
<template>
<div class="user">
<div class="content">
<el-table :data="userList " border style="width: 100%">
<template v-for="propItem in propList" :key="propItem.prop">
<el-table-column v-bind="propItem" align="center"></el-table-column>
</template>
</el-table>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from '@/store'
export default defineComponent({
name: 'user',
setup() {
const store = useStore()
store.dispatch('system/getPageListAction', {
pageUrl: '/users/list',
queryInfo: {
offset: 0,
size: 10
}
})
const userList = computed(() => store.state.system.userList)
const userCount = computed(() => store.state.system.userCount)
const propList = [
{ prop: 'name', label: '用户名', minWidth: '100' },
{ prop: 'realname', label: '真实姓名', minWidth: '100' },
{ prop: 'cellphone', label: '手机号码', minWidth: '100' },
{ prop: 'enable', label: '状态', minWidth: '100', slotName: 'status' },
{
prop: 'createAt',
label: '创建时间',
minWidth: '250',
slotName: 'createAt'
},
{
prop: 'updateAt',
label: '更新时间',
minWidth: '250',
slotName: 'updateAt'
}
]
return {
userList,
propList
}
}
})
</script>
<style scoped>
.content {
padding: 20px;
border-top: 20px solid #f5f5f5;
}
</style>
六、表格的二次封装(复杂版)
可以参考【后台管理系统项目实战(八)–08_(掌握)HyTable的动态插槽和作用域插槽】
6.1、通过作用域插槽修改某列数据的展示形式
6.1.1、封装的table.vue
<template>
<div class="hy-table">
<el-table :data="listData" border style="width: 100%">
<template v-for="propItem in propList" :key="propItem.prop">
<el-table-column v-bind="propItem" align="center">
<!-- #default="scope"是element自带的作用域插槽 -->
<!-- 获取这一行的数据:scope.row -->
<!-- 获取这一行的某一个字段数据:scope.row[propItem.prop] -->
<template #default="scope">
<!-- 所有的数据全部按照button的格式展示 -->
<!-- <el-button>{{ scope.row[propItem.prop] }}</el-button> -->
<!-- 某一列数据单独定制样式,自定义插槽 -->
<slot :name="propItem.slotName" :row="scope.row">
{{ scope.row[propItem.prop] }}
</slot>
</template>
</el-table-column>
</template>
</el-table>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
listData: {
type: Array,
required: true
},
propList: {
type: Array,
required: true
}
},
setup() {
return {}
}
})
</script>
<style scoped></style>
6.1.2、user.vue引用封装的table.vue
<template>
<div class="user">
<page-search :searchFormConfig="searchFormConfig" />
<div class="content">
<hy-table :listData="userList" :propList="propList">
<template #status="scope">
<el-button>{{ scope.row.enable ? '启用' : '禁用' }}</el-button>
</template>
<template #createAt="scope">
<strong>{{ scope.row.createAt }}</strong>
</template>
</hy-table>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import { useStore } from '@/store'
import PageSearch from '@/components/page-search'
import HyTable from '@/base-ui/table'
import { searchFormConfig } from './config/search.config'
export default defineComponent({
name: 'user',
components: {
PageSearch,
HyTable
},
setup() {
const store = useStore()
store.dispatch('system/getPageListAction', {
pageUrl: '/users/list',
queryInfo: {
offset: 0,
size: 10
}
})
const userList = computed(() => store.state.system.userList)
const userCount = computed(() => store.state.system.userCount)
const propList = [
{ prop: 'name', label: '用户名', minWidth: '100' },
{ prop: 'realname', label: '真实姓名', minWidth: '100' },
{ prop: 'cellphone', label: '手机号码', minWidth: '100' },
{ prop: 'enable', label: '状态', minWidth: '100', slotName: 'status' },
{
prop: 'createAt',
label: '创建时间',
minWidth: '250',
slotName: 'createAt'
},
{
prop: 'updateAt',
label: '更新时间',
minWidth: '250',
slotName: 'updateAt'
}
]
return {
searchFormConfig,
userList,
propList
}
}
})
</script>
<style scoped>
.content {
padding: 20px;
border-top: 20px solid #f5f5f5;
}
</style>
七、时间格式化:通过dayjs格式化
5.1、格式化函数的注册方式
5.1.1、普通方式:在当前需要格式化数据的文件中,在setup里定义一个函数,通过调用当前文件下的这个函数,格式化数据
<template #createAt="scope">
<span>{{ formatTime(scope.row.createAt) }}</span>
</template>
<script>
const formatTime = ()=>{...省略}
</script>
5.1.2、全局注册:通过app.config.globalProperties.属性名来全局绑定一个函数(或者对象)
注册方法:绑定函数
// 根文件main.js
// 在全局注册了一个$filter的函数,后期在任何地方都可以通过$filter()使用该函数
// $filter:是自定义的函数名
app.config.globalProperties.$filter = function() {
// 代码逻辑写在这
return '返回结果'
}
注册方法:绑定对象
// 绑定一个对象
app.config.globalProperties.$filter = {
foo(){
// foo函数的代码逻辑写在这
return '返回结果'
},
formatTime(){
// formatTime函数的代码逻辑写在这
return '返回结果'
}
}
使用方法:template中使用
// 在template中使用
<template #createAt="scope">
<span>{{ $filter.formatTime(scope.row.createAt) }}</span>
</template>
使用方法:setup中获取该函数
// main.js
// 导入
import push from '@/utils/push'
import api from '@/utils/api'
// 中间忽略了const app = createApp(App)等代码
// 绑定
app.config.globalProperties.$asyncPost = api.nextPost
app.config.globalProperties.$push = push
// 在setup中使用
<script>
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
console.log('instance是:', instance.appContext.config.globalProperties)
return {}
},
}
</script>
5.1.3、格式化函数的代码实现
import dayjs from 'dayjs'
// 导入utc才可以支持对utc转化
import utc from 'dayjs/plugin/utc'
// 使用utc
dayjs.extend(utc)
// 时间格式化:默认样式
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
// UTC时间格式化
export function formatUtcString(
utcString: string,
format: string = DATE_TIME_FORMAT
) {
// 时间偏移8小时:utcOffset(8)
return dayjs.utc(utcString).utcOffset(8).format(format)
}
// 时间戳时间格式化
export function formatTimeStamp(
timeStamp: number,
format: string = DATE_TIME_FORMAT
) {
if (Number.isInteger(timeStamp)) {
if (timeStamp.toString().length === 13) {
// 毫秒
return dayjs(timeStamp).format(format)
} else if (timeStamp.toString().length === 10) {
// 秒
return dayjs(timeStamp).format(format)
}
}
}
八、页面结构(封装结构)
思想:在软件工程里面,没有什么是通过分一层不能解决的,如果有的话,那给它分两层
8.1、基础版:分二层结构
第一层:最终的页面需求页面
User.vue:
1.引用第二层的封装组件
2.数据通过props/emits传递
3.网络请求可以通过Vuex进行发出
第二层:代码逻辑页面
Search.vue:
1.实现业务的逻辑代码
2.可以与User.vue通过props/emits传递数据
如果其他页面也有类似的组件需求:
1.可以通过复制当前 的组件,适当修改
2.扩展当前组件的复用性
8.2、进阶版:分三层结构
第一层:最终的页面需求页面
User.vue:
1.引用封装好的第二层的封装文件
2.引用封装好的第二层的配置文件
3.将配置文件和组件进行绑定
第二层:中间过渡层
PageContent.vue:
1.引用封装好的第三层的封装文件
2.通过props/emit实现与第一层和第三层的传递
3.与Vuex打通,实现数据状态管理
4.网络请求发送,在这层实现
5.补充并彻底实现第一层的需求
第三层:代码逻辑页面
maSZtable.vue:
1.实现业务的逻辑代码
2.通过props/emit实现与第二层的传递
九、搜索框的重置和查询功能(search.vue->user.vue->content.vue)
9.1、重置功能(搜索和重置逻辑一样)
9.1.1 监听事件,然后将input表单绑定的属性设置成原始的状态(或者是空的状态)
9.1.2 重新发送查询请求
// page-search.vue
// 监听事件
<template>
<div class="page-search">
<msz-form v-bind="searchFormConfig" v-model="formData">
<template #header>
<h1 class="header">高级检索</h1>
</template>
<template #footer>
<div class="handle-btns">
<el-button @click="handleResetClick">重置</el-button>
<el-button type="primary" @click="handleQueryClick">搜索</el-button>
</div>
</template>
</msz-form>
</div>
</template>
<script lang="ts">
setup(props, { emit }) {
const formDataInt = ref({
id: '',
name: '',
password: '',
sport: '',
createTime: ''
}),
const formData = ref({
id: '',
name: '',
password: '',
sport: '',
createTime: ''
})
// 当用户点击重置,进行数据重置
const handleResetClick = () => {
// 数据重置
// 因为这是个input表单,不会有数据的产生
formData.value = formDataInt.value
// 重新发送查询请求
emit('resetBtnClick')
}
}
</script>
// user.vue
<template>
<div class="user">
// 接收事件
<page-search @resetBtnClick="handleResetBtnClick"></page-search>
<page-content ref="pageContentRef"></page-content>
</div>
</template>
<script lang="ts">
export default defineComponent({
setup() {
// 处理事件
const handleResetBtnClick = () => {
// 获取PageContentRef,调用查询数据的方法
pageContentRef.value?.getPageData()
}
return {
handleResetBtnClick,
}
}
})
</script>
<template>
<div class="page-content">
<msz-table :listData="dateList" :listCount="dataCount">
</msz-table>
</div>
</template>
<script lang="ts">
export default defineComponent({
setup() {
// 1.发送网络请求
const getPageData = (queryInfo: any = {}) => {
// 发送网络前,判断是否有对应权限
// 没有权限
if (!isQuery) return
// 有权限
store.dispatch('system/getPageListAction', {
pageName: props.pageName,
queryInfo: {
offset: (pageInfo.value.currentPage - 1) * pageInfo.value.pageSize,
size: pageInfo.value.pageSize,
...queryInfo
}
})
}
// 2.调用发送网络请求 的函数
getPageData()
// 3.从Vuex中获取数据
const dateList = computed(() => {
return store.getters[`system/pageListDate`](props.pageName)
})
const dataCount = computed(() => {
return store.getters[`system/pageListCount`](props.pageName)
})
return {
dateList,
dataCount
}
}
})
</script>
9.2、搜索功能(搜索和重置逻辑一样)
9.1.1 监听事件,然后将input表单绑定的属性作为参数进行传递
9.1.2 重新发送查询请求
// page-search.vue
// 监听事件
<template>
<div class="page-search">
<msz-form v-bind="searchFormConfig" v-model="formData">
<template #header>
<h1 class="header">高级检索</h1>
</template>
<template #footer>
<div class="handle-btns">
<el-button @click="handleResetClick">重置</el-button>
<el-button type="primary" @click="handleQueryClick">搜索</el-button>
</div>
</template>
</msz-form>
</div>
</template>
<script lang="ts">
setup(props, { emit }) {
// 当用户点击搜索
const handleQueryClick = () => {
// formData.value就是查询条件
emit('queryBtnClick', formData.value)
}
}
</script>
// user.vue
<template>
<div class="user">
// 接收事件
<page-search @queryBtnClick="handleQueryBtnClick"></page-search>
<page-content ref="pageContentRef"></page-content>
</div>
</template>
<script lang="ts">
export default defineComponent({
setup() {
// 处理事件
const handleQueryBtnClick = (queryInfo) => {
// 获取PageContentRef,调用查询数据的方法,传递参数,把formData.value传递到queryInfo中
pageContentRef.value?.getPageData(queryInfo)
}
return {
handleQueryBtnClick ,
}
}
})
</script>
<template>
<div class="page-content">
<msz-table :listData="dateList" :listCount="dataCount">
</msz-table>
</div>
</template>
<script lang="ts">
export default defineComponent({
setup() {
// 1.发送网络请求,formData.value传递到queryInfo
const getPageData = (queryInfo: any = {}) => {
// 发送网络前,判断是否有对应权限
// 没有权限
if (!isQuery) return
// 有权限
store.dispatch('system/getPageListAction', {
pageName: props.pageName,
queryInfo: {
offset: (pageInfo.value.currentPage - 1) * pageInfo.value.pageSize,
size: pageInfo.value.pageSize,
// 将formData.value解构,作为查询条件进行传递
...queryInfo
}
})
}
// 2.调用发送网络请求 的函数
getPageData()
// 3.从Vuex中获取数据
const dateList = computed(() => {
return store.getters[`system/pageListDate`](props.pageName)
})
const dataCount = computed(() => {
return store.getters[`system/pageListCount`](props.pageName)
})
return {
dateList,
dataCount
}
}
})
</script>
总结
还有个登录模块的内容没有弄,因为现在也没做,等后续需要了看看在说。
更多推荐
所有评论(0)