Vue开发过程中经常使用大量相似的组件,比如弹窗、表格、表单、菜单栏、面包屑...等等,每个人也有每个人的一套代码逻辑,为了提高效率也会总结自己的组件逻辑,因此在这里进行自己的自定义组件的整理和总结,力求做出好用,易用的自定义组件
1.弹窗组件
弹窗dialog是Vue PC开发中使用最频繁的组件,但是弹窗其实除了窗体的内容略有不同之外,窗头和窗尾的样式和内容都是相似的,因此这个也是最值得被重用的一个组件。
- 弹窗组件页面 dialog.vue:
<template>
<div class="dialog">
<el-dialog
v-if="dialogData"
:title="dialogData.title"
:visible.sync="visible"
:before-close="cancelClick"
:center="false"
>
<div class="dialog-content">
<h1>这里是根据dialogData内容的不同形成的不同的弹窗内容</h1>
</div>
<slot name="fixContent">
<!-- 这里可以放置一些无需处理数据的固定内容 -->
</slot>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="cancelClick">取 消</el-button>
<el-button
type="primary"
size="medium"
v-resetButton="1000"
@click="confirmClick"
>确 定</el-button
>
</span>
</el-dialog>
</div>
</template>
<script>
export default {
name: "dialog",
data() {
return {
dialogData: null,
visible: false,
};
},
props: {
dialogVal: {
//父级传递过来的弹窗内容
type: Object,
default: () => {
return null;
},
},
dialogDefaultVal: {
//编辑弹窗时,弹窗内容上要呈现的默认值
type: Object,
default: () => {
return null;
},
},
dialogVisible: {
//弹窗打开关闭开关
type: Boolean,
default: false,
},
},
watch: {
dialogVisible(newVal) {
//监控弹窗关闭
if (newVal) {
//弹窗打开时,进行弹窗的初始化
this.initDialogView();
} else {
//弹窗关闭时,对弹窗进行相应的格式化
this.resetDialogView();
}
this.visible = newVal;
},
},
methods: {
initDialogView() {
this.dialogData = this.dialogVal; //初始化页面的数据和相对应的赋值
if (this.dialogDefaultVal) {
//当有默认值时要进行赋值操作
}
},
resetDialogView() {
//重置页面的数据
this.dialogData = null;
this.cancelClick();
},
cancelClick() {
//点击取消或者X弹窗消失的操作
this.$emit("cancelClick");
},
confirmClick() {
//点击确定时,发送弹窗的内容数据的操作
},
},
};
</script>
<style lang="scss" scoped>
/deep/.el-dialog__header {
display: flex;
align-items: center;
}
</style>
- 具体调用页面 view.vue
<template>
<div class="view-box">
<el-button @click="btnClick">弹窗按钮</el-button>
<dialogCus
:dialogVal="dialogVal"
:dialogVisible="dialogVisible"
:dialogDefaultVal="dialogDefaultVal"
@cancelClick="cancelClick"
>
<template v-slot:fixContent>
<h1>这里可以放置一些无需处理数据的固定内容</h1>
</template>
</dialogCus>
</div>
</template>
<script>
import dialogCus from "./dialog.vue";
export default {
name: "view",
data() {
return {
dialogVisible: false, //弹窗是否显示
dialogDefaultVal: null, //弹窗默认值
dialogVal: null, //弹窗结构
};
},
components: {
dialogCus,
},
methods: {
cancelClick() {
//弹窗关闭,对数据进行初始化
this.dialogVisible = false;
this.dialogDefaultVal = null;
this.dialogVal = null;
},
btnClick() {
this.dialogVal = {
title: "自定义弹窗表头",
contents: [], // 这里可以编写弹窗的具体内容
};
this.dialogVisible = true;
},
},
};
</script>
2.批量添加/修改组件
可以利用表格的特性,制作一个批量添加/修改组件,然后再结合VeeVaildate校验和el-table的slot-scope="scope"的特性,就可以模拟成一个可以批量添加删除修改的表单。
<template>
<div class="view-box">
<h1>多选表单</h1>
<el-button type="primary" icon="el-icon-plus" @click="newAdd()">新增</el-button>
<p></p>
<ValidationObserver ref="observer">
<el-table max-height="445px" :data="tableData" border>
<el-table-column type="index" label="序号" width="50"></el-table-column>
<el-table-column label="名称" prop="name">
<template slot-scope="scope">
<ValidationProvider rules="required" v-slot="{ errors }">
<el-input
:maxlength="30"
placeholder="请输入名字"
v-model="scope.row.name"
:class="{ 'error-txt': errors[0] }"
>
</el-input>
<span class="validate-span">{{ errors[0] }}</span>
</ValidationProvider>
</template>
</el-table-column>
<el-table-column label="性别" prop="gender">
<template slot-scope="scope">
<ValidationProvider rules="required" v-slot="{ errors }">
<el-input
:maxlength="30"
placeholder="请输入性别"
v-model="scope.row.gender"
:class="{ 'error-txt': errors[0] }"
>
</el-input>
<span class="validate-span">{{ errors[0] }}</span>
</ValidationProvider>
</template>
</el-table-column>
<el-table-column label="年龄" prop="age">
<template slot-scope="scope">
<ValidationProvider rules="required" v-slot="{ errors }">
<el-input
:maxlength="30"
placeholder="请输入年龄"
v-model="scope.row.age"
:class="{ 'error-txt': errors[0] }"
>
</el-input>
<span class="validate-span">{{ errors[0] }}</span>
</ValidationProvider>
</template>
</el-table-column>
<el-table-column label="操作" prop="operation">
<template slot-scope="scope">
<span @click="delClick(scope.row)"> 删除 </span>
</template>
</el-table-column>
</el-table>
</ValidationObserver>
<p></p>
<el-button type="primary" @click="submit()">提交</el-button>
</div>
</template>
<script>
import { required } from "vee-validate/dist/rules";
import { ValidationProvider, ValidationObserver, extend } from "vee-validate"; //自定义校验的方法
extend("required", {
...required,
message: "此项必填",
});
export default {
name: "view",
data() {
return {
tableData: [],
};
},
components: {
ValidationProvider,
ValidationObserver,
},
methods: {
newAdd() {
let tempObj = {
id: Date.now(), //给手动添加的表单,赋值id
isExised: false, //为了区分,自己手动添加的还是数据库查询出来的数据
name: "",
gender: "",
age: "",
};
this.tableData.push(tempObj);
},
delClick(row) {
this.tableData.splice(
this.tableData.findIndex((t) => t.id == row.id),
1
);
},
},
submit() {
this.$refs.observer.validate().then((vali) => {
if (vali) {
console.log("发送请求的信息", this.tableData);
}
});
},
};
</script>
<style lang="scss" scoped>
.view-box {
text-align: left;
.validate-span {
font-size: 14px;
color: tomato;
}
/deep/.el-input.error-txt .el-input__inner {
border: 1px solid tomato !important;
}
}
</style>
- 实现效果如图:
- 校验效果如图:
3.自定义form表单的实现
自定义表单也是非常常用的一个组件,但是经常需要花费大量的时间来编写表单本身,所以这里就把自己常用的表单做成一个公共页面,然后使用数据并结合el-row的colspan来控制表单的展示,最终获取相应的formData,将前端的精力聚焦数据本身即可。实现页面如下:
- data.js
//这是自定义的表单内容的数据,可以根据自己的需要进行编写,之后控制表单就可以按照数据来处理表单
let formContents = [
{
label: "基本信息", //名称
type: "titlebox", //类型,自己定义的一般加box,表示不是formItem,而是一个box的自定义内容
prop: "essentialInformation", //标识,在box自定义内容中,仅仅是一个标识
colspan: 24, //该自定义内容再el-row中所占的大小,24表示是一整行
},
{
label: "会议名称", //名称,在formItem中还是label名
type: "input", //类型,表示是formItem中的编辑内容,比如这个就是一个input
prop: "meetingName", //标识,再formItem中还是formData的属性名,且与表单校验相关联
disabled: true, //表示表单该内容是否禁用
colspan: 8, //代表该表单内容占三分之一宽度的大小
rules: [
{
required: true,
message: "会议名称不能为空",
trigger: "blur",
},
], //代表该表单内容的校验规则
},
{
label: "开始时间",
type: "datetimepicker",
prop: "startTime",
disabled: true,
colspan: 8,
rules: [
{
type: "date",
required: true,
message: "开始时间不能为空",
trigger: "change",
},
],
},
{
label: "结束时间",
type: "datetimepicker",
prop: "endTime",
disabled: true,
colspan: 8,
rules: [
{
type: "date",
required: true,
message: "开始时间不能为空",
trigger: "change",
},
],
},
{
label: "会议地点",
type: "input",
prop: "address",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "会议地点不能为空",
trigger: "blur",
},
],
},
{
label: "参会人员",
type: "input",
prop: "participants",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "参会人员不能为空",
trigger: "blur",
},
],
},
{
label: "会议类型",
type: "input",
prop: "meetingtype",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "会议类型不能为空",
trigger: "blur",
},
],
},
{
label: "会议密级",
type: "input",
prop: "meetingsecuritylevel",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "会议密级不能为空",
trigger: "blur",
},
],
},
{
label: "主讲人",
type: "input",
prop: "speaker",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "主讲人不能为空",
trigger: "blur",
},
],
},
{
label: "记录人",
type: "input",
prop: "noteTaker",
disabled: true,
colspan: 8,
rules: [
{
required: true,
message: "记录人不能为空",
trigger: "blur",
},
],
},
{
label: "会议纪要",
type: "buttonbox",
prop: "meetingMinutes",
disabled: false,
colspan: 24,
},
{
label: "会议议程",
type: "input",
prop: "meetingAgenda",
disabled: false,
colspan: 24,
rules: [
{
required: true,
message: "会议议程不能为空",
trigger: "blur",
},
],
},
{
label: "会议结论",
type: "textarea",
prop: "meetingConclusions",
disabled: false,
colspan: 24,
rules: [
{
required: true,
message: "会议结论不能为空",
trigger: "blur",
},
],
},
];
export { formContents };
- form.vue
<template>
<div class="meetingminutes">
<el-form
v-if="formData"
:model="formData"
ref="ruleForm"
label-width="130px"
label-suffix=":"
label-position="left"
>
<el-row :gutter="20">
<el-col
v-for="item in formDataContents"
:key="item.prop"
:span="item.colspan"
>
<template v-if="item.type == 'titlebox'">
<div class="title-box">{{ item.label }}:</div>
</template>
<template v-else-if="item.type == 'buttonbox'">
<div class="button-box">
<span>{{ item.label }}:</span>
<el-button type="primary" @click="exportMeetingMinutes(formData)">
导出会议纪要
</el-button>
</div>
</template>
<template v-else>
<el-form-item
:label="item.label"
:prop="item.prop"
:rules="!item.disabled ? item.rules : []"
>
<template v-if="item.type == 'input'">
<el-input
v-model="formData[item.prop]"
:disabled="item.disabled"
></el-input>
</template>
<template v-else-if="item.type == 'datetimepicker'">
<el-date-picker
:disabled="item.disabled"
style="width: 100%"
v-model="formData[item.prop]"
type="datetime"
placeholder="选择日期时间"
>
</el-date-picker>
</template>
<template v-else-if="item.type == 'textarea'">
<el-input
:disabled="item.disabled"
type="textarea"
:autosize="{ minRows: 4, maxRows: 10 }"
placeholder="请输入内容"
v-model="formData[item.prop]"
></el-input>
</template>
</el-form-item>
</template>
</el-col>
</el-row>
</el-form>
<el-button @click="cancel()"> 取消</el-button>
<el-button type="primary" @click="confirm()">提交</el-button>
</div>
</template>
<script>
import { formContents } from "./data";
export default {
name: "meetingminutes",
data() {
return {
formDataContents: formContents, //表单内容数据
formData: null, //表单数据,该页面最终要用来保存的数据
};
},
created() {
this.formData = {};
this.formDataContents.forEach((f) => {
if (["input", "datetimepicker", "textarea"].includes(f.type)) {
//给this.formData赋初始值。根据表单内容数据,给单表数据赋初始值,一般是新增操作为空,编辑操作有值,如果有值,则根据情况自行判断并赋值。
this.$set(this.formData, f.prop, ""); //使用数据动态生成的表单,得使用this.$set,才能完成双向绑定,否则无法初始化
}
});
},
methods: {
cancel() {},
confirm() {
this.$refs.ruleForm
.validate()
.then((res) => {
//校验无误后提交
console.log(res);
})
.catch((err) => {
console.log(err);
});
},
exportMeetingMinutes(formData) {
console.log("exportMeetingMinutes formData", formData); //从这里拿到相应得数据,进行相应得请求以导出数据
},
},
};
</script>
<style lang="scss" scoped>
.meetingminutes {
padding: 30px;
box-sizing: border-box;
.el-row {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap; //el-row本身不会让el-col进行换行,所以需要结合flex的换行机制,使其换行。
.button-box {
font-family: "Microsoft YaHei", sans-serif;
font-size: 16px;
font-weight: 700;
margin-bottom: 22px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-box {
font-family: "Microsoft YaHei", sans-serif;
font-size: 16px;
text-align: left;
font-weight: 700;
margin-bottom: 22px;
}
}
}
</style>
- 效果图如下:
自定义form表单的优化
上面的示例可以做出一个简单的没有自定义DOM的组件,但是如果业务需要做一些elementUI上没有的DOM元素,那就比较麻烦了,如果使用v-if来渲染自定义组件也可以做到,但是随着项目越来越大,组件就会越来越臃肿,越来越难以维护,无法做到重用的目的,失去了组件的意义。因此,需要使用mixins属性把相应的数据分离开,使用函数式组件和JSX结合的方式,使组件尽量做到重用。当然这样的作法也可以运用的到所有的组件中,让组件尽量有可复用性。
- view.vue
<template>
<div class="formData">
<formDataView
ref="formView"
:formDataContents="formDataContents"
v-model="formData"
@validateConfirm="validateConfirm"
></formDataView>
<div class="box">
<el-button type="primary" @click="confirmClick">确定</el-button>
</div>
</div>
</template>
<script>
import { formDataMixins } from "./formDataMixins.js";
import formDataView from "./formDataView.vue";
export default {
name: "formData",
mixins: [formDataMixins],
components: {
formDataView,
},
methods: {
validateConfirm(vail) {
console.log("confirmDataValidate vail", vail);
},
confirmClick() {
this.$refs.formView.confirmValidate();
},
},
};
</script>
- formDataMixins.js
export const formDataMixins = {
data() {
return {
pickerTime: "",
address: "",
formDataContents: [
{
label: "基本信息", //名称
type: "titlebox", //类型,自己定义的一般加box,表示不是formItem,而是一个box的自定义内容
prop: "essentialInformation", //标识,在box自定义内容中,仅仅是一个标识
colspan: 24, //该自定义内容再el-row中所占的大小,24表示是一整行
render: (h, item) => {
return <div class="title-box">{item.label}:</div>;
},
},
{
label: "会议名称", //名称,在formItem中还是label名
type: "input", //类型,表示是formItem中的编辑内容,比如这个就是一个input
prop: "meetingName", //标识,再formItem中还是formData的属性名,且与表单校验相关联
disabled: false, //表示表单该内容是否禁用
colspan: 8, //代表该表单内容占三分之一宽度的大小
rules: [
{
required: true,
message: "会议名称不能为空",
trigger: "blur",
},
], //代表该表单内容的校验规则
},
{
label: "会议类型",
type: "select",
prop: "type",
disabled: this.$route.params.isEditor == "1",
colspan: 8,
inClearable: true,
rules: [
{
required: true,
message: "会议类型不能为空",
trigger: "change",
},
],
selects: [
{
value: "A",
label: "A",
contents: {
id: "Aa",
},
},
{
value: "B",
label: "B",
contents: {
id: "Bb",
},
},
{
value: "C",
label: "C",
contents: {
id: "Cc",
},
},
{
value: "D",
label: "D",
contents: {
id: "Dd",
},
},
],
selectClick: (item, prop) => this.selectClick(item, prop),
},
{
label: "时间和地址",
type: "buttonAppendPopover",
prop: "timeAndAddress",
disabled: this.$route.params.isEditor == "1",
colspan: 8,
placeholder: "请选择时间和地址",
rules: [
{
required: true,
message: "时间和地址不能为空",
trigger: "blur",
},
],
readonly: true,
render: (h, item) => {
return (
<div class="pop-con-box">
<div class="pop-con-box-item">
<span>时间:</span>
<el-date-picker
vModel={this.pickerTime}
type="date"
placeholder="选择日期"
value-format="yyyy-MM-dd"
format="yyyy 年 MM 月 dd 日"
></el-date-picker>
</div>
<div class="pop-con-box-item">
<span>地址:</span>
<el-input size="medium" vModel={this.address}></el-input>
</div>
<el-button
type="text"
on-click={() => this.setAddressAndTimeTxt(item.prop)}
>
确定
</el-button>
</div>
);
},
},
{
label: "开始时间",
type: "datetimepicker",
prop: "startTime",
disabled: false,
colspan: 8,
rules: [
{
type: "date",
required: true,
message: "开始时间不能为空",
trigger: "change",
},
],
},
{
label: "结束时间",
type: "datetimepicker",
prop: "endTime",
disabled: false,
colspan: 8,
rules: [
{
type: "date",
required: true,
message: "开始时间不能为空",
trigger: "change",
},
],
},
{
label: "会议地点",
type: "input",
prop: "address",
disabled: false,
colspan: 8,
rules: [
{
required: true,
message: "会议地点不能为空",
trigger: "blur",
},
],
},
{
label: "会议类型",
type: "input",
prop: "meetingtype",
disabled: false,
colspan: 8,
rules: [
{
required: true,
message: "会议类型不能为空",
trigger: "blur",
},
],
},
{
label: "主讲人",
type: "input",
prop: "speaker",
disabled: false,
colspan: 8,
rules: [
{
required: true,
message: "主讲人不能为空",
trigger: "blur",
},
],
},
{
label: "记录人",
type: "input",
prop: "noteTaker",
disabled: false,
colspan: 8,
rules: [
{
required: true,
message: "记录人不能为空",
trigger: "blur",
},
],
},
{
label: "会议纪要",
type: "buttonbox",
prop: "meetingMinutes",
disabled: false,
colspan: 24,
render: (h, item) => {
return (
<div class="button-box">
<span>{item.label}:</span>
<el-button
type="primary"
on-click={() => this.exportMeetingMinutes()}
>
导出会议纪要
</el-button>
</div>
);
},
},
{
label: "是否通过",
type: "radio",
prop: "status",
disabled: false,
colspan: 8,
rules: [
{ required: true, message: "请选择评审结果", trigger: "change" },
],
radios: [
{
label: "PASS",
name: "通过",
},
{
label: "FAIL",
name: "不通过",
},
],
},
{
label: "会议议程",
type: "input",
prop: "meetingAgenda",
disabled: false,
colspan: 24,
rules: [
{
required: true,
message: "会议议程不能为空",
trigger: "blur",
},
],
},
{
label: "会议结论",
type: "textarea",
prop: "meetingConclusions",
disabled: false,
colspan: 24,
rules: [
{
required: true,
message: "会议结论不能为空",
trigger: "blur",
},
],
},
], //表单内容数据
formData: null, //表单数据,该页面最终要用来保存的数据
};
},
created() {
this.formData = {};
this.formDataContents.forEach((f) => {
if (
[
"input",
"datetimepicker",
"textarea",
"radio",
"select",
"timeAndAddress",
].includes(f.type)
) {
//给this.formData赋初始值。根据表单内容数据,给单表数据赋初始值,一般是新增操作为空,编辑操作有值,如果有值,则根据情况自行判断并赋值。
this.$set(this.formData, f.prop, ""); //使用数据动态生成的表单,得使用this.$set,才能完成双向绑定,否则无法初始化
}
});
},
methods: {
exportMeetingMinutes() {
console.log("exportMeetingMinutes formData", this.formData); //从这里拿到相应得数据,进行相应得请求以导出数据
},
selectClick(item, prop) {
//下拉框点击事件 item 是option的item,prop 是数组的标识,是唯一的标识
console.log("selectClick", item, prop);
},
setAddressAndTimeTxt(prop) {
if (this.pickerTime && this.address) {
this.$set(this.formData, prop, `${this.pickerTime} ${this.address}`);
} else {
this.$message.error("时间和地址都不可为空");
}
},
},
};
- formDataView.vue form表单组件
<template>
<div class="form-com">
<el-form
v-if="value"
:model="value"
ref="ruleForm"
:label-width="labelWidth || '100px'"
label-suffix=":"
label-position="left"
>
<el-row :gutter="20">
<el-col
v-for="item in formDataContents"
:key="item.prop"
:span="item.colspan"
>
<template v-if="['titlebox', 'buttonbox'].includes(item.type)">
<!-- 自定义函数式组件 -->
<cusSlot
v-if="item.render"
:itemObj="item"
:render="item.render"
></cusSlot>
</template>
<template v-else>
<el-form-item
:label="item.label"
:prop="item.prop"
:rules="!item.disabled ? item.rules : []"
>
<template v-if="item.type == 'input'">
<el-input
:size="item.size || 'medium'"
v-model="value[item.prop]"
:disabled="item.disabled"
></el-input>
</template>
<template v-else-if="item.type == 'datetimepicker'">
<el-date-picker
:size="item.size || 'medium'"
:disabled="item.disabled"
style="width: 100%"
v-model="value[item.prop]"
type="datetime"
placeholder="选择日期时间"
>
</el-date-picker>
</template>
<template v-else-if="item.type == 'textarea'">
<el-input
:size="item.size || 'medium'"
:disabled="item.disabled"
type="textarea"
:autosize="{ minRows: 4, maxRows: 10 }"
placeholder="请输入内容"
v-model="value[item.prop]"
></el-input>
</template>
<template v-else-if="item.type == 'select'">
<el-select
v-model="value[item.prop]"
placeholder="请选择"
:disabled="item.disabled"
:multiple="item.multiple"
:size="item.size || 'medium'"
filterable
:clearable="!item.inClearable"
style="width: 100%"
>
<el-option
v-for="selectItem in item.selects"
:key="selectItem.value"
:label="selectItem.label"
:value="selectItem.value"
@click.native="item.selectClick(selectItem, item.prop)"
>
</el-option>
</el-select>
</template>
<template v-else-if="item.type == 'radio'">
<el-radio-group v-model="value[item.prop]">
<el-radio
:size="item.size || 'medium'"
v-for="radio in item.radios"
:key="radio.label"
:label="radio.label"
:disabled="item.disabled"
>
{{ radio.name }}
</el-radio>
</el-radio-group>
</template>
<template v-else-if="item.type == 'buttonAppendPopover'">
<template v-if="item.prop == 'timeAndAddress'">
<div class="btnAppPop">
<el-popover
placement="bottom"
:size="item.size || 'medium'"
:popper-class="item.popperClass || ''"
:trigger="item.trigger || 'click'"
>
<cusSlot
v-if="item.render"
:itemObj="item"
:render="item.render"
></cusSlot>
<el-input
slot="reference"
:size="item.size || 'medium'"
:placeholder="item.placeholder"
v-model="value[item.prop]"
:readonly="item.readonly"
>
</el-input>
</el-popover>
</div>
</template>
</template>
</el-form-item>
</template>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script>
//自定义函数式组件 ==START
let cusSlot = {
functional: true, //代表这是一个函数式组件
props: {
itemObj: Object, //接受父组件传来得数据
render: Function, //接受父组件传来的回调函数,用于把JSX编写的DOM以及DOM上的事件抽取出来到数据脚本页面,尽量不污染外部组件,使组件更加的灵活轻便可复用。
},
render: (h, context) => {
//函数式组件用来渲染相应的DOM,有两个参数,h:render渲染函数,context:上下文对象,可以获取相应父组件传到props中的数据
//使用父组件传来的render回调函数,将JSX编写的DOM对象抽取出去,保持组件的灵活性。*****关键*****
return context.props.render(h, context.props.itemObj);
},
};
//自定义函数式组件 ==END
export default {
name: "formView",
components: {
cusSlot,
},
props: {
value: {
/*因为经常性的要处理表单数据,
所以为了减少跨组建传值的次数,
就模拟使用v-model来双向绑定表单数据,
这样可以方便处理数据。*/
type: Object,
default: () => null,
},
formDataContents: {
//表单内容DOM数组
type: Array,
default: () => [],
},
labelWidth: {
type: String,
default: "",
},
},
watch: {
value: {
handler(newVal) {
//就模拟使用v-model来双向绑定表单数据
console.log("watch value", newVal);
this.$emit("input", newVal);
},
deep: true,
immediate: true,
},
},
methods: {
confirmValidate() {
this.$refs.ruleForm
.validate()
.then((res) => {
//校验无误,方可提交数据
this.$emit("validateConfirm", res);
})
.catch((err) => {
this.$emit("validateConfirm", err);
});
},
},
};
</script>
<style lang="scss" scoped>
.form-com {
width: 100%;
padding: 30px;
box-sizing: border-box;
.el-row {
display: flex;
align-items: center;
justify-content: flex-start;
flex-wrap: wrap; //el-row本身不会让el-col进行换行,所以需要结合flex的换行机制,使其换行。
.button-box {
font-family: "Microsoft YaHei", sans-serif;
font-size: 16px;
font-weight: 700;
margin-bottom: 22px;
display: flex;
justify-content: space-between;
align-items: center;
}
.title-box {
font-family: "Microsoft YaHei", sans-serif;
font-size: 16px;
text-align: left;
font-weight: 700;
margin-bottom: 22px;
}
/deep/.el-form-item__content {
text-align: left;
.el-radio-group {
width: 100%;
}
}
.btnAppPop {
display: flex;
width: 100%;
justify-content: flex-start;
align-items: center;
.appendBtn {
flex: 0 0 98px;
height: 36px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
/deep/.el-input {
flex: 1;
.el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
span {
flex: 1;
/deep/.el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
}
}
}
</style>
<style lang="scss">
.pop-con-box {
width: 514px;
.pop-con-box-item {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
span {
flex: 1;
}
div {
flex: 3;
}
}
}
</style>
这样在
formDataView.vue表单中只进行了表单校验的操作,在view.vue主页面只进行提交交互操作,而处理form表单内部数据的操作都在formDataMixins.js脚本进行,在formDataMixins.js页面声明数据,处理数据,然后双向绑定this.formData避免了大量的父子组件之间的传值,这样可以使formDataView.vue表单组件尽可能的重用,formDataMixins.js脚本的数据在不同页面的相同数据情况下,也可以大量重复使用,从而避免多次声明数据的重复操作,也避免了view.vue主页面代码量过大,从而导致的后期难以维护。
- 效果如图:
自定义tree组件
- treeView.vue
<template>
<div class="treeview">
<el-tree
v-if="treeDataObj"
ref="tree"
:data="treeDataObj.treeData"
:default-expanded-keys="treeDataObj.expendNodesItems"
:current-node-key="treeDataObj.currentNodeKey"
:props="treeDataObj.defaultProps"
:node-key="treeDataObj.id"
:expand-on-click-node="treeDataObj.expand_on_click_node"
@node-click="treeDataObj.handleNodeClick"
></el-tree>
</div>
</template>
<script>
export default {
name: "treeview",
props: {
treeDataObj: {
type: Object,
default: () => null,
},
},
created() {
console.log(this.treeDataObj);
},
};
</script>
<style lang="scss" scoped>
.treeview {
width: 100%;
}
</style>
- treeViewMixins.js
import sysDocManage from "@/services/sysDocManage.service";
export const treeViewMixins = {
data() {
return {
expendNodesItems: [],
practiceFieldId: "",
treeDataObj: {
treeData: [], //树状数据
expendNodesItems: [], //展开的数据
currentNodeKey: "",
defaultProps: {
children: "children",
label: "practiceFieldName",
},
id: "practiceFieldId",
expand_on_click_node: false,
handleNodeClick: this.handleNodeClick,
},
};
},
created() {
this.initTreeDataView();
},
methods: {
initTreeProp(data) {
this.treeDataObj = {
treeData: data, //树状数据
expendNodesItems: this.expendNodesItems.map((m) => m.practiceFieldId), //展开树的数组
currentNodeKey: this.practiceFieldId, //最开始选中的节点
defaultProps: {
children: "children",
label: "practiceFieldName",
},
id: "practiceFieldId",
expand_on_click_node: false,
handleNodeClick: this.handleNodeClick,
};
},
initTreeDataView() {
sysDocManage.getPracticeDictTree().then((res) => {
let { code, data, message } = res;
if (code == 200) {
this.findNodeID(data);
this.treeDataObj = null; //因为有些属性,需要一起传入,所以要
this.$nextTick(() => {
this.initTreeProp(data);
});
this.initData();
} else {
this.$message.error(message);
}
});
},
findNodeID(arr, practiceFieldId) {
let obj = arr.find((a) => {
if (practiceFieldId) {
//如果有id,说明要找固定的id的node
} else {
//没有传id,说明找到第一个的最后的那个数据
if (
Object.prototype.toString.call(a.children).slice(8, -1) == "Array"
) {
if (this.findNodeID(a.children, practiceFieldId)) {
return true;
}
} else {
this.practiceFieldId = a.practiceFieldId; //找到了第一层的最底层
return true;
}
}
});
if (obj) {
this.expendNodesItems.unshift(obj);
}
return obj;
},
handleNodeClick(data, node) {
console.log(data, node);
if (node.isLeaf) {
this.practiceFieldId = data.practiceFieldId;
this.initData();
}
},
},
};
- viewVue.vue
<template>
<div>
<treeView :treeDataObj="treeDataObj"></treeView>
</div>
</template>
import { treeViewMixins } from "./treeViewMixins";
export default {
mixins: [treeViewMixins]
components: {
treeView,
},
}
自定义table组件
- tableView.vue
<template>
<div class="table-view">
<ValidationObserver ref="observer">
<el-table
:span-method="arraySpanMethod"
:height="maxHightNum || '100%'"
:data="value"
border
@selection-change="handleSelectionChange"
>
<el-table-column
v-if="selection"
type="selection"
width="50"
></el-table-column>
<el-table-column
v-if="order"
type="index"
label="序号"
width="50"
></el-table-column>
<el-table-column
v-for="column in tableColumns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:type="column.type"
:width="column.width"
>
<template slot-scope="scope">
<template v-if="column.render">
<cusSlot
:column="column"
:row="scope.row"
:index="scope.$index"
:render="column.render"
></cusSlot>
</template>
<template v-else-if="column.isEdit">
<template v-if="column.type == 'input'">
<ValidationProvider
:rules="column.rules.join('|')"
v-slot="{ errors }"
>
<el-input
:maxlength="30"
placeholder="请输入"
v-model="scope.row[column.prop]"
:class="{ 'error-txt': errors[0] }"
clearable
>
</el-input>
<span class="validate-span">{{ errors[0] }}</span>
</ValidationProvider>
</template>
<template v-else-if="column.type == 'select'">
<ValidationProvider
:rules="column.rules.join('|')"
v-slot="{ errors }"
>
<el-select
v-model="scope.row[column.prop]"
:placeholder="column.placeholder"
:disabled="column.disabled"
:multiple="column.multiple"
:size="column.size || 'medium'"
filterable
:clearable="!column.inClearable"
style="width: 100%"
:class="{ 'error-txt': errors[0] }"
>
<el-option
v-for="selectItem in column.selects"
:key="selectItem.value"
:label="selectItem.label"
:value="selectItem.value"
@click.native="
column.selectClick(selectItem, column.prop)
"
>
</el-option>
</el-select>
<span class="validate-span">{{ errors[0] }}</span>
</ValidationProvider>
</template>
</template>
<template v-else>
{{ scope.row[column.prop] }}
</template>
</template>
</el-table-column>
</el-table>
</ValidationObserver>
</div>
</template>
<script>
import Sortable from "sortablejs";//用来进行elementUI拖拽使用
//自定义函数式组件 ==START
let cusSlot = {
functional: true, //代表这是一个函数式组件
props: {
column: Object, //接受父组件传来得数据
row: Object,
index: Number,
render: Function, //接受父组件传来的回调函数,用于把JSX编写的DOM以及DOM上的事件抽取出来到数据脚本页面,尽量不污染外部组件,使组件更加的灵活轻便可复用。
},
render: (h, context) => {
//函数式组件用来渲染相应的DOM,有两个参数,h:render渲染函数,context:上下文对象,可以获取相应父组件传到props中的数据
let cell = {
column: context.props.column,
row: context.props.row,
index: context.props.index,
};
//使用父组件传来的render回调函数,将JSX编写的DOM对象抽取出去,保持组件的灵活性。*****关键*****
return context.props.render(h, cell);
},
};
//自定义函数式组件 ==END
//校验组件 ==START
import { required } from "vee-validate/dist/rules";
import { ValidationProvider, ValidationObserver, extend } from "vee-validate"; //自定义校验的方法
// import validate from "@/utils/validateCus.js";
extend("required", {
...required,
message: "此项必填",
});
//校验组件 ==END
export default {
name: "tableView",
props: {
value: {
type: Array,
default: () => [],
},
tableColumns: {
type: Array,
default: () => [],
},
order: {
type: Boolean,
default: false,
},
selection: {
type: Boolean,
default: false,
},
draggable: {
type: String,
default: "",
},//给elementUI table添加拖拽的属性。
arraySpanMethod: {
//如果table要内容折叠,就传入这个函数
type: Function,
default: () => {},
},
maxHightNum: {
type: String,
default: "",
},
},
watch: {
value: {
handler(newVal) {
this.$emit("input", newVal);
},
deep: true,
immediate: true,
},
},
mounted() {
switch (this.draggable) {
case "rowDraggy":
//行拖拽
this.rowDraggy();
break;
case "colDraggy":
this.colDraggy();
//列拖拽
break;
default:
break;
}
},
components: {
cusSlot,
ValidationProvider,
ValidationObserver,
},
methods: {
handleSelectionChange(val) {
this.$emit("selectionItems", val);
},
rowDraggy() {
const tbody = document.querySelector(".el-table__body-wrapper tbody");
const _this = this;
Sortable.create(tbody, {
onEnd({ newIndex, oldIndex }) {
_this.$emit("rowDraggyEnd", {
newIndex,
oldIndex,
});
},
});
},
colDraggy() {
const wrapperTr = document.querySelector(".el-table__header-wrapper tr");
const _this = this;
this.sortable = Sortable.create(wrapperTr, {
animation: 180,
delay: 0,
handle: ".allowDrag", // 格式为简单css选择器的字符串,使列表单元中符合选择器的元素成为拖动的手柄,只有按住拖动手柄才能使列表单元进行拖动
filter: ".noDrag", // 过滤器,不需要进行拖动的元素
preventOnFilter: true, // 在触发过滤器`filter`的时候调用`event.preventDefault()`
draggable: ".allowDrag", // 允许拖拽的项目类名
onEnd: (evt) => {
_this.$emit("colDraggy", {
newIndex: evt.newIndex,
oldIndex: evt.oldIndex,
});
},
});
},
validateFun() {
if (this.$refs.observer) {
this.$refs.observer
.validate()
.then((res) => {
this.$emit("validateTable", res);
})
.catch((err) => {
this.$emit("validateTable", err);
});
}
},
},
};
</script>
<style lang="scss" scoped>
.table-view {
width: 100%;
.validate-span {
font-size: $extra-small-font-size;
color: tomato;
}
/deep/.el-select.error-txt .el-input__inner,
/deep/.el-textarea.error-txt .el-textarea__inner,
/deep/.el-input.error-txt .el-input__inner,
/deep/.el-cascader.error-txt .el-input__inner {
border: 1px solid tomato !important;
}
}
</style>
- tableViewMixins.js
import organizeManageService from "@/services/organizeManage.service";
import sysDocManage from "@/services/sysDocManage.service";
export const tableViewMixins = {
data() {
return {
formValidateRes: false,
tableValidateRes: false,
paginator: {
currentPage: 1,
pageSize: 10,
totalCount: 0,
},
loading: false,
selection: true,
selectTableItems: [],
tableData: [],
tableDataInit: [],
tableColumns: [
{
label: "体系文件",
prop: "fileName",
disabled: false,
render: (h, tableItem) => {
return (
<div>
<el-button
type="text"
disabled={tableItem.column.disabled}
on-click={() => this.viewSysDoc(tableItem)}
>
{tableItem.row[tableItem.column.prop]}
</el-button>
</div>
);
},
},
{
label: "分类",
width: "200",
prop: "fileTypeId",
isEdit: true,
disabled: false,
type: "select",
placeholder: "请选择分类",
size: this.$store.state.settings.formSize,
selects: this.sysFileTypesSelect || [],
rules: ["required"],
selectClick: (item, prop) => this.selectTableClick(item, prop),
},
{
label: "操作",
prop: "operation",
width: "200",
disabled: false,
render: (h, tableItem) => {
return (
<div>
<el-button
type="text"
disabled={tableItem.column.disabled}
on-click={() => this.deleteClick(tableItem)}
>
删除
</el-button>
</div>
);
},
},
],
};
},
methods: {
selectionItems(selectItems) {
console.log("selectionItems", selectItems);
this.selectTableItems = selectItems;
},
deleteClick(tableItem) {
console.log("deleteClick", tableItem);
this.tableData.splice(tableItem.index, 1);
},
selectTableClick(item, prop) {
console.log("selectTableClick", item, prop);
},
getTableTypes(formData) {
console.log("getTableTypes formData", formData, this.tableData);
let tableDataT = [];
this.tableData.forEach((t) => {
if (this.selectTableItems.find((s) => s.fileInfoId == t.fileInfoId)) {
t.fileTypeId = formData.fileTypeId;
}
tableDataT.push(t);
});
this.$set(this, "tableData", tableDataT);
this.dialogVisible = false;
},
getFormApprove(formData) {
console.log("getFormApprove formData", formData);
let tempArr = this.handleEPGs(formData);
this.saveAndSubmitFun(this.handleSaveAndSubmitParams(tempArr));
},
validateTable(vail) {
this.formValidateRes = vail;
if (vail && this.tableValidateRes) {
this.isNeedApprove();
}
},
validateConfirm(vail) {
this.tableValidateRes = vail;
if (vail && this.formValidateRes) {
this.isNeedApprove();
}
},
isNeedApprove() {
organizeManageService
.trainNeedsNeedApprove({
applyType: "", //待输入
})
.then((res) => {
let { code, message, data } = res;
if (code == 200) {
//先按照全部需要审批做,
if (this.$route.name == "applyview") {
this.saveAndSubmitFunApplyView(this.handleSaveAndSubmitParams());
} else {
this.setFormApprove();
}
// if (data) {
// // 需要审批,
// this.setFormApprove();
// } else {
// //不需要审批的情况,直接请求
// this.saveAndSubmitFun(this.handleSaveAndSubmitParams());
// }
} else {
this.$message.error(message);
}
});
},
saveAndsubmit() {
if (this.tableData.length == 0) {
this.$message.error("请上传文件后,再保存");
} else {
this.$refs.fromViewRef.confirmValidate();
this.$refs.tableViewRefs.validateFun();
}
},
handleTime(val) {
if (val > 10) {
return val;
} else {
return `0${val}`;
}
},
handleSaveAndSubmitParams(FlowRoleList) {
let params = {
applicationFlowRoleUserVoList: FlowRoleList || [],
executeDatetime:
Object.prototype.toString
.call(this.formData.executeDatetime)
.slice(8, -1) == "String"
? this.formData.executeDatetime
: `${this.formData.executeDatetime.getFullYear()}-${this.handleTime(
this.formData.executeDatetime.getMonth() + 1
)}-${this.handleTime(this.formData.executeDatetime.getDate())}`,
publishDatetime:
Object.prototype.toString
.call(this.formData.publishDatetime)
.slice(8, -1) == "String"
? this.formData.publishDatetime
: `${this.formData.publishDatetime.getFullYear()}-${this.handleTime(
this.formData.publishDatetime.getMonth() + 1
)}-${this.handleTime(this.formData.publishDatetime.getDate())}`,
revisedNotes: this.formData.revisedNotes,
systemFilePvoList: [],
systemVersion: (this.formData.systemVersion + "").includes(".")
? `${this.formData.systemVersion}`
: `${this.formData.systemVersion}.0`,
systemVersionId: this.formData.systemVersionId || "",
};
this.tableData.forEach((t) => {
params.systemFilePvoList.push({
fileInfoId: t.fileInfoId,
fileName: t.fileName,
fileType: this.sysFileTypesSelect.find(
(sys) => sys.value == t.fileTypeId
).label,
fileTypeId: t.fileTypeId,
sort: 0,
systemFileId: t.systemFileId || "",
});
});
return params;
},
saveAndSubmitFun(params) {
console.log("saveAndSubmitFun", params);
sysDocManage.sysFileLibManageSaveAndPublish(params).then((res) => {
let { code, message } = res;
if (code == 200) {
this.dialogVisible = false;
this.saveContentData();
this.$router.push({
path: "/bgManage/sysDocManagement",
});
} else {
this.$message.error(message);
}
});
},
setFormApprove() {
//发起审批
let auditDialogData = {};
this.auditDialogContents.forEach((r) => {
auditDialogData[r.prop] = "";
});
this.dialogVal = {
title: "发起审批",
content: {
type: "fromData",
contents: this.auditDialogContents,
handleType: "setApprove", //处理类型
defaultVal: auditDialogData, //如果有值的话,传到这里
}, // 这里可以编写弹窗的具体内容
};
this.dialogVisible = true;
},
handleEPGs(formData) {
console.log("this.roleEpgZcs", this.roleEpgZcs, formData);
let tempArr = [];
this.roleEpgZcs.forEach((r) => {
if (this.roleEpgZcs.find((e) => e.value == formData.epgId)) {
tempArr.push({
roleId: "ROLE_EPG_ZC",
roleName: "EPG组长",
userId: r.value,
userName: r.label,
});
}
});
return tempArr;
},
batchSetType() {
console.log(
"batchDelete",
this.selectTableItems,
this.typeDialogContents
);
this.setTableType();
},
setTableType() {
//设置分类
let typeDialogData = {};
this.typeDialogContents.forEach((r) => {
typeDialogData[r.prop] = "";
});
this.dialogVal = {
title: "设置分类",
content: {
type: "fromData",
labelWidth: "60px",
contents: this.typeDialogContents,
handleType: "setType", //处理类型
defaultVal: typeDialogData, //如果有值的话,传到这里
}, // 这里可以编写弹窗的具体内容
};
this.dialogVisible = true;
},
batchDelete() {
let selectTableItemsTemp = [];
this.tableData.forEach((t) => {
if (!this.selectTableItems.find((s) => s.fileInfoId == t.fileInfoId)) {
selectTableItemsTemp.push(t);
}
});
this.tableData = selectTableItemsTemp;
},
batchUpload() {
this.initInputFileDom();
},
initInputFileDom() {
if (document.getElementById("inputUploadElCus")) {
//如果input file标签存在,则先删除该标签,以免出现过多的这样的标签
this.removeInputFileDom();
}
let inputEl = document.createElement("input");
inputEl.setAttribute("id", "inputUploadElCus");
inputEl.setAttribute("type", "file");
inputEl.setAttribute("multiple", "multiple");
inputEl.style.display = "none";
document.body.appendChild(inputEl);
inputEl.addEventListener("change", this.getFileData);
inputEl.click();
},
removeInputFileDom() {
document
.getElementById("inputUploadElCus")
.parentNode.removeChild(document.getElementById("inputUploadElCus"));
},
getFileData(e) {
let formData = new FormData(); //创建一个空对象实例
for (let i = 0; i < e.target.files.length; i++) {
formData.append("file", e.target.files[i]);
}
sysDocManage.sysFileLibManageUploads(formData).then((res) => {
let { code, data, message } = res;
if (code == 200) {
data.forEach((d) => {
this.tableData.push(d);
});
} else {
this.$message.error(message);
}
});
},
handleSizeChange(val) {
this.paginator.pageSize = val;
this.initTableData();
},
handleCurrentChange(val) {
this.paginator.currentPage = val;
this.initTableData();
},
},
};
- viewVue.vue
<template>
<div>
<div class="tablebox">
<tableView
v-loading="loading"
ref="tableViewRefs"
:order="true"
:selection="true"
v-model="tableData"
:tableColumns="tableColumns"
@selectionItems="selectionItems"
@validateTable="validateTable"
></tableView>
</div>
</div>
</template>
<script>
import tableView from "../components/tableView.vue";
import { tableViewMixins } from "../mixins/tableViewMixinsSDLMAM.js";
export default {
mixins: [ tableViewMixins],
components: {
tableView,
},
}
</script>
大部分自定义的组件,只要牵涉到数据处理,大概就是这样写的,都大同小异,但是主要时这样的思想,数据从哪里生成在哪里处理(在
...mixin.js中生成和处理数据),组件只是展示数据的容器,一个组件用最简单的结构展示最复杂的内容的时候,这个组件才可能被最大程度的重复利用,而要做到这样的效果,就需要让组件结合Vue函数式编程和JSX语言,这样就可以尽可能的做出最容易重复利用的组件
tab与动态组件加载页面结合
- sysDoc.config.ts
export const componentsItem: any[] = [
{
props: "PM",
component: () => import("./views/pm.vue"), //实践管理
},
{
props: "SDLM",
component: () => import("./views/sdlm.vue"), //体系文件库管理
},
{
props: "MCRBSDS",
component: () => import("./views/mcrbsds.vue"), //体系文件与标准对照关系管理
},
];
- index.vue
<template>
<div class="train-box full-height-remedy flex-column-remedy boxHidden">
<div class="flex justify-between bgc">
<div>
<el-button type="primary" size="medium" @click="goBack">返回</el-button>
</div>
</div>
<div
class="
app-container
padding-bottom-none-remedy
flex-1-remedy
boxHidden
flex-cus
"
>
<el-tabs v-model="activeName" type="card" :before-leave="beforeLeave">
<el-tab-pane
label="体系文件与标准对照关系管理"
name="MCRBSDS"
></el-tab-pane>
<el-tab-pane label="体系文件库管理" name="SDLM"></el-tab-pane>
<el-tab-pane label="实践管理" name="PM"></el-tab-pane>
</el-tabs>
<div class="flex flex-1 flex-row boxHidden">
<component
v-if="componentObj"
ref="components"
:capLevels="capLevels"
:practiceFields="practiceFields"
:sysFileTypes="sysFileTypes"
:versionList="versionList"
:is="componentObj"
></component>
</div>
</div>
</div>
</template>
<script>
import { componentsItem } from "./sysDoc.config";
import assetLibrary from "@/services/assetLibrary.service";
import sysDocManage from "@/services/sysDocManage.service";
import ConfigService from "@/services/config.service";
export default {
name: "sysDoc",
data() {
return {
activeName: "PM",
componentObj: null,
capLevels: [], //能力等级
practiceFields: [], //实践域
sysFileTypes: [], //体系文件分类
versionList: [], //版本下拉框
roleEpgZcs: [], //epg组成员列表
};
},
created() {
this.initData();
},
beforeRouteEnter(to, form, next) {
if (["sdlmAdd", "sdlmModify"].includes(form.name)) {
next((vm) => {
vm.$nextTick(() => {
vm.activeName = "SDLM";
});
});
} else {
next();
}
},
methods: {
initRoleEpgZcs(res) {
let { code, data, message } = res;
if (code == 200) {
this.roleEpgZcs = data;
} else {
this.$message.error(message);
}
},
listDircByType(res) {
let { data, message, code } = res;
if (code == 200) {
this.capLevels = [];
this.practiceFields = [];
this.sysFileTypes = [];
if (data) {
data.CAPABILITY_LEVEL &&
data.CAPABILITY_LEVEL.forEach((d) => {
this.capLevels.push({
value: d.dircId,
label: d.dicrName,
});
});
data.CAPABILITY_LEVEL &&
data.PRACTICE_FIELD.forEach((d) => {
this.practiceFields.push({
value: d.dircId,
label: d.dicrName,
});
});
data.SYSTEM_FILE_TYPE &&
data.SYSTEM_FILE_TYPE.forEach((d) => {
this.sysFileTypes.push({
value: d.dircId,
label: d.dicrName,
});
});
} else {
this.$message.error("下拉框数据为空");
}
} else {
this.$message.error(message);
}
},
sysGetVersionNum(res) {
let { data, message, code } = res;
if (code == 200) {
this.versionList = [];
if (data) {
data.forEach((d) => {
this.versionList.push({
value: d.systemVersionId,
label: d.systemVersion,
content: { ...d },
});
});
}
} else {
this.$message.error(message);
}
},
initData() {
Promise.all([
assetLibrary.listDircByType({
dictTypeList: [
"PRACTICE_FIELD",
"CAPABILITY_LEVEL",
"SYSTEM_FILE_TYPE",
],
}),
sysDocManage.sysGetVersionNum({
type: "ALL",
}),
ConfigService.findUserListByRoleId({ roleId: "ROLE_EPG_ZC" }),
])
.then((result) => {
this.listDircByType(result[0]);
this.sysGetVersionNum(result[1]);
this.initRoleEpgZcs(result[2]);
//必要有这些下拉框数据才能渲染这个component
this.componentObj = componentsItem.find(
(c) => c.props == this.activeName
).component;
})
.catch((err) => {
console.log(err);
});
},
beforeLeave(activeName) {
console.log("beforeLeave activeName", activeName);
this.componentObj = componentsItem.find(
(c) => c.props == activeName
).component;
},
goBack() {
this.$router.go(-1);
},
},
};
</script>
<style lang="scss" scoped>
/deep/ .el-tabs {
.el-tabs__content {
min-height: 0;
}
.el-tabs__header {
margin-bottom: 0;
padding: 10px 0;
box-sizing: border-box;
}
}
.flex-cus {
display: flex;
flex-direction: column;
}
.padding-scroll-style {
padding-left: 10px;
box-sizing: border-box;
}
</style>
批量引入自定义组件
- components.ts
import {
SearchItem,
PageFragment,
DetailItem,
Pagination,
MyTable,
BaseInfo,
MyPlainButton,
OperationBtn,
Breadcrumb,
DropDown,
ComCollapse,
UploadDialog,
ProcessDialog,
UploadDialogWithSecLevel,
RangeNumberInput,
StaffSelectTree,
BaseTable,
BaseNumber,
ApproverComponent,
DeleteApproveDialog,
} from ".";
const components: any = {
SearchItem,
PageFragment,
DetailItem,
Pagination,
MyTable,
BaseInfo,
MyPlainButton,
OperationBtn,
Breadcrumb,
DropDown,
ComCollapse,
UploadDialog,
ProcessDialog,
UploadDialogWithSecLevel,
RangeNumberInput,
StaffSelectTree,
BaseTable,
BaseNumber,
ApproverComponent,
DeleteApproveDialog,
};
const install = (Vue: any) => {
Object.keys(components).forEach((key: any) => {
Vue.component(key, components[key]);
});
};
const MyPlugin = {
install,
};
export default MyPlugin;
- main.ts
import Vue from "vue";
import MyPlugin from "./components/my-plugin";
Vue.use(MyPlugin);