博主封装了一个公共弹窗函数接收四个参数,(title:弹窗标题, ContentComponent:弹窗中显示的组件内容, opt:接收弹窗本身的属性和props, beforeSure:点击确定做的操作(请求后端接口))
- 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
- const closeable = opt.closeable; // Ref
- 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,
- };
- };
generateDialogComponent
函数解释:generateDialogComponent
函数定义了一个组件,并返回该组件。setup
函数中,首先定义了一些变量和常量,包括:loading
:一个用于表示加载状态的响应式变量(Ref)。vm
:当前组件实例的引用。visible
:一个响应式变量,表示对话框的可见性。closeable
:一个响应式变量,表示对话框是否可关闭。showCancelButton
:一个布尔值,表示是否显示取消按钮,默认为true
。showSureBtn
:一个布尔值,表示是否显示确定按钮,默认为true
。showFooter
:一个布尔值,表示是否显示底部内容,默认为true
。confirmButtonText
:确定按钮的文本,默认为"确定"。cancelButtonText
:取消按钮的文本,默认为"取消"。el-dialog
组件,该组件是一个基于Element UI库的对话框组件。el-dialog
组件的属性包括:closeOnClickModal
:控制是否在点击模态框时关闭对话框,根据closeable
的值进行设置。visible
:控制对话框的可见性,根据visible
的值进行设置。...opt.dialog
:将opt.dialog
对象中的所有属性都作为el-dialog
组件的属性。closeOnClickModal
:控制是否在点击模态框时关闭对话框,根据closeable
的值进行设置。closeOnPressEscape
:控制是否在按下Esc键时关闭对话框,根据closeable
的值进行设置。showClose
:控制是否显示关闭按钮,根据closeable
的值进行设置。el-dialog
组件的事件包括:update:visible
:当对话框的可见性发生变化时,更新visible
的值。closed
:当对话框关闭时,触发cancel
和destroy
函数。el-dialog
组件的插槽包括:{ padding: "20px" }
的div
元素,其中包含了wrapped
组件。showFooter
为true
时,底部插槽:包含一个具有样式{ display: "flex", justifyContent: "space-between" }
的div
元素,其中包含了底部内容。openDialog
的函数解释:openDialog
函数接受四个参数:title
(对话框标题),ContentComponent
(对话框内容组件),opt
(可选配置对象),和beforeSure
(可选的确定按钮点击前的回调函数)。defaultOpt
的默认配置对象,包含dialog
和props
两个属性。opt
和beforeSure
进行格式化处理:opt
为undefined
,则将其设置为defaultOpt
的值。opt
为函数,则将其设置为defaultOpt
的值,并将beforeSure
设置为该函数。beforeSure
不是函数,则将其设置为一个默认函数,该函数会调用传入的组件实例的submit
方法(如果存在)。opt
进行进一步处理:opt
是普通对象且没有props
属性,则将opt
的值复制给opt.props
。opt.dialog
设置为opt
或opt.dialog
的值,并将title
设置为opt.dialog.title
。mountComponent
的函数,用于将组件挂载到DOM上,并返回一个销毁函数。div
元素上。div
元素添加到document.body
中。document.body
中移除该div
元素。visible
,用于控制对话框的显示和隐藏。closeDialog
函数,用于关闭对话框。closeable
,用于控制对话框是否可以关闭。freeze
函数,将closeable
设置为false
,使对话框不可关闭。unfreeze
函数,将closeable
设置为true
,使对话框可关闭。wait
,用于等待对话框的操作完成。wait
的执行函数中,定义了一些内部函数和变量:disposer
:用于存储销毁函数的引用。destroy
函数:用于执行销毁函数。cancel
函数:用于拒绝Promise并抛出一个取消错误。sure
函数:在确定按钮点击时执行的回调函数,调用beforeSure
函数并传入wrappedComp
作为参数,并将返回的Promise解析为resolve
的值。disposer
设置为调用mountComponent
函数,并传入generateDialogComponent
函数生成的对话框组件。setTimeout
延迟20毫秒后,将visible
设置为true
,打开对话框。close
:关闭对话框的方法。promise
:返回等待对话框操作完成的Promise对象。freeze
:使对话框不可关闭的方法。unfreeze
:使对话框可关闭的方法。- 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);
- }
- );
- //组件弹窗测试
- 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;
- },
- },
- }),
- ]),
- ]);
- },
- };
- }
- //组件弹窗测试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组件:
-
-
-
- <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>
-
- <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>
- 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组件
- <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="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>
-
- <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>
模拟一个异步返回
- fetchData(params) {
- return new Promise((resolve, reject) => {
- // 模拟异步请求
- setTimeout(() => {
- const data = { name: "John", age: 30, ...params };
- // 模拟请求成功
- resolve(data);
- // this.$message.error("请求接口失败");
- // 模拟请求失败
- // reject('请求失败');
- }, 1000);
- });
- },
填写完必填项发起后端请求,拿到数据