Files
urldb/web/components/WechatBotTab.vue

456 lines
15 KiB
Vue
Raw Normal View History

2025-10-31 13:36:07 +08:00
<template>
<div class="tab-content-container">
<div class="space-y-6">
<!-- 基础配置 -->
<n-card title="基础配置" class="mb-6">
<n-form :model="configForm" label-placement="left" label-width="120px">
<n-form-item label="AppID">
<n-input v-model:value="configForm.app_id" placeholder="请输入微信公众号AppID" />
</n-form-item>
<n-form-item label="AppSecret">
<n-input v-model:value="configForm.app_secret" type="password" placeholder="请输入AppSecret" show-password-on="click" />
</n-form-item>
<n-form-item label="Token">
<n-input v-model:value="configForm.token" placeholder="请输入Token用于消息验证" />
</n-form-item>
<n-form-item label="EncodingAESKey">
<n-input v-model:value="configForm.encoding_aes_key" type="password" placeholder="请输入EncodingAESKey可选用于消息加密" show-password-on="click" />
</n-form-item>
</n-form>
</n-card>
<!-- 功能配置 -->
<n-card title="功能配置" class="mb-6">
<n-form :model="configForm" label-placement="left" label-width="120px">
<n-form-item label="启用机器人">
<n-switch v-model:value="configForm.enabled" />
</n-form-item>
<n-form-item label="自动回复">
<n-switch v-model:value="configForm.auto_reply_enabled" />
</n-form-item>
<n-form-item label="欢迎消息">
<n-input v-model:value="configForm.welcome_message" type="textarea" :rows="3" placeholder="新用户关注时发送的欢迎消息" />
</n-form-item>
<n-form-item label="搜索结果限制">
2025-11-14 17:49:09 +08:00
<n-input-number v-model:value="configForm.search_limit" :min="1" :max="100" placeholder="搜索结果返回数量" />
2025-10-31 13:36:07 +08:00
</n-form-item>
</n-form>
</n-card>
<!-- 微信公众号验证文件上传 -->
<n-card title="微信公众号验证文件" class="mb-6">
<div class="space-y-4">
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">验证文件上传说明</h4>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
微信公众号需要上传一个TXT验证文件到网站根目录请按照以下步骤操作
</p>
<ol class="text-sm text-gray-700 dark:text-gray-300 list-decimal list-inside space-y-1">
<li>点击下方"选择文件"按钮选择微信提供的TXT验证文件</li>
<li>点击"上传验证文件"按钮上传文件</li>
<li>上传成功后文件将可通过网站根目录直接访问</li>
<li>在微信公众平台完成域名验证</li>
</ol>
</div>
<div class="flex items-center space-x-4">
<n-upload
ref="uploadRef"
:show-file-list="false"
:accept="'.txt'"
:max="1"
:custom-request="handleUpload"
@before-upload="beforeUpload"
@change="handleFileChange"
>
<n-button type="primary">
<template #icon>
<i class="fas fa-file-upload"></i>
</template>
选择TXT文件
</n-button>
</n-upload>
<n-button
type="success"
@click="triggerUpload"
:disabled="!selectedFile"
:loading="uploading"
>
<template #icon>
<i class="fas fa-cloud-upload-alt"></i>
</template>
上传验证文件
</n-button>
</div>
<div v-if="uploadResult" class="p-3 rounded-md bg-green-50 dark:bg-green-900/20 text-green-700 dark:text-green-300">
<div class="flex items-center">
<i class="fas fa-check-circle mr-2"></i>
<span>文件上传成功</span>
</div>
<p class="text-xs mt-1">文件名: {{ uploadResult.file_name }}</p>
<p class="text-xs">访问地址: {{ getFullUrl(uploadResult.access_url) }}</p>
</div>
</div>
</n-card>
<!-- 微信公众号平台配置说明 -->
<n-card title="微信公众号平台配置" class="mb-6">
<div class="space-y-4">
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-4">
<h4 class="font-medium text-blue-800 dark:text-blue-200 mb-2">服务器配置</h4>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-2">
在微信公众平台后台的<strong>开发 > 基本配置 > 服务器配置</strong>中设置
</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-2">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">URL</label>
<div class="flex items-center space-x-2">
<n-input :value="serverUrl" readonly class="flex-1" />
<n-button size="small" @click="copyToClipboard(serverUrl)" type="primary">
<template #icon>
<i class="fas fa-copy"></i>
</template>
复制
</n-button>
</div>
</div>
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1 block">Token</label>
<div class="flex items-center space-x-2">
<n-input :value="configForm.token" readonly class="flex-1" />
<n-button size="small" @click="copyToClipboard(configForm.token)" type="primary">
<template #icon>
<i class="fas fa-copy"></i>
</template>
复制
</n-button>
</div>
</div>
</div>
</div>
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-4">
<h4 class="font-medium text-green-800 dark:text-green-200 mb-2">消息加解密配置</h4>
<p class="text-sm text-gray-700 dark:text-gray-300">
如果需要启用消息加密请在微信公众平台选择<strong>安全模式</strong>并填写上面的EncodingAESKey
</p>
</div>
<div class="bg-yellow-50 dark:bg-yellow-900/20 rounded-lg p-4">
<h4 class="font-medium text-yellow-800 dark:text-yellow-200 mb-2">注意事项</h4>
<ul class="text-sm text-gray-700 dark:text-gray-300 list-disc list-inside space-y-1">
<li>服务器必须支持HTTPS微信要求</li>
<li>域名必须已备案</li>
<li>首次配置时微信会发送GET请求验证服务器</li>
<li>配置完成后记得点击"启用"按钮</li>
</ul>
</div>
</div>
</n-card>
<!-- 操作按钮 -->
<div class="flex justify-end space-x-4">
<n-button @click="resetForm">重置</n-button>
<n-button type="primary" @click="saveConfig" :loading="loading">保存配置</n-button>
</div>
<!-- 状态信息 -->
<n-card title="运行状态" class="mt-6">
<div class="space-y-4">
<div class="flex items-center space-x-4">
<n-tag :type="botStatus.overall_status ? 'success' : 'default'">
{{ botStatus.status_text || '未知状态' }}
</n-tag>
<n-tag v-if="botStatus.config" :type="botStatus.config.enabled ? 'success' : 'default'">
配置状态: {{ botStatus.config.enabled ? '已启用' : '已禁用' }}
</n-tag>
<n-tag v-if="botStatus.config" :type="botStatus.config.app_id_configured ? 'success' : 'warning'">
AppID: {{ botStatus.config.app_id_configured ? '已配置' : '未配置' }}
</n-tag>
</div>
<div v-if="!botStatus.overall_status && botStatus.config && botStatus.config.enabled" class="bg-orange-50 dark:bg-orange-900/20 rounded-lg p-3">
<p class="text-sm text-orange-800 dark:text-orange-200">
<i class="fas fa-exclamation-triangle mr-2"></i>
机器人已启用但未运行请检查配置是否正确或查看系统日志
</p>
</div>
</div>
</n-card>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue'
import { useNotification } from 'naive-ui'
import { useWechatApi } from '~/composables/useApi'
// 定义配置表单类型
interface WechatBotConfigForm {
enabled: boolean
app_id: string
app_secret: string
token: string
encoding_aes_key: string
welcome_message: string
auto_reply_enabled: boolean
search_limit: number
}
const notification = useNotification()
const loading = ref(false)
const wechatApi = useWechatApi()
const botStatus = ref({
overall_status: false,
status_text: '',
config: null as any,
runtime: null as any
})
// 验证文件上传相关
const uploadRef = ref()
const selectedFile = ref<File | null>(null)
const uploading = ref(false)
const uploadResult = ref<any>(null)
// 配置表单 - 直接使用 reactive
const configForm = reactive<WechatBotConfigForm>({
enabled: false,
app_id: '',
app_secret: '',
token: '',
encoding_aes_key: '',
welcome_message: '欢迎关注老九网盘资源库!发送关键词即可搜索资源。',
auto_reply_enabled: true,
search_limit: 5
})
// 计算服务器URL
const serverUrl = computed(() => {
if (process.client) {
return `${window.location.origin}/api/wechat/callback`
}
return 'https://yourdomain.com/api/wechat/callback'
})
// 获取机器人配置
const fetchBotConfig = async () => {
try {
const response = await wechatApi.getBotConfig()
if (response) {
// 直接更新 configForm
configForm.enabled = response.enabled || false
configForm.app_id = response.app_id || ''
configForm.app_secret = response.app_secret || '' // 现在所有字段都不敏感
configForm.token = response.token || ''
configForm.encoding_aes_key = response.encoding_aes_key || ''
configForm.welcome_message = response.welcome_message || '欢迎关注老九网盘资源库!发送关键词即可搜索资源。'
configForm.auto_reply_enabled = response.auto_reply_enabled || true
configForm.search_limit = response.search_limit || 5
}
} catch (error) {
console.error('获取微信机器人配置失败:', error)
notification.error({
content: '获取配置失败',
duration: 3000
})
}
}
// 获取机器人状态
const fetchBotStatus = async () => {
try {
const response = await wechatApi.getBotStatus()
if (response) {
botStatus.value = response
}
} catch (error) {
console.error('获取微信机器人状态失败:', error)
notification.error({
content: '获取状态失败',
duration: 3000
})
}
}
// 保存配置
const saveConfig = async () => {
loading.value = true
try {
// 直接保存所有字段,不检测变更
const payload = {
enabled: configForm.enabled,
app_id: configForm.app_id,
app_secret: configForm.app_secret,
token: configForm.token,
encoding_aes_key: configForm.encoding_aes_key,
welcome_message: configForm.welcome_message,
auto_reply_enabled: configForm.auto_reply_enabled,
search_limit: configForm.search_limit
}
const response = await wechatApi.updateBotConfig(payload)
if (response.success) {
notification.success({
content: '配置保存成功',
duration: 3000
})
// 重新获取状态和配置
await fetchBotConfig()
await fetchBotStatus()
} else {
throw new Error(response.message || '保存失败')
}
} catch (error: any) {
console.error('保存微信机器人配置失败:', error)
notification.error({
content: error.message || '保存配置失败',
duration: 3000
})
} finally {
loading.value = false
}
}
// 重置表单
const resetForm = () => {
// 重新获取原始配置
fetchBotConfig()
}
// 复制到剪贴板
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text)
notification.success({
content: '已复制到剪贴板',
duration: 2000
})
} catch (error) {
console.error('复制失败:', error)
notification.error({
content: '复制失败',
duration: 2000
})
}
}
// 验证文件上传相关函数
const beforeUpload = (options: { file: any, fileList: any[] }) => {
// 从 options 中提取文件
const file = options?.file?.file || options?.file
// 检查文件对象是否有效
if (!file || !file.name) {
notification.error({
content: '文件选择失败,请重试',
duration: 2000
})
return false
}
// 验证文件类型 - 使用多重检查确保是TXT文件
const isValid = file.type === 'text/plain' ||
file.name.toLowerCase().endsWith('.txt') ||
file.type === 'text/plain;charset=utf-8'
if (!isValid) {
notification.error({
content: '请上传TXT文件',
duration: 2000
})
selectedFile.value = null // 清空之前的选择
return false // 阻止上传无效文件
}
// 保存选中的文件并更新状态
selectedFile.value = file
return false // 阻止自动上传
}
const handleUpload = ({ file, onSuccess, onError }: any) => {
// 这个函数不会被调用,因为我们阻止了自动上传
}
// 文件选择变化时的处理函数
const handleFileChange = (options: { file: any, fileList: any[] }) => {
// 从 change 事件中提取文件信息
const file = options?.file?.file || options?.file
if (file && file.name) {
// 更新选中的文件
selectedFile.value = file
}
}
const triggerUpload = async () => {
if (!selectedFile.value) {
notification.warning({
content: '请选择要上传的TXT文件',
duration: 2000
})
return
}
uploading.value = true
try {
const formData = new FormData()
formData.append('file', selectedFile.value)
const response = await wechatApi.uploadVerifyFile(formData)
if (response.success) {
uploadResult.value = response
notification.success({
content: '验证文件上传成功',
duration: 3000
})
// 清空选择的文件
selectedFile.value = null
if (uploadRef.value) {
uploadRef.value.clear()
}
} else {
throw new Error(response.message || '上传失败')
}
} catch (error: any) {
console.error('上传验证文件失败:', error)
notification.error({
content: error.message || '上传验证文件失败',
duration: 3000
})
} finally {
uploading.value = false
}
}
const getFullUrl = (path: string) => {
if (process.client) {
return `${window.location.origin}${path}`
}
return path
}
// 页面加载时获取配置和状态
onMounted(async () => {
await fetchBotConfig()
await fetchBotStatus()
// 定期刷新状态
const interval = setInterval(fetchBotStatus, 30000)
onUnmounted(() => clearInterval(interval))
})
</script>
<style scoped>
/* 微信公众号机器人标签样式 */
.tab-content-container {
height: calc(100vh - 240px);
overflow-y: auto;
padding-bottom: 1rem;
}
</style>