Initial commit: yu-zhi-ran platform with automation integration
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然 - 简单版</title>
|
||||
<script src="/static/vue.global.prod.js"></script>
|
||||
<link rel="stylesheet" href="/static/element-plus.css" />
|
||||
<script src="/static/element-plus.full.js"></script>
|
||||
<style>
|
||||
body { margin: 20px; font-family: sans-serif; }
|
||||
.card { border: 1px solid #ddd; padding: 20px; margin: 10px 0; border-radius: 8px; }
|
||||
.stat-value { font-size: 2rem; color: #409EFF; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>宇之然内容创作平台</h1>
|
||||
<div class="card">
|
||||
<h2>系统概览</h2>
|
||||
<div v-if="status">
|
||||
<p>选题总数: {{ status.total_topics }}</p>
|
||||
<p>待发布: {{ status.topics_by_status?.['待发布'] || 0 }}</p>
|
||||
<p>待处理: {{ status.topics_by_status?.['待处理'] || 0 }}</p>
|
||||
</div>
|
||||
<div v-else>加载中...</div>
|
||||
<button @click="refresh">刷新</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>选题列表</h2>
|
||||
<div v-if="topics.length">
|
||||
<ul>
|
||||
<li v-for="t in topics" :key="t.id">
|
||||
{{ t.id }} - {{ t.title }} - {{ t.status }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else-if="topics">无选题</div>
|
||||
<div v-else>加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp, ref, onMounted } = Vue;
|
||||
createApp({
|
||||
setup() {
|
||||
const API_BASE = '';
|
||||
const status = ref(null);
|
||||
const topics = ref([]);
|
||||
|
||||
const refresh = async () => {
|
||||
try {
|
||||
const [s, t] = await Promise.all([
|
||||
fetch(API_BASE + '/api/system/status').then(r => r.json()),
|
||||
fetch(API_BASE + '/api/topics').then(r => r.json())
|
||||
]);
|
||||
status.value = s;
|
||||
topics.value = t;
|
||||
console.log('数据加载成功', s, t);
|
||||
} catch (e) {
|
||||
console.error('刷新失败:', e);
|
||||
alert('加载失败: ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
console.log('应用启动');
|
||||
refresh();
|
||||
});
|
||||
|
||||
return { status, topics, refresh };
|
||||
}
|
||||
}).use(ElementPlus).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,573 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>宇之然内容创作平台</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="/static/vue.global.prod.js"></script>
|
||||
<link rel="stylesheet" href="/static/element-plus.css" />
|
||||
<script src="/static/element-plus.full.js"></script>
|
||||
<style>
|
||||
.page { padding: 20px; max-width: 1200px; margin: 0 auto; }
|
||||
.card { background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
|
||||
.stat-card { text-align: center; }
|
||||
.stat-value { font-size: 2rem; font-weight: bold; color: #409EFF; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<nav class="bg-blue-600 text-white p-4 mb-6">
|
||||
<div class="container mx-auto flex justify-between items-center">
|
||||
<h1 class="text-2xl font-bold">宇之然内容创作平台</h1>
|
||||
<div class="flex gap-2">
|
||||
<el-button type="primary" @click="refresh">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page">
|
||||
<div class="card">
|
||||
<h2 class="text-xl font-bold mb-4">系统概览</h2>
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ status.total_topics }}</div>
|
||||
<div>选题总数</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ (status.topics_by_status || {})['待发布'] || 0 }}</div>
|
||||
<div>待发布</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ (status.topics_by_status || {})['待处理'] || 0 }}</div>
|
||||
<div>待处理</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ status.today_articles }}</div>
|
||||
<div>今日生成</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flex gap-4">
|
||||
<el-button type="success" @click="triggerGenerate" :loading="generating">▶ 运行创作任务</el-button>
|
||||
<el-button type="warning" @click="triggerOptimize" :loading="optimizing">🔍 运行合规优化</el-button>
|
||||
<el-button type="info" @click="showLogs = true">📄 查看日志</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 流水线状态 -->
|
||||
<div class="card">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">📊 流水线状态</h2>
|
||||
<el-button size="small" @click="refreshPipeline">刷新</el-button>
|
||||
</div>
|
||||
<div v-if="pipelineLoading" class="text-gray-500">加载中...</div>
|
||||
<div v-else class="grid grid-cols-4 gap-4">
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ pipeline.status_distribution?.['待处理'] || 0 }}</div>
|
||||
<div>待处理</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ pipeline.status_distribution?.['待发布'] || 0 }}</div>
|
||||
<div>待发布</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ pipeline.status_distribution?.['已发布'] || 0 }}</div>
|
||||
<div>已发布</div>
|
||||
</div>
|
||||
<div class="card stat-card">
|
||||
<div class="stat-value">{{ pipeline.topics_count || 0 }}</div>
|
||||
<div>总选题数</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<h3 class="text-lg font-semibold mb-2">模块状态</h3>
|
||||
<el-table :data="pipelineModules" border style="width: 100%">
|
||||
<el-table-column prop="module" label="模块" width="120"></el-table-column>
|
||||
<el-table-column prop="last_run" label="最后运行" width="180"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="100">
|
||||
<template #default="scope">
|
||||
<el-tag :type="scope.row.status_ok ? 'success' : 'danger'">{{ scope.row.status_text }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="error" label="错误信息"></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h2 class="text-xl font-bold">选题管理</h2>
|
||||
<div class="flex gap-4">
|
||||
<el-button type="primary" size="small" @click="refreshAll">🔄 全量刷新</el-button>
|
||||
<el-button type="success" size="small" @click="openCreateTopic">+ 新建选题</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mb-4">
|
||||
<el-select v-model="filterStatus" placeholder="筛选状态" clearable style="width: 150px">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="待处理" value="待处理"></el-option>
|
||||
<el-option label="待审查" value="待审查"></el-option>
|
||||
<el-option label="待发布" value="待发布"></el-option>
|
||||
<el-option label="已发布" value="已发布"></el-option>
|
||||
</el-select>
|
||||
<span class="text-gray-500">共 {{ topics.length }} 条</span>
|
||||
</div>
|
||||
<div>
|
||||
<el-select v-model="filterStatus" placeholder="筛选状态" clearable style="width: 150px">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="待处理" value="待处理"></el-option>
|
||||
<el-option label="待审查" value="待审查"></el-option>
|
||||
<el-option label="待发布" value="待发布"></el-option>
|
||||
<el-option label="已发布" value="已发布"></el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="filteredTopics" stripe>
|
||||
<el-table-column prop="id" label="ID" width="80" header-align="center"></el-table-column>
|
||||
<el-table-column prop="title" label="标题" width="220" header-align="center"></el-table-column>
|
||||
<el-table-column prop="field" label="领域" width="90" header-align="center"></el-table-column>
|
||||
<el-table-column prop="priority_score" label="优先级" width="70" header-align="center"></el-table-column>
|
||||
<el-table-column prop="status" label="状态" width="90" header-align="center">
|
||||
<template #default="scope">
|
||||
<el-tag :type="statusTagType(scope.row.status)">{{ scope.row.status }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="compliance_score" label="合规分" width="70" header-align="center"></el-table-column>
|
||||
<el-table-column label="创建时间" width="120" header-align="center">
|
||||
<template #default="scope">{{ formatDate(scope.row.created_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="就绪时间" width="120" header-align="center">
|
||||
<template #default="scope">{{ formatDate(scope.row.ready_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="发布时间" width="120" header-align="center">
|
||||
<template #default="scope">{{ formatDate(scope.row.published_at) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320" header-align="center">
|
||||
<template #default="scope">
|
||||
<div class="flex gap-2 mb-1">
|
||||
<el-button size="small" @click="openPreview(scope.row)">预览</el-button>
|
||||
<el-button size="small" type="primary" :disabled="scope.row.status !== '待发布'" @click="openPublish(scope.row)">发布</el-button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<el-button size="small" type="success" :disabled="scope.row.status !== '待处理'" @click="createTopic(scope.row)">创作</el-button>
|
||||
<el-button size="small" type="warning" :disabled="scope.row.status !== '待审查'" @click="optimizeTopic(scope.row)">审查</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览对话框 -->
|
||||
<el-dialog v-model="previewVisible" :title="previewTopic.title" width="80%">
|
||||
<div class="mb-4">
|
||||
<el-radio-group v-model="previewPlatform" size="small">
|
||||
<el-radio-button label="zhihu">知乎</el-radio-button>
|
||||
<el-radio-button label="wechat">微信公众号</el-radio-button>
|
||||
<el-radio-button label="xiaohongshu">小红书</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<div v-if="previewHtml" class="border p-4 bg-gray-50" v-html="previewHtml" style="max-height: 70vh; overflow: auto;"></div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 发布管理对话框(增强版) -->
|
||||
<el-dialog v-model="publishVisible" :title="`发布管理:${publishTopic.title}`" width="900px">
|
||||
<el-tabs v-model="publishTab">
|
||||
<el-tab-pane label="发布链接" name="links">
|
||||
<el-form :model="publishForm" label-width="100px">
|
||||
<el-form-item label="知乎">
|
||||
<el-input v-model="publishForm.zhihu" placeholder="https://zhihu.com/..."></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="微信公众号">
|
||||
<el-input v-model="publishForm.wechat" placeholder="https://mp.weixin.qq.com/..."></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="小红书">
|
||||
<el-input v-model="publishForm.xiaohongshu" placeholder="https://xiaohongshu.com/note/..."></el-input>
|
||||
</el-form-item>
|
||||
<!-- 可扩展其他平台 -->
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="发布包" name="packages">
|
||||
<div v-if="loadingPackages" class="text-center py-4">加载中...</div>
|
||||
<div v-else-if="packages.length === 0" class="text-gray-500 py-4">暂未生成发布包,请点击"重新生成"</div>
|
||||
<div v-else>
|
||||
<div class="mb-4">
|
||||
<el-button type="primary" size="small" @click="generateAllPackages" :loading="generatingPackages">🔄 重新生成所有平台发布包</el-button>
|
||||
</div>
|
||||
<el-table :data="packages" border>
|
||||
<el-table-column prop="platform" label="平台" width="120"></el-table-column>
|
||||
<el-table-column prop="path" label="文件路径" width="300"></el-table-column>
|
||||
<el-table-column prop="size" label="大小" width="80">
|
||||
<template #default="scope">{{ formatSize(scope.row.size) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作">
|
||||
<template #default="scope">
|
||||
<el-button size="small" @click="viewPackage(scope.row)">查看</el-button>
|
||||
<el-button size="small" type="primary" @click="copyPackage(scope.row)">复制HTML</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="publishVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="publishing" @click="confirmPublish">确认发布并保存链接</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 预览单个发布包 -->
|
||||
<el-dialog v-model="packagePreviewVisible" :title="`预览:${currentPackage?.platform}`" width="80%">
|
||||
<div class="mb-4 flex gap-2">
|
||||
<el-button size="small" @click="packagePreviewVisible = false">关闭</el-button>
|
||||
<el-button size="small" type="primary" @click="copyCurrentHtml">复制HTML</el-button>
|
||||
</div>
|
||||
<div class="border p-4 bg-gray-50" v-html="currentHtml" style="max-height: 70vh; overflow: auto;"></div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 日志对话框 -->
|
||||
<el-dialog v-model="showLogs" title="系统日志" width="80%">
|
||||
<div class="mb-4 flex gap-2">
|
||||
<el-date-picker v-model="logDate" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD"></el-date-picker>
|
||||
<el-select v-model="logType" style="width: 120px">
|
||||
<el-option label="creator" value="creator"></el-option>
|
||||
<el-option label="publisher" value="publisher"></el-option>
|
||||
<el-option label="collector" value="collector"></el-option>
|
||||
</el-select>
|
||||
<el-button @click="fetchLogs">加载</el-button>
|
||||
</div>
|
||||
<pre class="bg-gray-100 p-4 rounded overflow-auto" style="max-height: 60vh;">{{ logContent }}</pre>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const { createApp, ref, computed, onMounted, watch } = Vue;
|
||||
|
||||
createApp({
|
||||
setup() {
|
||||
const API_BASE = '';
|
||||
|
||||
const status = ref({});
|
||||
const topics = ref([]);
|
||||
const filterStatus = ref('');
|
||||
const generating = ref(false);
|
||||
const optimizing = ref(false);
|
||||
const publishing = ref(false);
|
||||
const pipeline = ref({});
|
||||
const pipelineLoading = ref(false);
|
||||
const pipelineModules = ref([]);
|
||||
|
||||
const previewVisible = ref(false);
|
||||
const previewTopic = ref({});
|
||||
const previewPlatform = ref('zhihu');
|
||||
const previewHtml = ref('');
|
||||
|
||||
const publishVisible = ref(false);
|
||||
const publishTopic = ref({});
|
||||
const publishForm = ref({ zhihu: '', wechat: '', xiaohongshu: '' });
|
||||
const publishTab = ref('links');
|
||||
|
||||
// 新增:发布包管理
|
||||
const packages = ref([]);
|
||||
const loadingPackages = ref(false);
|
||||
const generatingPackages = ref(false);
|
||||
const packagePreviewVisible = ref(false);
|
||||
const currentPackage = ref(null);
|
||||
const currentHtml = ref('');
|
||||
|
||||
const showLogs = ref(false);
|
||||
const logType = ref('creator');
|
||||
const logDate = ref(new Date().toISOString().split('T')[0]);
|
||||
const logContent = ref('');
|
||||
|
||||
const filteredTopics = computed(() => {
|
||||
try {
|
||||
if (!filterStatus.value) return topics.value || [];
|
||||
return (topics.value || []).filter(t => t && t.status === filterStatus.value);
|
||||
} catch (e) {
|
||||
console.error('filteredTopics error:', e);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
const refresh = async () => {
|
||||
try {
|
||||
const [s, t] = await Promise.all([
|
||||
fetch(API_BASE + '/api/system/status').then(r => r.json()),
|
||||
fetch(API_BASE + '/api/topics').then(r => r.json())
|
||||
]);
|
||||
status.value = s;
|
||||
topics.value = t;
|
||||
console.log('刷新成功', s, t);
|
||||
} catch (e) {
|
||||
console.error('刷新失败:', e);
|
||||
// 设置默认数据用于测试
|
||||
status.value = { total_topics: 20, topics_by_status: { '待发布': 4, '待处理': 16 } };
|
||||
// topics.value = []; // 保持空数组
|
||||
}
|
||||
};
|
||||
|
||||
const refreshPipeline = async () => {
|
||||
pipelineLoading.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/system/pipeline/status');
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
pipeline.value = data;
|
||||
// 构建模块状态表格数据
|
||||
pipelineModules.value = Object.entries(data.pipeline_modules || {}).map(([name, info]) => ({
|
||||
module: name,
|
||||
last_run: info.last_run || '未运行',
|
||||
status_ok: !info.has_error && info.exists,
|
||||
status_text: info.exists && !info.has_error ? '正常' : info.exists ? '有错误' : '缺失',
|
||||
error: info.has_error ? '检测到错误' : ''
|
||||
}));
|
||||
} else {
|
||||
pipelineModules.value = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('流水线状态获取失败:', e);
|
||||
pipelineModules.value = [];
|
||||
} finally {
|
||||
pipelineLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const refreshAll = async () => {
|
||||
await Promise.all([refresh(), refreshPipeline()]);
|
||||
};
|
||||
|
||||
const triggerGenerate = async () => {
|
||||
generating.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/system/generate/run', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.result && data.result.ok) {
|
||||
ElementPlus.ElMessage.success('创作任务已启动');
|
||||
setTimeout(refresh, 5000);
|
||||
} else {
|
||||
ElementPlus.ElMessage.error('启动失败: ' + (data.error || '未知错误'));
|
||||
}
|
||||
} finally {
|
||||
generating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const triggerOptimize = async () => {
|
||||
optimizing.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + '/api/system/optimize/run', { method: 'POST' });
|
||||
const data = await res.json();
|
||||
if (data.summary) {
|
||||
ElementPlus.ElNotification({
|
||||
title: '优化完成',
|
||||
message: `自动通过 ${data.summary.passed_auto} 篇,需人工 ${data.summary.need_manual} 篇`,
|
||||
type: data.summary.need_manual === 0 ? 'success' : 'warning'
|
||||
});
|
||||
await refresh();
|
||||
} else {
|
||||
ElementPlus.ElMessage.success('优化完成');
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('请求失败: ' + e);
|
||||
} finally {
|
||||
optimizing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openPreview = async (topic) => {
|
||||
previewTopic.value = topic;
|
||||
previewVisible.value = true;
|
||||
previewPlatform.value = 'zhihu';
|
||||
await loadPreview();
|
||||
};
|
||||
|
||||
const loadPreview = async () => {
|
||||
const tid = previewTopic.value.id;
|
||||
const platform = previewPlatform.value;
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/articles/${tid}/preview?platform=${platform}`);
|
||||
const data = await res.json();
|
||||
previewHtml.value = data.html;
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('无法加载预览');
|
||||
}
|
||||
};
|
||||
|
||||
watch(previewPlatform, loadPreview);
|
||||
|
||||
const openPublish = async (topic) => {
|
||||
publishTopic.value = topic;
|
||||
publishForm.value = { zhihu: '', wechat: '', xiaohongshu: '' };
|
||||
publishVisible.value = true;
|
||||
publishTab.value = 'links';
|
||||
await loadPackages();
|
||||
};
|
||||
|
||||
// 加载发布包列表
|
||||
const loadPackages = async () => {
|
||||
const tid = publishTopic.value.id;
|
||||
loadingPackages.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/publisher/packages/${tid}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
packages.value = data.packages || [];
|
||||
} else {
|
||||
packages.value = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('加载发布包失败:', e);
|
||||
packages.value = [];
|
||||
} finally {
|
||||
loadingPackages.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 生成所有平台发布包
|
||||
const generateAllPackages = async () => {
|
||||
const tid = publishTopic.value.id;
|
||||
generatingPackages.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/publisher/generate/${tid}`, { method: 'POST' });
|
||||
if (res.ok) {
|
||||
ElementPlus.ElMessage.success('发布包生成任务已启动');
|
||||
setTimeout(loadPackages, 3000);
|
||||
} else {
|
||||
throw new Error('生成失败');
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('生成发布包失败: ' + e);
|
||||
} finally {
|
||||
generatingPackages.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 查看发布包内容
|
||||
const viewPackage = async (pkg) => {
|
||||
currentPackage.value = pkg;
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/publisher/package/${publishTopic.value.id}/${pkg.platform}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
currentHtml.value = data.html;
|
||||
packagePreviewVisible.value = true;
|
||||
} else {
|
||||
ElementPlus.ElMessage.error('无法加载发布包');
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('请求失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 复制发布包HTML
|
||||
const copyPackage = async (pkg) => {
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/publisher/package/${publishTopic.value.id}/${pkg.platform}`);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
await navigator.clipboard.writeText(data.html);
|
||||
ElementPlus.ElMessage.success('HTML已复制到剪贴板');
|
||||
} else {
|
||||
throw new Error('加载失败');
|
||||
}
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('复制失败: ' + e);
|
||||
}
|
||||
};
|
||||
|
||||
const copyCurrentHtml = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(currentHtml.value);
|
||||
ElementPlus.ElMessage.success('已复制');
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('复制失败');
|
||||
}
|
||||
};
|
||||
|
||||
const confirmPublish = async () => {
|
||||
publishing.value = true;
|
||||
try {
|
||||
const res = await fetch(API_BASE + `/api/topics/${publishTopic.value.id}/publish`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ platform_urls: publishForm.value })
|
||||
});
|
||||
if (res.ok) {
|
||||
ElementPlus.ElMessage.success('已标记为已发布');
|
||||
publishVisible.value = false;
|
||||
await refresh();
|
||||
} else {
|
||||
throw new Error('发布失败');
|
||||
}
|
||||
} finally {
|
||||
publishing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchLogs = async () => {
|
||||
try {
|
||||
const dateStr = logDate.value;
|
||||
const res = await fetch(API_BASE + `/api/system/logs/${dateStr}?log_type=${logType.value}`);
|
||||
if (!res.ok) throw new Error('日志文件不存在');
|
||||
const data = await res.json();
|
||||
logContent.value = data.content ? data.content.join('\n') : '无内容';
|
||||
} catch (e) {
|
||||
ElementPlus.ElMessage.error('加载日志失败');
|
||||
logContent.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const formatSize = (bytes) => {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
};
|
||||
|
||||
const statusTagType = (status) => {
|
||||
const map = { '待处理': 'info', '待审查': 'warning', '待发布': 'success', '已发布': 'primary' };
|
||||
return map[status] || '';
|
||||
};
|
||||
|
||||
const formatDate = (val) => {
|
||||
if (!val) return '';
|
||||
const d = new Date(val);
|
||||
if (isNaN(d.getTime())) return val;
|
||||
const yyyy = d.getFullYear();
|
||||
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const dd = String(d.getDate()).padStart(2, '0');
|
||||
const hh = String(d.getHours()).padStart(2, '0');
|
||||
const min = String(d.getMinutes()).padStart(2, '0');
|
||||
const ss = String(d.getSeconds()).padStart(2, '0');
|
||||
return `${yyyy}-${mm}-${dd} ${hh}:${min}:${ss}`;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
refresh();
|
||||
refreshPipeline();
|
||||
});
|
||||
|
||||
return {
|
||||
status, topics, filteredTopics, filterStatus,
|
||||
generating, optimizing,
|
||||
previewVisible, previewTopic, previewPlatform, previewHtml,
|
||||
publishVisible, publishTopic, publishForm, publishing,
|
||||
publishTab,
|
||||
packages, loadingPackages, generatingPackages,
|
||||
packagePreviewVisible, currentPackage, currentHtml,
|
||||
showLogs, logType, logDate, logContent, fetchLogs,
|
||||
refresh, refreshPipeline, refreshAll, triggerGenerate, triggerOptimize, openPreview, loadPreview,
|
||||
openPublish, loadPackages, generateAllPackages, viewPackage, copyPackage, copyCurrentHtml,
|
||||
confirmPublish, statusTagType, formatDate, formatSize,
|
||||
pipeline, pipelineLoading, pipelineModules
|
||||
};
|
||||
}
|
||||
}).use(ElementPlus).mount('#app').catch(err => {
|
||||
console.error('Vue mount error:', err);
|
||||
document.getElementById('app').innerHTML = '<div class="card"><h2>应用启动失败</h2><pre>' + err + '</pre></div>';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>前端测试</title>
|
||||
<script src="/static/vue.global.prod.js"></script>
|
||||
<link rel="stylesheet" href="/static/element-plus.css" />
|
||||
<script src="/static/element-plus.full.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<h1>测试页面</h1>
|
||||
<p>Vue 已加载: {{ loaded }}</p>
|
||||
<el-button type="primary">测试按钮</el-button>
|
||||
</div>
|
||||
<script>
|
||||
const { createApp, ref } = Vue;
|
||||
createApp({
|
||||
setup() {
|
||||
const loaded = ref(true);
|
||||
return { loaded };
|
||||
}
|
||||
}).use(ElementPlus).mount('#app');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user