ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Vue实战十一:Vue3+TS+Antd实现vben项目的增删改查

2021-12-03 15:34:06  阅读:576  来源: 互联网

标签:Vue const vben 改查 value record components import data


文章目录


说明:之前用Vue2+ElementUI|iView用的那是相当的爽,写法和之前做移动的用的Ionic很是类似,最近新开了一个项目,用的Vue3+TS+Antd那真是一把辛酸一把泪,完全摸索+找规律+瞎蒙,开发了一个模块之后终于有所得,以此篇文章记录。

1.效果图预览

老规矩先上效果图

列表
image

新增
image

附件上传
image

修改
image

详情
image

2.列表的实现

index.vue页面中

template布局如下,BasicTable列表页及操作列中定义按钮,DetailModal详情界面,InterfaceModal编辑及新增界面

<template>
  <PageWrapper dense
               contentFullHeight
               fixedHeight
               contentClass="flex">
    <DetailModal :info="rowInfo"
                 @register="registerDetailModal" />
    <BasicTable @register="registerTable"
                :searchInfo="searchInfo">
      <template #toolbar>
        <a-button type="primary"
                  @click="handleCreate">接口添加</a-button>
      </template>
      <template #action="{ record }">
        <TableAction :actions="[
            {
              icon: 'clarity:note-edit-line',
              tooltip: '编辑',
              onClick: handleEdit.bind(null, record),
            },
            {
              icon: 'mdi:arrow-down-bold-circle-outline',
              tooltip: '下载',
              onClick: handleDownload.bind(null, record),
              ifShow: () => {
                return record.interfaceDoc && record.docName;
              },
            },
             {
              icon: 'icon-park-outline:view-grid-detail',
              color:'success',
              tooltip: '详情',
              onClick: handleSetDetail.bind(null, record),
            },
             {
              icon: 'ant-design:delete-outlined',
              color: 'error',
              popConfirm: {
                title: '是否确认删除',
                confirm: handleDelete.bind(null, record),
              },
            },

          ]" />
      </template>
    </BasicTable>
    <InterfaceModal @register="registerModal"
               @success="handleSuccess" />

  </PageWrapper>
</template>

script中的逻辑实现

<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { set, del, page, pageCommon } from '/@/api/interface/list';
import { PageWrapper } from '/@/components/Page';
import { SvgIcon } from '/@/components/Icon';
import { useModal } from '/@/components/Modal';
import InterfaceModal from './InterfaceModal.vue';
import DetailModal from './DetailModal.vue';
import { columns, searchFormSchema } from './list.data';
import { useGo } from '/@/hooks/web/usePage';
import { useMessage } from '/@/hooks/web/useMessage';
import { downloadByUrl, downloadByData } from '/@/utils/file/download';
import { Alert } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import { InterFaceModel } from '/@/api/interface/model/interfaceModel';
const { createMessage } = useMessage();

const rowInfo = ref<Recordable>();

export default defineComponent({
  name: 'UserManagement',
  components: {
    BasicTable,
    PageWrapper,
    InterfaceModal,
    TableAction,
    DetailModal,
    [Alert.name]: Alert,
    SvgIcon,
  },
  setup() {
    const go = useGo();
    const [registerModal, { openModal }] = useModal();
    const [registerDetailModal, { openModal: openDetailModal }] = useModal();
    const searchInfo = reactive<Recordable>({});
    const [registerTable, { reload, updateTableDataRecord }] = useTable({
      title: '接口列表',
      api: page,
      rowKey: 'id',
      columns,
      formConfig: {
        labelWidth: 120,
        schemas: searchFormSchema,
        autoSubmitOnEnter: true,
      },
      useSearchForm: true,
      showTableSetting: true,
      bordered: true,
      handleSearchInfoFn(info) {
        console.log('handleSearchInfoFn', info);
        return info;
      },
      actionColumn: {
        width: 150,
        title: '操作',
        dataIndex: 'action',
        slots: { customRender: 'action' },
      },
    });

    const userStore = useUserStore();
    const { userId } = userStore.getUserInfo;

    function handleCreate() {
      openModal(true, {
        isUpdate: false,
      });
    }

    function handleEdit(record: Recordable) {
      openModal(true, {
        record,
        isUpdate: true,
      });
    }

    function handleDownload(record: Recordable) {
      let url =
        '/api/tzwy-component/attachment/download?fileName=' +
        record.interfaceDoc +
        '&docName=' +
        record.docName;
      handleDownloadByUrl(url);
    }

    async function handleDelete(record: Recordable) {
      await del({ id: record.id, isDel: '1', delBy: userId + '' });
      createMessage.success('删除成功!');
      handleSuccess();
    }

    function handleSuccess() {
      reload();
    }

    function handleSelect(departId = '') {
      searchInfo.departId = departId;
      reload();
    }

    function handleSetDetail(record: InterFaceModel) {
      rowInfo.value = record;
      openDetailModal(true, {
        info: record,
        isUpdate: true,
      });
    }

    function handleDetailSuccess() {
      reload();
    }

    function handleDownloadByUrl(urlValue) {
      downloadByUrl({
        url: urlValue,
        target: '_self',
      });
    }

    return {
      registerTable,
      registerModal,
      handleCreate,
      handleEdit,
      handleDelete,
      handleSuccess,
      handleSelect,
      handleView,
      handleAuth,
      searchInfo,
      handleSetDetail,
      registerDetailModal,
      handleDetailSuccess,
      handleDownloadByUrl,
      handleOpen,
      userId,
    };
  },
});
</script>

说明:新增调用handleCreate,编辑调用handleEdit,详情调用handleSetDetail,下载调用handleDownload,删除调用handleDelete

列表中显示字段定义list.data.ts文件中,在状态列中定义onChange方法实现接口开关的开启及关闭功能

export const columns: BasicColumn[] = [
    {
        title: '接口名称',
        dataIndex: 'xx1',
        width: 100,
    },
    {
        title: '接口编码',
        dataIndex: 'xx2',
        width: 100,
    },
    {
        title: '接口地址',
        dataIndex: 'xx3',
        width: 150,
    },
   ......
    {
        title: '状态',
        dataIndex: 'enabled',
        width: 120,
        customRender: ({ record }) => {
          if (!Reflect.has(record, 'pendingStatus')) {
            record.pendingStatus = false;
          }
          return h(Switch, {
            checked: record.enabled === '1',
            checkedChildren: '启用',
            unCheckedChildren: '禁用',
            loading: record.pendingStatus,
            onChange(checked: boolean) {
              record.pendingStatus = true;
              const newStatus = checked ? '1' : '0';
              const { createMessage } = useMessage();
              set({id:record.id, enabled:newStatus})
                .then(() => {
                  record.enabled = newStatus;
                  if(newStatus==='1'){
                    createMessage.success(`启用成功`);
                  }else{
                    createMessage.success(`禁用成功`);
                  }
                })
                .catch(() => {
                  createMessage.error('操作失败');
                })
                .finally(() => {
                  record.pendingStatus = false;
                });
            },
          });
        },
      },

];

在list.data.ts中定义查询接口参数

export const searchFormSchema: FormSchema[] = [
    {
        field: 'xx1',
        label: '接口名称',
        component: 'Input',
        colProps: { span: 6 },
    },
    {
        field: 'xx2',
        label: '接口描述',
        component: 'Input',
        colProps: { span: 6 },
    },
    {
        field: 'startDate',
        label: '起始时间',
        component: 'DatePicker',
        colProps: { span: 6 },
    },
    {
        field: 'endDate',
        label: '截止时间',
        component: 'DatePicker',
        colProps: { span: 6 },
    },
];

在api中定义模块文件,在模块文件下新建list.ts,在list.ts中定义接口

import {  Entity,EntityVO, EntityDTO } from './model/interfaceModel';
import { defHttp } from '/@/utils/http/axios';

enum Api {
  Page = '/xx/xx/page',
  PageCommon = '/xx/xx/openListPage',
  Set = '/xx/xx/save',
  Del = '/xx/xx/delById',
}

// 列表
export const page = (params?: EntityVO) => defHttp.get<EntityDTO>({ url: Api.Page, params });

// 列表
export const pageCommon = (params?: EntityVO) => defHttp.get<EntityDTO>({ url: Api.PageCommon, params });

// 保存
export const set = (params: Entity) => defHttp.post<Entity>({ url: Api.Set, params });

// 删除
export const del = (params: Entity) => defHttp.post<Entity>({ url: Api.Del, params });

在当前模块下新建model文件夹在interfaceModel中定义实现类、查询参数及响应模型

// 引入基础包
import { BasicPageParams, BasicFetchResult } from '/@/api/model/baseModel';

// 定义查询参数
export type EntityVO = BasicPageParams & {
  startDate?: string;
  endDate?: string;
};

// 定义对象
export interface Entity {
    xx1: string;
    xx2: number;
    xx3: string;
   ......
}

// 生成响应模型
export type EntityDTO = BasicFetchResult<EntityVO>;

至此,一个列表请求就做好了

3.数据新增、修改及附件上传的实现

数据新增及修改页面InterfaceModal.vue,在当前页面下实现template及script,

template代码如下

<template>
  <BasicModal v-bind="$attrs"
              @register="registerModal"
              :title="getTitle"
              @ok="handleSubmit">
    <div class="m-8">
      <BasicUpload :maxSize="20"
                   :maxNumber="1"
                   :accept="['doc','docx','rar','zip']"
                   @change="handleChange"
                   :api="uploadApi"
                   :showPreviewNumber="true" />
    </div>

    <BasicForm @register="registerForm" />
  </BasicModal>
</template>

script逻辑如下

<script lang="ts">
import { defineComponent, ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './list.data';
import { departList } from '/@/api/system/depart';
import { set } from '/@/api/模块名/list';
import { uploadApi } from '/@/api/sys/upload';
import { BasicUpload } from '/@/components/Upload';
import { PageWrapper } from '/@/components/Page';
import { Alert } from 'ant-design-vue';

export default defineComponent({
  name: 'InterfaceModal',
  components: { BasicModal, BasicForm, BasicUpload, PageWrapper, [Alert.name]: Alert },
  emits: ['success', 'register'],
  setup(_, { emit }) {
    const isUpdate = ref(true);
    const rowId = ref('');
    let fileNameValue: string;
    let bucketNameValue: string;
    const [registerForm, { setFieldsValue, updateSchema, resetFields, validate }] = useForm({
      labelWidth: 100,
      schemas: formSchema,
      showActionButtonGroup: false,
      actionColOptions: {
        span: 23,
      },
    });
    const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
      resetFields();
      setModalProps({ confirmLoading: false });
      isUpdate.value = !!data?.isUpdate;
      if (unref(isUpdate)) {
        rowId.value = data.record.id;
        setFieldsValue({
          ...data.record,
        });
      }
      const treeData = await departList();
      updateSchema([
        {
          field: 'xx',
          componentProps: { treeData },
        },
      ]);
    });

    const getTitle = computed(() => (!unref(isUpdate) ? '新增接口' : '编辑接口'));
    async function handleSubmit() {
      try {
        const values = await validate();
        setModalProps({ confirmLoading: true });
        values.docName = fileNameValue;
        values.interfaceDoc = bucketNameValue;
        if (!!unref(isUpdate)) {
          values.id = rowId.value;
        }
        await set(values);
        closeModal();
        emit('success', { isUpdate: unref(isUpdate), values: { ...values, id: rowId.value } });
      } finally {
        setModalProps({ confirmLoading: false });
      }
    }
    return {
      registerModal,
      registerForm,
      getTitle,
      handleSubmit,
      handleChange: (list) => {
        fileNameValue = list[0]?.fileName;
        bucketNameValue = list[0]?.bucketName;
        // createMessage.info(`已上传文件${JSON.stringify(list)}`);
      },
      uploadApi,
    };
  },
});
</script>

新增及修改页面按钮提交调用handleSubmit,根据isUpdate的值分别调用新增及修改接口,当前项目中新增与修改的区别是是否有id,修改时给表单的id赋值即可,最终调用await set(values);实现数据新增及修改。set方法是引用自import { set } from ‘/@/api/模块名/list’;

新增及修改字段配置import { formSchema } from ‘./list.data’;具体如下

export const formSchema: FormSchema[] = [
    {
        field: 'xx1',
        label: '接口名称',
        component: 'Input',

    },
    {
        field: 'xx2',
        label: '接口编码',
        component: 'Input',
    },
   

    {
        field: 'xx3',
        label: '所属单位',
        component: 'TreeSelect',
        componentProps: {
            replaceFields: {
                title: 'name',
                key: 'id',
                value: 'id',
            },
            getPopupContainer: () => document.body,
        },
    },
    {
        field: 'xx4',
        label: '请求方式',
        component: 'RadioButtonGroup',
        defaultValue: 'GET',
        componentProps: {
            options: [
                { label: 'GET', value: 'GET' },
                { label: 'POST', value: 'POST' },
            ],
        },
    },
    {
        field: 'xx5',
        label: '接口描述',
        component: 'InputTextArea',
    },
   ......
    {
        field: 'xx6',
        label: '参数类型',
        component: 'RadioButtonGroup',
        defaultValue: 'Json',
        helpMessage:['Json:{"interfaceName":"xxx","interfaceCode":"xxx"}','form-data:xxx...........','raw:xxx.................'],
        componentProps: {
            options: [
                { label: 'Json', value: 'Json' },
                { label: 'form-data', value: 'form-data' },
                { label: 'raw', value: 'raw' },
            ],
        },
    },
];

附件上传的实现

 <BasicUpload :maxSize="20"
                   :maxNumber="1"
                   :accept="['doc','docx','rar','zip']"
                   @change="handleChange"
                   :api="uploadApi"
                   :showPreviewNumber="true" />

参数maxNumber限制上传的最大数量,accept限制上传的附件类型,change方法是上传成功后并点击页面的保存获取到上传附件的信息数据,api是调用的上传接口,注意是通过调用/upload对应的值来实现的,可以在配置文件.env.development中修改/upload中的值来实现。

附件上传源代码修改路径/components/Upload/UploadModal.vue中存有保存附件的方法如下

   //   点击保存
    function handleOk() {
      const { maxNumber } = props;
      if (fileListRef.value.length > maxNumber) {
        return createMessage.warning(t('component.upload.maxNumber', [maxNumber]));
      }
      if (isUploadingRef.value) {
        return createMessage.warning(t('component.upload.saveWarn'));
      }
      let fileList: Array<{ bucketName: string|undefined , fileName: string|undefined, url: string|undefined}>=[];

      for (const item of fileListRef.value) {
        const { status, responseData } = item;
        if (status === UploadResultStatus.SUCCESS && responseData) {
          let obj = responseData?.data;
          let oneObj={
              bucketName:obj?.bucketName,
              fileName:obj?.fileName,
              url:obj?.url
          }
          fileList.push(oneObj);
        }
      }
      // 存在一个上传成功的即可保存
      if (fileList.length <= 0) {
        return createMessage.warning(t('component.upload.saveError'));
      }
      fileListRef.value = [];
      closeModal();
      emit('change', fileList);
    }

我们修改fileList中的值改为自己想要获取的值即可将fileList数组返回到handleChange中并接收,这里要熟悉下ts语法,要不可能变量都不会定义。

4.详情界面的实现

怀念vue2+ElementUI时详情界面的跳转在我弄了一天没搞定详情界面数据的展示时,这里涉及到Vue3的使用及TS语法,通过瞎蒙+找规律找到了setup这个关键方法,官方解释说setup在beforeCreate之前执行一次并且方法中无法使用this,vue2中用的最爽的this在vue3中没了…

详情界面template实现如下

<template>
  <BasicModal v-bind="$attrs"
              :width="1000"
              @register="registerModal"
              :title="getTitle">
    <Description @register="register" />
  </BasicModal>
</template>

script实现如下

<script lang="ts">
import { defineComponent, ref, computed, unref, reactive } from 'vue';
import { BasicModal, useModalInner } from '/@/components/Modal';
import { BasicForm, useForm } from '/@/components/Form/index';
import { uploadApi } from '/@/api/sys/upload';
import { BasicUpload } from '/@/components/Upload';
import { PageWrapper } from '/@/components/Page';
import { Alert } from 'ant-design-vue';
import {
  Description,
  DescItem,
  useDescription,
  UseDescReturnType,
} from '/@/components/Description/index';
import { detailFormSchema } from './list.data';
import { getDescSchema } from './data';

export default defineComponent({
  name: 'DetailModal',
  components: { Description, BasicModal, BasicForm,  PageWrapper },
  emits: ['success', 'register'],
  setup(_, { emit }) {
    const isUpdate = ref(true);
    const rowId = ref('');
    const formData = ref({});
    const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
      formData.value = await data?.info;
    });

    const [register] = useDescription({
      data: formData,
      column: 1,
      schema: detailFormSchema,
    });

    const getTitle = computed(() => ('接口详情'));
    async function handleSubmit() {
      try {
        setModalProps({ confirmLoading: true });
        closeModal();
        emit('success');
      } finally {
        setModalProps({ confirmLoading: false });
      }
    }
    return {
      registerModal,
      register,
      getTitle,
      handleSubmit,
      formData,
    };
  },
});
</script>

说明:由于对ts不太熟,刚开始初始化时给let formData:any;导致数据值无法加载,后来了解了ref和reactive的用法,觉得formData既然是对象应该用reactive,然后在formData中定义了一堆属性然后在给formData属性赋值时,只能一个字段一个字段赋值,最终用了ref代码就变的整洁了。

标签:Vue,const,vben,改查,value,record,components,import,data
来源: https://blog.csdn.net/qq_16497617/article/details/121699475

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有