diff --git a/admin-frontend/src/pages/SystemPage.vue b/admin-frontend/src/pages/SystemPage.vue
index df81d43..885d3a2 100644
--- a/admin-frontend/src/pages/SystemPage.vue
+++ b/admin-frontend/src/pages/SystemPage.vue
@@ -11,6 +11,7 @@ const loading = ref(false)
const maxConcurrentGlobal = ref(2)
const maxConcurrentPerAccount = ref(1)
const maxScreenshotConcurrent = ref(3)
+const dbSlowQueryMs = ref(120)
const proxyEnabled = ref(false)
const proxyApiUrl = ref('')
@@ -67,6 +68,7 @@ async function loadAll() {
maxConcurrentGlobal.value = system.max_concurrent_global ?? 2
maxConcurrentPerAccount.value = system.max_concurrent_per_account ?? 1
maxScreenshotConcurrent.value = system.max_screenshot_concurrent ?? 3
+ dbSlowQueryMs.value = system.db_slow_query_ms ?? 120
autoApproveEnabled.value = (system.auto_approve_enabled ?? 0) === 1
autoApproveHourlyLimit.value = system.auto_approve_hourly_limit ?? 10
@@ -100,11 +102,12 @@ async function saveConcurrency() {
max_concurrent_global: Number(maxConcurrentGlobal.value),
max_concurrent_per_account: Number(maxConcurrentPerAccount.value),
max_screenshot_concurrent: Number(maxScreenshotConcurrent.value),
+ db_slow_query_ms: Number(dbSlowQueryMs.value),
}
try {
await ElMessageBox.confirm(
- `确定更新并发配置吗?\n\n全局并发数: ${payload.max_concurrent_global}\n单账号并发数: ${payload.max_concurrent_per_account}\n截图并发数: ${payload.max_screenshot_concurrent}`,
+ `确定更新并发配置吗?\n\n全局并发数: ${payload.max_concurrent_global}\n单账号并发数: ${payload.max_concurrent_per_account}\n截图并发数: ${payload.max_screenshot_concurrent}\n慢 SQL 阈值: ${payload.db_slow_query_ms}ms`,
'保存并发配置',
{ confirmButtonText: '保存', cancelButtonText: '取消', type: 'warning' },
)
@@ -337,6 +340,11 @@ onMounted(loadAll)
截图资源占用较低,可按机器性能逐步提高。
+
+
+
+ 低于该阈值不会计入慢 SQL(0 表示关闭慢 SQL 采样)。
+
diff --git a/database.py b/database.py
index fadca2c..8f4c83c 100644
--- a/database.py
+++ b/database.py
@@ -120,7 +120,7 @@ config = get_config()
DB_FILE = config.DB_FILE
# 数据库版本 (用于迁移管理)
-DB_VERSION = 19
+DB_VERSION = 20
# ==================== 系统配置缓存(P1 / O-03) ====================
@@ -183,6 +183,12 @@ def init_database():
ensure_default_admin()
+ try:
+ config_value = get_system_config()
+ db_pool.configure_slow_query_runtime(threshold_ms=config_value.get("db_slow_query_ms"))
+ except Exception:
+ pass
+
def migrate_database():
"""数据库迁移(对外保留接口)。"""
@@ -240,6 +246,7 @@ def update_system_config(
kdocs_admin_notify_email=None,
kdocs_row_start=None,
kdocs_row_end=None,
+ db_slow_query_ms=None,
):
"""更新系统配置(写入后立即失效缓存)。"""
ok = _update_system_config(
@@ -268,7 +275,13 @@ def update_system_config(
kdocs_admin_notify_email=kdocs_admin_notify_email,
kdocs_row_start=kdocs_row_start,
kdocs_row_end=kdocs_row_end,
+ db_slow_query_ms=db_slow_query_ms,
)
if ok:
invalidate_system_config_cache()
+ try:
+ latest_config = get_system_config()
+ db_pool.configure_slow_query_runtime(threshold_ms=latest_config.get("db_slow_query_ms"))
+ except Exception:
+ pass
return ok
diff --git a/db/admin.py b/db/admin.py
index 7c57f3b..6cf07ca 100644
--- a/db/admin.py
+++ b/db/admin.py
@@ -17,6 +17,7 @@ _DEFAULT_SYSTEM_CONFIG = {
"max_concurrent_global": 2,
"max_concurrent_per_account": 1,
"max_screenshot_concurrent": 3,
+ "db_slow_query_ms": 120,
"schedule_enabled": 0,
"schedule_time": "02:00",
"schedule_browse_type": "应读",
@@ -49,6 +50,7 @@ _SYSTEM_CONFIG_UPDATERS = (
("schedule_weekdays", "schedule_weekdays"),
("max_concurrent_per_account", "max_concurrent_per_account"),
("max_screenshot_concurrent", "max_screenshot_concurrent"),
+ ("db_slow_query_ms", "db_slow_query_ms"),
("enable_screenshot", "enable_screenshot"),
("proxy_enabled", "proxy_enabled"),
("proxy_api_url", "proxy_api_url"),
@@ -265,6 +267,7 @@ def update_system_config(
kdocs_admin_notify_email=None,
kdocs_row_start=None,
kdocs_row_end=None,
+ db_slow_query_ms=None,
) -> bool:
"""更新系统配置(仅更新DB,不做缓存处理)。"""
arg_values = {
@@ -293,6 +296,7 @@ def update_system_config(
"kdocs_admin_notify_email": kdocs_admin_notify_email,
"kdocs_row_start": kdocs_row_start,
"kdocs_row_end": kdocs_row_end,
+ "db_slow_query_ms": db_slow_query_ms,
}
updates = []
diff --git a/db/migrations.py b/db/migrations.py
index 97ccbce..de60406 100644
--- a/db/migrations.py
+++ b/db/migrations.py
@@ -74,6 +74,7 @@ def _get_migration_steps():
(17, _migrate_to_v17),
(18, _migrate_to_v18),
(19, _migrate_to_v19),
+ (20, _migrate_to_v20),
]
@@ -884,3 +885,21 @@ def _migrate_to_v19(conn):
cursor.execute(statement)
conn.commit()
+
+
+
+def _migrate_to_v20(conn):
+ """迁移到版本20 - 慢SQL阈值系统配置"""
+ cursor = conn.cursor()
+
+ columns = _get_table_columns(cursor, "system_config")
+ _add_column_if_missing(
+ cursor,
+ "system_config",
+ columns,
+ "db_slow_query_ms",
+ "INTEGER DEFAULT 120",
+ ok_message=" [OK] 添加 system_config.db_slow_query_ms 字段",
+ )
+
+ conn.commit()
diff --git a/db/schema.py b/db/schema.py
index 1c53100..0772958 100644
--- a/db/schema.py
+++ b/db/schema.py
@@ -210,6 +210,7 @@ def ensure_schema(conn) -> None:
proxy_expire_minutes INTEGER DEFAULT 3,
max_screenshot_concurrent INTEGER DEFAULT 3,
max_concurrent_per_account INTEGER DEFAULT 1,
+ db_slow_query_ms INTEGER DEFAULT 120,
schedule_weekdays TEXT DEFAULT '1,2,3,4,5,6,7',
enable_screenshot INTEGER DEFAULT 1,
auto_approve_enabled INTEGER DEFAULT 0,
diff --git a/db_pool.py b/db_pool.py
index c66947d..02d5286 100755
--- a/db_pool.py
+++ b/db_pool.py
@@ -27,6 +27,45 @@ DB_LOCK_RETRY_BASE_MS = max(10, int(getattr(config, "DB_LOCK_RETRY_BASE_MS", 50)
DB_SLOW_QUERY_MS = max(0, int(getattr(config, "DB_SLOW_QUERY_MS", 120)))
DB_SLOW_QUERY_SQL_MAX_LEN = max(80, int(getattr(config, "DB_SLOW_QUERY_SQL_MAX_LEN", 240)))
+_slow_query_runtime_lock = threading.Lock()
+_slow_query_runtime_threshold_ms = DB_SLOW_QUERY_MS
+_slow_query_runtime_sql_max_len = DB_SLOW_QUERY_SQL_MAX_LEN
+
+
+def _get_slow_query_runtime_values() -> tuple[int, int]:
+ with _slow_query_runtime_lock:
+ return int(_slow_query_runtime_threshold_ms), int(_slow_query_runtime_sql_max_len)
+
+
+def get_slow_query_runtime() -> dict:
+ threshold_ms, sql_max_len = _get_slow_query_runtime_values()
+ return {"threshold_ms": threshold_ms, "sql_max_len": sql_max_len}
+
+
+def configure_slow_query_runtime(*, threshold_ms=None, sql_max_len=None) -> dict:
+ global _slow_query_runtime_threshold_ms, _slow_query_runtime_sql_max_len
+
+ with _slow_query_runtime_lock:
+ if threshold_ms is not None:
+ _slow_query_runtime_threshold_ms = max(0, int(threshold_ms))
+ if sql_max_len is not None:
+ _slow_query_runtime_sql_max_len = max(80, int(sql_max_len))
+
+ runtime_threshold_ms = int(_slow_query_runtime_threshold_ms)
+ runtime_sql_max_len = int(_slow_query_runtime_sql_max_len)
+
+ try:
+ from services.slow_sql_metrics import configure_slow_sql_runtime
+
+ configure_slow_sql_runtime(
+ threshold_ms=runtime_threshold_ms,
+ sql_max_len=runtime_sql_max_len,
+ )
+ except Exception:
+ pass
+
+ return {"threshold_ms": runtime_threshold_ms, "sql_max_len": runtime_sql_max_len}
+
def _is_lock_conflict_error(error: sqlite3.OperationalError) -> bool:
message = str(error or "").lower()
@@ -34,10 +73,11 @@ def _is_lock_conflict_error(error: sqlite3.OperationalError) -> bool:
def _compact_sql(sql: str) -> str:
+ _, sql_max_len = _get_slow_query_runtime_values()
statement = " ".join(str(sql or "").split())
- if len(statement) <= DB_SLOW_QUERY_SQL_MAX_LEN:
+ if len(statement) <= sql_max_len:
return statement
- return statement[: DB_SLOW_QUERY_SQL_MAX_LEN - 3] + "..."
+ return statement[: sql_max_len - 3] + "..."
def _describe_params(parameters) -> str:
@@ -298,9 +338,10 @@ class PooledConnection:
return False
def _on_query_executed(self, sql: str, parameters, elapsed_ms: float) -> None:
- if DB_SLOW_QUERY_MS <= 0:
+ slow_query_ms, _ = _get_slow_query_runtime_values()
+ if slow_query_ms <= 0:
return
- if elapsed_ms < DB_SLOW_QUERY_MS:
+ if elapsed_ms < slow_query_ms:
return
params_info = _describe_params(parameters)
diff --git a/routes/admin_api/system_config_api.py b/routes/admin_api/system_config_api.py
index 478b950..7daa56e 100644
--- a/routes/admin_api/system_config_api.py
+++ b/routes/admin_api/system_config_api.py
@@ -34,6 +34,7 @@ def update_system_config_api():
schedule_weekdays = data.get("schedule_weekdays")
new_max_concurrent_per_account = data.get("max_concurrent_per_account")
new_max_screenshot_concurrent = data.get("max_screenshot_concurrent")
+ db_slow_query_ms = data.get("db_slow_query_ms")
enable_screenshot = data.get("enable_screenshot")
auto_approve_enabled = data.get("auto_approve_enabled")
auto_approve_hourly_limit = data.get("auto_approve_hourly_limit")
@@ -62,6 +63,14 @@ def update_system_config_api():
if not isinstance(new_max_screenshot_concurrent, int) or new_max_screenshot_concurrent < 1:
return jsonify({"error": "截图并发数必须大于0(建议根据服务器配置设置,wkhtmltoimage 资源占用较低)"}), 400
+ if db_slow_query_ms is not None:
+ try:
+ db_slow_query_ms = int(db_slow_query_ms)
+ except (ValueError, TypeError):
+ return jsonify({"error": "慢 SQL 阈值必须是数字(毫秒)"}), 400
+ if db_slow_query_ms < 0 or db_slow_query_ms > 60000:
+ return jsonify({"error": "慢 SQL 阈值范围应在 0-60000 毫秒之间"}), 400
+
if enable_screenshot is not None:
if isinstance(enable_screenshot, bool):
enable_screenshot = 1 if enable_screenshot else 0
@@ -197,6 +206,7 @@ def update_system_config_api():
kdocs_admin_notify_email=kdocs_admin_notify_email,
kdocs_row_start=kdocs_row_start,
kdocs_row_end=kdocs_row_end,
+ db_slow_query_ms=db_slow_query_ms,
):
return jsonify({"error": "更新失败"}), 400
@@ -207,6 +217,14 @@ def update_system_config_api():
max_global=int(new_config.get("max_concurrent_global", old_config.get("max_concurrent_global", 2))),
max_per_user=int(new_config.get("max_concurrent_per_account", old_config.get("max_concurrent_per_account", 1))),
)
+
+ try:
+ import db_pool
+
+ db_pool.configure_slow_query_runtime(threshold_ms=new_config.get("db_slow_query_ms"))
+ except Exception as slow_sql_error:
+ logger.warning(f"慢 SQL 运行时阈值更新失败: {slow_sql_error}")
+
if new_max_screenshot_concurrent is not None:
try:
from browser_pool_worker import resize_browser_worker_pool
@@ -224,5 +242,7 @@ def update_system_config_api():
logger.info(f"单用户并发数已更新为: {new_max_concurrent_per_account}")
if new_max_screenshot_concurrent is not None:
logger.info(f"截图并发数已更新为: {new_max_screenshot_concurrent}")
+ if db_slow_query_ms is not None:
+ logger.info(f"慢 SQL 阈值已更新为: {db_slow_query_ms}ms")
return jsonify({"message": "系统配置已更新"})
diff --git a/services/slow_sql_metrics.py b/services/slow_sql_metrics.py
index 74041c8..19150ba 100644
--- a/services/slow_sql_metrics.py
+++ b/services/slow_sql_metrics.py
@@ -21,6 +21,10 @@ _RECENT_LIMIT = max(10, int(os.environ.get("DB_SLOW_SQL_RECENT_LIMIT", "50") or
_MAX_EVENTS = max(_RECENT_LIMIT, int(os.environ.get("DB_SLOW_SQL_MAX_EVENTS", "20000") or 20000))
_SQL_MAX_LEN = max(80, int(os.environ.get("DB_SLOW_QUERY_SQL_MAX_LEN", "240") or 240))
+_runtime_lock = threading.Lock()
+_runtime_threshold_ms = _SLOW_SQL_THRESHOLD_MS
+_runtime_sql_max_len = _SQL_MAX_LEN
+
_lock = threading.Lock()
_state = {
@@ -38,8 +42,29 @@ def _compact_text(value: str, max_len: int) -> str:
return f"{text[: max_len - 3]}..."
+def _get_runtime_values() -> tuple[float, int]:
+ with _runtime_lock:
+ return float(_runtime_threshold_ms), int(_runtime_sql_max_len)
+
+
+def configure_slow_sql_runtime(*, threshold_ms=None, sql_max_len=None) -> dict:
+ global _runtime_threshold_ms, _runtime_sql_max_len
+
+ with _runtime_lock:
+ if threshold_ms is not None:
+ _runtime_threshold_ms = max(0.0, float(threshold_ms))
+ if sql_max_len is not None:
+ _runtime_sql_max_len = max(80, int(sql_max_len))
+
+ return {
+ "threshold_ms": float(_runtime_threshold_ms),
+ "sql_max_len": int(_runtime_sql_max_len),
+ }
+
+
def _compact_sql(sql: str) -> str:
- return _compact_text(str(sql or ""), _SQL_MAX_LEN)
+ _, sql_max_len = _get_runtime_values()
+ return _compact_text(str(sql or ""), sql_max_len)
def _compact_params(params_info: str) -> str:
@@ -165,12 +190,14 @@ def get_slow_sql_metrics_snapshot() -> dict:
total_events = len(events)
avg_duration_ms = round((total_duration_ms / total_events), 2) if total_events > 0 else 0.0
+ runtime_threshold_ms, _ = _get_runtime_values()
+
return {
"since_ts": int(float(events[0].get("time") or 0.0)) if events else 0,
"window_seconds": _WINDOW_SECONDS,
"top_limit": _TOP_LIMIT,
"recent_limit": _RECENT_LIMIT,
- "slow_threshold_ms": _SLOW_SQL_THRESHOLD_MS,
+ "slow_threshold_ms": runtime_threshold_ms,
"total_slow_queries": total_events,
"unique_sql": len(grouped),
"avg_duration_ms": avg_duration_ms,
diff --git a/static/admin/.vite/manifest.json b/static/admin/.vite/manifest.json
index f0824d5..5bad685 100644
--- a/static/admin/.vite/manifest.json
+++ b/static/admin/.vite/manifest.json
@@ -1,6 +1,6 @@
{
- "_MetricGrid-Cm-6uQPC.js": {
- "file": "assets/MetricGrid-Cm-6uQPC.js",
+ "_MetricGrid-tQwTnWXY.js": {
+ "file": "assets/MetricGrid-tQwTnWXY.js",
"name": "MetricGrid",
"imports": [
"index.html",
@@ -14,29 +14,29 @@
"file": "assets/MetricGrid-yP_dkP6X.css",
"src": "_MetricGrid-yP_dkP6X.css"
},
- "_email-UCdCFkiw.js": {
- "file": "assets/email-UCdCFkiw.js",
+ "_email-BgLsevU3.js": {
+ "file": "assets/email-BgLsevU3.js",
"name": "email",
"imports": [
"index.html"
]
},
- "_system-BqUnjCUC.js": {
- "file": "assets/system-BqUnjCUC.js",
+ "_system-COpUDNdl.js": {
+ "file": "assets/system-COpUDNdl.js",
"name": "system",
"imports": [
"index.html"
]
},
- "_tasks-DW-oE78v.js": {
- "file": "assets/tasks-DW-oE78v.js",
+ "_tasks-Bprl-b9c.js": {
+ "file": "assets/tasks-Bprl-b9c.js",
"name": "tasks",
"imports": [
"index.html"
]
},
- "_users-LqVgYOIa.js": {
- "file": "assets/users-LqVgYOIa.js",
+ "_users-oERXj-b9.js": {
+ "file": "assets/users-oERXj-b9.js",
"name": "users",
"imports": [
"index.html"
@@ -54,7 +54,7 @@
"src": "_vendor-C68yOrAy.css"
},
"index.html": {
- "file": "assets/index-C51y9xCM.js",
+ "file": "assets/index-z8QOtKVT.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -77,7 +77,7 @@
]
},
"src/pages/AnnouncementsPage.vue": {
- "file": "assets/AnnouncementsPage-D4vGLsnP.js",
+ "file": "assets/AnnouncementsPage-B1ijXegw.js",
"name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true,
@@ -90,14 +90,14 @@
]
},
"src/pages/EmailPage.vue": {
- "file": "assets/EmailPage-BgnCtp6d.js",
+ "file": "assets/EmailPage-BA_Nuk9g.js",
"name": "EmailPage",
"src": "src/pages/EmailPage.vue",
"isDynamicEntry": true,
"imports": [
- "_email-UCdCFkiw.js",
+ "_email-BgLsevU3.js",
"index.html",
- "_MetricGrid-Cm-6uQPC.js",
+ "_MetricGrid-tQwTnWXY.js",
"_vendor-BczUEOE_.js"
],
"css": [
@@ -105,13 +105,13 @@
]
},
"src/pages/FeedbacksPage.vue": {
- "file": "assets/FeedbacksPage-B-5AB1AS.js",
+ "file": "assets/FeedbacksPage-Dg3BwCE1.js",
"name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
- "_MetricGrid-Cm-6uQPC.js",
+ "_MetricGrid-tQwTnWXY.js",
"_vendor-BczUEOE_.js"
],
"css": [
@@ -119,13 +119,13 @@
]
},
"src/pages/LogsPage.vue": {
- "file": "assets/LogsPage-DF5QqYRJ.js",
+ "file": "assets/LogsPage-B80ridTr.js",
"name": "LogsPage",
"src": "src/pages/LogsPage.vue",
"isDynamicEntry": true,
"imports": [
- "_users-LqVgYOIa.js",
- "_tasks-DW-oE78v.js",
+ "_users-oERXj-b9.js",
+ "_tasks-Bprl-b9c.js",
"index.html",
"_vendor-BczUEOE_.js"
],
@@ -134,30 +134,30 @@
]
},
"src/pages/ReportPage.vue": {
- "file": "assets/ReportPage-CJumwOUP.js",
+ "file": "assets/ReportPage-CVnILNWh.js",
"name": "ReportPage",
"src": "src/pages/ReportPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-BczUEOE_.js",
"index.html",
- "_email-UCdCFkiw.js",
- "_tasks-DW-oE78v.js",
- "_system-BqUnjCUC.js",
- "_MetricGrid-Cm-6uQPC.js"
+ "_email-BgLsevU3.js",
+ "_tasks-Bprl-b9c.js",
+ "_system-COpUDNdl.js",
+ "_MetricGrid-tQwTnWXY.js"
],
"css": [
"assets/ReportPage-Csv-Q6wC.css"
]
},
"src/pages/SecurityPage.vue": {
- "file": "assets/SecurityPage-rmlzXSVY.js",
+ "file": "assets/SecurityPage-tMcfFXjg.js",
"name": "SecurityPage",
"src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
- "_MetricGrid-Cm-6uQPC.js",
+ "_MetricGrid-tQwTnWXY.js",
"_vendor-BczUEOE_.js"
],
"css": [
@@ -165,7 +165,7 @@
]
},
"src/pages/SettingsPage.vue": {
- "file": "assets/SettingsPage-Czik5vp6.js",
+ "file": "assets/SettingsPage-Bagp2mPP.js",
"name": "SettingsPage",
"src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true,
@@ -178,26 +178,26 @@
]
},
"src/pages/SystemPage.vue": {
- "file": "assets/SystemPage-CYpwmM8C.js",
+ "file": "assets/SystemPage-8v_X5n5H.js",
"name": "SystemPage",
"src": "src/pages/SystemPage.vue",
"isDynamicEntry": true,
"imports": [
- "_system-BqUnjCUC.js",
+ "_system-COpUDNdl.js",
"index.html",
"_vendor-BczUEOE_.js"
],
"css": [
- "assets/SystemPage-DMyNBO3N.css"
+ "assets/SystemPage-DYBocGi2.css"
]
},
"src/pages/UsersPage.vue": {
- "file": "assets/UsersPage-hjzizufJ.js",
+ "file": "assets/UsersPage-CJwQe-N8.js",
"name": "UsersPage",
"src": "src/pages/UsersPage.vue",
"isDynamicEntry": true,
"imports": [
- "_users-LqVgYOIa.js",
+ "_users-oERXj-b9.js",
"index.html",
"_vendor-BczUEOE_.js"
],
diff --git a/static/admin/assets/AnnouncementsPage-D4vGLsnP.js b/static/admin/assets/AnnouncementsPage-B1ijXegw.js
similarity index 99%
rename from static/admin/assets/AnnouncementsPage-D4vGLsnP.js
rename to static/admin/assets/AnnouncementsPage-B1ijXegw.js
index ddcd3eb..d53c276 100644
--- a/static/admin/assets/AnnouncementsPage-D4vGLsnP.js
+++ b/static/admin/assets/AnnouncementsPage-B1ijXegw.js
@@ -1 +1 @@
-import{e as v,g as Y,r as p,W as Z,l as C,o as f,k as c,j as n,w as t,i as I,c as x,Y as q,Z as G,C as r,B as $,X as J,b as u,_ as D,E as h}from"./vendor-BczUEOE_.js";import{a as w,_ as K}from"./index-C51y9xCM.js";async function O(){const{data:o}=await w.get("/announcements");return o}async function Q(o){const{data:s}=await w.post("/announcements",o);return s}async function ee(o){const s=new FormData;s.append("file",o);const{data:_}=await w.post("/announcements/upload_image",s);return _}async function te(o){const{data:s}=await w.post(`/announcements/${o}/activate`);return s}async function ne(o){const{data:s}=await w.post(`/announcements/${o}/deactivate`);return s}async function ae(o){const{data:s}=await w.delete(`/announcements/${o}`);return s}const le={class:"page-stack"},se={class:"image-upload-row"},oe={key:1,class:"image-url"},ie={key:0,class:"image-preview"},ce=["src"],ue={class:"actions"},re={class:"table-wrap"},de=["title"],me={key:1,class:"app-muted"},fe={class:"actions"},pe={__name:"AnnouncementsPage",setup(o){const s=v(""),_=v(""),m=v(""),g=v(null),B=v(!1),T=v(!1),V=v([]);async function k(){T.value=!0;try{V.value=await O()}catch{V.value=[]}finally{T.value=!1}}function z(){s.value="",_.value="",m.value="",g.value&&(g.value.value="")}function U(){g.value?.click()}function F(){m.value="",g.value&&(g.value.value="")}async function P(l){const e=l.target?.files?.[0];if(e){if(e.type&&!e.type.startsWith("image/")){u.error("请选择图片文件"),l.target.value="";return}B.value=!0;try{const d=await ee(e);if(!d?.success||!d?.url){u.error(d?.error||"上传失败");return}m.value=d.url,u.success("上传成功")}catch{}finally{B.value=!1,l.target.value=""}}}async function E(l){const e=s.value.trim(),d=_.value.trim(),b=m.value.trim();if(!e||!d){u.error("标题和内容不能为空");return}try{const i=await Q({title:e,content:d,image_url:b,is_active:!!l});if(!i?.success){u.error(i?.error||"保存失败");return}u.success("保存成功"),z(),await k()}catch{}}async function R(l){const e=D("div",{class:"announcement-view"},[l.content?D("div",{class:"announcement-view-text"},l.content):null,l.image_url?D("img",{class:"announcement-view-image",src:l.image_url,alt:"公告图片"}):null]);await h.alert(e,l.title||"公告",{confirmButtonText:"关闭",dangerouslyUseHTMLString:!1})}async function S(l){try{await h.confirm("确定启用该公告吗?启用后将自动停用其他公告。","启用公告",{confirmButtonText:"启用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await te(l.id);if(!e?.success){u.error(e?.error||"启用失败");return}u.success("已启用"),await k()}catch{}}async function W(l){try{await h.confirm("确定停用该公告吗?","停用公告",{confirmButtonText:"停用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await ne(l.id);if(!e?.success){u.error(e?.error||"停用失败");return}u.success("已停用"),await k()}catch{}}async function j(l){try{await h.confirm("确定删除该公告吗?删除后无法恢复。","删除公告",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const e=await ae(l.id);if(!e?.success){u.error(e?.error||"删除失败");return}u.success("已删除"),await k()}catch{}}return Y(k),(l,e)=>{const d=p("el-input"),b=p("el-form-item"),i=p("el-button"),H=p("el-form"),M=p("el-card"),y=p("el-table-column"),N=p("el-tag"),L=p("el-table"),X=Z("loading");return f(),C("div",le,[e[17]||(e[17]=c("div",{class:"app-page-title"},[c("h2",null,"公告管理")],-1)),n(M,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[9]||(e[9]=c("h3",{class:"section-title"},"创建公告",-1)),n(H,{"label-width":"90px"},{default:t(()=>[n(b,{label:"公告标题"},{default:t(()=>[n(d,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=a=>s.value=a),placeholder:"请输入公告标题",maxlength:"100","show-word-limit":""},null,8,["modelValue"])]),_:1}),n(b,{label:"公告内容"},{default:t(()=>[n(d,{modelValue:_.value,"onUpdate:modelValue":e[1]||(e[1]=a=>_.value=a),type:"textarea",rows:5,placeholder:"请输入公告内容(将以弹窗形式展示)",maxlength:"2000","show-word-limit":""},null,8,["modelValue"])]),_:1}),n(b,{label:"公告图片"},{default:t(()=>[c("div",se,[n(i,{icon:q(G),loading:B.value,onClick:U},{default:t(()=>[...e[4]||(e[4]=[r("上传图片",-1)])]),_:1},8,["icon","loading"]),m.value?(f(),x(i,{key:0,onClick:F},{default:t(()=>[...e[5]||(e[5]=[r("移除",-1)])]),_:1})):I("",!0),m.value?(f(),C("span",oe,$(m.value),1)):I("",!0),c("input",{ref_key:"imageInputRef",ref:g,class:"image-input",type:"file",accept:"image/*",onChange:P},null,544)])]),_:1})]),_:1}),m.value?(f(),C("div",ie,[c("img",{src:m.value,alt:"公告图片预览"},null,8,ce)])):I("",!0),c("div",ue,[n(i,{type:"primary",onClick:e[2]||(e[2]=a=>E(!0))},{default:t(()=>[...e[6]||(e[6]=[r("发布并启用",-1)])]),_:1}),n(i,{onClick:e[3]||(e[3]=a=>E(!1))},{default:t(()=>[...e[7]||(e[7]=[r("保存但不启用",-1)])]),_:1}),n(i,{onClick:z},{default:t(()=>[...e[8]||(e[8]=[r("清空",-1)])]),_:1})]),e[10]||(e[10]=c("div",{class:"help"}," 说明:启用公告后,用户登录进入系统将弹窗提示;用户可选择“当次关闭”或“永久关闭本次公告”。 ",-1))]),_:1}),n(M,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[16]||(e[16]=c("h3",{class:"section-title"},"公告列表",-1)),c("div",re,[J((f(),x(L,{data:V.value,style:{width:"100%"}},{default:t(()=>[n(y,{prop:"id",label:"ID",width:"80"}),n(y,{label:"标题","min-width":"240"},{default:t(({row:a})=>[c("span",{class:"ellipsis",title:a.title},$(a.title),9,de)]),_:1}),n(y,{label:"状态",width:"120"},{default:t(({row:a})=>[n(N,{type:a.is_active?"success":"info",effect:"light"},{default:t(()=>[r($(a.is_active?"启用":"停用"),1)]),_:2},1032,["type"])]),_:1}),n(y,{label:"图片",width:"100"},{default:t(({row:a})=>[a.image_url?(f(),x(N,{key:0,type:"success",effect:"light"},{default:t(()=>[...e[11]||(e[11]=[r("有图",-1)])]),_:1})):(f(),C("span",me,"-"))]),_:1}),n(y,{prop:"created_at",label:"创建时间",width:"180"}),n(y,{label:"操作",width:"260",fixed:"right"},{default:t(({row:a})=>[c("div",fe,[n(i,{size:"small",onClick:A=>R(a)},{default:t(()=>[...e[12]||(e[12]=[r("查看",-1)])]),_:1},8,["onClick"]),a.is_active?(f(),x(i,{key:0,size:"small",onClick:A=>W(a)},{default:t(()=>[...e[13]||(e[13]=[r("停用",-1)])]),_:1},8,["onClick"])):(f(),x(i,{key:1,type:"success",size:"small",onClick:A=>S(a)},{default:t(()=>[...e[14]||(e[14]=[r("启用",-1)])]),_:1},8,["onClick"])),n(i,{type:"danger",size:"small",onClick:A=>j(a)},{default:t(()=>[...e[15]||(e[15]=[r("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[X,T.value]])])]),_:1})])}}},ge=K(pe,[["__scopeId","data-v-6f55521c"]]);export{ge as default};
+import{e as v,g as Y,r as p,W as Z,l as C,o as f,k as c,j as n,w as t,i as I,c as x,Y as q,Z as G,C as r,B as $,X as J,b as u,_ as D,E as h}from"./vendor-BczUEOE_.js";import{a as w,_ as K}from"./index-z8QOtKVT.js";async function O(){const{data:o}=await w.get("/announcements");return o}async function Q(o){const{data:s}=await w.post("/announcements",o);return s}async function ee(o){const s=new FormData;s.append("file",o);const{data:_}=await w.post("/announcements/upload_image",s);return _}async function te(o){const{data:s}=await w.post(`/announcements/${o}/activate`);return s}async function ne(o){const{data:s}=await w.post(`/announcements/${o}/deactivate`);return s}async function ae(o){const{data:s}=await w.delete(`/announcements/${o}`);return s}const le={class:"page-stack"},se={class:"image-upload-row"},oe={key:1,class:"image-url"},ie={key:0,class:"image-preview"},ce=["src"],ue={class:"actions"},re={class:"table-wrap"},de=["title"],me={key:1,class:"app-muted"},fe={class:"actions"},pe={__name:"AnnouncementsPage",setup(o){const s=v(""),_=v(""),m=v(""),g=v(null),B=v(!1),T=v(!1),V=v([]);async function k(){T.value=!0;try{V.value=await O()}catch{V.value=[]}finally{T.value=!1}}function z(){s.value="",_.value="",m.value="",g.value&&(g.value.value="")}function U(){g.value?.click()}function F(){m.value="",g.value&&(g.value.value="")}async function P(l){const e=l.target?.files?.[0];if(e){if(e.type&&!e.type.startsWith("image/")){u.error("请选择图片文件"),l.target.value="";return}B.value=!0;try{const d=await ee(e);if(!d?.success||!d?.url){u.error(d?.error||"上传失败");return}m.value=d.url,u.success("上传成功")}catch{}finally{B.value=!1,l.target.value=""}}}async function E(l){const e=s.value.trim(),d=_.value.trim(),b=m.value.trim();if(!e||!d){u.error("标题和内容不能为空");return}try{const i=await Q({title:e,content:d,image_url:b,is_active:!!l});if(!i?.success){u.error(i?.error||"保存失败");return}u.success("保存成功"),z(),await k()}catch{}}async function R(l){const e=D("div",{class:"announcement-view"},[l.content?D("div",{class:"announcement-view-text"},l.content):null,l.image_url?D("img",{class:"announcement-view-image",src:l.image_url,alt:"公告图片"}):null]);await h.alert(e,l.title||"公告",{confirmButtonText:"关闭",dangerouslyUseHTMLString:!1})}async function S(l){try{await h.confirm("确定启用该公告吗?启用后将自动停用其他公告。","启用公告",{confirmButtonText:"启用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await te(l.id);if(!e?.success){u.error(e?.error||"启用失败");return}u.success("已启用"),await k()}catch{}}async function W(l){try{await h.confirm("确定停用该公告吗?","停用公告",{confirmButtonText:"停用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await ne(l.id);if(!e?.success){u.error(e?.error||"停用失败");return}u.success("已停用"),await k()}catch{}}async function j(l){try{await h.confirm("确定删除该公告吗?删除后无法恢复。","删除公告",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const e=await ae(l.id);if(!e?.success){u.error(e?.error||"删除失败");return}u.success("已删除"),await k()}catch{}}return Y(k),(l,e)=>{const d=p("el-input"),b=p("el-form-item"),i=p("el-button"),H=p("el-form"),M=p("el-card"),y=p("el-table-column"),N=p("el-tag"),L=p("el-table"),X=Z("loading");return f(),C("div",le,[e[17]||(e[17]=c("div",{class:"app-page-title"},[c("h2",null,"公告管理")],-1)),n(M,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[9]||(e[9]=c("h3",{class:"section-title"},"创建公告",-1)),n(H,{"label-width":"90px"},{default:t(()=>[n(b,{label:"公告标题"},{default:t(()=>[n(d,{modelValue:s.value,"onUpdate:modelValue":e[0]||(e[0]=a=>s.value=a),placeholder:"请输入公告标题",maxlength:"100","show-word-limit":""},null,8,["modelValue"])]),_:1}),n(b,{label:"公告内容"},{default:t(()=>[n(d,{modelValue:_.value,"onUpdate:modelValue":e[1]||(e[1]=a=>_.value=a),type:"textarea",rows:5,placeholder:"请输入公告内容(将以弹窗形式展示)",maxlength:"2000","show-word-limit":""},null,8,["modelValue"])]),_:1}),n(b,{label:"公告图片"},{default:t(()=>[c("div",se,[n(i,{icon:q(G),loading:B.value,onClick:U},{default:t(()=>[...e[4]||(e[4]=[r("上传图片",-1)])]),_:1},8,["icon","loading"]),m.value?(f(),x(i,{key:0,onClick:F},{default:t(()=>[...e[5]||(e[5]=[r("移除",-1)])]),_:1})):I("",!0),m.value?(f(),C("span",oe,$(m.value),1)):I("",!0),c("input",{ref_key:"imageInputRef",ref:g,class:"image-input",type:"file",accept:"image/*",onChange:P},null,544)])]),_:1})]),_:1}),m.value?(f(),C("div",ie,[c("img",{src:m.value,alt:"公告图片预览"},null,8,ce)])):I("",!0),c("div",ue,[n(i,{type:"primary",onClick:e[2]||(e[2]=a=>E(!0))},{default:t(()=>[...e[6]||(e[6]=[r("发布并启用",-1)])]),_:1}),n(i,{onClick:e[3]||(e[3]=a=>E(!1))},{default:t(()=>[...e[7]||(e[7]=[r("保存但不启用",-1)])]),_:1}),n(i,{onClick:z},{default:t(()=>[...e[8]||(e[8]=[r("清空",-1)])]),_:1})]),e[10]||(e[10]=c("div",{class:"help"}," 说明:启用公告后,用户登录进入系统将弹窗提示;用户可选择“当次关闭”或“永久关闭本次公告”。 ",-1))]),_:1}),n(M,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[16]||(e[16]=c("h3",{class:"section-title"},"公告列表",-1)),c("div",re,[J((f(),x(L,{data:V.value,style:{width:"100%"}},{default:t(()=>[n(y,{prop:"id",label:"ID",width:"80"}),n(y,{label:"标题","min-width":"240"},{default:t(({row:a})=>[c("span",{class:"ellipsis",title:a.title},$(a.title),9,de)]),_:1}),n(y,{label:"状态",width:"120"},{default:t(({row:a})=>[n(N,{type:a.is_active?"success":"info",effect:"light"},{default:t(()=>[r($(a.is_active?"启用":"停用"),1)]),_:2},1032,["type"])]),_:1}),n(y,{label:"图片",width:"100"},{default:t(({row:a})=>[a.image_url?(f(),x(N,{key:0,type:"success",effect:"light"},{default:t(()=>[...e[11]||(e[11]=[r("有图",-1)])]),_:1})):(f(),C("span",me,"-"))]),_:1}),n(y,{prop:"created_at",label:"创建时间",width:"180"}),n(y,{label:"操作",width:"260",fixed:"right"},{default:t(({row:a})=>[c("div",fe,[n(i,{size:"small",onClick:A=>R(a)},{default:t(()=>[...e[12]||(e[12]=[r("查看",-1)])]),_:1},8,["onClick"]),a.is_active?(f(),x(i,{key:0,size:"small",onClick:A=>W(a)},{default:t(()=>[...e[13]||(e[13]=[r("停用",-1)])]),_:1},8,["onClick"])):(f(),x(i,{key:1,type:"success",size:"small",onClick:A=>S(a)},{default:t(()=>[...e[14]||(e[14]=[r("启用",-1)])]),_:1},8,["onClick"])),n(i,{type:"danger",size:"small",onClick:A=>j(a)},{default:t(()=>[...e[15]||(e[15]=[r("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[X,T.value]])])]),_:1})])}}},ge=K(pe,[["__scopeId","data-v-6f55521c"]]);export{ge as default};
diff --git a/static/admin/assets/EmailPage-BgnCtp6d.js b/static/admin/assets/EmailPage-BA_Nuk9g.js
similarity index 99%
rename from static/admin/assets/EmailPage-BgnCtp6d.js
rename to static/admin/assets/EmailPage-BA_Nuk9g.js
index 562647a..f02f49f 100644
--- a/static/admin/assets/EmailPage-BgnCtp6d.js
+++ b/static/admin/assets/EmailPage-BA_Nuk9g.js
@@ -1 +1 @@
-import{a as De,c as He,b as Ne,f as Fe,u as ze}from"./email-UCdCFkiw.js";import{a as U,_ as Ie}from"./index-C51y9xCM.js";import{M as me}from"./MetricGrid-Cm-6uQPC.js";import{e as d,$ as pe,h as Qe,f as G,g as je,r as c,W as Ge,l as x,o as f,k as u,X as K,j as t,c as B,w as n,C as p,B as _,i as C,F as te,m as ce,E as M,b as m}from"./vendor-BczUEOE_.js";async function Ke(){const{data:b}=await U.get("/smtp/configs");return b}async function Oe(b){const{data:v}=await U.post("/smtp/configs",b);return v}async function Re(b,v){const{data:g}=await U.put(`/smtp/configs/${b}`,v);return g}async function We(b){const{data:v}=await U.delete(`/smtp/configs/${b}`);return v}async function Xe(b,v){const{data:g}=await U.post(`/smtp/configs/${b}/test`,{email:v});return g}async function Je(b){const{data:v}=await U.post(`/smtp/configs/${b}/primary`);return v}async function Ye(){const{data:b}=await U.post("/smtp/configs/primary/clear");return b}const Ze={class:"page-stack"},el={class:"help app-muted"},ll={class:"section-head"},tl={class:"table-wrap"},al={class:"sub-stats"},sl={class:"help app-muted"},nl={class:"section-head"},ol={class:"toolbar"},il={class:"table-wrap"},ul=["title"],rl=["title"],dl=["title"],ml={class:"pagination"},pl={class:"page-hint app-muted"},cl={style:{width:"100%"}},fl={key:0,class:"help"},_l={key:0},bl={key:0},vl={key:0},yl={class:"dialog-actions"},fe=15,gl={__name:"EmailPage",setup(b){const v=d(!1),g=d(!1),o=pe({enabled:!1,failover_enabled:!0,register_verify_enabled:!1,login_alert_enabled:!0,task_notify_enabled:!1,base_url:"",updated_at:null});let L=null;async function ae(){v.value=!0;try{const s=await Ne();o.enabled=!!s.enabled,o.failover_enabled=!!s.failover_enabled,o.register_verify_enabled=!!s.register_verify_enabled,o.login_alert_enabled=s.login_alert_enabled===void 0?!0:!!s.login_alert_enabled,o.task_notify_enabled=!!s.task_notify_enabled,o.base_url=s.base_url||"",o.updated_at=s.updated_at||null}catch{}finally{v.value=!1}}async function _e(){if(!v.value){g.value=!0;try{const s=await ze({enabled:o.enabled,failover_enabled:o.failover_enabled,register_verify_enabled:o.register_verify_enabled,login_alert_enabled:o.login_alert_enabled,task_notify_enabled:o.task_notify_enabled,base_url:(o.base_url||"").trim()});if(!s?.success){m.error(s?.error||"更新失败");return}m.success("邮件设置已更新"),await ae()}catch{}finally{g.value=!1}}}function E(){L&&window.clearTimeout(L),L=window.setTimeout(_e,300)}Qe(()=>{L&&window.clearTimeout(L),L=null});const O=d(!1),R=d([]),S=d(!1),k=d(!1),W=d(!1),H=d(!1),a=pe({id:null,name:"默认配置",enabled:!0,host:"",port:465,username:"",password:"",use_ssl:!0,use_tls:!1,sender_name:"自动化学习",sender_email:"",daily_limit:0,priority:0}),N=[{key:"custom",label:"自定义(手动填写)",defaults:null,note:"适用于其他邮箱/自建SMTP",links:[]},{key:"gmail",label:"Gmail",defaults:{host:"smtp.gmail.com",port:465,use_ssl:!0,use_tls:!1},note:"通常需要开启两步验证并创建应用专用密码(App Password)",links:[{label:"SMTP 设置说明",url:"https://support.google.com/mail/answer/7126229?hl=zh-Hans"},{label:"App Password",url:"https://myaccount.google.com/apppasswords"}]},{key:"qq",label:"QQ 邮箱",defaults:{host:"smtp.qq.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并获取授权码(不是QQ登录密码)",links:[{label:"QQ邮箱 SMTP 帮助",url:"https://service.mail.qq.com/cgi-bin/help?subtype=1&id=28&no=1001256"}]},{key:"163",label:"163 邮箱",defaults:{host:"smtp.163.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并使用授权码/客户端授权密码",links:[{label:"网易邮箱 SMTP 帮助",url:"https://help.mail.163.com/faqDetail.do?code=d7a5dc8471a22b76"}]},{key:"126",label:"126 邮箱",defaults:{host:"smtp.126.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并使用授权码/客户端授权密码",links:[{label:"网易邮箱帮助",url:"https://help.mail.163.com/"}]},{key:"outlook",label:"Outlook/Hotmail",defaults:{host:"smtp-mail.outlook.com",port:587,use_ssl:!1,use_tls:!0},note:"建议使用 TLS 587(部分账号需开启 SMTP AUTH)",links:[{label:"微软 SMTP 设置",url:"https://support.microsoft.com/office/pop-imap-and-smtp-settings-for-outlook-com-d088b0b7-0d38-4f9a-bc5d-509f9e4c6d3d"}]},{key:"office365",label:"Microsoft 365/Exchange",defaults:{host:"smtp.office365.com",port:587,use_ssl:!1,use_tls:!0},note:"企业邮箱常用配置(需启用 SMTP AUTH)",links:[{label:"微软官方说明",url:"https://learn.microsoft.com/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission"}]},{key:"icloud",label:"iCloud",defaults:{host:"smtp.mail.me.com",port:587,use_ssl:!1,use_tls:!0},note:"需要在 Apple ID 中生成“App 专用密码”",links:[{label:"Apple 邮件服务器设置",url:"https://support.apple.com/zh-cn/HT202304"}]},{key:"tencent_exmail",label:"腾讯企业邮箱",defaults:{host:"smtp.exmail.qq.com",port:465,use_ssl:!0,use_tls:!1},note:"企业邮箱常用配置",links:[{label:"腾讯企业邮箱帮助",url:"https://service.exmail.qq.com/cgi-bin/help?subtype=1&id=23&no=1001068"}]},{key:"aliyun_exmail",label:"阿里企业邮箱",defaults:{host:"smtp.mxhichina.com",port:465,use_ssl:!0,use_tls:!1},note:"企业邮箱常用配置",links:[{label:"阿里云文档",url:"https://help.aliyun.com/document_detail/50652.html"}]}],$=d("custom"),w=G(()=>N.find(s=>s.key===$.value)||N[0]),be=G(()=>k.value&&W.value?"留空保持不变":"SMTP密码或授权码");function ve(s){const e=String(s?.host||"").trim().toLowerCase();return e&&{"smtp.gmail.com":"gmail","smtp.qq.com":"qq","smtp.163.com":"163","smtp.126.com":"126","smtp-mail.outlook.com":"outlook","smtp.office365.com":"office365","smtp.mail.me.com":"icloud","smtp.exmail.qq.com":"tencent_exmail","smtp.mxhichina.com":"aliyun_exmail"}[e]||"custom"}function ye(s){const e=N.find(i=>i.key===s);!e||!e.defaults||(a.host=e.defaults.host,a.port=e.defaults.port,a.use_ssl=e.defaults.use_ssl,a.use_tls=e.defaults.use_tls)}function se(){a.id=null,a.name="默认配置",a.enabled=!0,a.host="",a.port=465,a.username="",a.password="",a.use_ssl=!0,a.use_tls=!1,a.sender_name="自动化学习",a.sender_email="",a.daily_limit=0,a.priority=0,W.value=!1,H.value=!1,$.value="custom"}async function q(){O.value=!0;try{R.value=await Ke()}catch{R.value=[]}finally{O.value=!1}}function ge(){k.value=!1,se(),$.value="custom",S.value=!0}function ke(s){k.value=!0,se(),a.id=s.id,a.name=s.name||"默认配置",a.enabled=!!s.enabled,a.host=s.host||"",a.port=s.port||465,a.username=s.username||"",a.password="",a.use_ssl=!!s.use_ssl,a.use_tls=!!s.use_tls,a.sender_name=s.sender_name||"自动化学习",a.sender_email=s.sender_email||"",a.daily_limit=s.daily_limit??0,a.priority=s.priority??0,W.value=!!s.has_password,H.value=!!s.is_primary,$.value=ve(s),S.value=!0}function ne(s){return s.is_primary?{label:"主",type:"warning"}:s.enabled?{label:"备用",type:"success"}:{label:"禁用",type:"info"}}function he(s){return s.daily_limit&&s.daily_limit>0?`${s.daily_sent}/${s.daily_limit}`:`${s.daily_sent}/∞`}async function we(){if(!a.host.trim()){m.error("SMTP服务器地址不能为空");return}if(!a.username.trim()){m.error("SMTP用户名不能为空");return}const s={name:a.name.trim()||"默认配置",enabled:!!a.enabled,priority:Number(a.priority)||0,host:a.host.trim(),port:Number(a.port)||465,username:a.username.trim(),use_ssl:!!a.use_ssl,use_tls:!!a.use_tls,sender_name:(a.sender_name||"").trim(),sender_email:(a.sender_email||"").trim(),daily_limit:Number(a.daily_limit)||0};try{if(k.value){const e={...s};a.password&&(e.password=a.password);const i=await Re(a.id,e);if(!i?.success){m.error(i?.error||"更新失败");return}m.success("保存成功")}else{const e={...s};a.password&&(e.password=a.password);const i=await Oe(e);if(!i?.success){m.error(i?.error||"创建失败");return}m.success("创建成功")}S.value=!1,await q()}catch{}}async function Ve(){if(!k.value||!a.id){m.error("请先保存配置后再测试");return}let s;try{const e=await M.prompt("请输入测试收件邮箱","测试连接",{inputPlaceholder:"name@example.com",confirmButtonText:"发送测试邮件",cancelButtonText:"取消",inputValidator:i=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(i||"").trim()),inputErrorMessage:"邮箱格式不正确"});s=String(e.value||"").trim()}catch{return}try{const e=await Xe(a.id,s);e?.success?(m.success("测试成功,邮件已发送"),await q()):await M.alert(e?.error||"测试失败","测试失败",{confirmButtonText:"知道了"})}catch{}}async function Se(){if(!(!k.value||!a.id)){try{await M.confirm("确定将该配置设为主配置吗?","设为主配置",{confirmButtonText:"设为主配置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const s=await Je(a.id);if(!s?.success){m.error(s?.error||"设置失败");return}m.success("已设为主配置"),S.value=!1,await q()}catch{}}}async function Te(){if(k.value){try{await M.confirm("确定取消主配置吗?取消后将按优先级选择可用SMTP。","取消主配置",{confirmButtonText:"取消主配置",cancelButtonText:"保留",type:"warning"})}catch{return}try{const s=await Ye();if(!s?.success){m.error(s?.error||"操作失败");return}m.success("已取消主配置"),S.value=!1,await q()}catch{}}}async function xe(){if(!(!k.value||!a.id)){try{await M.confirm("确定删除该SMTP配置吗?此操作不可恢复。","删除配置",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const s=await We(a.id);if(!s?.success){m.error(s?.error||"删除失败");return}m.success("已删除"),S.value=!1,await q()}catch{}}}const A=d(!1),h=d({}),X=d(!1),F=d(""),z=d(""),I=d(1),J=d([]),Y=d(0),Z=d(1);function Ce(s){return{register:"注册验证",reset:"密码重置",bind:"邮箱绑定",task_complete:"任务完成",security_alert:"安全告警"}[s]||s}function oe(s){return s?.username&&s?.user_id?`${s.username} (#${s.user_id})`:s?.user_id?`用户#${s.user_id}`:"系统"}const Pe=G(()=>[{key:"total_sent",label:"总发送",value:h.value?.total_sent||0,tone:"blue"},{key:"total_success",label:"成功",value:h.value?.total_success||0,tone:"green"},{key:"total_failed",label:"失败",value:h.value?.total_failed||0,tone:"red"},{key:"success_rate",label:"成功率",value:`${h.value?.success_rate||0}%`,tone:"purple"}]),Be=G(()=>[{key:"register_sent",label:"注册验证",value:h.value?.register_sent||0,tone:"cyan"},{key:"reset_sent",label:"密码重置",value:h.value?.reset_sent||0,tone:"orange"},{key:"bind_sent",label:"邮箱绑定",value:h.value?.bind_sent||0,tone:"purple"},{key:"task_complete_sent",label:"任务完成",value:h.value?.task_complete_sent||0,tone:"green"}]);async function Me(){A.value=!0;try{h.value=await Fe()}catch{h.value={}}finally{A.value=!1}}async function D(s=1){X.value=!0;try{const e={page:s,page_size:fe};F.value&&(e.type=F.value),z.value&&(e.status=z.value);const i=await De(e);J.value=i?.logs||[],Y.value=i?.total||0,I.value=i?.page||s,Z.value=i?.total_pages||1}catch{J.value=[],Y.value=0,Z.value=1}finally{X.value=!1}}async function Ue(){let s;try{const e=await M.prompt("请输入保留天数(将删除该天数之前的日志)","清理日志",{inputValue:"30",confirmButtonText:"清理",cancelButtonText:"取消",inputValidator:i=>{const r=parseInt(String(i),10);return Number.isFinite(r)&&r>=7},inputErrorMessage:"天数必须大于等于7"});s=parseInt(String(e.value),10)}catch{return}try{await M.confirm(`确定删除 ${s} 天之前的邮件日志吗?`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await He(s);if(!e?.success){m.error(e?.error||"清理失败");return}m.success(`已清理 ${e.deleted} 条日志`),await D(1)}catch{}}async function Le(){await Promise.all([ae(),q(),Me(),D(1)])}return je(Le),(s,e)=>{const i=c("el-switch"),r=c("el-form-item"),Ee=c("el-divider"),P=c("el-input"),ie=c("el-form"),Q=c("el-card"),T=c("el-button"),ue=c("el-tag"),y=c("el-table-column"),re=c("el-table"),V=c("el-option"),ee=c("el-select"),$e=c("el-pagination"),qe=c("el-link"),le=c("el-input-number"),Ae=c("el-dialog"),j=Ge("loading");return f(),x("div",Ze,[e[43]||(e[43]=u("div",{class:"app-page-title"},[u("h2",null,"邮件配置")],-1)),K((f(),B(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[29]||(e[29]=u("h3",{class:"section-title"},"全局设置",-1)),t(ie,{"label-width":"140px"},{default:n(()=>[t(r,{label:"启用邮件功能"},{default:n(()=>[t(i,{modelValue:o.enabled,"onUpdate:modelValue":e[0]||(e[0]=l=>o.enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"启用故障转移"},{default:n(()=>[t(i,{modelValue:o.failover_enabled,"onUpdate:modelValue":e[1]||(e[1]=l=>o.failover_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"启用注册邮箱验证"},{default:n(()=>[t(i,{modelValue:o.register_verify_enabled,"onUpdate:modelValue":e[2]||(e[2]=l=>o.register_verify_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(Ee,{"content-position":"left"},{default:n(()=>[...e[26]||(e[26]=[p("通知设置",-1)])]),_:1}),t(r,{label:"启用任务完成通知"},{default:n(()=>[t(i,{modelValue:o.task_notify_enabled,"onUpdate:modelValue":e[3]||(e[3]=l=>o.task_notify_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"新设备登录提醒"},{default:n(()=>[t(i,{modelValue:o.login_alert_enabled,"onUpdate:modelValue":e[4]||(e[4]=l=>o.login_alert_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"]),e[27]||(e[27]=u("div",{class:"help"},"当检测到新设备或新IP登录时,发送邮件提醒用户",-1))]),_:1}),t(r,{label:"网站基础URL"},{default:n(()=>[t(P,{modelValue:o.base_url,"onUpdate:modelValue":e[5]||(e[5]=l=>o.base_url=l),placeholder:"例如: https://example.com",disabled:g.value,onBlur:E},null,8,["modelValue","disabled"]),e[28]||(e[28]=u("div",{class:"help"},"用于生成邮件中的验证链接,留空则使用默认配置。",-1))]),_:1})]),_:1}),u("div",el,"最近更新时间:"+_(o.updated_at||"-"),1)]),_:1})),[[j,v.value]]),t(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[u("div",ll,[e[31]||(e[31]=u("h3",{class:"section-title"},"SMTP配置列表",-1)),t(T,{type:"primary",onClick:ge},{default:n(()=>[...e[30]||(e[30]=[p("+ 添加配置",-1)])]),_:1})]),u("div",tl,[K((f(),B(re,{data:R.value,style:{width:"100%"}},{default:n(()=>[t(y,{label:"状态",width:"90"},{default:n(({row:l})=>[t(ue,{type:ne(l).type,effect:"light"},{default:n(()=>[p(_(ne(l).label),1)]),_:2},1032,["type"])]),_:1}),t(y,{prop:"name",label:"名称","min-width":"160"}),t(y,{label:"服务器","min-width":"200"},{default:n(({row:l})=>[p(_(l.host)+":"+_(l.port),1)]),_:1}),t(y,{label:"今日/限额",width:"110"},{default:n(({row:l})=>[p(_(he(l)),1)]),_:1}),t(y,{label:"成功率",width:"100"},{default:n(({row:l})=>[p(_(l.success_rate)+"%",1)]),_:1}),t(y,{label:"操作",width:"120",fixed:"right"},{default:n(({row:l})=>[t(T,{size:"small",onClick:de=>ke(l)},{default:n(()=>[...e[32]||(e[32]=[p("编辑",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[j,O.value]])])]),_:1}),K((f(),B(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[33]||(e[33]=u("h3",{class:"section-title"},"邮件发送统计",-1)),t(me,{items:Pe.value,loading:A.value,"min-width":160},null,8,["items","loading"]),u("div",al,[t(me,{items:Be.value,loading:A.value,"min-width":150},null,8,["items","loading"])]),u("div",sl,"最后更新:"+_(h.value.last_updated||"-"),1)]),_:1})),[[j,A.value]]),t(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[u("div",nl,[e[35]||(e[35]=u("h3",{class:"section-title"},"邮件发送日志",-1)),u("div",ol,[t(ee,{modelValue:F.value,"onUpdate:modelValue":e[6]||(e[6]=l=>F.value=l),style:{width:"140px"},onChange:e[7]||(e[7]=l=>D(1))},{default:n(()=>[t(V,{label:"全部类型",value:""}),t(V,{label:"注册验证",value:"register"}),t(V,{label:"密码重置",value:"reset"}),t(V,{label:"邮箱绑定",value:"bind"}),t(V,{label:"任务完成",value:"task_complete"}),t(V,{label:"安全告警",value:"security_alert"})]),_:1},8,["modelValue"]),t(ee,{modelValue:z.value,"onUpdate:modelValue":e[8]||(e[8]=l=>z.value=l),style:{width:"120px"},onChange:e[9]||(e[9]=l=>D(1))},{default:n(()=>[t(V,{label:"全部状态",value:""}),t(V,{label:"成功",value:"success"}),t(V,{label:"失败",value:"failed"})]),_:1},8,["modelValue"]),t(T,{type:"danger",plain:"",onClick:Ue},{default:n(()=>[...e[34]||(e[34]=[p("清理日志",-1)])]),_:1})])]),u("div",il,[K((f(),B(re,{data:J.value,style:{width:"100%"}},{default:n(()=>[t(y,{prop:"created_at",label:"时间",width:"180"}),t(y,{prop:"email_to",label:"收件人","min-width":"180"}),t(y,{label:"来源用户","min-width":"160"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:oe(l)},_(oe(l)),9,ul)]),_:1}),t(y,{label:"类型",width:"120"},{default:n(({row:l})=>[p(_(Ce(l.email_type)),1)]),_:1}),t(y,{label:"主题","min-width":"220"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:l.subject},_(l.subject),9,rl)]),_:1}),t(y,{label:"状态",width:"90"},{default:n(({row:l})=>[t(ue,{type:l.status==="success"?"success":"danger",effect:"light"},{default:n(()=>[p(_(l.status==="success"?"成功":"失败"),1)]),_:2},1032,["type"])]),_:1}),t(y,{label:"错误","min-width":"200"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:l.error_message||""},_(l.error_message||"-"),9,dl)]),_:1})]),_:1},8,["data"])),[[j,X.value]])]),u("div",ml,[t($e,{"current-page":I.value,"onUpdate:currentPage":e[10]||(e[10]=l=>I.value=l),"page-size":fe,total:Y.value,layout:"prev, pager, next, ->, total",onCurrentChange:D},null,8,["current-page","total"]),u("div",pl,"第 "+_(I.value)+" / "+_(Z.value)+" 页",1)])]),_:1}),t(Ae,{modelValue:S.value,"onUpdate:modelValue":e[25]||(e[25]=l=>S.value=l),title:k.value?"编辑SMTP配置":"添加SMTP配置",width:"min(560px, 92vw)"},{footer:n(()=>[u("div",yl,[t(T,{onClick:Ve},{default:n(()=>[...e[36]||(e[36]=[p("测试连接",-1)])]),_:1}),k.value&&H.value?(f(),B(T,{key:0,type:"warning",plain:"",onClick:Te},{default:n(()=>[...e[37]||(e[37]=[p("取消主配置",-1)])]),_:1})):C("",!0),k.value&&!H.value?(f(),B(T,{key:1,onClick:Se},{default:n(()=>[...e[38]||(e[38]=[p("设为主配置",-1)])]),_:1})):C("",!0),k.value?(f(),B(T,{key:2,type:"danger",plain:"",onClick:xe},{default:n(()=>[...e[39]||(e[39]=[p("删除配置",-1)])]),_:1})):C("",!0),e[42]||(e[42]=u("div",{class:"spacer"},null,-1)),t(T,{onClick:e[24]||(e[24]=l=>S.value=!1)},{default:n(()=>[...e[40]||(e[40]=[p("取消",-1)])]),_:1}),t(T,{type:"primary",onClick:we},{default:n(()=>[...e[41]||(e[41]=[p("保存",-1)])]),_:1})])]),default:n(()=>[t(ie,{"label-width":"120px"},{default:n(()=>[t(r,{label:"名称"},{default:n(()=>[t(P,{modelValue:a.name,"onUpdate:modelValue":e[11]||(e[11]=l=>a.name=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"启用"},{default:n(()=>[t(i,{modelValue:a.enabled,"onUpdate:modelValue":e[12]||(e[12]=l=>a.enabled=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"邮箱模板"},{default:n(()=>[u("div",cl,[t(ee,{modelValue:$.value,"onUpdate:modelValue":e[13]||(e[13]=l=>$.value=l),placeholder:"选择常用邮箱模板",style:{width:"100%"},onChange:ye},{default:n(()=>[(f(),x(te,null,ce(N,l=>t(V,{key:l.key,label:l.label,value:l.key},null,8,["label","value"])),64))]),_:1},8,["modelValue"]),w.value.note||w.value.links&&w.value.links.length?(f(),x("div",fl,[w.value.note?(f(),x("span",_l,_(w.value.note),1)):C("",!0),w.value.links&&w.value.links.length?(f(),x(te,{key:1},[w.value.note?(f(),x("span",bl," · ")):C("",!0),(f(!0),x(te,null,ce(w.value.links,(l,de)=>(f(),x("span",{key:l.url},[t(qe,{href:l.url,target:"_blank",type:"primary",underline:!1},{default:n(()=>[p(_(l.label),1)]),_:2},1032,["href"]),de[t(P,{modelValue:a.host,"onUpdate:modelValue":e[14]||(e[14]=l=>a.host=l),placeholder:"smtp.example.com"},null,8,["modelValue"])]),_:1}),t(r,{label:"端口"},{default:n(()=>[t(le,{modelValue:a.port,"onUpdate:modelValue":e[15]||(e[15]=l=>a.port=l),min:1,max:65535},null,8,["modelValue"])]),_:1}),t(r,{label:"用户名"},{default:n(()=>[t(P,{modelValue:a.username,"onUpdate:modelValue":e[16]||(e[16]=l=>a.username=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"密码"},{default:n(()=>[t(P,{modelValue:a.password,"onUpdate:modelValue":e[17]||(e[17]=l=>a.password=l),type:"password","show-password":"",placeholder:be.value},null,8,["modelValue","placeholder"])]),_:1}),t(r,{label:"SSL"},{default:n(()=>[t(i,{modelValue:a.use_ssl,"onUpdate:modelValue":e[18]||(e[18]=l=>a.use_ssl=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"TLS"},{default:n(()=>[t(i,{modelValue:a.use_tls,"onUpdate:modelValue":e[19]||(e[19]=l=>a.use_tls=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"发件人名称"},{default:n(()=>[t(P,{modelValue:a.sender_name,"onUpdate:modelValue":e[20]||(e[20]=l=>a.sender_name=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"发件人邮箱"},{default:n(()=>[t(P,{modelValue:a.sender_email,"onUpdate:modelValue":e[21]||(e[21]=l=>a.sender_email=l),placeholder:"可选"},null,8,["modelValue"])]),_:1}),t(r,{label:"每日限额"},{default:n(()=>[t(le,{modelValue:a.daily_limit,"onUpdate:modelValue":e[22]||(e[22]=l=>a.daily_limit=l),min:0,max:1e6},null,8,["modelValue"])]),_:1}),t(r,{label:"优先级"},{default:n(()=>[t(le,{modelValue:a.priority,"onUpdate:modelValue":e[23]||(e[23]=l=>a.priority=l),min:0,max:1e3},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue","title"])])}}},Sl=Ie(gl,[["__scopeId","data-v-4f511165"]]);export{Sl as default};
+import{a as De,c as He,b as Ne,f as Fe,u as ze}from"./email-BgLsevU3.js";import{a as U,_ as Ie}from"./index-z8QOtKVT.js";import{M as me}from"./MetricGrid-tQwTnWXY.js";import{e as d,$ as pe,h as Qe,f as G,g as je,r as c,W as Ge,l as x,o as f,k as u,X as K,j as t,c as B,w as n,C as p,B as _,i as C,F as te,m as ce,E as M,b as m}from"./vendor-BczUEOE_.js";async function Ke(){const{data:b}=await U.get("/smtp/configs");return b}async function Oe(b){const{data:v}=await U.post("/smtp/configs",b);return v}async function Re(b,v){const{data:g}=await U.put(`/smtp/configs/${b}`,v);return g}async function We(b){const{data:v}=await U.delete(`/smtp/configs/${b}`);return v}async function Xe(b,v){const{data:g}=await U.post(`/smtp/configs/${b}/test`,{email:v});return g}async function Je(b){const{data:v}=await U.post(`/smtp/configs/${b}/primary`);return v}async function Ye(){const{data:b}=await U.post("/smtp/configs/primary/clear");return b}const Ze={class:"page-stack"},el={class:"help app-muted"},ll={class:"section-head"},tl={class:"table-wrap"},al={class:"sub-stats"},sl={class:"help app-muted"},nl={class:"section-head"},ol={class:"toolbar"},il={class:"table-wrap"},ul=["title"],rl=["title"],dl=["title"],ml={class:"pagination"},pl={class:"page-hint app-muted"},cl={style:{width:"100%"}},fl={key:0,class:"help"},_l={key:0},bl={key:0},vl={key:0},yl={class:"dialog-actions"},fe=15,gl={__name:"EmailPage",setup(b){const v=d(!1),g=d(!1),o=pe({enabled:!1,failover_enabled:!0,register_verify_enabled:!1,login_alert_enabled:!0,task_notify_enabled:!1,base_url:"",updated_at:null});let L=null;async function ae(){v.value=!0;try{const s=await Ne();o.enabled=!!s.enabled,o.failover_enabled=!!s.failover_enabled,o.register_verify_enabled=!!s.register_verify_enabled,o.login_alert_enabled=s.login_alert_enabled===void 0?!0:!!s.login_alert_enabled,o.task_notify_enabled=!!s.task_notify_enabled,o.base_url=s.base_url||"",o.updated_at=s.updated_at||null}catch{}finally{v.value=!1}}async function _e(){if(!v.value){g.value=!0;try{const s=await ze({enabled:o.enabled,failover_enabled:o.failover_enabled,register_verify_enabled:o.register_verify_enabled,login_alert_enabled:o.login_alert_enabled,task_notify_enabled:o.task_notify_enabled,base_url:(o.base_url||"").trim()});if(!s?.success){m.error(s?.error||"更新失败");return}m.success("邮件设置已更新"),await ae()}catch{}finally{g.value=!1}}}function E(){L&&window.clearTimeout(L),L=window.setTimeout(_e,300)}Qe(()=>{L&&window.clearTimeout(L),L=null});const O=d(!1),R=d([]),S=d(!1),k=d(!1),W=d(!1),H=d(!1),a=pe({id:null,name:"默认配置",enabled:!0,host:"",port:465,username:"",password:"",use_ssl:!0,use_tls:!1,sender_name:"自动化学习",sender_email:"",daily_limit:0,priority:0}),N=[{key:"custom",label:"自定义(手动填写)",defaults:null,note:"适用于其他邮箱/自建SMTP",links:[]},{key:"gmail",label:"Gmail",defaults:{host:"smtp.gmail.com",port:465,use_ssl:!0,use_tls:!1},note:"通常需要开启两步验证并创建应用专用密码(App Password)",links:[{label:"SMTP 设置说明",url:"https://support.google.com/mail/answer/7126229?hl=zh-Hans"},{label:"App Password",url:"https://myaccount.google.com/apppasswords"}]},{key:"qq",label:"QQ 邮箱",defaults:{host:"smtp.qq.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并获取授权码(不是QQ登录密码)",links:[{label:"QQ邮箱 SMTP 帮助",url:"https://service.mail.qq.com/cgi-bin/help?subtype=1&id=28&no=1001256"}]},{key:"163",label:"163 邮箱",defaults:{host:"smtp.163.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并使用授权码/客户端授权密码",links:[{label:"网易邮箱 SMTP 帮助",url:"https://help.mail.163.com/faqDetail.do?code=d7a5dc8471a22b76"}]},{key:"126",label:"126 邮箱",defaults:{host:"smtp.126.com",port:465,use_ssl:!0,use_tls:!1},note:"需要在邮箱设置中开启 SMTP 并使用授权码/客户端授权密码",links:[{label:"网易邮箱帮助",url:"https://help.mail.163.com/"}]},{key:"outlook",label:"Outlook/Hotmail",defaults:{host:"smtp-mail.outlook.com",port:587,use_ssl:!1,use_tls:!0},note:"建议使用 TLS 587(部分账号需开启 SMTP AUTH)",links:[{label:"微软 SMTP 设置",url:"https://support.microsoft.com/office/pop-imap-and-smtp-settings-for-outlook-com-d088b0b7-0d38-4f9a-bc5d-509f9e4c6d3d"}]},{key:"office365",label:"Microsoft 365/Exchange",defaults:{host:"smtp.office365.com",port:587,use_ssl:!1,use_tls:!0},note:"企业邮箱常用配置(需启用 SMTP AUTH)",links:[{label:"微软官方说明",url:"https://learn.microsoft.com/exchange/clients-and-mobile-in-exchange-online/authenticated-client-smtp-submission"}]},{key:"icloud",label:"iCloud",defaults:{host:"smtp.mail.me.com",port:587,use_ssl:!1,use_tls:!0},note:"需要在 Apple ID 中生成“App 专用密码”",links:[{label:"Apple 邮件服务器设置",url:"https://support.apple.com/zh-cn/HT202304"}]},{key:"tencent_exmail",label:"腾讯企业邮箱",defaults:{host:"smtp.exmail.qq.com",port:465,use_ssl:!0,use_tls:!1},note:"企业邮箱常用配置",links:[{label:"腾讯企业邮箱帮助",url:"https://service.exmail.qq.com/cgi-bin/help?subtype=1&id=23&no=1001068"}]},{key:"aliyun_exmail",label:"阿里企业邮箱",defaults:{host:"smtp.mxhichina.com",port:465,use_ssl:!0,use_tls:!1},note:"企业邮箱常用配置",links:[{label:"阿里云文档",url:"https://help.aliyun.com/document_detail/50652.html"}]}],$=d("custom"),w=G(()=>N.find(s=>s.key===$.value)||N[0]),be=G(()=>k.value&&W.value?"留空保持不变":"SMTP密码或授权码");function ve(s){const e=String(s?.host||"").trim().toLowerCase();return e&&{"smtp.gmail.com":"gmail","smtp.qq.com":"qq","smtp.163.com":"163","smtp.126.com":"126","smtp-mail.outlook.com":"outlook","smtp.office365.com":"office365","smtp.mail.me.com":"icloud","smtp.exmail.qq.com":"tencent_exmail","smtp.mxhichina.com":"aliyun_exmail"}[e]||"custom"}function ye(s){const e=N.find(i=>i.key===s);!e||!e.defaults||(a.host=e.defaults.host,a.port=e.defaults.port,a.use_ssl=e.defaults.use_ssl,a.use_tls=e.defaults.use_tls)}function se(){a.id=null,a.name="默认配置",a.enabled=!0,a.host="",a.port=465,a.username="",a.password="",a.use_ssl=!0,a.use_tls=!1,a.sender_name="自动化学习",a.sender_email="",a.daily_limit=0,a.priority=0,W.value=!1,H.value=!1,$.value="custom"}async function q(){O.value=!0;try{R.value=await Ke()}catch{R.value=[]}finally{O.value=!1}}function ge(){k.value=!1,se(),$.value="custom",S.value=!0}function ke(s){k.value=!0,se(),a.id=s.id,a.name=s.name||"默认配置",a.enabled=!!s.enabled,a.host=s.host||"",a.port=s.port||465,a.username=s.username||"",a.password="",a.use_ssl=!!s.use_ssl,a.use_tls=!!s.use_tls,a.sender_name=s.sender_name||"自动化学习",a.sender_email=s.sender_email||"",a.daily_limit=s.daily_limit??0,a.priority=s.priority??0,W.value=!!s.has_password,H.value=!!s.is_primary,$.value=ve(s),S.value=!0}function ne(s){return s.is_primary?{label:"主",type:"warning"}:s.enabled?{label:"备用",type:"success"}:{label:"禁用",type:"info"}}function he(s){return s.daily_limit&&s.daily_limit>0?`${s.daily_sent}/${s.daily_limit}`:`${s.daily_sent}/∞`}async function we(){if(!a.host.trim()){m.error("SMTP服务器地址不能为空");return}if(!a.username.trim()){m.error("SMTP用户名不能为空");return}const s={name:a.name.trim()||"默认配置",enabled:!!a.enabled,priority:Number(a.priority)||0,host:a.host.trim(),port:Number(a.port)||465,username:a.username.trim(),use_ssl:!!a.use_ssl,use_tls:!!a.use_tls,sender_name:(a.sender_name||"").trim(),sender_email:(a.sender_email||"").trim(),daily_limit:Number(a.daily_limit)||0};try{if(k.value){const e={...s};a.password&&(e.password=a.password);const i=await Re(a.id,e);if(!i?.success){m.error(i?.error||"更新失败");return}m.success("保存成功")}else{const e={...s};a.password&&(e.password=a.password);const i=await Oe(e);if(!i?.success){m.error(i?.error||"创建失败");return}m.success("创建成功")}S.value=!1,await q()}catch{}}async function Ve(){if(!k.value||!a.id){m.error("请先保存配置后再测试");return}let s;try{const e=await M.prompt("请输入测试收件邮箱","测试连接",{inputPlaceholder:"name@example.com",confirmButtonText:"发送测试邮件",cancelButtonText:"取消",inputValidator:i=>/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(i||"").trim()),inputErrorMessage:"邮箱格式不正确"});s=String(e.value||"").trim()}catch{return}try{const e=await Xe(a.id,s);e?.success?(m.success("测试成功,邮件已发送"),await q()):await M.alert(e?.error||"测试失败","测试失败",{confirmButtonText:"知道了"})}catch{}}async function Se(){if(!(!k.value||!a.id)){try{await M.confirm("确定将该配置设为主配置吗?","设为主配置",{confirmButtonText:"设为主配置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const s=await Je(a.id);if(!s?.success){m.error(s?.error||"设置失败");return}m.success("已设为主配置"),S.value=!1,await q()}catch{}}}async function Te(){if(k.value){try{await M.confirm("确定取消主配置吗?取消后将按优先级选择可用SMTP。","取消主配置",{confirmButtonText:"取消主配置",cancelButtonText:"保留",type:"warning"})}catch{return}try{const s=await Ye();if(!s?.success){m.error(s?.error||"操作失败");return}m.success("已取消主配置"),S.value=!1,await q()}catch{}}}async function xe(){if(!(!k.value||!a.id)){try{await M.confirm("确定删除该SMTP配置吗?此操作不可恢复。","删除配置",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const s=await We(a.id);if(!s?.success){m.error(s?.error||"删除失败");return}m.success("已删除"),S.value=!1,await q()}catch{}}}const A=d(!1),h=d({}),X=d(!1),F=d(""),z=d(""),I=d(1),J=d([]),Y=d(0),Z=d(1);function Ce(s){return{register:"注册验证",reset:"密码重置",bind:"邮箱绑定",task_complete:"任务完成",security_alert:"安全告警"}[s]||s}function oe(s){return s?.username&&s?.user_id?`${s.username} (#${s.user_id})`:s?.user_id?`用户#${s.user_id}`:"系统"}const Pe=G(()=>[{key:"total_sent",label:"总发送",value:h.value?.total_sent||0,tone:"blue"},{key:"total_success",label:"成功",value:h.value?.total_success||0,tone:"green"},{key:"total_failed",label:"失败",value:h.value?.total_failed||0,tone:"red"},{key:"success_rate",label:"成功率",value:`${h.value?.success_rate||0}%`,tone:"purple"}]),Be=G(()=>[{key:"register_sent",label:"注册验证",value:h.value?.register_sent||0,tone:"cyan"},{key:"reset_sent",label:"密码重置",value:h.value?.reset_sent||0,tone:"orange"},{key:"bind_sent",label:"邮箱绑定",value:h.value?.bind_sent||0,tone:"purple"},{key:"task_complete_sent",label:"任务完成",value:h.value?.task_complete_sent||0,tone:"green"}]);async function Me(){A.value=!0;try{h.value=await Fe()}catch{h.value={}}finally{A.value=!1}}async function D(s=1){X.value=!0;try{const e={page:s,page_size:fe};F.value&&(e.type=F.value),z.value&&(e.status=z.value);const i=await De(e);J.value=i?.logs||[],Y.value=i?.total||0,I.value=i?.page||s,Z.value=i?.total_pages||1}catch{J.value=[],Y.value=0,Z.value=1}finally{X.value=!1}}async function Ue(){let s;try{const e=await M.prompt("请输入保留天数(将删除该天数之前的日志)","清理日志",{inputValue:"30",confirmButtonText:"清理",cancelButtonText:"取消",inputValidator:i=>{const r=parseInt(String(i),10);return Number.isFinite(r)&&r>=7},inputErrorMessage:"天数必须大于等于7"});s=parseInt(String(e.value),10)}catch{return}try{await M.confirm(`确定删除 ${s} 天之前的邮件日志吗?`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await He(s);if(!e?.success){m.error(e?.error||"清理失败");return}m.success(`已清理 ${e.deleted} 条日志`),await D(1)}catch{}}async function Le(){await Promise.all([ae(),q(),Me(),D(1)])}return je(Le),(s,e)=>{const i=c("el-switch"),r=c("el-form-item"),Ee=c("el-divider"),P=c("el-input"),ie=c("el-form"),Q=c("el-card"),T=c("el-button"),ue=c("el-tag"),y=c("el-table-column"),re=c("el-table"),V=c("el-option"),ee=c("el-select"),$e=c("el-pagination"),qe=c("el-link"),le=c("el-input-number"),Ae=c("el-dialog"),j=Ge("loading");return f(),x("div",Ze,[e[43]||(e[43]=u("div",{class:"app-page-title"},[u("h2",null,"邮件配置")],-1)),K((f(),B(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[29]||(e[29]=u("h3",{class:"section-title"},"全局设置",-1)),t(ie,{"label-width":"140px"},{default:n(()=>[t(r,{label:"启用邮件功能"},{default:n(()=>[t(i,{modelValue:o.enabled,"onUpdate:modelValue":e[0]||(e[0]=l=>o.enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"启用故障转移"},{default:n(()=>[t(i,{modelValue:o.failover_enabled,"onUpdate:modelValue":e[1]||(e[1]=l=>o.failover_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"启用注册邮箱验证"},{default:n(()=>[t(i,{modelValue:o.register_verify_enabled,"onUpdate:modelValue":e[2]||(e[2]=l=>o.register_verify_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(Ee,{"content-position":"left"},{default:n(()=>[...e[26]||(e[26]=[p("通知设置",-1)])]),_:1}),t(r,{label:"启用任务完成通知"},{default:n(()=>[t(i,{modelValue:o.task_notify_enabled,"onUpdate:modelValue":e[3]||(e[3]=l=>o.task_notify_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"])]),_:1}),t(r,{label:"新设备登录提醒"},{default:n(()=>[t(i,{modelValue:o.login_alert_enabled,"onUpdate:modelValue":e[4]||(e[4]=l=>o.login_alert_enabled=l),disabled:g.value,onChange:E},null,8,["modelValue","disabled"]),e[27]||(e[27]=u("div",{class:"help"},"当检测到新设备或新IP登录时,发送邮件提醒用户",-1))]),_:1}),t(r,{label:"网站基础URL"},{default:n(()=>[t(P,{modelValue:o.base_url,"onUpdate:modelValue":e[5]||(e[5]=l=>o.base_url=l),placeholder:"例如: https://example.com",disabled:g.value,onBlur:E},null,8,["modelValue","disabled"]),e[28]||(e[28]=u("div",{class:"help"},"用于生成邮件中的验证链接,留空则使用默认配置。",-1))]),_:1})]),_:1}),u("div",el,"最近更新时间:"+_(o.updated_at||"-"),1)]),_:1})),[[j,v.value]]),t(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[u("div",ll,[e[31]||(e[31]=u("h3",{class:"section-title"},"SMTP配置列表",-1)),t(T,{type:"primary",onClick:ge},{default:n(()=>[...e[30]||(e[30]=[p("+ 添加配置",-1)])]),_:1})]),u("div",tl,[K((f(),B(re,{data:R.value,style:{width:"100%"}},{default:n(()=>[t(y,{label:"状态",width:"90"},{default:n(({row:l})=>[t(ue,{type:ne(l).type,effect:"light"},{default:n(()=>[p(_(ne(l).label),1)]),_:2},1032,["type"])]),_:1}),t(y,{prop:"name",label:"名称","min-width":"160"}),t(y,{label:"服务器","min-width":"200"},{default:n(({row:l})=>[p(_(l.host)+":"+_(l.port),1)]),_:1}),t(y,{label:"今日/限额",width:"110"},{default:n(({row:l})=>[p(_(he(l)),1)]),_:1}),t(y,{label:"成功率",width:"100"},{default:n(({row:l})=>[p(_(l.success_rate)+"%",1)]),_:1}),t(y,{label:"操作",width:"120",fixed:"right"},{default:n(({row:l})=>[t(T,{size:"small",onClick:de=>ke(l)},{default:n(()=>[...e[32]||(e[32]=[p("编辑",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[j,O.value]])])]),_:1}),K((f(),B(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[33]||(e[33]=u("h3",{class:"section-title"},"邮件发送统计",-1)),t(me,{items:Pe.value,loading:A.value,"min-width":160},null,8,["items","loading"]),u("div",al,[t(me,{items:Be.value,loading:A.value,"min-width":150},null,8,["items","loading"])]),u("div",sl,"最后更新:"+_(h.value.last_updated||"-"),1)]),_:1})),[[j,A.value]]),t(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[u("div",nl,[e[35]||(e[35]=u("h3",{class:"section-title"},"邮件发送日志",-1)),u("div",ol,[t(ee,{modelValue:F.value,"onUpdate:modelValue":e[6]||(e[6]=l=>F.value=l),style:{width:"140px"},onChange:e[7]||(e[7]=l=>D(1))},{default:n(()=>[t(V,{label:"全部类型",value:""}),t(V,{label:"注册验证",value:"register"}),t(V,{label:"密码重置",value:"reset"}),t(V,{label:"邮箱绑定",value:"bind"}),t(V,{label:"任务完成",value:"task_complete"}),t(V,{label:"安全告警",value:"security_alert"})]),_:1},8,["modelValue"]),t(ee,{modelValue:z.value,"onUpdate:modelValue":e[8]||(e[8]=l=>z.value=l),style:{width:"120px"},onChange:e[9]||(e[9]=l=>D(1))},{default:n(()=>[t(V,{label:"全部状态",value:""}),t(V,{label:"成功",value:"success"}),t(V,{label:"失败",value:"failed"})]),_:1},8,["modelValue"]),t(T,{type:"danger",plain:"",onClick:Ue},{default:n(()=>[...e[34]||(e[34]=[p("清理日志",-1)])]),_:1})])]),u("div",il,[K((f(),B(re,{data:J.value,style:{width:"100%"}},{default:n(()=>[t(y,{prop:"created_at",label:"时间",width:"180"}),t(y,{prop:"email_to",label:"收件人","min-width":"180"}),t(y,{label:"来源用户","min-width":"160"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:oe(l)},_(oe(l)),9,ul)]),_:1}),t(y,{label:"类型",width:"120"},{default:n(({row:l})=>[p(_(Ce(l.email_type)),1)]),_:1}),t(y,{label:"主题","min-width":"220"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:l.subject},_(l.subject),9,rl)]),_:1}),t(y,{label:"状态",width:"90"},{default:n(({row:l})=>[t(ue,{type:l.status==="success"?"success":"danger",effect:"light"},{default:n(()=>[p(_(l.status==="success"?"成功":"失败"),1)]),_:2},1032,["type"])]),_:1}),t(y,{label:"错误","min-width":"200"},{default:n(({row:l})=>[u("span",{class:"ellipsis",title:l.error_message||""},_(l.error_message||"-"),9,dl)]),_:1})]),_:1},8,["data"])),[[j,X.value]])]),u("div",ml,[t($e,{"current-page":I.value,"onUpdate:currentPage":e[10]||(e[10]=l=>I.value=l),"page-size":fe,total:Y.value,layout:"prev, pager, next, ->, total",onCurrentChange:D},null,8,["current-page","total"]),u("div",pl,"第 "+_(I.value)+" / "+_(Z.value)+" 页",1)])]),_:1}),t(Ae,{modelValue:S.value,"onUpdate:modelValue":e[25]||(e[25]=l=>S.value=l),title:k.value?"编辑SMTP配置":"添加SMTP配置",width:"min(560px, 92vw)"},{footer:n(()=>[u("div",yl,[t(T,{onClick:Ve},{default:n(()=>[...e[36]||(e[36]=[p("测试连接",-1)])]),_:1}),k.value&&H.value?(f(),B(T,{key:0,type:"warning",plain:"",onClick:Te},{default:n(()=>[...e[37]||(e[37]=[p("取消主配置",-1)])]),_:1})):C("",!0),k.value&&!H.value?(f(),B(T,{key:1,onClick:Se},{default:n(()=>[...e[38]||(e[38]=[p("设为主配置",-1)])]),_:1})):C("",!0),k.value?(f(),B(T,{key:2,type:"danger",plain:"",onClick:xe},{default:n(()=>[...e[39]||(e[39]=[p("删除配置",-1)])]),_:1})):C("",!0),e[42]||(e[42]=u("div",{class:"spacer"},null,-1)),t(T,{onClick:e[24]||(e[24]=l=>S.value=!1)},{default:n(()=>[...e[40]||(e[40]=[p("取消",-1)])]),_:1}),t(T,{type:"primary",onClick:we},{default:n(()=>[...e[41]||(e[41]=[p("保存",-1)])]),_:1})])]),default:n(()=>[t(ie,{"label-width":"120px"},{default:n(()=>[t(r,{label:"名称"},{default:n(()=>[t(P,{modelValue:a.name,"onUpdate:modelValue":e[11]||(e[11]=l=>a.name=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"启用"},{default:n(()=>[t(i,{modelValue:a.enabled,"onUpdate:modelValue":e[12]||(e[12]=l=>a.enabled=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"邮箱模板"},{default:n(()=>[u("div",cl,[t(ee,{modelValue:$.value,"onUpdate:modelValue":e[13]||(e[13]=l=>$.value=l),placeholder:"选择常用邮箱模板",style:{width:"100%"},onChange:ye},{default:n(()=>[(f(),x(te,null,ce(N,l=>t(V,{key:l.key,label:l.label,value:l.key},null,8,["label","value"])),64))]),_:1},8,["modelValue"]),w.value.note||w.value.links&&w.value.links.length?(f(),x("div",fl,[w.value.note?(f(),x("span",_l,_(w.value.note),1)):C("",!0),w.value.links&&w.value.links.length?(f(),x(te,{key:1},[w.value.note?(f(),x("span",bl," · ")):C("",!0),(f(!0),x(te,null,ce(w.value.links,(l,de)=>(f(),x("span",{key:l.url},[t(qe,{href:l.url,target:"_blank",type:"primary",underline:!1},{default:n(()=>[p(_(l.label),1)]),_:2},1032,["href"]),de[t(P,{modelValue:a.host,"onUpdate:modelValue":e[14]||(e[14]=l=>a.host=l),placeholder:"smtp.example.com"},null,8,["modelValue"])]),_:1}),t(r,{label:"端口"},{default:n(()=>[t(le,{modelValue:a.port,"onUpdate:modelValue":e[15]||(e[15]=l=>a.port=l),min:1,max:65535},null,8,["modelValue"])]),_:1}),t(r,{label:"用户名"},{default:n(()=>[t(P,{modelValue:a.username,"onUpdate:modelValue":e[16]||(e[16]=l=>a.username=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"密码"},{default:n(()=>[t(P,{modelValue:a.password,"onUpdate:modelValue":e[17]||(e[17]=l=>a.password=l),type:"password","show-password":"",placeholder:be.value},null,8,["modelValue","placeholder"])]),_:1}),t(r,{label:"SSL"},{default:n(()=>[t(i,{modelValue:a.use_ssl,"onUpdate:modelValue":e[18]||(e[18]=l=>a.use_ssl=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"TLS"},{default:n(()=>[t(i,{modelValue:a.use_tls,"onUpdate:modelValue":e[19]||(e[19]=l=>a.use_tls=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"发件人名称"},{default:n(()=>[t(P,{modelValue:a.sender_name,"onUpdate:modelValue":e[20]||(e[20]=l=>a.sender_name=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"发件人邮箱"},{default:n(()=>[t(P,{modelValue:a.sender_email,"onUpdate:modelValue":e[21]||(e[21]=l=>a.sender_email=l),placeholder:"可选"},null,8,["modelValue"])]),_:1}),t(r,{label:"每日限额"},{default:n(()=>[t(le,{modelValue:a.daily_limit,"onUpdate:modelValue":e[22]||(e[22]=l=>a.daily_limit=l),min:0,max:1e6},null,8,["modelValue"])]),_:1}),t(r,{label:"优先级"},{default:n(()=>[t(le,{modelValue:a.priority,"onUpdate:modelValue":e[23]||(e[23]=l=>a.priority=l),min:0,max:1e3},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue","title"])])}}},Sl=Ie(gl,[["__scopeId","data-v-4f511165"]]);export{Sl as default};
diff --git a/static/admin/assets/FeedbacksPage-B-5AB1AS.js b/static/admin/assets/FeedbacksPage-Dg3BwCE1.js
similarity index 97%
rename from static/admin/assets/FeedbacksPage-B-5AB1AS.js
rename to static/admin/assets/FeedbacksPage-Dg3BwCE1.js
index c63f6d8..ca3ad04 100644
--- a/static/admin/assets/FeedbacksPage-B-5AB1AS.js
+++ b/static/admin/assets/FeedbacksPage-Dg3BwCE1.js
@@ -1 +1 @@
-import{_ as $,b as j,r as L,c as G,d as O}from"./index-C51y9xCM.js";import{M as R}from"./MetricGrid-Cm-6uQPC.js";import{L as U,e as f,f as W,g as X,r as i,W as q,l as w,o as v,k as s,j as t,w as l,F,m as A,B as d,X as H,c as J,C as u,i as K,E as k,b as x}from"./vendor-BczUEOE_.js";const Q={class:"page-stack"},Y={class:"app-page-title"},Z={class:"toolbar"},ee={class:"section-head"},te={class:"app-muted"},ae={class:"table-wrap"},le={class:"ellipsis"},ne={class:"ellipsis"},se={class:"ellipsis"},oe={class:"actions"},ie={__name:"FeedbacksPage",setup(ce){const T=U("refreshNavBadges",null),p=f(!1),b=f(""),c=f({total:0,pending:0,replied:0,closed:0}),_=f([]),V=[{label:"全部状态",value:""},{label:"待处理",value:"pending"},{label:"已回复",value:"replied"},{label:"已关闭",value:"closed"}],M=W(()=>[{key:"total",label:"总反馈",value:c.value.total||0,tone:"blue"},{key:"pending",label:"待处理",value:c.value.pending||0,tone:"orange"},{key:"replied",label:"已回复",value:c.value.replied||0,tone:"green"},{key:"closed",label:"已关闭",value:c.value.closed||0,tone:"purple"}]);function B(n){return n==="pending"?{label:"待处理",type:"warning"}:n==="replied"?{label:"已回复",type:"success"}:n==="closed"?{label:"已关闭",type:"info"}:{label:n||"-",type:"info"}}async function r(){p.value=!0;try{const n=await j(b.value);_.value=n?.feedbacks||[],c.value=n?.stats||{total:0,pending:0,replied:0,closed:0}}catch{_.value=[],c.value={total:0,pending:0,replied:0,closed:0}}finally{p.value=!1}await T?.({pendingFeedbacks:c.value.pending||0})}async function N(n){let a;try{a=(await k.prompt("请输入回复内容","回复反馈",{inputType:"textarea",inputPlaceholder:"回复内容",confirmButtonText:"提交",cancelButtonText:"取消",inputValidator:g=>!!String(g||"").trim(),inputErrorMessage:"回复内容不能为空"})).value}catch{return}try{const m=await L(n.id,String(a||"").trim());x.success(m?.message||"回复成功"),await r()}catch{}}async function D(n){try{await k.confirm("确定要关闭这个反馈吗?","关闭反馈",{confirmButtonText:"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}try{const a=await G(n.id);x.success(a?.message||"反馈已关闭"),await r()}catch{}}async function E(n){try{await k.confirm("确定要删除这个反馈吗?此操作不可恢复!","删除反馈",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const a=await O(n.id);x.success(a?.message||"反馈已删除"),await r()}catch{}}return X(r),(n,a)=>{const m=i("el-option"),g=i("el-select"),o=i("el-table-column"),y=i("el-tooltip"),z=i("el-tag"),h=i("el-button"),I=i("el-table"),P=i("el-card"),S=q("loading");return v(),w("div",Q,[s("div",Y,[a[1]||(a[1]=s("h2",null,"反馈管理",-1)),s("div",Z,[t(g,{modelValue:b.value,"onUpdate:modelValue":a[0]||(a[0]=e=>b.value=e),style:{width:"160px"},onChange:r},{default:l(()=>[(v(),w(F,null,A(V,e=>t(m,{key:e.value,label:e.label,value:e.value},null,8,["label","value"])),64))]),_:1},8,["modelValue"])])]),t(R,{items:M.value,loading:p.value,"min-width":165},null,8,["items","loading"]),t(P,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[s("div",ee,[a[2]||(a[2]=s("h3",{class:"section-title"},"反馈列表",-1)),s("div",te,"共 "+d(_.value.length)+" 条(当前筛选)",1)]),s("div",ae,[H((v(),J(I,{data:_.value,style:{width:"100%"}},{default:l(()=>[t(o,{prop:"id",label:"ID",width:"80"}),t(o,{prop:"username",label:"用户",width:"140"}),t(o,{label:"标题","min-width":"180"},{default:l(({row:e})=>[t(y,{content:e.title,placement:"top","show-after":300},{default:l(()=>[s("span",le,d(e.title),1)]),_:2},1032,["content"])]),_:1}),t(o,{label:"描述","min-width":"220"},{default:l(({row:e})=>[t(y,{content:e.description,placement:"top","show-after":300},{default:l(()=>[s("span",ne,d(e.description),1)]),_:2},1032,["content"])]),_:1}),t(o,{prop:"contact",label:"联系方式","min-width":"160"},{default:l(({row:e})=>[u(d(e.contact||"-"),1)]),_:1}),t(o,{label:"状态",width:"110"},{default:l(({row:e})=>[t(z,{type:B(e.status).type,effect:"light"},{default:l(()=>[u(d(B(e.status).label),1)]),_:2},1032,["type"])]),_:1}),t(o,{prop:"created_at",label:"提交时间",width:"180"}),t(o,{label:"回复","min-width":"180"},{default:l(({row:e})=>[t(y,{content:e.admin_reply||"",placement:"top","show-after":300},{default:l(()=>[s("span",se,d(e.admin_reply||"-"),1)]),_:2},1032,["content"])]),_:1}),t(o,{label:"操作",width:"220",fixed:"right"},{default:l(({row:e})=>[s("div",oe,[e.status!=="closed"?(v(),w(F,{key:0},[t(h,{type:"primary",size:"small",onClick:C=>N(e)},{default:l(()=>[...a[3]||(a[3]=[u("回复",-1)])]),_:1},8,["onClick"]),t(h,{size:"small",onClick:C=>D(e)},{default:l(()=>[...a[4]||(a[4]=[u("关闭",-1)])]),_:1},8,["onClick"])],64)):K("",!0),t(h,{type:"danger",size:"small",onClick:C=>E(e)},{default:l(()=>[...a[5]||(a[5]=[u("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[S,p.value]])])]),_:1})])}}},pe=$(ie,[["__scopeId","data-v-910fe89b"]]);export{pe as default};
+import{_ as $,b as j,r as L,c as G,d as O}from"./index-z8QOtKVT.js";import{M as R}from"./MetricGrid-tQwTnWXY.js";import{L as U,e as f,f as W,g as X,r as i,W as q,l as w,o as v,k as s,j as t,w as l,F,m as A,B as d,X as H,c as J,C as u,i as K,E as k,b as x}from"./vendor-BczUEOE_.js";const Q={class:"page-stack"},Y={class:"app-page-title"},Z={class:"toolbar"},ee={class:"section-head"},te={class:"app-muted"},ae={class:"table-wrap"},le={class:"ellipsis"},ne={class:"ellipsis"},se={class:"ellipsis"},oe={class:"actions"},ie={__name:"FeedbacksPage",setup(ce){const T=U("refreshNavBadges",null),p=f(!1),b=f(""),c=f({total:0,pending:0,replied:0,closed:0}),_=f([]),V=[{label:"全部状态",value:""},{label:"待处理",value:"pending"},{label:"已回复",value:"replied"},{label:"已关闭",value:"closed"}],M=W(()=>[{key:"total",label:"总反馈",value:c.value.total||0,tone:"blue"},{key:"pending",label:"待处理",value:c.value.pending||0,tone:"orange"},{key:"replied",label:"已回复",value:c.value.replied||0,tone:"green"},{key:"closed",label:"已关闭",value:c.value.closed||0,tone:"purple"}]);function B(n){return n==="pending"?{label:"待处理",type:"warning"}:n==="replied"?{label:"已回复",type:"success"}:n==="closed"?{label:"已关闭",type:"info"}:{label:n||"-",type:"info"}}async function r(){p.value=!0;try{const n=await j(b.value);_.value=n?.feedbacks||[],c.value=n?.stats||{total:0,pending:0,replied:0,closed:0}}catch{_.value=[],c.value={total:0,pending:0,replied:0,closed:0}}finally{p.value=!1}await T?.({pendingFeedbacks:c.value.pending||0})}async function N(n){let a;try{a=(await k.prompt("请输入回复内容","回复反馈",{inputType:"textarea",inputPlaceholder:"回复内容",confirmButtonText:"提交",cancelButtonText:"取消",inputValidator:g=>!!String(g||"").trim(),inputErrorMessage:"回复内容不能为空"})).value}catch{return}try{const m=await L(n.id,String(a||"").trim());x.success(m?.message||"回复成功"),await r()}catch{}}async function D(n){try{await k.confirm("确定要关闭这个反馈吗?","关闭反馈",{confirmButtonText:"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}try{const a=await G(n.id);x.success(a?.message||"反馈已关闭"),await r()}catch{}}async function E(n){try{await k.confirm("确定要删除这个反馈吗?此操作不可恢复!","删除反馈",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const a=await O(n.id);x.success(a?.message||"反馈已删除"),await r()}catch{}}return X(r),(n,a)=>{const m=i("el-option"),g=i("el-select"),o=i("el-table-column"),y=i("el-tooltip"),z=i("el-tag"),h=i("el-button"),I=i("el-table"),P=i("el-card"),S=q("loading");return v(),w("div",Q,[s("div",Y,[a[1]||(a[1]=s("h2",null,"反馈管理",-1)),s("div",Z,[t(g,{modelValue:b.value,"onUpdate:modelValue":a[0]||(a[0]=e=>b.value=e),style:{width:"160px"},onChange:r},{default:l(()=>[(v(),w(F,null,A(V,e=>t(m,{key:e.value,label:e.label,value:e.value},null,8,["label","value"])),64))]),_:1},8,["modelValue"])])]),t(R,{items:M.value,loading:p.value,"min-width":165},null,8,["items","loading"]),t(P,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[s("div",ee,[a[2]||(a[2]=s("h3",{class:"section-title"},"反馈列表",-1)),s("div",te,"共 "+d(_.value.length)+" 条(当前筛选)",1)]),s("div",ae,[H((v(),J(I,{data:_.value,style:{width:"100%"}},{default:l(()=>[t(o,{prop:"id",label:"ID",width:"80"}),t(o,{prop:"username",label:"用户",width:"140"}),t(o,{label:"标题","min-width":"180"},{default:l(({row:e})=>[t(y,{content:e.title,placement:"top","show-after":300},{default:l(()=>[s("span",le,d(e.title),1)]),_:2},1032,["content"])]),_:1}),t(o,{label:"描述","min-width":"220"},{default:l(({row:e})=>[t(y,{content:e.description,placement:"top","show-after":300},{default:l(()=>[s("span",ne,d(e.description),1)]),_:2},1032,["content"])]),_:1}),t(o,{prop:"contact",label:"联系方式","min-width":"160"},{default:l(({row:e})=>[u(d(e.contact||"-"),1)]),_:1}),t(o,{label:"状态",width:"110"},{default:l(({row:e})=>[t(z,{type:B(e.status).type,effect:"light"},{default:l(()=>[u(d(B(e.status).label),1)]),_:2},1032,["type"])]),_:1}),t(o,{prop:"created_at",label:"提交时间",width:"180"}),t(o,{label:"回复","min-width":"180"},{default:l(({row:e})=>[t(y,{content:e.admin_reply||"",placement:"top","show-after":300},{default:l(()=>[s("span",se,d(e.admin_reply||"-"),1)]),_:2},1032,["content"])]),_:1}),t(o,{label:"操作",width:"220",fixed:"right"},{default:l(({row:e})=>[s("div",oe,[e.status!=="closed"?(v(),w(F,{key:0},[t(h,{type:"primary",size:"small",onClick:C=>N(e)},{default:l(()=>[...a[3]||(a[3]=[u("回复",-1)])]),_:1},8,["onClick"]),t(h,{size:"small",onClick:C=>D(e)},{default:l(()=>[...a[4]||(a[4]=[u("关闭",-1)])]),_:1},8,["onClick"])],64)):K("",!0),t(h,{type:"danger",size:"small",onClick:C=>E(e)},{default:l(()=>[...a[5]||(a[5]=[u("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[S,p.value]])])]),_:1})])}}},pe=$(ie,[["__scopeId","data-v-910fe89b"]]);export{pe as default};
diff --git a/static/admin/assets/LogsPage-DF5QqYRJ.js b/static/admin/assets/LogsPage-B80ridTr.js
similarity index 98%
rename from static/admin/assets/LogsPage-DF5QqYRJ.js
rename to static/admin/assets/LogsPage-B80ridTr.js
index 8c3fa85..305d12f 100644
--- a/static/admin/assets/LogsPage-DF5QqYRJ.js
+++ b/static/admin/assets/LogsPage-B80ridTr.js
@@ -1 +1 @@
-import{f as G}from"./users-LqVgYOIa.js";import{g as H,h as J}from"./tasks-DW-oE78v.js";import{_ as K}from"./index-C51y9xCM.js";import{e as u,f as Q,g as Z,r,W as ee,l as E,o as b,k as f,j as t,w as l,F as te,m as ae,c as B,C as m,X as le,B as d,E as P,b as oe}from"./vendor-BczUEOE_.js";function ne(p){return String(p||"").trim()}function se(p){return!p.startsWith("user_scheduled")||!p.includes(":")?"":p.split(":",2)[1]||""}function ue(p){const s=ne(p);if(!s||s==="manual")return{group:"manual",label:"手动",type:"success",tooltip:""};if(s==="scheduled")return{group:"scheduled",label:"定时任务",type:"primary",tooltip:"系统定时"};if(s.startsWith("user_scheduled")){const i=se(s),v=String(i||"").replace(/^batch_/,"");return{group:"scheduled",label:"定时任务",type:"primary",tooltip:v?`用户定时批次:${v}`:"用户定时"}}return{group:"manual",label:"手动",type:"success",tooltip:{batch:"手动批量",manual_screenshot:"手动截图",immediate:"立即执行",resumed:"断点恢复"}[s]||s}}const re={class:"page-stack"},ie={class:"filters"},ce={class:"table-wrap"},de={class:"ellipsis"},pe={class:"pagination"},me={class:"page-hint app-muted"},F=20,fe={__name:"LogsPage",setup(p){const s=u(!1),S=u([]),y=u(0),i=u(1),v=u(!1),T=u([]),h=u(""),w=u(""),V=u(""),x=u(""),k=u(""),$=Q(()=>Math.max(1,Math.ceil((y.value||0)/F)));function Y(o){if(o==null)return"-";const e=Number(o);return Number.isFinite(e)?e<60?`${e}秒`:`${Math.floor(e/60)}分${e%60}秒`:"-"}function _(o){const e=ue(o);return{key:e.group,label:e.label,type:e.type,tooltip:e.tooltip}}function I(o){return o==="success"?{label:"成功",type:"success"}:o==="failed"?{label:"失败",type:"danger"}:{label:o||"-",type:"info"}}async function z(){v.value=!0;try{const o=await G();T.value=(o||[]).map(e=>({id:e.id,username:e.username}))}catch{T.value=[]}finally{v.value=!1}}async function M(){s.value=!0;try{const o=(i.value-1)*F,e={limit:F,offset:o};h.value&&(e.date=h.value),w.value&&(e.status=w.value),V.value&&(e.source=V.value),x.value&&(e.user_id=x.value),k.value&&(e.account=k.value);const g=await H(e);S.value=g?.logs||[],y.value=g?.total||0}catch{S.value=[],y.value=0}finally{s.value=!1}}function O(){i.value=1,M()}function W(){h.value="",w.value="",V.value="",x.value="",k.value="",i.value=1,M()}async function j(){let o;try{const e=await P.prompt("请输入要清理多少天前的日志(默认30天)","清理旧日志",{inputValue:"30",confirmButtonText:"下一步",cancelButtonText:"取消",inputValidator:g=>{const n=parseInt(String(g),10);return Number.isFinite(n)&&n>=1},inputErrorMessage:"请输入有效的天数(大于0的整数)"});o=parseInt(String(e.value),10)}catch{return}try{await P.confirm(`确定要删除 ${o} 天前的所有日志吗?此操作不可恢复!`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await J(o);oe.success(e?.message||"清理成功"),i.value=1,await M()}catch{}}return Z(async()=>{await z(),await M()}),(o,e)=>{const g=r("el-date-picker"),n=r("el-option"),C=r("el-select"),R=r("el-input"),U=r("el-button"),L=r("el-card"),c=r("el-table-column"),D=r("el-tag"),N=r("el-tooltip"),A=r("el-table"),X=r("el-pagination"),q=ee("loading");return b(),E("div",re,[e[9]||(e[9]=f("div",{class:"app-page-title"},[f("h2",null,"任务日志")],-1)),t(L,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[f("div",ie,[t(g,{modelValue:h.value,"onUpdate:modelValue":e[0]||(e[0]=a=>h.value=a),type:"date","value-format":"YYYY-MM-DD",placeholder:"日期",style:{width:"150px"}},null,8,["modelValue"]),t(C,{modelValue:w.value,"onUpdate:modelValue":e[1]||(e[1]=a=>w.value=a),placeholder:"状态",style:{width:"120px"}},{default:l(()=>[t(n,{label:"全部",value:""}),t(n,{label:"成功",value:"success"}),t(n,{label:"失败",value:"failed"})]),_:1},8,["modelValue"]),t(C,{modelValue:V.value,"onUpdate:modelValue":e[2]||(e[2]=a=>V.value=a),placeholder:"来源",style:{width:"120px"}},{default:l(()=>[t(n,{label:"全部",value:""}),t(n,{label:"手动",value:"manual"}),t(n,{label:"定时任务(系统)",value:"scheduled"}),t(n,{label:"定时任务(用户)",value:"user_scheduled"}),t(n,{label:"手动(批量)",value:"batch"}),t(n,{label:"手动(截图)",value:"manual_screenshot"}),t(n,{label:"手动(立即)",value:"immediate"}),t(n,{label:"手动(恢复)",value:"resumed"})]),_:1},8,["modelValue"]),t(C,{modelValue:x.value,"onUpdate:modelValue":e[3]||(e[3]=a=>x.value=a),placeholder:"用户",style:{width:"140px"},loading:v.value,filterable:"",clearable:""},{default:l(()=>[t(n,{label:"全部",value:""}),(b(!0),E(te,null,ae(T.value,a=>(b(),B(n,{key:a.id,label:a.username,value:String(a.id)},null,8,["label","value"]))),128))]),_:1},8,["modelValue","loading"]),t(R,{modelValue:k.value,"onUpdate:modelValue":e[4]||(e[4]=a=>k.value=a),placeholder:"账号关键字",style:{width:"170px"},clearable:""},null,8,["modelValue"]),t(U,{type:"primary",onClick:O},{default:l(()=>[...e[6]||(e[6]=[m("筛选",-1)])]),_:1}),t(U,{onClick:W},{default:l(()=>[...e[7]||(e[7]=[m("重置",-1)])]),_:1}),t(U,{type:"danger",plain:"",onClick:j},{default:l(()=>[...e[8]||(e[8]=[m("清理旧日志",-1)])]),_:1})])]),_:1}),t(L,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[f("div",ce,[le((b(),B(A,{data:S.value,style:{width:"100%"}},{default:l(()=>[t(c,{prop:"created_at",label:"时间",width:"180"}),t(c,{label:"来源",width:"110"},{default:l(({row:a})=>[_(a.source).tooltip?(b(),B(N,{key:0,content:_(a.source).tooltip,placement:"top","show-after":300},{default:l(()=>[t(D,{type:_(a.source).type,effect:"light"},{default:l(()=>[m(d(_(a.source).label),1)]),_:2},1032,["type"])]),_:2},1032,["content"])):(b(),B(D,{key:1,type:_(a.source).type,effect:"light"},{default:l(()=>[m(d(_(a.source).label),1)]),_:2},1032,["type"]))]),_:1}),t(c,{prop:"user_username",label:"用户",width:"140"}),t(c,{prop:"username",label:"账号",width:"160"}),t(c,{prop:"browse_type",label:"浏览类型",width:"120"}),t(c,{label:"状态",width:"90"},{default:l(({row:a})=>[t(D,{type:I(a.status).type,effect:"light"},{default:l(()=>[m(d(I(a.status).label),1)]),_:2},1032,["type"])]),_:1}),t(c,{label:"内容/附件",width:"110"},{default:l(({row:a})=>[m(d(a.total_items)+" / "+d(a.total_attachments),1)]),_:1}),t(c,{label:"用时",width:"90"},{default:l(({row:a})=>[m(d(Y(a.duration)),1)]),_:1}),t(c,{label:"失败原因","min-width":"220"},{default:l(({row:a})=>[t(N,{content:a.error_message||"",placement:"top","show-after":300},{default:l(()=>[f("span",de,d(a.error_message||"-"),1)]),_:2},1032,["content"])]),_:1})]),_:1},8,["data"])),[[q,s.value]])]),f("div",pe,[t(X,{"current-page":i.value,"onUpdate:currentPage":e[5]||(e[5]=a=>i.value=a),"page-size":F,total:y.value,layout:"prev, pager, next, jumper, ->, total",onCurrentChange:M},null,8,["current-page","total"]),f("div",me,"第 "+d(i.value)+" / "+d($.value)+" 页",1)])]),_:1})])}}},ye=K(fe,[["__scopeId","data-v-8803eb08"]]);export{ye as default};
+import{f as G}from"./users-oERXj-b9.js";import{g as H,h as J}from"./tasks-Bprl-b9c.js";import{_ as K}from"./index-z8QOtKVT.js";import{e as u,f as Q,g as Z,r,W as ee,l as E,o as b,k as f,j as t,w as l,F as te,m as ae,c as B,C as m,X as le,B as d,E as P,b as oe}from"./vendor-BczUEOE_.js";function ne(p){return String(p||"").trim()}function se(p){return!p.startsWith("user_scheduled")||!p.includes(":")?"":p.split(":",2)[1]||""}function ue(p){const s=ne(p);if(!s||s==="manual")return{group:"manual",label:"手动",type:"success",tooltip:""};if(s==="scheduled")return{group:"scheduled",label:"定时任务",type:"primary",tooltip:"系统定时"};if(s.startsWith("user_scheduled")){const i=se(s),v=String(i||"").replace(/^batch_/,"");return{group:"scheduled",label:"定时任务",type:"primary",tooltip:v?`用户定时批次:${v}`:"用户定时"}}return{group:"manual",label:"手动",type:"success",tooltip:{batch:"手动批量",manual_screenshot:"手动截图",immediate:"立即执行",resumed:"断点恢复"}[s]||s}}const re={class:"page-stack"},ie={class:"filters"},ce={class:"table-wrap"},de={class:"ellipsis"},pe={class:"pagination"},me={class:"page-hint app-muted"},F=20,fe={__name:"LogsPage",setup(p){const s=u(!1),S=u([]),y=u(0),i=u(1),v=u(!1),T=u([]),h=u(""),w=u(""),V=u(""),x=u(""),k=u(""),$=Q(()=>Math.max(1,Math.ceil((y.value||0)/F)));function Y(o){if(o==null)return"-";const e=Number(o);return Number.isFinite(e)?e<60?`${e}秒`:`${Math.floor(e/60)}分${e%60}秒`:"-"}function _(o){const e=ue(o);return{key:e.group,label:e.label,type:e.type,tooltip:e.tooltip}}function I(o){return o==="success"?{label:"成功",type:"success"}:o==="failed"?{label:"失败",type:"danger"}:{label:o||"-",type:"info"}}async function z(){v.value=!0;try{const o=await G();T.value=(o||[]).map(e=>({id:e.id,username:e.username}))}catch{T.value=[]}finally{v.value=!1}}async function M(){s.value=!0;try{const o=(i.value-1)*F,e={limit:F,offset:o};h.value&&(e.date=h.value),w.value&&(e.status=w.value),V.value&&(e.source=V.value),x.value&&(e.user_id=x.value),k.value&&(e.account=k.value);const g=await H(e);S.value=g?.logs||[],y.value=g?.total||0}catch{S.value=[],y.value=0}finally{s.value=!1}}function O(){i.value=1,M()}function W(){h.value="",w.value="",V.value="",x.value="",k.value="",i.value=1,M()}async function j(){let o;try{const e=await P.prompt("请输入要清理多少天前的日志(默认30天)","清理旧日志",{inputValue:"30",confirmButtonText:"下一步",cancelButtonText:"取消",inputValidator:g=>{const n=parseInt(String(g),10);return Number.isFinite(n)&&n>=1},inputErrorMessage:"请输入有效的天数(大于0的整数)"});o=parseInt(String(e.value),10)}catch{return}try{await P.confirm(`确定要删除 ${o} 天前的所有日志吗?此操作不可恢复!`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await J(o);oe.success(e?.message||"清理成功"),i.value=1,await M()}catch{}}return Z(async()=>{await z(),await M()}),(o,e)=>{const g=r("el-date-picker"),n=r("el-option"),C=r("el-select"),R=r("el-input"),U=r("el-button"),L=r("el-card"),c=r("el-table-column"),D=r("el-tag"),N=r("el-tooltip"),A=r("el-table"),X=r("el-pagination"),q=ee("loading");return b(),E("div",re,[e[9]||(e[9]=f("div",{class:"app-page-title"},[f("h2",null,"任务日志")],-1)),t(L,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[f("div",ie,[t(g,{modelValue:h.value,"onUpdate:modelValue":e[0]||(e[0]=a=>h.value=a),type:"date","value-format":"YYYY-MM-DD",placeholder:"日期",style:{width:"150px"}},null,8,["modelValue"]),t(C,{modelValue:w.value,"onUpdate:modelValue":e[1]||(e[1]=a=>w.value=a),placeholder:"状态",style:{width:"120px"}},{default:l(()=>[t(n,{label:"全部",value:""}),t(n,{label:"成功",value:"success"}),t(n,{label:"失败",value:"failed"})]),_:1},8,["modelValue"]),t(C,{modelValue:V.value,"onUpdate:modelValue":e[2]||(e[2]=a=>V.value=a),placeholder:"来源",style:{width:"120px"}},{default:l(()=>[t(n,{label:"全部",value:""}),t(n,{label:"手动",value:"manual"}),t(n,{label:"定时任务(系统)",value:"scheduled"}),t(n,{label:"定时任务(用户)",value:"user_scheduled"}),t(n,{label:"手动(批量)",value:"batch"}),t(n,{label:"手动(截图)",value:"manual_screenshot"}),t(n,{label:"手动(立即)",value:"immediate"}),t(n,{label:"手动(恢复)",value:"resumed"})]),_:1},8,["modelValue"]),t(C,{modelValue:x.value,"onUpdate:modelValue":e[3]||(e[3]=a=>x.value=a),placeholder:"用户",style:{width:"140px"},loading:v.value,filterable:"",clearable:""},{default:l(()=>[t(n,{label:"全部",value:""}),(b(!0),E(te,null,ae(T.value,a=>(b(),B(n,{key:a.id,label:a.username,value:String(a.id)},null,8,["label","value"]))),128))]),_:1},8,["modelValue","loading"]),t(R,{modelValue:k.value,"onUpdate:modelValue":e[4]||(e[4]=a=>k.value=a),placeholder:"账号关键字",style:{width:"170px"},clearable:""},null,8,["modelValue"]),t(U,{type:"primary",onClick:O},{default:l(()=>[...e[6]||(e[6]=[m("筛选",-1)])]),_:1}),t(U,{onClick:W},{default:l(()=>[...e[7]||(e[7]=[m("重置",-1)])]),_:1}),t(U,{type:"danger",plain:"",onClick:j},{default:l(()=>[...e[8]||(e[8]=[m("清理旧日志",-1)])]),_:1})])]),_:1}),t(L,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:l(()=>[f("div",ce,[le((b(),B(A,{data:S.value,style:{width:"100%"}},{default:l(()=>[t(c,{prop:"created_at",label:"时间",width:"180"}),t(c,{label:"来源",width:"110"},{default:l(({row:a})=>[_(a.source).tooltip?(b(),B(N,{key:0,content:_(a.source).tooltip,placement:"top","show-after":300},{default:l(()=>[t(D,{type:_(a.source).type,effect:"light"},{default:l(()=>[m(d(_(a.source).label),1)]),_:2},1032,["type"])]),_:2},1032,["content"])):(b(),B(D,{key:1,type:_(a.source).type,effect:"light"},{default:l(()=>[m(d(_(a.source).label),1)]),_:2},1032,["type"]))]),_:1}),t(c,{prop:"user_username",label:"用户",width:"140"}),t(c,{prop:"username",label:"账号",width:"160"}),t(c,{prop:"browse_type",label:"浏览类型",width:"120"}),t(c,{label:"状态",width:"90"},{default:l(({row:a})=>[t(D,{type:I(a.status).type,effect:"light"},{default:l(()=>[m(d(I(a.status).label),1)]),_:2},1032,["type"])]),_:1}),t(c,{label:"内容/附件",width:"110"},{default:l(({row:a})=>[m(d(a.total_items)+" / "+d(a.total_attachments),1)]),_:1}),t(c,{label:"用时",width:"90"},{default:l(({row:a})=>[m(d(Y(a.duration)),1)]),_:1}),t(c,{label:"失败原因","min-width":"220"},{default:l(({row:a})=>[t(N,{content:a.error_message||"",placement:"top","show-after":300},{default:l(()=>[f("span",de,d(a.error_message||"-"),1)]),_:2},1032,["content"])]),_:1})]),_:1},8,["data"])),[[q,s.value]])]),f("div",pe,[t(X,{"current-page":i.value,"onUpdate:currentPage":e[5]||(e[5]=a=>i.value=a),"page-size":F,total:y.value,layout:"prev, pager, next, jumper, ->, total",onCurrentChange:M},null,8,["current-page","total"]),f("div",me,"第 "+d(i.value)+" / "+d($.value)+" 页",1)])]),_:1})])}}},ye=K(fe,[["__scopeId","data-v-8803eb08"]]);export{ye as default};
diff --git a/static/admin/assets/MetricGrid-Cm-6uQPC.js b/static/admin/assets/MetricGrid-tQwTnWXY.js
similarity index 94%
rename from static/admin/assets/MetricGrid-Cm-6uQPC.js
rename to static/admin/assets/MetricGrid-tQwTnWXY.js
index d16c01e..a4ad5c4 100644
--- a/static/admin/assets/MetricGrid-Cm-6uQPC.js
+++ b/static/admin/assets/MetricGrid-tQwTnWXY.js
@@ -1 +1 @@
-import{_}from"./index-C51y9xCM.js";import{r as n,l as s,o as t,F as r,m as u,V as p,k as o,i as l,j as y,w as h,c as i,A as k,B as c,C as v,a0 as f}from"./vendor-BczUEOE_.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},C={class:"metric-value"},g={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=n("el-icon"),m=n("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,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(k(e.icon)))]),_:2},1024)])):l("",!0),o("div",B,c(e?.label||"-"),1)]),o("div",C,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(c(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,c(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},A=_(N,[["__scopeId","data-v-00e217d4"]]);export{A as M};
+import{_}from"./index-z8QOtKVT.js";import{r as n,l as s,o as t,F as r,m as u,V as p,k as o,i as l,j as y,w as h,c as i,A as k,B as c,C as v,a0 as f}from"./vendor-BczUEOE_.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},C={class:"metric-value"},g={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=n("el-icon"),m=n("el-skeleton");return t(),s("div",{class:"metric-grid",style:f({"--metric-min":`${a.minWidth}px`})},[(t(!0),s(r,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(k(e.icon)))]),_:2},1024)])):l("",!0),o("div",B,c(e?.label||"-"),1)]),o("div",C,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(c(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,c(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},A=_(N,[["__scopeId","data-v-00e217d4"]]);export{A as M};
diff --git a/static/admin/assets/ReportPage-CJumwOUP.js b/static/admin/assets/ReportPage-CVnILNWh.js
similarity index 98%
rename from static/admin/assets/ReportPage-CJumwOUP.js
rename to static/admin/assets/ReportPage-CVnILNWh.js
index d95eb34..92050cd 100644
--- a/static/admin/assets/ReportPage-CJumwOUP.js
+++ b/static/admin/assets/ReportPage-CVnILNWh.js
@@ -1 +1 @@
-import{L as re,e as m,f as a,M as Xe,N as Ye,O as el,P as ll,Q as tl,R as al,T as sl,g as ul,U as ol,r as R,l as q,o as k,k as s,j as u,i as I,B as _,F as ie,m as ce,w,C as ve,c as nl,V as rl}from"./vendor-BczUEOE_.js";import{a as il,_ as cl,f as vl}from"./index-C51y9xCM.js";import{f as dl}from"./email-UCdCFkiw.js";import{f as _l,a as ml,b as pl,c as bl,d as fl,e as hl}from"./tasks-DW-oE78v.js";import{f as wl}from"./system-BqUnjCUC.js";import{M as gl}from"./MetricGrid-Cm-6uQPC.js";async function kl(){const{data:H}=await il.get("/browser_pool/stats");return H}const yl={class:"page-stack"},ql={class:"report-hero"},Sl={class:"hero-head"},xl={class:"hero-main"},$l={class:"hero-meta app-muted"},Ml={key:0},Ll={key:1,class:"hero-dot"},Rl={key:2},Tl={class:"mobile-report"},Al={class:"mobile-module-head"},Pl={class:"mobile-module-title"},Cl={class:"mobile-module-desc app-muted"},Nl={class:"mobile-metrics"},Il={class:"mobile-metric-label app-muted"},Dl={class:"mobile-metric-value"},Wl={key:0,class:"module-extra-actions"},Ql={class:"request-dialog-summary app-muted"},Vl={class:"request-dialog-block"},zl={class:"table-wrap"},El={class:"request-dialog-block"},Bl={class:"table-wrap"},Ul={class:"request-dialog-summary app-muted"},Fl={class:"request-dialog-block"},Ol={class:"table-wrap"},jl={class:"request-dialog-block"},Gl={class:"table-wrap"},Hl=5e3,Zl=2e4,Jl={__name:"ReportPage",setup(H){const de=re("refreshStats",null),_e=re("adminStats",null),D=m(!1),W=m(!1),M=m(""),Q=m(null),b=m(null),p=m(null),y=m(null),g=m(null),P=m(null),L=m(null),f=m(null),c=m(null),i=m(null),V=m(!1),z=m(!1);m("running");function me(){try{M.value=new Date().toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{M.value=""}}function l(t){const e=Number(t);return Number.isFinite(e)?e:0}function pe(t){const e=String(t??"").trim();if(!e)return 0;const o=e.endsWith("%")?e.slice(0,-1):e,v=Number(o);return!Number.isFinite(v)||v<0?0:v>1e3?1e3:v}function E(t){return`${Math.round(pe(t))}%`}function d(t){const e=Number(t);return!Number.isFinite(e)||e<0?"-":e>=100?`${Math.round(e)}ms`:`${e.toFixed(1)}ms`}function Z(t){const e=Number(t);if(!Number.isFinite(e)||e<=0)return"-";try{return new Date(e*1e3).toLocaleTimeString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{return"-"}}function B(t){const e=Number(t);if(!Number.isFinite(e)||e<=0)return"-";try{return new Date(e*1e3).toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{return"-"}}function be(t){const e=String(t??"").trim();return!e||e==="manual"?"手动":e==="scheduled"?"系统定时":e==="batch"?"批量执行":e==="resumed"?"断点续跑":e.startsWith("user_scheduled:")?"用户定时":e}const J=a(()=>{const t=_e?.value||{},e=l(b.value?.max_concurrent);return[{label:"总用户数",value:l(t.total_users),icon:Xe,tone:"blue"},{label:"今日注册",value:l(t.new_users_today),icon:Ye,tone:"green"},{label:"近7天注册",value:l(t.new_users_7d),icon:el,tone:"purple"},{label:"总账号数",value:l(t.total_accounts),icon:ll,tone:"cyan"},{label:"VIP用户",value:l(t.vip_users),icon:tl,tone:"orange"},{label:"运行中任务",value:l(b.value?.running_count),icon:al,tone:"green",sub:e?`并发上限 ${e}`:""},{label:"排队任务",value:l(b.value?.queuing_count),icon:sl,tone:"purple"}]}),h=a(()=>Q.value?.today||{}),S=a(()=>Q.value?.total||{});a(()=>b.value?.running||[]);const C=a(()=>b.value?.queuing||[]),fe=a(()=>l(b.value?.running_count)),he=a(()=>l(b.value?.queuing_count)),we=a(()=>{const t=L.value?.workers;return Array.isArray(t)?[...t].sort((e,o)=>l(e?.worker_id)-l(o?.worker_id)):[]}),K=a(()=>l(L.value?.total_workers)),U=a(()=>we.value.filter(t=>!!t?.has_browser).length),X=a(()=>l(L.value?.idle_workers)),Y=a(()=>l(L.value?.queue_size)),F=a(()=>l(L.value?.active_workers)),ee=a(()=>{const t=l(h.value.success_tasks),e=l(h.value.failed_tasks),o=t+e;return o>0?Math.round(t/o*1e3)/10:0}),O=a(()=>l(p.value?.success_rate));a(()=>[{label:"总任务",value:l(h.value.total_tasks),tone:"blue"},{label:"成功",value:l(h.value.success_tasks),tone:"green"},{label:"失败",value:l(h.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(h.value.total_items),tone:"purple"},{label:"查看附件",value:l(h.value.total_attachments),tone:"cyan"}]),a(()=>[{label:"总任务",value:l(S.value.total_tasks),tone:"blue"},{label:"成功",value:l(S.value.success_tasks),tone:"green"},{label:"失败",value:l(S.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(S.value.total_items),tone:"purple"},{label:"查看附件",value:l(S.value.total_attachments),tone:"cyan"}]),a(()=>[{label:"总发送",value:l(p.value?.total_sent),tone:"blue"},{label:"成功",value:l(p.value?.total_success),tone:"green"},{label:"失败",value:l(p.value?.total_failed),tone:"red"},{label:"成功率",value:`${O.value}%`,tone:"purple"}]),a(()=>[{label:"注册验证",value:l(p.value?.register_sent),tone:"cyan"},{label:"密码重置",value:l(p.value?.reset_sent),tone:"orange"},{label:"邮箱绑定",value:l(p.value?.bind_sent),tone:"purple"},{label:"任务完成",value:l(p.value?.task_complete_sent),tone:"green"}]),a(()=>[{label:"总反馈",value:l(y.value?.total),tone:"blue"},{label:"待处理",value:l(y.value?.pending),tone:"orange"},{label:"已回复",value:l(y.value?.replied),tone:"green"}]),a(()=>[{label:"总 Worker",value:K.value,tone:"blue"},{label:"活跃 Worker",value:U.value,tone:"green"},{label:"空闲 Worker",value:X.value,tone:"cyan"},{label:"忙碌 Worker",value:F.value,tone:"orange"},{label:"队列",value:Y.value,tone:"purple"}]);const ge=a(()=>(f.value?.schedule_enabled??0)===1),ke=a(()=>f.value?.schedule_time||"-"),ye=a(()=>f.value?.schedule_browse_type||"-"),qe=a(()=>String(f.value?.schedule_weekdays||"").trim());a(()=>{const t=qe.value;if(!t)return"";const e={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},o=t.split(",").map(v=>v.trim()).filter(Boolean);return o.length?o.map(v=>e[Number(v)]||v).join("、"):t});const Se=a(()=>(f.value?.proxy_enabled??0)===1);a(()=>f.value?.proxy_api_url||"");const le=a(()=>l(f.value?.proxy_expire_minutes)),j=a(()=>l(f.value?.max_concurrent_global)),xe=a(()=>l(f.value?.max_concurrent_per_account)),$e=a(()=>l(f.value?.max_screenshot_concurrent)),Me=a(()=>{const t=l(b.value?.running_count),e=l(b.value?.queuing_count),o=l(b.value?.max_concurrent);return`运行中 ${t} / 排队 ${e} / 并发上限 ${o||j.value||"-"}`}),Le=a(()=>J.value.map(t=>({label:t.label,value:t.sub?`${t.value}(${t.sub})`:t.value}))),Re=a(()=>[{label:"今日总任务",value:l(h.value.total_tasks)},{label:"今日成功",value:l(h.value.success_tasks)},{label:"今日失败",value:l(h.value.failed_tasks)},{label:"今日成功率",value:`${ee.value}%`},{label:"累计任务",value:l(S.value.total_tasks)},{label:"累计成功",value:l(S.value.success_tasks)}]),Te=a(()=>[{label:"运行中",value:fe.value},{label:"排队中",value:he.value},{label:"并发上限",value:l(b.value?.max_concurrent)||j.value||"-"},{label:"排队首条来源",value:be(C.value[0]?.source)},{label:"排队首条状态",value:C.value[0]?.detail_status||C.value[0]?.status||"-"},{label:"最长等待",value:C.value[0]?.elapsed_display||"-"}]),Ae=a(()=>[{label:"总发送",value:l(p.value?.total_sent)},{label:"成功",value:l(p.value?.total_success)},{label:"失败",value:l(p.value?.total_failed)},{label:"成功率",value:`${O.value}%`},{label:"注册验证",value:l(p.value?.register_sent)},{label:"重置密码",value:l(p.value?.reset_sent)}]),Pe=a(()=>[{label:"总反馈",value:l(y.value?.total)},{label:"待处理",value:l(y.value?.pending)},{label:"已回复",value:l(y.value?.replied)}]),Ce=a(()=>[{label:"CPU",value:E(g.value?.cpu_percent)},{label:"内存",value:E(g.value?.memory_percent)},{label:"磁盘",value:E(g.value?.disk_percent)},{label:"容器状态",value:P.value?.status||"-"},{label:"容器名",value:P.value?.container_name||"-"},{label:"容器运行",value:P.value?.uptime||"-"}]),Ne=a(()=>[{label:"总 Worker",value:K.value},{label:"活跃 Worker",value:U.value},{label:"忙碌 Worker",value:F.value},{label:"空闲 Worker",value:X.value},{label:"任务队列",value:Y.value}]),Ie=a(()=>{const t=c.value?.top_paths;return Array.isArray(t)?t.slice(0,3):[]}),De=a(()=>{const t=[{label:"总请求",value:l(c.value?.total_requests)},{label:"API请求",value:l(c.value?.api_requests)},{label:"慢请求",value:l(c.value?.slow_requests)},{label:"错误请求",value:l(c.value?.error_requests)}];return Ie.value.forEach((e,o)=>{const v=String(e?.path||"-");t.push({label:`慢接口${o+1}`,value:`${v} · 峰值 ${d(e?.max_ms)}`})}),t}),We=a(()=>{const t=d(c.value?.avg_duration_ms),e=d(c.value?.max_duration_ms),o=Z(c.value?.last_request_ts),v=d(c.value?.slow_threshold_ms);return`均值 ${t} · 峰值 ${e} · 慢阈 ${v} · 最近 ${o}`}),Qe=a(()=>(Array.isArray(i.value?.top_sql)?i.value.top_sql:[]).slice(0,3)),te=a(()=>{const t=l(i.value?.window_seconds);return t<=0?24:Math.max(1,Math.round(t/3600))}),Ve=a(()=>{const t=[{label:`慢SQL(${te.value}h)`,value:l(i.value?.total_slow_queries)},{label:"去重SQL",value:l(i.value?.unique_sql)},{label:"平均耗时",value:d(i.value?.avg_duration_ms)},{label:"峰值耗时",value:d(i.value?.max_duration_ms)}];return Qe.value.forEach((e,o)=>{t.push({label:`慢SQL${o+1}`,value:`${d(e?.max_ms)} · ${String(e?.sql||"-")}`})}),t}),ze=a(()=>{const t=d(i.value?.slow_threshold_ms),e=Z(i.value?.last_slow_ts);return`窗口 ${te.value}h · 慢阈 ${t} · 最近 ${e}`}),Ee=a(()=>(Array.isArray(i.value?.top_sql)?i.value.top_sql:[]).map((e,o)=>({rank:o+1,sql:String(e?.sql||"-"),count:l(e?.count),avg_ms:d(e?.avg_ms),max_ms:d(e?.max_ms),last_seen:B(e?.last_ts),sample_params:String(e?.sample_params||"-")}))),Be=a(()=>[...Array.isArray(i.value?.recent_slow_sql)?i.value.recent_slow_sql:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:B(e?.time),sql:String(e?.sql||"-"),duration_ms:d(e?.duration_ms),params:String(e?.params||"-")}))),Ue=a(()=>(Array.isArray(c.value?.top_paths)?c.value.top_paths:[]).map((e,o)=>({rank:o+1,path:String(e?.path||"-"),count:l(e?.count),avg_ms:d(e?.avg_ms),max_ms:d(e?.max_ms),status_5xx:l(e?.status_5xx)}))),Fe=a(()=>[...Array.isArray(c.value?.recent_slow)?c.value.recent_slow:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:B(e?.time),method:String(e?.method||"-").toUpperCase(),path:String(e?.path||"-"),status:l(e?.status),duration_ms:d(e?.duration_ms)})));function Oe(t){const e=l(t);return e>=500?"danger":e>=400?"warning":e>=300?"info":"success"}function je(){V.value=!0}function Ge(){z.value=!0}const He=a(()=>[{label:"定时任务",value:ge.value?"启用":"关闭"},{label:"执行时间",value:ke.value||"-"},{label:"浏览类型",value:ye.value||"-"},{label:"代理",value:Se.value?"启用":"关闭"},{label:"代理有效期",value:le.value?`${le.value} 分钟`:"-"},{label:"全局并发",value:j.value||"-"},{label:"单账号并发",value:xe.value||"-"},{label:"截图并发",value:$e.value||"-"}]),Ze=a(()=>[{key:"overview",title:"平台概览",desc:M.value?`更新 ${M.value}`:"核心指标",tone:"blue",items:Le.value},{key:"task",title:"任务概览",desc:l(h.value.total_tasks)>0?`今日成功率 ${ee.value}%`:"今日暂无任务",tone:"purple",items:Re.value},{key:"queue",title:"队列监控",desc:Me.value,tone:"blue",items:Te.value},{key:"email",title:"邮件报表",desc:`成功率 ${O.value}%`,tone:"cyan",items:Ae.value},{key:"feedback",title:"反馈概览",desc:`待处理 ${l(y.value?.pending)} 条`,tone:"orange",items:Pe.value},{key:"resource",title:"系统资源",desc:g.value?.uptime?`运行 ${g.value.uptime}`:"运行状态获取中",tone:"green",items:Ce.value},{key:"request",title:"接口性能",desc:We.value,tone:"purple",items:De.value},{key:"slow_sql",title:"慢SQL监控",desc:ze.value,tone:"red",items:Ve.value},{key:"worker",title:"截图线程池",desc:`活跃 ${U.value} · 忙碌 ${F.value}`,tone:"cyan",items:Ne.value},{key:"config",title:"配置概览",desc:"并发 / 代理 / 定时任务",tone:"red",items:He.value}]);async function ae(t={}){const e=t.showLoading??!0;if(!W.value){W.value=!0,e&&(D.value=!0);try{const[o,v,n,x,N,A,r,$,oe,ne]=await Promise.allSettled([_l(),ml(),dl(),vl(),pl(),bl(),kl(),fl(),hl(),wl()]);o.status==="fulfilled"&&(Q.value=o.value),v.status==="fulfilled"&&(b.value=v.value),n.status==="fulfilled"&&(p.value=n.value),x.status==="fulfilled"&&(y.value=x.value),N.status==="fulfilled"&&(g.value=N.value),A.status==="fulfilled"&&(P.value=A.value),r.status==="fulfilled"&&(L.value=r.value),$.status==="fulfilled"&&(c.value=$.value),oe.status==="fulfilled"&&(i.value=oe.value),ne.status==="fulfilled"&&(f.value=ne.value),await de?.(),me()}finally{W.value=!1,e&&(D.value=!1)}}}let T=null;function Je(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function Ke(){return Je()?Zl:Hl}function se(){T&&(clearTimeout(T),T=null)}function G(){se(),T=window.setTimeout(async()=>{T=null,await ae({showLoading:!1}).catch(()=>{}),G()},Ke())}function ue(){G()}return ul(()=>{ae({showLoading:!1}).catch(()=>{}).finally(()=>{G()}),window.addEventListener("visibilitychange",ue)}),ol(()=>{se(),window.removeEventListener("visibilitychange",ue)}),(t,e)=>{const o=R("el-button"),v=R("el-card"),n=R("el-table-column"),x=R("el-table"),N=R("el-tag"),A=R("el-dialog");return k(),q("div",yl,[s("section",ql,[s("div",Sl,[s("div",xl,[e[2]||(e[2]=s("h2",null,"报表中心",-1)),s("div",$l,[M.value?(k(),q("span",Ml,"更新时间:"+_(M.value),1)):I("",!0),g.value?.uptime?(k(),q("span",Ll,"·")):I("",!0),g.value?.uptime?(k(),q("span",Rl,"运行 "+_(g.value.uptime),1)):I("",!0)])])]),u(gl,{class:"hero-overview-grid",items:J.value,loading:D.value,"min-width":165},null,8,["items","loading"])]),s("section",Tl,[(k(!0),q(ie,null,ce(Ze.value,r=>(k(),nl(v,{key:r.key,shadow:"never",class:rl(["mobile-module-card",`mobile-tone-${r.tone}`]),"body-style":{padding:"12px"}},{default:w(()=>[s("div",Al,[s("div",Pl,_(r.title),1),s("div",Cl,_(r.desc),1)]),s("div",Nl,[(k(!0),q(ie,null,ce(r.items,$=>(k(),q("div",{key:`${r.key}-${$.label}`,class:"mobile-metric-item"},[s("div",Il,_($.label),1),s("div",Dl,_($.value),1)]))),128))]),r.key==="request"||r.key==="slow_sql"?(k(),q("div",Wl,[u(o,{size:"small",type:"primary",plain:"",onClick:$=>r.key==="request"?je():Ge()},{default:w(()=>[ve(_(r.key==="request"?"查看慢接口详情":"查看慢SQL详情"),1)]),_:2},1032,["onClick"])])):I("",!0)]),_:2},1032,["class"]))),128))]),u(A,{modelValue:V.value,"onUpdate:modelValue":e[0]||(e[0]=r=>V.value=r),title:"慢接口详情",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ql,[s("span",null,"总请求:"+_(l(c.value?.total_requests)),1),s("span",null,"API请求:"+_(l(c.value?.api_requests)),1),s("span",null,"慢请求:"+_(l(c.value?.slow_requests)),1),s("span",null,"错误请求:"+_(l(c.value?.error_requests)),1)]),s("div",Vl,[e[3]||(e[3]=s("div",{class:"request-dialog-title"},"慢接口排行榜",-1)),s("div",zl,[u(x,{data:Ue.value,size:"small","max-height":"280"},{default:w(()=>[u(n,{prop:"rank",label:"#",width:"60"}),u(n,{prop:"path",label:"接口路径","min-width":"340","show-overflow-tooltip":""}),u(n,{prop:"count",label:"请求数",width:"100"}),u(n,{prop:"avg_ms",label:"平均耗时",width:"120"}),u(n,{prop:"max_ms",label:"峰值耗时",width:"120"}),u(n,{prop:"status_5xx",label:"5xx",width:"90"})]),_:1},8,["data"])])]),s("div",El,[e[4]||(e[4]=s("div",{class:"request-dialog-title"},"最近慢请求",-1)),s("div",Bl,[u(x,{data:Fe.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"time_text",label:"时间",width:"180"}),u(n,{prop:"method",label:"方法",width:"90"}),u(n,{prop:"path",label:"接口路径","min-width":"320","show-overflow-tooltip":""}),u(n,{label:"状态",width:"100"},{default:w(r=>[u(N,{size:"small",type:Oe(r.row.status)},{default:w(()=>[ve(_(r.row.status||"-"),1)]),_:2},1032,["type"])]),_:1}),u(n,{prop:"duration_ms",label:"耗时",width:"110"})]),_:1},8,["data"])])])]),_:1},8,["modelValue"]),u(A,{modelValue:z.value,"onUpdate:modelValue":e[1]||(e[1]=r=>z.value=r),title:"慢SQL详情(近24小时)",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ul,[s("span",null,"慢SQL总数:"+_(l(i.value?.total_slow_queries)),1),s("span",null,"去重SQL:"+_(l(i.value?.unique_sql)),1),s("span",null,"平均耗时:"+_(d(i.value?.avg_duration_ms)),1),s("span",null,"峰值耗时:"+_(d(i.value?.max_duration_ms)),1),s("span",null,"慢阈值:"+_(d(i.value?.slow_threshold_ms)),1)]),s("div",Fl,[e[5]||(e[5]=s("div",{class:"request-dialog-title"},"TOP 慢SQL(按出现次数)",-1)),s("div",Ol,[u(x,{data:Ee.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"rank",label:"#",width:"60"}),u(n,{prop:"sql",label:"SQL","min-width":"400","show-overflow-tooltip":""}),u(n,{prop:"count",label:"次数",width:"90"}),u(n,{prop:"avg_ms",label:"平均耗时",width:"120"}),u(n,{prop:"max_ms",label:"峰值耗时",width:"120"}),u(n,{prop:"last_seen",label:"最近出现",width:"180"}),u(n,{prop:"sample_params",label:"参数样本","min-width":"140","show-overflow-tooltip":""})]),_:1},8,["data"])])]),s("div",jl,[e[6]||(e[6]=s("div",{class:"request-dialog-title"},"最近慢SQL",-1)),s("div",Gl,[u(x,{data:Be.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"time_text",label:"时间",width:"180"}),u(n,{prop:"sql",label:"SQL","min-width":"420","show-overflow-tooltip":""}),u(n,{prop:"duration_ms",label:"耗时",width:"110"}),u(n,{prop:"params",label:"参数","min-width":"130","show-overflow-tooltip":""})]),_:1},8,["data"])])])]),_:1},8,["modelValue"])])}}},at=cl(Jl,[["__scopeId","data-v-1b662f5c"]]);export{at as default};
+import{L as re,e as m,f as a,M as Xe,N as Ye,O as el,P as ll,Q as tl,R as al,T as sl,g as ul,U as ol,r as R,l as q,o as k,k as s,j as u,i as I,B as _,F as ie,m as ce,w,C as ve,c as nl,V as rl}from"./vendor-BczUEOE_.js";import{a as il,_ as cl,f as vl}from"./index-z8QOtKVT.js";import{f as dl}from"./email-BgLsevU3.js";import{f as _l,a as ml,b as pl,c as bl,d as fl,e as hl}from"./tasks-Bprl-b9c.js";import{f as wl}from"./system-COpUDNdl.js";import{M as gl}from"./MetricGrid-tQwTnWXY.js";async function kl(){const{data:H}=await il.get("/browser_pool/stats");return H}const yl={class:"page-stack"},ql={class:"report-hero"},Sl={class:"hero-head"},xl={class:"hero-main"},$l={class:"hero-meta app-muted"},Ml={key:0},Ll={key:1,class:"hero-dot"},Rl={key:2},Tl={class:"mobile-report"},Al={class:"mobile-module-head"},Pl={class:"mobile-module-title"},Cl={class:"mobile-module-desc app-muted"},Nl={class:"mobile-metrics"},Il={class:"mobile-metric-label app-muted"},Dl={class:"mobile-metric-value"},Wl={key:0,class:"module-extra-actions"},Ql={class:"request-dialog-summary app-muted"},Vl={class:"request-dialog-block"},zl={class:"table-wrap"},El={class:"request-dialog-block"},Bl={class:"table-wrap"},Ul={class:"request-dialog-summary app-muted"},Fl={class:"request-dialog-block"},Ol={class:"table-wrap"},jl={class:"request-dialog-block"},Gl={class:"table-wrap"},Hl=5e3,Zl=2e4,Jl={__name:"ReportPage",setup(H){const de=re("refreshStats",null),_e=re("adminStats",null),D=m(!1),W=m(!1),M=m(""),Q=m(null),b=m(null),p=m(null),y=m(null),g=m(null),P=m(null),L=m(null),f=m(null),c=m(null),i=m(null),V=m(!1),z=m(!1);m("running");function me(){try{M.value=new Date().toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{M.value=""}}function l(t){const e=Number(t);return Number.isFinite(e)?e:0}function pe(t){const e=String(t??"").trim();if(!e)return 0;const o=e.endsWith("%")?e.slice(0,-1):e,v=Number(o);return!Number.isFinite(v)||v<0?0:v>1e3?1e3:v}function E(t){return`${Math.round(pe(t))}%`}function d(t){const e=Number(t);return!Number.isFinite(e)||e<0?"-":e>=100?`${Math.round(e)}ms`:`${e.toFixed(1)}ms`}function Z(t){const e=Number(t);if(!Number.isFinite(e)||e<=0)return"-";try{return new Date(e*1e3).toLocaleTimeString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{return"-"}}function B(t){const e=Number(t);if(!Number.isFinite(e)||e<=0)return"-";try{return new Date(e*1e3).toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{return"-"}}function be(t){const e=String(t??"").trim();return!e||e==="manual"?"手动":e==="scheduled"?"系统定时":e==="batch"?"批量执行":e==="resumed"?"断点续跑":e.startsWith("user_scheduled:")?"用户定时":e}const J=a(()=>{const t=_e?.value||{},e=l(b.value?.max_concurrent);return[{label:"总用户数",value:l(t.total_users),icon:Xe,tone:"blue"},{label:"今日注册",value:l(t.new_users_today),icon:Ye,tone:"green"},{label:"近7天注册",value:l(t.new_users_7d),icon:el,tone:"purple"},{label:"总账号数",value:l(t.total_accounts),icon:ll,tone:"cyan"},{label:"VIP用户",value:l(t.vip_users),icon:tl,tone:"orange"},{label:"运行中任务",value:l(b.value?.running_count),icon:al,tone:"green",sub:e?`并发上限 ${e}`:""},{label:"排队任务",value:l(b.value?.queuing_count),icon:sl,tone:"purple"}]}),h=a(()=>Q.value?.today||{}),S=a(()=>Q.value?.total||{});a(()=>b.value?.running||[]);const C=a(()=>b.value?.queuing||[]),fe=a(()=>l(b.value?.running_count)),he=a(()=>l(b.value?.queuing_count)),we=a(()=>{const t=L.value?.workers;return Array.isArray(t)?[...t].sort((e,o)=>l(e?.worker_id)-l(o?.worker_id)):[]}),K=a(()=>l(L.value?.total_workers)),U=a(()=>we.value.filter(t=>!!t?.has_browser).length),X=a(()=>l(L.value?.idle_workers)),Y=a(()=>l(L.value?.queue_size)),F=a(()=>l(L.value?.active_workers)),ee=a(()=>{const t=l(h.value.success_tasks),e=l(h.value.failed_tasks),o=t+e;return o>0?Math.round(t/o*1e3)/10:0}),O=a(()=>l(p.value?.success_rate));a(()=>[{label:"总任务",value:l(h.value.total_tasks),tone:"blue"},{label:"成功",value:l(h.value.success_tasks),tone:"green"},{label:"失败",value:l(h.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(h.value.total_items),tone:"purple"},{label:"查看附件",value:l(h.value.total_attachments),tone:"cyan"}]),a(()=>[{label:"总任务",value:l(S.value.total_tasks),tone:"blue"},{label:"成功",value:l(S.value.success_tasks),tone:"green"},{label:"失败",value:l(S.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(S.value.total_items),tone:"purple"},{label:"查看附件",value:l(S.value.total_attachments),tone:"cyan"}]),a(()=>[{label:"总发送",value:l(p.value?.total_sent),tone:"blue"},{label:"成功",value:l(p.value?.total_success),tone:"green"},{label:"失败",value:l(p.value?.total_failed),tone:"red"},{label:"成功率",value:`${O.value}%`,tone:"purple"}]),a(()=>[{label:"注册验证",value:l(p.value?.register_sent),tone:"cyan"},{label:"密码重置",value:l(p.value?.reset_sent),tone:"orange"},{label:"邮箱绑定",value:l(p.value?.bind_sent),tone:"purple"},{label:"任务完成",value:l(p.value?.task_complete_sent),tone:"green"}]),a(()=>[{label:"总反馈",value:l(y.value?.total),tone:"blue"},{label:"待处理",value:l(y.value?.pending),tone:"orange"},{label:"已回复",value:l(y.value?.replied),tone:"green"}]),a(()=>[{label:"总 Worker",value:K.value,tone:"blue"},{label:"活跃 Worker",value:U.value,tone:"green"},{label:"空闲 Worker",value:X.value,tone:"cyan"},{label:"忙碌 Worker",value:F.value,tone:"orange"},{label:"队列",value:Y.value,tone:"purple"}]);const ge=a(()=>(f.value?.schedule_enabled??0)===1),ke=a(()=>f.value?.schedule_time||"-"),ye=a(()=>f.value?.schedule_browse_type||"-"),qe=a(()=>String(f.value?.schedule_weekdays||"").trim());a(()=>{const t=qe.value;if(!t)return"";const e={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},o=t.split(",").map(v=>v.trim()).filter(Boolean);return o.length?o.map(v=>e[Number(v)]||v).join("、"):t});const Se=a(()=>(f.value?.proxy_enabled??0)===1);a(()=>f.value?.proxy_api_url||"");const le=a(()=>l(f.value?.proxy_expire_minutes)),j=a(()=>l(f.value?.max_concurrent_global)),xe=a(()=>l(f.value?.max_concurrent_per_account)),$e=a(()=>l(f.value?.max_screenshot_concurrent)),Me=a(()=>{const t=l(b.value?.running_count),e=l(b.value?.queuing_count),o=l(b.value?.max_concurrent);return`运行中 ${t} / 排队 ${e} / 并发上限 ${o||j.value||"-"}`}),Le=a(()=>J.value.map(t=>({label:t.label,value:t.sub?`${t.value}(${t.sub})`:t.value}))),Re=a(()=>[{label:"今日总任务",value:l(h.value.total_tasks)},{label:"今日成功",value:l(h.value.success_tasks)},{label:"今日失败",value:l(h.value.failed_tasks)},{label:"今日成功率",value:`${ee.value}%`},{label:"累计任务",value:l(S.value.total_tasks)},{label:"累计成功",value:l(S.value.success_tasks)}]),Te=a(()=>[{label:"运行中",value:fe.value},{label:"排队中",value:he.value},{label:"并发上限",value:l(b.value?.max_concurrent)||j.value||"-"},{label:"排队首条来源",value:be(C.value[0]?.source)},{label:"排队首条状态",value:C.value[0]?.detail_status||C.value[0]?.status||"-"},{label:"最长等待",value:C.value[0]?.elapsed_display||"-"}]),Ae=a(()=>[{label:"总发送",value:l(p.value?.total_sent)},{label:"成功",value:l(p.value?.total_success)},{label:"失败",value:l(p.value?.total_failed)},{label:"成功率",value:`${O.value}%`},{label:"注册验证",value:l(p.value?.register_sent)},{label:"重置密码",value:l(p.value?.reset_sent)}]),Pe=a(()=>[{label:"总反馈",value:l(y.value?.total)},{label:"待处理",value:l(y.value?.pending)},{label:"已回复",value:l(y.value?.replied)}]),Ce=a(()=>[{label:"CPU",value:E(g.value?.cpu_percent)},{label:"内存",value:E(g.value?.memory_percent)},{label:"磁盘",value:E(g.value?.disk_percent)},{label:"容器状态",value:P.value?.status||"-"},{label:"容器名",value:P.value?.container_name||"-"},{label:"容器运行",value:P.value?.uptime||"-"}]),Ne=a(()=>[{label:"总 Worker",value:K.value},{label:"活跃 Worker",value:U.value},{label:"忙碌 Worker",value:F.value},{label:"空闲 Worker",value:X.value},{label:"任务队列",value:Y.value}]),Ie=a(()=>{const t=c.value?.top_paths;return Array.isArray(t)?t.slice(0,3):[]}),De=a(()=>{const t=[{label:"总请求",value:l(c.value?.total_requests)},{label:"API请求",value:l(c.value?.api_requests)},{label:"慢请求",value:l(c.value?.slow_requests)},{label:"错误请求",value:l(c.value?.error_requests)}];return Ie.value.forEach((e,o)=>{const v=String(e?.path||"-");t.push({label:`慢接口${o+1}`,value:`${v} · 峰值 ${d(e?.max_ms)}`})}),t}),We=a(()=>{const t=d(c.value?.avg_duration_ms),e=d(c.value?.max_duration_ms),o=Z(c.value?.last_request_ts),v=d(c.value?.slow_threshold_ms);return`均值 ${t} · 峰值 ${e} · 慢阈 ${v} · 最近 ${o}`}),Qe=a(()=>(Array.isArray(i.value?.top_sql)?i.value.top_sql:[]).slice(0,3)),te=a(()=>{const t=l(i.value?.window_seconds);return t<=0?24:Math.max(1,Math.round(t/3600))}),Ve=a(()=>{const t=[{label:`慢SQL(${te.value}h)`,value:l(i.value?.total_slow_queries)},{label:"去重SQL",value:l(i.value?.unique_sql)},{label:"平均耗时",value:d(i.value?.avg_duration_ms)},{label:"峰值耗时",value:d(i.value?.max_duration_ms)}];return Qe.value.forEach((e,o)=>{t.push({label:`慢SQL${o+1}`,value:`${d(e?.max_ms)} · ${String(e?.sql||"-")}`})}),t}),ze=a(()=>{const t=d(i.value?.slow_threshold_ms),e=Z(i.value?.last_slow_ts);return`窗口 ${te.value}h · 慢阈 ${t} · 最近 ${e}`}),Ee=a(()=>(Array.isArray(i.value?.top_sql)?i.value.top_sql:[]).map((e,o)=>({rank:o+1,sql:String(e?.sql||"-"),count:l(e?.count),avg_ms:d(e?.avg_ms),max_ms:d(e?.max_ms),last_seen:B(e?.last_ts),sample_params:String(e?.sample_params||"-")}))),Be=a(()=>[...Array.isArray(i.value?.recent_slow_sql)?i.value.recent_slow_sql:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:B(e?.time),sql:String(e?.sql||"-"),duration_ms:d(e?.duration_ms),params:String(e?.params||"-")}))),Ue=a(()=>(Array.isArray(c.value?.top_paths)?c.value.top_paths:[]).map((e,o)=>({rank:o+1,path:String(e?.path||"-"),count:l(e?.count),avg_ms:d(e?.avg_ms),max_ms:d(e?.max_ms),status_5xx:l(e?.status_5xx)}))),Fe=a(()=>[...Array.isArray(c.value?.recent_slow)?c.value.recent_slow:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:B(e?.time),method:String(e?.method||"-").toUpperCase(),path:String(e?.path||"-"),status:l(e?.status),duration_ms:d(e?.duration_ms)})));function Oe(t){const e=l(t);return e>=500?"danger":e>=400?"warning":e>=300?"info":"success"}function je(){V.value=!0}function Ge(){z.value=!0}const He=a(()=>[{label:"定时任务",value:ge.value?"启用":"关闭"},{label:"执行时间",value:ke.value||"-"},{label:"浏览类型",value:ye.value||"-"},{label:"代理",value:Se.value?"启用":"关闭"},{label:"代理有效期",value:le.value?`${le.value} 分钟`:"-"},{label:"全局并发",value:j.value||"-"},{label:"单账号并发",value:xe.value||"-"},{label:"截图并发",value:$e.value||"-"}]),Ze=a(()=>[{key:"overview",title:"平台概览",desc:M.value?`更新 ${M.value}`:"核心指标",tone:"blue",items:Le.value},{key:"task",title:"任务概览",desc:l(h.value.total_tasks)>0?`今日成功率 ${ee.value}%`:"今日暂无任务",tone:"purple",items:Re.value},{key:"queue",title:"队列监控",desc:Me.value,tone:"blue",items:Te.value},{key:"email",title:"邮件报表",desc:`成功率 ${O.value}%`,tone:"cyan",items:Ae.value},{key:"feedback",title:"反馈概览",desc:`待处理 ${l(y.value?.pending)} 条`,tone:"orange",items:Pe.value},{key:"resource",title:"系统资源",desc:g.value?.uptime?`运行 ${g.value.uptime}`:"运行状态获取中",tone:"green",items:Ce.value},{key:"request",title:"接口性能",desc:We.value,tone:"purple",items:De.value},{key:"slow_sql",title:"慢SQL监控",desc:ze.value,tone:"red",items:Ve.value},{key:"worker",title:"截图线程池",desc:`活跃 ${U.value} · 忙碌 ${F.value}`,tone:"cyan",items:Ne.value},{key:"config",title:"配置概览",desc:"并发 / 代理 / 定时任务",tone:"red",items:He.value}]);async function ae(t={}){const e=t.showLoading??!0;if(!W.value){W.value=!0,e&&(D.value=!0);try{const[o,v,n,x,N,A,r,$,oe,ne]=await Promise.allSettled([_l(),ml(),dl(),vl(),pl(),bl(),kl(),fl(),hl(),wl()]);o.status==="fulfilled"&&(Q.value=o.value),v.status==="fulfilled"&&(b.value=v.value),n.status==="fulfilled"&&(p.value=n.value),x.status==="fulfilled"&&(y.value=x.value),N.status==="fulfilled"&&(g.value=N.value),A.status==="fulfilled"&&(P.value=A.value),r.status==="fulfilled"&&(L.value=r.value),$.status==="fulfilled"&&(c.value=$.value),oe.status==="fulfilled"&&(i.value=oe.value),ne.status==="fulfilled"&&(f.value=ne.value),await de?.(),me()}finally{W.value=!1,e&&(D.value=!1)}}}let T=null;function Je(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function Ke(){return Je()?Zl:Hl}function se(){T&&(clearTimeout(T),T=null)}function G(){se(),T=window.setTimeout(async()=>{T=null,await ae({showLoading:!1}).catch(()=>{}),G()},Ke())}function ue(){G()}return ul(()=>{ae({showLoading:!1}).catch(()=>{}).finally(()=>{G()}),window.addEventListener("visibilitychange",ue)}),ol(()=>{se(),window.removeEventListener("visibilitychange",ue)}),(t,e)=>{const o=R("el-button"),v=R("el-card"),n=R("el-table-column"),x=R("el-table"),N=R("el-tag"),A=R("el-dialog");return k(),q("div",yl,[s("section",ql,[s("div",Sl,[s("div",xl,[e[2]||(e[2]=s("h2",null,"报表中心",-1)),s("div",$l,[M.value?(k(),q("span",Ml,"更新时间:"+_(M.value),1)):I("",!0),g.value?.uptime?(k(),q("span",Ll,"·")):I("",!0),g.value?.uptime?(k(),q("span",Rl,"运行 "+_(g.value.uptime),1)):I("",!0)])])]),u(gl,{class:"hero-overview-grid",items:J.value,loading:D.value,"min-width":165},null,8,["items","loading"])]),s("section",Tl,[(k(!0),q(ie,null,ce(Ze.value,r=>(k(),nl(v,{key:r.key,shadow:"never",class:rl(["mobile-module-card",`mobile-tone-${r.tone}`]),"body-style":{padding:"12px"}},{default:w(()=>[s("div",Al,[s("div",Pl,_(r.title),1),s("div",Cl,_(r.desc),1)]),s("div",Nl,[(k(!0),q(ie,null,ce(r.items,$=>(k(),q("div",{key:`${r.key}-${$.label}`,class:"mobile-metric-item"},[s("div",Il,_($.label),1),s("div",Dl,_($.value),1)]))),128))]),r.key==="request"||r.key==="slow_sql"?(k(),q("div",Wl,[u(o,{size:"small",type:"primary",plain:"",onClick:$=>r.key==="request"?je():Ge()},{default:w(()=>[ve(_(r.key==="request"?"查看慢接口详情":"查看慢SQL详情"),1)]),_:2},1032,["onClick"])])):I("",!0)]),_:2},1032,["class"]))),128))]),u(A,{modelValue:V.value,"onUpdate:modelValue":e[0]||(e[0]=r=>V.value=r),title:"慢接口详情",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ql,[s("span",null,"总请求:"+_(l(c.value?.total_requests)),1),s("span",null,"API请求:"+_(l(c.value?.api_requests)),1),s("span",null,"慢请求:"+_(l(c.value?.slow_requests)),1),s("span",null,"错误请求:"+_(l(c.value?.error_requests)),1)]),s("div",Vl,[e[3]||(e[3]=s("div",{class:"request-dialog-title"},"慢接口排行榜",-1)),s("div",zl,[u(x,{data:Ue.value,size:"small","max-height":"280"},{default:w(()=>[u(n,{prop:"rank",label:"#",width:"60"}),u(n,{prop:"path",label:"接口路径","min-width":"340","show-overflow-tooltip":""}),u(n,{prop:"count",label:"请求数",width:"100"}),u(n,{prop:"avg_ms",label:"平均耗时",width:"120"}),u(n,{prop:"max_ms",label:"峰值耗时",width:"120"}),u(n,{prop:"status_5xx",label:"5xx",width:"90"})]),_:1},8,["data"])])]),s("div",El,[e[4]||(e[4]=s("div",{class:"request-dialog-title"},"最近慢请求",-1)),s("div",Bl,[u(x,{data:Fe.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"time_text",label:"时间",width:"180"}),u(n,{prop:"method",label:"方法",width:"90"}),u(n,{prop:"path",label:"接口路径","min-width":"320","show-overflow-tooltip":""}),u(n,{label:"状态",width:"100"},{default:w(r=>[u(N,{size:"small",type:Oe(r.row.status)},{default:w(()=>[ve(_(r.row.status||"-"),1)]),_:2},1032,["type"])]),_:1}),u(n,{prop:"duration_ms",label:"耗时",width:"110"})]),_:1},8,["data"])])])]),_:1},8,["modelValue"]),u(A,{modelValue:z.value,"onUpdate:modelValue":e[1]||(e[1]=r=>z.value=r),title:"慢SQL详情(近24小时)",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ul,[s("span",null,"慢SQL总数:"+_(l(i.value?.total_slow_queries)),1),s("span",null,"去重SQL:"+_(l(i.value?.unique_sql)),1),s("span",null,"平均耗时:"+_(d(i.value?.avg_duration_ms)),1),s("span",null,"峰值耗时:"+_(d(i.value?.max_duration_ms)),1),s("span",null,"慢阈值:"+_(d(i.value?.slow_threshold_ms)),1)]),s("div",Fl,[e[5]||(e[5]=s("div",{class:"request-dialog-title"},"TOP 慢SQL(按出现次数)",-1)),s("div",Ol,[u(x,{data:Ee.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"rank",label:"#",width:"60"}),u(n,{prop:"sql",label:"SQL","min-width":"400","show-overflow-tooltip":""}),u(n,{prop:"count",label:"次数",width:"90"}),u(n,{prop:"avg_ms",label:"平均耗时",width:"120"}),u(n,{prop:"max_ms",label:"峰值耗时",width:"120"}),u(n,{prop:"last_seen",label:"最近出现",width:"180"}),u(n,{prop:"sample_params",label:"参数样本","min-width":"140","show-overflow-tooltip":""})]),_:1},8,["data"])])]),s("div",jl,[e[6]||(e[6]=s("div",{class:"request-dialog-title"},"最近慢SQL",-1)),s("div",Gl,[u(x,{data:Be.value,size:"small","max-height":"320"},{default:w(()=>[u(n,{prop:"time_text",label:"时间",width:"180"}),u(n,{prop:"sql",label:"SQL","min-width":"420","show-overflow-tooltip":""}),u(n,{prop:"duration_ms",label:"耗时",width:"110"}),u(n,{prop:"params",label:"参数","min-width":"130","show-overflow-tooltip":""})]),_:1},8,["data"])])])]),_:1},8,["modelValue"])])}}},at=cl(Jl,[["__scopeId","data-v-1b662f5c"]]);export{at as default};
diff --git a/static/admin/assets/SecurityPage-rmlzXSVY.js b/static/admin/assets/SecurityPage-tMcfFXjg.js
similarity index 99%
rename from static/admin/assets/SecurityPage-rmlzXSVY.js
rename to static/admin/assets/SecurityPage-tMcfFXjg.js
index b0f0108..38625f0 100644
--- a/static/admin/assets/SecurityPage-rmlzXSVY.js
+++ b/static/admin/assets/SecurityPage-tMcfFXjg.js
@@ -1,4 +1,4 @@
-import{a as g,_ as qe}from"./index-C51y9xCM.js";import{M as ze}from"./MetricGrid-Cm-6uQPC.js";import{e as d,f as oe,g as Ee,r as v,W as Ae,l as P,o as c,k as p,j as a,w as l,C as i,F as Oe,m as Ge,c as b,X as ee,B as f,i as ue,E as te,b as w}from"./vendor-BczUEOE_.js";async function He(){const{data:u}=await g.get("/admin/security/dashboard");return u}async function Ke(u){const{data:m}=await g.get("/admin/security/threats",{params:u});return m}async function We(){const{data:u}=await g.get("/admin/security/banned-ips");return u}async function Xe(){const{data:u}=await g.get("/admin/security/banned-users");return u}async function Je(u){const{data:m}=await g.post("/admin/security/ban-ip",u);return m}async function Qe(u){const{data:m}=await g.post("/admin/security/unban-ip",{ip:u});return m}async function Ye(u){const{data:m}=await g.post("/admin/security/ban-user",u);return m}async function Ze(u){const{data:m}=await g.post("/admin/security/unban-user",{user_id:u});return m}async function et(u){const m=encodeURIComponent(String(u||"").trim()),{data:V}=await g.get(`/admin/security/ip-risk/${m}`);return V}async function tt(u){const{data:m}=await g.post("/admin/security/ip-risk/clear",{ip:u});return m}async function at(u){const m=encodeURIComponent(String(u||"").trim()),{data:V}=await g.get(`/admin/security/user-risk/${m}`);return V}async function lt(){const{data:u}=await g.post("/admin/security/cleanup",{});return u}const nt={class:"page-stack"},it={class:"app-page-title"},st={class:"toolbar"},ot={class:"filters"},ut={class:"table-wrap"},rt={key:1},dt={key:1},pt={class:"mono ellipsis"},ct={class:"ellipsis"},ft={class:"pagination"},mt={class:"page-hint app-muted"},vt={class:"toolbar"},_t={class:"table-wrap"},yt={class:"table-wrap"},bt={class:"filters"},gt={class:"filters"},kt={class:"risk-head"},ht={class:"risk-title"},wt={key:0},It={key:1},Vt={class:"toolbar"},xt={class:"table-wrap"},Ct={class:"mono ellipsis"},St={class:"ellipsis"},Tt={class:"dialog-actions"},re=20,Pt={__name:"SecurityPage",setup(u){const m=d("threats"),V=d(!1),L=d(null),ae=d(!1),q=d([]),z=d(0),C=d(1),U=d(""),B=d(""),$=d(!1),de=d([]),pe=d([]),ce=d("ips"),S=d(!1),R=d(!1),o=d({kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}),E=d("ip"),k=d(!1),A=d(""),O=d(""),_=d(null),h=d(""),Ie=["sql_injection","xss","path_traversal","command_injection","ssrf","scanner","bruteforce","csrf","xxe","file_upload"];function le(n){const e=Number(n);return Number.isFinite(e)?e:0}function D(n){const e=Number(n||0);return e>=80?{label:"高",type:"danger"}:e>=50?{label:"中",type:"warning"}:{label:"低",type:"success"}}function fe(n){const e=String(n||"").trim();return e||"永久"}function me(n){const e=[];return n?.field_name&&e.push(`字段: ${n.field_name}`),n?.rule&&e.push(`规则: ${n.rule}`),n?.matched&&e.push(`匹配: ${n.matched}`),n?.value_preview&&e.push(`值: ${n.value_preview}`),e.length?e.join(" · "):"-"}function G(n){const e=String(n?.request_method||"").trim(),s=String(n?.request_path||"").trim();return`${e} ${s}`.trim()||"-"}const Ve=oe(()=>{const n=new Set(Ie),e=L.value?.recent_threat_events||[];for(const s of e){const y=String(s?.threat_type||"").trim();y&&n.add(y)}for(const s of q.value||[]){const y=String(s?.threat_type||"").trim();y&&n.add(y)}return Array.from(n).sort((s,y)=>s.localeCompare(y)).map(s=>({label:s,value:s}))}),xe=oe(()=>{const n=L.value||{};return[{key:"threat_events_24h",label:"最近24小时威胁事件",value:le(n.threat_events_24h),tone:"red",hint:"用于衡量当前攻击面活跃度"},{key:"banned_ip_count",label:"当前封禁 IP 数",value:le(n.banned_ip_count),tone:"orange",hint:"自动与人工封禁总量"},{key:"banned_user_count",label:"当前封禁用户数",value:le(n.banned_user_count),tone:"purple",hint:"高风险账户拦截情况"}]}),Ce=oe(()=>Math.max(1,Math.ceil((z.value||0)/re)));async function N(){V.value=!0;try{L.value=await He()}catch{L.value=null}finally{V.value=!1}}async function H(){ae.value=!0;try{const n={page:C.value,per_page:re};U.value&&(n.event_type=U.value),B.value&&(n.severity=B.value);const e=await Ke(n);q.value=e?.items||[],z.value=e?.total||0}catch{q.value=[],z.value=0}finally{ae.value=!1}}async function F(){if(!$.value){$.value=!0;try{const[n,e]=await Promise.allSettled([We(),Xe()]);de.value=n.status==="fulfilled"?n.value?.items||[]:[],pe.value=e.status==="fulfilled"?e.value?.items||[]:[]}finally{$.value=!1}}}async function ve(){await Promise.allSettled([N(),H(),F()])}function Se(){C.value=1,H()}function Te(){U.value="",B.value="",C.value=1,H()}function _e(){o.value={kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}}function K(n="ip",e={}){_e(),o.value.kind=n==="user"?"user":"ip",o.value.kind==="ip"?o.value.ip=String(e.ip||"").trim():o.value.user_id=String(e.user_id||"").trim(),e.reason&&(o.value.reason=String(e.reason||"").trim()),S.value=!0}async function Pe(){const n=o.value.kind,e=String(o.value.reason||"").trim(),s=!!o.value.permanent,y=Number(o.value.duration_hours||24);if(!e){w.error("原因不能为空");return}if(n==="ip"){const I=String(o.value.ip||"").trim();if(!I){w.error("IP不能为空");return}R.value=!0;try{await Je({ip:I,reason:e,duration_hours:y,permanent:s}),w.success("IP已封禁"),S.value=!1,await Promise.allSettled([N(),F()])}catch{}finally{R.value=!1}return}const X=String(o.value.user_id||"").trim(),r=Number.parseInt(X,10);if(!Number.isFinite(r)){w.error("用户ID无效");return}R.value=!0;try{await Ye({user_id:r,reason:e,duration_hours:y,permanent:s}),w.success("用户已封禁"),S.value=!1,await Promise.allSettled([N(),F()])}catch{}finally{R.value=!1}}async function ye(n){const e=String(n||"").trim();if(e){try{await te.confirm(`确定解除对 IP ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Qe(e),w.success("已解除IP封禁"),await Promise.allSettled([N(),F()])}catch{}}}async function be(n){const e=Number.parseInt(String(n||"").trim(),10);if(Number.isFinite(e)){try{await te.confirm(`确定解除对 用户ID ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Ze(e),w.success("已解除用户封禁"),await Promise.allSettled([N(),F()])}catch{}}}function ge(n){const e=String(n||"").trim();e&&(m.value="risk",E.value="ip",A.value=e,W())}function ke(n){const e=String(n||"").trim();e&&(m.value="risk",E.value="user",O.value=e,ne())}async function W(){const n=String(A.value||"").trim();if(!n){w.error("请输入IP");return}k.value=!0;try{_.value=await et(n),h.value="ip"}catch{_.value=null,h.value=""}finally{k.value=!1}}async function ne(){const n=String(O.value||"").trim(),e=Number.parseInt(n,10);if(!Number.isFinite(e)){w.error("请输入有效的用户ID");return}k.value=!0;try{_.value=await at(e),h.value="user"}catch{_.value=null,h.value=""}finally{k.value=!1}}function Ue(){!_.value||!h.value||(h.value==="ip"?K("ip",{ip:_.value?.ip,reason:"风险查询手动封禁"}):K("user",{user_id:_.value?.user_id,reason:"风险查询手动封禁"}))}async function Be(){!_.value||!h.value||(h.value==="ip"?(await ye(_.value?.ip),await W()):(await be(_.value?.user_id),await ne()))}async function $e(){if(h.value!=="ip")return;const n=String(_.value?.ip||"").trim();if(n){try{await te.confirm(`确定清除 IP ${n} 的风险分吗?
+import{a as g,_ as qe}from"./index-z8QOtKVT.js";import{M as ze}from"./MetricGrid-tQwTnWXY.js";import{e as d,f as oe,g as Ee,r as v,W as Ae,l as P,o as c,k as p,j as a,w as l,C as i,F as Oe,m as Ge,c as b,X as ee,B as f,i as ue,E as te,b as w}from"./vendor-BczUEOE_.js";async function He(){const{data:u}=await g.get("/admin/security/dashboard");return u}async function Ke(u){const{data:m}=await g.get("/admin/security/threats",{params:u});return m}async function We(){const{data:u}=await g.get("/admin/security/banned-ips");return u}async function Xe(){const{data:u}=await g.get("/admin/security/banned-users");return u}async function Je(u){const{data:m}=await g.post("/admin/security/ban-ip",u);return m}async function Qe(u){const{data:m}=await g.post("/admin/security/unban-ip",{ip:u});return m}async function Ye(u){const{data:m}=await g.post("/admin/security/ban-user",u);return m}async function Ze(u){const{data:m}=await g.post("/admin/security/unban-user",{user_id:u});return m}async function et(u){const m=encodeURIComponent(String(u||"").trim()),{data:V}=await g.get(`/admin/security/ip-risk/${m}`);return V}async function tt(u){const{data:m}=await g.post("/admin/security/ip-risk/clear",{ip:u});return m}async function at(u){const m=encodeURIComponent(String(u||"").trim()),{data:V}=await g.get(`/admin/security/user-risk/${m}`);return V}async function lt(){const{data:u}=await g.post("/admin/security/cleanup",{});return u}const nt={class:"page-stack"},it={class:"app-page-title"},st={class:"toolbar"},ot={class:"filters"},ut={class:"table-wrap"},rt={key:1},dt={key:1},pt={class:"mono ellipsis"},ct={class:"ellipsis"},ft={class:"pagination"},mt={class:"page-hint app-muted"},vt={class:"toolbar"},_t={class:"table-wrap"},yt={class:"table-wrap"},bt={class:"filters"},gt={class:"filters"},kt={class:"risk-head"},ht={class:"risk-title"},wt={key:0},It={key:1},Vt={class:"toolbar"},xt={class:"table-wrap"},Ct={class:"mono ellipsis"},St={class:"ellipsis"},Tt={class:"dialog-actions"},re=20,Pt={__name:"SecurityPage",setup(u){const m=d("threats"),V=d(!1),L=d(null),ae=d(!1),q=d([]),z=d(0),C=d(1),U=d(""),B=d(""),$=d(!1),de=d([]),pe=d([]),ce=d("ips"),S=d(!1),R=d(!1),o=d({kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}),E=d("ip"),k=d(!1),A=d(""),O=d(""),_=d(null),h=d(""),Ie=["sql_injection","xss","path_traversal","command_injection","ssrf","scanner","bruteforce","csrf","xxe","file_upload"];function le(n){const e=Number(n);return Number.isFinite(e)?e:0}function D(n){const e=Number(n||0);return e>=80?{label:"高",type:"danger"}:e>=50?{label:"中",type:"warning"}:{label:"低",type:"success"}}function fe(n){const e=String(n||"").trim();return e||"永久"}function me(n){const e=[];return n?.field_name&&e.push(`字段: ${n.field_name}`),n?.rule&&e.push(`规则: ${n.rule}`),n?.matched&&e.push(`匹配: ${n.matched}`),n?.value_preview&&e.push(`值: ${n.value_preview}`),e.length?e.join(" · "):"-"}function G(n){const e=String(n?.request_method||"").trim(),s=String(n?.request_path||"").trim();return`${e} ${s}`.trim()||"-"}const Ve=oe(()=>{const n=new Set(Ie),e=L.value?.recent_threat_events||[];for(const s of e){const y=String(s?.threat_type||"").trim();y&&n.add(y)}for(const s of q.value||[]){const y=String(s?.threat_type||"").trim();y&&n.add(y)}return Array.from(n).sort((s,y)=>s.localeCompare(y)).map(s=>({label:s,value:s}))}),xe=oe(()=>{const n=L.value||{};return[{key:"threat_events_24h",label:"最近24小时威胁事件",value:le(n.threat_events_24h),tone:"red",hint:"用于衡量当前攻击面活跃度"},{key:"banned_ip_count",label:"当前封禁 IP 数",value:le(n.banned_ip_count),tone:"orange",hint:"自动与人工封禁总量"},{key:"banned_user_count",label:"当前封禁用户数",value:le(n.banned_user_count),tone:"purple",hint:"高风险账户拦截情况"}]}),Ce=oe(()=>Math.max(1,Math.ceil((z.value||0)/re)));async function N(){V.value=!0;try{L.value=await He()}catch{L.value=null}finally{V.value=!1}}async function H(){ae.value=!0;try{const n={page:C.value,per_page:re};U.value&&(n.event_type=U.value),B.value&&(n.severity=B.value);const e=await Ke(n);q.value=e?.items||[],z.value=e?.total||0}catch{q.value=[],z.value=0}finally{ae.value=!1}}async function F(){if(!$.value){$.value=!0;try{const[n,e]=await Promise.allSettled([We(),Xe()]);de.value=n.status==="fulfilled"?n.value?.items||[]:[],pe.value=e.status==="fulfilled"?e.value?.items||[]:[]}finally{$.value=!1}}}async function ve(){await Promise.allSettled([N(),H(),F()])}function Se(){C.value=1,H()}function Te(){U.value="",B.value="",C.value=1,H()}function _e(){o.value={kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}}function K(n="ip",e={}){_e(),o.value.kind=n==="user"?"user":"ip",o.value.kind==="ip"?o.value.ip=String(e.ip||"").trim():o.value.user_id=String(e.user_id||"").trim(),e.reason&&(o.value.reason=String(e.reason||"").trim()),S.value=!0}async function Pe(){const n=o.value.kind,e=String(o.value.reason||"").trim(),s=!!o.value.permanent,y=Number(o.value.duration_hours||24);if(!e){w.error("原因不能为空");return}if(n==="ip"){const I=String(o.value.ip||"").trim();if(!I){w.error("IP不能为空");return}R.value=!0;try{await Je({ip:I,reason:e,duration_hours:y,permanent:s}),w.success("IP已封禁"),S.value=!1,await Promise.allSettled([N(),F()])}catch{}finally{R.value=!1}return}const X=String(o.value.user_id||"").trim(),r=Number.parseInt(X,10);if(!Number.isFinite(r)){w.error("用户ID无效");return}R.value=!0;try{await Ye({user_id:r,reason:e,duration_hours:y,permanent:s}),w.success("用户已封禁"),S.value=!1,await Promise.allSettled([N(),F()])}catch{}finally{R.value=!1}}async function ye(n){const e=String(n||"").trim();if(e){try{await te.confirm(`确定解除对 IP ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Qe(e),w.success("已解除IP封禁"),await Promise.allSettled([N(),F()])}catch{}}}async function be(n){const e=Number.parseInt(String(n||"").trim(),10);if(Number.isFinite(e)){try{await te.confirm(`确定解除对 用户ID ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Ze(e),w.success("已解除用户封禁"),await Promise.allSettled([N(),F()])}catch{}}}function ge(n){const e=String(n||"").trim();e&&(m.value="risk",E.value="ip",A.value=e,W())}function ke(n){const e=String(n||"").trim();e&&(m.value="risk",E.value="user",O.value=e,ne())}async function W(){const n=String(A.value||"").trim();if(!n){w.error("请输入IP");return}k.value=!0;try{_.value=await et(n),h.value="ip"}catch{_.value=null,h.value=""}finally{k.value=!1}}async function ne(){const n=String(O.value||"").trim(),e=Number.parseInt(n,10);if(!Number.isFinite(e)){w.error("请输入有效的用户ID");return}k.value=!0;try{_.value=await at(e),h.value="user"}catch{_.value=null,h.value=""}finally{k.value=!1}}function Ue(){!_.value||!h.value||(h.value==="ip"?K("ip",{ip:_.value?.ip,reason:"风险查询手动封禁"}):K("user",{user_id:_.value?.user_id,reason:"风险查询手动封禁"}))}async function Be(){!_.value||!h.value||(h.value==="ip"?(await ye(_.value?.ip),await W()):(await be(_.value?.user_id),await ne()))}async function $e(){if(h.value!=="ip")return;const n=String(_.value?.ip||"").trim();if(n){try{await te.confirm(`确定清除 IP ${n} 的风险分吗?
清除风险分不会删除威胁历史,也不会解除封禁。`,"清除风险分",{confirmButtonText:"清除",cancelButtonText:"取消",type:"warning"})}catch{return}if(!k.value){k.value=!0;try{await tt(n),w.success("IP风险分已清零")}catch{}finally{k.value=!1}await W()}}}const ie=d(!1);async function Re(){try{await te.confirm(`确定清理过期封禁记录,并衰减风险分吗?
diff --git a/static/admin/assets/SettingsPage-Czik5vp6.js b/static/admin/assets/SettingsPage-Bagp2mPP.js
similarity index 98%
rename from static/admin/assets/SettingsPage-Czik5vp6.js
rename to static/admin/assets/SettingsPage-Bagp2mPP.js
index a058a46..be1cdb4 100644
--- a/static/admin/assets/SettingsPage-Czik5vp6.js
+++ b/static/admin/assets/SettingsPage-Bagp2mPP.js
@@ -1 +1 @@
-import{a as m,_ as h}from"./index-C51y9xCM.js";import{e as p,r as u,l as T,o as C,k as r,j as a,w as o,C as x,b as i,E as b}from"./vendor-BczUEOE_.js";async function P(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function E(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function S(){const{data:l}=await m.post("/logout");return l}const U={class:"page-stack"},A={__name:"SettingsPage",setup(l){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 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 P(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){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"),w=u("el-form-item"),v=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:o(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:o(()=>[a(w,{label:"新用户名"},{default:o(()=>[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:o(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:o(()=>[a(w,{label:"新密码"},{default:o(()=>[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:B},{default:o(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},j=h(A,[["__scopeId","data-v-83d3840a"]]);export{j as default};
+import{a as m,_ as h}from"./index-z8QOtKVT.js";import{e as p,r as u,l as T,o as C,k as r,j as a,w as o,C as x,b as i,E as b}from"./vendor-BczUEOE_.js";async function P(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function E(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function S(){const{data:l}=await m.post("/logout");return l}const U={class:"page-stack"},A={__name:"SettingsPage",setup(l){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 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 P(t),i.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){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"),w=u("el-form-item"),v=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:o(()=>[e[3]||(e[3]=r("h3",{class:"section-title"},"修改管理员用户名",-1)),a(v,{"label-width":"120px"},{default:o(()=>[a(w,{label:"新用户名"},{default:o(()=>[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:o(()=>[...e[2]||(e[2]=[x("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(_,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:o(()=>[e[5]||(e[5]=r("h3",{class:"section-title"},"修改管理员密码",-1)),a(v,{"label-width":"120px"},{default:o(()=>[a(w,{label:"新密码"},{default:o(()=>[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:B},{default:o(()=>[...e[4]||(e[4]=[x("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},j=h(A,[["__scopeId","data-v-83d3840a"]]);export{j as default};
diff --git a/static/admin/assets/SystemPage-8v_X5n5H.js b/static/admin/assets/SystemPage-8v_X5n5H.js
new file mode 100644
index 0000000..5ffcc5a
--- /dev/null
+++ b/static/admin/assets/SystemPage-8v_X5n5H.js
@@ -0,0 +1,6 @@
+import{f as be,u as Z}from"./system-COpUDNdl.js";import{a as P,_ as ge}from"./index-z8QOtKVT.js";import{e as n,f as ke,a1 as xe,h as we,g as Ue,r as p,W as Ce,X as Pe,o as V,l as b,k as s,j as l,w as t,C as g,i as ee,B as le,E as ue,b as m}from"./vendor-BczUEOE_.js";async function ae(r={}){const{data:c}=await P.get("/kdocs/status",{params:r});return c}async function Se(r={}){const c={force:!0,...r},{data:k}=await P.post("/kdocs/qr",c);return k}async function Ie(){const{data:r}=await P.post("/kdocs/clear-login",{});return r}async function Ae(){const{data:r}=await P.get("/proxy/config");return r}async function Ne(r){const{data:c}=await P.post("/proxy/config",r);return c}async function De(r){const{data:c}=await P.post("/proxy/test",r);return c}const Ke={class:"page-stack"},Ee={class:"config-grid"},Le={class:"row-actions"},Qe={class:"row-actions"},Be={class:"row-actions"},Te={class:"section-head"},qe={class:"status-inline app-muted"},$e={key:0},Me={key:1},Re={key:2},he={class:"kdocs-inline"},Fe={class:"kdocs-range"},He={class:"row-actions"},ze={key:0,class:"help"},Oe={key:1,class:"help"},je={class:"kdocs-qr"},Ge=["src"],We={__name:"SystemPage",setup(r){const c=n(!1),k=n(2),I=n(1),A=n(3),N=n(120),S=n(!1),f=n(""),D=n(3),K=n(!1),E=n(10),L=n(7),Q=n(!1),B=n(""),T=n(""),q=n(""),$=n(0),M=n("A"),R=n("D"),h=n(0),F=n(0),H=n(!1),z=n(""),v=n({}),x=n(!1),w=n(""),oe=n(!1),O=n(!1),U=n(!1),C=n(!1),j=n("");let G=null;const te=ke(()=>O.value||U.value||C.value);function i(a){if(!a){j.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});j.value=`${a} (${e})`}async function de(){c.value=!0;try{const[a,e,d]=await Promise.all([be(),Ae(),ae().catch(()=>({}))]);k.value=a.max_concurrent_global??2,I.value=a.max_concurrent_per_account??1,A.value=a.max_screenshot_concurrent??3,N.value=a.db_slow_query_ms??120,K.value=(a.auto_approve_enabled??0)===1,E.value=a.auto_approve_hourly_limit??10,L.value=a.auto_approve_vip_days??7,S.value=(e.proxy_enabled??0)===1,f.value=e.proxy_api_url||"",D.value=e.proxy_expire_minutes??3,Q.value=(a.kdocs_enabled??0)===1,B.value=a.kdocs_doc_url||"",T.value=a.kdocs_default_unit||"",q.value=a.kdocs_sheet_name||"",$.value=a.kdocs_sheet_index??0,M.value=(a.kdocs_unit_column||"A").toUpperCase(),R.value=(a.kdocs_image_column||"D").toUpperCase(),h.value=a.kdocs_row_start??0,F.value=a.kdocs_row_end??0,H.value=(a.kdocs_admin_notify_enabled??0)===1,z.value=a.kdocs_admin_notify_email||"",v.value=d||{}}catch{}finally{c.value=!1}}async function ie(){const a={max_concurrent_global:Number(k.value),max_concurrent_per_account:Number(I.value),max_screenshot_concurrent:Number(A.value),db_slow_query_ms:Number(N.value)};try{await ue.confirm(`确定更新并发配置吗?
+
+全局并发数: ${a.max_concurrent_global}
+单账号并发数: ${a.max_concurrent_per_account}
+截图并发数: ${a.max_screenshot_concurrent}
+慢 SQL 阈值: ${a.db_slow_query_ms}ms`,"保存并发配置",{confirmButtonText:"保存",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await Z(a);m.success(e?.message||"并发配置已更新")}catch{}}async function re(){if(S.value&&!f.value.trim()){m.error("启用代理时,API地址不能为空");return}const a={proxy_enabled:S.value?1:0,proxy_api_url:f.value.trim(),proxy_expire_minutes:Number(D.value)||3};try{const e=await Ne(a);m.success(e?.message||"代理配置已更新")}catch{}}async function me(){if(!f.value.trim()){m.error("请先输入代理API地址");return}try{const a=await De({api_url:f.value.trim()});await ue.alert(a?.message||"测试完成","代理测试",{confirmButtonText:"知道了"})}catch{}}async function ce(){const a=Number(E.value),e=Number(L.value);if(!Number.isFinite(a)||a<1){m.error("每小时注册限制必须大于0");return}if(!Number.isFinite(e)||e<0){m.error("VIP天数不能为负数");return}const d={auto_approve_enabled:K.value?1:0,auto_approve_hourly_limit:a,auto_approve_vip_days:e};try{const u=await Z(d);m.success(u?.message||"注册设置已保存")}catch{}}async function ve(){const a={kdocs_enabled:Q.value?1:0,kdocs_doc_url:B.value.trim(),kdocs_default_unit:T.value.trim(),kdocs_sheet_name:q.value.trim(),kdocs_sheet_index:Number($.value)||0,kdocs_unit_column:M.value.trim().toUpperCase(),kdocs_image_column:R.value.trim().toUpperCase(),kdocs_row_start:Number(h.value)||0,kdocs_row_end:Number(F.value)||0,kdocs_admin_notify_enabled:H.value?1:0,kdocs_admin_notify_email:z.value.trim()};try{const e=await Z(a);m.success(e?.message||"表格配置已更新")}catch{}}async function se(){if(!O.value){O.value=!0,i("正在刷新状态");try{v.value=await ae({live:1}),i("状态已刷新")}catch{i("刷新失败,请稍后重试")}finally{O.value=!1}}}async function ne(){try{const a=await ae({live:1});v.value=a,(a?.logged_in===!0||a?.last_login_ok===!0)&&(m.success("扫码成功,已登录"),i("扫码成功,已登录"),x.value=!1,W())}catch{}}function pe(){W(),oe.value=!0,i("扫码检测中"),ne(),G=setInterval(ne,2e3)}function W(){G&&(clearInterval(G),G=null),oe.value=!1}async function fe(){if(!U.value){U.value=!0,i("正在获取二维码");try{w.value="";const a=await Se();if(w.value=a?.qr_image||"",!w.value){if(a?.logged_in){m.success("当前已登录,无需扫码"),i("当前已登录,无需扫码"),await se();return}m.warning("未获取到二维码"),i("未获取到二维码");return}i("二维码已获取"),x.value=!0}catch{i("获取二维码失败")}finally{U.value=!1}}}async function _e(){if(!C.value){C.value=!0,i("正在清除登录态");try{await Ie(),x.value=!1,w.value="",m.success("登录态已清除"),i("登录态已清除"),await se()}catch{i("清除登录态失败")}finally{C.value=!1}}}return xe(x,a=>{a?pe():W()}),we(()=>{W()}),Ue(de),(a,e)=>{const d=p("el-input-number"),u=p("el-form-item"),X=p("el-form"),_=p("el-button"),J=p("el-card"),Y=p("el-switch"),y=p("el-input"),ye=p("el-dialog"),Ve=Ce("loading");return Pe((V(),b("div",Ke,[e[49]||(e[49]=s("div",{class:"app-page-title"},[s("h2",null,"系统配置")],-1)),s("div",Ee,[l(J,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[27]||(e[27]=s("h3",{class:"section-title"},"并发配置",-1)),e[28]||(e[28]=s("div",{class:"section-sub app-muted"},"控制任务与截图的并发资源上限",-1)),l(X,{"label-width":"122px"},{default:t(()=>[l(u,{label:"全局最大并发数"},{default:t(()=>[l(d,{modelValue:k.value,"onUpdate:modelValue":e[0]||(e[0]=o=>k.value=o),min:1,max:200},null,8,["modelValue"]),e[22]||(e[22]=s("div",{class:"help"},"同时最多运行账号数(浏览任务 API 执行,资源占用较低)。",-1))]),_:1}),l(u,{label:"单账号最大并发数"},{default:t(()=>[l(d,{modelValue:I.value,"onUpdate:modelValue":e[1]||(e[1]=o=>I.value=o),min:1,max:50},null,8,["modelValue"]),e[23]||(e[23]=s("div",{class:"help"},"建议保持为 1,避免同账号任务抢占。",-1))]),_:1}),l(u,{label:"截图最大并发数"},{default:t(()=>[l(d,{modelValue:A.value,"onUpdate:modelValue":e[2]||(e[2]=o=>A.value=o),min:1,max:50},null,8,["modelValue"]),e[24]||(e[24]=s("div",{class:"help"},"截图资源占用较低,可按机器性能逐步提高。",-1))]),_:1}),l(u,{label:"慢 SQL 阈值(ms)"},{default:t(()=>[l(d,{modelValue:N.value,"onUpdate:modelValue":e[3]||(e[3]=o=>N.value=o),min:0,max:6e4},null,8,["modelValue"]),e[25]||(e[25]=s("div",{class:"help"},"低于该阈值不会计入慢 SQL(0 表示关闭慢 SQL 采样)。",-1))]),_:1})]),_:1}),s("div",Le,[l(_,{type:"primary",onClick:ie},{default:t(()=>[...e[26]||(e[26]=[g("保存并发配置",-1)])]),_:1})])]),_:1}),l(J,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[33]||(e[33]=s("h3",{class:"section-title"},"代理设置",-1)),e[34]||(e[34]=s("div",{class:"section-sub app-muted"},"用于任务出网代理与连接有效期管理",-1)),l(X,{"label-width":"122px"},{default:t(()=>[l(u,{label:"启用 IP 代理"},{default:t(()=>[l(Y,{modelValue:S.value,"onUpdate:modelValue":e[4]||(e[4]=o=>S.value=o)},null,8,["modelValue"]),e[29]||(e[29]=s("div",{class:"help"},"开启后,浏览任务通过代理访问,失败自动重试。",-1))]),_:1}),l(u,{label:"代理 API 地址"},{default:t(()=>[l(y,{modelValue:f.value,"onUpdate:modelValue":e[5]||(e[5]=o=>f.value=o),placeholder:"http://api.xxx/Tools/IP.ashx?..."},null,8,["modelValue"]),e[30]||(e[30]=s("div",{class:"help"},"API 应返回 `IP:PORT`(例:123.45.67.89:8888)。",-1))]),_:1}),l(u,{label:"有效期(分钟)"},{default:t(()=>[l(d,{modelValue:D.value,"onUpdate:modelValue":e[6]||(e[6]=o=>D.value=o),min:1,max:60},null,8,["modelValue"])]),_:1})]),_:1}),s("div",Qe,[l(_,{type:"primary",onClick:re},{default:t(()=>[...e[31]||(e[31]=[g("保存代理配置",-1)])]),_:1}),l(_,{onClick:me},{default:t(()=>[...e[32]||(e[32]=[g("测试代理",-1)])]),_:1})])]),_:1}),l(J,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[37]||(e[37]=s("h3",{class:"section-title"},"注册设置",-1)),e[38]||(e[38]=s("div",{class:"section-sub app-muted"},"控制注册节流与新用户赠送 VIP",-1)),l(X,{"label-width":"122px"},{default:t(()=>[l(u,{label:"注册赠送 VIP"},{default:t(()=>[l(Y,{modelValue:K.value,"onUpdate:modelValue":e[7]||(e[7]=o=>K.value=o)},null,8,["modelValue"]),e[35]||(e[35]=s("div",{class:"help"},"开启后,新用户注册成功自动赠送下方设定的 VIP 天数。",-1))]),_:1}),l(u,{label:"每小时注册限制"},{default:t(()=>[l(d,{modelValue:E.value,"onUpdate:modelValue":e[8]||(e[8]=o=>E.value=o),min:1,max:1e4},null,8,["modelValue"])]),_:1}),l(u,{label:"赠送 VIP 天数"},{default:t(()=>[l(d,{modelValue:L.value,"onUpdate:modelValue":e[9]||(e[9]=o=>L.value=o),min:0,max:999999},null,8,["modelValue"])]),_:1})]),_:1}),s("div",Be,[l(_,{type:"primary",onClick:ce},{default:t(()=>[...e[36]||(e[36]=[g("保存注册设置",-1)])]),_:1})])]),_:1})]),l(J,{shadow:"never","body-style":{padding:"16px"},class:"card kdocs-card"},{default:t(()=>[s("div",Te,[e[40]||(e[40]=s("h3",{class:"section-title"},"金山文档上传",-1)),s("div",qe,[e[39]||(e[39]=s("span",null,"登录状态:",-1)),v.value.last_login_ok===!0?(V(),b("span",$e,"已登录")):v.value.login_required?(V(),b("span",Me,"需要扫码")):(V(),b("span",Re,"未知")),s("span",null,"· 待上传 "+le(v.value.queue_size||0),1)])]),l(X,{"label-width":"118px",class:"kdocs-form"},{default:t(()=>[l(u,{label:"启用上传"},{default:t(()=>[l(Y,{modelValue:Q.value,"onUpdate:modelValue":e[10]||(e[10]=o=>Q.value=o)},null,8,["modelValue"]),e[41]||(e[41]=s("div",{class:"help"},"表格结构变化时可先关闭,避免错误上传。",-1))]),_:1}),l(u,{label:"文档链接"},{default:t(()=>[l(y,{modelValue:B.value,"onUpdate:modelValue":e[11]||(e[11]=o=>B.value=o),placeholder:"https://kdocs.cn/..."},null,8,["modelValue"])]),_:1}),l(u,{label:"默认县区"},{default:t(()=>[l(y,{modelValue:T.value,"onUpdate:modelValue":e[12]||(e[12]=o=>T.value=o),placeholder:"如:道县(用户可覆盖)"},null,8,["modelValue"])]),_:1}),l(u,{label:"Sheet 名称"},{default:t(()=>[l(y,{modelValue:q.value,"onUpdate:modelValue":e[13]||(e[13]=o=>q.value=o),placeholder:"留空使用第一个 Sheet"},null,8,["modelValue"])]),_:1}),l(u,{label:"Sheet 序号"},{default:t(()=>[l(d,{modelValue:$.value,"onUpdate:modelValue":e[14]||(e[14]=o=>$.value=o),min:0,max:50},null,8,["modelValue"]),e[42]||(e[42]=s("div",{class:"help"},"0 表示第一个 Sheet。",-1))]),_:1}),l(u,{label:"列配置"},{default:t(()=>[s("div",he,[l(y,{modelValue:M.value,"onUpdate:modelValue":e[15]||(e[15]=o=>M.value=o),placeholder:"县区列,如 A"},null,8,["modelValue"]),l(y,{modelValue:R.value,"onUpdate:modelValue":e[16]||(e[16]=o=>R.value=o),placeholder:"图片列,如 D"},null,8,["modelValue"])])]),_:1}),l(u,{label:"有效行范围"},{default:t(()=>[s("div",Fe,[l(d,{modelValue:h.value,"onUpdate:modelValue":e[17]||(e[17]=o=>h.value=o),min:0,max:1e4,placeholder:"起始行",style:{width:"140px"}},null,8,["modelValue"]),e[43]||(e[43]=s("span",{class:"app-muted"},"至",-1)),l(d,{modelValue:F.value,"onUpdate:modelValue":e[18]||(e[18]=o=>F.value=o),min:0,max:1e4,placeholder:"结束行",style:{width:"140px"}},null,8,["modelValue"])]),e[44]||(e[44]=s("div",{class:"help"},"用于限制上传区间(如 50-100),0 表示不限制。",-1))]),_:1}),l(u,{label:"管理员通知"},{default:t(()=>[l(Y,{modelValue:H.value,"onUpdate:modelValue":e[19]||(e[19]=o=>H.value=o)},null,8,["modelValue"])]),_:1}),l(u,{label:"通知邮箱"},{default:t(()=>[l(y,{modelValue:z.value,"onUpdate:modelValue":e[20]||(e[20]=o=>z.value=o),placeholder:"admin@example.com"},null,8,["modelValue"])]),_:1})]),_:1}),s("div",He,[l(_,{type:"primary",onClick:ve},{default:t(()=>[...e[45]||(e[45]=[g("保存表格配置",-1)])]),_:1}),l(_,{type:"success",plain:"",loading:U.value,disabled:te.value&&!U.value,onClick:fe},{default:t(()=>[...e[46]||(e[46]=[g(" 获取二维码 ",-1)])]),_:1},8,["loading","disabled"]),l(_,{type:"danger",plain:"",loading:C.value,disabled:te.value&&!C.value,onClick:_e},{default:t(()=>[...e[47]||(e[47]=[g(" 清除登录 ",-1)])]),_:1},8,["loading","disabled"])]),v.value.last_error?(V(),b("div",ze,"最近错误:"+le(v.value.last_error),1)):ee("",!0),j.value?(V(),b("div",Oe,"操作提示:"+le(j.value),1)):ee("",!0)]),_:1}),l(ye,{modelValue:x.value,"onUpdate:modelValue":e[21]||(e[21]=o=>x.value=o),title:"扫码登录",width:"min(420px, 92vw)"},{default:t(()=>[s("div",je,[w.value?(V(),b("img",{key:0,src:`data:image/png;base64,${w.value}`,alt:"KDocs QR"},null,8,Ge)):ee("",!0),e[48]||(e[48]=s("div",{class:"help"},"请使用管理员微信扫码登录。",-1))])]),_:1},8,["modelValue"])])),[[Ve,c.value]])}}},Ze=ge(We,[["__scopeId","data-v-a5c40f1a"]]);export{Ze as default};
diff --git a/static/admin/assets/SystemPage-CYpwmM8C.js b/static/admin/assets/SystemPage-CYpwmM8C.js
deleted file mode 100644
index f215db1..0000000
--- a/static/admin/assets/SystemPage-CYpwmM8C.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import{f as ge,u as Y}from"./system-BqUnjCUC.js";import{a as P,_ as Ve}from"./index-C51y9xCM.js";import{e as n,f as ke,a1 as xe,h as be,g as we,r as v,W as Ue,X as Ce,o as g,l as V,k as s,j as l,w as t,C as k,i as Z,B as ee,E as ne,b as c}from"./vendor-BczUEOE_.js";async function le(r={}){const{data:m}=await P.get("/kdocs/status",{params:r});return m}async function Pe(r={}){const m={force:!0,...r},{data:x}=await P.post("/kdocs/qr",m);return x}async function Ie(){const{data:r}=await P.post("/kdocs/clear-login",{});return r}async function Ae(){const{data:r}=await P.get("/proxy/config");return r}async function Ne(r){const{data:m}=await P.post("/proxy/config",r);return m}async function Se(r){const{data:m}=await P.post("/proxy/test",r);return m}const De={class:"page-stack"},Ke={class:"config-grid"},Ee={class:"row-actions"},Be={class:"row-actions"},Te={class:"row-actions"},Le={class:"section-head"},he={class:"status-inline app-muted"},Qe={key:0},$e={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},Fe={class:"row-actions"},He={key:0,class:"help"},ze={key:1,class:"help"},Oe={class:"kdocs-qr"},je=["src"],Ge={__name:"SystemPage",setup(r){const m=n(!1),x=n(2),A=n(1),N=n(3),I=n(!1),f=n(""),S=n(3),D=n(!1),K=n(10),E=n(7),B=n(!1),T=n(""),L=n(""),h=n(""),Q=n(0),$=n("A"),q=n("D"),M=n(0),R=n(0),F=n(!1),H=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),z=n(!1),U=n(!1),C=n(!1),O=n("");let j=null;const oe=ke(()=>z.value||U.value||C.value);function d(a){if(!a){O.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});O.value=`${a} (${e})`}async function ue(){m.value=!0;try{const[a,e,i]=await Promise.all([ge(),Ae(),le().catch(()=>({}))]);x.value=a.max_concurrent_global??2,A.value=a.max_concurrent_per_account??1,N.value=a.max_screenshot_concurrent??3,D.value=(a.auto_approve_enabled??0)===1,K.value=a.auto_approve_hourly_limit??10,E.value=a.auto_approve_vip_days??7,I.value=(e.proxy_enabled??0)===1,f.value=e.proxy_api_url||"",S.value=e.proxy_expire_minutes??3,B.value=(a.kdocs_enabled??0)===1,T.value=a.kdocs_doc_url||"",L.value=a.kdocs_default_unit||"",h.value=a.kdocs_sheet_name||"",Q.value=a.kdocs_sheet_index??0,$.value=(a.kdocs_unit_column||"A").toUpperCase(),q.value=(a.kdocs_image_column||"D").toUpperCase(),M.value=a.kdocs_row_start??0,R.value=a.kdocs_row_end??0,F.value=(a.kdocs_admin_notify_enabled??0)===1,H.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{m.value=!1}}async function de(){const a={max_concurrent_global:Number(x.value),max_concurrent_per_account:Number(A.value),max_screenshot_concurrent:Number(N.value)};try{await ne.confirm(`确定更新并发配置吗?
-
-全局并发数: ${a.max_concurrent_global}
-单账号并发数: ${a.max_concurrent_per_account}
-截图并发数: ${a.max_screenshot_concurrent}`,"保存并发配置",{confirmButtonText:"保存",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await Y(a);c.success(e?.message||"并发配置已更新")}catch{}}async function ie(){if(I.value&&!f.value.trim()){c.error("启用代理时,API地址不能为空");return}const a={proxy_enabled:I.value?1:0,proxy_api_url:f.value.trim(),proxy_expire_minutes:Number(S.value)||3};try{const e=await Ne(a);c.success(e?.message||"代理配置已更新")}catch{}}async function re(){if(!f.value.trim()){c.error("请先输入代理API地址");return}try{const a=await Se({api_url:f.value.trim()});await ne.alert(a?.message||"测试完成","代理测试",{confirmButtonText:"知道了"})}catch{}}async function ce(){const a=Number(K.value),e=Number(E.value);if(!Number.isFinite(a)||a<1){c.error("每小时注册限制必须大于0");return}if(!Number.isFinite(e)||e<0){c.error("VIP天数不能为负数");return}const i={auto_approve_enabled:D.value?1:0,auto_approve_hourly_limit:a,auto_approve_vip_days:e};try{const u=await Y(i);c.success(u?.message||"注册设置已保存")}catch{}}async function me(){const a={kdocs_enabled:B.value?1:0,kdocs_doc_url:T.value.trim(),kdocs_default_unit:L.value.trim(),kdocs_sheet_name:h.value.trim(),kdocs_sheet_index:Number(Q.value)||0,kdocs_unit_column:$.value.trim().toUpperCase(),kdocs_image_column:q.value.trim().toUpperCase(),kdocs_row_start:Number(M.value)||0,kdocs_row_end:Number(R.value)||0,kdocs_admin_notify_enabled:F.value?1:0,kdocs_admin_notify_email:H.value.trim()};try{const e=await Y(a);c.success(e?.message||"表格配置已更新")}catch{}}async function te(){if(!z.value){z.value=!0,d("正在刷新状态");try{p.value=await le({live:1}),d("状态已刷新")}catch{d("刷新失败,请稍后重试")}finally{z.value=!1}}}async function se(){try{const a=await le({live:1});p.value=a,(a?.logged_in===!0||a?.last_login_ok===!0)&&(c.success("扫码成功,已登录"),d("扫码成功,已登录"),b.value=!1,G())}catch{}}function pe(){G(),ae.value=!0,d("扫码检测中"),se(),j=setInterval(se,2e3)}function G(){j&&(clearInterval(j),j=null),ae.value=!1}async function ve(){if(!U.value){U.value=!0,d("正在获取二维码");try{w.value="";const a=await Pe();if(w.value=a?.qr_image||"",!w.value){if(a?.logged_in){c.success("当前已登录,无需扫码"),d("当前已登录,无需扫码"),await te();return}c.warning("未获取到二维码"),d("未获取到二维码");return}d("二维码已获取"),b.value=!0}catch{d("获取二维码失败")}finally{U.value=!1}}}async function fe(){if(!C.value){C.value=!0,d("正在清除登录态");try{await Ie(),b.value=!1,w.value="",c.success("登录态已清除"),d("登录态已清除"),await te()}catch{d("清除登录态失败")}finally{C.value=!1}}}return xe(b,a=>{a?pe():G()}),be(()=>{G()}),we(ue),(a,e)=>{const i=v("el-input-number"),u=v("el-form-item"),W=v("el-form"),_=v("el-button"),X=v("el-card"),J=v("el-switch"),y=v("el-input"),_e=v("el-dialog"),ye=Ue("loading");return Ce((g(),V("div",De,[e[47]||(e[47]=s("div",{class:"app-page-title"},[s("h2",null,"系统配置")],-1)),s("div",Ke,[l(X,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[25]||(e[25]=s("h3",{class:"section-title"},"并发配置",-1)),e[26]||(e[26]=s("div",{class:"section-sub app-muted"},"控制任务与截图的并发资源上限",-1)),l(W,{"label-width":"122px"},{default:t(()=>[l(u,{label:"全局最大并发数"},{default:t(()=>[l(i,{modelValue:x.value,"onUpdate:modelValue":e[0]||(e[0]=o=>x.value=o),min:1,max:200},null,8,["modelValue"]),e[21]||(e[21]=s("div",{class:"help"},"同时最多运行账号数(浏览任务 API 执行,资源占用较低)。",-1))]),_:1}),l(u,{label:"单账号最大并发数"},{default:t(()=>[l(i,{modelValue:A.value,"onUpdate:modelValue":e[1]||(e[1]=o=>A.value=o),min:1,max:50},null,8,["modelValue"]),e[22]||(e[22]=s("div",{class:"help"},"建议保持为 1,避免同账号任务抢占。",-1))]),_:1}),l(u,{label:"截图最大并发数"},{default:t(()=>[l(i,{modelValue:N.value,"onUpdate:modelValue":e[2]||(e[2]=o=>N.value=o),min:1,max:50},null,8,["modelValue"]),e[23]||(e[23]=s("div",{class:"help"},"截图资源占用较低,可按机器性能逐步提高。",-1))]),_:1})]),_:1}),s("div",Ee,[l(_,{type:"primary",onClick:de},{default:t(()=>[...e[24]||(e[24]=[k("保存并发配置",-1)])]),_:1})])]),_:1}),l(X,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[31]||(e[31]=s("h3",{class:"section-title"},"代理设置",-1)),e[32]||(e[32]=s("div",{class:"section-sub app-muted"},"用于任务出网代理与连接有效期管理",-1)),l(W,{"label-width":"122px"},{default:t(()=>[l(u,{label:"启用 IP 代理"},{default:t(()=>[l(J,{modelValue:I.value,"onUpdate:modelValue":e[3]||(e[3]=o=>I.value=o)},null,8,["modelValue"]),e[27]||(e[27]=s("div",{class:"help"},"开启后,浏览任务通过代理访问,失败自动重试。",-1))]),_:1}),l(u,{label:"代理 API 地址"},{default:t(()=>[l(y,{modelValue:f.value,"onUpdate:modelValue":e[4]||(e[4]=o=>f.value=o),placeholder:"http://api.xxx/Tools/IP.ashx?..."},null,8,["modelValue"]),e[28]||(e[28]=s("div",{class:"help"},"API 应返回 `IP:PORT`(例:123.45.67.89:8888)。",-1))]),_:1}),l(u,{label:"有效期(分钟)"},{default:t(()=>[l(i,{modelValue:S.value,"onUpdate:modelValue":e[5]||(e[5]=o=>S.value=o),min:1,max:60},null,8,["modelValue"])]),_:1})]),_:1}),s("div",Be,[l(_,{type:"primary",onClick:ie},{default:t(()=>[...e[29]||(e[29]=[k("保存代理配置",-1)])]),_:1}),l(_,{onClick:re},{default:t(()=>[...e[30]||(e[30]=[k("测试代理",-1)])]),_:1})])]),_:1}),l(X,{shadow:"never","body-style":{padding:"16px"},class:"card section-card"},{default:t(()=>[e[35]||(e[35]=s("h3",{class:"section-title"},"注册设置",-1)),e[36]||(e[36]=s("div",{class:"section-sub app-muted"},"控制注册节流与新用户赠送 VIP",-1)),l(W,{"label-width":"122px"},{default:t(()=>[l(u,{label:"注册赠送 VIP"},{default:t(()=>[l(J,{modelValue:D.value,"onUpdate:modelValue":e[6]||(e[6]=o=>D.value=o)},null,8,["modelValue"]),e[33]||(e[33]=s("div",{class:"help"},"开启后,新用户注册成功自动赠送下方设定的 VIP 天数。",-1))]),_:1}),l(u,{label:"每小时注册限制"},{default:t(()=>[l(i,{modelValue:K.value,"onUpdate:modelValue":e[7]||(e[7]=o=>K.value=o),min:1,max:1e4},null,8,["modelValue"])]),_:1}),l(u,{label:"赠送 VIP 天数"},{default:t(()=>[l(i,{modelValue:E.value,"onUpdate:modelValue":e[8]||(e[8]=o=>E.value=o),min:0,max:999999},null,8,["modelValue"])]),_:1})]),_:1}),s("div",Te,[l(_,{type:"primary",onClick:ce},{default:t(()=>[...e[34]||(e[34]=[k("保存注册设置",-1)])]),_:1})])]),_:1})]),l(X,{shadow:"never","body-style":{padding:"16px"},class:"card kdocs-card"},{default:t(()=>[s("div",Le,[e[38]||(e[38]=s("h3",{class:"section-title"},"金山文档上传",-1)),s("div",he,[e[37]||(e[37]=s("span",null,"登录状态:",-1)),p.value.last_login_ok===!0?(g(),V("span",Qe,"已登录")):p.value.login_required?(g(),V("span",$e,"需要扫码")):(g(),V("span",qe,"未知")),s("span",null,"· 待上传 "+ee(p.value.queue_size||0),1)])]),l(W,{"label-width":"118px",class:"kdocs-form"},{default:t(()=>[l(u,{label:"启用上传"},{default:t(()=>[l(J,{modelValue:B.value,"onUpdate:modelValue":e[9]||(e[9]=o=>B.value=o)},null,8,["modelValue"]),e[39]||(e[39]=s("div",{class:"help"},"表格结构变化时可先关闭,避免错误上传。",-1))]),_:1}),l(u,{label:"文档链接"},{default:t(()=>[l(y,{modelValue:T.value,"onUpdate:modelValue":e[10]||(e[10]=o=>T.value=o),placeholder:"https://kdocs.cn/..."},null,8,["modelValue"])]),_:1}),l(u,{label:"默认县区"},{default:t(()=>[l(y,{modelValue:L.value,"onUpdate:modelValue":e[11]||(e[11]=o=>L.value=o),placeholder:"如:道县(用户可覆盖)"},null,8,["modelValue"])]),_:1}),l(u,{label:"Sheet 名称"},{default:t(()=>[l(y,{modelValue:h.value,"onUpdate:modelValue":e[12]||(e[12]=o=>h.value=o),placeholder:"留空使用第一个 Sheet"},null,8,["modelValue"])]),_:1}),l(u,{label:"Sheet 序号"},{default:t(()=>[l(i,{modelValue:Q.value,"onUpdate:modelValue":e[13]||(e[13]=o=>Q.value=o),min:0,max:50},null,8,["modelValue"]),e[40]||(e[40]=s("div",{class:"help"},"0 表示第一个 Sheet。",-1))]),_:1}),l(u,{label:"列配置"},{default:t(()=>[s("div",Me,[l(y,{modelValue:$.value,"onUpdate:modelValue":e[14]||(e[14]=o=>$.value=o),placeholder:"县区列,如 A"},null,8,["modelValue"]),l(y,{modelValue:q.value,"onUpdate:modelValue":e[15]||(e[15]=o=>q.value=o),placeholder:"图片列,如 D"},null,8,["modelValue"])])]),_:1}),l(u,{label:"有效行范围"},{default:t(()=>[s("div",Re,[l(i,{modelValue:M.value,"onUpdate:modelValue":e[16]||(e[16]=o=>M.value=o),min:0,max:1e4,placeholder:"起始行",style:{width:"140px"}},null,8,["modelValue"]),e[41]||(e[41]=s("span",{class:"app-muted"},"至",-1)),l(i,{modelValue:R.value,"onUpdate:modelValue":e[17]||(e[17]=o=>R.value=o),min:0,max:1e4,placeholder:"结束行",style:{width:"140px"}},null,8,["modelValue"])]),e[42]||(e[42]=s("div",{class:"help"},"用于限制上传区间(如 50-100),0 表示不限制。",-1))]),_:1}),l(u,{label:"管理员通知"},{default:t(()=>[l(J,{modelValue:F.value,"onUpdate:modelValue":e[18]||(e[18]=o=>F.value=o)},null,8,["modelValue"])]),_:1}),l(u,{label:"通知邮箱"},{default:t(()=>[l(y,{modelValue:H.value,"onUpdate:modelValue":e[19]||(e[19]=o=>H.value=o),placeholder:"admin@example.com"},null,8,["modelValue"])]),_:1})]),_:1}),s("div",Fe,[l(_,{type:"primary",onClick:me},{default:t(()=>[...e[43]||(e[43]=[k("保存表格配置",-1)])]),_:1}),l(_,{type:"success",plain:"",loading:U.value,disabled:oe.value&&!U.value,onClick:ve},{default:t(()=>[...e[44]||(e[44]=[k(" 获取二维码 ",-1)])]),_:1},8,["loading","disabled"]),l(_,{type:"danger",plain:"",loading:C.value,disabled:oe.value&&!C.value,onClick:fe},{default:t(()=>[...e[45]||(e[45]=[k(" 清除登录 ",-1)])]),_:1},8,["loading","disabled"])]),p.value.last_error?(g(),V("div",He,"最近错误:"+ee(p.value.last_error),1)):Z("",!0),O.value?(g(),V("div",ze,"操作提示:"+ee(O.value),1)):Z("",!0)]),_:1}),l(_e,{modelValue:b.value,"onUpdate:modelValue":e[20]||(e[20]=o=>b.value=o),title:"扫码登录",width:"min(420px, 92vw)"},{default:t(()=>[s("div",Oe,[w.value?(g(),V("img",{key:0,src:`data:image/png;base64,${w.value}`,alt:"KDocs QR"},null,8,je)):Z("",!0),e[46]||(e[46]=s("div",{class:"help"},"请使用管理员微信扫码登录。",-1))])]),_:1},8,["modelValue"])])),[[ye,m.value]])}}},Ye=Ve(Ge,[["__scopeId","data-v-c90d5cb2"]]);export{Ye as default};
diff --git a/static/admin/assets/SystemPage-DMyNBO3N.css b/static/admin/assets/SystemPage-DMyNBO3N.css
deleted file mode 100644
index ab29638..0000000
--- a/static/admin/assets/SystemPage-DMyNBO3N.css
+++ /dev/null
@@ -1 +0,0 @@
-.page-stack[data-v-c90d5cb2]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-c90d5cb2]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-c90d5cb2]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-c90d5cb2]{min-width:0}.section-title[data-v-c90d5cb2]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-c90d5cb2]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-c90d5cb2]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-c90d5cb2]{font-size:12px}.kdocs-form[data-v-c90d5cb2]{margin-top:6px}.kdocs-inline[data-v-c90d5cb2]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-c90d5cb2]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-c90d5cb2]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-c90d5cb2]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-c90d5cb2]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-c90d5cb2]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-c90d5cb2]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:768px){.config-grid[data-v-c90d5cb2],.kdocs-inline[data-v-c90d5cb2]{grid-template-columns:1fr}.kdocs-range[data-v-c90d5cb2]{align-items:stretch}}
diff --git a/static/admin/assets/SystemPage-DYBocGi2.css b/static/admin/assets/SystemPage-DYBocGi2.css
new file mode 100644
index 0000000..74a5146
--- /dev/null
+++ b/static/admin/assets/SystemPage-DYBocGi2.css
@@ -0,0 +1 @@
+.page-stack[data-v-a5c40f1a]{display:flex;flex-direction:column;gap:14px;min-width:0}.config-grid[data-v-a5c40f1a]{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.card[data-v-a5c40f1a]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-card[data-v-a5c40f1a]{min-width:0}.section-title[data-v-a5c40f1a]{margin:0;font-size:15px;font-weight:800;letter-spacing:.2px}.section-sub[data-v-a5c40f1a]{margin-top:6px;margin-bottom:10px;font-size:12px}.section-head[data-v-a5c40f1a]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px}.status-inline[data-v-a5c40f1a]{font-size:12px}.kdocs-form[data-v-a5c40f1a]{margin-top:6px}.kdocs-inline[data-v-a5c40f1a]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;width:100%}.kdocs-range[data-v-a5c40f1a]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.kdocs-qr[data-v-a5c40f1a]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-a5c40f1a]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-a5c40f1a]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-a5c40f1a]{display:flex;flex-wrap:wrap;gap:10px}@media(max-width:1200px){.config-grid[data-v-a5c40f1a]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:768px){.config-grid[data-v-a5c40f1a],.kdocs-inline[data-v-a5c40f1a]{grid-template-columns:1fr}.kdocs-range[data-v-a5c40f1a]{align-items:stretch}}
diff --git a/static/admin/assets/UsersPage-hjzizufJ.js b/static/admin/assets/UsersPage-CJwQe-N8.js
similarity index 99%
rename from static/admin/assets/UsersPage-hjzizufJ.js
rename to static/admin/assets/UsersPage-CJwQe-N8.js
index 25b1934..6833c77 100644
--- a/static/admin/assets/UsersPage-hjzizufJ.js
+++ b/static/admin/assets/UsersPage-CJwQe-N8.js
@@ -1 +1 @@
-import{a as Z,r as q,s as X,b as F,c as G,d as H,f as J}from"./users-LqVgYOIa.js";import{_ as K}from"./index-C51y9xCM.js";import{L as O,e as P,g as Q,r as _,W as Y,l as C,o as r,k as y,j as o,w as s,X as ee,c as f,i as p,B as b,C as c,E as v,b as h}from"./vendor-BczUEOE_.js";function I(g){if(!g)return null;if(g instanceof Date)return g;let a=String(g).trim();if(!a)return null;/^\d{4}-\d{2}-\d{2}$/.test(a)&&(a=`${a}T00:00:00`);let l=a.includes("T")?a:a.replace(" ","T");l=l.replace(/\.(\d{3})\d+/,".$1"),/([zZ]|[+-]\d{2}:\d{2})$/.test(l)||(l=`${l}+08:00`);const u=new Date(l);return Number.isNaN(u.getTime())?null:u}function D(g){const a=String(g||"");if(!a)return{ok:!1,message:"密码不能为空"};if(a.length<8)return{ok:!1,message:"密码长度不能少于8个字符"};if(a.length>128)return{ok:!1,message:"密码长度不能超过128个字符"};const l=/[a-zA-Z]/.test(a),w=/\d/.test(a);return!l||!w?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}const te={class:"page-stack"},ne={class:"table-wrap"},se={class:"user-block"},ae={class:"user-main"},ie={key:0,class:"app-muted user-sub"},re={key:1,class:"vip-sub"},le={key:0,class:"app-muted"},oe={class:"actions"},ce={__name:"UsersPage",setup(g){const a=O("refreshStats",null),l=P(!1),w=P([]);function u(n){const e=n?.vip_expire_time;if(!e)return!1;if(String(e).startsWith("2099-12-31"))return!0;const i=I(e);return i?i.getTime()>Date.now():!1}function V(n){const e=n?.vip_expire_time;if(!e||!u(n))return"";if(String(e).startsWith("2099-12-31"))return"永久VIP";const i=I(e);if(!i)return`到期: ${e}`;const d=Math.ceil((i.getTime()-Date.now())/(1e3*60*60*24));return`到期: ${e}(剩${d}天)`}function B(n){return n==="rejected"?{label:"禁用",type:"danger"}:{label:"正常",type:"success"}}async function x(){l.value=!0;try{w.value=await J()}catch{w.value=[]}finally{l.value=!1}}async function U(){await x()}async function z(n){try{await v.confirm(`确定启用用户「${n.username}」吗?启用后用户可正常登录。`,"启用用户",{confirmButtonText:"启用",cancelButtonText:"取消",type:"success"})}catch{return}try{await Z(n.id),h.success("用户已启用"),await x(),await a?.()}catch{}}async function S(n){try{await v.confirm(`确定禁用用户「${n.username}」吗?禁用后用户将无法登录。`,"禁用用户",{confirmButtonText:"禁用",cancelButtonText:"取消",type:"warning"})}catch{return}try{await q(n.id),h.success("用户已禁用"),await x(),await a?.()}catch{}}async function N(n){try{await v.confirm(`确定删除用户「${n.username}」吗?此操作将删除该用户的所有数据,不可恢复!`,"删除用户",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{await H(n.id),h.success("用户已删除"),await x(),await a?.()}catch{}}async function $(n,e){const i={7:"一周",30:"一个月",365:"一年",999999:"永久"}[e]||`${e}天`;try{await v.confirm(`确定为用户「${n.username}」开通 ${i} VIP 吗?`,"设置VIP",{confirmButtonText:"确认",cancelButtonText:"取消",type:"warning"})}catch{return}try{const d=await X(n.id,e);h.success(d?.message||"VIP设置成功"),await x(),await a?.()}catch{}}async function E(n){try{await v.confirm(`确定移除用户「${n.username}」的 VIP 吗?`,"移除VIP",{confirmButtonText:"移除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await F(n.id);h.success(e?.message||"VIP已移除"),await x(),await a?.()}catch{}}async function M(n){let e;try{e=(await v.prompt("请输入新密码(至少8位且包含字母和数字)","重置密码",{confirmButtonText:"提交",cancelButtonText:"取消",inputType:"password",inputPlaceholder:"新密码",inputValidator:k=>D(k).ok,inputErrorMessage:"密码至少8位且包含字母和数字"})).value}catch{return}const i=D(e);if(!i.ok){h.error(i.message);return}try{await v.confirm(`确定将用户「${n.username}」的密码重置为该新密码吗?`,"二次确认",{confirmButtonText:"确认重置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const d=await G(n.id,e);h.success(d?.message||"密码重置成功")}catch{}}return Q(U),(n,e)=>{const i=_("el-table-column"),d=_("el-tag"),k=_("el-button"),T=_("el-dropdown-item"),j=_("el-dropdown-menu"),L=_("el-dropdown"),A=_("el-table"),R=_("el-card"),W=Y("loading");return r(),C("div",te,[e[11]||(e[11]=y("div",{class:"app-page-title"},[y("h2",null,"用户")],-1)),o(R,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[y("div",ne,[ee((r(),f(A,{data:w.value,style:{width:"100%"}},{default:s(()=>[o(i,{prop:"id",label:"ID",width:"80"}),o(i,{label:"用户","min-width":"240"},{default:s(({row:t})=>[y("div",se,[y("div",ae,[y("strong",null,b(t.username),1),u(t)?(r(),f(d,{key:0,type:"warning",effect:"light",size:"small"},{default:s(()=>[...e[0]||(e[0]=[c("VIP",-1)])]),_:1})):p("",!0)]),t.email?(r(),C("div",ie,b(t.email),1)):p("",!0),V(t)?(r(),C("div",re,b(V(t)),1)):p("",!0)])]),_:1}),o(i,{label:"状态",width:"120"},{default:s(({row:t})=>[o(d,{type:B(t.status).type,effect:"light"},{default:s(()=>[c(b(B(t.status).label),1)]),_:2},1032,["type"])]),_:1}),o(i,{label:"时间","min-width":"220"},{default:s(({row:t})=>[y("div",null,b(t.created_at),1),t.vip_expire_time?(r(),C("div",le,"VIP到期: "+b(t.vip_expire_time),1)):p("",!0)]),_:1}),o(i,{label:"操作",width:"280",fixed:"right"},{default:s(({row:t})=>[y("div",oe,[t.status==="rejected"?(r(),f(k,{key:0,type:"success",size:"small",onClick:m=>z(t)},{default:s(()=>[...e[1]||(e[1]=[c("启用",-1)])]),_:1},8,["onClick"])):(r(),f(k,{key:1,type:"warning",size:"small",onClick:m=>S(t)},{default:s(()=>[...e[2]||(e[2]=[c("禁用",-1)])]),_:1},8,["onClick"])),o(L,{trigger:"click"},{dropdown:s(()=>[o(j,null,{default:s(()=>[u(t)?p("",!0):(r(),f(T,{key:0,onClick:m=>$(t,7)},{default:s(()=>[...e[4]||(e[4]=[c("开通一周",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:1,onClick:m=>$(t,30)},{default:s(()=>[...e[5]||(e[5]=[c("开通一月",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:2,onClick:m=>$(t,365)},{default:s(()=>[...e[6]||(e[6]=[c("开通一年",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:3,onClick:m=>$(t,999999)},{default:s(()=>[...e[7]||(e[7]=[c("永久VIP",-1)])]),_:1},8,["onClick"])),u(t)?(r(),f(T,{key:4,onClick:m=>E(t)},{default:s(()=>[...e[8]||(e[8]=[c("移除VIP",-1)])]),_:1},8,["onClick"])):p("",!0)]),_:2},1024)]),default:s(()=>[o(k,{size:"small"},{default:s(()=>[...e[3]||(e[3]=[c("VIP",-1)])]),_:1})]),_:2},1024),o(k,{size:"small",onClick:m=>M(t)},{default:s(()=>[...e[9]||(e[9]=[c("重置密码",-1)])]),_:1},8,["onClick"]),o(k,{type:"danger",size:"small",onClick:m=>N(t)},{default:s(()=>[...e[10]||(e[10]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[W,l.value]])])]),_:1})])}}},pe=K(ce,[["__scopeId","data-v-9e917879"]]);export{pe as default};
+import{a as Z,r as q,s as X,b as F,c as G,d as H,f as J}from"./users-oERXj-b9.js";import{_ as K}from"./index-z8QOtKVT.js";import{L as O,e as P,g as Q,r as _,W as Y,l as C,o as r,k as y,j as o,w as s,X as ee,c as f,i as p,B as b,C as c,E as v,b as h}from"./vendor-BczUEOE_.js";function I(g){if(!g)return null;if(g instanceof Date)return g;let a=String(g).trim();if(!a)return null;/^\d{4}-\d{2}-\d{2}$/.test(a)&&(a=`${a}T00:00:00`);let l=a.includes("T")?a:a.replace(" ","T");l=l.replace(/\.(\d{3})\d+/,".$1"),/([zZ]|[+-]\d{2}:\d{2})$/.test(l)||(l=`${l}+08:00`);const u=new Date(l);return Number.isNaN(u.getTime())?null:u}function D(g){const a=String(g||"");if(!a)return{ok:!1,message:"密码不能为空"};if(a.length<8)return{ok:!1,message:"密码长度不能少于8个字符"};if(a.length>128)return{ok:!1,message:"密码长度不能超过128个字符"};const l=/[a-zA-Z]/.test(a),w=/\d/.test(a);return!l||!w?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}const te={class:"page-stack"},ne={class:"table-wrap"},se={class:"user-block"},ae={class:"user-main"},ie={key:0,class:"app-muted user-sub"},re={key:1,class:"vip-sub"},le={key:0,class:"app-muted"},oe={class:"actions"},ce={__name:"UsersPage",setup(g){const a=O("refreshStats",null),l=P(!1),w=P([]);function u(n){const e=n?.vip_expire_time;if(!e)return!1;if(String(e).startsWith("2099-12-31"))return!0;const i=I(e);return i?i.getTime()>Date.now():!1}function V(n){const e=n?.vip_expire_time;if(!e||!u(n))return"";if(String(e).startsWith("2099-12-31"))return"永久VIP";const i=I(e);if(!i)return`到期: ${e}`;const d=Math.ceil((i.getTime()-Date.now())/(1e3*60*60*24));return`到期: ${e}(剩${d}天)`}function B(n){return n==="rejected"?{label:"禁用",type:"danger"}:{label:"正常",type:"success"}}async function x(){l.value=!0;try{w.value=await J()}catch{w.value=[]}finally{l.value=!1}}async function U(){await x()}async function z(n){try{await v.confirm(`确定启用用户「${n.username}」吗?启用后用户可正常登录。`,"启用用户",{confirmButtonText:"启用",cancelButtonText:"取消",type:"success"})}catch{return}try{await Z(n.id),h.success("用户已启用"),await x(),await a?.()}catch{}}async function S(n){try{await v.confirm(`确定禁用用户「${n.username}」吗?禁用后用户将无法登录。`,"禁用用户",{confirmButtonText:"禁用",cancelButtonText:"取消",type:"warning"})}catch{return}try{await q(n.id),h.success("用户已禁用"),await x(),await a?.()}catch{}}async function N(n){try{await v.confirm(`确定删除用户「${n.username}」吗?此操作将删除该用户的所有数据,不可恢复!`,"删除用户",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{await H(n.id),h.success("用户已删除"),await x(),await a?.()}catch{}}async function $(n,e){const i={7:"一周",30:"一个月",365:"一年",999999:"永久"}[e]||`${e}天`;try{await v.confirm(`确定为用户「${n.username}」开通 ${i} VIP 吗?`,"设置VIP",{confirmButtonText:"确认",cancelButtonText:"取消",type:"warning"})}catch{return}try{const d=await X(n.id,e);h.success(d?.message||"VIP设置成功"),await x(),await a?.()}catch{}}async function E(n){try{await v.confirm(`确定移除用户「${n.username}」的 VIP 吗?`,"移除VIP",{confirmButtonText:"移除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await F(n.id);h.success(e?.message||"VIP已移除"),await x(),await a?.()}catch{}}async function M(n){let e;try{e=(await v.prompt("请输入新密码(至少8位且包含字母和数字)","重置密码",{confirmButtonText:"提交",cancelButtonText:"取消",inputType:"password",inputPlaceholder:"新密码",inputValidator:k=>D(k).ok,inputErrorMessage:"密码至少8位且包含字母和数字"})).value}catch{return}const i=D(e);if(!i.ok){h.error(i.message);return}try{await v.confirm(`确定将用户「${n.username}」的密码重置为该新密码吗?`,"二次确认",{confirmButtonText:"确认重置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const d=await G(n.id,e);h.success(d?.message||"密码重置成功")}catch{}}return Q(U),(n,e)=>{const i=_("el-table-column"),d=_("el-tag"),k=_("el-button"),T=_("el-dropdown-item"),j=_("el-dropdown-menu"),L=_("el-dropdown"),A=_("el-table"),R=_("el-card"),W=Y("loading");return r(),C("div",te,[e[11]||(e[11]=y("div",{class:"app-page-title"},[y("h2",null,"用户")],-1)),o(R,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[y("div",ne,[ee((r(),f(A,{data:w.value,style:{width:"100%"}},{default:s(()=>[o(i,{prop:"id",label:"ID",width:"80"}),o(i,{label:"用户","min-width":"240"},{default:s(({row:t})=>[y("div",se,[y("div",ae,[y("strong",null,b(t.username),1),u(t)?(r(),f(d,{key:0,type:"warning",effect:"light",size:"small"},{default:s(()=>[...e[0]||(e[0]=[c("VIP",-1)])]),_:1})):p("",!0)]),t.email?(r(),C("div",ie,b(t.email),1)):p("",!0),V(t)?(r(),C("div",re,b(V(t)),1)):p("",!0)])]),_:1}),o(i,{label:"状态",width:"120"},{default:s(({row:t})=>[o(d,{type:B(t.status).type,effect:"light"},{default:s(()=>[c(b(B(t.status).label),1)]),_:2},1032,["type"])]),_:1}),o(i,{label:"时间","min-width":"220"},{default:s(({row:t})=>[y("div",null,b(t.created_at),1),t.vip_expire_time?(r(),C("div",le,"VIP到期: "+b(t.vip_expire_time),1)):p("",!0)]),_:1}),o(i,{label:"操作",width:"280",fixed:"right"},{default:s(({row:t})=>[y("div",oe,[t.status==="rejected"?(r(),f(k,{key:0,type:"success",size:"small",onClick:m=>z(t)},{default:s(()=>[...e[1]||(e[1]=[c("启用",-1)])]),_:1},8,["onClick"])):(r(),f(k,{key:1,type:"warning",size:"small",onClick:m=>S(t)},{default:s(()=>[...e[2]||(e[2]=[c("禁用",-1)])]),_:1},8,["onClick"])),o(L,{trigger:"click"},{dropdown:s(()=>[o(j,null,{default:s(()=>[u(t)?p("",!0):(r(),f(T,{key:0,onClick:m=>$(t,7)},{default:s(()=>[...e[4]||(e[4]=[c("开通一周",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:1,onClick:m=>$(t,30)},{default:s(()=>[...e[5]||(e[5]=[c("开通一月",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:2,onClick:m=>$(t,365)},{default:s(()=>[...e[6]||(e[6]=[c("开通一年",-1)])]),_:1},8,["onClick"])),u(t)?p("",!0):(r(),f(T,{key:3,onClick:m=>$(t,999999)},{default:s(()=>[...e[7]||(e[7]=[c("永久VIP",-1)])]),_:1},8,["onClick"])),u(t)?(r(),f(T,{key:4,onClick:m=>E(t)},{default:s(()=>[...e[8]||(e[8]=[c("移除VIP",-1)])]),_:1},8,["onClick"])):p("",!0)]),_:2},1024)]),default:s(()=>[o(k,{size:"small"},{default:s(()=>[...e[3]||(e[3]=[c("VIP",-1)])]),_:1})]),_:2},1024),o(k,{size:"small",onClick:m=>M(t)},{default:s(()=>[...e[9]||(e[9]=[c("重置密码",-1)])]),_:1},8,["onClick"]),o(k,{type:"danger",size:"small",onClick:m=>N(t)},{default:s(()=>[...e[10]||(e[10]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[W,l.value]])])]),_:1})])}}},pe=K(ce,[["__scopeId","data-v-9e917879"]]);export{pe as default};
diff --git a/static/admin/assets/email-UCdCFkiw.js b/static/admin/assets/email-BgLsevU3.js
similarity index 86%
rename from static/admin/assets/email-UCdCFkiw.js
rename to static/admin/assets/email-BgLsevU3.js
index 1018396..b18863a 100644
--- a/static/admin/assets/email-UCdCFkiw.js
+++ b/static/admin/assets/email-BgLsevU3.js
@@ -1 +1 @@
-import{a as n}from"./index-C51y9xCM.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-z8QOtKVT.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};
diff --git a/static/admin/assets/index-C51y9xCM.js b/static/admin/assets/index-z8QOtKVT.js
similarity index 89%
rename from static/admin/assets/index-C51y9xCM.js
rename to static/admin/assets/index-z8QOtKVT.js
index 9d3ad91..f3dad1f 100644
--- a/static/admin/assets/index-C51y9xCM.js
+++ b/static/admin/assets/index-z8QOtKVT.js
@@ -1,2 +1,2 @@
-const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./ReportPage-CJumwOUP.js","./vendor-BczUEOE_.js","./vendor-C68yOrAy.css","./email-UCdCFkiw.js","./tasks-DW-oE78v.js","./system-BqUnjCUC.js","./MetricGrid-Cm-6uQPC.js","./MetricGrid-yP_dkP6X.css","./ReportPage-Csv-Q6wC.css","./UsersPage-hjzizufJ.js","./users-LqVgYOIa.js","./UsersPage-Cow_LicY.css","./FeedbacksPage-B-5AB1AS.js","./FeedbacksPage-mrXjCiV2.css","./LogsPage-DF5QqYRJ.js","./LogsPage-D1bozCEo.css","./AnnouncementsPage-D4vGLsnP.js","./AnnouncementsPage-DOwZaaOu.css","./EmailPage-BgnCtp6d.js","./EmailPage-BmPCDPYC.css","./SecurityPage-rmlzXSVY.js","./SecurityPage-DN76ndc_.css","./SystemPage-CYpwmM8C.js","./SystemPage-DMyNBO3N.css","./SettingsPage-Czik5vp6.js","./SettingsPage-DaB8PeRL.css"])))=>i.map(i=>d[i]);
-import{r as m,c as w,o as _,a as me,E as te,b as ne,u as fe,d as pe,e as O,f as K,g as _e,h as he,w as o,i as W,j as d,k as u,l as C,F as Y,m as z,n as ye,p as ge,q as be,s as ve,t as we,v as Ee,x as ke,y as Pe,z as Re,A as X,B as L,C as J,S as Te,D as V,G as Se,H as Oe,I as Le,J as xe,K as Ae}from"./vendor-BczUEOE_.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const l of s)if(l.type==="childList")for(const i of l.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const l={};return s.integrity&&(l.integrity=s.integrity),s.referrerPolicy&&(l.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?l.credentials="include":s.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function a(s){if(s.ep)return;s.ep=!0;const l=n(s);fetch(s.href,l)}})();const ae=(e,t)=>{const n=e.__vccOpts||e;for(const[a,s]of t)n[a]=s;return n},Ne={};function Be(e,t){const n=m("RouterView");return _(),w(n)}const Ce=ae(Ne,[["render",Be]]),De="modulepreload",Ve=function(e,t){return new URL(e,t).href},Q={},E=function(t,n,a){let s=Promise.resolve();if(n&&n.length>0){let T=function(p){return Promise.all(p.map(b=>Promise.resolve(b).then(P=>({status:"fulfilled",value:P}),P=>({status:"rejected",reason:P}))))};const i=document.getElementsByTagName("link"),f=document.querySelector("meta[property=csp-nonce]"),k=f?.nonce||f?.getAttribute("nonce");s=T(n.map(p=>{if(p=Ve(p,a),p in Q)return;Q[p]=!0;const b=p.endsWith(".css"),P=b?'[rel="stylesheet"]':"";if(a)for(let R=i.length-1;R>=0;R--){const v=i[R];if(v.href===p&&(!b||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${p}"]${P}`))return;const h=document.createElement("link");if(h.rel=b?"stylesheet":De,b||(h.as="script"),h.crossOrigin="",h.href=p,k&&h.setAttribute("nonce",k),document.head.appendChild(h),b)return new Promise((R,v)=>{h.addEventListener("load",R),h.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${p}`)))})}))}function l(i){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=i,window.dispatchEvent(f),!f.defaultPrevented)throw i}return s.then(i=>{for(const f of i||[])f.status==="rejected"&&l(f.reason);return t().catch(l)})};let Z="",ee=0;const Ie=new Set([408,425,429,500,502,503,504]),Me=1,$e=300;function x(e,t,n=1500){const a=Date.now();e===Z&&a-ee=Me)return!1;const a=String(e?.code||"");if(a==="ECONNABORTED"||a==="ERR_NETWORK")return!0;const s=Number(e?.response?.status||0);return Ie.has(s)}function je(e){return new Promise(t=>{window.setTimeout(t,Math.max(0,Number(e||0)))})}async function Ge(e,t){const n=e?.config||{},a=Number(n.__retry_count||0);n.__retry_count=a+1;const s=$e*(a+1);return await je(s),t.request(n)}const y=me.create({baseURL:"/yuyx/api",timeout:3e4,withCredentials:!0});let A=null;async function He(){return A||(A=te.prompt("请输入管理员密码进行二次确认","安全确认",{inputType:"password",inputPlaceholder:"管理员密码",confirmButtonText:"确认",cancelButtonText:"取消",inputValidator:e=>!!String(e||"").trim(),inputErrorMessage:"密码不能为空"}).then(async e=>{const t=String(e.value||"").trim();await y.post("/admin/reauth",{password:t}),ne.success("已通过安全确认")}).finally(()=>{A=null}),A)}y.interceptors.request.use(e=>{const t=String(e?.method||"GET").toUpperCase();if(!["GET","HEAD","OPTIONS"].includes(t)){const n=Fe("csrf_token");n&&(e.headers=e.headers||{},e.headers["X-CSRF-Token"]=n)}return e});y.interceptors.response.use(e=>e,async e=>{const t=e?.response?.status,n=e?.response?.data,a=n?.error||n?.message||e?.message||"请求失败";if(n?.code==="reauth_required"&&e?.config&&!e.config.__reauth_retry)try{return e.config.__reauth_retry=!0,await He(),y.request(e.config)}catch{return Promise.reject(e)}return qe(e)?Ge(e,y):(t===401?(x("401",a,3e3),(window.location?.pathname||"").startsWith("/yuyx")||(window.location.href="/yuyx")):t===403?x("403",a,5e3):t?x(`http:${t}:${a}`,a):e?.code==="ECONNABORTED"?x("timeout","请求超时",3e3):x(`net:${a}`,a,3e3),Promise.reject(e))});async function gt(e=""){const{data:t}=await y.get("/feedbacks",{params:e?{status:e}:{}});return t}async function Ke(){const{data:e}=await y.get("/feedbacks",{params:{limit:1,offset:0}});return e?.stats}async function bt(e,t){const{data:n}=await y.post(`/feedbacks/${e}/reply`,{reply:t});return n}async function vt(e){const{data:t}=await y.post(`/feedbacks/${e}/close`);return t}async function wt(e){const{data:t}=await y.delete(`/feedbacks/${e}`);return t}async function We(){const{data:e}=await y.get("/stats");return e}const Ye={class:"menu-label"},ze={key:1,class:"menu-label"},Xe={class:"header-left"},Je={class:"header-right"},Qe={class:"admin-name"},Ze={class:"main-shell"},et={class:"menu-label"},tt={key:1,class:"menu-label"},nt=6e4,at=18e4,st={__name:"AdminLayout",setup(e){const t=fe(),n=pe(),a=O({}),s=K(()=>a.value?.admin_username||"");async function l(){try{a.value=await We()}finally{}}const i=O(!1),f=O(0);let k=null;async function T(g=null){if(g&&typeof g=="object"){Object.prototype.hasOwnProperty.call(g,"pendingFeedbacks")&&(f.value=Number(g.pendingFeedbacks||0));return}if(!i.value){i.value=!0;try{const r=await Ke();f.value=Number(r?.pending||0)}finally{i.value=!1}}}function p(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function b(){return p()?at:nt}function P(){k&&(window.clearTimeout(k),k=null)}function h(){P(),k=window.setTimeout(async()=>{k=null,await T().catch(()=>{}),h()},b())}function R(){h()}V("refreshStats",l),V("adminStats",a),V("refreshNavBadges",T);const v=O(!1),S=O(!1);let N;function D(){v.value=!!N?.matches,v.value||(S.value=!1)}_e(async()=>{N=window.matchMedia("(max-width: 768px)"),N.addEventListener?.("change",D),D(),await l(),await T(),h(),window.addEventListener("visibilitychange",R)}),he(()=>{N?.removeEventListener?.("change",D),P(),window.removeEventListener("visibilitychange",R)});const I=[{path:"/reports",label:"报表",icon:ye},{path:"/users",label:"用户",icon:ge},{path:"/feedbacks",label:"反馈",icon:be,badgeKey:"feedbacks"},{path:"/logs",label:"任务日志",icon:ve},{path:"/announcements",label:"公告",icon:we},{path:"/email",label:"邮件",icon:Ee},{path:"/security",label:"安全防护",icon:ke},{path:"/system",label:"系统配置",icon:Pe},{path:"/settings",label:"设置",icon:Re}],M=K(()=>t.path);function B(g){return g?.badgeKey&&g.badgeKey==="feedbacks"?Number(f.value||0):0}async function se(){try{await te.confirm("确定退出管理员登录吗?","退出登录",{confirmButtonText:"退出",cancelButtonText:"取消",type:"warning"})}catch{return}try{await y.post("/logout")}finally{window.location.href="/yuyx"}}async function $(g){await n.push(g),S.value=!1}return(g,r)=>{const F=m("el-icon"),U=m("el-badge"),q=m("el-menu-item"),j=m("el-menu"),oe=m("el-aside"),G=m("el-button"),re=m("el-header"),ce=m("RouterView"),le=m("el-skeleton"),ie=m("el-card"),ue=m("el-main"),H=m("el-container"),de=m("el-drawer");return _(),w(H,{class:"layout-root"},{default:o(()=>[v.value?W("",!0):(_(),w(oe,{key:0,width:"220px",class:"layout-aside"},{default:o(()=>[r[2]||(r[2]=u("div",{class:"brand"},[u("div",{class:"brand-title"},"后台管理"),u("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),d(j,{"default-active":M.value,class:"aside-menu",router:"",onSelect:$},{default:o(()=>[(_(),C(Y,null,z(I,c=>d(q,{key:c.path,index:c.path},{default:o(()=>[d(F,null,{default:o(()=>[(_(),w(X(c.icon)))]),_:2},1024),B(c)>0?(_(),w(U,{key:0,value:B(c),max:99,class:"menu-badge"},{default:o(()=>[u("span",Ye,L(c.label),1)]),_:2},1032,["value"])):(_(),C("span",ze,L(c.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1})),d(H,null,{default:o(()=>[d(re,{class:"layout-header"},{default:o(()=>[u("div",Xe,[v.value?(_(),w(G,{key:0,text:"",class:"header-menu-btn",onClick:r[0]||(r[0]=c=>S.value=!0)},{default:o(()=>[...r[3]||(r[3]=[J(" 菜单 ",-1)])]),_:1})):W("",!0),r[4]||(r[4]=u("div",{class:"header-title"},"后台管理系统",-1))]),u("div",Je,[u("div",Qe,[r[5]||(r[5]=u("span",{class:"app-muted"},"管理员",-1)),u("strong",null,L(s.value||"-"),1)]),d(G,{type:"primary",plain:"",class:"logout-btn",onClick:se},{default:o(()=>[...r[6]||(r[6]=[J("退出",-1)])]),_:1})])]),_:1}),d(ue,{class:"layout-main"},{default:o(()=>[u("div",Ze,[(_(),w(Te,null,{default:o(()=>[d(ce)]),fallback:o(()=>[d(ie,{shadow:"never","body-style":{padding:"16px"},class:"fallback-card"},{default:o(()=>[d(le,{rows:5,animated:""})]),_:1})]),_:1}))])]),_:1})]),_:1}),d(de,{modelValue:S.value,"onUpdate:modelValue":r[1]||(r[1]=c=>S.value=c),size:"min(82vw, 280px)",direction:"ltr","with-header":!1},{default:o(()=>[r[7]||(r[7]=u("div",{class:"drawer-brand"},[u("div",{class:"brand-title"},"后台管理"),u("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),d(j,{"default-active":M.value,class:"aside-menu",router:"",onSelect:$},{default:o(()=>[(_(),C(Y,null,z(I,c=>d(q,{key:c.path,index:c.path},{default:o(()=>[d(F,null,{default:o(()=>[(_(),w(X(c.icon)))]),_:2},1024),B(c)>0?(_(),w(U,{key:0,value:B(c),max:99,class:"menu-badge"},{default:o(()=>[u("span",et,L(c.label),1)]),_:2},1032,["value"])):(_(),C("span",tt,L(c.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1},8,["modelValue"])]),_:1})}}},ot=ae(st,[["__scopeId","data-v-ce83ce8c"]]),rt=()=>E(()=>import("./ReportPage-CJumwOUP.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8]),import.meta.url),ct=()=>E(()=>import("./UsersPage-hjzizufJ.js"),__vite__mapDeps([9,10,1,2,11]),import.meta.url),lt=()=>E(()=>import("./FeedbacksPage-B-5AB1AS.js"),__vite__mapDeps([12,6,1,2,7,13]),import.meta.url),it=()=>E(()=>import("./LogsPage-DF5QqYRJ.js"),__vite__mapDeps([14,10,4,1,2,15]),import.meta.url),ut=()=>E(()=>import("./AnnouncementsPage-D4vGLsnP.js"),__vite__mapDeps([16,1,2,17]),import.meta.url),dt=()=>E(()=>import("./EmailPage-BgnCtp6d.js"),__vite__mapDeps([18,3,6,1,2,7,19]),import.meta.url),mt=()=>E(()=>import("./SecurityPage-rmlzXSVY.js"),__vite__mapDeps([20,6,1,2,7,21]),import.meta.url),ft=()=>E(()=>import("./SystemPage-CYpwmM8C.js"),__vite__mapDeps([22,5,1,2,23]),import.meta.url),pt=()=>E(()=>import("./SettingsPage-Czik5vp6.js"),__vite__mapDeps([24,1,2,25]),import.meta.url),_t=[{path:"/",component:ot,children:[{path:"",redirect:"/reports"},{path:"/pending",redirect:"/reports"},{path:"/stats",redirect:"/reports"},{path:"/reports",name:"reports",component:rt},{path:"/users",name:"users",component:ct},{path:"/feedbacks",name:"feedbacks",component:lt},{path:"/logs",name:"logs",component:it},{path:"/announcements",name:"announcements",component:ut},{path:"/email",name:"email",component:dt},{path:"/security",name:"security",component:mt},{path:"/system",name:"system",component:ft},{path:"/settings",name:"settings",component:pt}]}],ht=Se({history:Oe(),routes:_t});Le(Ce).use(ht).use(xe,{locale:Ae}).mount("#app");export{ae as _,y as a,gt as b,vt as c,wt as d,Ke as f,bt as r};
+const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./ReportPage-CVnILNWh.js","./vendor-BczUEOE_.js","./vendor-C68yOrAy.css","./email-BgLsevU3.js","./tasks-Bprl-b9c.js","./system-COpUDNdl.js","./MetricGrid-tQwTnWXY.js","./MetricGrid-yP_dkP6X.css","./ReportPage-Csv-Q6wC.css","./UsersPage-CJwQe-N8.js","./users-oERXj-b9.js","./UsersPage-Cow_LicY.css","./FeedbacksPage-Dg3BwCE1.js","./FeedbacksPage-mrXjCiV2.css","./LogsPage-B80ridTr.js","./LogsPage-D1bozCEo.css","./AnnouncementsPage-B1ijXegw.js","./AnnouncementsPage-DOwZaaOu.css","./EmailPage-BA_Nuk9g.js","./EmailPage-BmPCDPYC.css","./SecurityPage-tMcfFXjg.js","./SecurityPage-DN76ndc_.css","./SystemPage-8v_X5n5H.js","./SystemPage-DYBocGi2.css","./SettingsPage-Bagp2mPP.js","./SettingsPage-DaB8PeRL.css"])))=>i.map(i=>d[i]);
+import{r as m,c as w,o as _,a as me,E as te,b as ne,u as fe,d as pe,e as O,f as K,g as _e,h as he,w as o,i as W,j as d,k as u,l as C,F as Y,m as z,n as ye,p as ge,q as be,s as ve,t as we,v as Ee,x as ke,y as Pe,z as Re,A as X,B as L,C as J,S as Te,D as V,G as Se,H as Oe,I as Le,J as xe,K as Ae}from"./vendor-BczUEOE_.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const l of s)if(l.type==="childList")for(const i of l.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const l={};return s.integrity&&(l.integrity=s.integrity),s.referrerPolicy&&(l.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?l.credentials="include":s.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function a(s){if(s.ep)return;s.ep=!0;const l=n(s);fetch(s.href,l)}})();const ae=(e,t)=>{const n=e.__vccOpts||e;for(const[a,s]of t)n[a]=s;return n},Ne={};function Be(e,t){const n=m("RouterView");return _(),w(n)}const Ce=ae(Ne,[["render",Be]]),De="modulepreload",Ve=function(e,t){return new URL(e,t).href},Q={},E=function(t,n,a){let s=Promise.resolve();if(n&&n.length>0){let T=function(p){return Promise.all(p.map(b=>Promise.resolve(b).then(P=>({status:"fulfilled",value:P}),P=>({status:"rejected",reason:P}))))};const i=document.getElementsByTagName("link"),f=document.querySelector("meta[property=csp-nonce]"),k=f?.nonce||f?.getAttribute("nonce");s=T(n.map(p=>{if(p=Ve(p,a),p in Q)return;Q[p]=!0;const b=p.endsWith(".css"),P=b?'[rel="stylesheet"]':"";if(a)for(let R=i.length-1;R>=0;R--){const v=i[R];if(v.href===p&&(!b||v.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${p}"]${P}`))return;const h=document.createElement("link");if(h.rel=b?"stylesheet":De,b||(h.as="script"),h.crossOrigin="",h.href=p,k&&h.setAttribute("nonce",k),document.head.appendChild(h),b)return new Promise((R,v)=>{h.addEventListener("load",R),h.addEventListener("error",()=>v(new Error(`Unable to preload CSS for ${p}`)))})}))}function l(i){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=i,window.dispatchEvent(f),!f.defaultPrevented)throw i}return s.then(i=>{for(const f of i||[])f.status==="rejected"&&l(f.reason);return t().catch(l)})};let Z="",ee=0;const Ie=new Set([408,425,429,500,502,503,504]),Me=1,$e=300;function x(e,t,n=1500){const a=Date.now();e===Z&&a-ee=Me)return!1;const a=String(e?.code||"");if(a==="ECONNABORTED"||a==="ERR_NETWORK")return!0;const s=Number(e?.response?.status||0);return Ie.has(s)}function je(e){return new Promise(t=>{window.setTimeout(t,Math.max(0,Number(e||0)))})}async function Ge(e,t){const n=e?.config||{},a=Number(n.__retry_count||0);n.__retry_count=a+1;const s=$e*(a+1);return await je(s),t.request(n)}const y=me.create({baseURL:"/yuyx/api",timeout:3e4,withCredentials:!0});let A=null;async function He(){return A||(A=te.prompt("请输入管理员密码进行二次确认","安全确认",{inputType:"password",inputPlaceholder:"管理员密码",confirmButtonText:"确认",cancelButtonText:"取消",inputValidator:e=>!!String(e||"").trim(),inputErrorMessage:"密码不能为空"}).then(async e=>{const t=String(e.value||"").trim();await y.post("/admin/reauth",{password:t}),ne.success("已通过安全确认")}).finally(()=>{A=null}),A)}y.interceptors.request.use(e=>{const t=String(e?.method||"GET").toUpperCase();if(!["GET","HEAD","OPTIONS"].includes(t)){const n=Fe("csrf_token");n&&(e.headers=e.headers||{},e.headers["X-CSRF-Token"]=n)}return e});y.interceptors.response.use(e=>e,async e=>{const t=e?.response?.status,n=e?.response?.data,a=n?.error||n?.message||e?.message||"请求失败";if(n?.code==="reauth_required"&&e?.config&&!e.config.__reauth_retry)try{return e.config.__reauth_retry=!0,await He(),y.request(e.config)}catch{return Promise.reject(e)}return qe(e)?Ge(e,y):(t===401?(x("401",a,3e3),(window.location?.pathname||"").startsWith("/yuyx")||(window.location.href="/yuyx")):t===403?x("403",a,5e3):t?x(`http:${t}:${a}`,a):e?.code==="ECONNABORTED"?x("timeout","请求超时",3e3):x(`net:${a}`,a,3e3),Promise.reject(e))});async function gt(e=""){const{data:t}=await y.get("/feedbacks",{params:e?{status:e}:{}});return t}async function Ke(){const{data:e}=await y.get("/feedbacks",{params:{limit:1,offset:0}});return e?.stats}async function bt(e,t){const{data:n}=await y.post(`/feedbacks/${e}/reply`,{reply:t});return n}async function vt(e){const{data:t}=await y.post(`/feedbacks/${e}/close`);return t}async function wt(e){const{data:t}=await y.delete(`/feedbacks/${e}`);return t}async function We(){const{data:e}=await y.get("/stats");return e}const Ye={class:"menu-label"},ze={key:1,class:"menu-label"},Xe={class:"header-left"},Je={class:"header-right"},Qe={class:"admin-name"},Ze={class:"main-shell"},et={class:"menu-label"},tt={key:1,class:"menu-label"},nt=6e4,at=18e4,st={__name:"AdminLayout",setup(e){const t=fe(),n=pe(),a=O({}),s=K(()=>a.value?.admin_username||"");async function l(){try{a.value=await We()}finally{}}const i=O(!1),f=O(0);let k=null;async function T(g=null){if(g&&typeof g=="object"){Object.prototype.hasOwnProperty.call(g,"pendingFeedbacks")&&(f.value=Number(g.pendingFeedbacks||0));return}if(!i.value){i.value=!0;try{const r=await Ke();f.value=Number(r?.pending||0)}finally{i.value=!1}}}function p(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function b(){return p()?at:nt}function P(){k&&(window.clearTimeout(k),k=null)}function h(){P(),k=window.setTimeout(async()=>{k=null,await T().catch(()=>{}),h()},b())}function R(){h()}V("refreshStats",l),V("adminStats",a),V("refreshNavBadges",T);const v=O(!1),S=O(!1);let N;function D(){v.value=!!N?.matches,v.value||(S.value=!1)}_e(async()=>{N=window.matchMedia("(max-width: 768px)"),N.addEventListener?.("change",D),D(),await l(),await T(),h(),window.addEventListener("visibilitychange",R)}),he(()=>{N?.removeEventListener?.("change",D),P(),window.removeEventListener("visibilitychange",R)});const I=[{path:"/reports",label:"报表",icon:ye},{path:"/users",label:"用户",icon:ge},{path:"/feedbacks",label:"反馈",icon:be,badgeKey:"feedbacks"},{path:"/logs",label:"任务日志",icon:ve},{path:"/announcements",label:"公告",icon:we},{path:"/email",label:"邮件",icon:Ee},{path:"/security",label:"安全防护",icon:ke},{path:"/system",label:"系统配置",icon:Pe},{path:"/settings",label:"设置",icon:Re}],M=K(()=>t.path);function B(g){return g?.badgeKey&&g.badgeKey==="feedbacks"?Number(f.value||0):0}async function se(){try{await te.confirm("确定退出管理员登录吗?","退出登录",{confirmButtonText:"退出",cancelButtonText:"取消",type:"warning"})}catch{return}try{await y.post("/logout")}finally{window.location.href="/yuyx"}}async function $(g){await n.push(g),S.value=!1}return(g,r)=>{const F=m("el-icon"),U=m("el-badge"),q=m("el-menu-item"),j=m("el-menu"),oe=m("el-aside"),G=m("el-button"),re=m("el-header"),ce=m("RouterView"),le=m("el-skeleton"),ie=m("el-card"),ue=m("el-main"),H=m("el-container"),de=m("el-drawer");return _(),w(H,{class:"layout-root"},{default:o(()=>[v.value?W("",!0):(_(),w(oe,{key:0,width:"220px",class:"layout-aside"},{default:o(()=>[r[2]||(r[2]=u("div",{class:"brand"},[u("div",{class:"brand-title"},"后台管理"),u("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),d(j,{"default-active":M.value,class:"aside-menu",router:"",onSelect:$},{default:o(()=>[(_(),C(Y,null,z(I,c=>d(q,{key:c.path,index:c.path},{default:o(()=>[d(F,null,{default:o(()=>[(_(),w(X(c.icon)))]),_:2},1024),B(c)>0?(_(),w(U,{key:0,value:B(c),max:99,class:"menu-badge"},{default:o(()=>[u("span",Ye,L(c.label),1)]),_:2},1032,["value"])):(_(),C("span",ze,L(c.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1})),d(H,null,{default:o(()=>[d(re,{class:"layout-header"},{default:o(()=>[u("div",Xe,[v.value?(_(),w(G,{key:0,text:"",class:"header-menu-btn",onClick:r[0]||(r[0]=c=>S.value=!0)},{default:o(()=>[...r[3]||(r[3]=[J(" 菜单 ",-1)])]),_:1})):W("",!0),r[4]||(r[4]=u("div",{class:"header-title"},"后台管理系统",-1))]),u("div",Je,[u("div",Qe,[r[5]||(r[5]=u("span",{class:"app-muted"},"管理员",-1)),u("strong",null,L(s.value||"-"),1)]),d(G,{type:"primary",plain:"",class:"logout-btn",onClick:se},{default:o(()=>[...r[6]||(r[6]=[J("退出",-1)])]),_:1})])]),_:1}),d(ue,{class:"layout-main"},{default:o(()=>[u("div",Ze,[(_(),w(Te,null,{default:o(()=>[d(ce)]),fallback:o(()=>[d(ie,{shadow:"never","body-style":{padding:"16px"},class:"fallback-card"},{default:o(()=>[d(le,{rows:5,animated:""})]),_:1})]),_:1}))])]),_:1})]),_:1}),d(de,{modelValue:S.value,"onUpdate:modelValue":r[1]||(r[1]=c=>S.value=c),size:"min(82vw, 280px)",direction:"ltr","with-header":!1},{default:o(()=>[r[7]||(r[7]=u("div",{class:"drawer-brand"},[u("div",{class:"brand-title"},"后台管理"),u("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),d(j,{"default-active":M.value,class:"aside-menu",router:"",onSelect:$},{default:o(()=>[(_(),C(Y,null,z(I,c=>d(q,{key:c.path,index:c.path},{default:o(()=>[d(F,null,{default:o(()=>[(_(),w(X(c.icon)))]),_:2},1024),B(c)>0?(_(),w(U,{key:0,value:B(c),max:99,class:"menu-badge"},{default:o(()=>[u("span",et,L(c.label),1)]),_:2},1032,["value"])):(_(),C("span",tt,L(c.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1},8,["modelValue"])]),_:1})}}},ot=ae(st,[["__scopeId","data-v-ce83ce8c"]]),rt=()=>E(()=>import("./ReportPage-CVnILNWh.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8]),import.meta.url),ct=()=>E(()=>import("./UsersPage-CJwQe-N8.js"),__vite__mapDeps([9,10,1,2,11]),import.meta.url),lt=()=>E(()=>import("./FeedbacksPage-Dg3BwCE1.js"),__vite__mapDeps([12,6,1,2,7,13]),import.meta.url),it=()=>E(()=>import("./LogsPage-B80ridTr.js"),__vite__mapDeps([14,10,4,1,2,15]),import.meta.url),ut=()=>E(()=>import("./AnnouncementsPage-B1ijXegw.js"),__vite__mapDeps([16,1,2,17]),import.meta.url),dt=()=>E(()=>import("./EmailPage-BA_Nuk9g.js"),__vite__mapDeps([18,3,6,1,2,7,19]),import.meta.url),mt=()=>E(()=>import("./SecurityPage-tMcfFXjg.js"),__vite__mapDeps([20,6,1,2,7,21]),import.meta.url),ft=()=>E(()=>import("./SystemPage-8v_X5n5H.js"),__vite__mapDeps([22,5,1,2,23]),import.meta.url),pt=()=>E(()=>import("./SettingsPage-Bagp2mPP.js"),__vite__mapDeps([24,1,2,25]),import.meta.url),_t=[{path:"/",component:ot,children:[{path:"",redirect:"/reports"},{path:"/pending",redirect:"/reports"},{path:"/stats",redirect:"/reports"},{path:"/reports",name:"reports",component:rt},{path:"/users",name:"users",component:ct},{path:"/feedbacks",name:"feedbacks",component:lt},{path:"/logs",name:"logs",component:it},{path:"/announcements",name:"announcements",component:ut},{path:"/email",name:"email",component:dt},{path:"/security",name:"security",component:mt},{path:"/system",name:"system",component:ft},{path:"/settings",name:"settings",component:pt}]}],ht=Se({history:Oe(),routes:_t});Le(Ce).use(ht).use(xe,{locale:Ae}).mount("#app");export{ae as _,y as a,gt as b,vt as c,wt as d,Ke as f,bt as r};
diff --git a/static/admin/assets/system-BqUnjCUC.js b/static/admin/assets/system-COpUDNdl.js
similarity index 68%
rename from static/admin/assets/system-BqUnjCUC.js
rename to static/admin/assets/system-COpUDNdl.js
index b37e04c..5bf0f49 100644
--- a/static/admin/assets/system-BqUnjCUC.js
+++ b/static/admin/assets/system-COpUDNdl.js
@@ -1 +1 @@
-import{a}from"./index-C51y9xCM.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-z8QOtKVT.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};
diff --git a/static/admin/assets/tasks-DW-oE78v.js b/static/admin/assets/tasks-Bprl-b9c.js
similarity index 90%
rename from static/admin/assets/tasks-DW-oE78v.js
rename to static/admin/assets/tasks-Bprl-b9c.js
index fc6ce5d..dae914e 100644
--- a/static/admin/assets/tasks-DW-oE78v.js
+++ b/static/admin/assets/tasks-Bprl-b9c.js
@@ -1 +1 @@
-import{a}from"./index-C51y9xCM.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 r(){const{data:t}=await a.get("/request_metrics");return t}async function o(){const{data:t}=await a.get("/slow_sql_metrics");return t}async function i(){const{data:t}=await a.get("/task/stats");return t}async function u(){const{data:t}=await a.get("/task/running");return t}async function f(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function g(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{u as a,c as b,e as c,r as d,o as e,i as f,f as g,g as h};
+import{a}from"./index-z8QOtKVT.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 r(){const{data:t}=await a.get("/request_metrics");return t}async function o(){const{data:t}=await a.get("/slow_sql_metrics");return t}async function i(){const{data:t}=await a.get("/task/stats");return t}async function u(){const{data:t}=await a.get("/task/running");return t}async function f(t){const{data:s}=await a.get("/task/logs",{params:t});return s}async function g(t){const{data:s}=await a.post("/task/logs/clear",{days:t});return s}export{u as a,c as b,e as c,r as d,o as e,i as f,f as g,g as h};
diff --git a/static/admin/assets/users-LqVgYOIa.js b/static/admin/assets/users-oERXj-b9.js
similarity index 90%
rename from static/admin/assets/users-LqVgYOIa.js
rename to static/admin/assets/users-oERXj-b9.js
index 49b211e..9c15e1d 100644
--- a/static/admin/assets/users-LqVgYOIa.js
+++ b/static/admin/assets/users-oERXj-b9.js
@@ -1 +1 @@
-import{a as t}from"./index-C51y9xCM.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-z8QOtKVT.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};
diff --git a/static/admin/index.html b/static/admin/index.html
index 215cf5c..39bf4f1 100644
--- a/static/admin/index.html
+++ b/static/admin/index.html
@@ -5,7 +5,7 @@
后台管理 - 知识管理平台
-
+