feat: 【ele】新增 general 单表代码生成示例
							parent
							
								
									d1aafb60d7
								
							
						
					
					
						commit
						316fc05c52
					
				|  | @ -0,0 +1,79 @@ | |||
| /* 来自 @vben/plugins/vxe-table style.css,覆盖 vxe-table 原有的样式定义,使用 vben 的样式主题 */ | ||||
| :root { | ||||
|   --vxe-ui-font-color: hsl(var(--foreground)); | ||||
|   --vxe-ui-font-primary-color: hsl(var(--primary)); | ||||
| 
 | ||||
|   /* --vxe-ui-font-lighten-color: #babdc0; | ||||
|   --vxe-ui-font-darken-color: #86898e; */ | ||||
|   --vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%); | ||||
| 
 | ||||
|   /* base */ | ||||
|   --vxe-ui-base-popup-border-color: hsl(var(--border)); | ||||
|   --vxe-ui-input-disabled-color: hsl(var(--border) / 60%); | ||||
| 
 | ||||
|   /* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */ | ||||
| 
 | ||||
|   /* layout */ | ||||
|   --vxe-ui-layout-background-color: hsl(var(--background)); | ||||
|   --vxe-ui-table-resizable-line-color: hsl(var(--heavy)); | ||||
| 
 | ||||
|   /* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent)); | ||||
|   --vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */ | ||||
| 
 | ||||
|   /* input */ | ||||
|   --vxe-ui-input-border-color: hsl(var(--border)); | ||||
| 
 | ||||
|   /* --vxe-ui-input-placeholder-color: #8d9095; */ | ||||
| 
 | ||||
|   /* --vxe-ui-input-disabled-background-color: #262727; */ | ||||
| 
 | ||||
|   /* loading */ | ||||
|   --vxe-ui-loading-background-color: hsl(var(--overlay-content)); | ||||
| 
 | ||||
|   /* table */ | ||||
|   --vxe-ui-table-header-background-color: hsl(var(--accent)); | ||||
|   --vxe-ui-table-border-color: hsl(var(--border)); | ||||
|   --vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover)); | ||||
|   --vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%); | ||||
|   --vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent)); | ||||
|   --vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent)); | ||||
|   --vxe-ui-table-row-hover-radio-checked-background-color: hsl( | ||||
|     var(--accent-hover) | ||||
|   ); | ||||
|   --vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent)); | ||||
|   --vxe-ui-table-row-hover-checkbox-checked-background-color: hsl( | ||||
|     var(--accent-hover) | ||||
|   ); | ||||
|   --vxe-ui-table-row-current-background-color: hsl(var(--accent)); | ||||
|   --vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover)); | ||||
|   --vxe-ui-font-primary-tinge-color: hsl(var(--primary)); | ||||
|   --vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%); | ||||
|   --vxe-ui-font-primary-darken-color: hsl(var(--primary)); | ||||
| 
 | ||||
|   /* height: auto !important; */ | ||||
| 
 | ||||
|   /* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */ | ||||
| } | ||||
| 
 | ||||
| .vxe-tools--operate { | ||||
|   margin-right: 0.25rem; | ||||
|   margin-left: 0.75rem; | ||||
| } | ||||
| 
 | ||||
| .vxe-table-custom--checkbox-option:hover { | ||||
|   background: none !important; | ||||
| } | ||||
| 
 | ||||
| .vxe-toolbar { | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .vxe-buttons--wrapper:not(:empty), | ||||
| .vxe-tools--operate:not(:empty), | ||||
| .vxe-tools--wrapper:not(:empty) { | ||||
|   padding: 0.6em 0; | ||||
| } | ||||
| 
 | ||||
| .vxe-tools--operate:not(:has(button)) { | ||||
|   margin-left: 0; | ||||
| } | ||||
|  | @ -18,6 +18,8 @@ import { $t } from '#/locales'; | |||
| 
 | ||||
| import { useVbenForm } from './form'; | ||||
| 
 | ||||
| import '#/adapter/style.css'; | ||||
| 
 | ||||
| setupVbenVxeTable({ | ||||
|   configVxeTable: (vxeUI) => { | ||||
|     vxeUI.setConfig({ | ||||
|  |  | |||
|  | @ -0,0 +1,53 @@ | |||
| <!-- | ||||
|   参考自 https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/components/ContentWrap/src/ContentWrap.vue | ||||
|   保证和 yudao-ui-admin-vue3 功能的一致性 | ||||
| --> | ||||
| <script lang="ts" setup> | ||||
| import type { CSSProperties } from 'vue'; | ||||
| 
 | ||||
| import { ShieldQuestion } from '@vben/icons'; | ||||
| 
 | ||||
| import { ElCard, ElTooltip } from 'element-plus'; | ||||
| 
 | ||||
| defineOptions({ name: 'ContentWrap' }); | ||||
| 
 | ||||
| withDefaults( | ||||
|   defineProps<{ | ||||
|     bodyStyle?: CSSProperties; | ||||
|     message?: string; | ||||
|     title?: string; | ||||
|   }>(), | ||||
|   { | ||||
|     bodyStyle: () => ({ padding: '10px' }), | ||||
|     title: '', | ||||
|     message: '', | ||||
|   }, | ||||
| ); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <ElCard :body-style="bodyStyle" :header="title" class="mb-4"> | ||||
|     <template v-if="title" #header> | ||||
|       <div class="flex items-center justify-between"> | ||||
|         <div class="flex items-center"> | ||||
|           <span class="text-4 font-[700]">{{ title }}</span> | ||||
|           <ElTooltip placement="right"> | ||||
|             <template #content> | ||||
|               <div class="max-w-[200px]">{{ message }}</div> | ||||
|             </template> | ||||
|             <ShieldQuestion :size="14" class="ml-5px" /> | ||||
|           </ElTooltip> | ||||
|           <div class="pl-20px flex flex-grow"> | ||||
|             <slot name="header"></slot> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div> | ||||
|           <slot name="extra"></slot> | ||||
|         </div> | ||||
|       </div> | ||||
|     </template> | ||||
|     <template #default> | ||||
|       <slot></slot> | ||||
|     </template> | ||||
|   </ElCard> | ||||
| </template> | ||||
|  | @ -0,0 +1 @@ | |||
| export { default as ContentWrap } from './content-wrap.vue'; | ||||
|  | @ -0,0 +1 @@ | |||
| export { default as TableToolbar } from './table-toolbar.vue'; | ||||
|  | @ -0,0 +1,79 @@ | |||
| <!-- add by puhui999:vxe table 工具栏二次封装,提供给 vxe 原生列表使用 --> | ||||
| <script setup lang="ts"> | ||||
| import type { VxeToolbarInstance } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { useContentMaximize, useRefresh } from '@vben/hooks'; | ||||
| import { Expand, MsRefresh, Search, TMinimize } from '@vben/icons'; | ||||
| 
 | ||||
| import { ElButton, ElTooltip } from 'element-plus'; | ||||
| 
 | ||||
| import { VxeToolbar } from '#/adapter/vxe-table'; | ||||
| 
 | ||||
| /** 列表工具栏封装 */ | ||||
| defineOptions({ name: 'TableToolbar' }); | ||||
| 
 | ||||
| const props = defineProps<{ | ||||
|   hiddenSearch: boolean; | ||||
| }>(); | ||||
| 
 | ||||
| const emits = defineEmits(['update:hiddenSearch']); | ||||
| 
 | ||||
| const toolbarRef = ref<VxeToolbarInstance>(); | ||||
| const { toggleMaximizeAndTabbarHidden, contentIsMaximize } = | ||||
|   useContentMaximize(); | ||||
| const { refresh } = useRefresh(); | ||||
| 
 | ||||
| /** 隐藏搜索栏 */ | ||||
| function onHiddenSearchBar() { | ||||
|   emits('update:hiddenSearch', !props.hiddenSearch); | ||||
| } | ||||
| 
 | ||||
| defineExpose({ | ||||
|   getToolbarRef: () => toolbarRef.value, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <VxeToolbar ref="toolbarRef" custom> | ||||
|     <template #toolPrefix> | ||||
|       <slot></slot> | ||||
|       <ElTooltip placement="bottom"> | ||||
|         <template #content> | ||||
|           <div class="max-w-[200px]">搜索</div> | ||||
|         </template> | ||||
|         <ElButton | ||||
|           class="ml-2 font-[8px]" | ||||
|           circle | ||||
|           @click="onHiddenSearchBar" | ||||
|         > | ||||
|           <Search :size="15" /> | ||||
|         </ElButton> | ||||
|       </ElTooltip> | ||||
|       <ElTooltip placement="bottom"> | ||||
|         <template #content> | ||||
|           <div class="max-w-[200px]">刷新</div> | ||||
|         </template> | ||||
|         <ElButton class="ml-2 font-[8px]" circle @click="refresh"> | ||||
|           <MsRefresh :size="15" /> | ||||
|         </ElButton> | ||||
|       </ElTooltip> | ||||
|       <ElTooltip placement="bottom"> | ||||
|         <template #content> | ||||
|           <div class="max-w-[200px]"> | ||||
|             {{ contentIsMaximize ? '还原' : '全屏' }} | ||||
|           </div> | ||||
|         </template> | ||||
|         <ElButton | ||||
|           class="ml-2 font-[8px]" | ||||
|           circle | ||||
|           @click="toggleMaximizeAndTabbarHidden" | ||||
|         > | ||||
|           <Expand v-if="!contentIsMaximize" :size="15" /> | ||||
|           <TMinimize v-else :size="15" /> | ||||
|         </ElButton> | ||||
|       </ElTooltip> | ||||
|     </template> | ||||
|   </VxeToolbar> | ||||
| </template> | ||||
|  | @ -0,0 +1 @@ | |||
| export * from './use-table-toolbar'; | ||||
|  | @ -0,0 +1,47 @@ | |||
| import type { VxeTableInstance, VxeToolbarInstance } from '#/adapter/vxe-table'; | ||||
| import type { TableToolbar } from '#/components/table-toolbar'; | ||||
| 
 | ||||
| import { ref, watch } from 'vue'; | ||||
| 
 | ||||
| /** | ||||
|  * vxe 原生工具栏挂载封装 | ||||
|  * 解决每个组件使用 vxe-table 组件时都需要写一遍的问题 | ||||
|  */ | ||||
| export function useTableToolbar() { | ||||
|   const hiddenSearchBar = ref(false); // 隐藏搜索栏
 | ||||
|   const tableToolbarRef = ref<InstanceType<typeof TableToolbar>>(); | ||||
|   const tableRef = ref<VxeTableInstance>(); | ||||
|   const isBound = ref<boolean>(false); | ||||
| 
 | ||||
|   /** 挂载 toolbar 工具栏 */ | ||||
|   async function bindTableToolbar() { | ||||
|     const table = tableRef.value; | ||||
|     const tableToolbar = tableToolbarRef.value; | ||||
|     if (table && tableToolbar) { | ||||
|       // 延迟 1 秒,确保 toolbar 组件已经挂载
 | ||||
|       setTimeout(async () => { | ||||
|         const toolbar = tableToolbar.getToolbarRef(); | ||||
|         if (!toolbar) { | ||||
|           console.error('[toolbar 挂载失败] Table toolbar not found'); | ||||
|         } | ||||
|         await table.connect(toolbar as VxeToolbarInstance); | ||||
|         isBound.value = true; | ||||
|       }, 1000); // 延迟挂载确保 toolbar 正确挂载
 | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   watch( | ||||
|     () => tableRef.value, | ||||
|     async (val) => { | ||||
|       if (!val || isBound.value) return; | ||||
|       await bindTableToolbar(); | ||||
|     }, | ||||
|     { immediate: true }, | ||||
|   ); | ||||
| 
 | ||||
|   return { | ||||
|     hiddenSearchBar, | ||||
|     tableToolbarRef, | ||||
|     tableRef, | ||||
|   }; | ||||
| } | ||||
|  | @ -0,0 +1,316 @@ | |||
| <script lang="ts" setup> | ||||
| import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; | ||||
| 
 | ||||
| import { h, onMounted, reactive, ref } from 'vue'; | ||||
| 
 | ||||
| import { Page, useVbenModal } from '@vben/common-ui'; | ||||
| import { Download, Plus, Trash2 } from '@vben/icons'; | ||||
| import { | ||||
|   cloneDeep, | ||||
|   downloadFileFromBlobPart, | ||||
|   formatDateTime, | ||||
|   isEmpty, | ||||
| } from '@vben/utils'; | ||||
| 
 | ||||
| import { | ||||
|   ElButton, | ||||
|   ElDatePicker, | ||||
|   ElForm, | ||||
|   ElFormItem, | ||||
|   ElInput, | ||||
|   ElLoading, | ||||
|   ElMessage, | ||||
|   ElOption, | ||||
|   ElPagination, | ||||
|   ElSelect, | ||||
| } from 'element-plus'; | ||||
| 
 | ||||
| import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; | ||||
| import { | ||||
|   deleteDemo01Contact, | ||||
|   deleteDemo01ContactList, | ||||
|   exportDemo01Contact, | ||||
|   getDemo01ContactPage, | ||||
| } from '#/api/infra/demo/demo01'; | ||||
| import { ContentWrap } from '#/components/content-wrap'; | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { TableToolbar } from '#/components/table-toolbar'; | ||||
| import { useTableToolbar } from '#/hooks'; | ||||
| import { $t } from '#/locales'; | ||||
| import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||
| 
 | ||||
| import Demo01ContactForm from './modules/form.vue'; | ||||
| 
 | ||||
| const loading = ref(true); // 列表的加载中 | ||||
| const list = ref<Demo01ContactApi.Demo01Contact[]>([]); // 列表的数据 | ||||
| 
 | ||||
| const total = ref(0); // 列表的总页数 | ||||
| const queryParams = reactive({ | ||||
|   pageNo: 1, | ||||
|   pageSize: 10, | ||||
|   name: undefined, | ||||
|   sex: undefined, | ||||
|   createTime: undefined, | ||||
| }); | ||||
| const queryFormRef = ref(); // 搜索的表单 | ||||
| const exportLoading = ref(false); // 导出的加载中 | ||||
| 
 | ||||
| /** 查询列表 */ | ||||
| const getList = async () => { | ||||
|   loading.value = true; | ||||
|   try { | ||||
|     const params = cloneDeep(queryParams) as any; | ||||
|     if (params.createTime && Array.isArray(params.createTime)) { | ||||
|       params.createTime = (params.createTime as string[]).join(','); | ||||
|     } | ||||
|     const data = await getDemo01ContactPage(params); | ||||
|     list.value = data.list; | ||||
|     total.value = data.total; | ||||
|   } finally { | ||||
|     loading.value = false; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| /** 搜索按钮操作 */ | ||||
| const handleQuery = () => { | ||||
|   queryParams.pageNo = 1; | ||||
|   getList(); | ||||
| }; | ||||
| 
 | ||||
| /** 重置按钮操作 */ | ||||
| const resetQuery = () => { | ||||
|   queryFormRef.value.resetFields(); | ||||
|   handleQuery(); | ||||
| }; | ||||
| 
 | ||||
| const [FormModal, formModalApi] = useVbenModal({ | ||||
|   connectedComponent: Demo01ContactForm, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 创建示例联系人 */ | ||||
| function handleCreate() { | ||||
|   formModalApi.setData({}).open(); | ||||
| } | ||||
| 
 | ||||
| /** 编辑示例联系人 */ | ||||
| function handleEdit(row: Demo01ContactApi.Demo01Contact) { | ||||
|   formModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 删除示例联系人 */ | ||||
| async function handleDelete(row: Demo01ContactApi.Demo01Contact) { | ||||
|   const loadingInstance = ElLoading.service({ | ||||
|     text: $t('ui.actionMessage.deleting', [row.id]), | ||||
|     background: 'rgba(0, 0, 0, 0.7)', | ||||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01Contact(row.id as number); | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.id])); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|     loadingInstance.close(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 批量删除示例联系人 */ | ||||
| async function handleDeleteBatch() { | ||||
|   const loadingInstance = ElLoading.service({ | ||||
|     text: $t('ui.actionMessage.deleting'), | ||||
|     background: 'rgba(0, 0, 0, 0.7)', | ||||
|   }); | ||||
|   try { | ||||
|     await deleteDemo01ContactList(checkedIds.value); | ||||
|     ElMessage.success($t('ui.actionMessage.deleteSuccess')); | ||||
|     await getList(); | ||||
|   } finally { | ||||
|     loadingInstance.close(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const checkedIds = ref<number[]>([]); | ||||
| function handleRowCheckboxChange({ | ||||
|   records, | ||||
| }: { | ||||
|   records: Demo01ContactApi.Demo01Contact[]; | ||||
| }) { | ||||
|   checkedIds.value = records.map((item) => item.id); | ||||
| } | ||||
| 
 | ||||
| /** 导出表格 */ | ||||
| async function onExport() { | ||||
|   try { | ||||
|     exportLoading.value = true; | ||||
|     const data = await exportDemo01Contact(queryParams); | ||||
|     downloadFileFromBlobPart({ fileName: '示例联系人.xls', source: data }); | ||||
|   } finally { | ||||
|     exportLoading.value = false; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** 初始化 */ | ||||
| const { hiddenSearchBar, tableToolbarRef, tableRef } = useTableToolbar(); | ||||
| onMounted(() => { | ||||
|   getList(); | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page auto-content-height> | ||||
|     <FormModal @success="getList" /> | ||||
| 
 | ||||
|     <ContentWrap v-if="!hiddenSearchBar"> | ||||
|       <!-- 搜索工作栏 --> | ||||
|       <ElForm :model="queryParams" ref="queryFormRef" inline> | ||||
|         <ElFormItem label="名字"> | ||||
|           <ElInput | ||||
|             v-model="queryParams.name" | ||||
|             placeholder="请输入名字" | ||||
|             clearable | ||||
|             @keyup.enter="handleQuery" | ||||
|             class="!w-[240px]" | ||||
|           /> | ||||
|         </ElFormItem> | ||||
|         <ElFormItem label="性别"> | ||||
|           <ElSelect | ||||
|             v-model="queryParams.sex" | ||||
|             placeholder="请选择性别" | ||||
|             clearable | ||||
|             class="!w-[240px]" | ||||
|           > | ||||
|             <ElOption | ||||
|               v-for="dict in getDictOptions( | ||||
|                 DICT_TYPE.SYSTEM_USER_SEX, | ||||
|                 'number', | ||||
|               )" | ||||
|               :key="dict.value" | ||||
|               :value="dict.value" | ||||
|               :label="dict.label" | ||||
|             /> | ||||
|           </ElSelect> | ||||
|         </ElFormItem> | ||||
|         <ElFormItem label="创建时间"> | ||||
|           <ElDatePicker | ||||
|             v-model="queryParams.createTime" | ||||
|             type="daterange" | ||||
|             value-format="YYYY-MM-DD" | ||||
|             range-separator="至" | ||||
|             start-placeholder="开始日期" | ||||
|             end-placeholder="结束日期" | ||||
|             class="!w-[240px]" | ||||
|           /> | ||||
|         </ElFormItem> | ||||
|         <ElFormItem> | ||||
|           <ElButton class="ml-2" @click="resetQuery"> 重置 </ElButton> | ||||
|           <ElButton class="ml-2" @click="handleQuery" type="primary"> | ||||
|             搜索 | ||||
|           </ElButton> | ||||
|         </ElFormItem> | ||||
|       </ElForm> | ||||
|     </ContentWrap> | ||||
| 
 | ||||
|     <!-- 列表 --> | ||||
|     <ContentWrap title="示例联系人"> | ||||
|       <template #extra> | ||||
|         <TableToolbar | ||||
|           ref="tableToolbarRef" | ||||
|           v-model:hidden-search="hiddenSearchBar" | ||||
|         > | ||||
|           <ElButton | ||||
|             class="ml-2" | ||||
|             :icon="h(Plus)" | ||||
|             type="primary" | ||||
|             @click="handleCreate" | ||||
|             v-access:code="['infra:demo01-contact:create']" | ||||
|           > | ||||
|             {{ $t('ui.actionTitle.create', ['示例联系人']) }} | ||||
|           </ElButton> | ||||
|           <ElButton | ||||
|             :icon="h(Download)" | ||||
|             type="primary" | ||||
|             class="ml-2" | ||||
|             :loading="exportLoading" | ||||
|             @click="onExport" | ||||
|             v-access:code="['infra:demo01-contact:export']" | ||||
|           > | ||||
|             {{ $t('ui.actionTitle.export') }} | ||||
|           </ElButton> | ||||
|           <ElButton | ||||
|             :icon="h(Trash2)" | ||||
|             type="danger" | ||||
|             class="ml-2" | ||||
|             :disabled="isEmpty(checkedIds)" | ||||
|             @click="handleDeleteBatch" | ||||
|             v-access:code="['infra:demo01-contact:delete']" | ||||
|           > | ||||
|             批量删除 | ||||
|           </ElButton> | ||||
|         </TableToolbar> | ||||
|       </template> | ||||
|       <VxeTable | ||||
|         ref="tableRef" | ||||
|         :data="list" | ||||
|         show-overflow | ||||
|         :loading="loading" | ||||
|         @checkbox-all="handleRowCheckboxChange" | ||||
|         @checkbox-change="handleRowCheckboxChange" | ||||
|       > | ||||
|         <VxeColumn type="checkbox" width="40" /> | ||||
|         <VxeColumn field="id" title="编号" align="center" /> | ||||
|         <VxeColumn field="name" title="名字" align="center" /> | ||||
|         <VxeColumn field="sex" title="性别" align="center"> | ||||
|           <template #default="{ row }"> | ||||
|             <DictTag :type="DICT_TYPE.SYSTEM_USER_SEX" :value="row.sex" /> | ||||
|           </template> | ||||
|         </VxeColumn> | ||||
|         <VxeColumn field="birthday" title="出生年" align="center"> | ||||
|           <template #default="{ row }"> | ||||
|             {{ formatDateTime(row.birthday) }} | ||||
|           </template> | ||||
|         </VxeColumn> | ||||
|         <VxeColumn field="description" title="简介" align="center" /> | ||||
|         <VxeColumn field="avatar" title="头像" align="center" /> | ||||
|         <VxeColumn field="createTime" title="创建时间" align="center"> | ||||
|           <template #default="{ row }"> | ||||
|             {{ formatDateTime(row.createTime) }} | ||||
|           </template> | ||||
|         </VxeColumn> | ||||
|         <VxeColumn field="operation" title="操作" align="center"> | ||||
|           <template #default="{ row }"> | ||||
|             <ElButton | ||||
|               size="small" | ||||
|               type="primary" | ||||
|               link | ||||
|               @click="handleEdit(row as any)" | ||||
|               v-access:code="['infra:demo01-contact:update']" | ||||
|             > | ||||
|               {{ $t('ui.actionTitle.edit') }} | ||||
|             </ElButton> | ||||
|             <ElButton | ||||
|               size="small" | ||||
|               type="danger" | ||||
|               link | ||||
|               class="ml-2" | ||||
|               @click="handleDelete(row as any)" | ||||
|               v-access:code="['infra:demo01-contact:delete']" | ||||
|             > | ||||
|               {{ $t('ui.actionTitle.delete') }} | ||||
|             </ElButton> | ||||
|           </template> | ||||
|         </VxeColumn> | ||||
|       </VxeTable> | ||||
|       <!-- 分页 --> | ||||
|       <div class="mt-2 flex justify-end"> | ||||
|         <ElPagination | ||||
|           :total="total" | ||||
|           v-model:current-page="queryParams.pageNo" | ||||
|           v-model:page-size="queryParams.pageSize" | ||||
|           :page-sizes="[10, 20, 50, 100]" | ||||
|           layout="total, sizes, prev, pager, next, jumper" | ||||
|           @size-change="getList" | ||||
|           @current-change="getList" | ||||
|         /> | ||||
|       </div> | ||||
|     </ContentWrap> | ||||
|   </Page> | ||||
| </template> | ||||
|  | @ -0,0 +1,145 @@ | |||
| <script lang="ts" setup> | ||||
| import type { FormRules } from 'element-plus'; | ||||
| 
 | ||||
| import type { Demo01ContactApi } from '#/api/infra/demo/demo01'; | ||||
| 
 | ||||
| import { computed, reactive, ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { | ||||
|   ElDatePicker, | ||||
|   ElForm, | ||||
|   ElFormItem, | ||||
|   ElInput, | ||||
|   ElMessage, | ||||
|   ElRadio, | ||||
|   ElRadioGroup, | ||||
| } from 'element-plus'; | ||||
| 
 | ||||
| import { | ||||
|   createDemo01Contact, | ||||
|   getDemo01Contact, | ||||
|   updateDemo01Contact, | ||||
| } from '#/api/infra/demo/demo01'; | ||||
| import { Tinymce as RichTextarea } from '#/components/tinymce'; | ||||
| import { ImageUpload } from '#/components/upload'; | ||||
| import { $t } from '#/locales'; | ||||
| import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| 
 | ||||
| const formRef = ref(); | ||||
| const formData = ref<Partial<Demo01ContactApi.Demo01Contact>>({ | ||||
|   id: undefined, | ||||
|   name: undefined, | ||||
|   sex: undefined, | ||||
|   birthday: undefined, | ||||
|   description: undefined, | ||||
|   avatar: undefined, | ||||
| }); | ||||
| const rules = reactive<FormRules>({ | ||||
|   name: [{ required: true, message: '名字不能为空', trigger: 'blur' }], | ||||
|   sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }], | ||||
|   birthday: [{ required: true, message: '出生年不能为空', trigger: 'blur' }], | ||||
|   description: [{ required: true, message: '简介不能为空', trigger: 'blur' }], | ||||
| }); | ||||
| const getTitle = computed(() => { | ||||
|   return formData.value?.id | ||||
|     ? $t('ui.actionTitle.edit', ['示例联系人']) | ||||
|     : $t('ui.actionTitle.create', ['示例联系人']); | ||||
| }); | ||||
| 
 | ||||
| /** 重置表单 */ | ||||
| const resetForm = () => { | ||||
|   formData.value = { | ||||
|     id: undefined, | ||||
|     name: undefined, | ||||
|     sex: undefined, | ||||
|     birthday: undefined, | ||||
|     description: undefined, | ||||
|     avatar: undefined, | ||||
|   }; | ||||
|   formRef.value?.resetFields(); | ||||
| }; | ||||
| 
 | ||||
| const [Modal, modalApi] = useVbenModal({ | ||||
|   async onConfirm() { | ||||
|     await formRef.value?.validate(); | ||||
|     modalApi.lock(); | ||||
|     // 提交表单 | ||||
|     const data = formData.value as Demo01ContactApi.Demo01Contact; | ||||
|     try { | ||||
|       await (formData.value?.id | ||||
|         ? updateDemo01Contact(data) | ||||
|         : createDemo01Contact(data)); | ||||
|       // 关闭并提示 | ||||
|       await modalApi.close(); | ||||
|       emit('success'); | ||||
|       ElMessage.success($t('ui.actionMessage.operationSuccess')); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       resetForm(); | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     let data = modalApi.getData<Demo01ContactApi.Demo01Contact>(); | ||||
|     if (!data) { | ||||
|       return; | ||||
|     } | ||||
|     if (data.id) { | ||||
|       modalApi.lock(); | ||||
|       try { | ||||
|         data = await getDemo01Contact(data.id); | ||||
|       } finally { | ||||
|         modalApi.unlock(); | ||||
|       } | ||||
|     } | ||||
|     formData.value = data; | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal :title="getTitle"> | ||||
|     <ElForm | ||||
|       ref="formRef" | ||||
|       :model="formData" | ||||
|       :rules="rules" | ||||
|       label-width="120px" | ||||
|       label-position="right" | ||||
|     > | ||||
|       <ElFormItem label="名字" prop="name"> | ||||
|         <ElInput v-model="formData.name" placeholder="请输入名字" /> | ||||
|       </ElFormItem> | ||||
|       <ElFormItem label="性别" prop="sex"> | ||||
|         <ElRadioGroup v-model="formData.sex"> | ||||
|           <ElRadio | ||||
|             v-for="dict in getDictOptions(DICT_TYPE.SYSTEM_USER_SEX, 'number')" | ||||
|             :key="dict.value" | ||||
|             :label="dict.value" | ||||
|           > | ||||
|             {{ dict.label }} | ||||
|           </ElRadio> | ||||
|         </ElRadioGroup> | ||||
|       </ElFormItem> | ||||
|       <ElFormItem label="出生年" prop="birthday"> | ||||
|         <ElDatePicker | ||||
|           v-model="formData.birthday" | ||||
|           value-format="x" | ||||
|           placeholder="选择出生年" | ||||
|         /> | ||||
|       </ElFormItem> | ||||
|       <ElFormItem label="简介" prop="description"> | ||||
|         <RichTextarea v-model="formData.description" height="500px" /> | ||||
|       </ElFormItem> | ||||
|       <ElFormItem label="头像" prop="avatar"> | ||||
|         <ImageUpload v-model="formData.avatar" /> | ||||
|       </ElFormItem> | ||||
|     </ElForm> | ||||
|   </Modal> | ||||
| </template> | ||||
		Loading…
	
		Reference in New Issue
	
	 puhui999
						puhui999