Foreword:
The blogger encapsulates a public pop-up window function that receives four parameters, (title: pop-up window title, ContentComponent: component content displayed in the pop-up window, opt: receives the properties and props of the pop-up window itself, beforeSure: click OK to do the operation ( Request backend interface))
Encapsulated public functions:
import { defineComponent, h, ref, getCurrentInstance } from "vue-demi";
import Vue from "vue";
import { isFunction, isUndefined, noop, isPlainObject } from "lodash";
const WRAPPED = "wrapped";
function generateDialogComponent(wrapped, opt, listeners = {}) {
return defineComponent({
setup() {
const loading = ref(false);
const vm = getCurrentInstance();
const visible = opt.visible; // Ref<boolean>
const closeable = opt.closeable; // Ref<boolean>
const showCancelButton = isUndefined(opt.dialog.showCancelButton)
? true
: opt.dialog.showCancelButton;
const showSureBtn =
isUndefined(opt.dialog.showSureBtn) &&
isUndefined(opt.dialog.showSureButton)
? true
: opt.dialog.showSureBtn || opt.dialog.showSureButton;
const showFooter = isUndefined(opt.dialog.showFooter)
? true
: opt.dialog.showFooter;
const confirmButtonText = opt.dialog.confirmButtonText || "确定";
const cancelButtonText = opt.dialog.cancelButtonText || "取消";
return () => {
const sure = listeners.sure || (() => Promise.resolve());
const cancel = listeners.cancel || noop;
const destroy = listeners.destroy || noop;
// footer
const sureBtn = h(
"el-button",
{
props: {
size: "mini",
type: "primary",
loading: loading.value,
},
on: {
click: () => {
loading.value = true;
const wrappedVm = vm.proxy.$refs[WRAPPED];
return sure(wrappedVm)
.then(() => {
visible.value = false;
})
.finally(() => (loading.value = false));
},
},
},
confirmButtonText
);
const cancelBtn = h(
"el-button",
{
props: {
size: "mini",
},
on: {
click: () => {
visible.value = false;
},
},
},
cancelButtonText
);
const footer = h(
"div",
{
slot: "footer",
style: {
display: "flex",
justifyContent: "space-between",
},
},
[
h("div", [opt.dialog?.leftFooter?.()]),
h("div", [
closeable.value && showCancelButton && cancelBtn,
showSureBtn && sureBtn,
]),
]
);
return h(
"el-dialog",
{
props: {
closeOnClickModal: false,
visible: visible.value,
...opt.dialog,
closeOnClickModal: closeable.value,
closeOnPressEscape: closeable.value,
showClose: closeable.value,
},
on: {
"update:visible": (val) => {
visible.value = val;
},
closed: () => {
cancel();
destroy();
},
},
},
[
h("div", { style: { padding: "20px" } }, [
h(wrapped, {
ref: WRAPPED,
attrs: Object.assign({}, opt.props),
on: {
close: sure, // 组件内部可以通过 emit('close') 来关闭弹窗
},
}),
]),
showFooter && footer,
]
);
};
},
});
}
function openDialog(title, ContentComponent, opt, beforeSure) {
const defaultOpt = {
dialog: {},
props: {},
};
// 参数格式化
if (isUndefined(opt)) {
opt = defaultOpt;
}
if (isFunction(opt)) {
opt = defaultOpt;
beforeSure = opt;
}
if (!isFunction(beforeSure)) {
beforeSure = (vm) => vm.submit?.();
}
if (isPlainObject(opt)) {
if (isUndefined(opt.props)) {
opt = {
...opt,
props: opt,
};
}
}
opt.dialog = opt.dialog || opt || {};
opt.dialog.title = title;
const mountComponent = ($vueconfig) => {
const vm = new Vue($vueconfig);
const anchor = document.createElement("div");
document.body.appendChild(anchor);
vm.$mount(anchor);
return () => {
vm.$destroy();
document.body.removeChild(vm.$el);
};
};
// 控制 dialog 显隐
const visible = ref(false);
const closeDialog = () => {
visible.value = false;
};
// 控制是否可以关闭
const closeable = ref(true);
// 不可关闭弹窗
const freeze = () => {
closeable.value = false;
};
// 可关闭弹窗
const unfreeze = () => {
closeable.value = true;
};
const wait = new Promise((resolve, reject) => {
let disposer = null;
const destroy = () => isFunction(disposer) && disposer();
const cancel = () => {
reject(new Error("cancel"));
};
const sure = async (wrappedComp) => {
const promise = await beforeSure(wrappedComp);
resolve(promise);
};
disposer = mountComponent(
generateDialogComponent(
ContentComponent,
{ ...opt, visible, closeable },
{ sure, cancel, destroy }
)
);
// 打开弹窗
setTimeout(() => (visible.value = true), 20);
});
return {
close: closeDialog,
promise: wait,
freeze,
unfreeze,
};
}
export function pickByDialog(...args) {
const { promise } = openDialog(...args);
return promise;
}
/**
* 让 pickByDialog 静默失败, 并且提供一个手动关闭弹窗的函数
* @Returns { close }
*/
export const openByDialog = (...args) => {
const { close, freeze, unfreeze, promise } = openDialog(...args);
promise.catch(() => {
// no throw error
});
return {
close,
freeze,
unfreeze,
};
};
Partial code explanation:
generateDialogComponent
Function explanation:
generateDialogComponent
The function defines a component and returns that component.- In the component's
setup
function, some variables and constants are first defined, including:
-
loading
: A reactive variable (Ref) used to represent the loading status.vm
: A reference to the current component instance.visible
: A responsive variable indicating the visibility of the dialog box.closeable
: A responsive variable indicating whether the dialog box can be closed.showCancelButton
: A Boolean value indicating whether to display the cancel button, the default istrue
.showSureBtn
: A Boolean value indicating whether to display the OK button, the default istrue
.showFooter
: A Boolean value indicating whether to display the bottom content, the default istrue
.confirmButtonText
: The text of the OK button, the default is "OK".cancelButtonText
: The text of the cancel button, the default is "Cancel".
- Returns a function that uses Vue 3's Composition API syntax as the component's render function.
- The render function returns a
el-dialog
component, which is a dialog component based on the Element UI library. el-dialog
Component properties include:
-
closeOnClickModal
: Controls whether to close the dialog box when the modal box is clicked,closeable
set according to the value.visible
: Control the visibility of the dialog box,visible
set according to the value....opt.dialog
: Treatopt.dialog
all properties in the object asel-dialog
properties of the component.closeOnClickModal
: Controls whether to close the dialog box when the modal box is clicked,closeable
set according to the value.closeOnPressEscape
: Controls whether to close the dialog box when the Esc key is pressed,closeable
set according to the value.showClose
: Control whether to display the close button,closeable
set according to the value.
el-dialog
Component events include:
-
update:visible
: The value that is updated when the visibility of the dialog box changesvisible
.closed
cancel
: Trigger and function when the dialog box is closeddestroy
.
el-dialog
Component slots include:
-
- Default slot: Contains a styled
{ padding: "20px" }
elementdiv
that containswrapped
the component. showFooter
Lasttrue
, bottom slot: Contains a styled{ display: "flex", justifyContent: "space-between" }
elementdiv
that contains the bottom content.
- Default slot: Contains a styled
- Returns the generated component.
openDialog
Function explanation:
openDialog
The function accepts four parameters:title
(dialog title),ContentComponent
(dialog content component),opt
(optional configuration object), andbeforeSure
(optional callback function before the OK button is clicked).defaultOpt
A default configuration object named is defined , containing two propertiesdialog
: and .props
- Format the incoming
opt
sum :beforeSure
-
- If
opt
it isundefined
, set it todefaultOpt
the value. - If
opt
a function, it is set todefaultOpt
the value of , and willbeforeSure
be set to the function. - If
beforeSure
not a function, sets it to a default function that callssubmit
the method of the component instance passed in, if one exists.
- If
- For
opt
further processing:
-
- If
opt
it is a normal object and has noprops
properties,opt
the value of is copied toopt.props
.
- If
- will
opt.dialog
be set to the value ofopt
oropt.dialog
, and willtitle
be set toopt.dialog.title
. - A function named is defined
mountComponent
, which is used to mount the component on the DOM and returns a destruction function.
-
- Inside the function, a new Vue instance is created and mounted on a newly created
div
element. - Add this
div
element todocument.body
. - Returns a function that, when called, destroys the Vue instance
document.body
and removes thediv
element from it.
- Inside the function, a new Vue instance is created and mounted on a newly created
- Created a responsive variable
visible
to control the display and hiding of the dialog box. - Defines a
closeDialog
function to close the dialog box. - Created a responsive variable
closeable
to control whether the dialog box can be closed. freeze
A function is defined that willcloseable
be setfalse
to make the dialog box unclosable.unfreeze
A function is defined that willcloseable
be settrue
to make the dialog box closeable.- A Promise object is created
wait
to wait for the dialog box operation to complete. - In
wait
the execution function, some internal functions and variables are defined:
disposer
: Used to store a reference to the destruction function.destroy
Function: used to execute the destruction function.cancel
Function: Used to reject a Promise and throw a cancellation error.sure
Function: The callback function that is executed when the button is clicked, callsbeforeSure
the function and passes itwrappedComp
in as a parameter, and resolves the returned Promise toresolve
the value.- Set
disposer
to callmountComponent
the function and pass ingenerateDialogComponent
the dialog component generated by the function. - After using
setTimeout
a delay of 20 milliseconds, which willvisible
be set totrue
, the dialog opens.
- Returns an object containing the following properties and methods:
close
: Method to close the dialog box.promise
: Returns the Promise object waiting for the dialog box operation to complete.freeze
: Method to make the dialog box unclosable.unfreeze
: Method to make the dialog box closeable.
Examples of usage are as follows
Example 1
const { close } = openByDialog(
"自定义列表",
Setting2,
{
props: menuProps,
dialog: {
width: "980px",
confirmButtonText: "保存",
leftFooter: () =>
h(
"el-button",
{
style: { color: "#2783fe" },
props: {
size: "mini",
type: "text",
loading: reseting.value,
},
on: {
click: () => doReset(),
},
},
"恢复系统默认设置"
),
},
},
async (componentWrapper) => {
const updatedColumns = await componentWrapper?.updateColumnSetting();
this.emitChangeColumns2(updatedColumns);
}
);
Example 2
//组件弹窗测试
async doImportLog() {
const [cancel, blob] = await to(
pickByDialog(
"弹窗导出测试",
getLogComp(this), //这个是组件
{
dialog: { width: "35%" },
// props: { value: this.value },
// on: {
// input: (val) => {
// this.value = val
// },
// },
},
async (vm) => {
const [changeDateBt, changeDateEt] = vm.date;
console.log("changeDateBt", changeDateBt);
console.log("changeDateEt", changeDateEt);
}
)
);
if (cancel) {
this.$message.info(this.$tof("cancel"));
return;
}
const curDate = dayjs().format("YYYYMMDD");
console.log("curDate", curDate);
saveAs(blob.data, this.$tof("file_name", { curDate }));
},
function getLogComp(vm) {
return {
data() {
return {
date: [],
};
},
render(h) {
return h("el-row", { style: { margin: "100px 50px" } }, [
h(
"el-col",
{ style: { lineHeight: "36px" }, attrs: { span: 8 } },
"导出范围时间"
),
h("el-col", { attrs: { span: 16 } }, [
h("el-date-picker", {
attrs: {
type: "daterange",
rangeSeparator: "-",
startPlaceholder: "开始日期",
endPlaceholder: "结束日期",
value: this.date,
valueFormat: "yyyy-MM-dd",
},
style: { width: "auto !important" },
on: {
input: (val) => {
this.date = val;
},
},
}),
]),
]);
},
};
}
Example three
//组件弹窗测试222
async doAdd() {
const [cancel] = await to(
pickByDialog(
"新增客户",
GroupCreate, //组件
{
dialog: {
width: "80%",
confirmButtonText: "保存",
leftFooter: () =>
h(
"el-button",
{
style: { color: "#2783fe" },
props: {
size: "mini",
type: "text",
loading: false,
},
on: {
click: () => doReset(),
},
},
"恢复系统默认设置"
),
},
props: {},
},
async (vm) => {
console.log("测试点击确定", vm);
}
)
);
if (cancel) {
this.$message.info("已取消");
return;
}
function doReset() {
console.log("测试左边操作");
}
},
GroupCreate组件:
<template>
<div>
<CollapsePanel :show-header="true">
<template #panel-button>
<el-button
size="small"
type="primary"
:loading="loading"
@click="doInquiry"
>
搜索
</el-button>
</template>
<template #panel-main>
<el-form
ref="filterForm"
:inline="true"
:model="schemaModel"
:max-height="200"
class="list-schema-form"
label-position="top"
>
<el-row
v-for="(row, index) in schemaList"
:key="index"
v-bind="layout"
>
<el-col
v-for="{ rules, label, prop, component, ...attrs } in row"
:key="prop"
:span="12"
>
<el-form-item v-if="prop" v-bind="{ label, rules, prop }">
{
{ component }}
<component
:is="`el-${component}`"
v-model="schemaModel[prop]"
:placeholder="component + '_placeholder'"
v-bind="attrs"
>
<el-option
v-for="ops in schemaOptions[prop]"
:key="ops.value"
v-bind="ops"
/>
</component>
</el-form-item>
</el-col>
</el-row>
</el-form>
</template>
</CollapsePanel>
<CollapsePanel :auto-height="true">
<template #panel-main>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
border
stripe
max-height="550"
>
<el-table-column type="selection" fixed="left" width="40" />
<el-table-column
type="index"
width="60"
fixed="left"
:label="'序号'"
/>
<el-table-column
v-for="{ field, render, ...attrs } in columns"
:key="field"
v-bind="attrs"
>
<template #default="scope">
<field-render :render="render" :scope="scope" :field="field" />
</template>
</el-table-column>
</el-table>
<div class="table-pagination">
<pagination
:limit="pageSize"
:page="pageNum"
:total="totalPages"
@pagination="(pageNum = $event.page) && (pageSize = $event.limit)"
/>
</div>
</template>
</CollapsePanel>
</div>
</template>
<script>
// import { customerinfoFindCustByCustNameOrCustCodeToCommodity } from '@/api/commodity/price'
import to from "await-to-js";
import { snakeCase } from "lodash";
export default {
name: "GroupCreate",
components: {
FieldRender: {
functional: true,
props: {
scope: {
type: Object,
default: () => ({}),
},
render: Function,
field: String,
},
render: (h, { props }) => {
const { render, scope, field } = props;
return render
? render(h, { ...scope, field })
: h("span", null, scope.row[field]);
},
},
},
data(vm) {
return {
...getSchemaList(vm),
list: [],
columns: getColumList(vm),
pageNum: 1,
pageSize: 10,
totalPages: 0,
loading: false,
layout: {
gutter: 60,
justify: "start",
},
};
},
watch: {
pageNum: {
handler() {
this.doInquiry();
},
},
pageSize: {
handler() {
this.doInquiry();
},
},
},
async created() {
// const options = await this.$ops({});
// Object.assign(this.schemaOptions, options);
this.doInquiry();
},
methods: {
async doInquiry() {
console.log("测试");
// this.loading = true
// const [err, data] = await to(
// customerinfoFindCustByCustNameOrCustCodeToCommodity(
// Object.assign(this.schemaModel, {
// page: this.pageNum - 1,
// size: this.pageSize,
// })
// )
// ).finally(() => (this.loading = false))
// if (err) {
// return
// }
// const { content, totalElements = 5 } = data
// this.totalPages = +totalElements
// this.list = content.map((i) => ({
// custCode: i.customerCode,
// custName: i.customerName,
// }))
},
doReset() {
this.schemaModel = {};
},
doDelete() {},
doAdd() {},
async doSave() {
const [err] = await to(this.$refs["filterForm"].validate());
if (err) {
return;
}
},
doBack() {
this.$store.dispatch("tabsBar/delVisitedRoute", this.$route.fullPath);
this.$router.back();
},
},
};
function getColumList(vm) {
const COLUM_FIELDS = ["custCode", "custName"];
const FORM_FIELDS_NAME = {
custName: "客户名称",
custCode: "客户编码",
};
const colProperties = {};
return COLUM_FIELDS.map((prop) =>
Object.assign(
{ prop, label: FORM_FIELDS_NAME[prop], field: prop },
colProperties[prop] || {
sortable: true,
}
)
);
}
function getSchemaList(vm) {
const FORM_FIELDS = ["custCode", "custName"];
const FORM_FIELDS_NAME = {
custName: "客户名称",
custCode: "客户编码",
};
let properties = {
custCode: {},
custName: {},
};
const array = FORM_FIELDS.map((prop) => {
const label = FORM_FIELDS_NAME[prop];
const attrs = properties[prop];
return {
prop,
label,
clearable: true,
filterable: true,
component: "input",
...attrs,
};
});
const schemaList = [array];
return {
schemaList,
schemaModel: { custStatus: ["20"], custCode: "", custNameList: [] },
schemaOptions: { status: [] },
};
}
</script>
<style lang="scss" scoped>
::v-deep .el-button--text {
color: #409eff;
font-size: 13px;
}
.total-number {
color: #f55448;
}
</style>
Example 4
async doAdd2() {
openByDialog(
"测试表单",
FormTest,
{
props: {
isShowCol: true,
},
},
async (componentWrapper) => {
await componentWrapper?.$children[0].validate();
console.log("componentWrapper的数据", componentWrapper);
componentWrapper.ruleForm.date1 = dayjs(
componentWrapper.ruleForm.date1
).format("YYYYMMDD");
let payload = componentWrapper.ruleForm;
return await this.fetchData(payload);
}
);
},
FormTest组件
<template>
<el-form
:model="ruleForm"
:rules="rules"
ref="ruleFormRef"
label-width="100px"
class="demo-ruleForm"
>
<el-form-item label="活动名称" prop="name">
<el-input v-model="ruleForm.name"></el-input>
</el-form-item>
<el-form-item label="活动区域" prop="region">
<el-select v-model="ruleForm.region" placeholder="请选择活动区域">
<el-option label="区域一" value="shanghai"></el-option>
<el-option label="区域二" value="beijing"></el-option>
</el-select>
</el-form-item>
<el-form-item label="活动时间" required>
<el-col :span="11">
<el-form-item prop="date1">
<el-date-picker
type="date"
placeholder="选择日期"
v-model="ruleForm.date1"
style="width: 100%"
></el-date-picker>
</el-form-item>
</el-col>
<el-col class="line" :span="2">-</el-col>
<el-col :span="11">
<el-form-item prop="date2">
<el-time-picker
placeholder="选择时间"
v-model="ruleForm.date2"
style="width: 100%"
></el-time-picker>
</el-form-item>
</el-col>
</el-form-item>
<el-form-item label="即时配送" prop="delivery">
<el-switch v-model="ruleForm.delivery"></el-switch>
</el-form-item>
<!-- <el-form-item label="活动性质" prop="type">
<el-checkbox-group v-model="ruleForm.type">
<el-checkbox label="美食/餐厅线上活动" name="type"></el-checkbox>
<el-checkbox label="地推活动" name="type"></el-checkbox>
<el-checkbox label="线下主题活动" name="type"></el-checkbox>
<el-checkbox label="单纯品牌曝光" name="type"></el-checkbox>
</el-checkbox-group>
</el-form-item> -->
<el-form-item label="特殊资源" prop="resource">
<el-radio-group v-model="ruleForm.resource">
<el-radio label="线上品牌商赞助"></el-radio>
<el-radio label="线下场地免费"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="活动形式" prop="desc">
<el-input type="textarea" v-model="ruleForm.desc"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm(ruleFormRef)">
立即创建
</el-button>
</el-form-item>
</el-form>
</template>
<script>
import { defineComponent, ref } from "vue-demi";
export default defineComponent({
name: "ruleFormTest",
props: {
isShowCol: {
type: Boolean,
default: false,
},
},
setup(props) {
const ruleFormRef = ref();
let ruleForm = ref({
name: "",
region: "",
date1: "",
date2: "",
delivery: props.isShowCol,
type: [],
resource: "",
desc: "",
});
let rules = ref({
name: [
{ required: true, message: "请输入活动名称", trigger: "blur" },
{ min: 3, max: 5, message: "长度在 3 到 5 个字符", trigger: "blur" },
],
region: [
{ required: true, message: "请选择活动区域", trigger: "change" },
],
date1: [
{
type: "date",
required: true,
message: "请选择日期",
trigger: "change",
},
],
date2: [
{
type: "date",
required: true,
message: "请选择时间",
trigger: "change",
},
],
type: [
{
type: "array",
required: true,
message: "请至少选择一个活动性质",
trigger: "change",
},
],
resource: [
{ required: true, message: "请选择活动资源", trigger: "change" },
],
desc: [{ required: true, message: "请填写活动形式", trigger: "blur" }],
});
const submitForm = async (formEl) => {
console.log("formEl", formEl);
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
console.log("submit!");
} else {
console.log("error submit!", fields);
}
});
};
const resetForm = (formName) => {};
// expose({ submitForm });
return {
ruleForm,
rules,
ruleFormRef,
submitForm,
resetForm,
};
},
});
</script>
Simulate an asynchronous return
fetchData(params) {
return new Promise((resolve, reject) => {
// 模拟异步请求
setTimeout(() => {
const data = { name: "John", age: 30, ...params };
// 模拟请求成功
resolve(data);
// this.$message.error("请求接口失败");
// 模拟请求失败
// reject('请求失败');
}, 1000);
});
},
After filling in the required fields, initiate a backend request and get the data.