perf(stability): add request metrics and resilient API retries

This commit is contained in:
2026-02-07 11:58:21 +08:00
parent 04b94d7fb2
commit a50294933b
38 changed files with 447 additions and 97 deletions

View File

@@ -4,6 +4,10 @@ import { ElMessage, ElMessageBox } from 'element-plus'
let lastToastKey = ''
let lastToastAt = 0
const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])
const MAX_RETRY_COUNT = 1
const RETRY_BASE_DELAY_MS = 300
function toastErrorOnce(key, message, minIntervalMs = 1500) {
const now = Date.now()
if (key === lastToastKey && now - lastToastAt < minIntervalMs) return
@@ -18,6 +22,41 @@ function getCookie(name) {
return match ? decodeURIComponent(match[1]) : ''
}
function isIdempotentMethod(method) {
return ['GET', 'HEAD', 'OPTIONS'].includes(String(method || 'GET').toUpperCase())
}
function shouldRetryRequest(error) {
const config = error?.config
if (!config || config.__no_retry) return false
if (!isIdempotentMethod(config.method)) return false
const retried = Number(config.__retry_count || 0)
if (retried >= MAX_RETRY_COUNT) return false
const code = String(error?.code || '')
if (code === 'ECONNABORTED' || code === 'ERR_NETWORK') return true
const status = Number(error?.response?.status || 0)
return RETRYABLE_STATUS.has(status)
}
function delay(ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, Math.max(0, Number(ms || 0)))
})
}
async function retryRequestOnce(error, client) {
const config = error?.config || {}
const retried = Number(config.__retry_count || 0)
config.__retry_count = retried + 1
const backoffMs = RETRY_BASE_DELAY_MS * (retried + 1)
await delay(backoffMs)
return client.request(config)
}
export const api = axios.create({
baseURL: '/yuyx/api',
timeout: 30_000,
@@ -76,6 +115,10 @@ api.interceptors.response.use(
}
}
if (shouldRetryRequest(error)) {
return retryRequestOnce(error, api)
}
if (status === 401) {
toastErrorOnce('401', message || '登录已过期,请重新登录', 3000)
const pathname = window.location?.pathname || ''

View File

@@ -34,7 +34,11 @@ async function refreshStats() {
const loadingBadges = ref(false)
const pendingFeedbackCount = ref(0)
let badgeTimer
const BADGE_POLL_ACTIVE_MS = 60_000
const BADGE_POLL_HIDDEN_MS = 180_000
let badgeTimer = null
async function refreshNavBadges(partial = null) {
if (partial && typeof partial === 'object') {
@@ -55,6 +59,34 @@ async function refreshNavBadges(partial = null) {
}
}
function isPageHidden() {
if (typeof document === 'undefined') return false
return document.visibilityState === 'hidden'
}
function currentBadgePollDelay() {
return isPageHidden() ? BADGE_POLL_HIDDEN_MS : BADGE_POLL_ACTIVE_MS
}
function stopBadgePolling() {
if (!badgeTimer) return
window.clearTimeout(badgeTimer)
badgeTimer = null
}
function scheduleBadgePolling() {
stopBadgePolling()
badgeTimer = window.setTimeout(async () => {
badgeTimer = null
await refreshNavBadges().catch(() => {})
scheduleBadgePolling()
}, currentBadgePollDelay())
}
function onVisibilityChange() {
scheduleBadgePolling()
}
provide('refreshStats', refreshStats)
provide('adminStats', stats)
provide('refreshNavBadges', refreshNavBadges)
@@ -75,12 +107,14 @@ onMounted(async () => {
await refreshStats()
await refreshNavBadges()
badgeTimer = window.setInterval(refreshNavBadges, 60_000)
scheduleBadgePolling()
window.addEventListener('visibilitychange', onVisibilityChange)
})
onBeforeUnmount(() => {
mediaQuery?.removeEventListener?.('change', syncIsMobile)
window.clearInterval(badgeTimer)
stopBadgePolling()
window.removeEventListener('visibilitychange', onVisibilityChange)
})
const menuItems = [

View File

@@ -4,6 +4,10 @@ import { ElMessage } from 'element-plus'
let lastToastKey = ''
let lastToastAt = 0
const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])
const MAX_RETRY_COUNT = 1
const RETRY_BASE_DELAY_MS = 300
function toastErrorOnce(key, message, minIntervalMs = 1500) {
const now = Date.now()
if (key === lastToastKey && now - lastToastAt < minIntervalMs) return
@@ -18,6 +22,41 @@ function getCookie(name) {
return match ? decodeURIComponent(match[1]) : ''
}
function isIdempotentMethod(method) {
return ['GET', 'HEAD', 'OPTIONS'].includes(String(method || 'GET').toUpperCase())
}
function shouldRetryRequest(error) {
const config = error?.config
if (!config || config.__no_retry) return false
if (!isIdempotentMethod(config.method)) return false
const retried = Number(config.__retry_count || 0)
if (retried >= MAX_RETRY_COUNT) return false
const code = String(error?.code || '')
if (code === 'ECONNABORTED' || code === 'ERR_NETWORK') return true
const status = Number(error?.response?.status || 0)
return RETRYABLE_STATUS.has(status)
}
function delay(ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, Math.max(0, Number(ms || 0)))
})
}
async function retryRequestOnce(error, client) {
const config = error?.config || {}
const retried = Number(config.__retry_count || 0)
config.__retry_count = retried + 1
const backoffMs = RETRY_BASE_DELAY_MS * (retried + 1)
await delay(backoffMs)
return client.request(config)
}
export const publicApi = axios.create({
baseURL: '/api',
timeout: 30_000,
@@ -39,6 +78,10 @@ publicApi.interceptors.request.use((config) => {
publicApi.interceptors.response.use(
(response) => response,
(error) => {
if (shouldRetryRequest(error)) {
return retryRequestOnce(error, publicApi)
}
const status = error?.response?.status
const payload = error?.response?.data
const message = payload?.error || payload?.message || error?.message || '请求失败'

59
app.py
View File

@@ -17,8 +17,9 @@ import os
import signal
import sys
import threading
import time
from flask import Flask, jsonify, redirect, request, send_from_directory, session, url_for
from flask import Flask, g, jsonify, redirect, request, send_from_directory, session, url_for
from flask_login import LoginManager, current_user
from flask_socketio import SocketIO
@@ -35,6 +36,7 @@ from routes import register_blueprints
from security import init_security_middleware
from services.checkpoints import init_checkpoint_manager
from services.maintenance import start_cleanup_scheduler, start_kdocs_monitor
from services.request_metrics import record_request_metric
from services.models import User
from services.runtime import init_runtime
from services.scheduler import scheduled_task_worker
@@ -98,6 +100,20 @@ init_logging(log_level=config.LOG_LEVEL, log_file=config.LOG_FILE)
logger = get_logger("app")
init_runtime(socketio=socketio, logger=logger)
_API_DIAGNOSTIC_LOG = str(os.environ.get("API_DIAGNOSTIC_LOG", "0")).strip().lower() in {
"1",
"true",
"yes",
"on",
}
_API_DIAGNOSTIC_SLOW_MS = max(0.0, float(os.environ.get("API_DIAGNOSTIC_SLOW_MS", "0") or 0.0))
def _is_api_or_health_path(path: str) -> bool:
raw = str(path or "")
return raw.startswith("/api/") or raw.startswith("/yuyx/api/") or raw == "/health"
# 初始化安全中间件(需在其他中间件/Blueprint 之前注册)
init_security_middleware(app)
@@ -131,6 +147,11 @@ def unauthorized():
return redirect(url_for("pages.login_page", next=request.url))
@app.before_request
def track_request_start_time():
g.request_start_perf = time.perf_counter()
@app.before_request
def enforce_csrf_protection():
if request.method in {"GET", "HEAD", "OPTIONS"}:
@@ -148,10 +169,40 @@ def enforce_csrf_protection():
return jsonify({"error": "CSRF token missing or invalid"}), 403
def _record_request_metric_after_response(response) -> None:
try:
started = float(getattr(g, "request_start_perf", 0.0) or 0.0)
if started <= 0:
return
duration_ms = max(0.0, (time.perf_counter() - started) * 1000.0)
path = request.path or "/"
method = request.method or "GET"
status_code = int(getattr(response, "status_code", 0) or 0)
is_api = _is_api_or_health_path(path)
record_request_metric(
path=path,
method=method,
status_code=status_code,
duration_ms=duration_ms,
is_api=is_api,
)
if _API_DIAGNOSTIC_LOG and is_api:
is_slow = _API_DIAGNOSTIC_SLOW_MS > 0 and duration_ms >= _API_DIAGNOSTIC_SLOW_MS
is_server_error = status_code >= 500
if is_slow or is_server_error:
logger.warning(
f"[API-DIAG] {method} {path} -> {status_code} ({duration_ms:.1f}ms)"
)
except Exception:
pass
@app.after_request
def ensure_csrf_cookie(response):
if request.path.startswith("/static/"):
return response
if not request.path.startswith("/static/"):
token = session.get("csrf_token")
if not token:
token = generate_csrf_token()
@@ -162,6 +213,8 @@ def ensure_csrf_cookie(response):
secure=bool(config.SESSION_COOKIE_SECURE),
samesite=config.SESSION_COOKIE_SAMESITE,
)
_record_request_metric_after_response(response)
return response

View File

@@ -9,6 +9,7 @@ from flask import Blueprint, jsonify
import database
import db_pool
from services.request_metrics import get_request_metrics_snapshot
from services.time_utils import get_beijing_now
health_bp = Blueprint("health", __name__)
@@ -57,6 +58,11 @@ def _build_runtime_metrics() -> dict:
except Exception:
pass
try:
metrics["requests"] = get_request_metrics_snapshot()
except Exception:
pass
return metrics

171
services/request_metrics.py Normal file
View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
请求级运行指标(轻量内存版)
- 记录请求总量、状态分布、耗时
- 记录慢请求样本(环形队列)
- 输出健康检查可读快照
"""
from __future__ import annotations
import os
import threading
import time
from collections import deque
from typing import Dict
_SLOW_REQUEST_MS = max(0.0, float(os.environ.get("REQUEST_METRICS_SLOW_MS", "1200") or 1200))
_PATH_STATS_LIMIT = max(20, int(os.environ.get("REQUEST_METRICS_PATH_LIMIT", "120") or 120))
_RECENT_SLOW_LIMIT = max(10, int(os.environ.get("REQUEST_METRICS_RECENT_SLOW_LIMIT", "20") or 20))
_lock = threading.Lock()
_state = {
"start_ts": time.time(),
"last_request_ts": 0.0,
"total_requests": 0,
"api_requests": 0,
"error_requests": 0,
"slow_requests": 0,
"duration_total_ms": 0.0,
"max_duration_ms": 0.0,
"status_counts": {},
"path_stats": {},
"recent_slow": deque(maxlen=_RECENT_SLOW_LIMIT),
}
def _status_bucket(status_code: int) -> str:
code = int(status_code or 0)
if code <= 0:
return "unknown"
head = code // 100
if head in (1, 2, 3, 4, 5):
return f"{head}xx"
return str(code)
def _normalize_path(path: str) -> str:
text = str(path or "/")
if len(text) > 160:
return f"{text[:157]}..."
return text
def _prune_path_stats(path_stats: Dict[str, dict]) -> None:
if len(path_stats) < _PATH_STATS_LIMIT:
return
# 删除最不活跃的路径,避免无限增长
removable_key = None
removable_score = None
for key, item in path_stats.items():
count = int(item.get("count", 0) or 0)
max_ms = float(item.get("max_ms", 0.0) or 0.0)
score = (count, max_ms)
if removable_score is None or score < removable_score:
removable_key = key
removable_score = score
if removable_key:
path_stats.pop(removable_key, None)
def record_request_metric(*, path: str, method: str, status_code: int, duration_ms: float, is_api: bool = False) -> None:
duration = max(0.0, float(duration_ms or 0.0))
code = int(status_code or 0)
method_name = str(method or "GET").upper()
normalized_path = _normalize_path(path)
route_key = f"{method_name} {normalized_path}"
now = time.time()
with _lock:
_state["total_requests"] += 1
if is_api:
_state["api_requests"] += 1
if code >= 500:
_state["error_requests"] += 1
_state["last_request_ts"] = now
_state["duration_total_ms"] += duration
if duration > _state["max_duration_ms"]:
_state["max_duration_ms"] = duration
bucket = _status_bucket(code)
status_counts = _state["status_counts"]
status_counts[bucket] = int(status_counts.get(bucket, 0) or 0) + 1
path_stats = _state["path_stats"]
if route_key not in path_stats:
_prune_path_stats(path_stats)
path_stats[route_key] = {
"count": 0,
"total_ms": 0.0,
"max_ms": 0.0,
"status_5xx": 0,
}
item = path_stats[route_key]
item["count"] = int(item.get("count", 0) or 0) + 1
item["total_ms"] = float(item.get("total_ms", 0.0) or 0.0) + duration
if duration > float(item.get("max_ms", 0.0) or 0.0):
item["max_ms"] = duration
if code >= 500:
item["status_5xx"] = int(item.get("status_5xx", 0) or 0) + 1
if _SLOW_REQUEST_MS > 0 and duration >= _SLOW_REQUEST_MS:
_state["slow_requests"] += 1
_state["recent_slow"].append(
{
"path": normalized_path,
"method": method_name,
"status": code,
"duration_ms": round(duration, 2),
"time": int(now),
}
)
def get_request_metrics_snapshot() -> dict:
with _lock:
total_requests = int(_state["total_requests"])
duration_total_ms = float(_state["duration_total_ms"])
avg_duration_ms = round((duration_total_ms / total_requests), 2) if total_requests > 0 else 0.0
path_rows = []
for key, item in _state["path_stats"].items():
count = int(item.get("count", 0) or 0)
total_ms = float(item.get("total_ms", 0.0) or 0.0)
avg_ms = round((total_ms / count), 2) if count > 0 else 0.0
path_rows.append(
{
"path": key,
"count": count,
"avg_ms": avg_ms,
"max_ms": round(float(item.get("max_ms", 0.0) or 0.0), 2),
"status_5xx": int(item.get("status_5xx", 0) or 0),
}
)
top_paths = sorted(
path_rows,
key=lambda row: (float(row.get("max_ms", 0.0)), float(row.get("avg_ms", 0.0)), int(row.get("count", 0))),
reverse=True,
)[:8]
return {
"since_ts": int(_state["start_ts"]),
"uptime_seconds": max(0, int(time.time() - float(_state["start_ts"]))),
"last_request_ts": int(_state["last_request_ts"] or 0),
"total_requests": total_requests,
"api_requests": int(_state["api_requests"]),
"error_requests": int(_state["error_requests"]),
"slow_requests": int(_state["slow_requests"]),
"avg_duration_ms": avg_duration_ms,
"max_duration_ms": round(float(_state["max_duration_ms"]), 2),
"status_counts": dict(_state["status_counts"]),
"top_paths": top_paths,
"recent_slow": list(_state["recent_slow"]),
"slow_threshold_ms": _SLOW_REQUEST_MS,
}

View File

@@ -1,6 +1,6 @@
{
"_MetricGrid-Cbhb9OGV.js": {
"file": "assets/MetricGrid-Cbhb9OGV.js",
"_MetricGrid-D-x_tNsK.js": {
"file": "assets/MetricGrid-D-x_tNsK.js",
"name": "MetricGrid",
"imports": [
"index.html",
@@ -14,29 +14,29 @@
"file": "assets/MetricGrid-yP_dkP6X.css",
"src": "_MetricGrid-yP_dkP6X.css"
},
"_email-CqsrP_Ts.js": {
"file": "assets/email-CqsrP_Ts.js",
"_email-BYiWDIoy.js": {
"file": "assets/email-BYiWDIoy.js",
"name": "email",
"imports": [
"index.html"
]
},
"_system-In7Gh8x7.js": {
"file": "assets/system-In7Gh8x7.js",
"_system-Bvj77zeB.js": {
"file": "assets/system-Bvj77zeB.js",
"name": "system",
"imports": [
"index.html"
]
},
"_tasks-DZnsc1fC.js": {
"file": "assets/tasks-DZnsc1fC.js",
"_tasks-BZsqSMnk.js": {
"file": "assets/tasks-BZsqSMnk.js",
"name": "tasks",
"imports": [
"index.html"
]
},
"_users-CTuo5Ynz.js": {
"file": "assets/users-CTuo5Ynz.js",
"_users-Kctz2ziD.js": {
"file": "assets/users-Kctz2ziD.js",
"name": "users",
"imports": [
"index.html"
@@ -74,7 +74,7 @@
]
},
"index.html": {
"file": "assets/index-D8t2quK2.js",
"file": "assets/index-iyjFO6XY.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -96,11 +96,11 @@
"src/pages/SettingsPage.vue"
],
"css": [
"assets/index-Tk47UJAg.css"
"assets/index-a3a11Ghn.css"
]
},
"src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-CaSWhmIw.js",
"file": "assets/AnnouncementsPage-BZGpQqUL.js",
"name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true,
@@ -116,14 +116,14 @@
]
},
"src/pages/EmailPage.vue": {
"file": "assets/EmailPage-DlOkUKzK.js",
"file": "assets/EmailPage-CfIczE0i.js",
"name": "EmailPage",
"src": "src/pages/EmailPage.vue",
"isDynamicEntry": true,
"imports": [
"_email-CqsrP_Ts.js",
"_email-BYiWDIoy.js",
"index.html",
"_MetricGrid-Cbhb9OGV.js",
"_MetricGrid-D-x_tNsK.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -134,13 +134,13 @@
]
},
"src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-B8rr6rHD.js",
"file": "assets/FeedbacksPage-yGzwD6JV.js",
"name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_MetricGrid-Cbhb9OGV.js",
"_MetricGrid-D-x_tNsK.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -151,13 +151,13 @@
]
},
"src/pages/LogsPage.vue": {
"file": "assets/LogsPage-WfvkzS-6.js",
"file": "assets/LogsPage-WmQFhJZO.js",
"name": "LogsPage",
"src": "src/pages/LogsPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-CTuo5Ynz.js",
"_tasks-DZnsc1fC.js",
"_users-Kctz2ziD.js",
"_tasks-BZsqSMnk.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
@@ -169,17 +169,17 @@
]
},
"src/pages/ReportPage.vue": {
"file": "assets/ReportPage-GPt5J1Db.js",
"file": "assets/ReportPage-BkIhdzJa.js",
"name": "ReportPage",
"src": "src/pages/ReportPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-element-CJoVtPsD.js",
"index.html",
"_email-CqsrP_Ts.js",
"_tasks-DZnsc1fC.js",
"_system-In7Gh8x7.js",
"_MetricGrid-Cbhb9OGV.js",
"_email-BYiWDIoy.js",
"_tasks-BZsqSMnk.js",
"_system-Bvj77zeB.js",
"_MetricGrid-D-x_tNsK.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
"_vendor-axios-B9ygI19o.js"
@@ -189,13 +189,13 @@
]
},
"src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-BWFFA3z9.js",
"file": "assets/SecurityPage-Ay9lQdJs.js",
"name": "SecurityPage",
"src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_MetricGrid-Cbhb9OGV.js",
"_MetricGrid-D-x_tNsK.js",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
"_vendor-vue-CWkOjFoA.js",
@@ -206,7 +206,7 @@
]
},
"src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-3cNfdgGx.js",
"file": "assets/SettingsPage-D1k6_4Nn.js",
"name": "SettingsPage",
"src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true,
@@ -222,12 +222,12 @@
]
},
"src/pages/SystemPage.vue": {
"file": "assets/SystemPage-CJ2F6_EU.js",
"file": "assets/SystemPage-wAMFferr.js",
"name": "SystemPage",
"src": "src/pages/SystemPage.vue",
"isDynamicEntry": true,
"imports": [
"_system-In7Gh8x7.js",
"_system-Bvj77zeB.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",
@@ -239,12 +239,12 @@
]
},
"src/pages/UsersPage.vue": {
"file": "assets/UsersPage-KZ5WSZrO.js",
"file": "assets/UsersPage-B5MDfX7T.js",
"name": "UsersPage",
"src": "src/pages/UsersPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-CTuo5Ynz.js",
"_users-Kctz2ziD.js",
"index.html",
"_vendor-element-CJoVtPsD.js",
"_vendor-sLgkZK1v.js",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{_}from"./index-D8t2quK2.js";import{aW as c,z as s,A as t,R as r,ak as u,E as p,B as o,N as l,S as y,L as h,K as i,O as k,Q as n,P as v,D as f}from"./vendor-sLgkZK1v.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},N={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},C={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,z)=>{const d=c("el-icon"),m=c("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,n(e?.label||"-"),1)]),o("div",N,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},S=_(C,[["__scopeId","data-v-00e217d4"]]);export{S as M};
import{_}from"./index-iyjFO6XY.js";import{aW as c,z as s,A as t,R as r,ak as u,E as p,B as o,N as l,S as y,L as h,K as i,O as k,Q as n,P as v,D as f}from"./vendor-sLgkZK1v.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},B={class:"metric-label"},N={class:"metric-value"},g={key:0,class:"metric-hint app-muted"},C={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,z)=>{const d=c("el-icon"),m=c("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,n(e?.label||"-"),1)]),o("div",N,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[v(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",g,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},S=_(C,[["__scopeId","data-v-00e217d4"]]);export{S as M};

View File

@@ -1 +1 @@
import{a as m,_ as h}from"./index-D8t2quK2.js";import{f as u,E as x}from"./vendor-element-CJoVtPsD.js";import{r as p,aW as i,z as T,A as P,B as r,S as a,L as o,P as b}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function S(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function A(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function C(){const{data:l}=await m.post("/logout");return l}const E={class:"page-stack"},U={__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 C()}catch{}finally{window.location.href="/yuyx"}}async function B(){const t=s.value.trim();if(!t){u.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),u.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function V(){const t=d.value;if(!t){u.error("请输入新密码");return}const e=k(t);if(!e.ok){u.error(e.message);return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await A(t),u.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=i("el-input"),v=i("el-form-item"),w=i("el-form"),y=i("el-button"),_=i("el-card");return P(),T("div",E,[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(w,{"label-width":"120px"},{default:o(()=>[a(v,{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:B},{default:o(()=>[...e[2]||(e[2]=[b("保存用户名",-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(w,{"label-width":"120px"},{default:o(()=>[a(v,{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:V},{default:o(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},W=h(U,[["__scopeId","data-v-83d3840a"]]);export{W as default};
import{a as m,_ as h}from"./index-iyjFO6XY.js";import{f as u,E as x}from"./vendor-element-CJoVtPsD.js";import{r as p,aW as i,z as T,A as P,B as r,S as a,L as o,P as b}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function S(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function A(l){const{data:s}=await m.put("/admin/password",{new_password:l});return s}async function C(){const{data:l}=await m.post("/logout");return l}const E={class:"page-stack"},U={__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 C()}catch{}finally{window.location.href="/yuyx"}}async function B(){const t=s.value.trim();if(!t){u.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await S(t),u.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function V(){const t=d.value;if(!t){u.error("请输入新密码");return}const e=k(t);if(!e.ok){u.error(e.message);return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await A(t),u.success("密码修改成功,请重新登录"),d.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}return(t,e)=>{const g=i("el-input"),v=i("el-form-item"),w=i("el-form"),y=i("el-button"),_=i("el-card");return P(),T("div",E,[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(w,{"label-width":"120px"},{default:o(()=>[a(v,{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:B},{default:o(()=>[...e[2]||(e[2]=[b("保存用户名",-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(w,{"label-width":"120px"},{default:o(()=>[a(v,{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:V},{default:o(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码至少8位且包含字母与数字。",-1))]),_:1})])}}},W=h(U,[["__scopeId","data-v-83d3840a"]]);export{W as default};

View File

@@ -1,4 +1,4 @@
import{f as Ve,u as Y}from"./system-In7Gh8x7.js";import{a as P,_ as ge}from"./index-D8t2quK2.js";import{E as ne,f as m}from"./vendor-element-CJoVtPsD.js";import{r as n,c as ke,w as xe,a1 as be,v as we,aW as v,b7 as Ue,M as Ce,A as V,z as g,B as s,S as l,L as t,P as k,N as Z,Q as ee}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function le(r={}){const{data:c}=await P.get("/kdocs/status",{params:r});return c}async function Pe(r={}){const c={force:!0,...r},{data:x}=await P.post("/kdocs/qr",c);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:c}=await P.post("/proxy/config",r);return c}async function Se(r){const{data:c}=await P.post("/proxy/test",r);return c}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"},Qe={class:"status-inline app-muted"},$e={key:0},he={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},ze={class:"row-actions"},Fe={key:0,class:"help"},He={key:1,class:"help"},Oe={class:"kdocs-qr"},Ge=["src"],We={__name:"SystemPage",setup(r){const c=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(""),Q=n(""),$=n(0),h=n("A"),q=n("D"),M=n(0),R=n(0),z=n(!1),F=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),H=n(!1),U=n(!1),C=n(!1),O=n("");let G=null;const oe=ke(()=>H.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(){c.value=!0;try{const[a,e,i]=await Promise.all([Ve(),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||"",Q.value=a.kdocs_sheet_name||"",$.value=a.kdocs_sheet_index??0,h.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,z.value=(a.kdocs_admin_notify_enabled??0)===1,F.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{c.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(`确定更新并发配置吗?
import{f as Ve,u as Y}from"./system-Bvj77zeB.js";import{a as P,_ as ge}from"./index-iyjFO6XY.js";import{E as ne,f as m}from"./vendor-element-CJoVtPsD.js";import{r as n,c as ke,w as xe,a1 as be,v as we,aW as v,b7 as Ue,M as Ce,A as V,z as g,B as s,S as l,L as t,P as k,N as Z,Q as ee}from"./vendor-sLgkZK1v.js";import"./vendor-vue-CWkOjFoA.js";import"./vendor-axios-B9ygI19o.js";async function le(r={}){const{data:c}=await P.get("/kdocs/status",{params:r});return c}async function Pe(r={}){const c={force:!0,...r},{data:x}=await P.post("/kdocs/qr",c);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:c}=await P.post("/proxy/config",r);return c}async function Se(r){const{data:c}=await P.post("/proxy/test",r);return c}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"},Qe={class:"status-inline app-muted"},$e={key:0},he={key:1},qe={key:2},Me={class:"kdocs-inline"},Re={class:"kdocs-range"},ze={class:"row-actions"},Fe={key:0,class:"help"},He={key:1,class:"help"},Oe={class:"kdocs-qr"},Ge=["src"],We={__name:"SystemPage",setup(r){const c=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(""),Q=n(""),$=n(0),h=n("A"),q=n("D"),M=n(0),R=n(0),z=n(!1),F=n(""),p=n({}),b=n(!1),w=n(""),ae=n(!1),H=n(!1),U=n(!1),C=n(!1),O=n("");let G=null;const oe=ke(()=>H.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(){c.value=!0;try{const[a,e,i]=await Promise.all([Ve(),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||"",Q.value=a.kdocs_sheet_name||"",$.value=a.kdocs_sheet_index??0,h.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,z.value=(a.kdocs_admin_notify_enabled??0)===1,F.value=a.kdocs_admin_notify_email||"",p.value=i||{}}catch{}finally{c.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}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a as n}from"./index-D8t2quK2.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-iyjFO6XY.js";async function i(){const{data:a}=await n.get("/email/settings");return a}async function e(a){const{data:t}=await n.post("/email/settings",a);return t}async function c(){const{data:a}=await n.get("/email/stats");return a}async function o(a){const{data:t}=await n.get("/email/logs",{params:a});return t}async function l(a){const{data:t}=await n.post("/email/logs/cleanup",{days:a});return t}export{o as a,i as b,l as c,c as f,e as u};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a}from"./index-D8t2quK2.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-iyjFO6XY.js";async function o(){const{data:t}=await a.get("/system/config");return t}async function e(t){const{data:n}=await a.post("/system/config",t);return n}export{o as f,e as u};

View File

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

View File

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

View File

@@ -5,13 +5,13 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-D8t2quK2.js"></script>
<script type="module" crossorigin src="./assets/index-iyjFO6XY.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-sLgkZK1v.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-element-CJoVtPsD.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-vue-CWkOjFoA.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-axios-B9ygI19o.js">
<link rel="stylesheet" crossorigin href="./assets/vendor-element-C68yOrAy.css">
<link rel="stylesheet" crossorigin href="./assets/index-Tk47UJAg.css">
<link rel="stylesheet" crossorigin href="./assets/index-a3a11Ghn.css">
</head>
<body>
<div id="app"></div>

View File

@@ -1,13 +1,13 @@
{
"_accounts-DpRp0y7G.js": {
"file": "assets/accounts-DpRp0y7G.js",
"_accounts-DWKsGalm.js": {
"file": "assets/accounts-DWKsGalm.js",
"name": "accounts",
"imports": [
"index.html"
]
},
"_auth-CtTBVBJk.js": {
"file": "assets/auth-CtTBVBJk.js",
"_auth-3qca7BnL.js": {
"file": "assets/auth-3qca7BnL.js",
"name": "auth",
"imports": [
"index.html"
@@ -52,7 +52,7 @@
]
},
"index.html": {
"file": "assets/index-C4rfJ09l.js",
"file": "assets/index-DIRUAu3R.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -76,12 +76,12 @@
]
},
"src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-Cj2MTk-l.js",
"file": "assets/AccountsPage-BsKyQ62c.js",
"name": "AccountsPage",
"src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DpRp0y7G.js",
"_accounts-DWKsGalm.js",
"index.html",
"_vendor-socket-ciQGyZ7q.js",
"_vendor-element-D5SbqSD5.js",
@@ -94,14 +94,14 @@
]
},
"src/pages/LoginPage.vue": {
"file": "assets/LoginPage-B4Cm3mBQ.js",
"file": "assets/LoginPage-DjXmnzTj.js",
"name": "LoginPage",
"src": "src/pages/LoginPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-DR-vtVVc.js",
"_vendor-vue-l2lnRGj2.js",
"_auth-CtTBVBJk.js",
"_auth-3qca7BnL.js",
"index.html",
"_vendor-element-D5SbqSD5.js",
"_vendor-axios-B9ygI19o.js"
@@ -111,14 +111,14 @@
]
},
"src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-DDkEhw5Z.js",
"file": "assets/RegisterPage-Nvd-XHp4.js",
"name": "RegisterPage",
"src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-DR-vtVVc.js",
"_vendor-vue-l2lnRGj2.js",
"_auth-CtTBVBJk.js",
"_auth-3qca7BnL.js",
"index.html",
"_vendor-element-D5SbqSD5.js",
"_vendor-axios-B9ygI19o.js"
@@ -128,14 +128,14 @@
]
},
"src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage-DTBT5Kre.js",
"file": "assets/ResetPasswordPage-DluVIq7o.js",
"name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true,
"imports": [
"_vendor-DR-vtVVc.js",
"_vendor-vue-l2lnRGj2.js",
"_auth-CtTBVBJk.js",
"_auth-3qca7BnL.js",
"index.html",
"_vendor-element-D5SbqSD5.js",
"_vendor-axios-B9ygI19o.js"
@@ -145,12 +145,12 @@
]
},
"src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-DtRro62b.js",
"file": "assets/SchedulesPage-DglS5EqW.js",
"name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DpRp0y7G.js",
"_accounts-DWKsGalm.js",
"index.html",
"_vendor-element-D5SbqSD5.js",
"_vendor-DR-vtVVc.js",
@@ -162,7 +162,7 @@
]
},
"src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-BMPLt3Or.js",
"file": "assets/ScreenshotsPage-L5gmloAw.js",
"name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true,
@@ -178,7 +178,7 @@
]
},
"src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-CdTQeWQk.js",
"file": "assets/VerifyResultPage-CSJomZjC.js",
"name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true,

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
import{a1 as A,r as d,c as S,v as M,z as N,Q as l,J as o,aY as v,A as b,B as n,I as U,L as B,aD as P,O as z,N as E}from"./vendor-DR-vtVVc.js";import{p as H}from"./vendor-vue-l2lnRGj2.js";import{g as J,f as O,b as Q}from"./auth-CtTBVBJk.js";import{_ as Y,v as j}from"./index-C4rfJ09l.js";import{E as c}from"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const q={class:"auth-wrap"},F={class:"hint app-muted"},G={class:"captcha-row"},W=["src"],X={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=A({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),D=S(()=>f.value?"邮箱 *":"邮箱(可选)"),I=S(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await J();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function K(){try{const u=await O();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function L(){t.value="",_.value="",k.value=""}async function C(){L();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=j(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await Q({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function R(){T.push("/login")}return M(async()=>{await y(),await K()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),N("div",q,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),U(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):B("",!0),_.value?(b(),U(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):B("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:D.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",F,z(I.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",G,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),N("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,W)):B("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[E("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[E("注册",-1)])]),_:1},8,["loading"]),n("div",X,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:R},{default:o(()=>[...e[9]||(e[9]=[E("立即登录",-1)])]),_:1})])]),_:1})])}}},re=Y(Z,[["__scopeId","data-v-a9d7804f"]]);export{re as default};
import{a1 as A,r as d,c as S,v as M,z as N,Q as l,J as o,aY as v,A as b,B as n,I as U,L as B,aD as P,O as z,N as E}from"./vendor-DR-vtVVc.js";import{p as H}from"./vendor-vue-l2lnRGj2.js";import{g as J,f as O,b as Q}from"./auth-3qca7BnL.js";import{_ as Y,v as j}from"./index-DIRUAu3R.js";import{E as c}from"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const q={class:"auth-wrap"},F={class:"hint app-muted"},G={class:"captcha-row"},W=["src"],X={class:"actions"},Z={__name:"RegisterPage",setup($){const T=H(),a=A({username:"",password:"",confirm_password:"",email:"",captcha:""}),f=d(!1),w=d(""),h=d(""),V=d(!1),t=d(""),_=d(""),k=d(""),D=S(()=>f.value?"邮箱 *":"邮箱(可选)"),I=S(()=>f.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function y(){try{const u=await J();h.value=u?.session_id||"",w.value=u?.captcha_image||"",a.captcha=""}catch{h.value="",w.value=""}}async function K(){try{const u=await O();f.value=!!u?.register_verify_enabled}catch{f.value=!1}}function L(){t.value="",_.value="",k.value=""}async function C(){L();const u=a.username.trim(),e=a.password,g=a.confirm_password,s=a.email.trim(),i=a.captcha.trim();if(u.length<3){t.value="用户名至少3个字符",c.error(t.value);return}const p=j(e);if(!p.ok){t.value=p.message||"密码格式不正确",c.error(t.value);return}if(e!==g){t.value="两次输入的密码不一致",c.error(t.value);return}if(f.value&&!s){t.value="请填写邮箱地址用于账号验证",c.error(t.value);return}if(s&&!s.includes("@")){t.value="邮箱格式不正确",c.error(t.value);return}if(!i){t.value="请输入验证码",c.error(t.value);return}V.value=!0;try{const m=await Q({username:u,password:e,email:s,captcha_session:h.value,captcha:i});_.value=m?.message||"注册成功",k.value=m?.need_verify?"请检查您的邮箱(包括垃圾邮件文件夹)":"",c.success("注册成功"),a.username="",a.password="",a.confirm_password="",a.email="",a.captcha="",setTimeout(()=>{window.location.href="/login"},3e3)}catch(m){const x=m?.response?.data;t.value=x?.error||"注册失败",c.error(t.value),await y()}finally{V.value=!1}}function R(){T.push("/login")}return M(async()=>{await y(),await K()}),(u,e)=>{const g=v("el-alert"),s=v("el-input"),i=v("el-form-item"),p=v("el-button"),m=v("el-form"),x=v("el-card");return b(),N("div",q,[l(x,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:o(()=>[e[11]||(e[11]=n("div",{class:"brand"},[n("div",{class:"brand-title"},"知识管理平台"),n("div",{class:"brand-sub app-muted"},"用户注册")],-1)),t.value?(b(),U(g,{key:0,type:"error",closable:!1,title:t.value,"show-icon":"",class:"alert"},null,8,["title"])):B("",!0),_.value?(b(),U(g,{key:1,type:"success",closable:!1,title:_.value,description:k.value,"show-icon":"",class:"alert"},null,8,["title","description"])):B("",!0),l(m,{"label-position":"top"},{default:o(()=>[l(i,{label:"用户名 *"},{default:o(()=>[l(s,{modelValue:a.username,"onUpdate:modelValue":e[0]||(e[0]=r=>a.username=r),placeholder:"至少3个字符",autocomplete:"username"},null,8,["modelValue"]),e[5]||(e[5]=n("div",{class:"hint app-muted"},"至少3个字符",-1))]),_:1}),l(i,{label:"密码 *"},{default:o(()=>[l(s,{modelValue:a.password,"onUpdate:modelValue":e[1]||(e[1]=r=>a.password=r),type:"password","show-password":"",placeholder:"至少8位且包含字母和数字",autocomplete:"new-password"},null,8,["modelValue"]),e[6]||(e[6]=n("div",{class:"hint app-muted"},"至少8位且包含字母和数字",-1))]),_:1}),l(i,{label:"确认密码 *"},{default:o(()=>[l(s,{modelValue:a.confirm_password,"onUpdate:modelValue":e[2]||(e[2]=r=>a.confirm_password=r),type:"password","show-password":"",placeholder:"请再次输入密码",autocomplete:"new-password",onKeyup:P(C,["enter"])},null,8,["modelValue"])]),_:1}),l(i,{label:D.value},{default:o(()=>[l(s,{modelValue:a.email,"onUpdate:modelValue":e[3]||(e[3]=r=>a.email=r),placeholder:"name@example.com",autocomplete:"email"},null,8,["modelValue"]),n("div",F,z(I.value),1)]),_:1},8,["label"]),l(i,{label:"验证码 *"},{default:o(()=>[n("div",G,[l(s,{modelValue:a.captcha,"onUpdate:modelValue":e[4]||(e[4]=r=>a.captcha=r),placeholder:"请输入验证码",onKeyup:P(C,["enter"])},null,8,["modelValue"]),w.value?(b(),N("img",{key:0,class:"captcha-img",src:w.value,alt:"验证码",title:"点击刷新",onClick:y},null,8,W)):B("",!0),l(p,{onClick:y},{default:o(()=>[...e[7]||(e[7]=[E("刷新",-1)])]),_:1})])]),_:1})]),_:1}),l(p,{type:"primary",class:"submit-btn",loading:V.value,onClick:C},{default:o(()=>[...e[8]||(e[8]=[E("注册",-1)])]),_:1},8,["loading"]),n("div",X,[e[10]||(e[10]=n("span",{class:"app-muted"},"已有账号?",-1)),l(p,{link:"",type:"primary",onClick:R},{default:o(()=>[...e[9]||(e[9]=[E("立即登录",-1)])]),_:1})])]),_:1})])}}},re=Y(Z,[["__scopeId","data-v-a9d7804f"]]);export{re as default};

View File

@@ -1 +1 @@
import{r as n,a1 as L,c as M,v as U,a0 as D,z as v,Q as s,J as a,aY as l,A as m,B as w,P as h,N as k,I as K,L as B,aD as j,O as z}from"./vendor-DR-vtVVc.js";import{u as F,p as J}from"./vendor-vue-l2lnRGj2.js";import{c as O}from"./auth-CtTBVBJk.js";import{_ as Q,v as Y}from"./index-C4rfJ09l.js";import{E as y}from"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const q={class:"auth-wrap"},G={class:"actions"},H={class:"actions"},W={key:0,class:"app-muted"},X={__name:"ResetPasswordPage",setup(Z){const x=F(),A=J(),r=n(String(x.params.token||"")),i=n(!0),b=n(""),t=L({newPassword:"",confirmPassword:""}),P=n(!1),f=n(""),d=n(0);let u=null;function N(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=M(()=>!!(i.value&&r.value&&!f.value));function S(){A.push("/login")}function C(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=Y(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}P.value=!0;try{await O({token:r.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",y.success("密码重置成功"),C()}catch(p){const _=p?.response?.data;y.error(_?.error||"重置失败")}finally{P.value=!1}}return U(()=>{const o=N();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),D(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),_=l("el-input"),T=l("el-form-item"),R=l("el-form"),E=l("el-card");return m(),v("div",q,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=w("div",{class:"brand"},[w("div",{class:"brand-title"},"知识管理平台"),w("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(m(),v(h,{key:1},[f.value?(m(),K(c,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):B("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(T,{label:"新密码至少8位且包含字母和数字"},{default:a(()=>[s(_,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=g=>t.newPassword=g),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(T,{label:"确认密码"},{default:a(()=>[s(_,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=g=>t.confirmPassword=g),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:j(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:P.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),w("div",H,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(m(),v("span",W,z(d.value)+" 秒后自动跳转…",1)):B("",!0)])],64)):(m(),v(h,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),w("div",G,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},ne=Q(X,[["__scopeId","data-v-0bbb511c"]]);export{ne as default};
import{r as n,a1 as L,c as M,v as U,a0 as D,z as v,Q as s,J as a,aY as l,A as m,B as w,P as h,N as k,I as K,L as B,aD as j,O as z}from"./vendor-DR-vtVVc.js";import{u as F,p as J}from"./vendor-vue-l2lnRGj2.js";import{c as O}from"./auth-3qca7BnL.js";import{_ as Q,v as Y}from"./index-DIRUAu3R.js";import{E as y}from"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const q={class:"auth-wrap"},G={class:"actions"},H={class:"actions"},W={key:0,class:"app-muted"},X={__name:"ResetPasswordPage",setup(Z){const x=F(),A=J(),r=n(String(x.params.token||"")),i=n(!0),b=n(""),t=L({newPassword:"",confirmPassword:""}),P=n(!1),f=n(""),d=n(0);let u=null;function N(){if(typeof window>"u")return null;const o=window.__APP_INITIAL_STATE__;return!o||typeof o!="object"?null:(window.__APP_INITIAL_STATE__=null,o)}const I=M(()=>!!(i.value&&r.value&&!f.value));function S(){A.push("/login")}function C(){d.value=3,u=window.setInterval(()=>{d.value-=1,d.value<=0&&(window.clearInterval(u),u=null,window.location.href="/login")},1e3)}async function V(){if(!I.value)return;const o=t.newPassword,e=t.confirmPassword,c=Y(o);if(!c.ok){y.error(c.message);return}if(o!==e){y.error("两次输入的密码不一致");return}P.value=!0;try{await O({token:r.value,new_password:o}),f.value="密码重置成功3秒后跳转到登录页面...",y.success("密码重置成功"),C()}catch(p){const _=p?.response?.data;y.error(_?.error||"重置失败")}finally{P.value=!1}}return U(()=>{const o=N();o?.page==="reset_password"?(r.value=String(o?.token||r.value||""),i.value=!!o?.valid,b.value=o?.error_message||(i.value?"":"重置链接无效或已过期,请重新申请密码重置")):r.value||(i.value=!1,b.value="重置链接无效或已过期,请重新申请密码重置")}),D(()=>{u&&window.clearInterval(u)}),(o,e)=>{const c=l("el-alert"),p=l("el-button"),_=l("el-input"),T=l("el-form-item"),R=l("el-form"),E=l("el-card");return m(),v("div",q,[s(E,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:a(()=>[e[5]||(e[5]=w("div",{class:"brand"},[w("div",{class:"brand-title"},"知识管理平台"),w("div",{class:"brand-sub app-muted"},"重置密码")],-1)),i.value?(m(),v(h,{key:1},[f.value?(m(),K(c,{key:0,type:"success",closable:!1,title:"重置成功",description:f.value,"show-icon":"",class:"alert"},null,8,["description"])):B("",!0),s(R,{"label-position":"top"},{default:a(()=>[s(T,{label:"新密码至少8位且包含字母和数字"},{default:a(()=>[s(_,{modelValue:t.newPassword,"onUpdate:modelValue":e[0]||(e[0]=g=>t.newPassword=g),type:"password","show-password":"",placeholder:"请输入新密码",autocomplete:"new-password"},null,8,["modelValue"])]),_:1}),s(T,{label:"确认密码"},{default:a(()=>[s(_,{modelValue:t.confirmPassword,"onUpdate:modelValue":e[1]||(e[1]=g=>t.confirmPassword=g),type:"password","show-password":"",placeholder:"请再次输入新密码",autocomplete:"new-password",onKeyup:j(V,["enter"])},null,8,["modelValue"])]),_:1})]),_:1}),s(p,{type:"primary",class:"submit-btn",loading:P.value,disabled:!I.value,onClick:V},{default:a(()=>[...e[3]||(e[3]=[k(" 确认重置 ",-1)])]),_:1},8,["loading","disabled"]),w("div",H,[s(p,{link:"",type:"primary",onClick:S},{default:a(()=>[...e[4]||(e[4]=[k("返回登录",-1)])]),_:1}),d.value>0?(m(),v("span",W,z(d.value)+" 秒后自动跳转…",1)):B("",!0)])],64)):(m(),v(h,{key:0},[s(c,{type:"error",closable:!1,title:"链接已失效",description:b.value,"show-icon":""},null,8,["description"]),w("div",G,[s(p,{type:"primary",onClick:S},{default:a(()=>[...e[2]||(e[2]=[k("返回登录",-1)])]),_:1})])],64))]),_:1})])}}},ne=Q(X,[["__scopeId","data-v-0bbb511c"]]);export{ne as default};

View File

@@ -1 +1 @@
import{p as U}from"./vendor-vue-l2lnRGj2.js";import{_ as E}from"./index-C4rfJ09l.js";import{r as o,c as h,v as R,a0 as z,z as B,Q as i,J as s,aY as d,A as _,B as l,L as k,I as W,N,O as v}from"./vendor-DR-vtVVc.js";import"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const $={class:"auth-wrap"},j={class:"actions"},D={key:0,class:"countdown app-muted"},J={__name:"VerifyResultPage",setup(M){const T=U(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function x(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function A(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const C=h(()=>!!(r.value&&u.value)),b=h(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await T.push(e)}}function L(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return R(()=>{const e=x();A(e),L()}),z(()=>{a&&window.clearInterval(a)}),(e,t)=>{const I=d("el-button"),P=d("el-result"),V=d("el-card");return _(),B("div",$,[i(V,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(P,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",j,[i(I,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[N(v(w.value),1)]),_:1}),C.value?(_(),W(I,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[N(v(r.value),1)]),_:1})):k("",!0)]),b.value?(_(),B("div",D,v(n.value)+" 秒后自动跳转... ",1)):k("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=E(J,[["__scopeId","data-v-1fc6b081"]]);export{G as default};
import{p as U}from"./vendor-vue-l2lnRGj2.js";import{_ as E}from"./index-DIRUAu3R.js";import{r as o,c as h,v as R,a0 as z,z as B,Q as i,J as s,aY as d,A as _,B as l,L as k,I as W,N,O as v}from"./vendor-DR-vtVVc.js";import"./vendor-element-D5SbqSD5.js";import"./vendor-axios-B9ygI19o.js";const $={class:"auth-wrap"},j={class:"actions"},D={key:0,class:"countdown app-muted"},J={__name:"VerifyResultPage",setup(M){const T=U(),p=o(!1),f=o(""),m=o(""),w=o(""),y=o(""),r=o(""),u=o(""),c=o(""),n=o(0);let a=null;function x(){if(typeof window>"u")return null;const e=window.__APP_INITIAL_STATE__;return!e||typeof e!="object"?null:(window.__APP_INITIAL_STATE__=null,e)}function A(e){const t=!!e?.success;p.value=t,f.value=e?.title||(t?"验证成功":"验证失败"),m.value=e?.message||e?.error_message||(t?"操作已完成,现在可以继续使用系统。":"操作失败,请稍后重试。"),w.value=e?.primary_label||(t?"立即登录":"重新注册"),y.value=e?.primary_url||(t?"/login":"/register"),r.value=e?.secondary_label||(t?"":"返回登录"),u.value=e?.secondary_url||(t?"":"/login"),c.value=e?.redirect_url||(t?"/login":""),n.value=Number(e?.redirect_seconds||(t?5:0))||0}const C=h(()=>!!(r.value&&u.value)),b=h(()=>!!(c.value&&n.value>0));async function g(e){if(e){if(e.startsWith("http://")||e.startsWith("https://")){window.location.href=e;return}await T.push(e)}}function L(){b.value&&(a=window.setInterval(()=>{n.value-=1,n.value<=0&&(window.clearInterval(a),a=null,window.location.href=c.value)},1e3))}return R(()=>{const e=x();A(e),L()}),z(()=>{a&&window.clearInterval(a)}),(e,t)=>{const I=d("el-button"),P=d("el-result"),V=d("el-card");return _(),B("div",$,[i(V,{shadow:"never",class:"auth-card","body-style":{padding:"22px"}},{default:s(()=>[t[2]||(t[2]=l("div",{class:"brand"},[l("div",{class:"brand-title"},"知识管理平台"),l("div",{class:"brand-sub app-muted"},"验证结果")],-1)),i(P,{icon:p.value?"success":"error",title:f.value,"sub-title":m.value,class:"result"},{extra:s(()=>[l("div",j,[i(I,{type:"primary",onClick:t[0]||(t[0]=S=>g(y.value))},{default:s(()=>[N(v(w.value),1)]),_:1}),C.value?(_(),W(I,{key:0,onClick:t[1]||(t[1]=S=>g(u.value))},{default:s(()=>[N(v(r.value),1)]),_:1})):k("",!0)]),b.value?(_(),B("div",D,v(n.value)+" 秒后自动跳转... ",1)):k("",!0)]),_:1},8,["icon","title","sub-title"])]),_:1})])}}},G=E(J,[["__scopeId","data-v-1fc6b081"]]);export{G as default};

View File

@@ -1 +1 @@
import{p as c}from"./index-C4rfJ09l.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};
import{p as c}from"./index-DIRUAu3R.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};

View File

@@ -1 +1 @@
import{p as s}from"./index-C4rfJ09l.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};
import{p as s}from"./index-DIRUAu3R.js";async function r(){const{data:a}=await s.get("/email/verify-status");return a}async function o(){const{data:a}=await s.post("/generate_captcha",{});return a}async function e(a){const{data:t}=await s.post("/login",a);return t}async function i(a){const{data:t}=await s.post("/register",a);return t}async function c(a){const{data:t}=await s.post("/resend-verify-email",a);return t}async function f(a){const{data:t}=await s.post("/forgot-password",a);return t}async function u(a){const{data:t}=await s.post("/reset-password-confirm",a);return t}export{f as a,i as b,u as c,r as f,o as g,e as l,c as r};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>知识管理平台</title>
<script type="module" crossorigin src="./assets/index-C4rfJ09l.js"></script>
<script type="module" crossorigin src="./assets/index-DIRUAu3R.js"></script>
<link rel="modulepreload" crossorigin href="./assets/vendor-DR-vtVVc.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-element-D5SbqSD5.js">
<link rel="modulepreload" crossorigin href="./assets/vendor-vue-l2lnRGj2.js">