Kaynağa Gözat

消息模板管理

Alex 9 ay önce
ebeveyn
işleme
ab374f6e52

+ 240 - 0
src/views/marketing/messagePush/components/message-push-dialog.vue

@@ -0,0 +1,240 @@
+<template>
+  <el-dialog
+    title="消息推送"
+    v-model="visible"
+    width="800px"
+    destroy-on-close
+  >
+    <el-form
+      ref="sendFormRef"
+      :model="sendForm"
+      :rules="rules"
+      label-width="100px"
+      class="send-form"
+    >
+      <el-form-item>
+        <el-radio-group v-model="sendForm.userType">
+          <el-radio :label="0">全平台用户</el-radio>
+          <el-radio :label="1">部分用户</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item v-if="sendForm.userType === 1" label="搜索条件:" prop="userSearch">
+        <el-input
+          v-model="sendForm.userSearch"
+          placeholder="请输入搜索条件"
+          clearable
+        />
+      </el-form-item>
+
+      <div class="content-section">
+        <div class="section-title">设置消息内容</div>
+
+        <el-form-item label="消息模板:" prop="templateId" required>
+          <el-select
+            v-model="sendForm.templateId"
+            placeholder="请选择消息模板"
+            clearable
+            @change="handleTemplateChange"
+          >
+            <el-option
+              v-for="item in templateList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="签名名称:" prop="sign">
+          <el-input
+            v-model="sendForm.sign"
+            placeholder="请选择模板后自动填充"
+            disabled
+          />
+        </el-form-item>
+
+        <el-form-item label="短信内容:" prop="content">
+          <el-input
+            v-model="sendForm.content"
+            type="textarea"
+            :rows="4"
+            placeholder="请选择模板后自动填充"
+            disabled
+          />
+        </el-form-item>
+
+        <el-form-item label="备注:" prop="remark">
+          <el-input
+            v-model="sendForm.remark"
+            placeholder="请输入"
+            clearable
+          />
+        </el-form-item>
+      </div>
+
+      <el-form-item label="发送方式:" prop="sendType">
+        <el-radio-group v-model="sendForm.sendType">
+          <el-radio :label="0">立即发送</el-radio>
+          <el-radio :label="1">定时发送</el-radio>
+        </el-radio-group>
+      </el-form-item>
+
+      <el-form-item
+        v-if="sendForm.sendType === 1"
+        label="发送时间:"
+        prop="sendTime"
+      >
+        <el-date-picker
+          v-model="sendForm.sendTime"
+          type="datetime"
+          placeholder="选择时间"
+        />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false">取 消</el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmitSend">
+        确 定
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, onMounted } from 'vue';
+import { EleMessage } from 'ele-admin-plus/es';
+import request from '@/utils/request';
+
+defineOptions({ name: 'MessagePushDialog' });
+
+const emit = defineEmits(['success']);
+const visible = defineModel({ type: Boolean });
+const loading = ref(false);
+const sendFormRef = ref(null);
+const templateList = ref([]);
+
+const sendForm = reactive({
+  userType: 0,
+  userSearch: '',
+  templateId: '',
+  sign: '',
+  content: '',
+  remark: '',
+  sendType: 0,
+  sendTime: ''
+});
+
+const rules = {
+  templateId: [{ required: true, message: '请选择消息模板', trigger: 'change' }],
+  userSearch: [{ required: true, message: '请输入搜索条件', trigger: 'blur' }],
+  sendTime: [{ required: true, message: '请选择发送时间', trigger: 'change' }]
+};
+
+// 获取模板列表
+const getTemplateList = async () => {
+  try {
+    const { data } = await request.get('/message/templetemsg/listNormal');
+    if (data.code === 200) {
+      templateList.value = data.data;
+    }
+  } catch (e) {
+    console.error('获取模板列表失败:', e);
+  }
+};
+
+// 模板选择变更
+const handleTemplateChange = async (templateId) => {
+  if (!templateId) {
+    sendForm.sign = '';
+    sendForm.content = '';
+    return;
+  }
+  try {
+    const { data } = await request.get(`/message/templetemsg/getInfo/${templateId}`);
+    if (data.code === 200) {
+      sendForm.sign = data.data.sign;
+      sendForm.content = data.data.content;
+    }
+  } catch (e) {
+    console.error('获取模板详情失败:', e);
+  }
+};
+
+function handleReset() {
+  Object.assign(sendForm, {
+    userType: 0,
+    userSearch: '',
+    templateId: '',
+    sign: '',
+    content: '',
+    remark: '',
+    sendType: 0,
+    sendTime: ''
+  });
+}
+
+async function handleSubmitSend() {
+  if (loading.value) return;
+  try {
+    const valid = await sendFormRef.value?.validate();
+    if (!valid) return;
+    loading.value = true;
+
+    // 构建请求参数
+    const params = {
+      templateId: sendForm.templateId,
+      userType: sendForm.userType,
+      userSearch: sendForm.userSearch,
+      remark: sendForm.remark,
+      sendType: sendForm.sendType,
+      sendTime: sendForm.sendTime ? sendForm.sendTime.toISOString() : undefined
+    };
+
+    const { data } = await request.post('/message/message/add', params);
+    if (data.code === 200) {
+      EleMessage.success('发送成功');
+      visible.value = false;
+      handleReset();
+      emit('success');
+    } else {
+      EleMessage.error(data.msg);
+    }
+  } catch (e) {
+    EleMessage.error(e.message);
+  } finally {
+    loading.value = false;
+  }
+}
+
+// 打开弹窗时获取模板列表
+const handleOpen = () => {
+  getTemplateList();
+  handleReset();
+};
+
+defineExpose({ handleOpen });
+</script>
+
+<style lang="scss" scoped>
+.send-form {
+  padding: 0 20px;
+}
+
+.content-section {
+  margin: 20px 0;
+
+  .section-title {
+    font-weight: bold;
+    margin-bottom: 20px;
+  }
+}
+
+:deep(.el-form-item__label) {
+  color: #333;
+}
+
+:deep(.el-input), :deep(.el-textarea) {
+  width: 100%;
+}
+</style>

+ 124 - 0
src/views/marketing/messagePush/components/message-push.vue

@@ -0,0 +1,124 @@
+<template>
+  <div class="message-push">
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :bodyStyle="{ padding: 0 }"
+    >
+      <template #toolbar>
+        <div style="display: flex; align-items: center; gap: 10px">
+          <el-button
+            type="primary"
+            plain
+            :icon="PlusOutlined"
+            v-permission="'marketing:messagePush:add'"
+            @click="handleAdd()"
+          >
+            新增推送
+          </el-button>
+
+          <div style="display: flex; align-items: center; gap: 10px">
+            <el-input
+              v-model="search"
+              placeholder="搜索消息内容"
+              style="width: 300px"
+            />
+            <el-button type="primary" @click="reload()">查询</el-button>
+            <el-button type="primary" plain @click="reset">重置</el-button>
+          </div>
+        </div>
+      </template>
+    </common-table>
+
+    <message-push-dialog ref="dialogRef" @success="reload()"></message-push-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { EleMessage } from 'ele-admin-plus/es';
+import { PlusOutlined } from '@/components/icons';
+import CommonTable from '@/components/CommonPage/CommonTable.vue';
+import MessagePushDialog from './message-push-dialog.vue';
+import request from '@/utils/request';
+
+defineOptions({ name: 'MessagePush' });
+
+const search = ref('');
+const pageRef = ref(null);
+const dialogRef = ref(null);
+
+const reset = () => {
+  search.value = '';
+  reload();
+};
+
+/** 表格列配置 */
+const columns = ref([
+  {
+    label: '模板名称',
+    prop: 'templateName',
+    align: 'center',
+    minWidth: 140
+  },
+  {
+    label: '发送类型',
+    prop: 'userType',
+    align: 'center',
+    width: 100,
+    formatter: (row) => {
+      return row.userType === 0 ? '全平台用户' : '部分用户';
+    }
+  },
+  {
+    label: '搜索条件',
+    prop: 'userSearch',
+    align: 'center',
+    minWidth: 140
+  },
+  {
+    label: '发送方式',
+    prop: 'sendType',
+    align: 'center',
+    width: 100,
+    formatter: (row) => {
+      return row.sendType === 0 ? '立即发送' : '定时发送';
+    }
+  },
+  {
+    label: '发送时间',
+    prop: 'sendTime',
+    align: 'center',
+    minWidth: 160
+  },
+  {
+    label: '备注',
+    prop: 'remark',
+    align: 'center',
+    minWidth: 140
+  },
+  {
+    label: '添加时间',
+    prop: 'createTime',
+    align: 'center',
+    minWidth: 160
+  }
+]);
+
+const pageConfig = reactive({
+  pageUrl: '/message/message/pagelist',
+  fileName: '消息推送',
+  cacheKey: 'messagePushTable'
+});
+
+// 刷新表格
+function reload(where) {
+  pageRef.value?.reload({ ...where, content: search.value });
+}
+
+// 新增推送
+function handleAdd() {
+  dialogRef.value?.handleOpen();
+}
+</script>

+ 148 - 0
src/views/marketing/messagePush/components/template-edit.vue

@@ -0,0 +1,148 @@
+<template>
+  <el-dialog
+    title="添加短信模板"
+    v-model="visible"
+    width="600px"
+    destroy-on-close
+  >
+    <el-form
+      ref="formRef"
+      :model="form"
+      :rules="rules"
+      label-width="100px"
+      @keyup.enter="handleSubmit"
+    >
+      <el-form-item label="模板名称" prop="name">
+        <el-input
+          v-model="form.name"
+          placeholder="请输入"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="签名名称" prop="sign">
+        <el-input
+          v-model="form.sign"
+          placeholder="请输入"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="短信内容" prop="content">
+        <el-input
+          v-model="form.content"
+          type="textarea"
+          :rows="6"
+          placeholder="请输入"
+        />
+      </el-form-item>
+      <el-form-item label="备注">
+        <el-input
+          v-model="form.remark"
+          placeholder="请输入"
+          clearable
+        />
+      </el-form-item>
+      <el-form-item label="推送方式" prop="pushWay">
+        <el-checkbox-group v-model="form.pushWay">
+          <el-checkbox label="1">小程序站内推送</el-checkbox>
+          <el-checkbox label="2">公众号推送</el-checkbox>
+          <el-checkbox label="3">短信推送</el-checkbox>
+        </el-checkbox-group>
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <el-button @click="visible = false">取 消</el-button>
+      <el-button type="primary" :loading="loading" @click="handleSubmit">
+        确 定
+      </el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'TemplateEdit' });
+
+  const emit = defineEmits(['success']);
+  const visible = ref(false);
+  const loading = ref(false);
+  const formRef = ref(null);
+  const editId = ref(null);
+
+  const form = reactive({
+    name: '',
+    sign: '',
+    content: '',
+    remark: '',
+    pushWay: []
+  });
+
+  const rules = {
+    name: [
+      { required: true, message: '请输入模板名称', trigger: 'blur' }
+    ],
+    sign: [{ required: true, message: '请输入签名名称', trigger: 'blur' }],
+    content: [
+      { required: true, message: '请输入短信内容', trigger: 'blur' }
+    ]
+  };
+
+  // 打开弹窗
+  function handleOpen(row) {
+    visible.value = true;
+    editId.value = null;
+    form.name = '';
+    form.sign = '';
+    form.content = '';
+    form.remark = '';
+    form.pushWay = [];
+    if (row) {
+      Object.assign(form, row);
+      editId.value = row.id;
+      if (typeof form.pushWay === 'string') {
+        form.pushWay = form.pushWay.split(',');
+      }
+    }
+  }
+
+  // 提交表单
+  async function handleSubmit() {
+    if (loading.value) return;
+    try {
+      const valid = await formRef.value?.validate();
+      if (!valid) return;
+      loading.value = true;
+
+      const params = {
+        name: form.name,
+        sign: form.sign,
+        content: form.content,
+        remark: form.remark,
+        pushWay: form.pushWay.join(',')
+      };
+
+      if (editId.value) {
+        params.id = editId.value;
+      }
+
+      const url = editId.value ? '/message/templetemsg/update' : '/message/templetemsg/add';
+      const { data } = await request.post(url, params);
+
+      if (data.code === 200) {
+        EleMessage.success(editId.value ? '修改成功' : '添加成功');
+        visible.value = false;
+        emit('success');
+      } else {
+        EleMessage.error(data.msg);
+      }
+    } catch (e) {
+      EleMessage.error(e.message);
+    } finally {
+      loading.value = false;
+    }
+  }
+
+  defineExpose({ handleOpen });
+</script>

+ 273 - 0
src/views/marketing/messagePush/components/template-manage.vue

@@ -0,0 +1,273 @@
+<template>
+  <div class="template-manage">
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :bodyStyle="{ padding: 0 }"
+    >
+      <template #toolbar>
+        <div style="display: flex; align-items: center; gap: 10px">
+          <el-button
+            type="primary"
+            plain
+            :icon="PlusOutlined"
+            v-permission="'marketing:messagePush:add'"
+            @click="handleUpdate()"
+          >
+            新增模板
+          </el-button>
+          <el-button
+            type="danger"
+            plain
+            :icon="DeleteOutlined"
+            v-permission="'marketing:messagePush:batchDelete'"
+            @click="handleDelete()"
+          >
+            批量删除
+          </el-button>
+
+          <div style="display: flex; align-items: center; gap: 10px">
+            <el-input
+              v-model="search"
+              placeholder="搜索模板名称"
+              style="width: 300px"
+            />
+            <el-button type="primary" @click="reload()">查询</el-button>
+            <el-button type="primary" plain @click="reset">重置</el-button>
+          </div>
+        </div>
+      </template>
+
+      <template #miniappNotice="{ row }">
+        <el-switch
+          :model-value="row.pushWay.indexOf('1') !== -1"
+          style="--el-switch-on-color: #13ce66"
+          @change="statusSwitchChange($event, row, '1')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.pushWay.indexOf('1') !== -1 ? '已开启' : '已关闭'
+        }}</el-text>
+      </template>
+
+      <template #officialNotice="{ row }">
+        <el-switch
+          :model-value="row.pushWay.indexOf('2') !== -1"
+          style="--el-switch-on-color: #13ce66"
+          @change="statusSwitchChange($event, row, '2')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.pushWay.indexOf('2') !== -1 ? '已开启' : '已关闭'
+        }}</el-text>
+      </template>
+
+      <template #smsNotice="{ row }">
+        <el-switch
+          :model-value="row.pushWay.indexOf('3') !== -1"
+          style="--el-switch-on-color: #13ce66"
+          @change="statusSwitchChange($event, row, '3')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.pushWay.indexOf('3') !== -1 ? '已开启' : '已关闭'
+        }}</el-text>
+      </template>
+
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'marketing:messagePush:edit'"
+            @click="handleUpdate(row)"
+          >
+            [编辑]
+          </el-button>
+          <el-button
+            type="danger"
+            link
+            v-permission="'marketing:messagePush:delete'"
+            @click="handleDelete(row)"
+          >
+            [删除]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+
+    <template-edit ref="editRef" @success="reload()"></template-edit>
+  </div>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { PlusOutlined, DeleteOutlined } from '@/components/icons';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import TemplateEdit from './template-edit.vue';
+  import request from '@/utils/request';
+
+  const search = ref('');
+  const reset = () => {
+    search.value = '';
+    reload();
+  };
+  defineOptions({ name: 'TemplateManage' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '模板名称',
+      prop: 'name',
+      align: 'center',
+      minWidth: 140
+    },
+    {
+      label: '模板签名',
+      prop: 'sign',
+      align: 'center',
+      minWidth: 120
+    },
+    {
+      label: '内容',
+      prop: 'content',
+      align: 'center',
+      minWidth: 240
+    },
+    {
+      label: '小程序消息通知',
+      prop: 'miniappNotice',
+      align: 'center',
+      minWidth: 120,
+      slot: 'miniappNotice'
+    },
+    {
+      label: '公众号消息通知',
+      prop: 'officialNotice',
+      align: 'center',
+      minWidth: 120,
+      slot: 'officialNotice'
+    },
+    {
+      label: '短信消息通知',
+      prop: 'smsNotice',
+      align: 'center',
+      minWidth: 120,
+      slot: 'smsNotice'
+    },
+    {
+      label: '添加时间',
+      prop: 'createTime',
+      align: 'center',
+      minWidth: 160
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 160,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+  const editRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/message/templetemsg/pagelist',
+    fileName: '消息模板',
+    cacheKey: 'messageTemplateTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload({ ...where, name: search.value });
+  }
+
+  //修改状态
+  const statusSwitchChange = (value, row, key) => {
+    let data = JSON.parse(JSON.stringify(row));
+    let message = value == 0 ? '确认关闭?' : '确认开启?';
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '关闭',
+      type: 'warning'
+    })
+      .then(() => {
+        let pushWay = data.pushWay ? data.pushWay.split(',') : [];
+        if (!value) {
+          pushWay = pushWay.filter((item) => item !== key);
+        } else {
+          pushWay.push(key);
+        }
+        let params = {
+          id: data.id,
+          pushWay: pushWay.join(',')
+        };
+
+        request
+          .post('/message/templetemsg/setSendWay?id=' + params.id + '&pushWay=' + params.pushWay)
+          .then((res) => {
+            if (res.data.code === 200) {
+              EleMessage.success('操作成功');
+              reload();
+          } else {
+            EleMessage.error(res.data.msg);
+          }
+        });
+      })
+      .catch(() => {
+        row[key] = value == 1 ? 0 : 1;
+      });
+  };
+
+  //删除
+  function handleDelete(row) {
+    let ids = [];
+    let message = '确认删除该模板?';
+
+    if (row?.id) {
+      // 单个删除
+      ids = [row.id];
+    } else {
+      // 批量删除
+      const selections = pageRef.value?.getSelections();
+      if (!selections || selections.length === 0) {
+        EleMessage.warning('请选择要删除的数据');
+        return;
+      }
+      ids = selections.map((item) => item.id);
+      message = `确认删除选中的 ${ids.length} 条数据?`;
+    }
+
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '取消',
+      type: 'warning'
+    }).then(() => {
+      request
+        .post('/message/templetemsg/removeTmp', { idList: ids })
+        .then((res) => {
+          if (res.data.code === 200) {
+            EleMessage.success('删除成功');
+            reload();
+          } else {
+            EleMessage.error(res.data.msg);
+          }
+        });
+    });
+  }
+
+  //编辑页面
+  function handleUpdate(row) {
+    editRef.value?.handleOpen(row);
+  }
+</script>

+ 39 - 1
src/views/marketing/messagePush/index.vue

@@ -1,3 +1,41 @@
 <template>
-  <div> 消息推送 </div>
+  <ele-page flex-table>
+    <div class="message-push-container">
+      <el-tabs v-model="activeTab">
+        <el-tab-pane label="模板管理" name="template">
+          <template-manage ref="templateRef"></template-manage>
+        </el-tab-pane>
+        <el-tab-pane label="消息推送" name="push">
+          <message-push ref="pushRef"></message-push>
+        </el-tab-pane>
+      </el-tabs>
+    </div>
+  </ele-page>
 </template>
+
+<script setup>
+  import { ref } from 'vue';
+  import TemplateManage from './components/template-manage.vue';
+  import MessagePush from './components/message-push.vue';
+
+  defineOptions({ name: 'MessagePush' });
+
+  const activeTab = ref('template');
+  const templateRef = ref(null);
+  const pushRef = ref(null);
+</script>
+
+<style lang="scss" scoped>
+  .message-push-container {
+    padding: 16px;
+    height: calc(100% - 10px);
+    background-color: #fff;
+    box-sizing: border-box;
+
+    .partner-tabs {
+      :deep(.el-tabs__header) {
+        margin-bottom: 0;
+      }
+    }
+  }
+</style>