init: 管理会计OS初始代码
包含前后端完整代码: - 前端:Vue3+Vite+ElementPlus - 后端:FastAPI+SQLAlchemy - 模块:驾驶舱/KPI/战略地图/预警/预算/成本/预测/改善行动 - 当前版本:v1.0.0
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user