Pre Merge pull request !197 from XuZhiqiang/feat-erp
						commit
						c67919965b
					
				|  | @ -4,11 +4,12 @@ export namespace ErpProductCategoryApi { | ||||||
|   /** ERP 产品分类信息 */ |   /** ERP 产品分类信息 */ | ||||||
|   export interface ProductCategory { |   export interface ProductCategory { | ||||||
|     id?: number; // 分类编号
 |     id?: number; // 分类编号
 | ||||||
|     parentId: number; // 父分类编号
 |     parentId?: number; // 父分类编号
 | ||||||
|     name: string; // 分类名称
 |     name: string; // 分类名称
 | ||||||
|     code: string; // 分类编码
 |     code?: string; // 分类编码
 | ||||||
|     sort: number; // 分类排序
 |     sort?: number; // 分类排序
 | ||||||
|     status: number; // 开启状态
 |     status?: number; // 开启状态
 | ||||||
|  |     children?: ProductCategory[]; // 子分类
 | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,125 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpProductCategoryApi } from '#/api/erp/product/category'; | ||||||
|  | 
 | ||||||
|  | import { handleTree } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { z } from '#/adapter/form'; | ||||||
|  | import { getProductCategoryList } from '#/api/erp/product/category'; | ||||||
|  | import { CommonStatusEnum, DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'id', | ||||||
|  |       component: 'Input', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'parentId', | ||||||
|  |       label: '上级分类', | ||||||
|  |       component: 'ApiTreeSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         allowClear: true, | ||||||
|  |         api: async () => { | ||||||
|  |           const data = await getProductCategoryList(); | ||||||
|  |           data.unshift({ | ||||||
|  |             id: 0, | ||||||
|  |             name: '顶级分类', | ||||||
|  |           }); | ||||||
|  |           return handleTree(data); | ||||||
|  |         }, | ||||||
|  |         labelField: 'name', | ||||||
|  |         valueField: 'id', | ||||||
|  |         childrenField: 'children', | ||||||
|  |         placeholder: '请选择上级分类', | ||||||
|  |         treeDefaultExpandAll: true, | ||||||
|  |       }, | ||||||
|  |       rules: 'selectRequired', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '分类名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入分类名称', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'code', | ||||||
|  |       label: '分类编码', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入分类编码', | ||||||
|  |       }, | ||||||
|  |       rules: z.string().regex(/^[A-Z]+$/, '分类编码必须为大写字母'), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'sort', | ||||||
|  |       label: '显示顺序', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         min: 0, | ||||||
|  |         controlsPosition: 'right', | ||||||
|  |         placeholder: '请输入显示顺序', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '状态', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |         buttonStyle: 'solid', | ||||||
|  |         optionType: 'button', | ||||||
|  |       }, | ||||||
|  |       rules: z.number().default(CommonStatusEnum.ENABLE), | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns(): VxeTableGridOptions<ErpProductCategoryApi.ProductCategory>['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'name', | ||||||
|  |       title: '分类名称', | ||||||
|  |       align: 'left', | ||||||
|  |       treeNode: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'code', | ||||||
|  |       title: '分类编码', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sort', | ||||||
|  |       title: '显示顺序', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '分类状态', | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.COMMON_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'createTime', | ||||||
|  |       title: '创建时间', | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       title: '操作', | ||||||
|  |       width: 220, | ||||||
|  |       fixed: 'right', | ||||||
|  |       slots: { default: 'actions' }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -1,34 +1,179 @@ | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { DocAlert, Page } from '@vben/common-ui'; | import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { ErpProductCategoryApi } from '#/api/erp/product/category'; | ||||||
| 
 | 
 | ||||||
| import { Button } from 'ant-design-vue'; | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteProductCategory, | ||||||
|  |   getProductCategoryList, | ||||||
|  | } from '#/api/erp/product/category'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns } from './data'; | ||||||
|  | import Form from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Form, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 切换树形展开/收缩状态 */ | ||||||
|  | const isExpanded = ref(true); | ||||||
|  | function toggleExpand() { | ||||||
|  |   isExpanded.value = !isExpanded.value; | ||||||
|  |   gridApi.grid.setAllTreeExpand(isExpanded.value); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 创建分类 */ | ||||||
|  | function handleCreate() { | ||||||
|  |   formModalApi.setData(null).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 添加下级分类 */ | ||||||
|  | function handleAppend(row: ErpProductCategoryApi.ProductCategory) { | ||||||
|  |   formModalApi.setData({ parentId: row.id }).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑分类 */ | ||||||
|  | function handleEdit(row: ErpProductCategoryApi.ProductCategory) { | ||||||
|  |   formModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除分类 */ | ||||||
|  | async function handleDelete(row: ErpProductCategoryApi.ProductCategory) { | ||||||
|  |   const hideLoading = message.loading({ | ||||||
|  |     content: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|  |     key: 'action_key_msg', | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteProductCategory(row.id as number); | ||||||
|  |     message.success({ | ||||||
|  |       content: $t('ui.actionMessage.deleteSuccess', [row.name]), | ||||||
|  |       key: 'action_key_msg', | ||||||
|  |     }); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     hideLoading(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const checkedIds = ref<number[]>([]); | ||||||
|  | function handleRowCheckboxChange({ | ||||||
|  |   records, | ||||||
|  | }: { | ||||||
|  |   records: ErpProductCategoryApi.ProductCategory[]; | ||||||
|  | }) { | ||||||
|  |   checkedIds.value = records.map((item) => item.id as number); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(), | ||||||
|  |     height: 'auto', | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async () => { | ||||||
|  |           return await getProductCategoryList(); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     pagerConfig: { | ||||||
|  |       enabled: false, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |       isHover: true, | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: true, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |     treeConfig: { | ||||||
|  |       transform: true, | ||||||
|  |       rowField: 'id', | ||||||
|  |       parentField: 'parentId', | ||||||
|  |       expandAll: true, | ||||||
|  |       accordion: false, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<ErpProductCategoryApi.ProductCategory>, | ||||||
|  |   gridEvents: { | ||||||
|  |     checkboxAll: handleRowCheckboxChange, | ||||||
|  |     checkboxChange: handleRowCheckboxChange, | ||||||
|  |   }, | ||||||
|  | }); | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|   <Page> |   <Page auto-content-height> | ||||||
|     <template #doc> |     <template #doc> | ||||||
|       <DocAlert |       <DocAlert | ||||||
|         title="【产品】产品信息、分类、单位" |         title="【产品】产品信息、分类、单位" | ||||||
|         url="https://doc.iocoder.cn/erp/product/" |         url="https://doc.iocoder.cn/erp/product/" | ||||||
|       /> |       /> | ||||||
|     </template> |     </template> | ||||||
|     <Button |     <FormModal @success="onRefresh" /> | ||||||
|       danger |     <Grid table-title="产品分类列表"> | ||||||
|       type="link" |       <template #toolbar-tools> | ||||||
|       target="_blank" |         <TableAction | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" |           :actions="[ | ||||||
|     > |             { | ||||||
|       该功能支持 Vue3 + element-plus 版本! |               label: $t('ui.actionTitle.create', ['产品分类']), | ||||||
|     </Button> |               type: 'primary', | ||||||
|     <br /> |               icon: ACTION_ICON.ADD, | ||||||
|     <Button |               auth: ['erp:product-category:create'], | ||||||
|       type="link" |               onClick: handleCreate, | ||||||
|       target="_blank" |             }, | ||||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/product/category/index" |             { | ||||||
|     > |               label: isExpanded ? '收缩' : '展开', | ||||||
|       可参考 |               type: 'primary', | ||||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/erp/product/category/index |               onClick: toggleExpand, | ||||||
|       代码,pull request 贡献给我们! |             }, | ||||||
|     </Button> |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |       <template #actions="{ row }"> | ||||||
|  |         <TableAction | ||||||
|  |           :actions="[ | ||||||
|  |             { | ||||||
|  |               label: '新增下级', | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.ADD, | ||||||
|  |               auth: ['erp:product-category:create'], | ||||||
|  |               onClick: handleAppend.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.edit'), | ||||||
|  |               type: 'link', | ||||||
|  |               icon: ACTION_ICON.EDIT, | ||||||
|  |               auth: ['erp:product-category:update'], | ||||||
|  |               onClick: handleEdit.bind(null, row), | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |               label: $t('common.delete'), | ||||||
|  |               type: 'link', | ||||||
|  |               danger: true, | ||||||
|  |               icon: ACTION_ICON.DELETE, | ||||||
|  |               auth: ['erp:product-category:delete'], | ||||||
|  |               ifShow: !(row.children && row.children.length > 0), | ||||||
|  |               popConfirm: { | ||||||
|  |                 title: $t('ui.actionMessage.deleteConfirm', [row.name]), | ||||||
|  |                 confirm: handleDelete.bind(null, row), | ||||||
|  |               }, | ||||||
|  |             }, | ||||||
|  |           ]" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|   </Page> |   </Page> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,92 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { ErpProductCategoryApi } from '#/api/erp/product/category'; | ||||||
|  | 
 | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { message } from 'ant-design-vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createProductCategory, | ||||||
|  |   getProductCategory, | ||||||
|  |   updateProductCategory, | ||||||
|  | } from '#/api/erp/product/category'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<ErpProductCategoryApi.ProductCategory>(); | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   return formData.value?.id | ||||||
|  |     ? $t('ui.actionTitle.edit', ['分类']) | ||||||
|  |     : $t('ui.actionTitle.create', ['分类']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     formItemClass: 'col-span-2', | ||||||
|  |     labelWidth: 80, | ||||||
|  |   }, | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = | ||||||
|  |       (await formApi.getValues()) as ErpProductCategoryApi.ProductCategory; | ||||||
|  |     try { | ||||||
|  |       await (formData.value?.id | ||||||
|  |         ? updateProductCategory(data) | ||||||
|  |         : createProductCategory(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       message.success($t('ui.actionMessage.operationSuccess')); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     let data = modalApi.getData<ErpProductCategoryApi.ProductCategory>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     if (data.id) { | ||||||
|  |       modalApi.lock(); | ||||||
|  |       try { | ||||||
|  |         data = await getProductCategory(data.id); | ||||||
|  |       } finally { | ||||||
|  |         modalApi.unlock(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     // 设置到 values | ||||||
|  |     formData.value = data; | ||||||
|  |     await formApi.setValues(formData.value); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="getTitle"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	 XuZhiqiang
						XuZhiqiang