pet-ai/web/src/view/wechat/mp/config/index.vue

431 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<!-- 配置管理 -->
<el-card class="config-card">
<template #header>
<div class="card-header">
<span>公众号配置管理</span>
<el-tag type="primary" size="small">微信公众号</el-tag>
</div>
</template>
<el-form ref="configFormRef" :model="configForm" :rules="configRules" label-width="150px">
<el-form-item label="公众号AppID" prop="appId">
<el-input v-model="configForm.appId" placeholder="请输入公众号AppID" />
</el-form-item>
<el-form-item label="公众号AppSecret" prop="appSecret">
<el-input v-model="configForm.appSecret" type="password" placeholder="请输入公众号AppSecret" show-password />
</el-form-item>
<el-form-item label="服务器Token" prop="token">
<el-input v-model="configForm.token" placeholder="请输入服务器Token" />
</el-form-item>
<el-form-item label="消息加解密密钥" prop="encodingAESKey">
<el-input v-model="configForm.encodingAESKey" placeholder="请输入消息加解密密钥(可选)" />
</el-form-item>
<el-form-item label="服务器URL">
<el-input v-model="webhookUrl" readonly>
<template #append>
<el-button @click="copyWebhookUrl">复制</el-button>
</template>
</el-input>
<div class="form-tip">
请将此URL配置到微信公众平台的服务器配置中
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveConfig">保存配置</el-button>
<el-button @click="testConfig">测试配置</el-button>
<el-button type="success" @click="validateConfig" :loading="validating">验证配置</el-button>
<el-button type="warning" @click="generateQrCode" :loading="generating">生成二维码</el-button>
<el-button type="danger" @click="clearQuota" :loading="clearing">清空配额</el-button>
</el-form-item>
</el-form>
</el-card>
<!-- Webhook日志 -->
<el-card class="webhook-logs-card">
<template #header>
<div class="card-header">
<span>Webhook日志</span>
<div>
<el-button type="primary" @click="getWebhookLogs">刷新日志</el-button>
<el-button type="danger" @click="clearWebhookLogs">清空日志</el-button>
</div>
</div>
</template>
<el-table :data="webhookLogs" style="width: 100%">
<el-table-column label="时间" width="180">
<template #default="scope">
{{ formatDate(scope.row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="请求方法" prop="requestMethod" width="100" />
<el-table-column label="请求路径" prop="requestUrl" width="200" />
<el-table-column label="状态码" prop="responseStatus" width="100">
<template #default="scope">
<el-tag :type="getStatusTag(scope.row.responseStatus)">{{ scope.row.responseStatus }}</el-tag>
</template>
</el-table-column>
<el-table-column label="响应时间" prop="processTime" width="120">
<template #default="scope">
{{ scope.row.processTime }}ms
</template>
</el-table-column>
<el-table-column label="请求内容" prop="requestBody" show-overflow-tooltip />
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button type="primary" link @click="viewLogDetail(scope.row)">详情</el-button>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 日志详情对话框 -->
<el-dialog v-model="logDetailVisible" title="Webhook日志详情" width="800px">
<div v-if="currentLog" class="log-detail">
<el-descriptions :column="2" border>
<el-descriptions-item label="时间">{{ formatDate(currentLog.CreatedAt) }}</el-descriptions-item>
<el-descriptions-item label="方法">{{ currentLog.requestMethod }}</el-descriptions-item>
<el-descriptions-item label="路径">{{ currentLog.requestUrl }}</el-descriptions-item>
<el-descriptions-item label="状态码">
<el-tag :type="getStatusTag(currentLog.responseStatus)">{{ currentLog.responseStatus }}</el-tag>
</el-descriptions-item>
<el-descriptions-item label="响应时间">{{ currentLog.processTime }}ms</el-descriptions-item>
<el-descriptions-item label="IP地址">{{ currentLog.clientIP || '未知' }}</el-descriptions-item>
</el-descriptions>
<div class="log-content">
<h4>请求内容</h4>
<pre>{{ formatJSON(currentLog.requestBody) }}</pre>
<h4>响应内容</h4>
<pre>{{ formatJSON(currentLog.responseBody) }}</pre>
</div>
</div>
</el-dialog>
<!-- 二维码显示对话框 -->
<el-dialog v-model="qrCodeVisible" title="公众号二维码" width="400px" center>
<div class="text-center">
<img v-if="qrCodeUrl" :src="qrCodeUrl" alt="公众号二维码" style="max-width: 300px;" />
<p class="mt-4 text-gray-600">扫描二维码关注公众号</p>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { formatDate } from '@/utils/format'
import {
getWechatConfig,
saveWechatConfig,
testWechatConfig,
getWebhookLogs as getWebhookLogsApi,
validateMpConfig,
generateMpQrCode,
clearMpQuota
} from '@/api/wechat/config'
defineOptions({
name: 'MpAccount'
})
const webhookLogs = ref([])
const logDetailVisible = ref(false)
const currentLog = ref(null)
// 新增的响应式变量
const validating = ref(false)
const generating = ref(false)
const clearing = ref(false)
const qrCodeVisible = ref(false)
const qrCodeUrl = ref('')
const configForm = reactive({
appId: '',
appSecret: '',
token: '',
encodingAESKey: ''
})
const configRules = {
appId: [{ required: true, message: '请输入公众号AppID', trigger: 'blur' }],
appSecret: [{ required: true, message: '请输入公众号AppSecret', trigger: 'blur' }],
token: [{ required: true, message: '请输入服务器Token', trigger: 'blur' }]
}
const webhookUrl = ref(window.location.origin + '/api/wechat/official/webhook')
// 获取配置
const getConfig = async() => {
const res = await getWechatConfig()
if (res.code === 0) {
Object.assign(configForm, res.data)
}
}
// 保存配置
const saveConfig = async() => {
// 添加configType字段标识为公众号类型
const configData = {
...configForm,
configType: 'mp' // 公众号类型标识
}
const res = await saveWechatConfig(configData)
if (res.code === 0) {
ElMessage.success('配置保存成功')
}
}
// 测试配置
const testConfig = async() => {
// 添加configType字段标识为公众号类型
const configData = {
...configForm,
configType: 'mp' // 公众号类型标识
}
const res = await testWechatConfig(configData)
if (res.code === 0) {
ElMessage.success('配置测试成功')
} else {
ElMessage.error('配置测试失败:' + res.msg)
}
}
// 复制Webhook URL
const copyWebhookUrl = () => {
navigator.clipboard.writeText(webhookUrl.value).then(() => {
ElMessage.success('URL已复制到剪贴板')
})
}
// 获取Webhook日志
const getWebhookLogs = async() => {
const res = await getWebhookLogsApi()
if (res.code === 0) {
webhookLogs.value = res.data.list || []
}
}
// 清空Webhook日志
const clearWebhookLogs = () => {
ElMessageBox.confirm('确定要清空所有Webhook日志吗', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
webhookLogs.value = []
ElMessage.success('日志已清空')
})
}
// 查看日志详情
const viewLogDetail = (log) => {
currentLog.value = log
logDetailVisible.value = true
}
// 验证配置
const validateConfig = async() => {
validating.value = true
try {
const res = await validateMpConfig()
if (res.code === 0) {
ElMessage.success('配置验证成功')
} else {
ElMessage.error(res.msg || '配置验证失败')
}
} catch (error) {
console.error('配置验证失败:', error)
ElMessage.error('配置验证失败')
} finally {
validating.value = false
}
}
// 生成二维码
const generateQrCode = async() => {
generating.value = true
try {
const res = await generateMpQrCode()
if (res.code === 0) {
qrCodeUrl.value = res.data.qrCodeUrl
qrCodeVisible.value = true
ElMessage.success('二维码生成成功')
} else {
ElMessage.error(res.msg || '二维码生成失败')
}
} catch (error) {
console.error('二维码生成失败:', error)
ElMessage.error('二维码生成失败')
} finally {
generating.value = false
}
}
// 清空配额
const clearQuota = async() => {
try {
await ElMessageBox.confirm('确定要清空API配额吗', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
clearing.value = true
const res = await clearMpQuota()
if (res.code === 0) {
ElMessage.success('API配额清空成功')
} else {
ElMessage.error(res.msg || 'API配额清空失败')
}
} catch (error) {
if (error !== 'cancel') {
console.error('API配额清空失败:', error)
ElMessage.error('API配额清空失败')
}
} finally {
clearing.value = false
}
}
// 获取服务类型标签
const getServiceTypeTag = (type) => {
const typeMap = {
0: 'info', // 订阅号
1: 'success', // 由历史老帐号升级后的订阅号
2: 'warning' // 服务号
}
return typeMap[type] || 'info'
}
// 获取服务类型名称
const getServiceTypeName = (type) => {
const typeMap = {
0: '订阅号',
1: '订阅号(老账号)',
2: '服务号'
}
return typeMap[type] || '未知'
}
// 获取认证类型标签
const getVerifyTypeTag = (type) => {
const typeMap = {
'-1': 'danger', // 未认证
0: 'success', // 微信认证
1: 'warning' // 新浪微博认证
}
return typeMap[type] || 'info'
}
// 获取认证类型名称
const getVerifyTypeName = (type) => {
const typeMap = {
'-1': '未认证',
0: '微信认证',
1: '新浪微博认证'
}
return typeMap[type] || '未知'
}
// 获取状态标签
const getStatusTag = (status) => {
if (status >= 200 && status < 300) return 'success'
if (status >= 400 && status < 500) return 'warning'
if (status >= 500) return 'danger'
return 'info'
}
// 格式化JSON
const formatJSON = (str) => {
if (!str) return '无'
try {
return JSON.stringify(JSON.parse(str), null, 2)
} catch {
return str
}
}
onMounted(() => {
getConfig()
getWebhookLogs()
})
</script>
<style scoped>
.config-card,
.webhook-logs-card {
margin-top: 20px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header span {
font-size: 16px;
font-weight: 600;
color: #303133;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.form-tip {
font-size: 12px;
color: #909399;
margin-top: 5px;
}
.log-detail {
padding: 20px 0;
}
.log-content {
margin-top: 20px;
}
.log-content h4 {
margin: 20px 0 10px 0;
color: #303133;
}
.log-content pre {
background: #f5f5f5;
padding: 15px;
border-radius: 4px;
max-height: 300px;
overflow-y: auto;
font-size: 12px;
line-height: 1.5;
}
.text-center {
text-align: center;
}
.mt-4 {
margin-top: 16px;
}
.text-gray-600 {
color: #6b7280;
}
</style>