Encapsulation of multi-functional components of Vue3.2 pop-up form

Combined with working examples, a multi-functional universal pop-up window component is encapsulated
Technology used: Vue3.2 + arco-design + ts
Function implementation: Realize the addition, deletion, modification and query of tables, realize the encapsulation of pop-up windows, and one pop-up window can adapt to the three functional points of addition, deletion and modification
The general idea is as follows:
//1. The sub-component exposure method opens its own pop-up window, using the defineExpose method. The parent component obtains the sub-component exposure method through the ref of the sub-component and calls it
//2. The parent component tells the child component what operations it wants to perform through the parent-to-child method, and then the child component determines
//3. After the child component implements the relevant functions, it needs to trigger the emit event and tell the parent component. The parent component triggers the relevant events to update the data.
If you want to know how to encapsulate the front-end development of components, you can take a closer look. The encapsulation should be very brief and easy to understand
The parent component code is as follows: You can take a closer look at the details. The relevant interfaces may need to call other people’s interfaces or mock data themselves
<template>
  <a-space direction="vertical" :size="20" fill>
    <div class="header">
      <a-space fill>
        <div class="search">
          <a-input
            v-model="searchKey"
            :style="{ width: '320px' }"
            :placeholder="$t('settings.search')"
            class="search"
            allow-clear
            @clear="searchIdenInfo"
            @keyup.enter="searchIdenInfo"
          >
            <template #prefix>
              <a-button type="text" shape="circle" @click="searchIdenInfo">
                <template #icon>
                  <SvgIcon name="search" />
                </template>
              </a-button>
            </template>
          </a-input>
        </div>
        <div class="btns">
          <a-button
            type="primary"
            :disabled="selectedKeys.length === 0"
            @click="delChooseInfo"
          >
            {<!-- -->{<!-- --> $t('basicData.mailList.identityInfo.del') }}
          </a-button>
          <a-button type="primary" @click="addIdenInfo">
            {<!-- -->{<!-- --> $t('basicData.mailList.identityInfo.add') }}
          </a-button>
        </div>
      </a-space>
    </div>
    <div class="table">
      <div class="theTable">
        <a-table
          ref="tableRef"
          v-model:selectedKeys="selectedKeys"
          row-key="guid"
          :columns="columns"
          :data="IndeListdata"
          :loading="loading"
          :bordered="{ headerCell: true, wrapper: false }"
          :row-selection="rowSelection"
          :scroll="{ x: '100%', y: '90%' }"
          :hoverable="false"
          :pagination="false"
        >
          <!-- Siren slot -->
          <template #siren="{ record }">
            <a-link @click="getPolice(record)">{<!-- -->{<!-- --> record.siren }}</a-link>
          </template>
          <!-- Operation slot -->
          <template #optional="{ record }">
            <div class="btns">
              <a-button
                shape="circle"
                :disabled="record.judge_sync_edit !== 1"
                @click="editRow(record)"
              >
                <template #icon>
                  <SvgIcon name="edit" />
                </template>
              </a-button>
              <a-button
                shape="circle"
                :disabled="record.judge_sync_edit !== 1"
                @click="delRow(record)"
              >
                <template #icon>
                  <SvgIcon name="delete" />
                </template>
              </a-button>
            </div>
          </template>
        </a-table>
      </div>
      <div class="pagination">
        <a-pagination
          ref="paginationRef"
          show-total
          show-jumper
          show-page-size
          :total="pageTotal"
          :default-page-size="defaultPageSize"
          :page-size-options="pageSizeOption"
          :current="currentPage.current"
          @change="pageChange"
          @page-size-change="pageSizeChange"
        />
      </div>
    </div>
  </a-space>
  <IdenInfoModal
    ref="IdenInfoModelRef"
    :operation-type="operationType"
    @refreshInfo="searchIdenInfo"
  />
</template>

<script lang="ts" setup>
  import {<!-- --> ref, reactive, computed } from 'vue';
  import {<!-- --> Message } from '@arco-design/web-vue';
  import {<!-- --> useI18n } from 'vue-i18n';
  import {<!-- --> Random } from 'mockjs';
  import {<!-- --> configLoginInfoStore } from '@/store';
  import request from '@/utils/request';
  import useLoading from '@/hooks/loading';
  import IdenInfoModal from './IdenInfoModal.vue';

  const {<!-- --> t } = useI18n();
  const {<!-- --> loading, setLoading } = useLoading(true);
  const configLoginStore = configLoginInfoStore();
  const IdenInfoModelRef = ref(null);

  const searchKey = ref<string>('');

  const selectedKeys = ref([]);

  type operType = 'add' | 'edit' | 'del' | 'view' | 'delChoose';
  const operationType = ref<operType>('add');
  const rowSelection = reactive<any>({<!-- -->
    type: 'checkbox',
    showCheckedAll: true,
  });
  //Default display number
  const defaultPageSize = 15;
  //Display the number of items
  const pageSizeOption = [10, 15, 20, 30, 40, 50];
  //Display the total number of items
  const pageTotal = ref<number>(0);
  //Current page number, currently displayed quantity
  const currentPage = ref({<!-- -->
    current: 1,
    pageSize: 15,
  });

  const columns = computed<any>(() => {<!-- -->
    return [
      {<!-- -->
        title: t('basicData.mailList.identityInfo.sysId'),
        dataIndex: 'system_id',
        align: 'center',
        ellipsis: true,
        tooltip: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.personId'),
        dataIndex: 'id_no',
        align: 'center',
        ellipsis: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.policeID'),
        slotName: 'siren',
        align: 'center',
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.policeName'),
        dataIndex: 'siren_name',
        align: 'center',
        ellipsis: true,
        tooltip: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.departmentId'),
        dataIndex: 'department_id',
        align: 'center',
        ellipsis: true,
        tooltip: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.departmentAll'),
        dataIndex: 'department_full_name',
        align: 'center',
        ellipsis: true,
        tooltip: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.departmentShort'),
        dataIndex: 'department_name',
        align: 'center',
        ellipsis: true,
        tooltip: true,
        width: 100,
      },
      {<!-- -->
        title: t('basicData.mailList.identityInfo.operation'),
        slotName: 'optional',
        align: 'center',
        fixed: 'right',
        width: 100,
      },
    ];
  });

  const IndeListdata = ref([]);

  // Get the list
  const searchIdenInfo = async () => {<!-- -->
    setLoading(true);
    const params = {<!-- -->
      realm: configLoginStore.getRealm,
      cmd_name: 'staff_identifier_request',
      cmd_guid: Random.guid(),
      is_fuzzy_qry: 1,
      page_sizes: currentPage.value.pageSize,
      page_index: currentPage.value.current,
      querykey: searchKey.value,
    };
    const data: any = await request(params);
    if (data.result === 0) {<!-- -->
      IndeListdata.value = data.staff_identifier_list;
      pageTotal.value = data.count ? data.count : 0;
      setLoading(false);
    }
    console.log(data);
  };

  // Get police details
  const getPolice = (item) => {<!-- -->
    operationType.value = 'view';
    IdenInfoModelRef.value.handleClick(item);
  };

  //Add identity information
  const addIdenInfo = () => {<!-- -->
    operationType.value = 'add';
    IdenInfoModelRef.value.handleClick();
  };

  // Get selected person information
  const isChecked = ref([]);
  const getAllChecked = () => {<!-- -->
    isChecked.value = IndeListdata.value
      .map((item) => {<!-- -->
        if (selectedKeys.value.includes(item.guid)) {<!-- -->
          return {<!-- -->
            puc_id: configLoginStore.getPucId,
            realm: configLoginStore.getRealm,
            system_id: item.system_id,
            siren: item.siren,
          };
        }
        return null;
      })
      .filter((v) => v);
  };
  // Delete the selected identity information (multiple selection delete)
  const delChooseInfo = () => {<!-- -->
    operationType.value = 'delChoose';
    getAllChecked();
    IdenInfoModelRef.value.handleClick(isChecked.value);
  };
  //Edit current identity information
  const editRow = (item) => {<!-- -->
    operationType.value = 'edit';
    IdenInfoModelRef.value.handleClick(item);
  };

  //Delete current identity information
  const delRow = (item) => {<!-- -->
    operationType.value = 'del';
    IdenInfoModelRef.value.handleClick(item);
  };

  // paging
  const pageChange = (current: number) => {<!-- -->
    currentPage.value.current = current;
    searchIdenInfo();
  };

  const pageSizeChange = (pageSize: number) => {<!-- -->
    currentPage.value.pageSize = pageSize;
    searchIdenInfo();
  };

  defineExpose({<!-- -->
    searchIdenInfo,
  });
</script>

<style lang="less" scoped>

</style>

Subcomponent code
<template>
  <a-modal
    v-model:visible="visible"
    :title="title"
    title-align="start"
    :ok-text="
      operationType !== 'del' & amp; & amp; operationType !== 'delChoose'
        ? $t('basicData.mailList.identityInfo.save')
        : $t('basicData.mailList.identityInfo.sure')
    "
    :cancel-text="$t('basicData.mailList.identityInfo.cancel')"
    :width="
      operationType !== 'del' & amp; & amp; operationType !== 'delChoose'
        ? '680px'
        : '440px'
    "
    :footer="operationType !== 'view'"
    @cancel="handleCancel"
    @before-ok="handleBeforeOk"
  >
    <a-form
      v-if="operationType !== 'del' & amp; & amp; operationType !== 'delChoose'"
      ref="formRef"
      class="form-modal"
      layout="vertical"
      :model="form"
      :rules="rules"
      :disabled="operationType === 'view'"
    >
      <a-form-item
        :disabled="operationType === 'edit' || operationType === 'view'"
        field="system_id"
        :label="$t('basicData.mailList.identityInfo.sysId')"
      >
        <a-select v-model="form.system_id" allow-clear>
          <a-option
            v-for="item of systemList"
            :key="item.label"
            :value="item.value"
            :label="item.label"
          />
        </a-select>
      </a-form-item>
      <a-form-item
        :disabled="operationType === 'edit' || operationType === 'view'"
        field="siren"
        :label="$t('basicData.mailList.identityInfo.policeID')"
      >
        <a-input v-model="form.siren" />
      </a-form-item>
      <a-form-item
        field="id_no"
        :label="$t('basicData.mailList.identityInfo.personId')"
      >
        <a-input v-model="form.id_no" />
      </a-form-item>
      <a-form-item
        field="siren_name"
        :label="$t('basicData.mailList.identityInfo.policeName')"
      >
        <a-input v-model="form.siren_name" />
      </a-form-item>
      <a-form-item
        field="department_id"
        :label="$t('basicData.mailList.identityInfo.departmentId')"
      >
        <a-input v-model="form.department_id" />
      </a-form-item>
      <a-form-item
        field="department_full_name"
        :label="$t('basicData.mailList.identityInfo.departmentAll')"
      >
        <a-input v-model="form.department_full_name" />
      </a-form-item>
      <a-form-item
        field="department_name"
        :label="$t('basicData.mailList.identityInfo.departmentShort')"
      >
        <a-input v-model="form.department_name" />
      </a-form-item>
    </a-form>
    <div v-else>
      {<!-- -->{<!-- -->
        props.operationType === 'del'
          ? $t('basicData.mailList.identityInfo.sureCancelRow')
          : $t('basicData.mailList.identityInfo.sureCancelChoose')
      }}
    </div>
  </a-modal>
</template>

<script setup lang="ts">
  import {<!-- --> ref, computed, nextTick } from 'vue';
  import {<!-- --> useI18n } from 'vue-i18n';
  import {<!-- --> Random } from 'mockjs';
  import {<!-- --> configLoginInfoStore } from '@/store';
  import {<!-- --> Message } from '@arco-design/web-vue';
  import {<!-- --> showErrorMsgDesc } from '@/hooks/error';
  import request from '@/utils/request/index';

  const emit = defineEmits<{<!-- -->
    (event: 'refreshInfo'): void;
  }>();

  const {<!-- --> t } = useI18n();
  const configLoginStore = configLoginInfoStore();
  interface formType {<!-- -->
    system_id: string;
    id_no: string;
    siren: string;
    siren_name: string;
    department_id: string;
    department_full_name: string;
    department_name: string;
  }
  const visible = ref(false);
  const formRef = ref();
  const systemList = ref<object[]>([]);
  const props = defineProps<{<!-- -->
    operationType: string;
  }>();

  const form = ref<formType>({<!-- -->
    system_id: '',
    id_no: '',
    siren: '',
    siren_name: '',
    department_id: '',
    department_full_name: '',
    department_name: '',
  });

  const getSystemList = async () => {<!-- -->
    const params = ref({<!-- -->
      cmd_name: 'system_list_request',
      puc_id: configLoginStore.getPucId,
      user_id: configLoginStore.getUserId,
      realm: configLoginStore.getRealm,
    });
    const res = await request(params.value);
    const data = res?.system_list || [];
    const system_list = data.map((item) => {<!-- -->
      return {<!-- -->
        ...item,
        value: item.system_id,
        label: `${<!-- -->item.system_alias}(${<!-- -->item.system_id})`,
      };
    });
    systemList.value = system_list;
    console.log(systemList.value);
  };

  //Multiple selection to delete content
  const delMultiple = ref([]);

  const title = computed(() => {<!-- -->
    return t(`basicData.mailList.identityInfo.${<!-- -->props.operationType}`);
  });

  const rules = computed(() => {<!-- -->
    return {<!-- -->
      system_id: [
        {<!-- -->
          required: true,
          message: t('basicData.mailList.identityInfo.notNull'),
        },
      ],
      siren: [
        {<!-- -->
          required: true,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          match: /^[0-9]*$/,
          message: t('basicData.mailList.equipment.onlynumber'),
        },
      ],
      id_no: [
        {<!-- -->
          required: true,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          match: /^[0-9]*$/,
          message: t('basicData.mailList.equipment.onlynumber'),
        },
      ],
      siren_name: [
        {<!-- -->
          required: false,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          validator: (value: any, cb: any) => {<!-- -->
            const nameReg = /[\/:*?<>| & amp;#@%$^]/im;
            if (nameReg.test(value)) {<!-- -->
              cb(
                t('basicData.mailList.equipment.device_alias.reg').replace(
                  '$1',
                  '/ : * ? < > | & amp; # @ % $ ^'
                )
              );
            }
          },
        },
      ],
      department_id: [
        {<!-- -->
          required: false,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          match: /^[0-9]*$/,
          message: t('basicData.mailList.equipment.onlynumber'),
        },
      ],
      department_full_name: [
        {<!-- -->
          required: false,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          validator: (value: any, cb: any) => {<!-- -->
            const nameReg = /[\/:*?<>| & amp;#@%$^]/im;
            if (nameReg.test(value)) {<!-- -->
              cb(
                t('basicData.mailList.equipment.device_alias.reg').replace(
                  '$1',
                  '/ : * ? < > | & amp; # @ % $ ^'
                )
              );
            }
          },
        },
      ],
      department_name: [
        {<!-- -->
          required: false,
          trigger: 'blur',
          message: t('basicData.mailList.identityInfo.notNull'),
        },
        {<!-- -->
          validator: (value: any, cb: any) => {<!-- -->
            const nameReg = /[\/:*?<>| & amp;#@%$^]/im;
            if (nameReg.test(value)) {<!-- -->
              cb(
                t('basicData.mailList.equipment.device_alias.reg').replace(
                  '$1',
                  '/ : * ? < > | & amp; # @ % $ ^'
                )
              );
            }
          },
        },
      ],
    };
  });

  // Clear the form
  const clearForm = () => {<!-- -->
    formRef.value.resetFields();
  };

  // The function needs to be asynchronous, otherwise the relevant judgment will not take effect
  const handleClick = (info) => {<!-- -->
    getSystemList();
    nextTick(() => {<!-- -->
      visible.value = true;
      console.log(props.operationType);
      if (props.operationType === 'add') {<!-- -->
        clearForm();
        return;
      }
      if (props.operationType === 'delChoose') {<!-- -->
        delMultiple.value = info;
      }
      // deep copy
      form.value = JSON.parse(JSON.stringify(info));
    });
  };

  // Increase
  const add = async (done: (arg: boolean) => void) => {<!-- -->
    const param = {<!-- -->
      cmd_name: 'add_staff_identifier',
      cmd_guid: Random.guid(),
      staff_identifier_info: {<!-- -->
        puc_id: configLoginStore.getPucId,
        guid: '',
        realm: configLoginStore.getRealm,
        ...form.value,
      },
    };
    const res: any = await request(param);
    if (res.extbody.result !== 0) {<!-- -->
      done(false);
      showErrorMsgDesc(res.result, res.msg);
      return;
    }
    await nextTick(() => {<!-- -->
      emit('refreshInfo');
      done(true);
    });
    clearForm();
  };

  // Revise
  const edit = async (done: (arg: boolean) => void) => {<!-- -->
    const param = {<!-- -->
      cmd_name: 'update_staff_identifier',
      cmd_guid: Random.guid(),
      staff_identifier_info: {<!-- -->
        puc_id: configLoginStore.getPucId,
        guid: '',
        realm: configLoginStore.getRealm,
        ...form.value,
      },
    };
    const res = await request(param);
    if (res.extbody.result !== 0) {<!-- -->
      done(false);
      showErrorMsgDesc(res.result, res.msg);
      return;
    }
    await nextTick(() => {<!-- -->
      emit('refreshInfo');
      done(true);
    });
  };

  // Delete the current
  const del = async (done: (arg: boolean) => void) => {<!-- -->
    const param = {<!-- -->
      cmd_name: 'delete_staff_identifier',
      cmd_guid: Random.guid(),
      del_info_list: [
        {<!-- -->
          puc_id: configLoginStore.getPucId,
          realm: configLoginStore.getRealm,
          system_id: form.value.system_id,
          siren: form.value.siren,
        },
      ],
    };
    const res = await request(param);
    if (res.extbody.result !== 0) {<!-- -->
      done(false);
      showErrorMsgDesc(res.result, res.msg);
      return;
    }
    await nextTick(() => {<!-- -->
      emit('refreshInfo');
      done(true);
    });
  };

  //Multiple selection delete
  const delChoose = async (done: (arg: boolean) => void) => {<!-- -->
    const param = {<!-- -->
      cmd_name: 'delete_staff_identifier',
      cmd_guid: Random.guid(),
      del_info_list: delMultiple.value,
    };
    const res = await request(param);
    if (res.extbody.result !== 0) {<!-- -->
      done(false);
      showErrorMsgDesc(res.result, res.msg);
      return;
    }
    await nextTick(() => {<!-- -->
      emit('refreshInfo');
      done(true);
    });
    done(true);
  };

  const handleBeforeOk = (done) => {<!-- -->
    // Increase
    if (props.operationType === 'add') {<!-- -->
      formRef.value.validate().then((value) => {<!-- -->
        if (value) {<!-- -->
          Message.warning(t('basicData.mailList.identityInfo.checkwarn'));
          done(false);
        } else {<!-- -->
          add(done);
        }
      });
    }
    // change
    if (props.operationType === 'edit') {<!-- -->
      formRef.value.validate().then((value) => {<!-- -->
        if (value) {<!-- -->
          Message.warning(t('basicData.mailList.identityInfo.checkwarn'));
          done(false);
        } else {<!-- -->
          edit(done);
        }
      });
    }
    // delete
    if (props.operationType === 'del') {<!-- -->
      del(done);
    }
    //Delete multiple selections
    if (props.operationType === 'delChoose') {<!-- -->
      delChoose(done);
    }
  };

  const handleCancel = () => {<!-- -->
    visible.value = false;
    if (props.operationType !== 'delChoose' & amp; & amp; props.operationType !== 'del') {<!-- -->
      clearForm();
    }
  };

  defineExpose({<!-- -->
    handleClick,
  });
</script>

<style scoped lang="less">
</style>