197 lines
7.7 KiB
Vue
197 lines
7.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, reactive, ref } from "vue";
|
|
import { ElMessage } from "element-plus";
|
|
import { adminApi, type AdminMessageLogItem, type AdminMessageOverviewCard, type AdminMessageTemplateItem, type AdminMessageTemplatePayload } from "../../api/admin";
|
|
import OrderStatusTag from "../../components/OrderStatusTag.vue";
|
|
|
|
const loading = ref(false);
|
|
const cards = ref<AdminMessageOverviewCard[]>([]);
|
|
const templates = ref<AdminMessageTemplateItem[]>([]);
|
|
const logs = ref<AdminMessageLogItem[]>([]);
|
|
const templateDialogVisible = ref(false);
|
|
const templateSubmitting = ref(false);
|
|
const messageEventOptions = ref<Array<{ event_code: string; title: string; desc: string }>>([]);
|
|
|
|
const templateForm = reactive<AdminMessageTemplatePayload>({
|
|
template_name: "",
|
|
template_code: "",
|
|
channel: "inbox",
|
|
event_code: "order_created",
|
|
title: "",
|
|
content: "",
|
|
is_enabled: true,
|
|
});
|
|
|
|
const currentEventDesc = computed(
|
|
() => messageEventOptions.value.find((item) => item.event_code === templateForm.event_code)?.desc || "",
|
|
);
|
|
|
|
function eventTitle(eventCode: string) {
|
|
return messageEventOptions.value.find((item) => item.event_code === eventCode)?.title || eventCode;
|
|
}
|
|
|
|
async function fetchAll() {
|
|
loading.value = true;
|
|
try {
|
|
const [overviewRes, templatesRes, logsRes, metaRes] = await Promise.all([
|
|
adminApi.getMessageOverview(),
|
|
adminApi.getMessageTemplates(),
|
|
adminApi.getMessageLogs(),
|
|
adminApi.getContentMeta(),
|
|
]);
|
|
cards.value = overviewRes.data.cards;
|
|
templates.value = templatesRes.data.list;
|
|
logs.value = logsRes.data.list;
|
|
messageEventOptions.value = metaRes.data.meta_config.message_events;
|
|
} catch (error) {
|
|
console.error(error);
|
|
ElMessage.error("消息中心数据加载失败");
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
function openTemplateDialog(row?: AdminMessageTemplateItem) {
|
|
if (row) {
|
|
templateForm.id = row.id;
|
|
templateForm.template_name = row.template_name;
|
|
templateForm.template_code = row.template_code;
|
|
templateForm.channel = row.channel;
|
|
templateForm.event_code = row.event_code;
|
|
templateForm.title = row.title;
|
|
templateForm.content = row.content;
|
|
templateForm.is_enabled = row.is_enabled;
|
|
} else {
|
|
templateForm.id = undefined;
|
|
templateForm.template_name = "";
|
|
templateForm.template_code = "";
|
|
templateForm.channel = "inbox";
|
|
templateForm.event_code = "order_created";
|
|
templateForm.title = "";
|
|
templateForm.content = "";
|
|
templateForm.is_enabled = true;
|
|
}
|
|
templateDialogVisible.value = true;
|
|
}
|
|
|
|
async function submitTemplate() {
|
|
templateSubmitting.value = true;
|
|
try {
|
|
await adminApi.saveMessageTemplate({ ...templateForm });
|
|
ElMessage.success(templateForm.id ? "模板更新成功" : "模板创建成功");
|
|
templateDialogVisible.value = false;
|
|
await fetchAll();
|
|
} catch (error) {
|
|
console.error(error);
|
|
ElMessage.error("消息模板保存失败");
|
|
} finally {
|
|
templateSubmitting.value = false;
|
|
}
|
|
}
|
|
|
|
onMounted(fetchAll);
|
|
</script>
|
|
|
|
<template>
|
|
<div v-loading="loading">
|
|
<div class="metric-grid" style="margin-bottom: 18px">
|
|
<div v-for="item in cards" :key="item.title" class="metric-card">
|
|
<div class="metric-card__label">{{ item.title }}</div>
|
|
<div class="metric-card__value">{{ item.value }}</div>
|
|
<div class="metric-card__desc">{{ item.desc }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<el-card class="panel-card" shadow="never">
|
|
<el-tabs>
|
|
<el-tab-pane label="模板列表">
|
|
<div class="filters-row" style="margin-bottom: 16px">
|
|
<el-button type="primary" @click="openTemplateDialog()">新增模板</el-button>
|
|
</div>
|
|
<el-table :data="templates" stripe>
|
|
<el-table-column prop="template_name" label="模板名称" min-width="180" />
|
|
<el-table-column prop="template_code" label="模板编码" min-width="180" />
|
|
<el-table-column prop="channel_text" label="发送渠道" min-width="140" />
|
|
<el-table-column label="触发事件" min-width="180">
|
|
<template #default="{ row }">
|
|
{{ eventTitle(row.event_code) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="title" label="标题" min-width="180" />
|
|
<el-table-column label="状态" min-width="100">
|
|
<template #default="{ row }">
|
|
<OrderStatusTag :status="row.is_enabled ? '已启用' : '未启用'" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" fixed="right" width="100">
|
|
<template #default="{ row }">
|
|
<el-button link type="primary" @click="openTemplateDialog(row)">编辑</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</el-tab-pane>
|
|
|
|
<el-tab-pane label="发送记录">
|
|
<el-table :data="logs" stripe>
|
|
<el-table-column prop="template_name" label="模板名称" min-width="180" />
|
|
<el-table-column prop="channel_text" label="发送渠道" min-width="120" />
|
|
<el-table-column prop="biz_type" label="业务类型" min-width="120" />
|
|
<el-table-column prop="biz_id" label="业务ID" min-width="100" />
|
|
<el-table-column label="发送状态" min-width="120">
|
|
<template #default="{ row }">
|
|
<OrderStatusTag :status="row.status_text" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="sent_at" label="发送时间" min-width="170" />
|
|
<el-table-column prop="fail_reason" label="失败原因" min-width="220" />
|
|
</el-table>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</el-card>
|
|
|
|
<el-dialog v-model="templateDialogVisible" :title="templateForm.id ? '编辑消息模板' : '新增消息模板'" width="620px">
|
|
<el-form label-position="top">
|
|
<el-form-item label="模板名称">
|
|
<el-input v-model="templateForm.template_name" placeholder="请输入模板名称" />
|
|
</el-form-item>
|
|
<el-form-item label="模板编码">
|
|
<el-input v-model="templateForm.template_code" placeholder="请输入模板编码" />
|
|
</el-form-item>
|
|
<el-form-item label="发送渠道">
|
|
<el-radio-group v-model="templateForm.channel">
|
|
<el-radio value="inbox">站内消息</el-radio>
|
|
<el-radio value="sms">短信</el-radio>
|
|
<el-radio value="wechat_subscribe">微信订阅消息</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="触发事件">
|
|
<el-select v-model="templateForm.event_code" style="width: 100%">
|
|
<el-option
|
|
v-for="item in messageEventOptions"
|
|
:key="item.event_code"
|
|
:label="item.title"
|
|
:value="item.event_code"
|
|
/>
|
|
</el-select>
|
|
<div style="margin-top: 6px; color: var(--admin-text-subtle); font-size: 12px;">
|
|
{{ currentEventDesc }}
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item label="标题">
|
|
<el-input v-model="templateForm.title" placeholder="请输入消息标题" />
|
|
</el-form-item>
|
|
<el-form-item label="内容">
|
|
<el-input v-model="templateForm.content" type="textarea" :rows="5" placeholder="请输入模板内容" />
|
|
</el-form-item>
|
|
<el-form-item label="是否启用">
|
|
<el-switch v-model="templateForm.is_enabled" />
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="templateDialogVisible = false">取消</el-button>
|
|
<el-button type="primary" :loading="templateSubmitting" @click="submitTemplate">保存</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|