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

feat(web): 改版v1 通知模板管理页 (W3)

- 新增 api/notificationTemplate.js 接口封装
- 新增 NotificationTemplateManage.vue 管理页
  - 类型/标题/内容模板/推送通道(小程序内/短信/微信/电话)/状态
  - 通道多选 + 占位符提示 {domainName} {reward}
- 路由注册 notification-template-manage

Ultraworked with [Sisyphus](https://github.com/OhMyOpenCode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 1 месяц назад
Родитель
Сommit
c587c59d68

+ 16 - 0
cfc-web/src/api/notificationTemplate.js

@@ -0,0 +1,16 @@
+import request from '@/utils/request'
+
+// 通知模板列表
+export function getNotificationTemplateList(params) {
+  return request({ url: '/api/admin/notification-template/list', method: 'post', data: params || {} })
+}
+
+// 新增/更新通知模板
+export function saveNotificationTemplate(data) {
+  return request({ url: '/api/admin/notification-template/save', method: 'post', data })
+}
+
+// 删除通知模板
+export function deleteNotificationTemplate(data) {
+  return request({ url: '/api/admin/notification-template/delete', method: 'post', data })
+}

+ 6 - 0
cfc-web/src/router/index.js

@@ -581,6 +581,12 @@ const routes = [
           component: () => import('@/views/admin/ProblemDomainManage.vue'),
           meta: { title: '问题域管理', perm: 'admin:all' }
         },
+        {
+          path: 'notification-template-manage',
+          name: 'NotificationTemplateManage',
+          component: () => import('@/views/admin/NotificationTemplateManage.vue'),
+          meta: { title: '通知模板管理', perm: 'admin:all' }
+        },
         {
           path: 'ai-questionnaire-scenes',
           name: 'AiQuestionnaireScenes',

+ 208 - 0
cfc-web/src/views/admin/NotificationTemplateManage.vue

@@ -0,0 +1,208 @@
+<template>
+  <div class="notification-template-manage">
+    <el-card shadow="never">
+      <div slot="header" class="clearfix">
+        <span>通知模板管理</span>
+        <el-button type="primary" size="small" style="float: right" @click="handleAdd">新增模板</el-button>
+      </div>
+
+      <!-- 模板列表 -->
+      <el-table :data="list" border v-loading="loading">
+        <el-table-column prop="type" label="类型" width="160">
+          <template slot-scope="{ row }">
+            <el-tag size="small">{{ row.type }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="content" label="内容模板" min-width="280" show-overflow-tooltip></el-table-column>
+        <el-table-column prop="channels" label="推送通道" width="220">
+          <template slot-scope="{ row }">
+            <el-tag v-for="ch in channelLabels(row.channels)" :key="ch.value" :type="ch.type" size="mini"
+              style="margin-right: 4px">{{ ch.label }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="状态" width="90">
+          <template slot-scope="{ row }">
+            <el-tag :type="row.status === 1 ? 'success' : 'info'" size="small">
+              {{ row.status === 1 ? '启用' : '停用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="updatedAt" label="更新时间" width="170"></el-table-column>
+        <el-table-column label="操作" width="140" fixed="right">
+          <template slot-scope="{ row }">
+            <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
+            <el-button size="mini" type="danger" @click="handleDelete(row)">删除</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <!-- 编辑弹窗 -->
+    <el-dialog :title="form.id ? '编辑通知模板' : '新增通知模板'" :visible.sync="dialogVisible" width="600px">
+      <el-form :model="form" label-width="110px">
+        <el-form-item label="类型" required>
+          <el-select v-model="form.type" placeholder="请选择类型" filterable allow-create
+            style="width: 100%">
+            <el-option label="report_ready - 报告完成" value="report_ready"></el-option>
+            <el-option label="reward_granted - 奖励到账" value="reward_granted"></el-option>
+            <el-option label="system - 系统通知" value="system"></el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="标题" required>
+          <el-input v-model="form.title" placeholder="通知标题,如:健康报告已完成分析"></el-input>
+        </el-form-item>
+        <el-form-item label="内容模板" required>
+          <el-input type="textarea" v-model="form.content" :rows="4"
+            placeholder="支持占位符:{domainName} 问题域名称、{reward} 奖励CF值"></el-input>
+        </el-form-item>
+        <el-form-item label="推送通道">
+          <el-checkbox-group v-model="form.channelList">
+            <el-checkbox label="in_app">小程序内</el-checkbox>
+            <el-checkbox label="sms">短信</el-checkbox>
+            <el-checkbox label="wechat">微信</el-checkbox>
+            <el-checkbox label="phone">电话</el-checkbox>
+          </el-checkbox-group>
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-switch v-model="form.status" :active-value="1" :inactive-value="0"></el-switch>
+          <span style="margin-left: 10px; color: #999">停用后系统将使用默认文案</span>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSave" :loading="saving">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {
+  getNotificationTemplateList,
+  saveNotificationTemplate,
+  deleteNotificationTemplate
+} from '@/api/notificationTemplate'
+
+const CHANNEL_MAP = {
+  in_app: { label: '小程序内', type: 'primary' },
+  sms: { label: '短信', type: 'warning' },
+  wechat: { label: '微信', type: 'success' },
+  phone: { label: '电话', type: 'danger' }
+}
+
+export default {
+  name: 'NotificationTemplateManage',
+  data() {
+    return {
+      list: [],
+      loading: false,
+      saving: false,
+      dialogVisible: false,
+      form: {
+        id: null,
+        type: '',
+        title: '',
+        content: '',
+        channels: 'in_app',
+        channelList: ['in_app'],
+        status: 1
+      }
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      this.loading = true
+      try {
+        const res = await getNotificationTemplateList()
+        if (res.code === 0 || res.code === 200) {
+          this.list = res.data || []
+        } else {
+          this.$message.error(res.message || '加载失败')
+        }
+      } finally {
+        this.loading = false
+      }
+    },
+    channelLabels(channels) {
+      if (!channels) return []
+      return channels.split(',').filter(c => CHANNEL_MAP[c]).map(c => CHANNEL_MAP[c])
+    },
+    handleAdd() {
+      this.form = {
+        id: null,
+        type: '',
+        title: '',
+        content: '',
+        channels: 'in_app',
+        channelList: ['in_app'],
+        status: 1
+      }
+      this.dialogVisible = true
+    },
+    handleEdit(row) {
+      this.form = {
+        id: row.id,
+        type: row.type,
+        title: row.title,
+        content: row.content,
+        channels: row.channels || 'in_app',
+        channelList: (row.channels || 'in_app').split(',').filter(Boolean),
+        status: row.status === 0 ? 0 : 1
+      }
+      this.dialogVisible = true
+    },
+    async handleSave() {
+      if (!this.form.type || !this.form.type.trim()) {
+        this.$message.warning('请填写类型')
+        return
+      }
+      if (!this.form.title || !this.form.title.trim()) {
+        this.$message.warning('请填写标题')
+        return
+      }
+      if (!this.form.content || !this.form.content.trim()) {
+        this.$message.warning('请填写内容模板')
+        return
+      }
+      this.saving = true
+      try {
+        const data = {
+          ...this.form,
+          channels: (this.form.channelList || []).join(',') || 'in_app'
+        }
+        const res = await saveNotificationTemplate(data)
+        if (res.code === 0 || res.code === 200) {
+          this.$message.success('保存成功')
+          this.dialogVisible = false
+          this.loadData()
+        } else {
+          this.$message.error(res.message || '保存失败')
+        }
+      } finally {
+        this.saving = false
+      }
+    },
+    handleDelete(row) {
+      this.$confirm(`确定删除模板「${row.type}」吗?`, '提示', { type: 'warning' }).then(async () => {
+        const res = await deleteNotificationTemplate({ id: row.id })
+        if (res.code === 0 || res.code === 200) {
+          this.$message.success('删除成功')
+          this.loadData()
+        } else {
+          this.$message.error(res.message || '删除失败')
+        }
+      }).catch(() => {})
+    }
+  }
+}
+</script>
+
+<style scoped>
+.notification-template-manage {
+  padding: 16px;
+}
+</style>