Преглед изворни кода

feat(系统模块-租户套餐): 租户套餐新增功能:设置菜单

shizhongming пре 2 година
родитељ
комит
d734921908

+ 88 - 0
src/modules/smart-system/views/tenant/tenantPackage/SysTenantPackageListView.api.ts

@@ -0,0 +1,88 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/sys/tenant/package/list',
+  getById = '/sys/tenant/package/getById',
+  batchSaveUpdate = '/sys/tenant/package/saveUpdateBatch',
+  delete = '/sys/tenant/package/batchDeleteById',
+  setUseYn = '/sys/tenant/package/setUseYn',
+  listFunction = 'sys/function/list',
+  listFunctionId = '/sys/tenant/package/listFunctionId',
+  savePackageFunction = '/sys/tenant/package/savePackageFunction',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const batchSaveUpdateApi = (modelList: any[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.batchSaveUpdate,
+    data: modelList,
+  });
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.id),
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.getById,
+    data: id,
+  });
+};
+
+/**
+ * 启用停用接口
+ * @param rows 选中的数据
+ * @param useYn 启用停用
+ */
+export const setUseYnApi = (rows: any[], useYn: boolean) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.setUseYn,
+    data: {
+      idList: rows.map((item) => item.id),
+      useYn,
+    },
+  });
+};
+
+export const listFunctionApi = (data: Recordable) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: 'sys/function/list',
+    data,
+  });
+};
+
+export const listFunctionIdApi = (packageId: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.listFunctionId,
+    params: {
+      id: packageId,
+    },
+  });
+};
+
+export const savePackageFunctionApi = (data: Recordable) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_SYSTEM,
+    url: Api.savePackageFunction,
+    data,
+  });
+};

+ 172 - 0
src/modules/smart-system/views/tenant/tenantPackage/SysTenantPackageListView.config.ts

@@ -0,0 +1,172 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+import { getUseYnSelectOptions } from '@/utils/form';
+import { tableUseYnClass } from '@/components/SmartTable';
+
+export enum Permission {
+  save = 'sys:tenant:package:save',
+  delete = 'sys:tenant:package:delete',
+  update = 'sys:tenant:package:update',
+  setUseYn = 'sys:tenant:package:setUseYn',
+}
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+      field: 'checkbox',
+    },
+    {
+      field: 'packageCode',
+      sortable: true,
+      title: '{system.views.tenant.package.title.packageCode}',
+      width: 120,
+    },
+    {
+      field: 'packageName',
+      title: '{system.views.tenant.package.title.packageName}',
+      width: 120,
+    },
+    {
+      field: 'effectTime',
+      sortable: true,
+      title: '{system.views.tenant.package.title.effectTime}',
+      width: 165,
+    },
+    {
+      field: 'expireTime',
+      sortable: true,
+      title: '{system.views.tenant.package.title.expireTime}',
+      width: 165,
+    },
+    {
+      field: 'remark',
+      title: '{common.table.remark}',
+      minWidth: 120,
+    },
+    {
+      field: 'seq',
+      sortable: true,
+      title: '{common.table.seq}',
+      width: 120,
+    },
+    {
+      ...tableUseYnClass(),
+      field: 'useYn',
+      sortable: true,
+    },
+    {
+      field: 'createTime',
+      title: '{common.table.createTime}',
+      width: 165,
+    },
+    {
+      field: 'createBy',
+      title: '{common.table.createUser}',
+      width: 120,
+    },
+    {
+      field: 'updateTime',
+      title: '{common.table.updateTime}',
+      width: 165,
+    },
+    {
+      field: 'updateBy',
+      title: '{common.table.updateUser}',
+      width: 120,
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'id',
+      show: false,
+      label: t('system.views.tenant.package.title.id'),
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'packageCode',
+      label: t('system.views.tenant.package.title.packageCode'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'packageName',
+      label: t('system.views.tenant.package.title.packageName'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'times',
+      label: t('system.views.tenant.package.title.effectTime'),
+      component: 'RangePicker',
+      componentProps: {
+        showTime: true,
+      },
+    },
+    {
+      field: 'seq',
+      label: t('common.table.seq'),
+      component: 'InputNumber',
+      componentProps: {},
+      defaultValue: 1,
+    },
+    {
+      field: 'remark',
+      label: t('common.table.remark'),
+      component: 'InputTextArea',
+      componentProps: {},
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'packageCode',
+      label: t('system.views.tenant.package.title.packageCode'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'packageName',
+      label: t('system.views.tenant.package.title.packageName'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'times',
+      label: t('system.views.tenant.package.title.effectTime'),
+      component: 'RangePicker',
+      componentProps: {
+        showTime: true,
+        style: { width: '320px' },
+      },
+    },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Select',
+      componentProps: {
+        options: getUseYnSelectOptions(),
+        style: { width: '100px' },
+      },
+      searchSymbol: '=',
+      defaultValue: 1,
+    },
+  ];
+};

+ 148 - 0
src/modules/smart-system/views/tenant/tenantPackage/SysTenantPackageListView.vue

@@ -0,0 +1,148 @@
+<template>
+  <div class="full-height page-container">
+    <SmartLayoutSeparate draggable class="full-height" second-size="240px">
+      <template #first>
+        <SmartTable
+          @register="registerTable"
+          :size="getTableSize"
+          @current-change="handleCurrentChange"
+        />
+      </template>
+      <template #second>
+        <TenantPackageSetFunction :tenant-package-id="currentPackage?.id" />
+      </template>
+    </SmartLayoutSeparate>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  import { SmartTable, useSmartTable } from '@/components/SmartTable';
+  import { SmartLayoutSeparate } from '@/components/SmartLayoutSeparate';
+
+  import TenantPackageSetFunction from './components/TenantPackageSetFunction.vue';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+    Permission,
+  } from './SysTenantPackageListView.config';
+  import {
+    listApi,
+    deleteApi,
+    setUseYnApi,
+    getByIdApi,
+    batchSaveUpdateApi,
+  } from './SysTenantPackageListView.api';
+  import { ref } from 'vue';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const currentPackage = ref<Recordable | null>(null);
+  const handleCurrentChange = ({ row }) => {
+    currentPackage.value = row;
+  };
+
+  const [registerTable] = useSmartTable({
+    columns: getTableColumns(),
+    height: 'auto',
+    border: true,
+    sortConfig: {
+      remote: true,
+      defaultSort: {
+        field: 'seq',
+        order: 'asc',
+      },
+    },
+    showOverflow: 'tooltip',
+    rowConfig: {
+      isHover: true,
+      isCurrent: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    pagerConfig: true,
+    useSearchForm: true,
+    searchFormConfig: {
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+      compact: true,
+    },
+    addEditConfig: {
+      formConfig: {
+        schemas: getFormSchemas(t),
+        colon: true,
+        baseColProps: { span: 24 },
+        labelCol: { span: 5 },
+        wrapperCol: { span: 18 },
+      },
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => {
+          currentPackage.value = null;
+          return listApi(params.ajaxParameter);
+        },
+        save: ({ body: { insertRecords, updateRecords } }) => {
+          const dataList = [...insertRecords, ...updateRecords];
+          dataList.forEach((item) => {
+            const times = item.times as Array<Date> | undefined;
+            if (times && times.length > 0) {
+              item.effectTime = times[0];
+              item.expireTime = times[1];
+            }
+          });
+          return batchSaveUpdateApi(dataList);
+        },
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: async (params) => {
+          const data = await getByIdApi(params.id);
+          if (data && data.effectTime && data.expireTime) {
+            data.times = [data.effectTime, data.expireTime];
+          }
+          return data;
+        },
+        useYn: setUseYnApi,
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      column: {
+        columnOrder: true,
+      },
+      buttons: [
+        {
+          code: 'ModalAdd',
+          auth: Permission.save,
+        },
+        {
+          code: 'ModalEdit',
+          auth: Permission.update,
+        },
+        {
+          code: 'delete',
+          auth: Permission.delete,
+        },
+        {
+          code: 'useYnTrue',
+          auth: Permission.setUseYn,
+        },
+        {
+          code: 'useYnFalse',
+          auth: Permission.setUseYn,
+        },
+      ],
+    },
+  });
+</script>

+ 151 - 0
src/modules/smart-system/views/tenant/tenantPackage/components/TenantPackageSetFunction.vue

@@ -0,0 +1,151 @@
+<template>
+  <a-layout class="full-height" :class="prefixCls">
+    <a-layout-header class="header">
+      <h3>{{ t('system.views.tenant.package.title.setFunction') }}</h3>
+    </a-layout-header>
+    <Divider style="margin: 0" />
+    <a-layout-content class="content">
+      <Spin :spinning="dataLoading">
+        <BasicTree
+          ref="treeRef"
+          checkable
+          :treeData="functionTreeData"
+          v-model:checkedKeys="checkedKeysModel"
+        />
+      </Spin>
+    </a-layout-content>
+
+    <Divider style="margin: 0" />
+    <a-layout-footer class="footer">
+      <div style="padding: 0 5px">
+        <a-button :loading="saveLoading" block type="primary" @click="handleSave">
+          {{ $t('common.button.save') }}
+        </a-button>
+      </div>
+    </a-layout-footer>
+  </a-layout>
+</template>
+
+<script setup lang="ts">
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { Divider, Spin } from 'ant-design-vue';
+  import { useDesign } from '@/hooks/web/useDesign';
+  import { onMounted, ref, unref, watch } from 'vue';
+  import { BasicTree } from '@/components/Tree';
+  import {
+    listFunctionApi,
+    listFunctionIdApi,
+    savePackageFunctionApi,
+  } from '../SysTenantPackageListView.api';
+  import TreeUtils from '@/utils/TreeUtils';
+  import { propTypes } from '@/utils/propTypes';
+  import { errorMessage, successMessage } from '@/utils/message/SystemNotice';
+
+  const props = defineProps({
+    tenantPackageId: propTypes.number,
+  });
+
+  const treeRef = ref();
+
+  const dataLoading = ref(false);
+  const saveLoading = ref(false);
+
+  const { t } = useI18n();
+
+  const { prefixCls } = useDesign('system-tenant-package-setFunction');
+
+  onMounted(() => loadFunctionTreeData());
+  watch(
+    () => props.tenantPackageId,
+    () => loadPackageFunctionIds(),
+  );
+
+  const functionTreeData = ref<Array<any>>([]);
+  const checkedKeysModel = ref([]);
+
+  const loadFunctionTreeData = async () => {
+    dataLoading.value = true;
+    try {
+      const functionList = await listFunctionApi({
+        sortName: 'seq',
+      });
+      functionTreeData.value =
+        TreeUtils.convertList2Tree(
+          functionList.map(({ functionId, functionName, parentId }: any) => {
+            return {
+              key: functionId,
+              title: functionName,
+              parentId: parentId,
+            };
+          }),
+          (item) => item.key,
+          (item) => item.parentId,
+          0,
+        ) || [];
+    } finally {
+      dataLoading.value = false;
+    }
+  };
+
+  const loadPackageFunctionIds = async () => {
+    const tenantPackageId = props.tenantPackageId;
+    if (!tenantPackageId) {
+      checkedKeysModel.value = [];
+    } else {
+      dataLoading.value = true;
+      try {
+        checkedKeysModel.value = await listFunctionIdApi(tenantPackageId);
+      } finally {
+        dataLoading.value = false;
+      }
+    }
+  };
+
+  /**
+   * 保存操作
+   */
+  const handleSave = async () => {
+    const tenantPackageId = props.tenantPackageId;
+    if (!tenantPackageId) {
+      errorMessage(t('system.views.tenant.package.message.chosePackage'));
+      return false;
+    }
+    const tree = unref(treeRef);
+    saveLoading.value = true;
+    try {
+      await savePackageFunctionApi({
+        tenantPackageId,
+        functionIdList: tree.getAntInstance().checkedKeys,
+        halfFunctionIdList: tree.getAntInstance().halfCheckedKeys,
+      });
+      successMessage(t('system.views.tenant.package.message.saveFunctionSuccess'));
+    } finally {
+      saveLoading.value = false;
+    }
+  };
+</script>
+
+<style lang="less">
+  @prefix-cls: ~'@{namespace}-system-tenant-package-setFunction';
+
+  .@{prefix-cls} {
+    .header {
+      height: 48px;
+      background: white;
+      line-height: 48px;
+      text-align: center;
+    }
+
+    .content {
+      overflow: auto;
+      background: white;
+    }
+
+    .footer {
+      height: 50px;
+      padding: 10px 0;
+      background: white;
+      text-align: center;
+    }
+  }
+</style>

+ 39 - 0
src/modules/smart-system/views/tenant/tenantPackage/lang/zh_CN.ts

@@ -0,0 +1,39 @@
+/**
+ * 租户产品套餐 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'system.views.tenant.package',
+  data: {
+    title: {
+      packageCode: '产品包编码',
+      packageName: '产品包名',
+      effectTime: '生效时间',
+      expireTime: '过期时间',
+      createBy: 'createBy',
+      updateBy: 'updateBy',
+      deleteKey: 'deleteKey',
+      deleteTime: 'deleteTime',
+      deleteBy: 'deleteBy',
+      deleteUserId: 'deleteUserId',
+      setFunction: '套餐功能',
+    },
+    validate: {
+      packageCode: '请输入产品包编码',
+      packageName: '请输入产品包名',
+      effectTime: '请输入生效时间',
+      expireTime: '请输入过期时间',
+    },
+    rules: {},
+    search: {
+      packageCode: '请输入产品包编码',
+      packageName: '请输入产品包名',
+      effectTime: '请输入生效时间',
+      expireTime: '请输入过期时间',
+    },
+    message: {
+      chosePackage: '请先选择租户套餐',
+      saveFunctionSuccess: '设置菜单成功',
+    },
+  },
+};