diff --git a/src/models/index.ts b/src/models/index.ts index eb009b2..edcbdc6 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -3,6 +3,7 @@ export * from './product.data.ts' export * from './role.data.ts' export * from './position.data.ts' export * from './department.data.ts' +export * from './tenant.data.ts' export interface ResponseDTO{ code: number; diff --git a/src/models/tenant.data.ts b/src/models/tenant.data.ts new file mode 100644 index 0000000..1d23e42 --- /dev/null +++ b/src/models/tenant.data.ts @@ -0,0 +1,49 @@ +export interface TenantVO { + id: number + name: string + contactName: string + contactMobile: string + status: number + domain: string + packageId: number + username: string + password: string + expireTime: Date + accountCount: number + createTime: Date +} + +export interface TenantPageReqVO extends PageParam { + name?: string + contactName?: string + contactMobile?: string + status?: number + createTime?: Date[] +} + +export interface TenantExportReqVO { + name?: string + contactName?: string + contactMobile?: string + status?: number + createTime?: Date[] +} + +export interface TenantPackageVO { + id: number + name: string + status: number + remark: string + creator: string + updater: string + updateTime: string + menuIds: number[] + createTime: Date +} + +export interface TenantPackagePageReqVO extends PageParam { + name?: string + status?: number + remark?: string + createTime?: Date[] +} diff --git a/src/pages/system/dept/create-department.tsx b/src/pages/system/dept/create-department.tsx index 72383ed..0a8783c 100644 --- a/src/pages/system/dept/create-department.tsx +++ b/src/pages/system/dept/create-department.tsx @@ -66,6 +66,7 @@ const DepartmentEditor: React.FC = (props) => { width={640} onOk={save} onCancel={() => onCancel()} + confirmLoading= {saveLoading} destroyOnClose >
= (props) => { width={640} onOk={save} onCancel={() => onCancel()} + confirmLoading= {saveLoading} destroyOnClose > = (props) => { width={640} onOk={save} onCancel={() => onCancel()} + confirmLoading= {saveLoading} destroyOnClose > = (props) => { width={640} onOk={save} onCancel={() => onCancel()} + confirmLoading= {saveLoading} destroyOnClose > { + + const [editorVisable, seEditorVisable] = useState(false); + const [editData, seEditData] = useState(); + const [dataSource, setDataSource] = useState([]); + const [searchFrom] = Form.useForm(); + + + const { runAsync: getPageApi } = useRequest(tenantService.getTenantPageApi, { manual: true }); + const { runAsync: deleteApi } = useRequest(tenantService.deleteTenantApi, { manual: true }); + + const load = async () => { + const [error, { data }] = await getPageApi(searchFrom.getFieldsValue()); + if (!error) { + setDataSource(data.list); + } + }; + + + const showDeleteConfirm = (data: TenantVO) => { + antdUtils.modal?.confirm({ + title: '确认要将该菜单删除吗?', + icon: , + content: '请注意删除以后不可恢复!', + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk() { + return new Promise(async (resolve) => { + const [error, { code, msg }] = await deleteApi(data.id); + if (error || code !== 0) { + antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) + } else { + antdUtils.message?.open({ type: 'success', content: '删除成功' }) + } + await load(); + resolve('') + }).catch(() => antdUtils.message?.open({ + type: 'error', + content: '操作失败', + })); + }, + onCancel() { + }, + }); + }; + + + + + + + + + + + const columns: TableColumnsType = [ + { + title: '租户编号', + dataIndex: 'id', + key: 'id', + align: 'right', + width: 100, + }, + { + title: '租户名称', + dataIndex: 'name', + key: 'name', + align: 'center', + width: 150, + }, + { + title: '租户套餐', + dataIndex: 'packageId', + key: 'packageId', + align: 'center', + width: 100, + }, + { + title: '联系人', + dataIndex: 'contactName', + key: 'contactName', + align: 'center', + width: 150 + }, + { + title: '联系手机', + dataIndex: 'contactMobile', + key: 'contactMobile', + align: 'center', + width: 150 + }, + { + title: '账号额度', + dataIndex: 'accountCount', + key: 'accountCount', + align: 'center', + width: 150 + }, + { + title: '过期时间', + key: 'expireTime', + dataIndex: 'expireTime', + width: 200, + align: 'center', + render: (value: number) => { + return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") + } + }, + { + title: '绑定域名', + dataIndex: 'domain', + key: 'domain', + align: 'center', + width: 200 + }, + { + title: '租户状态', + key: 'status', + dataIndex: 'status', + width: 100, + align: 'center', + render: (value: number) => { + return (value === 0 ? : ) + } + }, + { + title: '创建时间', + key: 'createTime', + dataIndex: 'createTime', + width: 200, + render: (value: number) => { + return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") + } + }, + { + title: t("QkOmYwne" /* 操作 */), + key: 'action', + render: (value: TenantVO, record) => ( + )}> + { + seEditData(value); + seEditorVisable(true); + }}> + 编辑 + + { + showDeleteConfirm(value) + }}> + 删除 + + + ), + }, + ]; + + useEffect(() => { + load(); + }, []); + + const onReset = () => { + searchFrom.resetFields() + load() + } + + return ( + <> +
+ +
+
+ + + + + + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + + { + load(); + seEditorVisable(false); + }} + onCancel={() => { seEditorVisable(false) }} + visible={editorVisable} + data={editData} /> + + ); +}; + diff --git a/src/pages/system/tenant/list/tenant-editor.tsx b/src/pages/system/tenant/list/tenant-editor.tsx new file mode 100644 index 0000000..ea8b813 --- /dev/null +++ b/src/pages/system/tenant/list/tenant-editor.tsx @@ -0,0 +1,194 @@ +import React, { useEffect, useState } from 'react' +import { Form, Input, InputNumber, Radio, Modal, Select, DatePicker } from 'antd'; +import tenantService from '@/request/service/tenant'; +import tenantPkgService from '@/request/service/tenant-package'; +import { useRequest } from '@/hooks/use-request'; +import type { TenantVO } from '@/models' +import { antdUtils } from '@/utils/antd'; +import customParseFormat from 'dayjs/plugin/customParseFormat'; + +import dayjs from 'dayjs'; +dayjs.extend(customParseFormat); + + +const layout = { + labelCol: { span: 4, }, + wrapperCol: { span: 16 } +}; + +interface EditorProps { + visible: boolean; + onCancel: (flag?: boolean) => void; + onSave: (role: TenantVO) => void; + data?: TenantVO | null; +} + + +const TenantEditor: React.FC = (props) => { + + const { visible, onCancel, onSave, data } = props; + + const { runAsync: updateApi } = useRequest(tenantService.updateTenantApi, { manual: true }); + const { runAsync: createApi } = useRequest(tenantService.createTenantApi, { manual: true }); + const { data: tenanPackages, run: getTenantPackageList } = useRequest(tenantPkgService.getTenantPackageList, { manual: true }); + + const isEdit = !!data; + + + const [saveLoading, setSaveLoading] = useState(false); + const [form] = Form.useForm(); + + useEffect(() => { + if (visible) { + if (data) { + form.setFieldsValue(data); + form.setFieldValue('expireTime', dayjs(data.expireTime)) + } + } else { + form.resetFields(); + } + }, [visible]); + + useEffect(() => { + getTenantPackageList() + }, [getTenantPackageList]) + + const save = async () => { + setSaveLoading(true); + const fieldValues = form.getFieldsValue(); + const expireTime = dayjs(form.getFieldValue('expireTime')).toDate().getTime() + const newValue = isEdit ? { ...data, ...fieldValues, expireTime } : {...fieldValues, expireTime}; + const [error, { msg, code }] = isEdit ? await updateApi(newValue) : await createApi(newValue); + if (!error && code === 0) { + onSave(newValue); + } else { + antdUtils.message?.open({ + type: 'error', + content: msg ?? '操作失败', + }); + } + setSaveLoading(false); + } + + return ( + <> + onCancel()} + confirmLoading= {saveLoading} + destroyOnClose + > +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + ) +} + +export default TenantEditor; diff --git a/src/pages/system/tenant/package/index.tsx b/src/pages/system/tenant/package/index.tsx new file mode 100644 index 0000000..3d44738 --- /dev/null +++ b/src/pages/system/tenant/package/index.tsx @@ -0,0 +1,184 @@ +import { Space, Table, Button, Input, Select, Divider, Tag, Card, Badge, Form } from 'antd'; +import type { TableColumnsType } from 'antd'; +import { t } from '@/utils/i18n'; +import React, { useState, useEffect } from 'react'; +import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } from '@ant-design/icons'; +import type { TenantPackageVO } from '@/models' +import { antdUtils } from '@/utils/antd'; +import { useRequest } from '@/hooks/use-request'; +import tenantPkgService from '@/request/service/tenant-package'; +import { formatDate } from '@/utils/formatTime' +import TenantPackageEditor from './tenant-pkg-editor'; + +export default () => { + + const [editorVisable, seEditorVisable] = useState(false); + const [editData, seEditData] = useState(); + const [dataSource, setDataSource] = useState([]); + const [searchFrom] = Form.useForm(); + + + const { runAsync: getPageApi } = useRequest(tenantPkgService.getTenantPackageTypePageApi, { manual: true }); + const { runAsync: deleteApi } = useRequest(tenantPkgService.deleteTenantPackageTypeApi, { manual: true }); + + const load = async () => { + const [error, { data }] = await getPageApi(searchFrom.getFieldsValue()); + if (!error) { + setDataSource(data.list); + } + }; + + + const showDeleteConfirm = (data: TenantPackageVO) => { + antdUtils.modal?.confirm({ + title: '确认要将该菜单删除吗?', + icon: , + content: '请注意删除以后不可恢复!', + okText: '删除', + okType: 'danger', + cancelText: '取消', + onOk() { + return new Promise(async (resolve) => { + const [error, { code, msg} ] = await deleteApi(data.id); + if(error || code !== 0) { + antdUtils.message?.open({ type: 'error', content: msg??'操作失败'}) + } else { + antdUtils.message?.open({ type: 'success', content: '删除成功'}) + } + await load(); + resolve('') + }).catch(() => antdUtils.message?.open({ + type: 'error', + content: '操作失败', + })); + }, + onCancel() { + }, + }); + }; + + const columns: TableColumnsType = [ + { + title: '套餐编号', + dataIndex: 'id', + key: 'id', + align: 'right', + width: 200, + }, + { + title: '套餐名称', + dataIndex: 'name', + key: 'name', + align: 'center', + width: 150 + }, + { + title: '备注', + key: 'remark', + dataIndex: 'remark', + align: 'center', + }, + { + title: '状态', + key: 'status', + dataIndex: 'status', + width: 100, + align: 'center', + render: (value: number) => { + return (value === 0 ? : ) + } + }, + { + title: '创建时间', + key: 'createTime', + dataIndex: 'createTime', + width: 200, + render: (value: number) => { + return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") + } + }, + { + title: t("QkOmYwne" /* 操作 */), + key: 'action', + render: (value: TenantPackageVO, record) => ( + )}> + { + seEditData(value); + seEditorVisable(true); + }}> + 编辑 + + { + showDeleteConfirm(value) + }}> + 删除 + + + ), + }, + ]; + + useEffect(() => { + load(); + }, []); + + const onReset = () => { + searchFrom.resetFields() + load() + } + + return ( + <> +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ +
+
+
+ +
+ + + { + load(); + seEditorVisable(false); + }} + onCancel={() => { seEditorVisable(false) }} + visible={editorVisable} + data={editData} /> + + ); +}; + diff --git a/src/pages/system/tenant/package/tenant-pkg-editor.tsx b/src/pages/system/tenant/package/tenant-pkg-editor.tsx new file mode 100644 index 0000000..127705b --- /dev/null +++ b/src/pages/system/tenant/package/tenant-pkg-editor.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useState } from 'react' +import { Form, Input, InputNumber, Radio, Modal, TreeSelect } from 'antd'; +import tenantPkgService from '@/request/service/tenant-package'; +import menuService from '@/request/service/menu'; +import { useRequest } from '@/hooks/use-request'; +import type { DefaultOptionType } from 'antd/es/select'; +import type { Menu, TenantPackageVO } from '@/models'; +import { antdUtils } from '@/utils/antd'; + +const layout = { + labelCol: { span: 4, }, + wrapperCol: { span: 16 }, + bordered: false, +}; + +interface EditorProps { + visible: boolean; + onCancel: (flag?: boolean) => void; + onSave: (role: TenantPackageVO) => void; + data?: TenantPackageVO | null; +} +const toMenuTree = (data: Menu[]) => { + const root: Menu[] = data.filter(item => { + return !data.some(it => it.id === item.parentId) + }); + const toMenuTreeItem = (item: Menu): DefaultOptionType => { + return { + label: item.name, + value: item.id, + children: data.filter(it => it.parentId === item.id).map(it => { + return toMenuTreeItem(it) + }) + } + } + return root.map(item => { + return toMenuTreeItem(item) + }) +} + +const TenantPackageEditor: React.FC = (props) => { + + const { visible, onCancel, onSave, data } = props; + + const { runAsync: updateApi } = useRequest(tenantPkgService.updateTenantPackageTypeApi, { manual: true }); + const { runAsync: createApi } = useRequest(tenantPkgService.createTenantPackageTypeApi, { manual: true }); + const { data: menus, run: getMenuList } = useRequest(menuService.listSimpleMenusApi, { manual: true }); + // const [value, setValue] = useState(['0-0-0']); + + // const onChange = (newValue: string[]) => { + // console.log('onChange ', value); + // setValue(newValue); + // }; + + const isEdit = !!data; + + const [saveLoading, setSaveLoading] = useState(false); + const [form] = Form.useForm(); + + useEffect(() => { + if (visible) { + if (data) { + form.setFieldsValue(data); + } + } else { + form.resetFields(); + } + }, [visible]); + + useEffect(() => { + getMenuList(); + }, [getMenuList]); + + const save = async () => { + setSaveLoading(true); + const fieldValues = form.getFieldsValue(); + const newValue = isEdit ? { ...data, ...fieldValues } : fieldValues; + const [error, { msg, code }] = isEdit ? await updateApi(newValue) : await createApi(newValue); + if (!error && code === 0) { + onSave(newValue); + } else { + antdUtils.message?.open({ + type: 'error', + content: msg ?? '操作失败', + }); + } + setSaveLoading(false); + } + + return ( + <> + onCancel()} + destroyOnClose + > +
+ + + + + + + + + + + + + + + + + +
+ + ) +} + +export default TenantPackageEditor; diff --git a/src/request/service/menu.ts b/src/request/service/menu.ts index 8fa1f8f..84f86db 100644 --- a/src/request/service/menu.ts +++ b/src/request/service/menu.ts @@ -7,9 +7,10 @@ export default { getMenuList: (params?: { name?: string, status?: number }) => { return request.get(`${BASE_URL}/list`, { params }); }, + // 获取菜单详情 getMenu: (id: number) => { - return request.get(`${BASE_URL}/get`, { params: { id }}) + return request.get(`${BASE_URL}/get`, { params: { id } }) }, // 新增菜单 @@ -24,5 +25,11 @@ export default { deleteMenu: (params: { id: number }) => { return request.delete(`${BASE_URL}/delete`, { params }) + }, + + // 查询菜单(精简)列表 + listSimpleMenusApi: () => { + return request.get(`${BASE_URL}/list-all-simple`); } + }; diff --git a/src/request/service/tenant-package.ts b/src/request/service/tenant-package.ts new file mode 100644 index 0000000..a7d03c2 --- /dev/null +++ b/src/request/service/tenant-package.ts @@ -0,0 +1,38 @@ +import request from '@/request'; +import { PageData, TenantPackagePageReqVO, TenantPackageVO } from '@/models'; + +const BASE_URL = '/admin-api/system/tenant-package'; + +export default { + // 查询租户套餐列表 + getTenantPackageTypePageApi: (params: TenantPackagePageReqVO) => { + return request.get>(`${BASE_URL}/page`, { params }); + }, + + // 获得租户 + getTenantPackageApi: (id: number) => { + return request.get(`${BASE_URL}/get?id=${id}`); + }, + + // 新增租户套餐 + createTenantPackageTypeApi: (data: TenantPackageVO) => { + return request.post(`${BASE_URL}/create`, data ); + }, + + // 修改租户套餐 + updateTenantPackageTypeApi: (data: TenantPackageVO) => { + return request.put(`${BASE_URL}/update`, data ); + }, + + // 删除租户套餐 + deleteTenantPackageTypeApi: (id: number) => { + return request.delete(`${BASE_URL}/delete?id=${id}`); + }, + + // 获取租户套餐精简信息列表 + getTenantPackageList: () => { + return request.get(`${BASE_URL}/get-simple-list`); + } +} + + diff --git a/src/request/service/tenant.ts b/src/request/service/tenant.ts new file mode 100644 index 0000000..2bfdaa5 --- /dev/null +++ b/src/request/service/tenant.ts @@ -0,0 +1,39 @@ +import request from '@/request'; +import { TenantPageReqVO, TenantVO, TenantExportReqVO, PageData } from '@/models'; + +const BASE_URL = '/admin-api/system/tenant'; + +export default { + // 查询租户列表 + getTenantPageApi: (params: TenantPageReqVO) => { + return request.get>(`${BASE_URL}/page`, { params }) + }, + + // 查询租户详情 + getTenantApi: (id: number) => { + return request.get(`${BASE_URL}/get?id=${id}`) + }, + + // 新增租户 + createTenantApi: (data: TenantVO) => { + return request.post(`${BASE_URL}/create`, data) + }, + + // 修改租户 + updateTenantApi: (data: TenantVO) => { + return request.put(`${BASE_URL}/update`, data) + }, + + // 删除租户 + deleteTenantApi: (id: number) => { + return request.delete(`${BASE_URL}/delete?id=${id}`) + }, + + // 导出租户 + exportTenantApi: (params: TenantExportReqVO) => { + return request.download(`${BASE_URL}/export-excel`, params) + }, + +} + +