优化报表页面,移除统计页面

This commit is contained in:
2025-12-15 22:12:15 +08:00
parent 9aa28f5b9e
commit d650c6f584
30 changed files with 851 additions and 904 deletions

View File

@@ -5,7 +5,6 @@ import { ElMessageBox } from 'element-plus'
import {
Bell,
ChatLineSquare,
DataAnalysis,
Document,
List,
Message,
@@ -106,7 +105,6 @@ const menuItems = [
{ path: '/reports', label: '报表', icon: Document },
{ path: '/users', label: '用户', icon: User, badgeKey: 'resets' },
{ path: '/feedbacks', label: '反馈', icon: ChatLineSquare, badgeKey: 'feedbacks' },
{ path: '/stats', label: '统计', icon: DataAnalysis },
{ path: '/logs', label: '任务日志', icon: List },
{ path: '/announcements', label: '公告', icon: Bell },
{ path: '/email', label: '邮件', icon: Message },

File diff suppressed because it is too large Load Diff

View File

@@ -1,480 +0,0 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { fetchDockerStats, fetchRunningTasks, fetchServerInfo, fetchTaskStats } from '../api/tasks'
import { getTaskSourceMeta } from '../utils/taskSource'
const initialLoading = ref(true)
const lastUpdatedAt = ref('')
const server = ref({
cpu_percent: '-',
memory_used: '-',
memory_total: '-',
disk_used: '-',
disk_total: '-',
uptime: '-',
})
const docker = ref({
status: 'Unknown',
memory_usage: 'N/A',
memory_limit: 'N/A',
memory_percent: 'N/A',
uptime: 'N/A',
})
const taskStats = ref({
today: { success_tasks: 0, failed_tasks: 0, total_items: 0, total_attachments: 0 },
total: { success_tasks: 0, failed_tasks: 0, total_items: 0, total_attachments: 0 },
})
const monitor = ref({
running_count: 0,
queuing_count: 0,
max_concurrent: 0,
running: [],
queuing: [],
})
const statusColorMap = {
初始化: '#6b7280',
正在登录: '#f59e0b',
正在浏览: '#10b981',
浏览完成: '#3b82f6',
正在截图: '#06b6d4',
}
function statusColor(text) {
return statusColorMap[text] || '#6b7280'
}
const serverMemoryDisplay = computed(() => `${server.value.memory_used} / ${server.value.memory_total}`)
const serverDiskDisplay = computed(() => `${server.value.disk_used} / ${server.value.disk_total}`)
let stop = false
let timer = null
function recordUpdatedAt() {
try {
lastUpdatedAt.value = new Date().toLocaleTimeString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
} catch {
lastUpdatedAt.value = ''
}
}
async function loadOnce() {
try {
const [serverInfo, dockerInfo, taskStat, running] = await Promise.all([
fetchServerInfo(),
fetchDockerStats(),
fetchTaskStats(),
fetchRunningTasks(),
])
server.value = serverInfo || server.value
docker.value = dockerInfo || docker.value
taskStats.value = taskStat || taskStats.value
monitor.value = running || monitor.value
recordUpdatedAt()
} catch {
// handled by interceptor
} finally {
initialLoading.value = false
}
}
async function loop() {
if (stop) return
const start = Date.now()
await loadOnce()
if (stop) return
const elapsed = Date.now() - start
// server/info 正常会阻塞约 1s如果异常很快失败避免疯狂重试
timer = window.setTimeout(loop, elapsed < 900 ? 1000 : 0)
}
onMounted(() => {
stop = false
loop()
})
onBeforeUnmount(() => {
stop = true
if (timer) window.clearTimeout(timer)
})
</script>
<template>
<div class="page-stack" v-loading="initialLoading">
<div class="app-page-title">
<h2>统计</h2>
<span class="app-muted">{{ lastUpdatedAt ? `最后更新:${lastUpdatedAt}` : '实时更新' }}</span>
</div>
<el-row :gutter="12">
<el-col :xs="12" :sm="8" :md="6">
<el-card shadow="never" class="metric-card" :body-style="{ padding: '14px' }">
<div class="metric-label">CPU</div>
<div class="metric-value">{{ server.cpu_percent }}%</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-card shadow="never" class="metric-card" :body-style="{ padding: '14px' }">
<div class="metric-label">内存</div>
<div class="metric-value">{{ serverMemoryDisplay }}</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-card shadow="never" class="metric-card" :body-style="{ padding: '14px' }">
<div class="metric-label">磁盘</div>
<div class="metric-value">{{ serverDiskDisplay }}</div>
</el-card>
</el-col>
<el-col :xs="12" :sm="8" :md="6">
<el-card shadow="never" class="metric-card" :body-style="{ padding: '14px' }">
<div class="metric-label">容器内存</div>
<div class="metric-value">{{ docker.memory_limit !== 'N/A' ? `${docker.memory_usage} / ${docker.memory_limit}` : docker.memory_usage }}</div>
<div v-if="docker.memory_percent !== 'N/A'" class="metric-sub app-muted">{{ docker.memory_percent }}</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :xs="24" :md="14">
<el-card shadow="never" class="card" :body-style="{ padding: '16px' }">
<div class="section-head">
<h3 class="section-title">实时监控</h3>
<span class="app-muted">最大并发{{ monitor.max_concurrent }}</span>
</div>
<el-row :gutter="12" class="count-row">
<el-col :span="8">
<el-card shadow="never" class="count-card ok" :body-style="{ padding: '12px' }">
<div class="count-value">{{ monitor.running_count }}</div>
<div class="count-label">运行中</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never" class="count-card warn" :body-style="{ padding: '12px' }">
<div class="count-value">{{ monitor.queuing_count }}</div>
<div class="count-label">排队中</div>
</el-card>
</el-col>
<el-col :span="8">
<el-card shadow="never" class="count-card" :body-style="{ padding: '12px' }">
<div class="count-value">{{ monitor.max_concurrent }}</div>
<div class="count-label">并发上限</div>
</el-card>
</el-col>
</el-row>
<div class="sub-title">运行中任务</div>
<div v-if="monitor.running.length === 0" class="empty app-muted">暂无运行中的任务</div>
<div v-else class="task-list">
<div v-for="t in monitor.running" :key="`r-${t.account_id}`" class="task-item">
<div class="task-left">
<div class="task-line">
<el-tag :type="getTaskSourceMeta(t.source).type" effect="light" size="small">
{{ getTaskSourceMeta(t.source).label }}
</el-tag>
<span class="task-user">{{ t.user_username }}</span>
<span class="app-muted"></span>
<span class="task-account">{{ t.username }}</span>
<el-tag effect="plain" size="small">{{ t.browse_type }}</el-tag>
</div>
<div class="task-line2">
<span class="dot" :style="{ background: statusColor(t.detail_status) }"></span>
<span class="task-status" :style="{ color: statusColor(t.detail_status) }">{{ t.detail_status }}</span>
<span v-if="t.progress_items || t.progress_attachments" class="app-muted"
>内容/附件{{ t.progress_items }} / {{ t.progress_attachments }}</span
>
</div>
</div>
<div class="task-right">{{ t.elapsed_display }}</div>
</div>
</div>
<div class="sub-title">排队中任务</div>
<div v-if="monitor.queuing.length === 0" class="empty app-muted">暂无排队中的任务</div>
<div v-else class="task-list">
<div v-for="t in monitor.queuing" :key="`q-${t.account_id}`" class="task-item queue">
<div class="task-left">
<div class="task-line">
<el-tag :type="getTaskSourceMeta(t.source).type" effect="light" size="small">
{{ getTaskSourceMeta(t.source).label }}
</el-tag>
<span class="task-user">{{ t.user_username }}</span>
<span class="app-muted"></span>
<span class="task-account">{{ t.username }}</span>
<el-tag effect="plain" size="small">{{ t.browse_type }}</el-tag>
</div>
<div class="task-line2">
<span class="dot" style="background: #f59e0b"></span>
<span class="task-status" style="color: #f59e0b">{{ t.detail_status || '等待资源' }}</span>
</div>
</div>
<div class="task-right warn">{{ t.elapsed_display }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :xs="24" :md="10">
<el-card shadow="never" class="card" :body-style="{ padding: '16px' }">
<div class="section-head">
<h3 class="section-title">任务统计</h3>
<span class="app-muted">运行{{ server.uptime }}</span>
</div>
<div class="stat-grid">
<div class="stat-box ok">
<div class="stat-name">成功任务</div>
<div class="stat-row">
<span class="stat-big">{{ taskStats.today.success_tasks }}</span>
<span class="app-muted">今日</span>
</div>
<div class="stat-row2 app-muted">累计{{ taskStats.total.success_tasks }}</div>
</div>
<div class="stat-box err">
<div class="stat-name">失败任务</div>
<div class="stat-row">
<span class="stat-big">{{ taskStats.today.failed_tasks }}</span>
<span class="app-muted">今日</span>
</div>
<div class="stat-row2 app-muted">累计{{ taskStats.total.failed_tasks }}</div>
</div>
<div class="stat-box info">
<div class="stat-name">浏览内容</div>
<div class="stat-row">
<span class="stat-big">{{ taskStats.today.total_items }}</span>
<span class="app-muted">今日</span>
</div>
<div class="stat-row2 app-muted">累计{{ taskStats.total.total_items }}</div>
</div>
<div class="stat-box info2">
<div class="stat-name">查看附件</div>
<div class="stat-row">
<span class="stat-big">{{ taskStats.today.total_attachments }}</span>
<span class="app-muted">今日</span>
</div>
<div class="stat-row2 app-muted">累计{{ taskStats.total.total_attachments }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<style scoped>
.page-stack {
display: flex;
flex-direction: column;
gap: 12px;
}
.metric-card,
.card {
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
}
.metric-label {
font-size: 12px;
color: var(--app-muted);
}
.metric-value {
margin-top: 6px;
font-size: 18px;
font-weight: 800;
}
.metric-sub {
margin-top: 4px;
font-size: 12px;
}
.section-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
margin-bottom: 12px;
}
.section-title {
margin: 0;
font-size: 14px;
font-weight: 800;
}
.count-row {
margin-bottom: 10px;
}
.count-card {
border-radius: 10px;
border: 1px solid var(--app-border);
}
.count-card.ok {
background: rgba(16, 185, 129, 0.08);
}
.count-card.warn {
background: rgba(245, 158, 11, 0.08);
}
.count-value {
font-size: 22px;
font-weight: 900;
line-height: 1.1;
}
.count-label {
margin-top: 4px;
font-size: 12px;
color: var(--app-muted);
}
.sub-title {
margin-top: 14px;
margin-bottom: 8px;
font-size: 13px;
font-weight: 800;
}
.empty {
padding: 10px 0;
}
.task-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.task-item {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border-radius: 10px;
border: 1px solid var(--app-border);
background: #fff;
}
.task-item.queue {
background: rgba(245, 158, 11, 0.06);
}
.task-line {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.task-line2 {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
margin-top: 6px;
font-size: 12px;
}
.task-user {
font-weight: 600;
}
.task-account {
font-weight: 700;
color: #2563eb;
}
.dot {
width: 8px;
height: 8px;
border-radius: 999px;
display: inline-block;
}
.task-status {
font-weight: 700;
}
.task-right {
font-size: 12px;
font-weight: 700;
color: #10b981;
white-space: nowrap;
}
.task-right.warn {
color: #f59e0b;
}
@media (max-width: 768px) {
.task-item {
flex-direction: column;
}
.task-right {
align-self: flex-end;
}
}
.stat-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.stat-box {
border-radius: 12px;
border: 1px solid var(--app-border);
padding: 12px;
}
.stat-box.ok {
background: rgba(16, 185, 129, 0.08);
}
.stat-box.err {
background: rgba(239, 68, 68, 0.08);
}
.stat-box.info {
background: rgba(59, 130, 246, 0.08);
}
.stat-box.info2 {
background: rgba(6, 182, 212, 0.08);
}
.stat-name {
font-size: 12px;
font-weight: 800;
margin-bottom: 6px;
}
.stat-row {
display: flex;
align-items: baseline;
gap: 8px;
}
.stat-big {
font-size: 20px;
font-weight: 900;
}
.stat-row2 {
margin-top: 6px;
font-size: 12px;
}
</style>

View File

@@ -5,7 +5,6 @@ import AdminLayout from '../layouts/AdminLayout.vue'
const ReportPage = () => import('../pages/ReportPage.vue')
const UsersPage = () => import('../pages/UsersPage.vue')
const FeedbacksPage = () => import('../pages/FeedbacksPage.vue')
const StatsPage = () => import('../pages/StatsPage.vue')
const LogsPage = () => import('../pages/LogsPage.vue')
const AnnouncementsPage = () => import('../pages/AnnouncementsPage.vue')
const EmailPage = () => import('../pages/EmailPage.vue')
@@ -19,10 +18,10 @@ const routes = [
children: [
{ path: '', redirect: '/reports' },
{ path: '/pending', redirect: '/reports' },
{ path: '/stats', redirect: '/reports' },
{ path: '/reports', name: 'reports', component: ReportPage },
{ path: '/users', name: 'users', component: UsersPage },
{ path: '/feedbacks', name: 'feedbacks', component: FeedbacksPage },
{ path: '/stats', name: 'stats', component: StatsPage },
{ path: '/logs', name: 'logs', component: LogsPage },
{ path: '/announcements', name: 'announcements', component: AnnouncementsPage },
{ path: '/email', name: 'email', component: EmailPage },

View File

@@ -1,38 +1,34 @@
{
"_email-DiVz51rK.js": {
"file": "assets/email-DiVz51rK.js",
"_email-BV9AnvmL.js": {
"file": "assets/email-BV9AnvmL.js",
"name": "email",
"imports": [
"index.html"
]
},
"_taskSource-CFicR2zp.js": {
"file": "assets/taskSource-CFicR2zp.js",
"name": "taskSource"
},
"_tasks-Bpfaxqqb.js": {
"file": "assets/tasks-Bpfaxqqb.js",
"_tasks-qpTYE_u9.js": {
"file": "assets/tasks-qpTYE_u9.js",
"name": "tasks",
"imports": [
"index.html"
]
},
"_update-B-ZRn1LV.js": {
"file": "assets/update-B-ZRn1LV.js",
"_update-DfOjb_I1.js": {
"file": "assets/update-DfOjb_I1.js",
"name": "update",
"imports": [
"index.html"
]
},
"_users-CgASQeNW.js": {
"file": "assets/users-CgASQeNW.js",
"_users-BhMai3uM.js": {
"file": "assets/users-BhMai3uM.js",
"name": "users",
"imports": [
"index.html"
]
},
"index.html": {
"file": "assets/index-Do26tg8I.js",
"file": "assets/index-DJ3M_U2-.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -40,7 +36,6 @@
"src/pages/ReportPage.vue",
"src/pages/UsersPage.vue",
"src/pages/FeedbacksPage.vue",
"src/pages/StatsPage.vue",
"src/pages/LogsPage.vue",
"src/pages/AnnouncementsPage.vue",
"src/pages/EmailPage.vue",
@@ -48,11 +43,11 @@
"src/pages/SettingsPage.vue"
],
"css": [
"assets/index-C73IFBwi.css"
"assets/index-2V61oOJ0.css"
]
},
"src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-BoMMAWcs.js",
"file": "assets/AnnouncementsPage-D3-leUXL.js",
"name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true,
@@ -64,12 +59,12 @@
]
},
"src/pages/EmailPage.vue": {
"file": "assets/EmailPage-VvSoafV1.js",
"file": "assets/EmailPage-CQQbxruj.js",
"name": "EmailPage",
"src": "src/pages/EmailPage.vue",
"isDynamicEntry": true,
"imports": [
"_email-DiVz51rK.js",
"_email-BV9AnvmL.js",
"index.html"
],
"css": [
@@ -77,7 +72,7 @@
]
},
"src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-DSgG1B6b.js",
"file": "assets/FeedbacksPage-Bazxwg3y.js",
"name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true,
@@ -89,14 +84,13 @@
]
},
"src/pages/LogsPage.vue": {
"file": "assets/LogsPage-D1qK6xGt.js",
"file": "assets/LogsPage-Bx0aa_bn.js",
"name": "LogsPage",
"src": "src/pages/LogsPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-CgASQeNW.js",
"_tasks-Bpfaxqqb.js",
"_taskSource-CFicR2zp.js",
"_users-BhMai3uM.js",
"_tasks-qpTYE_u9.js",
"index.html"
],
"css": [
@@ -104,22 +98,22 @@
]
},
"src/pages/ReportPage.vue": {
"file": "assets/ReportPage-BtciWYlz.js",
"file": "assets/ReportPage-DxWg5wJn.js",
"name": "ReportPage",
"src": "src/pages/ReportPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_email-DiVz51rK.js",
"_tasks-Bpfaxqqb.js",
"_update-B-ZRn1LV.js"
"_email-BV9AnvmL.js",
"_tasks-qpTYE_u9.js",
"_update-DfOjb_I1.js"
],
"css": [
"assets/ReportPage-Ds4jOTh9.css"
"assets/ReportPage-TpqQWWvU.css"
]
},
"src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-Bw9iP0hP.js",
"file": "assets/SettingsPage-DzBEGicw.js",
"name": "SettingsPage",
"src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true,
@@ -130,27 +124,13 @@
"assets/SettingsPage-DGdwb4W2.css"
]
},
"src/pages/StatsPage.vue": {
"file": "assets/StatsPage-BumsyjN6.js",
"name": "StatsPage",
"src": "src/pages/StatsPage.vue",
"isDynamicEntry": true,
"imports": [
"_tasks-Bpfaxqqb.js",
"_taskSource-CFicR2zp.js",
"index.html"
],
"css": [
"assets/StatsPage-CiLeKa5o.css"
]
},
"src/pages/SystemPage.vue": {
"file": "assets/SystemPage-CosZ9Vtj.js",
"file": "assets/SystemPage-DxT5Bmy6.js",
"name": "SystemPage",
"src": "src/pages/SystemPage.vue",
"isDynamicEntry": true,
"imports": [
"_update-B-ZRn1LV.js",
"_update-DfOjb_I1.js",
"index.html"
],
"css": [
@@ -158,12 +138,12 @@
]
},
"src/pages/UsersPage.vue": {
"file": "assets/UsersPage-C6sG2ovw.js",
"file": "assets/UsersPage-D0YtBHGu.js",
"name": "UsersPage",
"src": "src/pages/UsersPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-CgASQeNW.js",
"_users-BhMai3uM.js",
"index.html"
],
"css": [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-e84f7d23]{display:flex;flex-direction:column;gap:12px}.title-group[data-v-e84f7d23]{display:flex;flex-direction:column;gap:2px}.toolbar[data-v-e84f7d23]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-e84f7d23]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.metric-label[data-v-e84f7d23]{font-size:12px;color:var(--app-muted)}.metric-value[data-v-e84f7d23]{margin-top:6px;font-size:22px;font-weight:900;line-height:1.1}.section-head[data-v-e84f7d23]{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:12px}.section-title[data-v-e84f7d23]{margin:0;font-size:14px;font-weight:800}.kv[data-v-e84f7d23]{border:1px solid var(--app-border);border-radius:12px;padding:12px;background:#fff}.kv-v[data-v-e84f7d23]{font-size:18px;font-weight:900;line-height:1.1}.kv-k[data-v-e84f7d23]{margin-top:6px;font-size:12px}.ok[data-v-e84f7d23]{color:#047857}.warn[data-v-e84f7d23]{color:#b45309}.err[data-v-e84f7d23]{color:#b91c1c}.divider[data-v-e84f7d23]{height:1px;background:var(--app-border);margin:14px 0}.sys-grid[data-v-e84f7d23]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}.sys-item[data-v-e84f7d23]{border:1px solid var(--app-border);border-radius:12px;padding:12px;background:#fff}.sys-k[data-v-e84f7d23]{font-size:12px}.sys-sub[data-v-e84f7d23]{margin-top:8px;font-size:12px}.desc-inline[data-v-e84f7d23]{margin-left:8px}.sub-title[data-v-e84f7d23]{font-size:13px;font-weight:800;margin-bottom:10px}.type-grid[data-v-e84f7d23]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.type-item[data-v-e84f7d23]{border:1px solid var(--app-border);border-radius:12px;padding:12px;background:#fff}.type-v[data-v-e84f7d23]{font-size:16px;font-weight:900}.type-k[data-v-e84f7d23]{margin-top:6px;font-size:12px}.table-wrap[data-v-e84f7d23]{overflow-x:auto}.help[data-v-e84f7d23]{margin-top:10px;font-size:12px}@media(max-width:768px){.sys-grid[data-v-e84f7d23]{grid-template-columns:1fr}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{D as m,_ as T,r as p,a as h,b as r,d as a,w as s,e as u,h as k,j as b,q as d,E as x}from"./index-Do26tg8I.js";async function C(o){const{data:t}=await m.put("/admin/username",{new_username:o});return t}async function E(o){const{data:t}=await m.put("/admin/password",{new_password:o});return t}async function P(){const{data:o}=await m.post("/logout");return o}const U={class:"page-stack"},N={__name:"SettingsPage",setup(o){const t=p(""),i=p(""),n=p(!1);async function f(){try{await P()}catch{}finally{window.location.href="/yuyx"}}async function V(){const l=t.value.trim();if(!l){d.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${l}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(l),d.success("用户名修改成功,请重新登录"),t.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){const l=i.value;if(!l){d.error("请输入新密码");return}if(l.length<6){d.error("密码至少6个字符");return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await E(l),d.success("密码修改成功,请重新登录"),i.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(l,e)=>{const w=u("el-input"),v=u("el-form-item"),y=u("el-form"),_=u("el-button"),g=u("el-card");return k(),h("div",U,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(g,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(y,{"label-width":"120px"},{default:s(()=>[a(v,{label:"新用户名"},{default:s(()=>[a(w,{modelValue:t.value,"onUpdate:modelValue":e[0]||(e[0]=c=>t.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(_,{type:"primary",loading:n.value,onClick:V},{default:s(()=>[...e[2]||(e[2]=[b("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(g,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(y,{"label-width":"120px"},{default:s(()=>[a(v,{label:"新密码"},{default:s(()=>[a(w,{modelValue:i.value,"onUpdate:modelValue":e[1]||(e[1]=c=>i.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(_,{type:"primary",loading:n.value,onClick:B},{default:s(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},M=T(N,[["__scopeId","data-v-2f4b840f"]]);export{M as default};

View File

@@ -0,0 +1 @@
import{S as m,_ as T,r as p,e as h,f as r,g as a,w as s,n as u,x as k,y as x,L as i,K as b}from"./index-DJ3M_U2-.js";async function C(o){const{data:t}=await m.put("/admin/username",{new_username:o});return t}async function P(o){const{data:t}=await m.put("/admin/password",{new_password:o});return t}async function U(){const{data:o}=await m.post("/logout");return o}const E={class:"page-stack"},N={__name:"SettingsPage",setup(o){const t=p(""),d=p(""),n=p(!1);async function f(){try{await U()}catch{}finally{window.location.href="/yuyx"}}async function V(){const l=t.value.trim();if(!l){i.error("请输入新用户名");return}try{await b.confirm(`确定将管理员用户名修改为「${l}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await C(l),i.success("用户名修改成功,请重新登录"),t.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){const l=d.value;if(!l){i.error("请输入新密码");return}if(l.length<6){i.error("密码至少6个字符");return}try{await b.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await P(l),i.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(l,e)=>{const w=u("el-input"),y=u("el-form-item"),v=u("el-form"),_=u("el-button"),g=u("el-card");return k(),h("div",E,[e[7]||(e[7]=r("div",{class:"app-page-title"},[r("h2",null,"设置"),r("span",{class:"app-muted"},"管理员账号设置")],-1)),a(g,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:s(()=>[a(y,{label:"新用户名"},{default:s(()=>[a(w,{modelValue:t.value,"onUpdate:modelValue":e[0]||(e[0]=c=>t.value=c),placeholder:"输入新用户名",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(_,{type:"primary",loading:n.value,onClick:V},{default:s(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(g,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:s(()=>[a(y,{label:"新密码"},{default:s(()=>[a(w,{modelValue:d.value,"onUpdate:modelValue":e[1]||(e[1]=c=>d.value=c),type:"password","show-password":"",placeholder:"输入新密码",disabled:n.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(_,{type:"primary",loading:n.value,onClick:B},{default:s(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},A=T(N,[["__scopeId","data-v-2f4b840f"]]);export{A as default};

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-84b6799b]{display:flex;flex-direction:column;gap:12px}.metric-card[data-v-84b6799b],.card[data-v-84b6799b]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.metric-label[data-v-84b6799b]{font-size:12px;color:var(--app-muted)}.metric-value[data-v-84b6799b]{margin-top:6px;font-size:18px;font-weight:800}.metric-sub[data-v-84b6799b]{margin-top:4px;font-size:12px}.section-head[data-v-84b6799b]{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:12px}.section-title[data-v-84b6799b]{margin:0;font-size:14px;font-weight:800}.count-row[data-v-84b6799b]{margin-bottom:10px}.count-card[data-v-84b6799b]{border-radius:10px;border:1px solid var(--app-border)}.count-card.ok[data-v-84b6799b]{background:#10b98114}.count-card.warn[data-v-84b6799b]{background:#f59e0b14}.count-value[data-v-84b6799b]{font-size:22px;font-weight:900;line-height:1.1}.count-label[data-v-84b6799b]{margin-top:4px;font-size:12px;color:var(--app-muted)}.sub-title[data-v-84b6799b]{margin-top:14px;margin-bottom:8px;font-size:13px;font-weight:800}.empty[data-v-84b6799b]{padding:10px 0}.task-list[data-v-84b6799b]{display:flex;flex-direction:column;gap:8px}.task-item[data-v-84b6799b]{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:10px 12px;border-radius:10px;border:1px solid var(--app-border);background:#fff}.task-item.queue[data-v-84b6799b]{background:#f59e0b0f}.task-line[data-v-84b6799b]{display:flex;align-items:center;flex-wrap:wrap;gap:8px}.task-line2[data-v-84b6799b]{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:6px;font-size:12px}.task-user[data-v-84b6799b]{font-weight:600}.task-account[data-v-84b6799b]{font-weight:700;color:#2563eb}.dot[data-v-84b6799b]{width:8px;height:8px;border-radius:999px;display:inline-block}.task-status[data-v-84b6799b]{font-weight:700}.task-right[data-v-84b6799b]{font-size:12px;font-weight:700;color:#10b981;white-space:nowrap}.task-right.warn[data-v-84b6799b]{color:#f59e0b}@media(max-width:768px){.task-item[data-v-84b6799b]{flex-direction:column}.task-right[data-v-84b6799b]{align-self:flex-end}}.stat-grid[data-v-84b6799b]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.stat-box[data-v-84b6799b]{border-radius:12px;border:1px solid var(--app-border);padding:12px}.stat-box.ok[data-v-84b6799b]{background:#10b98114}.stat-box.err[data-v-84b6799b]{background:#ef444414}.stat-box.info[data-v-84b6799b]{background:#3b82f614}.stat-box.info2[data-v-84b6799b]{background:#06b6d414}.stat-name[data-v-84b6799b]{font-size:12px;font-weight:800;margin-bottom:6px}.stat-row[data-v-84b6799b]{display:flex;align-items:baseline;gap:8px}.stat-big[data-v-84b6799b]{font-size:20px;font-weight:900}.stat-row2[data-v-84b6799b]{margin-top:6px;font-size:12px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{D as n}from"./index-Do26tg8I.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{i as a,o as b,l as c,c as f,e as u};
import{S as n}from"./index-DJ3M_U2-.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{i as a,o as b,l as c,c as f,e as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
function s(t){return String(t||"").trim()}function a(t){return!t.startsWith("user_scheduled")||!t.includes(":")?"":t.split(":",2)[1]||""}function i(t){const e=s(t);if(!e||e==="manual")return{group:"manual",label:"手动",type:"success",tooltip:""};if(e==="scheduled")return{group:"scheduled",label:"定时任务",type:"primary",tooltip:"系统定时"};if(e.startsWith("user_scheduled")){const u=a(e),r=String(u||"").replace(/^batch_/,"");return{group:"scheduled",label:"定时任务",type:"primary",tooltip:r?`用户定时批次:${r}`:"用户定时"}}return{group:"manual",label:"手动",type:"success",tooltip:{batch:"手动批量",manual_screenshot:"手动截图",immediate:"立即执行",resumed:"断点恢复"}[e]||e}}export{i as g};

View File

@@ -1 +1 @@
import{D as a}from"./index-Do26tg8I.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};
import{S as a}from"./index-DJ3M_U2-.js";async function c(){const{data:t}=await a.get("/server/info");return t}async function e(){const{data:t}=await a.get("/docker_stats");return t}async function o(){const{data:t}=await a.get("/task/stats");return t}async function r(){const{data:t}=await a.get("/task/running");return t}async function i(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function f(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{r as a,c as b,e as c,i as d,f as e,o as f};

View File

@@ -1 +1 @@
import{D as a}from"./index-Do26tg8I.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function u(){const{data:t}=await a.post("/schedule/execute",{});return t}async function o(){const{data:t}=await a.get("/update/status");return t}async function r(){const{data:t}=await a.get("/update/result");return t}async function d(t={}){const{data:e}=await a.get("/update/log",{params:t});return e}async function i(){const{data:t}=await a.post("/update/check",{});return t}async function f(t={}){const{data:e}=await a.post("/update/run",t);return e}export{o as a,r as b,d as c,f as d,u as e,s as f,i as r,c as u};
import{S as a}from"./index-DJ3M_U2-.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function u(){const{data:t}=await a.post("/schedule/execute",{});return t}async function o(){const{data:t}=await a.get("/update/status");return t}async function r(){const{data:t}=await a.get("/update/result");return t}async function d(t={}){const{data:e}=await a.get("/update/log",{params:t});return e}async function i(){const{data:t}=await a.post("/update/check",{});return t}async function f(t={}){const{data:e}=await a.post("/update/run",t);return e}export{o as a,r as b,d as c,f as d,u as e,s as f,i as r,c as u};

View File

@@ -1 +1 @@
import{D as t}from"./index-Do26tg8I.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};
import{S as t}from"./index-DJ3M_U2-.js";async function n(){const{data:s}=await t.get("/users");return s}async function o(s){const{data:a}=await t.post(`/users/${s}/approve`);return a}async function c(s){const{data:a}=await t.post(`/users/${s}/reject`);return a}async function i(s){const{data:a}=await t.delete(`/users/${s}`);return a}async function u(s,a){const{data:e}=await t.post(`/users/${s}/vip`,{days:a});return e}async function p(s){const{data:a}=await t.delete(`/users/${s}/vip`);return a}async function d(s,a){const{data:e}=await t.post(`/users/${s}/reset_password`,{new_password:a});return e}export{o as a,p as b,d as c,i as d,n as f,c as r,u as s};

View File

@@ -5,8 +5,8 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-Do26tg8I.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-C73IFBwi.css">
<script type="module" crossorigin src="./assets/index-DJ3M_U2-.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-2V61oOJ0.css">
</head>
<body>
<div id="app"></div>