Bläddra i källkod

消息模块添加

yuanliang 1 år sedan
förälder
incheckning
135f8578c1

+ 5 - 1
src/router/constant.ts

@@ -27,5 +27,9 @@ 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') ,// 消息配置应用
 }

+ 14 - 0
src/utils/constant.ts

@@ -278,4 +278,18 @@ export const musicSheetAvailableType = {
 export const musicSheetAudioType = {
   HOMEMODE: '自制伴奏',
   COMMON: '普通伴奏',
+} as any
+
+export const messageSenderMode = {
+  SMS: '短信',
+  PUSH: '推送',
+  WECHAT: '公众号',
+  EMAIL: '邮件',
+} as any
+
+export const messageSenderFunctionModule = {
+  PUSH: '报名',
+  SMS: '会员',
+  EMAIL: '课程',
+  WECHAT: '作业',
 } as any

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

@@ -0,0 +1,57 @@
+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, clientType, messageSenderMode} from '@/utils/constant'
+import {sysApplicationPage} from '@views/menu-manage/api'
+import MessageTemplateList from "@views/message/message-template/message-template-list";
+import {getMapValueByKey} from "@/utils/objectUtil";
+import MusicSheetKt from "@views/music-library/project-music-sheet/module/kt/music-sheet-kt";
+import SmsConfig from "@views/message/message-config/sms/sms-config";
+
+export default defineComponent({
+  name: 'message-config-index',
+  setup() {
+    const state = reactive({
+      tabName: 'SMS',
+    })
+    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)}
+              >
+                {Object.keys(messageSenderMode).map((app: any, index: number) => (
+                    <NTabPane name={app} tab={getMapValueByKey(app, new Map(Object.entries(messageSenderMode)))}>
+                      {/*{app == 'SMS' && (<SmsConfig/>)}*/}
+                      <SmsConfig/>
+                    </NTabPane>
+                ))}
+              </NTabs>
+            </div>
+          </div>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,129 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItem, NFormItemGi, NGrid, NInput, NSpace, useMessage} from "naive-ui";
+import {musicSheetApplicationExtendUpdate, smsConfigSave, smsConfigUpdate} from "@views/music-library/api";
+
+export default defineComponent({
+  name: 'sms-config-app-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({
+      appId: null,
+      autograph: 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.appId = state.rowData.appId
+        forms.autograph = state.rowData.autograph
+      }
+    })
+
+    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="appId"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入平台名称'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.appId}
+                      placeholder="请输入平台名称"
+                      clearable
+                  />
+                </NFormItemGi>
+                <NFormItemGi
+                    label="签名"
+                    path="autograph"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入签名'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.autograph}
+                      placeholder="请输入签名"
+                      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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,185 @@
+import {defineComponent, onMounted, reactive, ref} from "vue";
+import {NButton, NForm, NFormItem, NFormItemGi, NGrid, NInput, NSpace, useMessage} from "naive-ui";
+import {musicSheetApplicationExtendUpdate, smsConfigSave, smsConfigUpdate} from "@views/music-library/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,
+      accessUrl: null,
+      account: null,
+      password: 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.name = state.rowData.platformName
+        forms.accessUrl = state.rowData.accessUrl
+        forms.account = state.rowData.accessAccount
+        forms.password = state.rowData.accessPassword
+        forms.extendData = state.rowData.accessExtParam
+      }
+    })
+
+    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="accessUrl"
+                    rule={[
+                      {
+                        required: true,
+                        message: '请输入接入地址'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.accessUrl}
+                      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="extendData"
+                    rule={[
+                      {
+                        required: false,
+                        message: '请输入拓展参数'
+                      }
+                    ]}
+                >
+                  <NInput
+                      v-model:value={forms.extendData}
+                      placeholder="请输入拓展参数"
+                      autosize={{minRows: 3}}
+                      type={'textarea'}
+                      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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,262 @@
+import {NButton, NDataTable, NFormItem, NInput, NModal, NPageHeader, NSelect, NSpace, 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 SmsConfigEdit from "@views/message/message-config/sms/modal/sms-config-edit";
+import {appSendConfigPage, smsConfigDetail} from "@views/music-library/api";
+import deepClone from "@/utils/deep.clone";
+import SmsConfigAppEdit from "@views/message/message-config/sms/modal/sms-config-app-edit";
+
+export default defineComponent({
+  name: 'sms-config-app',
+  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 // 状态
+      },
+      name: null as any,
+      smsConfigId: null as any,
+      dataList: [],
+      showEdit: false,
+      editMode: 'add',
+      editRowData: {} as any,
+    })
+    const tabsViewStore = useTabsViewStore()
+    const gotoBack = () => {
+      tabsViewStore.closeCurrentTab(route)
+      router.push({
+        path: '/message/messageConfig'
+      })
+    }
+
+    onMounted(async () => {
+      state.smsConfigId = route.query.id;
+      console.log('1111111111', state.smsConfigId)
+      if (state.smsConfigId) {
+        const {data} = await smsConfigDetail(state.smsConfigId)
+        if (data) {
+          state.name = data.name
+        }
+        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 appSendConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id'
+        },
+        {
+          title: '应用',
+          key: 'name'
+        },
+        {
+          title: '签名',
+          key: 'accessUrl'
+        },
+        {
+          title: '状态',
+          key: 'status'
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      onClick={() => {
+
+
+                      }}
+                  >
+                    {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={() => {
+
+                      }}
+                  >
+                    删除
+                  </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"
+                  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="sms-config"
+                ></Pagination>
+              </div>
+              <NModal
+                  blockScroll={true}
+                  v-model:show={state.showEdit}
+                  preset="dialog"
+                  showIcon={false}
+                  title={(state.editMode == 'add' ? '新增' : '编辑') + '短信平台'}
+                  style={{width: 'auto'}}
+              >
+                <SmsConfigAppEdit
+                    editMode={state.editMode}
+                    rowData={state.editRowData}
+                    style={{width: '700px'}}
+                    onClose={() => (state.showEdit = false)}
+                    onGetList={() => {
+                      state.pagination.page = 1
+                      getList()
+                    }}
+                />
+              </NModal>
+            </div>
+          </div>
+        </div>
+    )
+  }
+})

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

@@ -0,0 +1,311 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NFormItem, NInput, NModal, NSelect, NSpace, NTag, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {appKey, clientType, messageSenderFunctionModule, messageSenderMode} from '@/utils/constant'
+import {
+  musicSheetApplicationExtendCategoryList,
+  musicSheetApplicationExtendStatus,
+  musicSheetRemove,
+  musicSheetStatusList,
+  smsConfigPage, smsConfigRemove,
+  smsConfigStatus,
+  sysMessageConfigPage
+} from '@views/music-library/api'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+import MusicPreView from "@views/music-library/music-sheet/modal/musicPreView";
+import SmsConfigEdit from "@views/message/message-config/sms/modal/sms-config-edit";
+import deepClone from "@/utils/deep.clone";
+import {useRouter} from "vue-router";
+
+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 || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const onChangeStatus = (row: any) => {
+      const statusStr = row.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await smsConfigStatus({
+              ids: new Array(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({id: row.id})
+            getList()
+            message.success('删除成功')
+          } catch {
+          }
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '编号',
+          key: 'id',
+        },
+        {
+          title: '平台名称',
+          key: 'name'
+        },
+        {
+          title: '接入地址',
+          key: 'accessUrl'
+        },
+        {
+          title: '接入账号',
+          key: 'account'
+        },
+        {
+          title: '接入密码',
+          key: 'password'
+        },
+        {
+          title: '拓展参数',
+          key: 'extendData'
+        },
+        {
+          title: '状态',
+          key: 'status'
+        },
+        {
+          title: '操作',
+          key: 'operation',
+          fixed: 'right',
+          render(row: any) {
+            return (
+                <NSpace>
+                  <NButton
+                      type="primary"
+                      size="small"
+                      text
+                      onClick={() => {
+                        router.push({
+                          path: '/message/messageConfigApp',
+                          query: row.id
+                        })
+                      }}
+                  >
+                    应用详情
+                  </NButton>
+                  <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 () => {
+      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"
+                  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'}}
+            >
+              <SmsConfigEdit
+                  editMode={state.editMode}
+                  rowData={state.editRowData}
+                  style={{width: '700px'}}
+                  onClose={() => (state.showEdit = false)}
+                  onGetList={() => {
+                    state.pagination.page = 1
+                    getList()
+                  }}
+              />
+            </NModal>
+          </div>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,201 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {appKey, clientType, messageSenderFunctionModule, messageSenderMode, musicSheetSourceType, musicSheetType} from '@/utils/constant'
+import {musicSheetApplicationExtendCategoryList, musicSheetApplicationExtendStatus, sysMessageConfigPage} from '@views/music-library/api'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+import TheTooltip from "@components/TheTooltip";
+import {getOwnerName} from "@views/music-library/musicUtil";
+
+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: {
+        description: 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 sysMessageConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '发送平台',
+          key: 'sendTime'
+        },
+        {
+          title: '收件人地址',
+          key: 'username'
+        },
+        {
+          title: '标题',
+          key: 'username'
+        },
+        {
+          title: '正文',
+          key: 'title'
+        },
+        {
+          title: '附件',
+          key: 'title'
+        },
+        {
+          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="description">
+                <NInput
+                    placeholder="请输入消息名称"
+                    v-model:value={state.searchForm.description}
+                    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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,107 @@
+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, messageSenderMode} from '@/utils/constant'
+import {sysApplicationPage} from '@views/menu-manage/api'
+import MessageTemplateList from "@views/message/message-template/message-template-list";
+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',
+      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="line"
+                          size="large"
+                          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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,209 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {appKey, clientType, messageSenderFunctionModule, messageSenderMode} from '@/utils/constant'
+import {musicSheetApplicationExtendCategoryList, musicSheetApplicationExtendStatus, sysMessageConfigPage} from '@views/music-library/api'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+
+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: {
+        description: null, //消息名称
+        clientId: null, //客户端
+        model: 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 sysMessageConfigPage({
+          ...state.pagination,
+          description: state.searchForm.description,
+          clientId: state.searchForm.clientId,
+          model: state.searchForm.model,
+          appKey: props.appKey,
+          ...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: 'sendTime'
+        },
+        {
+          title: '发送平台',
+          key: 'sender'
+        },
+        {
+          title: '姓名',
+          key: 'username'
+        },
+        {
+          title: '消息名称',
+          key: 'title'
+        },
+        {
+          title: '发送对象',
+          key: 'clientId',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '功能模块',
+          key: 'messageType'
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+        {
+          title: '推送标题',
+          key: 'title'
+        },
+        {
+          title: '推送内容',
+          key: 'content'
+        },
+      ]
+    }
+
+    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="description">
+                <NInput
+                    placeholder="请输入消息名称"
+                    v-model:value={state.searchForm.description}
+                    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="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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,205 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {appKey, clientType, messageSenderFunctionModule, messageSenderMode} from '@/utils/constant'
+import {musicSheetApplicationExtendCategoryList, musicSheetApplicationExtendStatus, sysMessageConfigPage} from '@views/music-library/api'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+
+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: {
+        description: null, //消息名称
+        clientId: null, //客户端
+        model: 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 sysMessageConfigPage({
+          ...state.pagination,
+          description: state.searchForm.description,
+          clientId: state.searchForm.clientId,
+          model: state.searchForm.model,
+          appKey: props.appKey,
+          ...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: 'sendTime'
+        },
+        {
+          title: '发送平台',
+          key: 'sender'
+        },
+        {
+          title: '姓名',
+          key: 'username'
+        },
+        {
+          title: '消息名称',
+          key: 'title'
+        },
+        {
+          title: '发送对象',
+          key: 'clientId',
+          render: (row: any) => {
+            return (
+                <div>
+                  {getMapValueByKey(row.clientId, new Map(Object.entries(clientType)))}
+                </div>
+            )
+          }
+        },
+        {
+          title: '功能模块',
+          key: 'messageType'
+        },
+        {
+          title: '触发条件',
+          key: 'triggerCondition'
+        },
+        {
+          title: '消息内容',
+          key: 'content'
+        },
+      ]
+    }
+
+    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="description">
+                <NInput
+                    placeholder="请输入短信名称"
+                    v-model:value={state.searchForm.description}
+                    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="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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,230 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {DataTableRowKey, NButton, NDataTable, NDatePicker, NDescriptions, NDescriptionsItem, NFormItem, NInput, NSelect, NSpace, NTag, useDialog, useMessage} from 'naive-ui'
+import Pagination from '@components/pagination'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {appKey, clientType, messageSenderFunctionModule, messageSenderMode, musicSheetSourceType, musicSheetType} from '@/utils/constant'
+import {musicSheetApplicationExtendCategoryList, musicSheetApplicationExtendStatus, sysMessageConfigPage} from '@views/music-library/api'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes, getTimes} from "@/utils/dateUtil";
+import TheTooltip from "@components/TheTooltip";
+import {getOwnerName} from "@views/music-library/musicUtil";
+
+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: {
+        description: 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 sysMessageConfigPage({
+          ...state.pagination,
+          ...state.searchForm,
+          appKey: props.appKey,
+        })
+        state.pagination.pageTotal = Number(data.total)
+        state.dataList = data.rows || []
+      } catch {
+      }
+      state.loading = false
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          title: '发送时间',
+          key: 'sendTime'
+        },
+        {
+          title: '姓名',
+          key: 'username'
+        },
+        {
+          title: 'openID',
+          key: 'openId'
+        },
+        {
+          title: '消息名称',
+          key: 'title'
+        },
+        {
+          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: 'messageType'
+        },
+        {
+          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="description">
+                <NInput
+                    placeholder="请输入消息名称"
+                    v-model:value={state.searchForm.description}
+                    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>
+      )
+    }
+  }
+})

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

@@ -0,0 +1,392 @@
+import {defineComponent, onMounted, reactive, ref} from 'vue'
+import SaveForm from '@components/save-form'
+import {
+  DataTableRowKey,
+  NButton,
+  NCascader,
+  NDataTable,
+  NDatePicker,
+  NDescriptions,
+  NDescriptionsItem,
+  NFormItem,
+  NIcon,
+  NImage,
+  NInput,
+  NModal,
+  NSelect,
+  NSpace,
+  NTag,
+  NTooltip,
+  useDialog,
+  useMessage
+} from 'naive-ui'
+import Pagination from '@components/pagination'
+import TheTooltip from '@components/TheTooltip'
+import AddMusic from '@views/music-library/project-music-sheet/module/gym/addMusic'
+import {getMapValueByKey, getSelectDataFromObj} from '@/utils/objectUtil'
+import {
+  appKey, clientType, messageSenderFunctionModule, messageSenderMode,
+  musicSheetAudioType,
+  musicSheetPaymentType,
+  musicSheetSourceType,
+  musicSheetType
+} from '@/utils/constant'
+import {
+  musicSheetApplicationExtendCategoryList,
+  musicSheetApplicationExtendDel,
+  musicSheetApplicationExtendStatus,
+  musicSheetApplicationOwnerList,
+  musicSheetPageByApplication,
+  musicSheetStatusList, sysMessageConfigPage
+} from '@views/music-library/api'
+import UpdateMusic from '@views/music-library/project-music-sheet/module/gym/updateMusic'
+import {subjectPage, sysApplicationPage} from '@views/system-manage/api'
+import {filterTimes} from '@/utils/dateUtil'
+import deepClone from '@/utils/deep.clone'
+import {getOwnerName} from '@views/music-library/musicUtil'
+import MusicPreView from '@views/music-library/music-sheet/modal/musicPreView'
+import {HelpCircleOutline} from '@vicons/ionicons5'
+
+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,
+        status: null
+      },
+      subjectList: [],
+      dataList: [] as any[],
+      musicSheetCategories: [],
+      showAddDialog: false,
+      showEditDialog: false,
+      userIdDisable: true,
+      userIdData: [] as any,
+      updateRow: {} as any, // 修改选择的行
+      applicationId: null, //应用ID
+      musicPreview: false,
+      musicScore: null as any,
+      useProjectData: [] as any // 适用项目行数据
+    })
+
+    onMounted(async () => {
+      state.loading = true
+      // 获取应用APP信息
+
+      try {
+        const {data} = await sysApplicationPage({page: 1, rows: 1, appKey: props.appKey})
+        const tempList = data.rows || []
+        if (!tempList || tempList.length == 0) {
+          state.loading = false
+          message.error('加载项目信息失败')
+          return
+        }
+        state.appId = tempList[0].id
+        state.applicationId = tempList[0].id
+      } catch {
+      }
+
+      // 加载声部
+
+      try {
+        const {data} = await subjectPage({page: 1, rows: 999})
+        const tempList = data.rows || []
+        tempList.forEach((item: any) => {
+          item.label = item.name
+          item.value = item.id + ''
+        })
+        state.subjectList = tempList
+      } catch {
+      }
+
+      //加载曲目分类列表
+
+      try {
+        const {data} = await musicSheetApplicationExtendCategoryList({
+          applicationIds: state.appId
+        })
+        if (data && data.length > 0) {
+          state.musicSheetCategories = data[0].musicSheetCategories
+        }
+      } catch {
+      }
+
+      // 加载表格数据
+      initUseAppList()
+      getList()
+    })
+
+    const initUseAppList = async () => {
+      try {
+        const appKeys = Object.keys(appKey)
+        const {data} = await sysApplicationPage({page: 1, rows: 999})
+        const tempList = data.rows || []
+        state.useProjectData = []
+        const filter = tempList.filter((next: any) => {
+          return appKeys.includes(next.appKey)
+        })
+        filter.forEach((item: any) => {
+          state.useProjectData.push({
+            ...item,
+            label: item.appName,
+            value: item.id
+          })
+        })
+      } catch {
+      }
+    }
+
+    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.status ? '停用' : '启用'
+      dialog.warning({
+        title: '提示',
+        content: `是否${statusStr}?`,
+        positiveText: '确定',
+        negativeText: '取消',
+        onPositiveClick: async () => {
+          try {
+            await musicSheetApplicationExtendStatus({
+              ids: row.applicationExtendId,
+              status: !row.status
+            })
+            getList()
+            message.success(`${statusStr}成功`)
+          } catch {
+          }
+        }
+      })
+    }
+
+    const columns = (): any => {
+      return [
+        {
+          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: 'triggerCondition'
+        },
+        {
+          title: '消息模板',
+          key: 'content'
+        },
+        {
+          title: '示例',
+          key: 'contentExample'
+        },
+        {
+          title: '更新人',
+          key: 'operatorName'
+        },
+        {
+          title: '更新时间',
+          key: 'updateTime'
+        },
+        {
+          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="musicSheetApplicationExtend/status1751235150422736897"
+                      onClick={() => onChangeStatus(row)}
+                  >
+                    {row.status ? '停用' : '启用'}
+                  </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.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.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>
+          </div>
+      )
+    }
+  }
+})

+ 86 - 0
src/views/music-library/api.ts

@@ -342,3 +342,89 @@ export const musicSheetApplicationExtendDel = (applicationExtendId?: any) => {
     method: 'post'
   } as any)
 }
+
+/**
+ * @description: 消息模板
+ */
+export const sysMessageConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/sysMessageConfig/page',
+    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,
+    method: 'post',
+  } as any)
+}
+
+/**
+ * @description: 短信配置详情
+ */
+export const smsConfigDetail = (id: object) => {
+  return request({
+    url: '/cbs-app/smsConfig/detail/' + id,
+    method: 'get',
+  } as any)
+}
+
+/**
+ * @description: app推送配置表分页
+ */
+export const appSendConfigPage = (params: object) => {
+  return request({
+    url: '/cbs-app/appSendConfig/page',
+    method: 'post',
+    data: params
+  } as any)
+}

+ 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://test.resource.colexiu.com'
 // https://test.resource.colexiu.com/
 
 export default ({ command, mode }: ConfigEnv): UserConfig => {