Ver código fonte

feat(消息模块): 添加消息模块

shizhongming 2 anos atrás
pai
commit
6cf79dcbec

+ 2 - 0
src/components/Form/src/componentMap.ts

@@ -35,6 +35,7 @@ import { CountdownInput } from '@/components/CountDown';
 import { BasicTitle } from '@/components/Basic';
 import { CropperAvatar } from '@/components/Cropper';
 import SmartApiSelectDict from './smart-boot/components/SmartApiSelectDict.vue';
+import SmartUserTableSelect from './smart-boot/components/user/SmartUserTableSelect.vue';
 
 const componentMap = new Map<ComponentType | string, Component>();
 
@@ -81,6 +82,7 @@ componentMap.set('CropperAvatar', CropperAvatar);
 componentMap.set('BasicTitle', BasicTitle);
 
 componentMap.set('SmartApiSelectDict', SmartApiSelectDict);
+componentMap.set('SmartUserTableSelect', SmartUserTableSelect);
 
 export function add<T extends string, R extends Component>(
   compName: ComponentType | T,

+ 4 - 0
src/components/Form/src/types/index.ts

@@ -127,6 +127,9 @@ interface _CustomComponents {
   SmartApiSelectDict: ExtractPropTypes<
     (typeof import('@/components/Form/src/smart-boot/components/SmartApiSelectDict.vue'))['default']
   >;
+  SmartUserTableSelect: ExtractPropTypes<
+    (typeof import('@/components/Form/src/smart-boot/components/user/SmartUserTableSelect.vue'))['default']
+  >;
 }
 
 type CustomComponents<T = _CustomComponents> = {
@@ -177,4 +180,5 @@ export interface ComponentProps {
   CropperAvatar: CustomComponents['CropperAvatar'];
   BasicTitle: CustomComponents['BasicTitle'];
   SmartApiSelectDict: CustomComponents['SmartApiSelectDict'] & ComponentProps['ApiSelect'];
+  SmartUserTableSelect: CustomComponents['SmartUserTableSelect'];
 }

+ 3 - 0
src/components/Tinymce/src/Editor.vue

@@ -242,6 +242,9 @@
   }
 
   function setValue(editor: Record<string, any>, val?: string, prevVal?: string) {
+    if (val === undefined) {
+      val = '';
+    }
     if (
       editor &&
       typeof val === 'string' &&

+ 32 - 0
src/modules/message/SmartMessageConstants.ts

@@ -0,0 +1,32 @@
+export const getMessageTypeEnum = (t: Function) => {
+  return [
+    {
+      label: t('smart.message.systemMessage.form.messageType.announcement'),
+      value: 'ANNOUNCEMENT',
+    },
+    {
+      label: t('smart.message.systemMessage.form.messageType.systemMessage'),
+      value: 'SYSTEM_MESSAGE',
+    },
+  ];
+};
+
+export const getMessagePriorityEnum = (t: Function) => {
+  return [
+    {
+      label: t('smart.message.systemMessage.form.messagePriority.LOW'),
+      value: 'L',
+      color: 'green',
+    },
+    {
+      label: t('smart.message.systemMessage.form.messagePriority.MIDDLE'),
+      value: 'M',
+      color: 'orange',
+    },
+    {
+      label: t('smart.message.systemMessage.form.messagePriority.HIGH'),
+      value: 'H',
+      color: 'pink',
+    },
+  ];
+};

+ 82 - 0
src/modules/message/components/SystemMessageShowModal.vue

@@ -0,0 +1,82 @@
+<template>
+  <BasicModal :title="t('common.button.look')" @register="register" :minHeight="500" :width="800">
+    <template #footer>
+      <div></div>
+    </template>
+    <div>
+      <div>
+        <h2 class="message-title">{{ messageDataRef.title }}</h2>
+        <div class="sub-title" v-if="computedMessagePriority !== null">
+          <a-tag :color="computedMessagePriority.color">{{ computedMessagePriority.label }}</a-tag>
+          <span class="sub-title-sender">
+            {{ messageDataRef.sender?.fullName || messageDataRef.createBy }}
+          </span>
+          <span class="sub-title-sender">{{ messageDataRef.sendTime }}</span>
+        </div>
+      </div>
+      <div v-html="messageDataRef.content"></div>
+    </div>
+  </BasicModal>
+</template>
+
+<script setup lang="ts">
+  import { computed, ref, unref } from 'vue';
+
+  import { useModalInner, BasicModal } from '@/components/Modal';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { getDetailByIdApi } from '../views/SystemMessage/SmartMessageSystemListView.api';
+  import { getMessagePriorityEnum } from '@/modules/message/SmartMessageConstants';
+
+  const { t } = useI18n();
+
+  const messagePriorityEnum = getMessagePriorityEnum(t);
+  /**
+   * 优先级计算属性
+   */
+  const computedMessagePriority = computed(() => {
+    const enumList = messagePriorityEnum.filter(
+      (item) => item.value === unref(messageDataRef).priority,
+    );
+    if (enumList.length > 0) {
+      return enumList[0];
+    }
+    return null;
+  });
+
+  const messageDataRef = ref<any>({});
+
+  const [register, { changeLoading }] = useModalInner(async ({ id }) => {
+    try {
+      changeLoading(true);
+      messageDataRef.value = (await getDetailByIdApi(id)) || {};
+    } finally {
+      changeLoading(false);
+    }
+  });
+</script>
+
+<style lang="less" scoped>
+  .message-title {
+    margin: 0 0 14px;
+    font-size: 22px;
+    line-height: 1.4;
+  }
+
+  .sub-title {
+    margin-bottom: 22px;
+    font-size: 0;
+    line-height: 20px;
+    word-wrap: break-word;
+    hyphens: auto;
+    hyphens: auto;
+    hyphens: auto;
+  }
+
+  .sub-title-sender {
+    display: inline-block;
+    margin: 0 10px 10px 0;
+    font-size: 15px;
+    vertical-align: middle;
+    -webkit-tap-highlight-color: rgb(0 0 0 / 0%);
+  }
+</style>

+ 42 - 0
src/modules/message/views/MessageTemplate/SmartMessageTemplateListView.api.ts

@@ -0,0 +1,42 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/smart/message/template/list',
+  getById = '/smart/message/template/getById',
+  batchSaveUpdate = '/smart/message/template/saveUpdateBatch',
+  delete = '/smart/message/template/batchDeleteById',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const batchSaveUpdateApi = (modelList: any[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.batchSaveUpdate,
+    data: modelList,
+  });
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.id),
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.getById,
+    data: id,
+  });
+};

+ 137 - 0
src/modules/message/views/MessageTemplate/SmartMessageTemplateListView.config.ts

@@ -0,0 +1,137 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (): SmartColumn[] => {
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'id',
+      visible: false,
+      title: '',
+      width: 120,
+    },
+    {
+      field: 'templateCode',
+      title: '{smart.message.template.title.templateCode}',
+      fixed: 'left',
+      width: 120,
+    },
+    {
+      field: 'templateName',
+      title: '{smart.message.template.title.templateName}',
+      fixed: 'left',
+      width: 120,
+    },
+    {
+      field: 'templateContent',
+      title: '{smart.message.template.title.templateContent}',
+      minWidth: 200,
+    },
+    {
+      field: 'useYn',
+      component: 'booleanTag',
+      title: '{common.table.useYn}',
+      width: 120,
+    },
+    {
+      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,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'id',
+      show: false,
+      label: '',
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'templateCode',
+      label: t('smart.message.template.title.templateCode'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'templateName',
+      label: t('smart.message.template.title.templateName'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'templateContent',
+      label: t('smart.message.template.title.templateContent'),
+      slot: 'addEdit-templateContent',
+      componentProps: {
+        height: 600,
+        imageAction: defHttp.getApiUrlByService(ApiServiceEnum.SMART_FILE) + '/smart/file/upload',
+      },
+    },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Switch',
+      defaultValue: true,
+      componentProps: {},
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'templateCode',
+      label: t('smart.message.template.title.templateCode'),
+      component: 'Input',
+      searchSymbol: '=',
+    },
+    {
+      field: 'templateName',
+      label: t('smart.message.template.title.templateName'),
+      component: 'Input',
+      searchSymbol: '=',
+    },
+  ];
+};

+ 118 - 0
src/modules/message/views/MessageTemplate/SmartMessageTemplateListView.vue

@@ -0,0 +1,118 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction :actions="getActions(row)" />
+      </template>
+      <template #addEdit-templateContent="{ model }">
+        <Tinymce
+          v-model:value="model.templateContent"
+          :height="600"
+          :imageAction="
+            defHttp.getApiUrlByService(ApiServiceEnum.SMART_FILE) + '/smart/file/upload'
+          "
+        />
+      </template>
+    </SmartTable>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { Tinymce } from '@/components/Tinymce';
+
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+  } from './SmartMessageTemplateListView.config';
+  import {
+    listApi,
+    deleteApi,
+    getByIdApi,
+    batchSaveUpdateApi,
+  } from './SmartMessageTemplateListView.api';
+  import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.edit'),
+        onClick: () => editByRowModal(row),
+      },
+    ];
+  };
+
+  const [registerTable, { editByRowModal }] = useSmartTable({
+    columns: getTableColumns(),
+    height: 'auto',
+    border: true,
+    sortConfig: {
+      remote: true,
+    },
+    pagerConfig: true,
+    columnConfig: {
+      resizable: true,
+    },
+    showOverflow: 'tooltip',
+    useSearchForm: true,
+    searchFormConfig: {
+      compact: true,
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+      labelCol: {
+        span: 8,
+      },
+    },
+    addEditConfig: {
+      modalConfig: {
+        defaultFullscreen: true,
+      },
+      formConfig: {
+        schemas: getFormSchemas(t),
+        colon: true,
+        baseColProps: { span: 24 },
+        labelCol: { span: 3 },
+        wrapperCol: { span: 20 },
+      },
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => listApi(params.ajaxParameter),
+        save: ({ body: { insertRecords, updateRecords } }) =>
+          batchSaveUpdateApi([...insertRecords, ...updateRecords]),
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: (params) => getByIdApi(params.id),
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      custom: true,
+      buttons: [
+        {
+          code: 'ModalAdd',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>

+ 24 - 0
src/modules/message/views/MessageTemplate/lang/zh_CN.ts

@@ -0,0 +1,24 @@
+/**
+ * 消息模板表 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'smart.message.template',
+  data: {
+    title: {
+      templateCode: '模板编码',
+      templateName: '模板名称',
+      templateContent: '模板内容',
+    },
+    validate: {
+      templateCode: '请输入模板编码',
+      templateName: '请输入模板名称',
+      templateContent: '请输入模板内容',
+    },
+    rules: {},
+    search: {
+      templateCode: '请输入模板编码',
+      templateName: '请输入模板名称',
+    },
+  },
+};

+ 22 - 0
src/modules/message/views/SmartMyMessage/SmartMyMessageListView.api.ts

@@ -0,0 +1,22 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/smart/message/messageSend/pageCurrentUserMessage',
+  markAsRead = '/smart/message/messageSend/markAsRead',
+}
+
+export const pageCurrentUserMessageApi = (data) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.list,
+    data,
+  });
+};
+
+export const markAsReadApi = (data) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.markAsRead,
+    data,
+  });
+};

+ 140 - 0
src/modules/message/views/SmartMyMessage/SmartMyMessageListView.config.tsx

@@ -0,0 +1,140 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+
+import { getMessageTypeEnum, getMessagePriorityEnum } from '../../SmartMessageConstants';
+
+export const getTableColumns = (t: Function): SmartColumn[] => {
+  const messageTypeEnum = getMessageTypeEnum(t);
+  const messagePriorityEnum = getMessagePriorityEnum(t);
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'title',
+      title: '{smart.message.systemMessage.title.title}',
+      minWidth: 200,
+      fixed: 'left',
+    },
+    {
+      field: 'messageType',
+      title: '{smart.message.systemMessage.title.messageType}',
+      width: 120,
+      dynamicClass: ({ row }) => {
+        const messageType = row.messageType;
+        if (!messageType) {
+          return '';
+        }
+        return messageType === 'ANNOUNCEMENT'
+          ? 'text-color--success-bold'
+          : 'text-color--link-bold';
+      },
+      formatter: ({ row }) => {
+        const messageType = row.messageType;
+        if (!messageType) {
+          return '';
+        }
+        const enumList = messageTypeEnum.filter((item) => item.value === row.messageType);
+        if (enumList.length === 0) {
+          return '';
+        }
+        const data = enumList[0];
+        return data.label;
+      },
+    },
+    {
+      field: 'sendUserName',
+      title: '{smart.message.smartMyMessage.title.sendUserName}',
+      width: 120,
+    },
+    {
+      field: 'sendTime',
+      title: '{smart.message.smartMyMessage.title.sendTime}',
+      width: 165,
+      sortable: true,
+    },
+    {
+      field: 'priority',
+      title: '{smart.message.systemMessage.title.priority}',
+      width: 120,
+      sortable: true,
+      slots: {
+        default: ({ row }) => {
+          if (!row.priority) {
+            return '';
+          }
+          const enumList = messagePriorityEnum.filter((item) => item.value === row.priority);
+          if (enumList.length === 0) {
+            return '';
+          }
+          const data = enumList[0];
+          let color = 'pink';
+          if (data.value === 'MIDDLE') {
+            color = 'orange';
+          } else if (data.value === 'LOW') {
+            color = 'green';
+          }
+          return <a-tag color={color}>{data.label}</a-tag>;
+        },
+      },
+    },
+    {
+      field: 'readYn',
+      title: '{smart.message.smartMyMessage.title.readYn}',
+      width: 100,
+      autoClass: 'Boolean',
+      formatter: ({ row }) => {
+        return row.readYn === true ? t('common.form.yes') : t('common.form.no');
+      },
+      sortable: true,
+    },
+    {
+      field: 'readTime',
+      title: '{smart.message.smartMyMessage.title.readTime}',
+      width: 165,
+      sortable: true,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'title',
+      label: t('smart.message.systemMessage.title.title'),
+      component: 'Input',
+    },
+    {
+      field: 'readYn',
+      label: t('smart.message.smartMyMessage.title.readYn'),
+      component: 'Select',
+      searchSymbol: '=',
+      componentProps: {
+        style: {
+          width: '100px',
+        },
+        options: [
+          {
+            label: t('common.form.yes'),
+            value: 1,
+          },
+          {
+            label: t('common.form.no'),
+            value: 0,
+          },
+        ],
+      },
+    },
+  ];
+};

+ 129 - 0
src/modules/message/views/SmartMyMessage/SmartMyMessageListView.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction :actions="getActions(row)" />
+      </template>
+    </SmartTable>
+    <SystemMessageShowModal @register="registerShowMessage" />
+  </div>
+</template>
+
+<script setup lang="ts">
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+
+  import { getTableColumns, getSearchFormSchemas } from './SmartMyMessageListView.config';
+  import { pageCurrentUserMessageApi, markAsReadApi } from './SmartMyMessageListView.api';
+  import { createConfirm, successMessage, warnMessage } from '@/utils/message/SystemNotice';
+
+  import SystemMessageShowModal from '../../components/SystemMessageShowModal.vue';
+  import { useModal } from '@/components/Modal';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+
+  const [registerShowMessage, { openModal: openShowModal }] = useModal();
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    return [
+      {
+        label: t('common.button.look'),
+        onClick: () => openShowModal(true, { id: row.messageId }),
+      },
+    ];
+  };
+
+  /**
+   * 标记已读
+   */
+  const handleMarkRead = () => {
+    const selectRows = getCheckboxRecords(false);
+    const parameter: any = {};
+    if (selectRows.length === 0) {
+      parameter.markAll = true;
+    } else {
+      const noReadList = selectRows.filter((item) => item.readYn === false);
+      if (noReadList.length === 0) {
+        warnMessage({
+          message: t('smart.message.smartMyMessage.message.noRead'),
+        });
+        return false;
+      }
+      parameter.markAll = false;
+      parameter.messageIdList = noReadList.map((item) => item.id);
+    }
+    createConfirm({
+      iconType: 'warning',
+      content: t('smart.message.smartMyMessage.message.confirmMarkRead'),
+      onOk: async () => {
+        await markAsReadApi(parameter);
+        successMessage(t('smart.message.smartMyMessage.message.markReadSuccess'));
+        query();
+      },
+    });
+  };
+
+  const [registerTable, { getCheckboxRecords, query }] = useSmartTable({
+    id: 'smart-message-smart-my-message',
+    columns: getTableColumns(t),
+    border: true,
+    height: 'auto',
+    sortConfig: {
+      remote: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    checkboxConfig: {
+      highlight: true,
+    },
+    pagerConfig: true,
+    showOverflow: 'tooltip',
+    useSearchForm: true,
+    searchFormConfig: {
+      compact: true,
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      autoSubmitOnEnter: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      column: {
+        columnOrder: true,
+      },
+      refresh: true,
+      buttons: [
+        {
+          name: t('smart.message.smartMyMessage.button.markRead'),
+          customRender: 'ant',
+          props: {
+            preIcon: 'ant-design:highlight-outlined',
+            type: 'primary',
+            onClick: handleMarkRead,
+          },
+        },
+      ],
+    },
+    proxyConfig: {
+      ajax: {
+        query: ({ ajaxParameter }) => {
+          return pageCurrentUserMessageApi(ajaxParameter);
+        },
+      },
+    },
+  });
+</script>
+
+<style scoped lang="less"></style>

+ 30 - 0
src/modules/message/views/SmartMyMessage/lang/zh_CN.ts

@@ -0,0 +1,30 @@
+/**
+ * 系统消息表 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'smart.message.smartMyMessage',
+  data: {
+    title: {
+      sendUserName: '发送人',
+      sendTime: '发送时间',
+      readYn: '是否已读',
+      readTime: '阅读时间',
+    },
+    validate: {
+      receiveUserType: '请输入通知用户类型',
+    },
+    rules: {},
+    search: {},
+    form: {},
+    message: {
+      selectRows: '请选择至少一行记录',
+      noRead: '没有未读的消息',
+      confirmMarkRead: '确定要标记为已读吗?',
+      markReadSuccess: '标记已读成功',
+    },
+    button: {
+      markRead: '标记已读',
+    },
+  },
+};

+ 77 - 0
src/modules/message/views/SystemMessage/SmartMessageSystemListView.api.ts

@@ -0,0 +1,77 @@
+import { ApiServiceEnum, defHttp } from '@/utils/http/axios';
+
+enum Api {
+  list = '/smart/message/systemMessage/list',
+  getById = '/smart/message/systemMessage/getById',
+  batchSaveUpdate = '/smart/message/systemMessage/saveUpdateBatch',
+  delete = '/smart/message/systemMessage/batchDeleteById',
+  getDetailById = '/smart/message/systemMessage/getDetailById',
+  publish = '/smart/message/systemMessage/publish',
+  revoke = '/smart/message/systemMessage/revoke',
+}
+
+export const listApi = (params) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.list,
+    data: {
+      ...params,
+    },
+  });
+};
+
+export const batchSaveUpdateApi = (modelList: any[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.batchSaveUpdate,
+    data: modelList,
+  });
+};
+
+export const deleteApi = (removeRecords: Recordable[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.delete,
+    data: removeRecords.map((item) => item.id),
+  });
+};
+
+export const getByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.getById,
+    data: id,
+  });
+};
+
+export const getDetailByIdApi = (id: number) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.getDetailById,
+    data: id,
+  });
+};
+
+/**
+ * 发布API
+ * @param ids
+ */
+export const publishApi = (ids: number[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.publish,
+    data: ids,
+  });
+};
+
+/**
+ * 发布API
+ * @param ids
+ */
+export const revokeApi = (ids: number[]) => {
+  return defHttp.post({
+    service: ApiServiceEnum.SMART_MESSAGE,
+    url: Api.revoke,
+    data: ids,
+  });
+};

+ 360 - 0
src/modules/message/views/SystemMessage/SmartMessageSystemListView.config.tsx

@@ -0,0 +1,360 @@
+import type { SmartColumn, SmartSearchFormSchema } from '@/components/SmartTable';
+import type { FormSchema } from '@/components/Form';
+
+import { getMessageTypeEnum, getMessagePriorityEnum } from '../../SmartMessageConstants';
+
+export const getMessageSendStatusEnum = (t: Function) => {
+  return [
+    {
+      label: t('smart.message.systemMessage.form.sendStatus.NO_SEND'),
+      value: 'NO_SEND',
+    },
+    {
+      label: t('smart.message.systemMessage.form.sendStatus.SEND'),
+      value: 'SEND',
+    },
+    {
+      label: t('smart.message.systemMessage.form.sendStatus.CANCEL'),
+      value: 'CANCEL',
+    },
+  ];
+};
+
+export const getReceiveUserTypeEnum = (t: Function) => {
+  return [
+    {
+      label: t('smart.message.systemMessage.form.receiveUserType.ALL_USER'),
+      value: 'ALL_USER',
+    },
+    {
+      label: t('smart.message.systemMessage.form.receiveUserType.SPECIFY_USER'),
+      value: 'SPECIFY_USER',
+    },
+    {
+      label: t('smart.message.systemMessage.form.receiveUserType.BUSINESS_USER'),
+      value: 'BUSINESS_USER',
+    },
+  ];
+};
+
+/**
+ * 表格列表
+ */
+export const getTableColumns = (t: Function): SmartColumn[] => {
+  const messageTypeEnum = getMessageTypeEnum(t);
+  const sendStatusEnum = getMessageSendStatusEnum(t);
+  const messagePriorityEnum = getMessagePriorityEnum(t);
+  const receiveUserTypeEnum = getReceiveUserTypeEnum(t);
+  return [
+    {
+      type: 'checkbox',
+      width: 60,
+      align: 'center',
+      fixed: 'left',
+    },
+    {
+      field: 'title',
+      title: '{smart.message.systemMessage.title.title}',
+      width: 200,
+      fixed: 'left',
+    },
+    {
+      field: 'abstractContent',
+      title: '{smart.message.systemMessage.title.abstract}',
+      width: 120,
+    },
+    {
+      field: 'messageType',
+      title: '{smart.message.systemMessage.title.messageType}',
+      width: 120,
+      dynamicClass: ({ row }) => {
+        const messageType = row.messageType;
+        if (!messageType) {
+          return '';
+        }
+        return messageType === 'ANNOUNCEMENT'
+          ? 'text-color--success-bold'
+          : 'text-color--link-bold';
+      },
+      formatter: ({ row }) => {
+        const messageType = row.messageType;
+        if (!messageType) {
+          return '';
+        }
+        const enumList = messageTypeEnum.filter((item) => item.value === row.messageType);
+        if (enumList.length === 0) {
+          return '';
+        }
+        const data = enumList[0];
+        return data.label;
+      },
+    },
+    {
+      field: 'sendStatus',
+      title: '{smart.message.systemMessage.title.sendStatus}',
+      width: 120,
+      slots: {
+        default: ({ row }) => {
+          if (!row.sendStatus) {
+            return '';
+          }
+          const enumList = sendStatusEnum.filter((item) => item.value === row.sendStatus);
+          if (enumList.length === 0) {
+            return '';
+          }
+          const data = enumList[0];
+          let color = 'pink';
+          if (data.value === 'SEND') {
+            color = 'green';
+          } else if (data.value === 'CANCEL') {
+            color = 'grey';
+          }
+          return <a-tag color={color}>{data.label}</a-tag>;
+        },
+      },
+    },
+    {
+      field: 'priority',
+      title: '{smart.message.systemMessage.title.priority}',
+      width: 120,
+      slots: {
+        default: ({ row }) => {
+          if (!row.priority) {
+            return '';
+          }
+          const enumList = messagePriorityEnum.filter((item) => item.value === row.priority);
+          if (enumList.length === 0) {
+            return '';
+          }
+          const data = enumList[0];
+          let color = 'pink';
+          if (data.value === 'MIDDLE') {
+            color = 'orange';
+          } else if (data.value === 'LOW') {
+            color = 'green';
+          }
+          return <a-tag color={color}>{data.label}</a-tag>;
+        },
+      },
+    },
+    {
+      field: 'receiveUserType',
+      title: '{smart.message.systemMessage.title.receiveUserType}',
+      width: 120,
+      slots: {
+        default: ({ row }) => {
+          if (!row.receiveUserType) {
+            return '';
+          }
+          const enumList = receiveUserTypeEnum.filter((item) => item.value === row.receiveUserType);
+          if (enumList.length === 0) {
+            return '';
+          }
+          const data = enumList[0];
+          let color = '#2db7f5';
+          if (data.value === 'SPECIFY_USER') {
+            color = '#87d068';
+          } else if (data.value === 'BUSINESS_USER') {
+            color = '#108ee9';
+          }
+          return <a-tag color={color}>{data.label}</a-tag>;
+        },
+      },
+    },
+    {
+      field: 'sendTime',
+      title: '{smart.message.systemMessage.title.sendTime}',
+      width: 165,
+      sortable: true,
+    },
+    {
+      field: 'cancelTime',
+      title: '{smart.message.systemMessage.title.cancelTime}',
+      width: 165,
+      sortable: true,
+    },
+    {
+      field: 'useYn',
+      component: 'booleanTag',
+      title: '{common.table.useYn}',
+      width: 120,
+    },
+    {
+      field: 'createTime',
+      title: '{common.table.createTime}',
+      width: 170,
+      sortable: true,
+    },
+    {
+      field: 'createBy',
+      title: '{common.table.createUser}',
+      width: 120,
+    },
+    {
+      field: 'updateTime',
+      title: '{common.table.updateTime}',
+      width: 170,
+      sortable: true,
+    },
+    {
+      field: 'updateBy',
+      title: '{common.table.updateUser}',
+      width: 120,
+    },
+    {
+      title: '{common.table.operation}',
+      field: 'operation',
+      width: 120,
+      fixed: 'right',
+      slots: {
+        default: 'table-operation',
+      },
+    },
+  ];
+};
+
+/**
+ * 添加修改表单
+ */
+export const getFormSchemas = (t: Function): FormSchema[] => {
+  return [
+    {
+      field: 'id',
+      show: false,
+      label: t('smart.message.systemMessage.title.id'),
+      component: 'Input',
+      componentProps: {},
+    },
+    {
+      field: 'messageType',
+      label: t('smart.message.systemMessage.title.messageType'),
+      component: 'RadioGroup',
+      componentProps: {
+        options: getMessageTypeEnum(t),
+      },
+      defaultValue: 'ANNOUNCEMENT',
+    },
+    {
+      field: 'title',
+      label: t('smart.message.systemMessage.title.title'),
+      component: 'Input',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'abstractContent',
+      label: t('smart.message.systemMessage.title.abstract'),
+      component: 'InputTextArea',
+      componentProps: {},
+      required: true,
+    },
+    {
+      field: 'priority',
+      label: t('smart.message.systemMessage.title.priority'),
+      component: 'RadioGroup',
+      componentProps: {
+        options: getMessagePriorityEnum(t),
+      },
+      defaultValue: 'H',
+    },
+    {
+      field: 'receiveUserType',
+      label: t('smart.message.systemMessage.title.receiveUserType'),
+      component: 'RadioGroup',
+      componentProps: {
+        options: getReceiveUserTypeEnum(t),
+      },
+      defaultValue: 'ALL_USER',
+    },
+    {
+      field: 'userIds',
+      label: t('smart.message.systemMessage.title.userIds'),
+      component: 'SmartUserTableSelect',
+      show: ({ model }) => {
+        return model.receiveUserType !== 'ALL_USER';
+      },
+      required: ({ model }) => {
+        return model.receiveUserType !== 'ALL_USER';
+      },
+    },
+    {
+      field: 'useYn',
+      label: t('common.table.useYn'),
+      component: 'Switch',
+      defaultValue: true,
+      componentProps: {},
+    },
+    {
+      field: 'content',
+      label: t('smart.message.systemMessage.title.content'),
+      slot: 'addEdit-content',
+    },
+  ];
+};
+
+export const getSearchFormSchemas = (t: Function): SmartSearchFormSchema[] => {
+  return [
+    {
+      field: 'title',
+      label: t('smart.message.systemMessage.title.title'),
+      component: 'Input',
+      searchSymbol: 'like',
+    },
+    {
+      field: 'messageType',
+      label: t('smart.message.systemMessage.title.messageType'),
+      component: 'Select',
+      searchSymbol: '=',
+      componentProps: {
+        options: getMessageTypeEnum(t),
+        style: {
+          width: '140px',
+        },
+      },
+    },
+    {
+      field: 'sendStatus',
+      label: t('smart.message.systemMessage.title.sendStatus'),
+      component: 'Select',
+      searchSymbol: '=',
+      componentProps: {
+        options: getMessageSendStatusEnum(t),
+        style: {
+          width: '140px',
+        },
+      },
+    },
+    {
+      field: 'priority',
+      label: t('smart.message.systemMessage.title.priority'),
+      component: 'Select',
+      searchSymbol: '=',
+      componentProps: {
+        options: getMessagePriorityEnum(t),
+        style: {
+          width: '125px',
+        },
+      },
+    },
+    {
+      field: 'receiveUserType',
+      label: t('smart.message.systemMessage.title.receiveUserType'),
+      component: 'Select',
+      searchSymbol: '=',
+      componentProps: {
+        options: getReceiveUserTypeEnum(t),
+        style: {
+          width: '140px',
+        },
+      },
+    },
+  ];
+};
+
+export enum Auth {
+  delete = 'smart:message:systemMessage:delete',
+  update = 'smart:message:systemMessage:update',
+  save = 'smart:message:systemMessage:save',
+  publish = 'smart:message:systemMessage:publish',
+  cancel = 'smart:message:systemMessage:cancel',
+}

+ 188 - 0
src/modules/message/views/SystemMessage/SmartMessageSystemListView.vue

@@ -0,0 +1,188 @@
+<template>
+  <div class="full-height page-container">
+    <SmartTable @register="registerTable" :size="getTableSize">
+      <template #table-operation="{ row }">
+        <SmartVxeTableAction
+          :actions="getActions(row)"
+          :drop-down-actions="getDropDownAction(row)"
+        />
+      </template>
+      <template #addEdit-content="{ model }">
+        <Tinymce v-model:value="model.content" />
+      </template>
+    </SmartTable>
+    <SystemMessageShowModal @register="registerModal" />
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { useI18n } from '@/hooks/web/useI18n';
+  import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
+  import { useModal } from '@/components/Modal';
+  import { Tinymce } from '@/components/Tinymce';
+
+  import SystemMessageShowModal from '../../components/SystemMessageShowModal.vue';
+
+  import {
+    ActionItem,
+    SmartTable,
+    SmartVxeTableAction,
+    useSmartTable,
+  } from '@/components/SmartTable';
+
+  import {
+    getTableColumns,
+    getFormSchemas,
+    getSearchFormSchemas,
+    Auth,
+  } from './SmartMessageSystemListView.config';
+  import {
+    listApi,
+    deleteApi,
+    getByIdApi,
+    batchSaveUpdateApi,
+    publishApi,
+    revokeApi,
+  } from './SmartMessageSystemListView.api';
+  import { createConfirm, successMessage } from '@/utils/message/SystemNotice';
+
+  const { t } = useI18n();
+  const { getTableSize } = useSizeSetting();
+  const [registerModal, { openModal: openShowModal }] = useModal();
+
+  const getActions = (row: Recordable): ActionItem[] => {
+    const actions: ActionItem[] = [];
+    if (row.sendStatus === 'NO_SEND') {
+      actions.push({
+        label: t('common.button.edit'),
+        onClick: () => editByRowModal(row),
+        auth: Auth.update,
+      });
+    }
+    return actions;
+  };
+
+  const getDropDownAction = (row: Recordable): ActionItem[] => {
+    const actions: ActionItem[] = [];
+    const sendStatus = row.sendStatus;
+    if (sendStatus !== 'SEND') {
+      actions.push({
+        label: t('common.button.delete'),
+        onClick: () => deleteByRow(row),
+        auth: Auth.delete,
+      });
+    }
+    if (sendStatus === 'NO_SEND') {
+      actions.push({
+        label: t('common.button.publish'),
+        onClick: () => handlePublish(row),
+        auth: Auth.publish,
+      });
+    }
+    if (sendStatus === 'SEND') {
+      actions.push({
+        label: t('common.button.revoke'),
+        onClick: () => handleRevoke(row),
+        auth: Auth.cancel,
+      });
+    }
+    actions.push({
+      label: t('common.button.look'),
+      onClick: () => openShowModal(true, row),
+    });
+    return actions;
+  };
+
+  /**
+   * 发布操作
+   * @param row
+   */
+  const handlePublish = (row: any) => {
+    createConfirm({
+      iconType: 'warning',
+      content: t('smart.message.systemMessage.message.confirmPublish'),
+      onOk: async () => {
+        await publishApi([row.id]);
+        successMessage(t('smart.message.systemMessage.message.publishSuccess'));
+        query();
+      },
+    });
+  };
+
+  const handleRevoke = (row: any) => {
+    createConfirm({
+      iconType: 'warning',
+      content: t('smart.message.systemMessage.message.confirmRevoke'),
+      onOk: async () => {
+        await revokeApi([row.id]);
+        successMessage(t('smart.message.systemMessage.message.revokeSuccess'));
+        query();
+      },
+    });
+  };
+
+  const [registerTable, { editByRowModal, deleteByRow, query }] = useSmartTable({
+    columns: getTableColumns(t),
+    height: 'auto',
+    border: true,
+    sortConfig: {
+      remote: true,
+    },
+    columnConfig: {
+      resizable: true,
+    },
+    checkboxConfig: {
+      highlight: true,
+    },
+    pagerConfig: true,
+    showOverflow: 'tooltip',
+    useSearchForm: true,
+    searchFormConfig: {
+      compact: true,
+      schemas: getSearchFormSchemas(t),
+      searchWithSymbol: true,
+      colon: true,
+      autoSubmitOnEnter: true,
+      layout: 'inline',
+      actionColOptions: {
+        span: undefined,
+      },
+    },
+    addEditConfig: {
+      modalConfig: {
+        defaultFullscreen: true,
+      },
+      formConfig: {
+        schemas: getFormSchemas(t),
+        colon: true,
+        baseColProps: { span: 24 },
+        labelCol: { span: 6 },
+        wrapperCol: { span: 17 },
+      },
+    },
+    proxyConfig: {
+      ajax: {
+        query: (params) => listApi(params.ajaxParameter),
+        save: ({ body: { insertRecords, updateRecords } }) =>
+          batchSaveUpdateApi([...insertRecords, ...updateRecords]),
+        delete: ({ body: { removeRecords } }) => deleteApi(removeRecords),
+        getById: (params) => getByIdApi(params.id),
+      },
+    },
+    toolbarConfig: {
+      zoom: true,
+      refresh: true,
+      column: {
+        columnOrder: true,
+      },
+      buttons: [
+        {
+          code: 'ModalAdd',
+        },
+        {
+          code: 'delete',
+        },
+      ],
+    },
+  });
+</script>

+ 66 - 0
src/modules/message/views/SystemMessage/lang/zh_CN.ts

@@ -0,0 +1,66 @@
+/**
+ * 系统消息表 国际化信息
+ */
+export default {
+  trans: true,
+  key: 'smart.message.systemMessage',
+  data: {
+    title: {
+      id: 'id',
+      title: '标题',
+      content: '内容',
+      abstract: '摘要',
+      messageType: '消息类型',
+      sendStatus: '发布状态',
+      priority: '优先级',
+      receiveUserType: '通知用户类型',
+      sendTime: '发布时间',
+      cancelTime: '撤销时间',
+      userIds: '指定用户',
+    },
+    validate: {
+      id: '请输入',
+      title: '请输入标题',
+      abstract: '请输入摘要',
+      content: '请输入内容',
+      messageType: '请输入消息类型',
+      priority: '请输入优先级',
+      receiveUserType: '请输入通知用户类型',
+    },
+    rules: {},
+    search: {
+      title: '请输入标题',
+      messageType: '请输入消息类型',
+      sendStatus: '请输入发布状态',
+      priority: '请输入优先级',
+      receiveUserType: '请输入通知用户类型',
+    },
+    form: {
+      messageType: {
+        announcement: '通知公告',
+        systemMessage: '系统消息',
+      },
+      messagePriority: {
+        LOW: '低',
+        MIDDLE: '中',
+        HIGH: '高',
+      },
+      receiveUserType: {
+        ALL_USER: '全部用户',
+        SPECIFY_USER: '指定用户',
+        BUSINESS_USER: '业务用户',
+      },
+      sendStatus: {
+        NO_SEND: '未发布',
+        SEND: '已发布',
+        CANCEL: '已取消',
+      },
+    },
+    message: {
+      confirmPublish: '确定要发布吗?',
+      publishSuccess: '发布成功',
+      confirmRevoke: '确定要撤销吗?',
+      revokeSuccess: '撤销成功',
+    },
+  },
+};