Files
cma-management/frontend/src/views/NotificationManage.vue
T
Hermes CI Fix 3dddd36866 init: 管理会计OS初始代码
包含前后端完整代码:
- 前端:Vue3+Vite+ElementPlus
- 后端:FastAPI+SQLAlchemy
- 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动
- 当前版本:v1.0.0
2026-05-28 17:32:22 +08:00

202 lines
7.8 KiB
Vue

<template>
<div>
<div style="display:flex;justify-content:space-between;align-items:center;">
<h3>通知配置</h3>
<el-button type="primary" @click="showAddDialog = true">添加渠道</el-button>
</div>
<!-- 渠道列表 -->
<el-table :data="channels" v-loading="loading" style="width:100%;margin-top:16px;">
<el-table-column prop="name" label="名称" width="150" />
<el-table-column prop="channel_type" label="类型" width="120">
<template #default="{ row }">
<el-tag :type="row.channel_type === 'wecom' ? 'success' : row.channel_type === 'wecom_app' ? 'warning' : 'primary'">
{{ row.channel_type === 'wecom' ? '企业微信' : row.channel_type === 'wecom_app' ? '企微应用' : '邮件' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="配置摘要" min-width="200">
<template #default="{ row }">
<span v-if="row.channel_type === 'wecom'" style="font-size:12px;color:#999;">
Webhook: {{ (row.config?.webhook_url || '').substring(0, 40) }}...
</span>
<span v-else-if="row.channel_type === 'mail'" style="font-size:12px;color:#999;">
收件人: {{ (row.config?.to || []).join(', ') }}
</span>
</template>
</el-table-column>
<el-table-column label="状态" width="80">
<template #default="{ row }">
<el-switch :model-value="row.enabled" @change="toggleChannel(row)" size="small" />
</template>
</el-table-column>
<el-table-column label="操作" width="160">
<template #default="{ row }">
<el-button size="small" @click="testChannel(row)">测试</el-button>
<el-button size="small" type="danger" plain @click="deleteChannel(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!loading && channels.length === 0" description="暂无通知渠道,请添加" />
<!-- 通知日志 -->
<h3 style="margin-top:30px;">发送记录</h3>
<el-table :data="logs" style="width:100%;margin-top:8px;" size="small">
<el-table-column prop="created_at" label="时间" width="160" />
<el-table-column prop="title" label="标题" min-width="200" />
<el-table-column prop="channel" label="渠道" width="80" />
<el-table-column prop="recipient" label="接收方" width="120" />
<el-table-column prop="status" label="状态" width="80">
<template #default="{ row }">
<el-tag :type="row.status === 'sent' ? 'success' : 'danger'" size="small">{{ row.status === 'sent' ? '成功' : '失败' }}</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 添加渠道对话框 -->
<MyDialog v-model="showAddDialog" title="添加通知渠道" :width="500">
<el-form :model="form" label-width="100px">
<el-form-item label="名称"><el-input v-model="form.name" placeholder="如:管理群、财务部邮箱" /></el-form-item>
<el-form-item label="类型">
<el-select v-model="form.channel_type" style="width:100%;">
<el-option value="wecom" label="企业微信群机器人" />
<el-option value="wecom_app" label="企微应用消息" />
<el-option value="mail" label="邮件" />
</el-select>
</el-form-item>
<el-form-item v-if="form.channel_type === 'wecom'" label="Webhook地址">
<el-input v-model="form.webhook_url" type="textarea" rows="2" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx" />
</el-form-item>
<template v-if="form.channel_type === 'wecom_app'">
<el-form-item label="CorpID"><el-input v-model="form.corp_id" placeholder="企业ID,如 wx..." /></el-form-item>
<el-form-item label="CorpSecret"><el-input v-model="form.corp_secret" type="password" show-password /></el-form-item>
<el-form-item label="AgentID"><el-input v-model="form.agent_id" placeholder="应用AgentID,如 1000020" /></el-form-item>
<el-form-item label="接收人UserID"><el-input v-model="form.touser" placeholder="企微成员UserID,多个用 | 分隔" /></el-form-item>
</template>
<template v-if="form.channel_type === 'mail'">
<el-form-item label="SMTP地址"><el-input v-model="form.smtp_host" placeholder="smtp.qq.com" /></el-form-item>
<el-form-item label="端口"><el-input v-model="form.smtp_port" placeholder="465" /></el-form-item>
<el-form-item label="账号"><el-input v-model="form.smtp_user" placeholder="xxx@qq.com" /></el-form-item>
<el-form-item label="密码"><el-input v-model="form.smtp_password" type="password" show-password /></el-form-item>
<el-form-item label="收件人"><el-input v-model="form.mail_to" placeholder="用逗号分隔多个邮箱" /></el-form-item>
</template>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="saveChannel" :loading="saving">保存</el-button>
</template>
</MyDialog>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { notificationApi } from '../api/index'
import MyDialog from '../components/MyDialog.vue'
const loading = ref(false)
const saving = ref(false)
const channels = ref<any[]>([])
const logs = ref<any[]>([])
const showAddDialog = ref(false)
const form = reactive({
name: '',
channel_type: 'wecom',
webhook_url: '',
corp_id: '',
corp_secret: '',
agent_id: '',
touser: '',
smtp_host: 'smtp.qq.com',
smtp_port: '465',
smtp_user: '',
smtp_password: '',
mail_to: '',
})
async function loadChannels() {
loading.value = true
try {
const r: any = await notificationApi.list()
channels.value = r.data || []
} catch (e: any) {
ElMessage.error('加载失败')
}
loading.value = false
}
async function loadLogs() {
try {
const r: any = await notificationApi.logs()
logs.value = r.data || []
} catch (_) {}
}
async function saveChannel() {
saving.value = true
try {
const config: any = {}
if (form.channel_type === 'wecom') {
config.webhook_url = form.webhook_url
} else if (form.channel_type === 'wecom_app') {
config.corp_id = form.corp_id
config.corp_secret = form.corp_secret
config.agent_id = form.agent_id
config.touser = form.touser
} else {
config.host = form.smtp_host
config.port = form.smtp_port
config.user = form.smtp_user
config.password = form.smtp_password
config.from_addr = form.smtp_user
config.to = form.mail_to.split(',').map((s: string) => s.trim()).filter(Boolean)
}
await notificationApi.create({
name: form.name,
channel_type: form.channel_type,
config,
})
ElMessage.success('添加成功')
showAddDialog.value = false
loadChannels()
} catch (e: any) {
ElMessage.error(e?.response?.data?.detail || '保存失败')
}
saving.value = false
}
async function toggleChannel(row: any) {
try {
await notificationApi.update(row.id, { enabled: !row.enabled })
row.enabled = !row.enabled
} catch (_) {}
}
async function testChannel(row: any) {
try {
const r: any = await notificationApi.test(row.id)
const result = r.results?.[0]
if (result?.success) {
ElMessage.success(result.message || '测试推送成功')
} else {
ElMessage.warning(result?.message || '推送失败')
}
} catch (e: any) {
ElMessage.error('测试异常')
}
}
async function deleteChannel(row: any) {
try {
await ElMessageBox.confirm('确定删除此通知渠道?', '确认')
await notificationApi.delete(row.id)
ElMessage.success('已删除')
loadChannels()
} catch (_) {}
}
onMounted(() => { loadChannels(); loadLogs() })
</script>