github: https://github.com/heyongsheng/hevue3-admin 码云: https://gitee.com/ihope_top/hevue3-admin 线上体验地址 https://ihope_top.gitee.io/hevue3-admin
本篇文章会记录一些项目中用到的,但又没必要单独写成文章的一些小知识点的汇总,还有我自己用的前端字典(仅供参考)
在vue2中,我们的全局变量通常使用prototype
挂载到vue上,但是在vue3中因为没有this,所以我们不能这么做,我们只能把全局变量挂载到app上,就像这样
// main.ts
const app = createApp(App)
app.config.globalProperties.adminName = 'admin'
app.mount('#app')
// 页面中使用
import { getCurrentInstance } from 'vue'
const globalVar = getCurrentInstance().appContext.app.config.globalProperties
这样使用起来难免不是太方便,我这里给全局变量做了一个封装,我们在utils
目录下创建一个global.ts
import { getCurrentInstance } from 'vue'
import { superAdmin, superAdminRole } from '../dictionary/staff'
export default function useGetGlobalProperties() {
const {
appContext: {
app: {
config: { globalProperties },
},
},
} = getCurrentInstance() as any
return { ...globalProperties, superAdmin, superAdminRole }
}
这里我们把全局变量做一个汇总,使用的时候就会更方便一点。
import useGetGlobalProperties from '@/utils/global'
const globalProperties = useGetGlobalProperties()
<el-button
type="primary"
link
@click="showModal(row)"
v-permission="'sys:role:edit'"
v-if="row.role_code !== globalProperties.superAdminRole"
>编辑</el-button
>
在后台管理系统中,我们的表单中往往会出现大量的基础数据选项,比如性别(男/女),用户状态(正常/禁用),往往后端存储的时候会用数字或者单词代表它们,所以我们传值和接受到的时候都是数字或者单词,我们进行回显的时候又需要把这些数字或者单词转变为汉字进行展示,往往需要一长串的三元运算符或者循环进行展示(我是这么写的,不知道大家的做法)。一旦这个数据用在两个甚至两个以上的地方,并且需要进行修改的话,我们必须一个个地方的去修改,将极其痛苦,你甚至都不知道这个数据都用在了哪,所以我就想,能不能把这些数据也存成变量,需要用的时候直接使用变量。
在稍微成熟点的团队里,后端应该都会维护一个叫字典项的东西,这东西其实我只是大概知道,也不太懂,因为没有过系统的学习过后端。至少在我以前工作的场景中,后端只会口头或者以文档的方式告知字段中哪个值代表的什么含义,比如在性别中,1代表男,2代表女。不会有专门的接口去获取这些字典项。所以我就想,如果后端没提供接口的话,前端能不能也自己维护这么一个字典呢,这次系统的写管理系统,正好就摸索一下,如果有相同需求的小伙伴可以看一下,如果你所在的项目流程及其规范,请跳过。
我项目里能用的地方我基本都用了,比如性别、菜单类型、菜单是否隐藏、菜单是否缓存等。这里我们就拿菜单是否隐藏来举例子吧,因为这个也比较典型。
首先我们先把他的数据写出来
// 菜单是否隐藏
export const menuHideDic: object = {
'0': '否',
'1': '是'
}
这就是一个很简单的数据,我们只需在使用的时候引入这个数据就行
例如表单处:
import { menuHideDic } from '@/dictionary/menu'
<el-form-item label="隐藏菜单:" v-if="formContent.menuType !== '3'">
<el-radio-group v-model="formContent.hidden">
<el-radio
v-for="(value, key) in menuHideDic"
:key="key"
:label="key"
>{{ value }}</el-radio
>
</el-radio-group>
</el-form-item>
展示表格处:
import { menuHideDic } from '@/dictionary/menu'
<el-table-column prop="hidden" label="隐藏菜单" width="80" align="center">
<template #default="{ row }">
<div>{{ menuHideDic[row.hidden] }}</div>
</template>
</el-table-column>
怎么样,是不是感觉有那么一点好用了。但是这时候我发现,在表单初始化的时候,这种radio类的标签,往往需要给一个默认值,如果我们写死的话,就和字典的初衷有点违背了,虽然这种东西的值很大概率是不会修改的,但我有强迫症,总是感觉不舒服。
这时候就能体现出前端字典项的一个优点了,那就是灵活性,我们想加什么加什么。比如这里我们可以给他加一个默认值。
interface defaultType {
default: string
[key: string]: string
}
// 菜单是否隐藏
export const menuHideDic: defaultType = {
'0': '否',
'1': '是',
default: '0',
}
初始化的时候就可以直接写menuHideDic.default
来当初始值啦。但这样写又会有一个问题,那就是在表单渲染的时候会把这个default也渲染上。
这个问题我们可以给default字段设置不可枚举来解决。
Object.defineProperty(menuHideDic, 'default', {
enumerable: false,
})
现在页面就可以正常的展示了
还有就是这种有判断意义的字段,例如是否隐藏啊,是否固定标签栏啊,肯定都会有地方进行判断,由于我们字段值很可能不是boolean
类型的值,所以我们还需要一个字段,用来表示真值。
interface boolRadio {
trueValue: string
[key: string]: string
}
interface defaultType {
default: string
[key: string]: string
}
// 菜单是否隐藏
export const menuHideDic: boolRadio | defaultType = {
'0': '否',
'1': '是',
trueValue: '1',
default: '0',
}
Object.defineProperty(menuHideDic, 'trueValue', {
enumerable: false,
})
Object.defineProperty(menuHideDic, 'default', {
enumerable: false,
})
用的地方
可能很多人会觉得这个东西有点多此一举,因为我对字典这个东西了解的也不多,但我自己觉得还挺好用的,所以见仁见智吧,只是给大家提供一个思路,如果感兴趣的同学也可以试试。
这里补上一个之前遗漏的点,就是element-plus国际化,element-plus默认语言为英文,我们这里需要设置为中文,所以需要引入一下中文包。
由于我们是自动引入的,所以无法进行全局配置,element-plus为我们提供了一个全局配置的组件,我们可以在App.vue
中进行使用
<template>
<el-config-provider :locale="locale">
<router-view></router-view>
</el-config-provider>
</template>
<script setup lang="ts">
import { useDark } from '@vueuse/core'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
const locale = ref(zhCn)
useDark()
</script>
这里可能会报ts的错误
那我们就直接按提示的进行声明好了。我们可以在根目录env.d.ts
中添加提示的代码进行解决,现在element-plus展示的就是中文了。
declare module 'element-plus/dist/locale/zh-cn.mjs'
这个小问题有经验的朋友肯定都知道,这里记录一下,帮助经验少的朋友避个坑。
有很多初学的朋友在进行弹窗表单开发的时候可能会发现遇到各种表单无法重置的问题,最终只能在重置的地方手动赋值为初始值,这样虽然能解决问题,但毕竟不是一个很好的办法,我们还是需要知道具体的原因所在。
比如拿我这里的菜单管理举例,如果先点击添加,再点击其他操作,表单可以被重置,但如果我们先点击编辑,再点击添加,表单就无法被重置,这里我们来看一下具体的例子。
这里首先说一下,我会在添加弹窗关闭的时候重置表单
const modalClose = () => {
addFormRef.value?.resetFields()
}
下面是显示弹窗的代码,这里我们的解决方案就是加两个nextTick()
,但为了让大家看到效果,我们先来看看把两个nextTick()
都注释掉的效果。
const showPop = async (row: any) => {
addVisible.value = true
// await nextTick()
row && (formContent.menuType = row.menuType)
// await nextTick()
if (row) {
Object.keys(formContent).forEach((key) => {
formContent[key] = row[key] || formContent[key]
})
if (row._id) {
menuId.value = row._id
modalName.value = '编辑菜单'
} else {
modalName.value = '添加菜单'
menuId.value = ''
}
} else {
modalName.value = '添加菜单'
menuId.value = ''
}
}
先来看正常的点添加,然后关闭弹窗。
这个没什么问题,一切正常。但如果我们先点击编辑,再点击添加,就会发现问题了。
这时候我们会发现,进页面就点击编辑,之后再添加添加,编辑时候的数据不会被重置,所以我们可以得出一个结论,那就是表单重置的数据是会以弹窗第一次展示时的数据为初始数据的。我们可以根据这个结论再测试一下。
先点击添加,再点击编辑,再点击添加
没问题,因为我们页面第一次渲染的时候是添加事件触发的,展示的就是空数据,所以重置的时候就会重置会空数据。那按这个推论,如果我们开始的时候点击编辑,后面无论怎么操作,再点击添加,都会显示之前编辑的数据,我们这里来试一下,先点击编辑,修改一个字段,再点击添加,看看显示的会不会是编辑之前的数据。
看,猜测果然如我们所料,那为了解决这个问题,我们就在编辑的时候让弹窗先渲染,再修改数据就好了。这时候我们放开第一个await nextTick()
的注释,再来测试一下。
现在可以看到没问题了。但是如果我们点击二级菜单的修改,再点击添加,之后选二级菜单(就是页面),就会发现,这部分的数据依然没有被重置。
细心的朋友可能会发现,没有被重置的都是我们第一次初始状态下没有显示的字段。
在开发菜单管理时,添加和编辑菜单因为要根据菜单类型展示不同的表单,并使隐藏的部分验证规则不生效,所以我们采用v-if
来控制表单的显示与隐藏,上面我们已经说过表单无法重置的原因了,那就是首次展示的内容被当成了初始内容,所以再执行重置操作,就会重置到初次展示的内容。
虽然我们上面执行了await nextTick()
操作,使得表单在被赋值前就已经渲染了一遍,但渲染的时候menuType
是1
(也就是一级菜单),那么那些v-if
条件为menuType
等于2
或3
时候才显示的表单显然无法初始化,所以他们相当于是没有初始值的。而我们修改二级菜单时(menuType
等于2
),这时候这部分字段对应的表单才被初次初始化,而他们接收的值是编辑时候传入的值,这个值也就变成了这个表单项的初始值。要解决这个问题很简单,和上面一样,我们可以在第一次执行await nextTick()
之后先修改v-if
的依赖字段,比如这里就是menuType
,修改完之后我们执行await nextTick()
让表单再渲染一次,这时候显示条件为menuType
等于2
的表单项就会去我们事先定义好的初始值里去找,如果找到就会作为自己的初始值。之后我们再对其他值进行赋值,这样我们之后再执行重置操作,表单就会显示我们最初定义的数据了。
这里我们把第二个await nextTick()
的注释放开
可以看到完全没有问题了。
打包这里就说一下配置公共资源目录吧。就是不配置你放网站二级目录就会白屏那个。
// vite.config.ts
base: './',
到这里本系列的初版就告一段落啦,因为最近事情挺多的,所以写的比较仓促,后期有时间也会不断的完善。欢迎大家指出文章和代码的不足,感谢观看,希望可以对大家有所帮助。