diff --git a/src/api/system/tenantPackage/index.ts b/src/api/system/tenantPackage/index.ts index e431a9ee..37f7067a 100644 --- a/src/api/system/tenantPackage/index.ts +++ b/src/api/system/tenantPackage/index.ts @@ -20,27 +20,27 @@ export interface TenantPackagePageReqVO extends PageParam { } // 查询租户套餐列表 -export const getTenantPackageTypePageApi = (params: TenantPackagePageReqVO) => { +export const getTenantPackageTypePage = (params: TenantPackagePageReqVO) => { return request.get({ url: '/system/tenant-package/page', params }) } // 获得租户 -export const getTenantPackageApi = (id: number) => { +export const getTenantPackage = (id: number) => { return request.get({ url: '/system/tenant-package/get?id=' + id }) } // 新增租户套餐 -export const createTenantPackageTypeApi = (data: TenantPackageVO) => { +export const createTenantPackageType = (data: TenantPackageVO) => { return request.post({ url: '/system/tenant-package/create', data }) } // 修改租户套餐 -export const updateTenantPackageTypeApi = (data: TenantPackageVO) => { +export const updateTenantPackageType = (data: TenantPackageVO) => { return request.put({ url: '/system/tenant-package/update', data }) } // 删除租户套餐 -export const deleteTenantPackageTypeApi = (id: number) => { +export const deleteTenantPackageType = (id: number) => { return request.delete({ url: '/system/tenant-package/delete?id=' + id }) } // 获取租户套餐精简信息列表 diff --git a/src/types/auto-components.d.ts b/src/types/auto-components.d.ts index 480691fc..4b1eb7ef 100644 --- a/src/types/auto-components.d.ts +++ b/src/types/auto-components.d.ts @@ -52,7 +52,6 @@ declare module '@vue/runtime-core' { ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] - ElImage: typeof import('element-plus/es')['ElImage'] ElImageViewer: typeof import('element-plus/es')['ElImageViewer'] ElInput: typeof import('element-plus/es')['ElInput'] ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] @@ -73,10 +72,7 @@ declare module '@vue/runtime-core' { ElTabPane: typeof import('element-plus/es')['ElTabPane'] ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] - ElTimeline: typeof import('element-plus/es')['ElTimeline'] - ElTimelineItem: typeof import('element-plus/es')['ElTimelineItem'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] - ElTransfer: typeof import('element-plus/es')['ElTransfer'] ElTree: typeof import('element-plus/es')['ElTree'] ElTreeSelect: typeof import('element-plus/es')['ElTreeSelect'] ElUpload: typeof import('element-plus/es')['ElUpload'] diff --git a/src/views/system/tenantPackage/form.vue b/src/views/system/tenantPackage/form.vue new file mode 100644 index 00000000..e90222e7 --- /dev/null +++ b/src/views/system/tenantPackage/form.vue @@ -0,0 +1,199 @@ +<template> + <Dialog :title="modelTitle" v-model="modelVisible" width="50%"> + <el-form + ref="formRef" + :model="formData" + :rules="formRules" + label-width="80px" + v-loading="formLoading" + > + <el-form-item label="套餐名" prop="name"> + <el-input v-model="formData.name" placeholder="请输入套餐名" /> + </el-form-item> + <el-form-item label="菜单权限"> + <el-card class="cardHeight"> + <template #header> + 父子联动(选中父节点,自动选择子节点): + <el-switch + v-model="menuCheckStrictly" + inline-prompt + active-text="是" + inactive-text="否" + /> + 全选/全不选: + <el-switch + v-model="treeNodeAll" + inline-prompt + active-text="是" + inactive-text="否" + @change="handleCheckedTreeNodeAll" + /> + 全部展开/折叠: + <el-switch + v-model="menuExpand" + inline-prompt + active-text="展开" + inactive-text="折叠" + @change="handleCheckedTreeExpand" + /> + </template> + <el-tree + ref="treeRef" + node-key="id" + :check-strictly="!menuCheckStrictly" + show-checkbox + :props="defaultProps" + :data="menuOptions as any[]" + empty-text="加载中,请稍候" + /> + </el-card> + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-radio-group v-model="formData.status"> + <el-radio + v-for="dict in getDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="parseInt(dict.value)" + > + {{ dict.label }} + </el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="备注" prop="remark"> + <el-input v-model="formData.remark" placeholder="请输入备注" /> + </el-form-item> + </el-form> + <template #footer> + <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> + <el-button @click="modelVisible = false">取 消</el-button> + </template> + </Dialog> +</template> +<script setup lang="ts" name="TenantPackageForm"> +import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { CommonStatusEnum } from '@/utils/constants' +import type { FormRules } from 'element-plus' +import { defaultProps } from '@/utils/tree' +// 业务相关 +import * as TenantPackageApi from '@/api/system/tenantPackage' +import { getSimpleMenusList } from '@/api/system/menu' +import { ElTree } from 'element-plus' +import { handleTree } from '@/utils/tree' + +const { t } = useI18n() // 国际化 +const message = useMessage() // 消息弹窗 +const modelVisible = ref(false) // 弹窗的是否展示 +const modelTitle = ref('') // 弹窗的标题 +const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 +const formType = ref('') // 表单的类型:create - 新增;update - 修改 +const formData: Record<string, any> = ref<Record<string, any>>({ + id: null, + name: null, + remark: null, + menuIds: [], + status: CommonStatusEnum.ENABLE +}) +const formRules: FormRules = ref<FormRules>({ + name: [{ required: true, message: '套餐名不能为空', trigger: 'blur' }], + status: [{ required: true, message: '状态不能为空', trigger: 'blur' }], + menuIds: [{ required: true, message: '关联的菜单编号不能为空', trigger: 'blur' }] +}) +const formRef = ref() // 表单 Ref +const menuOptions = ref<any[]>([]) // 树形结构数据 +const menuCheckStrictly = ref(false) // 在显示复选框的情况下,是否严格的遵循父子不互相关联的做法,默认为 true +const menuExpand = ref(false) // 展开/折叠 +const treeRef = ref<InstanceType<typeof ElTree>>() // 树组件Ref +const treeNodeAll = ref(false) // 全选/全不选 + +// 全选/全不选 +const handleCheckedTreeNodeAll = () => { + treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) +} +// 全部(展开/折叠)TODO:for循环全部展开和折叠树组件数据 +const handleCheckedTreeExpand = () => { + const nodes = treeRef.value!.store.nodesMap + for (let node in nodes) { + nodes[node].expanded = !nodes[node].expanded + } +} + +/** 打开弹窗 */ +const open = async (type: string, id?: number) => { + modelVisible.value = true + modelTitle.value = t('action.' + type) + formType.value = type + resetForm() + // 加载Menu列表 + menuOptions.value = handleTree(await getSimpleMenusList()) + // 修改时,设置数据 + if (id) { + formLoading.value = true + try { + const res = await TenantPackageApi.getTenantPackage(id) + // 设置选中 + formData.value = res + // 设置选中 + res.menuIds?.forEach((item: any) => { + treeRef.value?.setChecked(item, true, false) + }) + } finally { + formLoading.value = false + } + } +} +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 + +/** 提交表单 */ +const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 +const submitForm = async () => { + // 校验表单 + if (!formRef) return + const valid = await formRef.value.validate() + if (!valid) return + // 提交请求 + formLoading.value = true + try { + const data = formData.value as unknown as TenantPackageApi.TenantPackageVO + data.menuIds = [ + ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), + ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) + ] + if (formType.value === 'create') { + await TenantPackageApi.createTenantPackageType(data) + message.success(t('common.createSuccess')) + } else { + await TenantPackageApi.updateTenantPackageType(data) + message.success(t('common.updateSuccess')) + } + modelVisible.value = false + // 发送操作成功的事件 + emit('success') + } finally { + formLoading.value = false + } +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + id: null, + name: null, + remark: null, + menuIds: [], + status: CommonStatusEnum.ENABLE + } + treeRef.value?.setCheckedNodes([]) + treeNodeAll.value = false + menuExpand.value = false + // 设置为非严格,继续使用半选中 + menuCheckStrictly.value = false + formRef.value?.resetFields() +} +</script> +<style lang="scss" scoped> +.cardHeight { + width: 100%; + max-height: 400px; + overflow-y: scroll; +} +</style> diff --git a/src/views/system/tenantPackage/index.vue b/src/views/system/tenantPackage/index.vue index 07ea39c6..955bf0da 100644 --- a/src/views/system/tenantPackage/index.vue +++ b/src/views/system/tenantPackage/index.vue @@ -1,187 +1,179 @@ <template> + <!-- 搜索 --> <ContentWrap> - <!-- 列表 --> - <XTable @register="registerTable"> - <template #toolbar_buttons> - <XButton - type="primary" - preIcon="ep:zoom-in" - :title="t('action.add')" - @click="handleCreate()" + <el-form + class="-mb-15px" + :model="queryParams" + ref="queryFormRef" + :inline="true" + label-width="68px" + > + <el-form-item label="套餐名" prop="name"> + <el-input + v-model="queryParams.name" + placeholder="请输入套餐名" + clearable + @keyup.enter="handleQuery" /> - </template> - <template #actionbtns_default="{ row }"> - <XTextButton preIcon="ep:edit" :title="t('action.edit')" @click="handleUpdate(row.id)" /> - <XTextButton preIcon="ep:delete" :title="t('action.del')" @click="deleteData(row.id)" /> - </template> - </XTable> - <XModal v-model="dialogVisible" :title="dialogTitle"> - <!-- 对话框(添加 / 修改) --> - <Form - v-if="['create', 'update'].includes(actionType)" - :schema="allSchemas.formSchema" - :rules="rules" - ref="formRef" - > - <template #menuIds> - <el-card class="cardHeight"> - <template #header> - <div class="card-header"> - 全选/全不选: - <el-switch - v-model="treeNodeAll" - inline-prompt - active-text="是" - inactive-text="否" - @change="handleCheckedTreeNodeAll()" - /> - </div> - </template> - <el-tree - ref="treeRef" - node-key="id" - show-checkbox - :props="defaultProps" - :data="menuOptions" - empty-text="加载中,请稍候" - /> - </el-card> - </template> - </Form> - <!-- 操作按钮 --> - <template #footer> - <!-- 按钮:保存 --> - <XButton - v-if="['create', 'update'].includes(actionType)" - type="primary" - :title="t('action.save')" - :loading="loading" - @click="submitForm()" + </el-form-item> + <el-form-item label="状态" prop="status"> + <el-select v-model="queryParams.status" placeholder="请选择状态" clearable> + <el-option + v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + <el-form-item label="创建时间" prop="createTime"> + <el-date-picker + v-model="queryParams.createTime" + style="width: 240px" + type="daterange" + value-format="YYYY-MM-DD HH:mm:ss" + start-placeholder="开始日期" + end-placeholder="结束日期" + class="!w-240px" /> - <!-- 按钮:关闭 --> - <XButton :loading="loading" :title="t('dialog.close')" @click="dialogVisible = false" /> - </template> - </XModal> + </el-form-item> + + <el-form-item> + <el-button @click="handleQuery"> + <Icon icon="ep:search" class="mr-5px" /> + 搜索 + </el-button> + <el-button @click="resetQuery"> + <Icon icon="ep:refresh" class="mr-5px" /> + 重置 + </el-button> + <el-button + type="primary" + @click="handleCreate('create')" + v-hasPermi="['system:tenant-package:create']" + > + <Icon icon="ep:plus" class="mr-5px" /> + 新增 + </el-button> + </el-form-item> + </el-form> </ContentWrap> + + <!-- 列表 --> + <ContentWrap> + <el-table v-loading="loading" :data="list" align="center"> + <el-table-column label="套餐编号" align="center" prop="id" width="120" /> + <el-table-column label="套餐名" align="center" prop="name" /> + <el-table-column label="状态" align="center" prop="status" width="100"> + <template #default="scope"> + <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /> + </template> + </el-table-column> + <el-table-column label="备注" align="center" prop="remark" /> + <el-table-column label="创建时间" align="center" prop="createTime" width="180"> + <template #default="scope"> + <span>{{ parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + <el-table-column label="操作" align="center" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-button + size="mini" + type="text" + icon="el-icon-edit" + @click="handleUpdate('update', scope.row.id)" + v-hasPermi="['system:tenant-package:update']" + >修改 + </el-button> + <el-button + size="mini" + type="text" + icon="el-icon-delete" + @click="handleDelete(scope.row.id)" + v-hasPermi="['system:tenant-package:delete']" + >删除 + </el-button> + </template> + </el-table-column> + </el-table> + <!-- 分页 --> + <Pagination + :total="total" + v-model:page="queryParams.pageNo" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </ContentWrap> + <TenantPackageForm ref="formRef" @success="getList" /> </template> <script setup lang="ts" name="TenantPackage"> -import { handleTree, defaultProps } from '@/utils/tree' -import type { FormExpose } from '@/components/Form' -import type { ElTree } from 'element-plus' +import TenantPackageForm from './form.vue' // 业务相关的 import -import { rules, allSchemas } from './tenantPackage.data' +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' +import { parseTime } from '@/utils/formatTime' import * as TenantPackageApi from '@/api/system/tenantPackage' -import { getSimpleMenusList } from '@/api/system/menu' -const { t } = useI18n() // 国际化 const message = useMessage() // 消息弹窗 +const { t } = useI18n() // 国际化 -const menuOptions = ref<any[]>([]) // 树形结构 -const menuExpand = ref(false) -const menuNodeAll = ref(false) -const treeRef = ref<InstanceType<typeof ElTree>>() -const treeNodeAll = ref(false) -const formRef = ref<FormExpose>() // 表单 Ref -const loading = ref(false) // 遮罩层 -const actionType = ref('') // 操作按钮的类型 -const dialogVisible = ref(false) // 是否显示弹出层 -const dialogTitle = ref('edit') // 弹出层标题 - -// 全选/全不选 -const handleCheckedTreeNodeAll = () => { - treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : []) -} - -const validateCategory = (rule: any, value: any, callback: any) => { - if (!treeRef.value!.getCheckedKeys().length) { - callback(new Error('该项为必填项')) - } else { - callback() - } -} -rules.menuIds = [{ required: true, validator: validateCategory, trigger: 'blur' }] - -const getTree = async () => { - const res = await getSimpleMenusList() - menuOptions.value = handleTree(res) -} - -const [registerTable, { reload, deleteData }] = useXTable({ - allSchemas: allSchemas, - getListApi: TenantPackageApi.getTenantPackageTypePageApi, - deleteApi: TenantPackageApi.deleteTenantPackageTypeApi +const loading = ref(true) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 列表的数据 +const queryParams: Record<string, any> = ref<Record<string, any>>({ + pageNo: 1, + pageSize: 10, + name: null, + status: null, + remark: null, + createTime: [] }) +const queryFormRef = ref() // 搜索的表单 +const formRef = ref() // 表单 Ref -// 设置标题 -const setDialogTile = (type: string) => { - dialogTitle.value = t('action.' + type) - actionType.value = type - dialogVisible.value = true +/** 查询列表 */ +const getList = () => { + loading.value = true + // 执行查询 + TenantPackageApi.getTenantPackageTypePage(queryParams.value).then((response) => { + list.value = response.list + total.value = response.total + loading.value = false + }) +} + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNo = 1 + getList() +} + +/** 重置按钮操作 */ +const resetQuery = () => { + // 表单重置 + queryFormRef.value?.resetFields() + getList() } // 新增操作 -const handleCreate = () => { - //重置菜单树 - unref(treeRef)?.setCheckedKeys([]) - menuExpand.value = false - menuNodeAll.value = false - setDialogTile('create') +const handleCreate = (type: string) => { + formRef.value.open(type) } // 修改操作 -const handleUpdate = async (rowId: number) => { - setDialogTile('update') - // 设置数据 - const res = await TenantPackageApi.getTenantPackageApi(rowId) - unref(formRef)?.setValues(res) - // 设置选中 - res.menuIds?.forEach((item: any) => { - unref(treeRef)?.setChecked(item, true, false) - }) +const handleUpdate = async (type: string, id?: number) => { + formRef.value.open(type, id) } - -// 提交按钮 -const submitForm = async () => { - const elForm = unref(formRef)?.getElFormRef() - if (!elForm) return - elForm.validate(async (valid) => { - if (valid) { - loading.value = true - // 提交请求 - try { - const data = unref(formRef)?.formModel as TenantPackageApi.TenantPackageVO - data.menuIds = [ - ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), - ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) - ] - if (actionType.value === 'create') { - await TenantPackageApi.createTenantPackageTypeApi(data) - message.success(t('common.createSuccess')) - } else { - await TenantPackageApi.updateTenantPackageTypeApi(data) - message.success(t('common.updateSuccess')) - } - // 操作成功,重新加载列表 - dialogVisible.value = false - } finally { - loading.value = false - // 刷新列表 - await reload() - } - } - }) +/** 删除按钮操作 */ +const handleDelete = async (id: number) => { + try { + // 删除的二次确认 + await message.delConfirm() + // 发起删除 + await TenantPackageApi.deleteTenantPackageType(id) + message.success(t('common.delSuccess')) + // 刷新列表 + await getList() + } catch {} } - -// ========== 初始化 ========== -onMounted(async () => { - await getTree() -}) -// getList() +getList() </script> -<style scoped> -.cardHeight { - width: 100%; - max-height: 400px; - overflow-y: scroll; -} -</style> diff --git a/src/views/system/tenantPackage/tenantPackage.data.ts b/src/views/system/tenantPackage/tenantPackage.data.ts deleted file mode 100644 index d57dee29..00000000 --- a/src/views/system/tenantPackage/tenantPackage.data.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { VxeCrudSchema } from '@/hooks/web/useVxeCrudSchemas' -const { t } = useI18n() // 国际化 - -// 表单校验 -export const rules = reactive({ - name: [required], - id: [required], - type: [required], - remark: [required], - status: [required], - menuIds: [required] -}) - -// CrudSchema -const crudSchemas = reactive<VxeCrudSchema>({ - primaryKey: 'id', - primaryType: 'id', - primaryTitle: '套餐编号', - action: true, - columns: [ - { - title: '套餐名称', - field: 'name', - isSearch: true - }, - { - title: t('common.status'), - field: 'status', - dictType: DICT_TYPE.COMMON_STATUS, - dictClass: 'number', - isSearch: true - }, - { - title: '菜单权限', - field: 'menuIds', - isTable: false, - form: { - colProps: { - span: 24 - } - } - }, - { - title: t('form.remark'), - field: 'remark', - isTable: true, - isSearch: true, - form: { - component: 'Input', - componentProps: { - type: 'textarea', - rows: 4 - }, - colProps: { - span: 24 - } - } - }, - { - title: '创建时间', - field: 'createTime', - formatter: 'formatDate', - isForm: false, - search: { - show: true, - itemRender: { - name: 'XDataTimePicker' - } - } - } - ] -}) -export const { allSchemas } = useVxeCrudSchemas(crudSchemas)