refactor(report): remove duplicated detail section and keep compact cards

This commit is contained in:
2026-02-07 10:16:35 +08:00
parent 462e12ca0d
commit 2d5be0feb2
24 changed files with 62 additions and 434 deletions

View File

@@ -38,7 +38,6 @@ const dockerStats = ref(null)
const browserPoolStats = ref(null) const browserPoolStats = ref(null)
const systemConfig = ref(null) const systemConfig = ref(null)
const queueTab = ref('running') const queueTab = ref('running')
const detailPanels = ref([])
function recordUpdatedAt() { function recordUpdatedAt() {
try { try {
@@ -460,344 +459,6 @@ onUnmounted(() => {
</div> </div>
</el-card> </el-card>
</section> </section>
<el-collapse v-model="detailPanels" class="detail-collapse">
<el-collapse-item name="detail">
<template #title>
<span class="detail-collapse-title">查看详细数据队列明细 / 线程池明细 / 系统资源明细</span>
</template>
<div class="desktop-report">
<el-row :gutter="12">
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-purple">
<el-icon><TrendCharts /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">任务概览</div>
<div class="panel-sub app-muted">
<template v-if="normalizeCount(taskToday.total_tasks) > 0">
今日成功率 {{ taskTodaySuccessRate }}% · {{ runningCountsLabel }}
</template>
<template v-else>今日无任务 · {{ runningCountsLabel }}</template>
</div>
</div>
</div>
<el-progress
type="circle"
:percentage="normalizeCount(taskToday.total_tasks) > 0 ? Math.round(taskTodaySuccessRate) : 0"
:width="74"
:stroke-width="10"
:status="normalizeCount(taskToday.total_tasks) === 0 ? 'success' : taskTodaySuccessRate >= 90 ? 'success' : taskTodaySuccessRate >= 60 ? 'warning' : 'exception'"
/>
</div>
<div class="metrics-block">
<div class="block-title">今日</div>
<MetricGrid :items="taskTodayCards" :loading="loading" :min-width="120" />
</div>
<div class="divider"></div>
<div class="metrics-block">
<div class="block-title">累计</div>
<MetricGrid :items="taskTotalCards" :loading="loading" :min-width="120" />
</div>
</el-card>
</el-col>
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-blue">
<el-icon><Tickets /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">队列监控</div>
<div class="panel-sub app-muted">{{ runningCountsLabel }}</div>
</div>
</div>
</div>
<el-tabs v-model="queueTab" class="queue-tabs" stretch>
<el-tab-pane name="running">
<template #label>
<span class="tab-label">
运行中
<el-tag size="small" effect="light" type="success">{{ runningCount }}</el-tag>
</span>
</template>
<div class="table-wrap">
<el-table :data="runningTaskList.slice(0, 10)" size="small" style="width: 100%">
<el-table-column label="用户" min-width="120">
<template #default="{ row }">{{ row.user_username || '-' }}</template>
</el-table-column>
<el-table-column label="账号" min-width="150">
<template #default="{ row }">{{ row.username || '-' }}</template>
</el-table-column>
<el-table-column label="来源" width="100">
<template #default="{ row }">{{ sourceLabel(row.source) }}</template>
</el-table-column>
<el-table-column label="类型" width="90">
<template #default="{ row }">{{ row.browse_type || '-' }}</template>
</el-table-column>
<el-table-column label="进度" width="100">
<template #default="{ row }">{{ row.progress_items }}/{{ row.progress_attachments }}</template>
</el-table-column>
<el-table-column label="耗时" width="100">
<template #default="{ row }">{{ row.elapsed_display || '-' }}</template>
</el-table-column>
<el-table-column label="状态" min-width="140">
<template #default="{ row }">{{ row.detail_status || row.status || '-' }}</template>
</el-table-column>
</el-table>
</div>
<div v-if="runningCount === 0" class="help app-muted">当前无运行任务</div>
</el-tab-pane>
<el-tab-pane name="queuing">
<template #label>
<span class="tab-label">
排队中
<el-tag size="small" effect="light" type="warning">{{ queuingCount }}</el-tag>
</span>
</template>
<div class="table-wrap">
<el-table :data="queuingTaskList.slice(0, 10)" size="small" style="width: 100%">
<el-table-column label="用户" min-width="120">
<template #default="{ row }">{{ row.user_username || '-' }}</template>
</el-table-column>
<el-table-column label="账号" min-width="150">
<template #default="{ row }">{{ row.username || '-' }}</template>
</el-table-column>
<el-table-column label="来源" width="100">
<template #default="{ row }">{{ sourceLabel(row.source) }}</template>
</el-table-column>
<el-table-column label="类型" width="90">
<template #default="{ row }">{{ row.browse_type || '-' }}</template>
</el-table-column>
<el-table-column label="等待" width="100">
<template #default="{ row }">{{ row.elapsed_display || '-' }}</template>
</el-table-column>
<el-table-column label="状态" min-width="160">
<template #default="{ row }">{{ row.detail_status || row.status || '-' }}</template>
</el-table-column>
</el-table>
</div>
<div v-if="queuingCount === 0" class="help app-muted">当前无排队任务</div>
</el-tab-pane>
</el-tabs>
</el-card>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-cyan">
<el-icon><Message /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">邮件报表</div>
<div class="panel-sub app-muted">成功率 {{ emailSuccessRate }}%</div>
</div>
</div>
</div>
<MetricGrid :items="emailCards" :loading="loading" :min-width="132" />
<div class="divider"></div>
<div class="metrics-block">
<div class="block-title">类型统计</div>
<MetricGrid :items="emailTypeCards" :loading="loading" :min-width="132" />
</div>
</el-card>
</el-col>
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-orange">
<el-icon><ChatLineSquare /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">反馈概览</div>
<div class="panel-sub app-muted">待处理 {{ normalizeCount(feedbackStats?.pending) }} </div>
</div>
</div>
</div>
<MetricGrid :items="feedbackCards" :loading="loading" :min-width="145" />
<div class="help app-muted">提示反馈处理越及时用户留存与满意度越高</div>
</el-card>
</el-col>
</el-row>
<el-row :gutter="12">
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-green">
<el-icon><Cpu /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">系统资源</div>
<div class="panel-sub app-muted">服务器与容器运行状态</div>
</div>
</div>
<el-tag v-if="serverInfo?.uptime" effect="light" type="info">运行 {{ serverInfo.uptime }}</el-tag>
</div>
<div class="resource-grid">
<div class="resource-item">
<div class="resource-k app-muted">CPU</div>
<el-progress
:percentage="Math.round(parsePercent(serverInfo?.cpu_percent))"
:status="parsePercent(serverInfo?.cpu_percent) >= 90 ? 'exception' : parsePercent(serverInfo?.cpu_percent) >= 75 ? 'warning' : 'success'"
/>
<div class="resource-sub app-muted">{{ Math.round(parsePercent(serverInfo?.cpu_percent)) }}%</div>
</div>
<div class="resource-item">
<div class="resource-k app-muted">内存</div>
<el-progress
:percentage="Math.round(parsePercent(serverInfo?.memory_percent))"
:status="parsePercent(serverInfo?.memory_percent) >= 90 ? 'exception' : parsePercent(serverInfo?.memory_percent) >= 75 ? 'warning' : 'success'"
/>
<div class="resource-sub app-muted">
{{ serverInfo?.memory_used || '-' }} / {{ serverInfo?.memory_total || '-' }}{{ Math.round(parsePercent(serverInfo?.memory_percent)) }}%
</div>
</div>
<div class="resource-item">
<div class="resource-k app-muted">磁盘</div>
<el-progress
:percentage="Math.round(parsePercent(serverInfo?.disk_percent))"
:status="parsePercent(serverInfo?.disk_percent) >= 90 ? 'exception' : parsePercent(serverInfo?.disk_percent) >= 75 ? 'warning' : 'success'"
/>
<div class="resource-sub app-muted">
{{ serverInfo?.disk_used || '-' }} / {{ serverInfo?.disk_total || '-' }}{{ Math.round(parsePercent(serverInfo?.disk_percent)) }}%
</div>
</div>
</div>
<div class="divider"></div>
<div class="block-title">容器</div>
<el-descriptions border :column="2" size="small">
<el-descriptions-item label="状态">{{ dockerStats?.status || '-' }}</el-descriptions-item>
<el-descriptions-item label="容器名">{{ dockerStats?.container_name || '-' }}</el-descriptions-item>
<el-descriptions-item label="运行时长">{{ dockerStats?.uptime || '-' }}</el-descriptions-item>
<el-descriptions-item label="CPU">{{ dockerStats?.cpu_percent || '-' }}</el-descriptions-item>
<el-descriptions-item label="内存">{{ dockerStats?.memory_usage || '-' }}</el-descriptions-item>
<el-descriptions-item label="内存占比">{{ dockerStats?.memory_percent || '-' }}</el-descriptions-item>
</el-descriptions>
<div class="divider"></div>
<div class="panel-head">
<div class="head-left">
<div class="head-text">
<div class="panel-title">截图线程池</div>
<div class="panel-sub app-muted">
活跃有执行环境{{ browserPoolActiveWorkers }} · 忙碌 {{ browserPoolBusyWorkers }} · 队列 {{ browserPoolQueueSize }}
</div>
</div>
</div>
<el-tag v-if="browserPoolStats?.server_time_cst" effect="light" type="info">{{ browserPoolStats.server_time_cst }}</el-tag>
</div>
<MetricGrid :items="browserPoolCards" :loading="loading" :min-width="120" />
<div class="divider"></div>
<div class="table-wrap">
<el-table :data="browserPoolWorkers" size="small" border>
<el-table-column prop="worker_id" label="Worker" width="90" />
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag :type="workerPoolStatusType(row)" effect="light">{{ workerPoolStatusLabel(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="执行" width="90">
<template #default="{ row }">
<el-tag :type="workerRunTagType(row)" effect="light">{{ workerRunLabel(row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="任务" width="120">
<template #default="{ row }">
<span>{{ normalizeCount(row?.total_tasks) }}</span>
<span class="app-muted"> / </span>
<span :class="normalizeCount(row?.failed_tasks) ? 'err' : 'app-muted'">{{ normalizeCount(row?.failed_tasks) }}</span>
</template>
</el-table-column>
<el-table-column prop="browser_use_count" label="复用" width="90" />
<el-table-column prop="last_active_at" label="最近活跃" min-width="160" />
<el-table-column prop="browser_created_at" label="环境创建" min-width="160" />
</el-table>
</div>
</el-card>
</el-col>
<el-col :xs="24" :lg="12">
<el-card shadow="never" class="panel" :body-style="{ padding: '16px' }">
<div class="panel-head">
<div class="head-left">
<div class="head-icon tone-red">
<el-icon><Tools /></el-icon>
</div>
<div class="head-text">
<div class="panel-title">配置概览</div>
<div class="panel-sub app-muted">定时 / 代理 / 并发</div>
</div>
</div>
</div>
<div class="config-grid">
<div class="config-item">
<div class="config-k app-muted">定时任务</div>
<div class="config-v">
<el-tag v-if="scheduleEnabled" type="success" effect="light">启用</el-tag>
<el-tag v-else type="info" effect="light">关闭</el-tag>
<span class="config-inline app-muted"> {{ scheduleTime }} / {{ scheduleBrowseType }}</span>
</div>
<div class="config-sub app-muted">日期{{ scheduleWeekdaysDisplay || scheduleWeekdays || '-' }}</div>
</div>
<div class="config-item">
<div class="config-k app-muted">代理</div>
<div class="config-v">
<el-tag v-if="proxyEnabled" type="success" effect="light">启用</el-tag>
<el-tag v-else type="info" effect="light">关闭</el-tag>
<span v-if="proxyEnabled && proxyApiUrl" class="config-inline app-muted">{{ proxyApiUrl }}</span>
</div>
<div class="config-sub app-muted">有效期{{ proxyExpireMinutes || '-' }} 分钟</div>
</div>
<div class="config-item">
<div class="config-k app-muted">并发</div>
<div class="config-v">
<span>全局 {{ maxConcurrentGlobal || '-' }}</span>
<span class="config-split app-muted">/</span>
<span>单账号 {{ maxConcurrentPerAccount || '-' }}</span>
<span class="config-split app-muted">/</span>
<span>截图 {{ maxScreenshotConcurrent || '-' }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</el-collapse-item>
</el-collapse>
</div> </div>
</template> </template>
@@ -960,35 +621,6 @@ onUnmounted(() => {
display: none; display: none;
} }
.detail-collapse {
border: 1px solid rgba(17, 24, 39, 0.08);
border-radius: 14px;
background: rgba(255, 255, 255, 0.72);
overflow: hidden;
}
.detail-collapse :deep(.el-collapse-item__header) {
padding: 0 14px;
min-height: 42px;
font-size: 13px;
font-weight: 800;
background: rgba(248, 250, 252, 0.92);
border-bottom: 1px solid rgba(17, 24, 39, 0.08);
}
.detail-collapse :deep(.el-collapse-item__wrap) {
border: none;
background: transparent;
}
.detail-collapse :deep(.el-collapse-item__content) {
padding: 14px;
}
.detail-collapse-title {
color: #0f172a;
}
.panel { .panel {
border-radius: 18px; border-radius: 18px;
border: 1px solid rgba(17, 24, 39, 0.1); border: 1px solid rgba(17, 24, 39, 0.1);
@@ -1200,10 +832,6 @@ onUnmounted(() => {
gap: 10px; gap: 10px;
} }
.detail-collapse {
display: none;
}
.report-hero { .report-hero {
border-radius: 14px; border-radius: 14px;
padding: 12px; padding: 12px;

View File

@@ -1,6 +1,6 @@
{ {
"_MetricGrid-XvOZSVk5.js": { "_MetricGrid-CgcBn9pb.js": {
"file": "assets/MetricGrid-XvOZSVk5.js", "file": "assets/MetricGrid-CgcBn9pb.js",
"name": "MetricGrid", "name": "MetricGrid",
"imports": [ "imports": [
"index.html" "index.html"
@@ -13,36 +13,36 @@
"file": "assets/MetricGrid-yP_dkP6X.css", "file": "assets/MetricGrid-yP_dkP6X.css",
"src": "_MetricGrid-yP_dkP6X.css" "src": "_MetricGrid-yP_dkP6X.css"
}, },
"_email-CAc6RZKX.js": { "_email-CVq2PrUv.js": {
"file": "assets/email-CAc6RZKX.js", "file": "assets/email-CVq2PrUv.js",
"name": "email", "name": "email",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_system-iq9dIb3c.js": { "_system-DC-mm2Aw.js": {
"file": "assets/system-iq9dIb3c.js", "file": "assets/system-DC-mm2Aw.js",
"name": "system", "name": "system",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_tasks-CE5ZHWXT.js": { "_tasks-7DYyQLMx.js": {
"file": "assets/tasks-CE5ZHWXT.js", "file": "assets/tasks-7DYyQLMx.js",
"name": "tasks", "name": "tasks",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_users-BL7sGO9P.js": { "_users-UHxQPBbl.js": {
"file": "assets/users-BL7sGO9P.js", "file": "assets/users-UHxQPBbl.js",
"name": "users", "name": "users",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"index.html": { "index.html": {
"file": "assets/index-Bnl51FPS.js", "file": "assets/index-Cg-w8G7A.js",
"name": "index", "name": "index",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
@@ -62,7 +62,7 @@
] ]
}, },
"src/pages/AnnouncementsPage.vue": { "src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-CRT23KZN.js", "file": "assets/AnnouncementsPage-Xjvdn6OS.js",
"name": "AnnouncementsPage", "name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue", "src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -74,40 +74,40 @@
] ]
}, },
"src/pages/EmailPage.vue": { "src/pages/EmailPage.vue": {
"file": "assets/EmailPage-CgX09trg.js", "file": "assets/EmailPage-CB0pDhEa.js",
"name": "EmailPage", "name": "EmailPage",
"src": "src/pages/EmailPage.vue", "src": "src/pages/EmailPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_email-CAc6RZKX.js", "_email-CVq2PrUv.js",
"index.html", "index.html",
"_MetricGrid-XvOZSVk5.js" "_MetricGrid-CgcBn9pb.js"
], ],
"css": [ "css": [
"assets/EmailPage-BmPCDPYC.css" "assets/EmailPage-BmPCDPYC.css"
] ]
}, },
"src/pages/FeedbacksPage.vue": { "src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-B8K-ZGjj.js", "file": "assets/FeedbacksPage-Cek35_AN.js",
"name": "FeedbacksPage", "name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue", "src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-XvOZSVk5.js" "_MetricGrid-CgcBn9pb.js"
], ],
"css": [ "css": [
"assets/FeedbacksPage-mrXjCiV2.css" "assets/FeedbacksPage-mrXjCiV2.css"
] ]
}, },
"src/pages/LogsPage.vue": { "src/pages/LogsPage.vue": {
"file": "assets/LogsPage-DJN9jmoo.js", "file": "assets/LogsPage-DlgiquJf.js",
"name": "LogsPage", "name": "LogsPage",
"src": "src/pages/LogsPage.vue", "src": "src/pages/LogsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-BL7sGO9P.js", "_users-UHxQPBbl.js",
"_tasks-CE5ZHWXT.js", "_tasks-7DYyQLMx.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -115,36 +115,36 @@
] ]
}, },
"src/pages/ReportPage.vue": { "src/pages/ReportPage.vue": {
"file": "assets/ReportPage-B-b5AWRN.js", "file": "assets/ReportPage-Bowqk0qw.js",
"name": "ReportPage", "name": "ReportPage",
"src": "src/pages/ReportPage.vue", "src": "src/pages/ReportPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_email-CAc6RZKX.js", "_email-CVq2PrUv.js",
"_tasks-CE5ZHWXT.js", "_tasks-7DYyQLMx.js",
"_system-iq9dIb3c.js", "_system-DC-mm2Aw.js",
"_MetricGrid-XvOZSVk5.js" "_MetricGrid-CgcBn9pb.js"
], ],
"css": [ "css": [
"assets/ReportPage-CUu4BCzC.css" "assets/ReportPage-BUAmM22W.css"
] ]
}, },
"src/pages/SecurityPage.vue": { "src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-CReIKqoO.js", "file": "assets/SecurityPage-ZwNpU8eS.js",
"name": "SecurityPage", "name": "SecurityPage",
"src": "src/pages/SecurityPage.vue", "src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_MetricGrid-XvOZSVk5.js" "_MetricGrid-CgcBn9pb.js"
], ],
"css": [ "css": [
"assets/SecurityPage-DN76ndc_.css" "assets/SecurityPage-DN76ndc_.css"
] ]
}, },
"src/pages/SettingsPage.vue": { "src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-CbhhZ79T.js", "file": "assets/SettingsPage-CoSYWzHG.js",
"name": "SettingsPage", "name": "SettingsPage",
"src": "src/pages/SettingsPage.vue", "src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -156,12 +156,12 @@
] ]
}, },
"src/pages/SystemPage.vue": { "src/pages/SystemPage.vue": {
"file": "assets/SystemPage-DiPSfpi5.js", "file": "assets/SystemPage-Dg22nmv0.js",
"name": "SystemPage", "name": "SystemPage",
"src": "src/pages/SystemPage.vue", "src": "src/pages/SystemPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_system-iq9dIb3c.js", "_system-DC-mm2Aw.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -169,12 +169,12 @@
] ]
}, },
"src/pages/UsersPage.vue": { "src/pages/UsersPage.vue": {
"file": "assets/UsersPage-Bst8TOab.js", "file": "assets/UsersPage-BBavbhFt.js",
"name": "UsersPage", "name": "UsersPage",
"src": "src/pages/UsersPage.vue", "src": "src/pages/UsersPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-BL7sGO9P.js", "_users-UHxQPBbl.js",
"index.html" "index.html"
], ],
"css": [ "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

View File

@@ -1 +1 @@
import{_ as m,f as c,g as s,h as t,F as l,q as u,D as p,j as o,n as r,m as y,w as h,B as i,T as v,p as n,x as k,U as f}from"./index-Bnl51FPS.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,w)=>{const d=c("el-icon"),_=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(l,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(v(e.icon)))]),_:2},1024)])):r("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(_,{key:0,rows:1,animated:""})):(t(),s(l,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):r("",!0)],2))),128))],4)}}},M=m(N,[["__scopeId","data-v-00e217d4"]]);export{M}; import{_ as m,f as c,g as s,h as t,F as l,q as u,y as p,j as o,n as r,m as y,x as h,w as i,O as v,p as n,B as k,P as f}from"./index-Cg-w8G7A.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,w)=>{const d=c("el-icon"),_=c("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(l,null,u(a.items,e=>(t(),s("div",{key:e?.key||e?.label,class:p(["metric-card",`metric-tone--${e?.tone||"blue"}`])},[o("div",b,[e?.icon?(t(),s("div",x,[y(d,null,{default:h(()=>[(t(),i(v(e.icon)))]),_:2},1024)])):r("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(_,{key:0,rows:1,animated:""})):(t(),s(l,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):r("",!0)],2))),128))],4)}}},z=m(N,[["__scopeId","data-v-00e217d4"]]);export{z as M};

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a as n}from"./index-Bnl51FPS.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{o as a,i as b,l as c,c as f,e as u}; import{a as n}from"./index-Cg-w8G7A.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{o as a,i as b,l as c,c as f,e as u};

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a}from"./index-Bnl51FPS.js";async function o(){const{data:t}=await a.get("/system/config");return t}async function e(t){const{data:n}=await a.post("/system/config",t);return n}export{o as f,e as u}; import{a}from"./index-Cg-w8G7A.js";async function o(){const{data:t}=await a.get("/system/config");return t}async function e(t){const{data:n}=await a.post("/system/config",t);return n}export{o as f,e as u};

View File

@@ -1 +1 @@
import{a}from"./index-Bnl51FPS.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{a}from"./index-Cg-w8G7A.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{a as t}from"./index-Bnl51FPS.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{a as t}from"./index-Cg-w8G7A.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,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title> <title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-Bnl51FPS.js"></script> <script type="module" crossorigin src="./assets/index-Cg-w8G7A.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-DRsk2q1y.css"> <link rel="stylesheet" crossorigin href="./assets/index-DRsk2q1y.css">
</head> </head>
<body> <body>