Просмотр исходного кода

Merge branch 'feature/0508-message' into online

yuanliang 1 год назад
Родитель
Сommit
cf46246fbc
28 измененных файлов с 6296 добавлено и 3 удалено
  1. 8 1
      src/router/constant.ts
  2. 53 0
      src/utils/constant.ts
  3. 433 0
      src/views/message/api.ts
  4. 348 0
      src/views/message/message-config/email/email-config.tsx
  5. 253 0
      src/views/message/message-config/email/modal/email-config-edit.tsx
  6. 61 0
      src/views/message/message-config/index.tsx
  7. 327 0
      src/views/message/message-config/push/modal/push-config-edit.tsx
  8. 387 0
      src/views/message/message-config/push/push-config.tsx
  9. 159 0
      src/views/message/message-config/sms/modal/sms-config-app-edit.tsx
  10. 222 0
      src/views/message/message-config/sms/modal/sms-config-edit.tsx
  11. 347 0
      src/views/message/message-config/sms/sms-config-app.tsx
  12. 327 0
      src/views/message/message-config/sms/sms-config.tsx
  13. 229 0
      src/views/message/message-config/wechat/modal/wechat-config-edit.tsx
  14. 218 0
      src/views/message/message-config/wechat/modal/wechat-config-template-edit.tsx
  15. 342 0
      src/views/message/message-config/wechat/wechat-config-template-message.tsx
  16. 350 0
      src/views/message/message-config/wechat/wechat-config-template.tsx
  17. 355 0
      src/views/message/message-config/wechat/wechat-config.tsx
  18. 198 0
      src/views/message/message-record/email-record.tsx
  19. 82 0
      src/views/message/message-record/index.tsx
  20. 228 0
      src/views/message/message-record/push-record.tsx
  21. 259 0
      src/views/message/message-record/sms-record.tsx
  22. 240 0
      src/views/message/message-record/wechat-record.tsx
  23. 68 0
      src/views/message/message-template/index.tsx
  24. 349 0
      src/views/message/message-template/message-template-list.tsx
  25. 211 0
      src/views/message/message-template/modal/message-template-edit.tsx
  26. 11 0
      src/views/system-manage/api.ts
  27. 229 0
      src/views/system-manage/expection-log/index.tsx
  28. 2 2
      vite.config.ts

+ 8 - 1
src/router/constant.ts

@@ -27,5 +27,12 @@ export const asyncRoutes = {
   unitExamination: () => import('@/views/teaching-manage/unit-test/index'),
   unitTestCreate: () => import('@/views/teaching-manage/unit-test/unit-test-index/editAndUpdate'),
   musicSheet: () => import('@/views/music-library/music-sheet/index'), // 曲谱管理
-  projectMusicSheetManager: () => import('@/views/music-library/project-music-sheet/index') // 曲谱管理
+  projectMusicSheetManager: () => import('@/views/music-library/project-music-sheet/index') ,// 曲谱管理
+  messageTemplate: () => import('@/views/message/message-template/index') ,// 消息模板
+  messageRecord: () => import('@/views/message/message-record/index') ,// 消息记录
+  messageConfig: () => import('@/views/message/message-config/index') ,// 消息配置
+  messageConfigApp: () => import('@/views/message/message-config/sms/sms-config-app') ,// 消息配置应用
+  wxTemplateConfig: () => import('@views/message/message-config/wechat/wechat-config-template') ,// 推送消息配置模板
+  wxTemplateMessage: () => import('@views/message/message-config/wechat/wechat-config-template-message') ,// 推送消息配置模板内容
+  sysExceptionLog: () => import('@views/system-manage/expection-log/index') ,// 异常上报
 }

+ 53 - 0
src/utils/constant.ts

@@ -278,4 +278,57 @@ export const musicSheetAvailableType = {
 export const musicSheetAudioType = {
   HOMEMODE: '自制伴奏',
   COMMON: '普通伴奏',
+} as any
+
+export const messageSenderMode = {
+  SMS: '短信',
+  PUSH: '推送',
+  WECHAT: '公众号',
+  EMAIL: '邮件',
+} as any
+
+export const messageSenderFunctionModule = {
+  DEFAULT: '默认',
+  SYSTEM: '系统',
+  TRAINING: '作业',
+  COURSE: '课程',
+  PAY: '缴费通知',
+  LEAVE: '请假通知',
+  VIPAPPLY: 'VIP课通知',
+  STUDENT: '学员通知',
+  MUSICGROUP: '退团通知',
+  MUSIC: '建团通知',
+  WORK: '作业通知',
+  OA: 'OA通知',
+  TEACHER: '老师通知',
+} as any
+
+export const messageSendStatus= {
+  FAILED: '发送失败',
+  WAIT: '待发送',
+  SENDING: '发送中',
+  SUCCESSED: '发送完成',
+} as any
+
+export const deviceType= {
+  ANDROID: '安卓',
+  IOS: '苹果',
+} as any
+
+/**
+ * 应用类型
+ */
+export const appType= {
+  ANDROID: '安卓',
+  IOS: '苹果',
+  HARMONY: '鸿蒙',
+  WEB: '浏览器',
+} as any
+
+/**
+ * 异常类型
+ */
+export const exceptionType= {
+  ERROR: '错误',
+  RECORD: '记录',
 } as any

+ 433 - 0
src/views/message/api.ts

@@ -0,0 +1,433 @@
+import request from "@/utils/request";
+
+/**
+ * @description: 消息模板
+ */
+export const sysMessageConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessageConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 消息模板修改
+ */
+export const sysMessageConfigUpdate= (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessageConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 消息模板新增
+ */
+export const sysMessageConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessageConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 消息模板启用/停用
+ */
+export const sysMessageConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessageConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 短信配置分页
+ */
+export const smsConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 添加短信配置
+ */
+export const smsConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 修改短信配置
+ */
+export const smsConfigUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 短信配置启用/停用
+ */
+export const smsConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 短信配置删除
+ */
+export const smsConfigRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 短信配置详情
+ */
+export const smsConfigDetail = (id: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/detail/' + id,
+    method: 'get',
+  } as any)
+}
+
+/**
+ * @description: 短信应用配置表分页
+ */
+export const smsAppConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/smsAppConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 添加短信应用配置
+ */
+export const smsAppConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/smsAppConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 修改短信配置
+ */
+export const smsAppConfigUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/smsAppConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 短信应用配置表删除
+ */
+export const smsAppConfigRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/smsAppConfig/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 短信应用配置表修改状态
+ */
+export const smsAppConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/smsAppConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: app推送配置表分页
+ */
+export const appSendConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: app推送配置表启用/停用
+ */
+export const appSendConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: app推送配置表删除
+ */
+export const appSendConfigRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 添加短信配置
+ */
+export const appSendConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 修改短信配置
+ */
+export const appSendConfigUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+
+/**
+ * @description: 消息记录
+ */
+export const sysMessagePage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessage/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信配置信息分页
+ */
+export const wxConfigInfoPage = (params: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信配置信息新增
+ */
+export const wxConfigInfoSave = (params: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信配置信息修改
+ */
+export const wxConfigInfoUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+
+/**
+ * @description: 微信配置信息删除
+ */
+export const wxConfigInfoRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 微信配置信息删除启用/停用
+ */
+export const wxConfigInfoStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信配置信息详情
+ */
+export const wxConfigInfoDetail = (id: object) => {
+  return request({
+    url: '/cbs-app/wxConfigInfo/detail/' + id,
+    method: 'get',
+  } as any)
+}
+
+/**
+ * @description: 微信模板配置新增
+ */
+export const wxTemplateConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+/**
+ * @description: 微信模板配置修改
+ */
+export const wxTemplateConfigUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信模板配置分页
+ */
+export const wxTemplateConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+
+/**
+ * @description: 微信模板配置启用/停用
+ */
+export const wxTemplateConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信模板配置删除
+ */
+export const wxTemplateConfigRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 微信模板配置详情
+ */
+export const wxTemplateConfigDetail = (id: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateConfig/detail/' + id,
+    method: 'get',
+  } as any)
+}
+
+/**
+ * @description: 微信模板消息内容分页
+ */
+export const wxTemplateMessagePage = (params: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateMessage/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 微信模板消息内容详情
+ */
+export const wxTemplateMessageDetail = (id: object) => {
+  return request({
+    url: '/cbs-app/wxTemplateMessage/detail/' + id,
+    method: 'get',
+  } as any)
+}
+
+/**
+ * @description: 邮件配置表分页
+ */
+export const emailConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/emailConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 邮件配置表删除
+ */
+export const emailConfigRemove = (id: object) => {
+  return request({
+    url: '/cbs-app/emailConfig/remove?id=' + id,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 邮件配置表启用/停用
+ */
+export const emailConfigStatus = (params: object) => {
+  return request({
+    url: '/cbs-app/emailConfig/status',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 邮件配置表新增
+ */
+export const emailConfigSave = (params: object) => {
+  return request({
+    url: '/cbs-app/emailConfig/save',
+    method: 'post',
+    data: params
+  } as any)
+}
+
+/**
+ * @description: 邮件配置表修改
+ */
+export const emailConfigUpdate = (params: object) => {
+  return request({
+    url: '/cbs-app/emailConfig/update',
+    method: 'post',
+    data: params
+  } as any)
+}

+ 348 - 0
src/views/message/message-config/email/email-config.tsx

@@ -0,0 +1,348 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import deepClone from '@/utils/deep.clone'
+import EmailConfigEdit from '@views/message/message-config/email/modal/email-config-edit'
+import { getMapValueByKey } from '@/utils/objectUtil'
+import { appKey } from '@/utils/constant'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import { emailConfigPage, emailConfigRemove, emailConfigStatus } from '@views/message/api'
+
+export default defineComponent({
+  name: 'email-config',
+  setup(props, ctx) {
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null, // 状态
+        appKey: null // 接入应用
+      },
+      name: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      appData: [] as any
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      // 应用
+      {
+        state.appData = []
+        const { data } = await sysApplicationPage({ page: 1, rows: 999 })
+        if (data && data.rows) {
+          data.rows.forEach((item: any) => {
+            state.appData.push({ label: item.appName, value: item.appKey })
+          })
+        }
+      }
+
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await emailConfigPage({
+          ...state.pagination,
+          ...state.searchForm
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await emailConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await emailConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '平台名称',
+          key: 'name'
+        },
+        {
+          title: '接入应用',
+          key: 'appKey',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.appKey, new Map(Object.entries(appKey)))}</div>
+          }
+        },
+        {
+          title: '域名',
+          key: 'hostUrl'
+        },
+        {
+          title: '端口',
+          key: 'smtpPort'
+        },
+        {
+          title: '用户名',
+          key: 'account'
+        },
+        {
+          title: '密码',
+          key: 'password'
+        },
+        {
+          title: '发送方',
+          key: 'from'
+        },
+        {
+          title: '发送名称',
+          key: 'fromName'
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="emailConfig/status1790935300606857218"
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="emailConfig/update1790935437341167617"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="emailConfig/remove1790935226178932738"
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="email-config"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput
+                  placeholder="编号/平台名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="接入应用" path="appKey">
+                <NSelect
+                  placeholder="请选择接入应用"
+                  v-model:value={state.searchForm.appKey}
+                  options={state.appData}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                v-auth="emailConfig/save1790935373034098690"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增邮件配置
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="email-config"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '邮件配置'}
+              style={{ width: '700px' }}
+            >
+              <EmailConfigEdit
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 253 - 0
src/views/message/message-config/email/modal/email-config-edit.tsx

@@ -0,0 +1,253 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, NSpace, useMessage} from "naive-ui";
+import {sysApplicationPage} from "@views/menu-manage/api";
+import {emailConfigSave, emailConfigUpdate} from "@views/message/api";
+
+export default defineComponent({
+  mpName: 'email-config-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      name: null, //平台名称
+      sender: null, //三方服务厂商
+      smtpPort: null, //端口
+      account: null, //账号
+      password: null, //密码
+      from: null, //密码
+      fromName: null, //发送方名称
+      appKey: null, //应用端
+      hostUrl: null, //域名
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+      appData: [] as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.name = state.rowData.name
+        forms.sender = state.rowData.sender
+        forms.smtpPort = state.rowData.smtpPort
+        forms.account = state.rowData.account
+        forms.password = state.rowData.password
+        forms.from = state.rowData.from
+        forms.fromName = state.rowData.fromName
+        forms.appKey = state.rowData.appKey
+        forms.hostUrl = state.rowData.hostUrl
+      }
+      // 应用
+      {
+        state.appData = []
+        const {data} = await sysApplicationPage({page: 1, rows: 999})
+        if (data && data.rows) {
+          data.rows.forEach((item: any) => {
+            state.appData.push({label: item.appName, value: item.appKey})
+          })
+        }
+      }
+
+
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await emailConfigSave(
+                {
+                  ...forms,
+                }
+            ) as any;
+          } else {
+            res = await emailConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="平台名称"
+                    path="name"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.name}
+                      placeholder="请输入平台名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="域名"
+                    path="hostUrl"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入域名'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.hostUrl}
+                      placeholder="请输入域名"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="端口"
+                    path="smtpPort"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入端口'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.smtpPort}
+                      placeholder="请输入端口"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="用户名"
+                    path="account"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入用户名'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.account}
+                      placeholder="请输入用户名"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="密码"
+                    path="password"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入密码'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.password}
+                      placeholder="请输入密码"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="发送方"
+                    path="from"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入发送方'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.from}
+                      placeholder="请输入发送方"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="发送方名称"
+                    path="fromName"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入发送方名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.fromName}
+                      placeholder="请输入发送方名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入应用"
+                    path="appKey"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择接入应用'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.appKey}
+                      placeholder="请选择接入应用"
+                      options={state.appData}
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 61 - 0
src/views/message/message-config/index.tsx

@@ -0,0 +1,61 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+import SmsConfig from '@views/message/message-config/sms/sms-config'
+import PushConfig from '@views/message/message-config/push/push-config'
+import WechatConfig from '@views/message/message-config/wechat/wechat-config'
+import EmailConfig from '@views/message/message-config/email/email-config'
+
+export default defineComponent({
+  name: 'message-config-index',
+  setup() {
+    const state = reactive({
+      tabName: 'SMS' as 'SMS' | 'PUSH' | 'EMAIL' | 'WECHAT'
+    })
+    const tabsInstRef = ref()
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        state.tabName = val.form.tabName
+        nextTick(() => tabsInstRef.value?.syncBarPosition())
+      }
+    })
+    const route = useRoute()
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+
+    onMounted(async () => {
+      nextTick(() => tabsInstRef.value?.syncBarPosition())
+    })
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <div class={['section-container']} style="padding-top: 0">
+            <NTabs
+              ref={tabsInstRef}
+              type="line"
+              size="large"
+              v-model:value={state.tabName}
+              onUpdate:value={(val: any) => setTabs(val)}
+            >
+              <NTabPane name="SMS" tab="短信" v-auth="smsConfig/page1790929887618469889">
+                <SmsConfig />
+              </NTabPane>
+              <NTabPane name="PUSH" tab="推送" v-auth="appSendConfig/page1790933531566870529">
+                <PushConfig />
+              </NTabPane>
+              <NTabPane name="WECHAT" tab="公众号" v-auth="wxConfigInfo/page1790934372273164290">
+                <WechatConfig />
+              </NTabPane>
+              <NTabPane name="EMAIL" tab="邮件" v-auth="emailConfig/page1790935157727891457">
+                <EmailConfig />
+              </NTabPane>
+            </NTabs>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 327 - 0
src/views/message/message-config/push/modal/push-config-edit.tsx

@@ -0,0 +1,327 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NInputNumber, NSelect, NSpace, useMessage} from "naive-ui";
+import {getSelectDataFromObj} from "@/utils/objectUtil";
+import {clientType, deviceType} from "@/utils/constant";
+import {appSendConfigSave, appSendConfigUpdate} from "@views/message/api";
+
+export default defineComponent({
+  name: 'push-config-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+    appData: {
+      type: [] as any,
+      required: true
+    },
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      name: null, // 平台名称
+      sender: null, // 平台标识
+      appKey: null, // 应用端
+      accessUrl: null, // 接入地址
+      clientId: null, // 客户端
+      apnsProduction: null as any, // 推送环境
+      account: null, // 接入账号
+      password: null, // 接入密码
+      timeToLive: null as any, // 离线保存时长
+      extendData: null, // 扩展参数
+      deviceType: null, // 设备类型
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+      musicSheetCategories: [] as any,
+      appData: [] as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.name = state.rowData.name
+        forms.sender = state.rowData.sender
+        forms.appKey = state.rowData.appKey
+        forms.clientId = state.rowData.clientId
+        forms.apnsProduction = state.rowData.apnsProduction
+        forms.account = state.rowData.account
+        forms.password = state.rowData.password
+        forms.timeToLive = Number.parseFloat(state.rowData.timeToLive)/3600
+        forms.accessUrl = state.rowData.accessUrl
+        forms.extendData = state.rowData.extendData
+        forms.deviceType = state.rowData.deviceType
+      }
+      // 客户端
+
+
+      // state.appData = []
+      // const {data} = await sysApplicationPage({page: 1, rows: 999})
+      // if (data && data.rows) {
+      //   data.rows.forEach((item: any) => {
+      //     state.appData.push({label: item.appName, value: item.id + ''})
+      //   })
+      // }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await appSendConfigSave(
+                {
+                  ...forms,
+                  timeToLive: Number.parseFloat(forms.timeToLive) * 3600
+                }
+            ) as any;
+          } else {
+            res = await appSendConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id,
+                  timeToLive: Number.parseFloat(forms.timeToLive) * 3600
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="平台名称"
+                    path="name"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.name}
+                      placeholder="请输入平台名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="平台标识"
+                    path="sender"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台标识'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.sender}
+                      placeholder="请输入平台标识"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入应用"
+                    path="appKey"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入应用'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.appKey}
+                      placeholder="请输入接入应用"
+                      options={props.appData}
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="客户端"
+                    path="clientId"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择客户端'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.clientId}
+                      options={getSelectDataFromObj(clientType)}
+                      placeholder="请选择客户端"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入地址"
+                    path="accessUrl"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入地址'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.accessUrl}
+                      placeholder="请输入接入地址"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="推送环境"
+                    path="apnsProduction"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入推送环境',
+                        type: 'boolean'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      v-model:value={forms.apnsProduction}
+                      placeholder="请输入推送环境"
+                      options={[
+                        {
+                          label: '线上',
+                          value: true
+                        }, {
+                          label: '开发',
+                          value: false
+                        }
+
+                      ] as any}
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入key"
+                    path="account"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入key'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.account}
+                      placeholder="请输入接入key"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入密钥"
+                    path="password"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入密钥'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.password}
+                      placeholder="请输入接入密码"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="离线保留时长(小时)"
+                    path="timeToLive"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入离线保留时长(小时)'
+                      }
+                    ]}
+                >
+                  <NInputNumber
+                      v-model:value={forms.timeToLive}
+                      placeholder="请输入离线保留时长(小时)"
+                      min={0}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="设备类型"
+                    path="deviceType"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请选择设备类型'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择设备类型"
+                      value={forms.deviceType}
+                      options={getSelectDataFromObj(deviceType)}
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="拓展参数"
+                    path="extendData"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入拓展参数'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.extendData}
+                      placeholder="请输入拓展参数"
+                      autosize={{minRows: 3}}
+                      type={'textarea'}
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 387 - 0
src/views/message/message-config/push/push-config.tsx

@@ -0,0 +1,387 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import PushConfigEdit from '@views/message/message-config/push/modal/push-config-edit'
+import TheTooltip from '@components/TheTooltip'
+import { getMapValueByKey, getSelectDataFromObj } from '@/utils/objectUtil'
+import { appKey, clientType, deviceType } from '@/utils/constant'
+import deepClone from '@/utils/deep.clone'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import { appSendConfigPage, appSendConfigRemove, appSendConfigStatus } from '@views/message/api'
+
+export default defineComponent({
+  name: 'push-config',
+  setup(props, ctx) {
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null, // 状态
+        appKey: null, // 应用
+        clientId: null // 客户端
+      },
+      name: null as any,
+      smsConfigId: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      appData: [] as any
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      state.appData = []
+      const { data } = await sysApplicationPage({ page: 1, rows: 999 })
+      if (data && data.rows) {
+        data.rows.forEach((item: any) => {
+          state.appData.push({ label: item.appName, value: item.appKey })
+        })
+      }
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      state.loading = true
+      try {
+        const { data } = await appSendConfigPage({
+          ...state.pagination,
+          ...state.searchForm
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await appSendConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await appSendConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '平台名称',
+          key: 'name'
+        },
+        {
+          title: '平台标识',
+          key: 'sender'
+        },
+        {
+          title: '应用',
+          key: 'appKey',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.appKey, new Map(Object.entries(appKey)))}</div>
+          }
+        },
+        {
+          title: '客户端',
+          key: 'clientId',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}</div>
+          }
+        },
+        {
+          title: '设备类型',
+          key: 'deviceType',
+          render(row: any) {
+            return (
+              <div>{getMapValueByKey(row.deviceType, new Map(Object.entries(deviceType)))}</div>
+            )
+          }
+        },
+        {
+          title: '接入地址',
+          key: 'accessUrl'
+        },
+        {
+          title: '离线保留时长(小时)',
+          key: 'timeToLive',
+          render(row: any) {
+            return row.timeToLive / 3600
+          }
+        },
+        {
+          title: '推送环境',
+          key: 'apnsProduction',
+          render(row: any) {
+            return row.apnsProduction ? '线上' : '开发'
+          }
+        },
+        {
+          title: '接入key',
+          key: 'account'
+        },
+        {
+          title: '接入秘匙',
+          key: 'password'
+        },
+        {
+          title: '拓展参数',
+          key: 'extendData',
+          render(row: any) {
+            return <TheTooltip content={row.extendData} />
+          }
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          minWidth: '180px',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="appSendConfig/status1790933632397938690"
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="appSendConfig/update1790933865680932866"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="appSendConfig/remove1790933707882827777"
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="push-config-app"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput
+                  placeholder="应用编号/名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="应用" path="appKey">
+                <NSelect
+                  placeholder="请选择应用"
+                  v-model:value={state.searchForm.appKey}
+                  options={state.appData}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="客户端" path="clientId">
+                <NSelect
+                  placeholder="请选择客户端"
+                  v-model:value={state.searchForm.clientId}
+                  options={getSelectDataFromObj(clientType)}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                v-auth="appSendConfig/save1790933794033831937"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增推送平台
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="push-config-app"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '推送平台'}
+              // style={{width: 'auto'}}
+              style={{ width: '700px' }}
+            >
+              <PushConfigEdit
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                appData={state.appData}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 159 - 0
src/views/message/message-config/sms/modal/sms-config-app-edit.tsx

@@ -0,0 +1,159 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, NSpace} from "naive-ui";
+import {smsAppConfigSave, smsAppConfigUpdate} from "@views/message/api";
+
+export default defineComponent({
+  name: 'sms-config-app-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    smsConfigId: {
+      type: Number,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+    appData: {
+      type: Array as any,
+      required: true
+    }
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const btnLoading = ref(false)
+    const forms = reactive({
+      appKey: null as any,
+      sign: null,
+      extendData: null,
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+      musicSheetCategories: [] as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.appKey = state.rowData.appKey
+        forms.sign = state.rowData.sign
+        forms.extendData = state.rowData.extendData
+      }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await smsAppConfigSave(
+                {
+                  ...forms,
+                  sysConfigId: props.smsConfigId
+                }
+            ) as any;
+          } else {
+            res = await smsAppConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="应用"
+                    path="appKey"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台名称'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择平台"
+                      v-model:value={forms.appKey}
+                      options={props.appData}
+                      filterable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="签名"
+                    path="sign"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入签名'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.sign}
+                      placeholder="请输入签名"
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="拓展参数"
+                    path="extendData"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入拓展参数'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.extendData}
+                      placeholder="请输入拓展参数"
+                      autosize={{minRows: 3}}
+                      type={'textarea'}
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 222 - 0
src/views/message/message-config/sms/modal/sms-config-edit.tsx

@@ -0,0 +1,222 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSpace, useMessage} from "naive-ui";
+import {smsConfigSave, smsConfigUpdate} from "@views/message/api";
+
+export default defineComponent({
+  name: 'sms-config-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      name: null,
+      sender: null,
+      accessUrl: null,
+      account: null,
+      password: null,
+      extendData: null,
+      appId: null,
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+      musicSheetCategories: [] as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.name = state.rowData.name
+        forms.sender = state.rowData.sender
+        forms.accessUrl = state.rowData.accessUrl
+        forms.account = state.rowData.account
+        forms.password = state.rowData.password
+        forms.extendData = state.rowData.extendData
+        forms.appId = state.rowData.appId
+      }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await smsConfigSave(
+                {
+                  ...forms,
+                }
+            ) as any;
+          } else {
+            res = await smsConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="平台名称"
+                    path="name"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.name}
+                      placeholder="请输入平台名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="平台标识"
+                    path="sender"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台标识'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.sender}
+                      placeholder="请输入平台标识"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入地址"
+                    path="accessUrl"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入地址'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.accessUrl}
+                      placeholder="请输入接入地址"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="应用ID"
+                    path="appId"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入应用ID'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.appId}
+                      placeholder="请输入应用ID"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入账号"
+                    path="account"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入账号'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.account}
+                      placeholder="请输入接入账号"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入密码"
+                    path="password"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入密码'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.password}
+                      placeholder="请输入接入密码"
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="拓展参数"
+                    path="extendData"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入拓展参数'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.extendData}
+                      placeholder="请输入拓展参数"
+                      autosize={{minRows: 3}}
+                      type={'textarea'}
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 347 - 0
src/views/message/message-config/sms/sms-config-app.tsx

@@ -0,0 +1,347 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NPageHeader,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import deepClone from '@/utils/deep.clone'
+import SmsConfigAppEdit from '@views/message/message-config/sms/modal/sms-config-app-edit'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import TheTooltip from '@components/TheTooltip'
+import {
+  smsAppConfigPage,
+  smsAppConfigRemove,
+  smsAppConfigStatus,
+  smsConfigDetail
+} from '@views/message/api'
+
+export default defineComponent({
+  name: 'sms-config-app',
+  setup(props, ctx) {
+    const dialog = useDialog()
+    const route = useRoute()
+    const message = useMessage()
+    const router = useRouter()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null // 状态
+      },
+      name: null as any,
+      smsConfigId: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      appData: [] as any,
+      appMap: new Map()
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      if (!route.query) {
+        return
+      }
+      state.smsConfigId = route.query.id
+      {
+        const { data } = await smsConfigDetail(state.smsConfigId)
+        if (data) {
+          state.name = data.name
+        }
+      }
+
+      // 应用
+      {
+        state.appData = []
+        const { data } = await sysApplicationPage({ page: 1, rows: 999 })
+        if (data && data.rows) {
+          data.rows.forEach((item: any) => {
+            state.appData.push({ label: item.appName, value: item.appKey })
+            state.appMap.set(item.appKey, item.appName)
+          })
+        }
+      }
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      if (!state.smsConfigId) {
+        return
+      }
+      try {
+        state.loading = true
+        const { data } = await smsAppConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+          sysConfigId: state.smsConfigId
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await smsAppConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await smsAppConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '应用',
+          key: 'appKey',
+          render(row: any) {
+            return state.appMap.get(row.appKey)
+          }
+        },
+        {
+          title: '签名',
+          key: 'sign'
+        },
+        {
+          title: '拓展参数',
+          key: 'extendData',
+          render: (row: any) => {
+            return <TheTooltip content={row.extendData} />
+          }
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="smsAppConfig/status1790942026752135170"
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="smsAppConfig/update1790942447902199809"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="smsAppConfig/remove1790942087347245058"
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <NPageHeader on-back={() => gotoBack()} title={state.name}></NPageHeader>
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="sms-config-app"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput
+                  placeholder="应用编号/名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                v-auth="smsAppConfig/save1790942383565770753"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增应用
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="sms-config-app"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '应用'}
+              style={{ width: '700px' }}
+            >
+              <SmsConfigAppEdit
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                appData={state.appData}
+                smsConfigId={state.smsConfigId}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 327 - 0
src/views/message/message-config/sms/sms-config.tsx

@@ -0,0 +1,327 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import SaveForm from '@components/save-form'
+import {
+  DataTableRowKey,
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import Pagination from '@components/pagination'
+import SmsConfigEdit from '@views/message/message-config/sms/modal/sms-config-edit'
+import deepClone from '@/utils/deep.clone'
+import { useRouter } from 'vue-router'
+import TheTooltip from '@components/TheTooltip'
+import { smsConfigPage, smsConfigRemove, smsConfigStatus } from '@views/message/api'
+
+export default defineComponent({
+  name: 'sms-config',
+
+  setup(props) {
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null // 状态
+      },
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any
+    })
+
+    onMounted(async () => {
+      getList()
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await smsConfigPage({
+          ...state.pagination,
+          ...state.searchForm
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+        // state.dataList = []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await smsConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await smsConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '平台名称',
+          key: 'name'
+        },
+        {
+          title: '平台标识',
+          key: 'sender'
+        },
+        {
+          title: '接入地址',
+          key: 'accessUrl'
+        },
+        {
+          title: '应用ID',
+          key: 'appId'
+        },
+        {
+          title: '接入账号',
+          key: 'account'
+        },
+        {
+          title: '接入密码',
+          key: 'password'
+        },
+        {
+          title: '拓展参数',
+          key: 'extendData',
+          render: (row: any) => {
+            return <TheTooltip content={row.extendData} />
+          }
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="message/messageConfigApp1788130264099217409"
+                  onClick={() => {
+                    router.push({
+                      path: '/message/messageConfigApp',
+                      query: {
+                        id: row.id
+                      }
+                    })
+                  }}
+                >
+                  应用详情
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  v-auth="smsConfig/status1790932263540690945"
+                  text
+                  onClick={() => onChangeStatus(row)}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="smsConfig/update1790932998848319490"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="smsConfig/remove1790932360940818433"
+                  disabled={!!row.status}
+                  onClick={() => onRmove(row)}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <SaveForm
+            ref={saveForm}
+            model={state.searchForm}
+            onSubmit={onSubmit}
+            saveKey="sms-config"
+            onSetModel={(val: any) => (state.searchForm = val)}
+          >
+            <NFormItem label="关键字" path="keyword">
+              <NInput placeholder="编号/名称" v-model:value={state.searchForm.keyword} clearable />
+            </NFormItem>
+            <NFormItem label="状态" path="status">
+              <NSelect
+                v-model:value={state.searchForm.status}
+                clearable
+                options={
+                  [
+                    {
+                      label: '启用',
+                      value: 1
+                    },
+                    {
+                      label: '停用',
+                      value: 0
+                    }
+                  ] as any
+                }
+                placeholder="全部状态"
+              />
+            </NFormItem>
+            <NFormItem>
+              <NSpace>
+                <NButton type="primary" onClick={onSearch}>
+                  搜索
+                </NButton>
+                <NButton type="default" onClick={onBtnReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </SaveForm>
+
+          <NSpace style={{ paddingBottom: '12px' }}>
+            <NButton
+              type="primary"
+              v-auth="smsConfig/save1790932891084066817"
+              onClick={() => {
+                state.showEdit = true
+                state.editMode = 'add'
+              }}
+            >
+              新增短信平台
+            </NButton>
+          </NSpace>
+
+          <div class={['section-container']}>
+            <NDataTable
+              loading={state.loading}
+              columns={columns()}
+              data={state.dataList}
+              rowKey={(row: any) => row.id}
+              scrollX={'1400'}
+            ></NDataTable>
+
+            <Pagination
+              v-model:page={state.pagination.page}
+              v-model:pageSize={state.pagination.rows}
+              v-model:pageTotal={state.pagination.pageTotal}
+              onList={getList}
+              sync
+              saveKey="sms-config"
+            ></Pagination>
+          </div>
+          <NModal
+            blockScroll={true}
+            v-model:show={state.showEdit}
+            preset="dialog"
+            showIcon={false}
+            title={(state.editMode == 'add' ? '新增' : '编辑') + '短信平台'}
+            // style={{width: 'auto'}}
+            style={{ width: '700px' }}
+          >
+            <SmsConfigEdit
+              editMode={state.editMode}
+              rowData={state.editRowData}
+              onClose={() => (state.showEdit = false)}
+              onGetList={() => {
+                state.pagination.page = 1
+                getList()
+              }}
+            />
+          </NModal>
+        </div>
+      )
+    }
+  }
+})

+ 229 - 0
src/views/message/message-config/wechat/modal/wechat-config-edit.tsx

@@ -0,0 +1,229 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, NSpace, useMessage} from "naive-ui";
+import {wxConfigInfoSave, wxConfigInfoUpdate} from "@views/message/api";
+
+export default defineComponent({
+  mpName: 'wechat-config-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+    appData: {
+      type: Array as any,
+      required: true
+    }
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+
+
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      mpName: null, //公众号名称
+      appid: null, //公众号ID
+      secret: null, //接入密钥
+      token: null, //消息密钥
+      aeskey: null, //加密模式密钥
+      appKey: null, //应用
+      content: null, //关注公众号提示信息
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.mpName = state.rowData.mpName
+        forms.appid = state.rowData.appid
+        forms.secret = state.rowData.secret
+        forms.token = state.rowData.token
+        forms.aeskey = state.rowData.aeskey
+        forms.appKey = state.rowData.appKey
+        forms.content = state.rowData.content
+      }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await wxConfigInfoSave(
+                {
+                  ...forms,
+                }
+            ) as any;
+          } else {
+            res = await wxConfigInfoUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="公众号名称"
+                    path="mpName"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入公众号名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.mpName}
+                      placeholder="请输入公众号名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="公众号ID"
+                    path="appid"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入公众号ID'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.appid}
+                      placeholder="请输入公众号ID"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="接入密钥"
+                    path="secret"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入密钥'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.secret}
+                      placeholder="请输入接入密钥"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="消息密钥"
+                    path="token"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入消息密钥'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.token}
+                      placeholder="请输入消息密钥"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="加密模式密钥"
+                    path="aeskey"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入加密模式密钥'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.aeskey}
+                      placeholder="请输入加密模式密钥"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="应用"
+                    path="appKey"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择应用'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择应用"
+                      v-model:value={forms.appKey}
+                      options={props.appData}
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="关注公众号提示信息"
+                    path="content"
+                    rule={[
+                      {
+                        required: true,
+                        message: '关注公众号提示信息'
+                      }
+                    ]}
+                >
+                  <NInput
+                      type={'textarea'}
+                      v-model:value={forms.content}
+                      placeholder="关注公众号提示信息"
+                      autosize={{minRows:3}}
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 218 - 0
src/views/message/message-config/wechat/modal/wechat-config-template-edit.tsx

@@ -0,0 +1,218 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSpace, useMessage} from "naive-ui";
+import {wxTemplateConfigSave, wxTemplateConfigUpdate} from "@views/message/api";
+
+export default defineComponent({
+  mpName: 'wechat-config-template-edit',
+  props: {
+    appid: {
+      type: String,
+      required: false
+    },
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      wxTemplateId: null, //微信模板id
+      command: null, //指令
+      url: null, //模板消息地址
+      description: null, //描述
+      content: {
+        content: null,
+        title: null,
+      } as any
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+    })
+
+    onMounted(async () => {
+      if (!props.appid) {
+        return
+      }
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.command = state.rowData.command
+        forms.wxTemplateId = state.rowData.wxTemplateId
+        forms.url = state.rowData.url
+        forms.description = state.rowData.description
+        if (state.rowData.content) {
+          forms.content.content = state.rowData.content.content
+          forms.content.title = state.rowData.content.title
+        }
+      }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await wxTemplateConfigSave(
+                {
+                  ...forms,
+                  appid: props.appid
+                }
+            ) as any;
+          } else {
+            res = await wxTemplateConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id,
+                  appid: props.appid
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="标题"
+                    path="content.title"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入标题'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.content.title}
+                      placeholder="请输入标题"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="指令"
+                    path="command"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入指令'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.command}
+                      placeholder="请输入指令"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="模板ID"
+                    path="wxTemplateId"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入模板ID'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.wxTemplateId}
+                      placeholder="请输入模板ID"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="模板地址"
+                    path="appid"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入模板地址'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.url}
+                      placeholder="请输入模板地址"
+                      clearable
+                  />
+                </NFormItemGi>
+
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="内容"
+                    path="content.content"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入内容'
+                      }
+                    ]}
+                >
+                  <NInput
+                      type={'textarea'}
+                      v-model:value={forms.content.content}
+                      placeholder="请输入内容"
+                      autosize={{minRows:3}}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="描述"
+                    path="description"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入描述'
+                      }
+                    ]}
+                >
+                  <NInput
+                      type={'textarea'}
+                      v-model:value={forms.description}
+                      placeholder="请输入描述"
+                      autosize={{minRows:3}}
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 342 - 0
src/views/message/message-config/wechat/wechat-config-template-message.tsx

@@ -0,0 +1,342 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NPageHeader,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import deepClone from '@/utils/deep.clone'
+import WechatConfigTemplateEdit from '@views/message/message-config/wechat/modal/wechat-config-template-edit'
+import {
+  wxTemplateConfigDetail,
+  wxTemplateConfigRemove,
+  wxTemplateConfigStatus,
+  wxTemplateMessagePage
+} from '@views/message/api'
+
+export default defineComponent({
+  name: 'wechat-config-template',
+  setup(props, ctx) {
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null // 状态
+      },
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      appid: null as any,
+      wxTemplateConfigId: null as any
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/wxTemplateConfig',
+        query: state.appid
+      })
+    }
+
+    onMounted(async () => {
+      state.wxTemplateConfigId = route.query
+      if (!state.wxTemplateConfigId) {
+        return
+      }
+      {
+        const { data } = await wxTemplateConfigDetail(state.wxTemplateConfigId)
+        if (data) {
+          state.appid = data.appid
+        } else {
+          return
+        }
+      }
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      if (!state.appid) {
+        return
+      }
+      try {
+        state.loading = true
+        const { data } = await wxTemplateMessagePage({
+          ...state.pagination,
+          ...state.searchForm,
+          appid: state.appid
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxTemplateConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxTemplateConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '应用标志',
+          key: 'command'
+        },
+        {
+          title: '指令',
+          key: 'wxTemplateId'
+        },
+        {
+          title: '标题',
+          key: 'url'
+        },
+        {
+          title: '内容',
+          key: 'description'
+        },
+        {
+          title: '版本',
+          key: 'description'
+        },
+        {
+          title: '类型WECHAT',
+          key: 'description'
+        },
+        {
+          title: 'APP',
+          key: 'description'
+        },
+        {
+          title: '描述',
+          key: 'description'
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <NPageHeader on-back={() => gotoBack()} title={state.wxTemplateConfigId}></NPageHeader>
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="wechat-config-template"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput
+                  placeholder="平台编号/名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增消息内容
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="wechat-config-template"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '模板'}
+              style={{ width: 'auto' }}
+            >
+              <WechatConfigTemplateEdit
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                style={{ width: '450' }}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 350 - 0
src/views/message/message-config/wechat/wechat-config-template.tsx

@@ -0,0 +1,350 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NPageHeader,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import deepClone from '@/utils/deep.clone'
+import WechatConfigTemplateEdit from '@views/message/message-config/wechat/modal/wechat-config-template-edit'
+import TheTooltip from '@components/TheTooltip'
+import {
+  wxConfigInfoDetail,
+  wxTemplateConfigPage,
+  wxTemplateConfigRemove,
+  wxTemplateConfigStatus
+} from '@views/message/api'
+
+export default defineComponent({
+  name: 'wechat-config-template',
+  setup(props, ctx) {
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        status: null // 状态
+      },
+      mpName: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      wxConfigAppId: null as any,
+      appid: null as any
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      if (!route.query) {
+        return
+      }
+      state.wxConfigAppId = route.query.id
+      {
+        const { data } = await wxConfigInfoDetail(state.wxConfigAppId)
+        if (data) {
+          state.appid = data.appid
+          state.mpName = data.mpName
+          state.wxConfigAppId = data.appid
+        } else {
+          return
+        }
+      }
+
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      if (!state.wxConfigAppId) {
+        return
+      }
+      try {
+        state.loading = true
+        const { data } = await wxTemplateConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+          appid: state.wxConfigAppId
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxTemplateConfigStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxTemplateConfigRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '指令',
+          key: 'command'
+        },
+        {
+          title: '模板ID',
+          key: 'wxTemplateId'
+        },
+        {
+          title: '模板地址',
+          key: 'url'
+        },
+        {
+          title: '标题',
+          key: 'content.title'
+        },
+        {
+          title: '内容',
+          key: 'content.content',
+          render(row: any) {
+            return <TheTooltip content={row.content.content} />
+          }
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                {/*<NButton*/}
+                {/*    type="primary"*/}
+                {/*    size="small"*/}
+                {/*    text*/}
+                {/*    onClick={() => {*/}
+                {/*      router.push({*/}
+                {/*        path: '/message/wxTemplateMessage',*/}
+                {/*        query: row.id*/}
+                {/*      })*/}
+                {/*    }}*/}
+                {/*>*/}
+                {/*  消息内容配置*/}
+                {/*</NButton>*/}
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxTemplateConfig/status1790945458334535681"
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxTemplateConfig/update1790945691365871618"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxTemplateConfig/remove1790945187923562498"
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <NPageHeader on-back={() => gotoBack()} title={state.mpName}></NPageHeader>
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="wechat-config-template"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput placeholder="编号" v-model:value={state.searchForm.keyword} clearable />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                v-auth="wxTemplateConfig/save1790945603360985090"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增模板
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="wechat-config-template"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '模板'}
+              style={{ width: '700px' }}
+            >
+              <WechatConfigTemplateEdit
+                appid={state.appid}
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 355 - 0
src/views/message/message-config/wechat/wechat-config.tsx

@@ -0,0 +1,355 @@
+import {
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { useTabsViewStore } from '@/store/modules/tabsView'
+import SaveForm from '@components/save-form'
+import Pagination from '@components/pagination'
+import deepClone from '@/utils/deep.clone'
+import WechatConfigEdit from '@views/message/message-config/wechat/modal/wechat-config-edit'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import { getMapValueByKey } from '@/utils/objectUtil'
+import { appKey } from '@/utils/constant'
+import { wxConfigInfoPage, wxConfigInfoRemove, wxConfigInfoStatus } from '@views/message/api'
+
+export default defineComponent({
+  name: 'wechat-config',
+  setup(props, ctx) {
+    const route = useRoute()
+    const router = useRouter()
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        keyword: null, //关键字
+        appKey: null, //应用
+        status: null // 状态
+      },
+      name: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+      appData: [] as any
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      state.appData = []
+      const { data } = await sysApplicationPage({ page: 1, rows: 999 })
+      if (data && data.rows) {
+        data.rows.forEach((item: any) => {
+          state.appData.push({ label: item.appName, value: item.appKey })
+        })
+      }
+
+      getList()
+    })
+
+    const saveForm = ref()
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await wxConfigInfoPage({
+          ...state.pagination,
+          ...state.searchForm
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxConfigInfoStatus({
+              id: row.id,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const onRmove = (row: any): void => {
+      dialog.warning({
+        title: '提示',
+        content: `删除"${row.name}",是否继续?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await wxConfigInfoRemove(row.id)
+            getList()
+            message.success('删除成功')
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '应用',
+          key: 'appKey',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.appKey, new Map(Object.entries(appKey)))}</div>
+          }
+        },
+        {
+          title: '公众号名称',
+          key: 'mpName'
+        },
+        {
+          title: '公众号ID',
+          key: 'appid'
+        },
+        {
+          title: '接入密钥',
+          key: 'secret'
+        },
+        {
+          title: '消息密钥',
+          key: 'token'
+        },
+        {
+          title: '加密模式密钥',
+          key: 'aeskey'
+        },
+        {
+          title: '状态',
+          key: 'status',
+          render(row: any) {
+            return (
+              <NTag type={row.status ? 'primary' : 'default'}>{row.status ? '启用' : '停用'}</NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="message/wxTemplateConfig1788457824507092993"
+                  onClick={() => {
+                    router.push({
+                      path: '/message/wxTemplateConfig',
+                      query: {
+                        id: row.id
+                      }
+                    })
+                  }}
+                >
+                  查看模板
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxConfigInfo/status1790934722325581825"
+                  onClick={() => {
+                    onChangeStatus(row)
+                  }}
+                >
+                  {row.status ? '停用' : '启用'}
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxConfigInfo/update1790934555132235777"
+                  onClick={() => {
+                    state.showEdit = true
+                    state.editRowData = deepClone(row)
+                    state.editMode = 'edit'
+                  }}
+                >
+                  修改
+                </NButton>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="wxConfigInfo/remove1790934636120051714"
+                  disabled={!!row.status}
+                  onClick={() => {
+                    onRmove(row)
+                  }}
+                >
+                  删除
+                </NButton>
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => (
+      <div class="system-menu-container">
+        <div class={['section-container']}>
+          <div class="system-menu-container">
+            <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey="wechat-config"
+              onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="关键字" path="keyword">
+                <NInput
+                  placeholder="应用编号/名称"
+                  v-model:value={state.searchForm.keyword}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="应用" path="appKey">
+                <NSelect
+                  placeholder="请选择应用"
+                  v-model:value={state.searchForm.appKey}
+                  options={state.appData}
+                  clearable
+                />
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                  v-model:value={state.searchForm.status}
+                  clearable
+                  options={
+                    [
+                      {
+                        label: '启用',
+                        value: 1
+                      },
+                      {
+                        label: '停用',
+                        value: 0
+                      }
+                    ] as any
+                  }
+                  placeholder="全部状态"
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <NSpace style={{ paddingBottom: '12px' }}>
+              <NButton
+                type="primary"
+                v-auth="wxConfigInfo/save1790934473301364738"
+                onClick={() => {
+                  state.showEdit = true
+                  state.editMode = 'add'
+                }}
+              >
+                新增公众号配置
+              </NButton>
+            </NSpace>
+
+            <div class={['section-container']}>
+              <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+                rowKey={(row: any) => row.id}
+                scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+                saveKey="wechat-config"
+              ></Pagination>
+            </div>
+            <NModal
+              blockScroll={true}
+              v-model:show={state.showEdit}
+              preset="dialog"
+              showIcon={false}
+              title={(state.editMode == 'add' ? '新增' : '编辑') + '短信平台'}
+              // style={{width: 'auto'}}
+              style={{ width: '700px' }}
+            >
+              <WechatConfigEdit
+                editMode={state.editMode}
+                rowData={state.editRowData}
+                appData={state.appData}
+                onClose={() => (state.showEdit = false)}
+                onGetList={() => {
+                  state.pagination.page = 1
+                  getList()
+                }}
+              />
+            </NModal>
+          </div>
+        </div>
+      </div>
+    )
+  }
+})

+ 198 - 0
src/views/message/message-record/email-record.tsx

@@ -0,0 +1,198 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NFormItem, NInput, NSelect, NSpace, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getSelectDataFromObj} from '@/utils/objectUtil'
+import {clientType, messageSenderFunctionModule} from '@/utils/constant'
+import {sysMessagePage} from "@views/message/api";
+import TheTooltip from "@components/TheTooltip";
+
+export default defineComponent({
+  name: 'email-record',
+  props: {
+    appKey: {
+      type: String,
+      required: true
+    }
+  },
+
+  setup(props) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        title: null, //消息名称
+        clientId: null, //客户端
+        model: null, // 功能模块
+        status: null // 状态
+      },
+      dataList: []
+    })
+
+    onMounted(async () => {
+      getList()
+    })
+
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const {data} = await sysMessagePage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+          sendMode:'EMAIL',
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '标题',
+          key: 'title'
+        },
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '发送平台',
+          key: 'senderName'
+        },
+        {
+          title: '收件人地址',
+          key: 'receiver'
+        },
+        {
+          title: '正文',
+          key: 'content',
+          render: (row: any) => {
+            return <TheTooltip showContentWidth={300} content={row.content}/>
+          }
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="email-record"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="标题" path="title">
+                <NInput
+                    placeholder="请输入标题"
+                    v-model:value={state.searchForm.title}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="发送对象" path="clientId">
+                <NSelect
+                    placeholder="全部发送对象"
+                    v-model:value={state.searchForm.clientId}
+                    options={getSelectDataFromObj(clientType)}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="功能模块" path="model">
+                <NSelect
+                    filterable
+                    placeholder="全部功能模块"
+                    v-model:value={state.searchForm.model}
+                    options={getSelectDataFromObj(messageSenderFunctionModule)}
+                    clearable
+                ></NSelect>
+              </NFormItem>
+              <NFormItem label="状态" path="status">
+                <NSelect
+                    v-model:value={state.searchForm.status}
+                    placeholder="请选择状态"
+                    options={
+                      [
+                        {
+                          label: '启用',
+                          value: true
+                        },
+                        {
+                          label: '停用',
+                          value: false
+                        }
+                      ] as any
+                    }
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  data={state.dataList}
+                  rowKey={(row: any) => row.id}
+                  scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  onList={getList}
+                  sync
+                  saveKey="email-record"
+              ></Pagination>
+            </div>
+          </div>
+      )
+    }
+  }
+})

+ 82 - 0
src/views/message/message-record/index.tsx

@@ -0,0 +1,82 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, nextTick, onMounted, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+import { appKey } from '@/utils/constant'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import SmsRecord from '@views/message/message-record/sms-record'
+import PushRecord from '@views/message/message-record/push-record'
+import WechatRecord from '@views/message/message-record/wechat-record'
+import EmailRecord from '@views/message/message-record/email-record'
+
+export default defineComponent({
+  name: 'message-template-index',
+  setup() {
+    const state = reactive({
+      tabName: 'KT' as 'KT' | 'GYT' | 'KLX' | 'GYM' | 'CBS',
+      appKeyList: [] as any,
+      appNameList: [] as any,
+      messageSendMode: 'SMS' as 'SMS' | 'PUSH' | 'EMAIL' | 'WECHAT'
+    })
+    const tabsInstRef = ref()
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        state.tabName = val.form.tabName
+        nextTick(() => tabsInstRef.value?.syncBarPosition())
+      }
+    })
+    const route = useRoute()
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+
+    onMounted(async () => {
+      // 获取应用APP信息
+      // const appKeys = Object.keys(appKey)
+      const { data } = await sysApplicationPage({ page: 1, rows: 99, parentId: 0 })
+      const tempList = data.rows || []
+      tempList.forEach((next: any) => {
+        // if (appKeys.includes(next.appKey)) {
+        state.appKeyList.push(next.appKey)
+        state.appNameList.push(next.appName)
+        // }
+      })
+      nextTick(() => tabsInstRef.value?.syncBarPosition())
+    })
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <div class={['section-container']} style="padding-top: 0">
+            <NTabs
+              ref={tabsInstRef}
+              type="line"
+              size="large"
+              v-model:value={state.tabName}
+              onUpdate:value={(val: any) => setTabs(val)}
+            >
+              {state.appKeyList.map((app: any, index: number) => (
+                <NTabPane name={app} tab={state.appNameList[index]}>
+                  <NTabs type="card" size="small" v-model:value={state.messageSendMode}>
+                    <NTabPane name={'SMS'} tab={'短信'}>
+                      <SmsRecord appKey={app} />
+                    </NTabPane>
+                    <NTabPane name={'PUSH'} tab={'推送'}>
+                      <PushRecord appKey={app} />
+                    </NTabPane>
+                    <NTabPane name={'WECHAT'} tab={'公众号'}>
+                      <WechatRecord appKey={app} />
+                    </NTabPane>
+                    <NTabPane name={'EMAIL'} tab={'邮件'}>
+                      <EmailRecord appKey={app} />
+                    </NTabPane>
+                  </NTabs>
+                </NTabPane>
+              ))}
+            </NTabs>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 228 - 0
src/views/message/message-record/push-record.tsx

@@ -0,0 +1,228 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NFormItem, NInput, NSelect, NSpace, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {clientType, messageSenderFunctionModule} from '@/utils/constant'
+import {getTimes} from "@/utils/dateUtil";
+import {sysMessageConfigPage, sysMessagePage} from "@views/message/api";
+import TheTooltip from "@components/TheTooltip";
+
+export default defineComponent({
+  name: 'push-record',
+  props: {
+    appKey: {
+      type: String,
+      required: true
+    }
+  },
+
+  setup(props) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        title: null, //消息名称
+        receiver: null, // 接收人手机号
+        clientId: null, //客户端
+        group: null, // 功能模块
+        sendTime: null // 发送时间
+      },
+      dataList: []
+    })
+
+    onMounted(async () => {
+      getList()
+    })
+
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const {data} = await sysMessagePage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+          sendMode: 'PUSH',
+          ...getTimes(state.searchForm.sendTime, ['sendTimeStart', 'sendTimeEnd']),
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '标题',
+          key: 'title'
+        },
+        {
+          title: '推送内容',
+          key: 'content',
+          render: (row: any) => {
+            return (
+                <TheTooltip showContentWidth={400} content={row.content}/>
+            )
+          }
+        },
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '接收人姓名',
+          key: 'username'
+        },
+        {
+          title: '接收人手机号',
+          key: 'receiver'
+        },
+        {
+          title: '发送平台',
+          key: 'senderName'
+        },
+        {
+          title: '发送对象',
+          key: 'clientId',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '功能模块',
+          key: 'group',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.group, new Map(Object.entries(messageSenderFunctionModule)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="push-record"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="标题" path="title">
+                <NInput
+                    placeholder="请输入标题"
+                    v-model:value={state.searchForm.title}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="接收人手机号" path="receiver">
+                <NInput
+                    placeholder="请输入接收人手机号"
+                    v-model:value={state.searchForm.receiver}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="发送对象" path="clientId">
+                <NSelect
+                    placeholder="全部发送对象"
+                    v-model:value={state.searchForm.clientId}
+                    options={getSelectDataFromObj(clientType)}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="功能模块" path="group">
+                <NSelect
+                    filterable
+                    placeholder="全部功能模块"
+                    v-model:value={state.searchForm.group}
+                    options={getSelectDataFromObj(messageSenderFunctionModule)}
+                    clearable
+                ></NSelect>
+              </NFormItem>
+              <NFormItem label="发送时间" path="sendTime">
+                <NDatePicker
+                    v-model:value={state.searchForm.sendTime}
+                    type="daterange"
+                    clearable
+                    value-format="yyyy.MM.dd"
+                    startPlaceholder="开始时间"
+                    endPlaceholder="结束时间"
+                />
+              </NFormItem>
+
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  data={state.dataList}
+                  rowKey={(row: any) => row.id}
+                  scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  onList={getList}
+                  sync
+                  saveKey="push-record"
+              ></Pagination>
+            </div>
+          </div>
+      )
+    }
+  }
+})

+ 259 - 0
src/views/message/message-record/sms-record.tsx

@@ -0,0 +1,259 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import SaveForm from '@components/save-form'
+import {
+  DataTableRowKey,
+  NButton,
+  NDataTable,
+  NDatePicker,
+  NFormItem,
+  NInput,
+  NSelect,
+  NSpace,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import Pagination from '@components/pagination'
+import { getMapValueByKey, getSelectDataFromObj } from '@/utils/objectUtil'
+import { clientType, messageSenderFunctionModule, messageSendStatus } from '@/utils/constant'
+import { getTimes } from '@/utils/dateUtil'
+import { sysMessagePage } from '@views/message/api'
+import TheTooltip from '@components/TheTooltip'
+
+export default defineComponent({
+  name: 'sms-record',
+  props: {
+    appKey: {
+      type: String,
+      required: true
+    }
+  },
+
+  setup(props) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        title: null, //消息名称
+        clientId: null, //客户端
+        group: null, // 功能模块
+        sendTime: null, // 发送时间
+        status: null, // 发送状态
+        receiver: null // 接收人手机号
+      },
+      dataList: []
+    })
+
+    onMounted(async () => {
+      getList()
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await sysMessagePage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+          sendMode: 'SMS',
+          ...getTimes(state.searchForm.sendTime, ['sendTimeStart', 'sendTimeEnd'])
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '标题',
+          key: 'title'
+        },
+        {
+          title: '消息内容',
+          key: 'content',
+          render: (row: any) => {
+            return <TheTooltip showContentWidth={400} content={row.content} />
+          }
+        },
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '发送对象',
+          key: 'clientId',
+          render: (row: any) => {
+            return <div>{getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}</div>
+          }
+        },
+        {
+          title: '接收人姓名',
+          key: 'username'
+        },
+        {
+          title: '接收人手机号',
+          key: 'receiver'
+        },
+        {
+          title: '发送平台',
+          key: 'senderName'
+        },
+        {
+          title: '发送状态',
+          key: 'status',
+          render: (row: any) => {
+            return (
+              <div>{getMapValueByKey(row.status, new Map(Object.entries(messageSendStatus)))}</div>
+            )
+          }
+        },
+        {
+          title: '读取状态',
+          key: 'readStatus',
+          render: (row: any) => {
+            return <div>{row.readStatus ? '已读' : '未读'}</div>
+          }
+        },
+        {
+          title: '功能模块',
+          key: 'group',
+          render: (row: any) => {
+            return (
+              <div>
+                {getMapValueByKey(row.group, new Map(Object.entries(messageSenderFunctionModule)))}
+              </div>
+            )
+          }
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+        {
+          title: '错误信息',
+          key: 'errorMsg',
+          render: (row: any) => {
+            return <TheTooltip content={row.errorMsg} />
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <SaveForm
+            ref={saveForm}
+            model={state.searchForm}
+            onSubmit={onSubmit}
+            saveKey="sms-record"
+            onSetModel={(val: any) => (state.searchForm = val)}
+          >
+            <NFormItem label="标题" path="title">
+              <NInput placeholder="请输入标题" v-model:value={state.searchForm.title} clearable />
+            </NFormItem>
+            <NFormItem label="接收人手机号" path="receiver">
+              <NInput
+                placeholder="请输入接收人手机号"
+                v-model:value={state.searchForm.receiver}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="发送对象" path="clientId">
+              <NSelect
+                placeholder="全部发送对象"
+                v-model:value={state.searchForm.clientId}
+                options={getSelectDataFromObj(clientType)}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="发送状态" path="status">
+              <NSelect
+                placeholder="全部发送对象"
+                v-model:value={state.searchForm.status}
+                options={getSelectDataFromObj(messageSendStatus)}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="功能模块" path="group">
+              <NSelect
+                filterable
+                placeholder="全部功能模块"
+                v-model:value={state.searchForm.group}
+                options={getSelectDataFromObj(messageSenderFunctionModule)}
+                clearable
+              ></NSelect>
+            </NFormItem>
+            <NFormItem label="发送时间" path="sendTime">
+              <NDatePicker
+                v-model:value={state.searchForm.sendTime}
+                type="daterange"
+                clearable
+                value-format="yyyy.MM.dd"
+                startPlaceholder="开始时间"
+                endPlaceholder="结束时间"
+              />
+            </NFormItem>
+
+            <NFormItem>
+              <NSpace>
+                <NButton type="primary" onClick={onSearch}>
+                  搜索
+                </NButton>
+                <NButton type="default" onClick={onBtnReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </SaveForm>
+
+          <div class={['section-container']}>
+            <NDataTable
+              loading={state.loading}
+              columns={columns()}
+              data={state.dataList}
+              rowKey={(row: any) => row.id}
+              scrollX={'1400'}
+            ></NDataTable>
+
+            <Pagination
+              v-model:page={state.pagination.page}
+              v-model:pageSize={state.pagination.rows}
+              v-model:pageTotal={state.pagination.pageTotal}
+              onList={getList}
+              sync
+              saveKey="sms-record"
+            ></Pagination>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 240 - 0
src/views/message/message-record/wechat-record.tsx

@@ -0,0 +1,240 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDescriptions, NDescriptionsItem, NFormItem, NInput, NSelect, NSpace, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {clientType, messageSenderFunctionModule, musicSheetSourceType} from '@/utils/constant'
+import TheTooltip from "@components/TheTooltip";
+import {getOwnerName} from "@views/music-library/musicUtil";
+import {sysMessageConfigPage, sysMessagePage} from "@views/message/api";
+
+export default defineComponent({
+  name: 'wechat-record',
+  props: {
+    appKey: {
+      type: String,
+      required: true
+    }
+  },
+
+  setup(props) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        title: null, //消息名称
+        clientId: null, //客户端
+        model: null, // 功能模块
+        status: null // 状态
+      },
+      dataList: []
+    })
+
+    onMounted(async () => {
+      getList()
+    })
+
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const {data} = await sysMessagePage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+          sendMode:'WECHAT',
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '标题',
+          key: 'title'
+        },
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '发送平台',
+          key: 'senderName'
+        },
+        {
+          title: '接收人姓名',
+          key: 'username'
+        },
+        {
+          title: '接收人手机号',
+          key: 'receiver'
+        },
+        {
+          title: '消息内容',
+          key: 'content',
+          render: (row: any) => (
+              <>
+                <NDescriptions labelPlacement="left" column={1}>
+                  <NDescriptionsItem label="流程名称">
+                    {getMapValueByKey(row.sourceType, new Map(Object.entries(musicSheetSourceType)))}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="流程编号">
+                    {row.wxTemplateId}
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="申请人">
+                    <TheTooltip content={getOwnerName(row.musicSheetExtend, row.sourceType)} />
+                  </NDescriptionsItem>
+                  <NDescriptionsItem label="申请时间">
+                    <TheTooltip content={row.createTime} />
+                  </NDescriptionsItem>
+                </NDescriptions>
+              </>
+          )
+        },
+        {
+          title: '发送对象',
+          key: 'clientId',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '功能模块',
+          key: 'group',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.group, new Map(Object.entries(messageSenderFunctionModule)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+      ]
+    }
+
+    return () => {
+      return (
+          <div class="system-menu-container">
+            <SaveForm
+                ref={saveForm}
+                model={state.searchForm}
+                onSubmit={onSubmit}
+                saveKey="wechat-record"
+                onSetModel={(val: any) => (state.searchForm = val)}
+            >
+              <NFormItem label="标题" path="title">
+                <NInput
+                    placeholder="请输入标题"
+                    v-model:value={state.searchForm.title}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="发送对象" path="clientId">
+                <NSelect
+                    placeholder="全部发送对象"
+                    v-model:value={state.searchForm.clientId}
+                    options={getSelectDataFromObj(clientType)}
+                    clearable
+                />
+              </NFormItem>
+              <NFormItem label="功能模块" path="model">
+                <NSelect
+                    filterable
+                    placeholder="全部功能模块"
+                    v-model:value={state.searchForm.model}
+                    options={getSelectDataFromObj(messageSenderFunctionModule)}
+                    clearable
+                ></NSelect>
+              </NFormItem>
+              {/*<NFormItem label="状态" path="status">*/}
+              {/*  <NSelect*/}
+              {/*      v-model:value={state.searchForm.status}*/}
+              {/*      placeholder="请选择状态"*/}
+              {/*      options={*/}
+              {/*        [*/}
+              {/*          {*/}
+              {/*            label: '启用',*/}
+              {/*            value: true*/}
+              {/*          },*/}
+              {/*          {*/}
+              {/*            label: '停用',*/}
+              {/*            value: false*/}
+              {/*          }*/}
+              {/*        ] as any*/}
+              {/*      }*/}
+              {/*      clearable*/}
+              {/*  />*/}
+              {/*</NFormItem>*/}
+              <NFormItem>
+                <NSpace>
+                  <NButton type="primary" onClick={onSearch}>
+                    搜索
+                  </NButton>
+                  <NButton type="default" onClick={onBtnReset}>
+                    重置
+                  </NButton>
+                </NSpace>
+              </NFormItem>
+            </SaveForm>
+
+            <div class={['section-container']}>
+              <NDataTable
+                  loading={state.loading}
+                  columns={columns()}
+                  data={state.dataList}
+                  rowKey={(row: any) => row.id}
+                  scrollX={'1400'}
+              ></NDataTable>
+
+              <Pagination
+                  v-model:page={state.pagination.page}
+                  v-model:pageSize={state.pagination.rows}
+                  v-model:pageTotal={state.pagination.pageTotal}
+                  onList={getList}
+                  sync
+                  saveKey="wechat-record"
+              ></Pagination>
+            </div>
+          </div>
+      )
+    }
+  }
+})

+ 68 - 0
src/views/message/message-template/index.tsx

@@ -0,0 +1,68 @@
+import { NTabPane, NTabs } from 'naive-ui'
+import { defineComponent, h, nextTick, onMounted, reactive, ref } from 'vue'
+import { useRoute } from 'vue-router'
+import { getTabsCache, setTabsCaches } from '@/hooks/use-async'
+import { appKey } from '@/utils/constant'
+import { sysApplicationPage } from '@views/menu-manage/api'
+import MessageTemplateList from '@views/message/message-template/message-template-list'
+
+export default defineComponent({
+  name: 'message-template-index',
+  setup() {
+    const state = reactive({
+      tabName: 'KT' as 'KT' | 'GYT' | 'KLX' | 'GYM',
+      appKeyList: [] as any,
+      appNameList: [] as any
+    })
+    const tabsInstRef = ref()
+    getTabsCache((val: any) => {
+      if (val.form.tabName) {
+        state.tabName = val.form.tabName
+        nextTick(() => tabsInstRef.value?.syncBarPosition())
+      }
+    })
+    const route = useRoute()
+    const setTabs = (val: any) => {
+      setTabsCaches(val, 'tabName', route)
+    }
+
+    onMounted(async () => {
+      // 获取应用APP信息
+      const appKeys = Object.keys(appKey)
+      const { data } = await sysApplicationPage({ page: 1, rows: 99, parentId: 0 })
+      const tempList = data.rows || []
+      tempList.forEach((next: any) => {
+        // if (appKeys.includes(next.appKey)) {
+        state.appKeyList.push(next.appKey)
+        state.appNameList.push(next.appName)
+        // }
+      })
+      nextTick(() => tabsInstRef.value?.syncBarPosition())
+    })
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <div class={['section-container']} style="padding-top: 0">
+            <NTabs
+              ref={tabsInstRef}
+              type="line"
+              size="large"
+              v-model:value={state.tabName}
+              onUpdate:value={(val: any) => setTabs(val)}
+            >
+              {state.appKeyList.map((app: any, index: number) => (
+                <NTabPane name={app} tab={state.appNameList[index]}>
+                  <MessageTemplateList
+                    // v-auth="musicSheet/pageByApplication1751225386909302786"
+                    appKey={app}
+                  />
+                </NTabPane>
+              ))}
+            </NTabs>
+          </div>
+        </div>
+      )
+    }
+  }
+})

+ 349 - 0
src/views/message/message-template/message-template-list.tsx

@@ -0,0 +1,349 @@
+import { defineComponent, onMounted, reactive, ref } from 'vue'
+import SaveForm from '@components/save-form'
+import {
+  DataTableRowKey,
+  NButton,
+  NDataTable,
+  NFormItem,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import Pagination from '@components/pagination'
+import TheTooltip from '@components/TheTooltip'
+import { getMapValueByKey, getSelectDataFromObj } from '@/utils/objectUtil'
+import { clientType, messageSenderFunctionModule, messageSenderMode } from '@/utils/constant'
+import { sysMessageConfigPage, sysMessageConfigStatus } from '@views/message/api'
+import deepClone from '@/utils/deep.clone'
+import MessageTemplateEdit from '@views/message/message-template/modal/message-template-edit'
+
+export default defineComponent({
+  name: 'message-template-list',
+  props: {
+    appKey: {
+      type: String,
+      default: 'kT'
+    }
+  },
+
+  setup(props) {
+    const dialog = useDialog()
+    const message = useMessage()
+    const state = reactive({
+      loading: false,
+      appId: null as any,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        description: null, //消息名称
+        sendMode: null, // 消息类型
+        clientId: null, //客户端
+        model: null,
+        sendFlag: null
+      },
+      dataList: [] as any[],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any
+    })
+
+    onMounted(async () => {
+      // 加载表格数据
+      getList()
+    })
+
+    const saveForm = ref()
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const checkedRowKeysRef = ref<DataTableRowKey[]>([])
+    const handleCheck = (rowKeys: DataTableRowKey[]) => {
+      checkedRowKeysRef.value = rowKeys
+    }
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const { data } = await sysMessageConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {}
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.sendFlag ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await sysMessageConfigStatus({
+              id: row.id,
+              sendFlag: row.sendFlag == 1 ? 0 : 1
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {}
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '消息名称',
+          key: 'description'
+        },
+        {
+          title: '消息发送类型',
+          key: 'sendMode',
+          render: (row: any) => {
+            return (
+              <div>
+                {getMapValueByKey(row.sendMode, new Map(Object.entries(messageSenderMode)))}
+              </div>
+            )
+          }
+        },
+        {
+          title: '客户端',
+          key: 'clientId',
+          render: (row: any) => {
+            return <div>{getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}</div>
+          }
+        },
+        {
+          title: '消息类型',
+          key: 'messageType'
+        },
+        {
+          title: '功能模块',
+          key: 'group',
+          render: (row: any) => {
+            return (
+              <div>
+                {getMapValueByKey(row.group, new Map(Object.entries(messageSenderFunctionModule)))}
+              </div>
+            )
+          }
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+        {
+          title: '参数',
+          key: 'params'
+        },
+        {
+          title: '消息模板',
+          key: 'content',
+          minWidth: '200px',
+          render(row: any) {
+            return <TheTooltip showContentWidth={300} content={row.content} />
+          }
+        },
+        {
+          title: '示例',
+          key: 'contentExample',
+          render(row: any) {
+            return <TheTooltip showContentWidth={300} content={row.contentExample} />
+          }
+        },
+        {
+          title: '更新人',
+          key: 'operatorName'
+        },
+        {
+          title: '更新时间',
+          key: 'updateTime'
+        },
+        {
+          title: '状态',
+          key: 'sendFlag',
+          render(row: any) {
+            return (
+              <NTag type={row.sendFlag ? 'primary' : 'default'}>
+                {row.sendFlag ? '启用' : '停用'}
+              </NTag>
+            )
+          }
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+              <NSpace>
+                <NButton
+                  type="primary"
+                  size="small"
+                  text
+                  v-auth="sysMessageConfig/status1790940236048568322"
+                  onClick={() => onChangeStatus(row)}
+                >
+                  {row.sendFlag ? '停用' : '启用'}
+                </NButton>
+                {/*<NButton*/}
+                {/*    type="primary"*/}
+                {/*    size="small"*/}
+                {/*    text*/}
+                {/*    onClick={() => {*/}
+                {/*      state.showEdit = true*/}
+                {/*      state.editRowData = deepClone(row)*/}
+                {/*      state.editMode = 'edit'*/}
+                {/*    }}*/}
+                {/*>*/}
+                {/*  修改*/}
+                {/*</NButton>*/}
+              </NSpace>
+            )
+          }
+        }
+      ]
+    }
+
+    return () => {
+      return (
+        <div class="system-menu-container">
+          <SaveForm
+            ref={saveForm}
+            model={state.searchForm}
+            onSubmit={onSubmit}
+            saveKey="message-template"
+            onSetModel={(val: any) => (state.searchForm = val)}
+          >
+            <NFormItem label="消息名称" path="description">
+              <NInput
+                placeholder="请输入消息名称"
+                v-model:value={state.searchForm.description}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="消息类型" path="sendMode">
+              <NSelect
+                placeholder="全部类型"
+                v-model:value={state.searchForm.sendMode}
+                options={getSelectDataFromObj(messageSenderMode)}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="客户端" path="clientId">
+              <NSelect
+                placeholder="请选择所属项目"
+                v-model:value={state.searchForm.clientId}
+                options={getSelectDataFromObj(clientType)}
+                clearable
+              />
+            </NFormItem>
+            <NFormItem label="功能模块" path="model">
+              <NSelect
+                filterable
+                placeholder="全部功能模块"
+                options={getSelectDataFromObj(messageSenderFunctionModule)}
+                clearable
+              ></NSelect>
+            </NFormItem>
+            <NFormItem label="状态" path="status">
+              <NSelect
+                v-model:value={state.searchForm.sendFlag}
+                placeholder="请选择状态"
+                options={
+                  [
+                    {
+                      label: '启用',
+                      value: 1
+                    },
+                    {
+                      label: '停用',
+                      value: 0
+                    }
+                  ] as any
+                }
+                clearable
+              />
+            </NFormItem>
+            <NFormItem>
+              <NSpace>
+                <NButton type="primary" onClick={onSearch}>
+                  搜索
+                </NButton>
+                <NButton type="default" onClick={onBtnReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </SaveForm>
+
+          <div class={['section-container']}>
+            <NDataTable
+              loading={state.loading}
+              columns={columns()}
+              data={state.dataList}
+              rowKey={(row: any) => row.applicationExtendId}
+              // onUpdateCheckedRowKeys={handleCheck}
+              scrollX={'1400'}
+            ></NDataTable>
+
+            <Pagination
+              v-model:page={state.pagination.page}
+              v-model:pageSize={state.pagination.rows}
+              v-model:pageTotal={state.pagination.pageTotal}
+              onList={getList}
+              sync
+              saveKey="message-template"
+            ></Pagination>
+          </div>
+          <NModal
+            blockScroll={true}
+            v-model:show={state.showEdit}
+            preset="dialog"
+            showIcon={false}
+            title={(state.editMode == 'add' ? '新增' : '编辑') + '消息模板'}
+            // style={{width: 'auto'}}
+            style={{ width: '700px' }}
+          >
+            <MessageTemplateEdit
+              editMode={state.editMode}
+              rowData={state.editRowData}
+              onClose={() => (state.showEdit = false)}
+              onGetList={() => {
+                state.pagination.page = 1
+                getList()
+              }}
+            />
+          </NModal>
+        </div>
+      )
+    }
+  }
+})

+ 211 - 0
src/views/message/message-template/modal/message-template-edit.tsx

@@ -0,0 +1,211 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItemGi, NGrid, NInput, NSelect, NSpace, useMessage} from "naive-ui";
+import {smsConfigSave, smsConfigUpdate, sysMessageConfigSave, sysMessageConfigUpdate} from "@views/message/api";
+import {getSelectDataFromObj} from "@/utils/objectUtil";
+import {clientType, messageSenderFunctionModule} from "@/utils/constant";
+
+export default defineComponent({
+  name: 'message-template-edit',
+  props: {
+    editMode: {
+      type: String,
+      required: true
+    },
+    rowData: {
+      type: Object,
+      required: false
+    },
+  },
+  emits: ['close', 'getList'],
+  setup(props, {slots, attrs, emit}) {
+    const message = useMessage()
+    const btnLoading = ref(false)
+    const forms = reactive({
+      clientId: null,
+      content: null,
+      contentExample: null,
+      description: null,
+      group: null,
+      params: null,
+      triggerCondition: null,
+    })
+    const formsRef = ref()
+
+    const state = reactive({
+      rowData: null as any,
+      musicSheetCategories: [] as any,
+    })
+
+    onMounted(async () => {
+      state.rowData = props.rowData
+      if (props.editMode == 'edit' && props.rowData) {
+        forms.clientId = state.rowData.clientId
+        forms.content = state.rowData.content
+        forms.contentExample = state.rowData.contentExample
+        forms.description = state.rowData.description
+        forms.group = state.rowData.group
+        forms.params = state.rowData.params
+        forms.triggerCondition = state.rowData.triggerCondition
+      }
+    })
+
+    const onSubmit = async () => {
+      formsRef.value.validate(async (error: any) => {
+        if (error) return false
+        btnLoading.value = true
+        try {
+          let res;
+          if (props.editMode == 'add') {
+            res = await sysMessageConfigSave(
+                {
+                  ...forms,
+                }
+            ) as any;
+          } else {
+            res = await sysMessageConfigUpdate(
+                {
+                  ...forms,
+                  id: state.rowData.id
+                }
+            ) as any;
+          }
+          if (res && res.code === 200) {
+            emit('close')
+            emit('getList')
+          }
+        } catch (error) {
+        }
+        btnLoading.value = false
+      })
+    }
+
+    return () => {
+      return (
+          <div style="background: #fff; padding-top: 12px">
+            <NForm
+                ref={formsRef}
+                labelPlacement="top"
+                model={forms}
+                label-placement="left"
+                label-width="100"
+            >
+              <NGrid cols={2}>
+                <NFormItemGi
+                    label="客户端"
+                    path="clientId"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请选择客户端'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择客户端"
+                      v-model:value={forms.clientId}
+                      options={getSelectDataFromObj(clientType)}
+                      filterable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="功能模块"
+                    path="group"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请选择功能模块'
+                      }
+                    ]}
+                >
+                  <NSelect
+                      placeholder="请选择功能模块"
+                      v-model:value={forms.group}
+                      options={getSelectDataFromObj(messageSenderFunctionModule)}
+                      filterable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="参数"
+                    path="params"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入参数'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.params}
+                      placeholder="请输入参数"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="触发条件"
+                    path="triggerCondition"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入触发条件'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.triggerCondition}
+                      placeholder="请输入触发条件"
+                      clearable
+                  />
+                </NFormItemGi>
+              </NGrid>
+              <NGrid cols={1}>
+                <NFormItemGi
+                    label="消息内容"
+                    path="content"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入消息内容'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.content}
+                      placeholder="请输入消息内容"
+                      type={'textarea'}
+                      autosize={{minRows: 3}}
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="消息内容示例"
+                    path="contentExample"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入消息内容示例'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.contentExample}
+                      placeholder="请输入消息内容示例"
+                      type={'textarea'}
+                      autosize={{minRows: 3}}
+                  />
+                </NFormItemGi>
+              </NGrid>
+            </NForm>
+
+            <NSpace justify="end">
+              <NButton onClick={() => emit('close')}>取消</NButton>
+              <NButton type="primary" onClick={onSubmit}
+                       loading={btnLoading.value}
+                       disabled={btnLoading.value}
+              >
+                保存
+              </NButton>
+            </NSpace>
+          </div>
+      )
+    }
+  }
+})

+ 11 - 0
src/views/system-manage/api.ts

@@ -746,3 +746,14 @@ export const sysUserDeviceRemove = (params?: object) => {
     data: params
   } as any)
 }
+
+/**
+ * 系统错误日志 查询分页
+ */
+export const sysExceptionLogPage = (params?: object) => {
+  return request({
+    url: '/cbs-app/sysExceptionLog/page',
+    method: 'post',
+    data: params
+  } as any)
+}

+ 229 - 0
src/views/system-manage/expection-log/index.tsx

@@ -0,0 +1,229 @@
+import Pagination from '@/components/pagination'
+import {NButton, NDataTable, NDatePicker, NFormItem, NInput, NSelect, NSpace, NTag} from 'naive-ui'
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import {sysExceptionLogPage, sysSuggestionPage} from '../api'
+import {filterClientType, filterSuggestionType} from '@/utils/filters'
+import TheTooltip from '@/components/TheTooltip'
+import SaveForm from "@components/save-form";
+import {getMapValueByKey, getSelectDataFromObj} from "@/utils/objectUtil";
+import {appKey, appType, clientType, deviceType, exceptionType} from "@/utils/constant";
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+
+export default defineComponent({
+  name: 'expection-log-index',
+  setup() {
+    const state = reactive({
+      loading: false,
+      pagination: {
+        page: 1,
+        rows: 10,
+        pageTotal: 0
+      },
+      searchForm: {
+        appKey: null,
+        clientType: null,
+        phone: null,
+        appType: null,
+        exceptionType: null,
+        deviceType: null,
+        exceptionTime: null,
+        exceptionStartTime: null,
+        exceptionEndTime: null,
+      },
+      dataList: [] as any
+    })
+
+    const saveForm = ref()
+
+
+    onMounted(() => {
+      getList()
+    })
+
+
+    const getList = async () => {
+      try {
+        state.loading = true
+        const {data} = await sysExceptionLogPage({
+          ...state.pagination,
+          ...state.searchForm,
+          ...getTimes(state.searchForm.exceptionTime, ['exceptionStartTime', 'exceptionEndTime']),
+            },
+        )
+        state.loading = false
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+        state.loading = false
+      }
+    }
+
+    const onSearch = () => {
+      saveForm.value?.submit()
+    }
+    const onBtnReset = () => {
+      saveForm.value?.reset()
+    }
+
+    const onSubmit = () => {
+      state.pagination.page = 1
+      getList()
+    }
+
+    const columns = () => {
+      return [
+        {
+          title: '应用标识',
+          key: 'appKey',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.appKey, new Map(Object.entries(appKey)))}</div>
+          }
+        },
+        {
+          title: '客户端类型',
+          key: 'clientType',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.clientType, new Map(Object.entries(clientType)))}</div>
+          }
+        },
+        {
+          title: '手机号',
+          key: 'phone'
+        },
+        {
+          title: '异常类型',
+          key: 'exceptionType',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.exceptionType, new Map(Object.entries(exceptionType)))}</div>
+          }
+        },
+        {
+          title: '客户端信息',
+          key: 'userAgent',
+          render(row: any) {
+            return (
+                <TheTooltip showContentWidth={280} content={row.userAgent}/>
+            )
+          }
+        },
+        {
+          title: '应用类型',
+          key: 'appType',
+          render(row: any) {
+            return <div>{getMapValueByKey(row.appType, new Map(Object.entries(appType)))}</div>
+          }
+        },
+        {
+          title: '设备类型',
+          key: 'deviceType',
+        },
+        {
+          title: '设备版本',
+          key: 'deviceVersion'
+        },
+        {
+          title: '异常时间',
+          key: 'exceptionTime'
+        },
+        {
+          title: '内容',
+          key: 'content',
+          width: 400,
+          render(row: any) {
+            return (
+                <TheTooltip showContentWidth={280} content={row.content}/>
+            )
+          }
+        },
+      ]
+    }
+
+    return () => (
+        <div class="system-menu-container">
+          <SaveForm
+              ref={saveForm}
+              model={state.searchForm}
+              onSubmit={onSubmit}
+              saveKey='expection-log-index'
+              onSetModel={(val: any) => (state.searchForm = val)}
+          >
+            <NFormItem label="应用标识" path="appKey">
+              <NSelect
+                  v-model:value={state.searchForm.appKey}
+                  placeholder="请选择应用标识"
+                  clearable
+                  options={getSelectDataFromObj(appKey)}
+              />
+            </NFormItem>
+            <NFormItem label="客户端类型" path="clientType">
+              <NSelect
+                  v-model:value={state.searchForm.clientType}
+                  placeholder="请选择客户端类型"
+                  clearable
+                  options={getSelectDataFromObj(clientType)}
+              />
+            </NFormItem>
+            <NFormItem label="手机号" path="phone">
+              <NInput
+                  v-model:value={state.searchForm.phone}
+                  placeholder="请输入手机号"
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="应用类型" path="appType">
+              <NSelect
+                  v-model={[state.searchForm.appType, 'value']}
+                  placeholder="请选择应用类型"
+                  options={getSelectDataFromObj(appType)}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="异常类型" path="exceptionType">
+              <NSelect
+                  v-model={[state.searchForm.exceptionType, 'value']}
+                  placeholder="请选择异常类型"
+                  options={getSelectDataFromObj(exceptionType)}
+                  clearable
+              />
+            </NFormItem>
+            <NFormItem label="异常时间" path="exceptionTime">
+              <NDatePicker
+                  v-model:value={state.searchForm.exceptionTime}
+                  type="daterange"
+                  clearable
+                  value-format="yyyy-MM-dd HH:mm:dd"
+                  startPlaceholder="开始时间"
+                  endPlaceholder="结束时间"
+              />
+            </NFormItem>
+            <NFormItem>
+              <NSpace>
+                <NButton type="primary" onClick={onSearch}>
+                  搜索
+                </NButton>
+                <NButton type="default" onClick={onBtnReset}>
+                  重置
+                </NButton>
+              </NSpace>
+            </NFormItem>
+          </SaveForm>
+
+
+          <div class={['section-container']}>
+            <NDataTable
+                loading={state.loading}
+                columns={columns()}
+                data={state.dataList}
+            ></NDataTable>
+            <Pagination
+                v-model:page={state.pagination.page}
+                v-model:pageSize={state.pagination.rows}
+                v-model:pageTotal={state.pagination.pageTotal}
+                onList={getList}
+                sync
+            ></Pagination>
+          </div>
+        </div>
+    )
+  }
+})

+ 2 - 2
vite.config.ts

@@ -19,9 +19,9 @@ function pathResolve(dir: string) {
 }
 
 // const proxyUrl = 'https://dev.lexiaoya.cn'
-// const proxyUrl = 'http://127.0.0.1:7293/'
+const proxyUrl = 'http://127.0.0.1:7293/'
 // const proxyUrl = 'https://resource.colexiu.com/'
-const proxyUrl = 'https://test.resource.colexiu.com'
+// const proxyUrl = 'https://dev.resource.colexiu.com'
 // https://test.resource.colexiu.com/
 
 export default ({ command, mode }: ConfigEnv): UserConfig => {