Skip to content
On this page

数据增删改

基本介绍

增删改查是基本一个业务最基本的功能,他包含表格的使用,表单录入,接口请求等。

定义表格和表单配置项

/src/const/crud/cbb/base目录下创建名为person.ts的文件,其中cbb表示框架,base表示模块,person表示业务功能;我们在业务系统中应该在crud目录下创建框架名,依次是模块,业务功能。 声明内容如下

ts
import type { VxeGridPropTypes, VxeFormPropTypes } from 'vxe-table';
import { REGEXP_PHONE, REGEXP_PWD, REGEXP_EMAIL, REGEXP_ID_CARD } from '@/config';
import { isName } from '@/utils';
import baseDict from '@/const/dict/base';

/**
 * 账号规则验证
 * @param itemValue
 * @param data
 * @returns {Promise<never>|Promise<unknown>}
 */
const validateAccount = ({ itemValue }: any) => {
  if (!isName(itemValue)) {
    return Promise.reject(new Error('登录账号只能包含中文、英文字母,不能包含特殊字符'));
  }
  return Promise.resolve(itemValue);
};

/**
 * @func: personTableColumns
 * @description: 人员table列字段配置
 * @param {*}
 * @return {VxeGridPropTypes.Columns}
 * @example:
 */
export function personTableColumns(): VxeGridPropTypes.Columns {
  return [
    { type: 'seq', width: 60, title: '序号', fixed: 'left' },
    { type: 'checkbox', width: 45 },
    { field: 'id', title: 'ID', minWidth: 100, visible: false },
    { field: 'name', title: '姓名', minWidth: 100 },
    { field: 'displayName', title: '显示姓名', minWidth: 100 },
    {
      field: 'gender',
      title: '性别',
      minWidth: 100,
      formatter: ['formatDict', baseDict.getDictItems('basePersonGender')]
    },
    { field: 'groupNum', title: '集团编号', minWidth: 100 },
    { field: 'account', title: '登录帐号', minWidth: 100 },
    { field: 'mobile', title: '手机号码', minWidth: 100 },
    {
      field: 'status',
      title: '人员状态',
      minWidth: 100,
      formatter: ['formatDict', baseDict.getDictItems('basePersonStatus')]
    },
    {
      title: '操作',
      width: 140,
      fixed: 'right',
      slots: { default: 'operation' }
    }
  ];
}

/**
 * @func: personFormData
 * @description: 人员form数据
 * @param {*}
 * @return {*}
 * @example:
 */
export function personFormData() {
  return {
    id: null,
    name: null,
    displayName: null,
    gender: null,
    workTime: null,
    email: null,
    idnum: null,
    mobile: null,
    groupNum: null,
    account: null,
    memo: null,
    status: null
  };
}

/**
 * @func: personTableFormConfig
 * @description: 人员table查询表单配置
 * @param {*}
 * @return {*}
 * @example:
 */
export function personTableFormConfig(): VxeGridPropTypes.FormConfig {
  return {
    data: { account: null, displayName: null, name: null, mobile: null, groupNum: null, status: '1' },
    items: [
      {
        field: 'account',
        title: '登录帐号',
        itemRender: {
          name: 'NInput',
          attrs: { placeholder: '请输入登录帐号' }
        }
      },
      {
        field: 'displayName',
        title: '显示姓名',
        itemRender: {
          name: 'NInput',
          attrs: { placeholder: '请输入显示姓名' }
        }
      },
      {
        field: 'name',
        title: '姓名',
        itemRender: {
          name: 'NInput',
          attrs: { placeholder: '请输入姓名' }
        }
      },
      {
        field: 'mobile',
        title: '手机号码',
        itemRender: {
          name: 'NInput',
          attrs: { placeholder: '请输入手机号码' }
        }
      },
      {
        field: 'groupNum',
        title: '集团编号',
        itemRender: {
          name: 'NInput',
          attrs: { placeholder: '请输入集团编号' }
        }
      },
      {
        field: 'status',
        title: '人员状态',
        itemRender: {
          name: 'NSelect',
          options: baseDict.getDictItems('basePersonStatus'),
          optionProps: { value: 'code', label: 'name' },
          props: { placeholder: '请选择人员状态' }
        }
      },
      {
        itemRender: {
          name: 'NButton',
          props: { content: '查询', attrType: 'submit', type: 'primary' }
        }
      },
      {
        itemRender: {
          name: 'NButton',
          props: { content: '重置', attrType: 'reset' }
        }
      }
    ]
  };
}

/**
 * @func: personFormItems
 * @description: 人员form表单配置项
 * @param {*} options.formType 表单类型
 * @return {VxeFormPropTypes.Items}
 * @example:
 */
export function personFormItems(): VxeFormPropTypes.Items {
  return [
    {
      field: 'name',
      title: '姓名',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入真实姓名' }
      }
    },
    {
      field: 'displayName',
      title: '显示姓名',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入显示姓名' }
      }
    },
    {
      field: 'gender',
      title: '性别',
      span: 12,
      itemRender: {
        name: 'NSelect',
        options: baseDict.getDictItems('basePersonGender'),
        optionProps: { value: 'code', label: 'name' },
        props: { placeholder: '请选择性别' }
      }
    },
    {
      field: 'workTime',
      title: '参加工作时间',
      span: 12,
      itemRender: {
        name: 'NDatePicker',
        props: { placeholder: '请输入参加工作时间', type: 'datetime', valueFormat: 'yyyy-MM-dd HH:mm:ss' }
      }
    },
    {
      field: 'email',
      title: '电子邮件',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入电子邮件(全小写)' }
      }
    },
    {
      field: 'idnum',
      title: '身份证号',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入身份证号(全小写,18位)' }
      }
    },
    {
      field: 'mobile',
      title: '手机号码',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入手机号码(11位数字)' }
      }
    },
    {
      field: 'groupNum',
      title: '集团编号',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入集团编号(10位以内数字)' }
      }
    },
    {
      field: 'account',
      title: '登录帐号',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入登录帐号' }
      }
    },
    {
      field: 'password',
      title: '登录密码',
      span: 12,
      itemRender: {
        name: 'NInput',
        props: { placeholder: '请输入登录密码' }
      }
    },
    {
      field: 'memo',
      title: '备注',
      span: 24,
      itemRender: {
        name: 'NInput',
        props: {
          type: 'textarea',
          autosize: { minRows: 1, maxRows: 2 },
          placeholder: '请输入备注'
        }
      }
    },
    {
      align: 'center',
      span: 24,
      slots: { default: 'formOperation' }
    }
  ];
}

/**
 * @func: personFormRules
 * @description: 人员form数据验证规则
 * @param {*}
 * @return {VxeFormPropTypes.Rules}
 * @example:
 */
export function personFormRules(): VxeFormPropTypes.Rules {
  return {
    name: [
      { required: true, message: '请输入姓名' },
      { max: 10, message: '长度在10个字符之内' }
    ],
    displayName: [
      { required: true, message: '请输入名称' },
      { max: 64, message: '长度在64个字符之内' }
    ],
    gender: [{ required: true, message: '请选择性别' }],
    phone: [
      {
        pattern: REGEXP_PHONE,
        message: '请输入11位电话号码'
      }
    ],
    email: [
      {
        pattern: REGEXP_EMAIL,
        message: '请输入正确的邮箱,例如:example@qq.com'
      }
    ],
    idnum: [
      {
        pattern: REGEXP_ID_CARD,
        message: '请输入正确的身份证号(第二代)'
      }
    ],
    account: [
      { required: true, message: '请输入登录帐号' },
      {
        min: 2,
        max: 20,
        message: '长度2到15位字符之内'
      },
      { validator: validateAccount }
    ],
    password: [
      {
        required: false,
        pattern: REGEXP_PWD,
        message: '包含大写字母、小写字母、数字和特殊字符,长度8-20位'
      }
    ],
    groupNum: [
      {
        required: false,
        pattern: /^[0-9]{1,10}$/,
        message: '10位以内数字'
      }
    ]
  };
}

/**
 * @func: personExtTableColumns
 * @description: 人员扩展属性table列字段配置
 * @param {*}
 * @return {VxeGridPropTypes.Columns}
 * @example:
 */
export function personExtTableColumns(): VxeGridPropTypes.Columns {
  return [
    { type: 'seq', width: 60, title: '序号', fixed: 'left' },
    { type: 'checkbox', width: 45 },
    { field: 'id', title: 'ID', minWidth: 100, visible: false },
    {
      field: 'displayName',
      title: '人员名称',
      minWidth: 120,
      editRender: { enabled: false, name: 'NInput' }
    },
    {
      field: 'attrCode',
      title: '扩展属性',
      minWidth: 100,
      editRender: {
        name: 'NInput',
        props: {
          placeholder: '请输入备注'
        }
      }
    },
    {
      field: 'attrVal',
      title: '属性值',
      minWidth: 100,
      editRender: {
        name: 'NInput',
        props: {
          placeholder: '请输入属性值'
        }
      }
    },
    {
      title: '操作',
      width: 200,
      fixed: 'right',
      slots: { default: 'operation' }
    }
  ];
}

/**
 * @func: personPicTableColumns
 * @description: 人员图片table列字段配置
 * @param {*}
 * @return {VxeGridPropTypes.Columns}
 * @example:
 */
export function personPicTableColumns(): VxeGridPropTypes.Columns {
  return [
    { type: 'seq', width: 60, title: '序号', fixed: 'left' },
    { type: 'checkbox', width: 45 },
    { field: 'id', title: 'ID', minWidth: 100, visible: false },
    {
      field: 'displayName',
      title: '人员名称',
      minWidth: 120,
      editRender: { enabled: false, name: 'NInput' }
    },
    {
      field: 'type',
      title: '照片类别',
      minWidth: 100,
      editRender: {
        name: 'baseDictRender',
        props: {
          autoLoadData: true,
          dictCode: 'person_pic_type'
        }
      }
    },
    {
      field: 'url',
      title: '照片路径',
      minWidth: 100,
      editRender: {
        name: 'NInput',
        props: {
          placeholder: '请输入属性值'
        }
      }
    },
    {
      field: 'memo',
      title: '备注',
      editRender: {
        name: 'NInput',
        props: {
          type: 'textarea',
          autosize: { minRows: 1, maxRows: 1 },
          placeholder: '请输入备注'
        }
      }
    },
    {
      title: '操作',
      width: 200,
      fixed: 'right',
      slots: { default: 'operation' }
    }
  ];
}

定义接口声明

/src/service/api/cbb/base目录下创建名为person.ts的文件,其中cbb表示框架,base表示模块,person表示业务功能;我们在业务系统中应该在api目录下创建框架名,依次是模块,业务功能。

表示请求后台的接口数据,提供页面调用加载数据。 声明内容如下

ts
import { EnumContentType } from '@/enum';
import { request } from '@/service/request';

export function getPersonForm(params: any) {
  return request.get<any>('/base/person/form', {
    params
  });
}

export function getPersonPage(params: any) {
  return request.get<any>('/base/person/page', {
    params
  });
}

export function doPersonAdd(data: any) {
  return request.post<any>('/base/person/add', data, {
    encryption: { type: 'aes' }
  });
}

export function doPersonEdit(data: any) {
  return request.post<any>('/base/person/edit', data, {
    encryption: { type: 'aes' }
  });
}

export function doPersonDelete(data: any) {
  return request.post<any>('/base/person/delete', data, {
    encryption: { type: 'aes' }
  });
}

export function doPersonResetPassword(data: any) {
  return request.post<any>('/base/person/resetPassword', data, {
    encryption: { type: 'aes' }
  });
}

export function doUnlockedPerson(data: any) {
  return request.post<any>('/base/person/unlocked', data, {
    encryption: {
      type: 'aes',
      param: ['personId'],
      paramDataKey: 'personId'
    },
    headers: { 'Content-Type': EnumContentType.formUrlencoded }
  });
}

export function getPersonListByIds(params: any) {
  return request.get<any>('/base/person/listByIds', {
    params
  });
}

export function doBindBigAntAccount(data: any) {
  return request.post<any>('/base/person/bindBigAntAccount', data, {
    encryption: { type: 'aes' }
  });
}

export function getBindInfo() {
  return request.get<any>('/base/person/getBindInfo', {});
}

export function cancelBindBigAnt() {
  return request.post<any>('/base/person/cancelBindBigAnt', null, {
    encryption: { type: 'aes' }
  });
}

/**
 * 停用人员
 * @param data
 * @returns axios实例
 */
export function doPersonDisableStatus(data: any) {
  return request.post<any>('/base/person/disableStatus', data, {
    encryption: {
      type: 'aes',
      param: ['personId'],
      paramDataKey: 'personId'
    },
    headers: { 'Content-Type': EnumContentType.formUrlencoded }
  });
}

/**
 * @func: getOrgCanAddPersonPage
 * @description: 查询人员列表,根据传入组织查询上级组织的人员,用于判断添加上级组织中是否存在该用户,只有存在才能返回。
 * @param {any} params
 * @return {*}
 * @example:
 */
export function getOrgCanAddPersonPage(params: any) {
  return request.get<any>('/base/person/orgCanAddPersonPage', { params });
}

/**
 * @func: doUploadPersonEsign
 * @description: 上传人员的签名照
 * @param {any} data
 * @return {*}
 * @example:
 */
export function doUploadPersonEsign(data: any) {
  return request.post<any>('/base/person/upload/esign', data, {
    headers: { 'Content-Type': EnumContentType.formData }
  });
}

/**
 * @func: doUploadPersonEsign
 * @description: 删除人员的签名照
 * @return {*}
 * @example:
 */
export function doDeletePersonEsign() {
  return request.post<any>('/base/person/delete/esign', {});
}

/**
 * @func: getPersonPicInfo
 * @description: 获取人员图片信息
 * @return {*}
 * @example:
 */
export function getPersonPicInfo() {
  return request.get<any>('/base/person/pic/info');
}

创建功能页

/src/views/cbb/base/person目录下创建名为index.ts的文件,其中cbb表示框架,base表示模块,person表示业务功能;我们在业务系统中应该在views目录下创建框架名,依次是模块,业务功能。 声明内容如下

vue
<template>
  <div>
    <n-card :bordered="false" class="rounded-16px shadow-sm person-container">
      <crud-tabs :crud-key="crudKey">
        <template #index>
          <vxe-grid
            ref="personTableRef"
            v-bind="personTableOptions"
            @form-submit="personProxy.queryTableData"
            @page-change="personProxy.handlePageChange"
            @sort-change="personProxy.queryTableData"
            @form-reset="personProxy.handleResetForm"
            @current-change="handlePersonTableCurrentChange"
          >
            <!--自定义插槽 toolbar buttons 插槽-->
            <template #toolbar_buttons>
              <n-space>
                <n-button
                  v-permission="{ permission: ['base:person:add'] }"
                  type="primary"
                  @click="personProxy.handleAddPane({ name: 'add', tab: '人员新增', params: {} })"
                >
                  新增
                </n-button>
              </n-space>
            </template>
            <template #operation="{ row }">
              <n-space>
                <n-button
                  v-if="row.status === '1'"
                  v-permission="{ permission: ['base:person:edit'] }"
                  text
                  type="primary"
                  @click="personProxy.handleAddPane({ name: 'edit', tab: '人员修改', params: row })"
                >
                  修改
                </n-button>
                <n-button v-if="row.status === '1'" text type="info" @click="handlePersonStatus(row)"> 停用 </n-button>

                <n-dropdown v-if="row.status === '1'" trigger="hover" :options="moreButton.options({ row })">
                  <n-button type="primary" text> 更多 </n-button>
                </n-dropdown>
              </n-space>
            </template>
          </vxe-grid>
        </template>
        <template #add="{ tabPane }">
          <person-form :crud-key="crudKey" :tab-pane="tabPane"></person-form>
        </template>
        <template #view="{ tabPane }">
          <person-form :crud-key="crudKey" :tab-pane="tabPane"></person-form>
        </template>
        <template #edit="{ tabPane }">
          <person-form :crud-key="crudKey" :tab-pane="tabPane"></person-form>
        </template>
      </crud-tabs>
    </n-card>

    <vxe-modal v-model="personExtModal" destroy-on-close min-width="600" resize :title="'人员扩展属性'" width="1200">
      <template #default>
        <person-ext ref="personExtRef" :current-person="currentPersonRow" @on-finished="handlePersonExtFinished" />
      </template>
    </vxe-modal>

    <vxe-modal v-model="personPicModal" destroy-on-close min-width="600" resize :title="'人员照片'" width="1200">
      <template #default>
        <person-pic ref="personPicRef" :current-person="currentPersonRow" @on-finished="handlePersonPicFinished" />
      </template>
    </vxe-modal>
  </div>
</template>

<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import type { VxeTableInstance, VxeGridProps } from 'vxe-table';
import { useCrudStore } from '@/store';
import { usePermission } from '@/composables';
import { baseTableHeight } from '@/utils';
import { doPersonDelete, getPersonPage, doPersonResetPassword, doPersonDisableStatus } from '@/service/api/cbb/base';
import { tableMenuConfig, tablePagerConfig, tableToolbarConfig, crudProxy } from '@/const';
import { personTableColumns, personTableFormConfig } from '@/const/crud/cbb/base';
import personForm from './form.vue';
import PersonExt from './components/PersonExt.vue';
import PersonPic from './components/PersonPic.vue';

const route = useRoute();
const crudKey = route.path;
const { addCrudTab } = useCrudStore();
addCrudTab({ crudKey, pane: { name: 'index', tab: '人员管理' } });

const personProxy: Crud.CrudProxyInstance = crudProxy();
const personTableRef = ref({} as VxeTableInstance);
const orgPersonRef = ref({} as any);
const userRef = ref({} as any);
const personExtRef = ref({} as VxeTableInstance);
const personPicRef = ref({} as VxeTableInstance);
const personExtModal = ref(false);
const personPicModal = ref(false);

const currentPersonRow = ref({});
const currentPersonRowRight = ref({ status: '1' });

const tableHeight = baseTableHeight();

const personTableOptions = reactive<VxeGridProps>({
  columns: personTableColumns(),
  data: [],
  formConfig: personTableFormConfig(),
  keyboardConfig: { isArrow: true },
  loading: false,
  pagerConfig: tablePagerConfig(),
  toolbarConfig: {
    ...tableToolbarConfig(),
    refresh: { query: personProxy.queryTableData }
  },
  exportConfig: {},
  printConfig: {},
  menuConfig: tableMenuConfig(),
  params: {
    COPY_ADD: personProxy.handleCopyAdd
  },
  height: tableHeight
});

onMounted(() => {
  personProxy.init({
    tableOptions: personTableOptions,
    tableRef: personTableRef.value,
    crudKey,
    queryApi: getPersonPage,
    deleteApi: doPersonDelete,
    crudName: '人员管理',
    copyAddPaneName: 'add'
  });
  personProxy.queryTableData();
});

/**
 * @func: handlePersonExtList
 * @description: 处理人员扩展属性列表显示
 * @param {*} row
 * @return {*}
 * @example:
 */
function handlePersonExtList(row: any) {
  personExtModal.value = true;
  currentPersonRow.value = row;
}

/**
 * @func: handleOrgPersonFinished
 * @description: 人员扩展属性列表处理完成
 * @param {*}
 * @return {*}
 * @example:
 */
function handlePersonExtFinished() {
  personExtModal.value = false;
  personProxy.queryTableData();
}

/**
 * @func: handlePersonPicList
 * @description: 处理人图片列表显示
 * @param {*} row
 * @return {*}
 * @example:
 */
function handlePersonPicList(row: any) {
  personPicModal.value = true;
  currentPersonRow.value = row;
}

/**
 * @func: handlePersonPicFinished
 * @description: 人员图片列表处理完成
 * @param {*}
 * @return {*}
 * @example:
 */
function handlePersonPicFinished() {
  personPicModal.value = false;
  personProxy.queryTableData();
}

function handlePersonResetPassword(data: any[]) {
  window.$dialog?.warning({
    title: '重置密码',
    content: '您确定要重置用户密码吗?',
    positiveText: '确定',
    negativeText: '取消',
    maskClosable: false,
    onPositiveClick: () => {
      doPersonResetPassword(data).then(() => {
        window.$message?.success('重置密码成功');
      });
    }
  });
}

/**
 * 更多按钮选项
 * @param params
 */
const moreButton = {
  options(params: any) {
    const { filterDropdownOptions } = usePermission();
    return filterDropdownOptions([
      {
        label: '重置密码',
        key: 'authorizedatascope',
        permissions: { permission: ['base:person:reset_password'] },
        props: {
          onClick: () => {
            handlePersonResetPassword([params.row.id]);
          }
        }
      },
      {
        label: '扩展信息',
        key: 'extInfo',
        permissions: { permission: ['base:person:ext'] },
        props: {
          onClick: () => {
            handlePersonExtList(params.row);
          }
        }
      },
      {
        label: '照片信息',
        key: 'picInfo',
        permissions: { permission: ['base:person:pic'] },
        props: {
          onClick: () => {
            handlePersonPicList(params.row);
          }
        }
      }
    ]);
  }
};

function handlePersonTableCurrentChange({ row }: any) {
  currentPersonRowRight.value = row;
}

async function handlePersonStatus(row: any) {
  if (row.status === '1') {
    const { error } = await doPersonDisableStatus({ personId: row.id });
    if (!error) {
      window.$message?.success('停用成功');
      personProxy.queryTableData();
      orgPersonRef.value.fetchOrgPersonTableData();
      userRef.value.userProxy.queryTableData();
    }
  }
}
</script>
<style scoped></style>

创建表单页

/src/views/cbb/base/person目录下创建名为form.ts的文件,其中cbb表示框架,base表示模块,person表示业务功能;我们在业务系统中应该在views目录下创建框架名,依次是模块,业务功能。 声明内容如下

vue
<template>
  <div class="person-container">
    <vxe-form ref="personFormRef" v-bind="personFormOptions" @submit="personFormProxy.handleSave">
      <template #formOperation>
        <n-space justify="center">
          <n-button v-if="props.tabPane.name !== 'view'" type="primary" attr-type="submit"> 保存 </n-button>
          <n-button v-if="props.tabPane.name === 'add'" @click="personFormProxy.handleResetForm"> 重置 </n-button>
          <n-button @click="personFormProxy.handleClosePane"> 关闭 </n-button>
        </n-space>
      </template>
    </vxe-form>
  </div>
</template>

<script setup lang="ts">
import { reactive, onMounted, ref } from 'vue';
import type { VxeFormInstance, VxeFormProps } from 'vxe-table';
import { getPersonForm, doPersonAdd, doPersonEdit } from '@/service/api/cbb/base';
import { personFormData, personFormRules, personFormItems } from '@/const/crud/cbb/base';
import { crudFormProxy } from '@/const';

interface Props {
  crudKey: string | number;
  tabPane: Crud.CrudTabPaneProps;
}
const props = withDefaults(defineProps<Props>(), {
  crudKey: '',
  tabPane: () => {
    return { name: '', params: {} };
  }
});

const personFormProxy: Crud.CrudFormProxyInstance = crudFormProxy();
const personFormRef = ref({} as VxeFormInstance);
const personFormOptions = reactive<VxeFormProps>({
  loading: false,
  titleAlign: 'right',
  titleWidth: '80',
  data: personFormData(),
  items: personFormItems(),
  rules: personFormRules()
});

onMounted(() => {
  personFormProxy.init({
    props,
    formOptions: personFormOptions,
    formRef: personFormRef.value,
    queryApi: getPersonForm,
    addApi: doPersonAdd,
    editApi: doPersonEdit
  });
  personFormProxy.handleAutoLoad();
});
</script>
<style lang="scss" scoped></style>