From 08864e51ba97ab5d5b6cb51d5d6ff7bbb47c4e03 Mon Sep 17 00:00:00 2001 From: yuyx <237899745@qq.com> Date: Sat, 7 Feb 2026 21:37:55 +0800 Subject: [PATCH] security: harden admin password change and production session headers --- .env.example | 3 +- admin-frontend/src/api/admin.js | 10 ++- admin-frontend/src/pages/SettingsPage.vue | 40 ++++++++++- app.py | 36 ++++++++++ docker-compose.yml | 3 +- routes/admin_api/account_api.py | 24 ++++++- static/admin/.vite/manifest.json | 66 +++++++++---------- ...ecIAp.js => AnnouncementsPage-PdPHO5Q2.js} | 2 +- ...Page-DMQiuF7T.js => EmailPage-yqRvXEJ2.js} | 2 +- ...-DPSbObwj.js => FeedbacksPage-9Z4ULgo9.js} | 2 +- ...sPage-C3EIiKOD.js => LogsPage-MDq3eoIe.js} | 2 +- ...rid-Bj2rgY20.js => MetricGrid-Dsqo4YZI.js} | 2 +- ...age-B5zHPJFA.js => ReportPage-ycVtg2rZ.js} | 2 +- ...e-ClrVJm-9.js => SecurityPage-CXcU2SbL.js} | 2 +- static/admin/assets/SettingsPage-COD6KO3P.js | 1 - static/admin/assets/SettingsPage-CUZAbAFF.js | 1 + static/admin/assets/SettingsPage-DaB8PeRL.css | 1 - static/admin/assets/SettingsPage-NWcEVLn7.css | 1 + ...age-CkuZRgDH.js => SystemPage-B2BrKkTP.js} | 2 +- ...Page-CwNX_aN-.js => UsersPage-yptpHEoN.js} | 2 +- .../{email-UyqcDGmM.js => email--WygXDwI.js} | 2 +- .../{index-COtE2fCT.js => index-BsqM_wut.js} | 4 +- ...{system-CCwKPotj.js => system-CAzjuaad.js} | 2 +- .../{tasks-BpEmoe5G.js => tasks-OWsi7T-E.js} | 2 +- .../{users-DXFAixXH.js => users-BZkLUJZL.js} | 2 +- static/admin/index.html | 2 +- 26 files changed, 159 insertions(+), 59 deletions(-) rename static/admin/assets/{AnnouncementsPage-CB2ecIAp.js => AnnouncementsPage-PdPHO5Q2.js} (99%) rename static/admin/assets/{EmailPage-DMQiuF7T.js => EmailPage-yqRvXEJ2.js} (99%) rename static/admin/assets/{FeedbacksPage-DPSbObwj.js => FeedbacksPage-9Z4ULgo9.js} (97%) rename static/admin/assets/{LogsPage-C3EIiKOD.js => LogsPage-MDq3eoIe.js} (98%) rename static/admin/assets/{MetricGrid-Bj2rgY20.js => MetricGrid-Dsqo4YZI.js} (94%) rename static/admin/assets/{ReportPage-B5zHPJFA.js => ReportPage-ycVtg2rZ.js} (98%) rename static/admin/assets/{SecurityPage-ClrVJm-9.js => SecurityPage-CXcU2SbL.js} (99%) delete mode 100644 static/admin/assets/SettingsPage-COD6KO3P.js create mode 100644 static/admin/assets/SettingsPage-CUZAbAFF.js delete mode 100644 static/admin/assets/SettingsPage-DaB8PeRL.css create mode 100644 static/admin/assets/SettingsPage-NWcEVLn7.css rename static/admin/assets/{SystemPage-CkuZRgDH.js => SystemPage-B2BrKkTP.js} (99%) rename static/admin/assets/{UsersPage-CwNX_aN-.js => UsersPage-yptpHEoN.js} (99%) rename static/admin/assets/{email-UyqcDGmM.js => email--WygXDwI.js} (88%) rename static/admin/assets/{index-COtE2fCT.js => index-BsqM_wut.js} (89%) rename static/admin/assets/{system-CCwKPotj.js => system-CAzjuaad.js} (76%) rename static/admin/assets/{tasks-BpEmoe5G.js => tasks-OWsi7T-E.js} (93%) rename static/admin/assets/{users-DXFAixXH.js => users-BZkLUJZL.js} (90%) diff --git a/.env.example b/.env.example index 00503ab..72d9698 100644 --- a/.env.example +++ b/.env.example @@ -13,7 +13,8 @@ FLASK_DEBUG=false # Session配置 SESSION_LIFETIME_HOURS=24 -SESSION_COOKIE_SECURE=false # 使用HTTPS时设为true +SESSION_COOKIE_SECURE=true # 生产环境HTTPS必须为true,本地HTTP调试可临时设为false +HTTPS_ENABLED=true # ==================== 数据库配置 ==================== DB_FILE=data/app_data.db diff --git a/admin-frontend/src/api/admin.js b/admin-frontend/src/api/admin.js index c8cafbb..3d8c5ef 100644 --- a/admin-frontend/src/api/admin.js +++ b/admin-frontend/src/api/admin.js @@ -5,8 +5,13 @@ export async function updateAdminUsername(newUsername) { return data } -export async function updateAdminPassword(newPassword) { - const { data } = await api.put('/admin/password', { new_password: newPassword }) +export async function updateAdminPassword(payload = {}) { + const currentPassword = String(payload.currentPassword || '') + const newPassword = String(payload.newPassword || '') + const { data } = await api.put('/admin/password', { + current_password: currentPassword, + new_password: newPassword, + }) return data } @@ -14,4 +19,3 @@ export async function logout() { const { data } = await api.post('/logout') return data } - diff --git a/admin-frontend/src/pages/SettingsPage.vue b/admin-frontend/src/pages/SettingsPage.vue index d4c827c..f509b7d 100644 --- a/admin-frontend/src/pages/SettingsPage.vue +++ b/admin-frontend/src/pages/SettingsPage.vue @@ -5,7 +5,9 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { logout, updateAdminPassword, updateAdminUsername } from '../api/admin' const username = ref('') +const currentPassword = ref('') const password = ref('') +const confirmPassword = ref('') const submitting = ref(false) function validateStrongPassword(value) { @@ -57,17 +59,31 @@ async function saveUsername() { } async function savePassword() { + const currentValue = currentPassword.value const value = password.value + const confirmValue = confirmPassword.value + + if (!currentValue) { + ElMessage.error('请输入当前密码') + return + } + if (!value) { ElMessage.error('请输入新密码') return } + const check = validateStrongPassword(value) if (!check.ok) { ElMessage.error(check.message) return } + if (value !== confirmValue) { + ElMessage.error('两次输入的新密码不一致') + return + } + try { await ElMessageBox.confirm('确定修改管理员密码吗?修改后需要重新登录。', '修改密码', { confirmButtonText: '确认修改', @@ -80,9 +96,11 @@ async function savePassword() { submitting.value = true try { - await updateAdminPassword(value) + await updateAdminPassword({ currentPassword: currentValue, newPassword: value }) ElMessage.success('密码修改成功,请重新登录') + currentPassword.value = '' password.value = '' + confirmPassword.value = '' setTimeout(relogin, 1200) } catch { // handled by interceptor @@ -112,6 +130,16 @@ async function savePassword() {

修改管理员密码

+ + + + + + + + 保存密码
建议使用更强密码(至少8位且包含字母与数字)。
diff --git a/app.py b/app.py index 7658a41..1c2f874 100644 --- a/app.py +++ b/app.py @@ -135,6 +135,33 @@ def _is_api_or_health_path(path: str) -> bool: return raw.startswith("/api/") or raw.startswith("/yuyx/api/") or raw == "/health" +def _request_uses_https() -> bool: + try: + if bool(request.is_secure): + return True + except Exception: + pass + + try: + forwarded_proto = str(request.headers.get("X-Forwarded-Proto", "") or "").split(",", 1)[0].strip().lower() + if forwarded_proto == "https": + return True + except Exception: + pass + + return False + + +_SECURITY_RESPONSE_HEADERS = { + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "SAMEORIGIN", + "Referrer-Policy": "strict-origin-when-cross-origin", + "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()", +} + +_SECURITY_CSP_HEADER = str(os.environ.get("SECURITY_CONTENT_SECURITY_POLICY", "") or "").strip() + + _HASHED_STATIC_ASSET_RE = re.compile(r".*-[a-z0-9_-]{8,}\.(?:js|css|woff2?|ttf|svg|png|jpe?g|webp)$", re.IGNORECASE) @@ -238,6 +265,15 @@ def ensure_csrf_cookie(response): samesite=config.SESSION_COOKIE_SAMESITE, ) + for header_name, header_value in _SECURITY_RESPONSE_HEADERS.items(): + response.headers.setdefault(header_name, header_value) + + if _request_uses_https(): + response.headers.setdefault("Strict-Transport-Security", "max-age=31536000; includeSubDomains") + + if _SECURITY_CSP_HEADER: + response.headers.setdefault("Content-Security-Policy", _SECURITY_CSP_HEADER) + _record_request_metric_after_response(response) return response diff --git a/docker-compose.yml b/docker-compose.yml index 3fbdd51..9529975 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -48,7 +48,8 @@ services: # 加密密钥配置(重要!防止容器重建时丢失密钥) - ENCRYPTION_KEY_RAW=${ENCRYPTION_KEY_RAW} - SESSION_LIFETIME_HOURS=24 - - SESSION_COOKIE_SECURE=false + - SESSION_COOKIE_SECURE=true + - HTTPS_ENABLED=true - MAX_CAPTCHA_ATTEMPTS=5 - MAX_IP_ATTEMPTS_PER_HOUR=10 # 日志配置 diff --git a/routes/admin_api/account_api.py b/routes/admin_api/account_api.py index 6a7d3ba..a497eb3 100644 --- a/routes/admin_api/account_api.py +++ b/routes/admin_api/account_api.py @@ -14,15 +14,35 @@ from routes.decorators import admin_required @admin_api_bp.route("/admin/password", methods=["PUT"]) @admin_required def update_admin_password(): - """修改管理员密码""" + """修改管理员密码(要求提供当前密码并校验新密码强度)""" data = request.json or {} + current_password = (data.get("current_password") or "").strip() new_password = (data.get("new_password") or "").strip() + if not current_password: + return jsonify({"error": "当前密码不能为空"}), 400 + if not new_password: - return jsonify({"error": "密码不能为空"}), 400 + return jsonify({"error": "新密码不能为空"}), 400 + + if current_password == new_password: + return jsonify({"error": "新密码不能与当前密码相同"}), 400 + + is_valid, error_msg = validate_password(new_password) + if not is_valid: + return jsonify({"error": error_msg}), 400 username = session.get("admin_username") + if not username: + return jsonify({"error": "未登录"}), 401 + + admin = database.verify_admin(username, current_password) + if not admin: + return jsonify({"error": "当前密码错误"}), 401 + if database.update_admin_password(username, new_password): + session["admin_reauth_until"] = 0 + session.modified = True return jsonify({"success": True}) return jsonify({"error": "修改失败"}), 400 diff --git a/static/admin/.vite/manifest.json b/static/admin/.vite/manifest.json index ed2e97c..71194c1 100644 --- a/static/admin/.vite/manifest.json +++ b/static/admin/.vite/manifest.json @@ -1,6 +1,6 @@ { - "_MetricGrid-Bj2rgY20.js": { - "file": "assets/MetricGrid-Bj2rgY20.js", + "_MetricGrid-Dsqo4YZI.js": { + "file": "assets/MetricGrid-Dsqo4YZI.js", "name": "MetricGrid", "imports": [ "index.html", @@ -14,29 +14,29 @@ "file": "assets/MetricGrid-yP_dkP6X.css", "src": "_MetricGrid-yP_dkP6X.css" }, - "_email-UyqcDGmM.js": { - "file": "assets/email-UyqcDGmM.js", + "_email--WygXDwI.js": { + "file": "assets/email--WygXDwI.js", "name": "email", "imports": [ "index.html" ] }, - "_system-CCwKPotj.js": { - "file": "assets/system-CCwKPotj.js", + "_system-CAzjuaad.js": { + "file": "assets/system-CAzjuaad.js", "name": "system", "imports": [ "index.html" ] }, - "_tasks-BpEmoe5G.js": { - "file": "assets/tasks-BpEmoe5G.js", + "_tasks-OWsi7T-E.js": { + "file": "assets/tasks-OWsi7T-E.js", "name": "tasks", "imports": [ "index.html" ] }, - "_users-DXFAixXH.js": { - "file": "assets/users-DXFAixXH.js", + "_users-BZkLUJZL.js": { + "file": "assets/users-BZkLUJZL.js", "name": "users", "imports": [ "index.html" @@ -73,7 +73,7 @@ "name": "vendor-vue" }, "index.html": { - "file": "assets/index-COtE2fCT.js", + "file": "assets/index-BsqM_wut.js", "name": "index", "src": "index.html", "isEntry": true, @@ -99,7 +99,7 @@ ] }, "src/pages/AnnouncementsPage.vue": { - "file": "assets/AnnouncementsPage-CB2ecIAp.js", + "file": "assets/AnnouncementsPage-PdPHO5Q2.js", "name": "AnnouncementsPage", "src": "src/pages/AnnouncementsPage.vue", "isDynamicEntry": true, @@ -115,14 +115,14 @@ ] }, "src/pages/EmailPage.vue": { - "file": "assets/EmailPage-DMQiuF7T.js", + "file": "assets/EmailPage-yqRvXEJ2.js", "name": "EmailPage", "src": "src/pages/EmailPage.vue", "isDynamicEntry": true, "imports": [ - "_email-UyqcDGmM.js", + "_email--WygXDwI.js", "index.html", - "_MetricGrid-Bj2rgY20.js", + "_MetricGrid-Dsqo4YZI.js", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", "_vendor-axios-B9ygI19o.js", @@ -133,13 +133,13 @@ ] }, "src/pages/FeedbacksPage.vue": { - "file": "assets/FeedbacksPage-DPSbObwj.js", + "file": "assets/FeedbacksPage-9Z4ULgo9.js", "name": "FeedbacksPage", "src": "src/pages/FeedbacksPage.vue", "isDynamicEntry": true, "imports": [ "index.html", - "_MetricGrid-Bj2rgY20.js", + "_MetricGrid-Dsqo4YZI.js", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", "_vendor-axios-B9ygI19o.js", @@ -150,13 +150,13 @@ ] }, "src/pages/LogsPage.vue": { - "file": "assets/LogsPage-C3EIiKOD.js", + "file": "assets/LogsPage-MDq3eoIe.js", "name": "LogsPage", "src": "src/pages/LogsPage.vue", "isDynamicEntry": true, "imports": [ - "_users-DXFAixXH.js", - "_tasks-BpEmoe5G.js", + "_users-BZkLUJZL.js", + "_tasks-OWsi7T-E.js", "index.html", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", @@ -168,17 +168,17 @@ ] }, "src/pages/ReportPage.vue": { - "file": "assets/ReportPage-B5zHPJFA.js", + "file": "assets/ReportPage-ycVtg2rZ.js", "name": "ReportPage", "src": "src/pages/ReportPage.vue", "isDynamicEntry": true, "imports": [ "_vendor-element-B5S5pUKo.js", "index.html", - "_email-UyqcDGmM.js", - "_tasks-BpEmoe5G.js", - "_system-CCwKPotj.js", - "_MetricGrid-Bj2rgY20.js", + "_email--WygXDwI.js", + "_tasks-OWsi7T-E.js", + "_system-CAzjuaad.js", + "_MetricGrid-Dsqo4YZI.js", "_vendor-vue-CVxSw_oJ.js", "_vendor-misc-BeoNyvBp.js", "_vendor-axios-B9ygI19o.js" @@ -188,13 +188,13 @@ ] }, "src/pages/SecurityPage.vue": { - "file": "assets/SecurityPage-ClrVJm-9.js", + "file": "assets/SecurityPage-CXcU2SbL.js", "name": "SecurityPage", "src": "src/pages/SecurityPage.vue", "isDynamicEntry": true, "imports": [ "index.html", - "_MetricGrid-Bj2rgY20.js", + "_MetricGrid-Dsqo4YZI.js", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", "_vendor-axios-B9ygI19o.js", @@ -205,7 +205,7 @@ ] }, "src/pages/SettingsPage.vue": { - "file": "assets/SettingsPage-COD6KO3P.js", + "file": "assets/SettingsPage-CUZAbAFF.js", "name": "SettingsPage", "src": "src/pages/SettingsPage.vue", "isDynamicEntry": true, @@ -217,16 +217,16 @@ "_vendor-misc-BeoNyvBp.js" ], "css": [ - "assets/SettingsPage-DaB8PeRL.css" + "assets/SettingsPage-NWcEVLn7.css" ] }, "src/pages/SystemPage.vue": { - "file": "assets/SystemPage-CkuZRgDH.js", + "file": "assets/SystemPage-B2BrKkTP.js", "name": "SystemPage", "src": "src/pages/SystemPage.vue", "isDynamicEntry": true, "imports": [ - "_system-CCwKPotj.js", + "_system-CAzjuaad.js", "index.html", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", @@ -238,12 +238,12 @@ ] }, "src/pages/UsersPage.vue": { - "file": "assets/UsersPage-CwNX_aN-.js", + "file": "assets/UsersPage-yptpHEoN.js", "name": "UsersPage", "src": "src/pages/UsersPage.vue", "isDynamicEntry": true, "imports": [ - "_users-DXFAixXH.js", + "_users-BZkLUJZL.js", "index.html", "_vendor-element-B5S5pUKo.js", "_vendor-vue-CVxSw_oJ.js", diff --git a/static/admin/assets/AnnouncementsPage-CB2ecIAp.js b/static/admin/assets/AnnouncementsPage-PdPHO5Q2.js similarity index 99% rename from static/admin/assets/AnnouncementsPage-CB2ecIAp.js rename to static/admin/assets/AnnouncementsPage-PdPHO5Q2.js index 77cfc83..affeb12 100644 --- a/static/admin/assets/AnnouncementsPage-CB2ecIAp.js +++ b/static/admin/assets/AnnouncementsPage-PdPHO5Q2.js @@ -1 +1 @@ -import{p as J,a as r,E as C}from"./vendor-element-B5S5pUKo.js";import{a as w,_ as W}from"./index-COtE2fCT.js";import{r as v,o as K,aj as p,ap as O,n as h,q as f,t as u,L as n,E as t,G as A,D as x,u as Q,I as c,J as D,F as X,a8 as $}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-misc-BeoNyvBp.js";import"./vendor-axios-B9ygI19o.js";async function Y(){const{data:o}=await w.get("/announcements");return o}async function Z(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"},re=["src"],ue={class:"actions"},ce={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),I=v(!1),T=v([]);async function k(){I.value=!0;try{T.value=await Y()}catch{T.value=[]}finally{I.value=!1}}function E(){s.value="",_.value="",m.value="",g.value&&(g.value.value="")}function N(){g.value?.click()}function U(){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/")){r.error("请选择图片文件"),l.target.value="";return}B.value=!0;try{const d=await ee(e);if(!d?.success||!d?.url){r.error(d?.error||"上传失败");return}m.value=d.url,r.success("上传成功")}catch{}finally{B.value=!1,l.target.value=""}}}async function z(l){const e=s.value.trim(),d=_.value.trim(),b=m.value.trim();if(!e||!d){r.error("标题和内容不能为空");return}try{const i=await Z({title:e,content:d,image_url:b,is_active:!!l});if(!i?.success){r.error(i?.error||"保存失败");return}r.success("保存成功"),E(),await k()}catch{}}async function L(l){const e=$("div",{class:"announcement-view"},[l.content?$("div",{class:"announcement-view-text"},l.content):null,l.image_url?$("img",{class:"announcement-view-image",src:l.image_url,alt:"公告图片"}):null]);await C.alert(e,l.title||"公告",{confirmButtonText:"关闭",dangerouslyUseHTMLString:!1})}async function R(l){try{await C.confirm("确定启用该公告吗?启用后将自动停用其他公告。","启用公告",{confirmButtonText:"启用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await te(l.id);if(!e?.success){r.error(e?.error||"启用失败");return}r.success("已启用"),await k()}catch{}}async function S(l){try{await C.confirm("确定停用该公告吗?","停用公告",{confirmButtonText:"停用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await ne(l.id);if(!e?.success){r.error(e?.error||"停用失败");return}r.success("已停用"),await k()}catch{}}async function j(l){try{await C.confirm("确定删除该公告吗?删除后无法恢复。","删除公告",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const e=await ae(l.id);if(!e?.success){r.error(e?.error||"删除失败");return}r.success("已删除"),await k()}catch{}}return K(k),(l,e)=>{const d=p("el-input"),b=p("el-form-item"),i=p("el-button"),q=p("el-form"),F=p("el-card"),y=p("el-table-column"),M=p("el-tag"),G=p("el-table"),H=O("loading");return f(),h("div",le,[e[17]||(e[17]=u("div",{class:"app-page-title"},[u("h2",null,"公告管理")],-1)),n(F,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[9]||(e[9]=u("h3",{class:"section-title"},"创建公告",-1)),n(q,{"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(()=>[u("div",se,[n(i,{icon:Q(J),loading:B.value,onClick:N},{default:t(()=>[...e[4]||(e[4]=[c("上传图片",-1)])]),_:1},8,["icon","loading"]),m.value?(f(),x(i,{key:0,onClick:U},{default:t(()=>[...e[5]||(e[5]=[c("移除",-1)])]),_:1})):A("",!0),m.value?(f(),h("span",oe,D(m.value),1)):A("",!0),u("input",{ref_key:"imageInputRef",ref:g,class:"image-input",type:"file",accept:"image/*",onChange:P},null,544)])]),_:1})]),_:1}),m.value?(f(),h("div",ie,[u("img",{src:m.value,alt:"公告图片预览"},null,8,re)])):A("",!0),u("div",ue,[n(i,{type:"primary",onClick:e[2]||(e[2]=a=>z(!0))},{default:t(()=>[...e[6]||(e[6]=[c("发布并启用",-1)])]),_:1}),n(i,{onClick:e[3]||(e[3]=a=>z(!1))},{default:t(()=>[...e[7]||(e[7]=[c("保存但不启用",-1)])]),_:1}),n(i,{onClick:E},{default:t(()=>[...e[8]||(e[8]=[c("清空",-1)])]),_:1})]),e[10]||(e[10]=u("div",{class:"help"}," 说明:启用公告后,用户登录进入系统将弹窗提示;用户可选择“当次关闭”或“永久关闭本次公告”。 ",-1))]),_:1}),n(F,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[16]||(e[16]=u("h3",{class:"section-title"},"公告列表",-1)),u("div",ce,[X((f(),x(G,{data:T.value,style:{width:"100%"}},{default:t(()=>[n(y,{prop:"id",label:"ID",width:"80"}),n(y,{label:"标题","min-width":"240"},{default:t(({row:a})=>[u("span",{class:"ellipsis",title:a.title},D(a.title),9,de)]),_:1}),n(y,{label:"状态",width:"120"},{default:t(({row:a})=>[n(M,{type:a.is_active?"success":"info",effect:"light"},{default:t(()=>[c(D(a.is_active?"启用":"停用"),1)]),_:2},1032,["type"])]),_:1}),n(y,{label:"图片",width:"100"},{default:t(({row:a})=>[a.image_url?(f(),x(M,{key:0,type:"success",effect:"light"},{default:t(()=>[...e[11]||(e[11]=[c("有图",-1)])]),_:1})):(f(),h("span",me,"-"))]),_:1}),n(y,{prop:"created_at",label:"创建时间",width:"180"}),n(y,{label:"操作",width:"260",fixed:"right"},{default:t(({row:a})=>[u("div",fe,[n(i,{size:"small",onClick:V=>L(a)},{default:t(()=>[...e[12]||(e[12]=[c("查看",-1)])]),_:1},8,["onClick"]),a.is_active?(f(),x(i,{key:0,size:"small",onClick:V=>S(a)},{default:t(()=>[...e[13]||(e[13]=[c("停用",-1)])]),_:1},8,["onClick"])):(f(),x(i,{key:1,type:"success",size:"small",onClick:V=>R(a)},{default:t(()=>[...e[14]||(e[14]=[c("启用",-1)])]),_:1},8,["onClick"])),n(i,{type:"danger",size:"small",onClick:V=>j(a)},{default:t(()=>[...e[15]||(e[15]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[H,I.value]])])]),_:1})])}}},ke=W(pe,[["__scopeId","data-v-6f55521c"]]);export{ke as default}; +import{p as J,a as r,E as C}from"./vendor-element-B5S5pUKo.js";import{a as w,_ as W}from"./index-BsqM_wut.js";import{r as v,o as K,aj as p,ap as O,n as h,q as f,t as u,L as n,E as t,G as A,D as x,u as Q,I as c,J as D,F as X,a8 as $}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-misc-BeoNyvBp.js";import"./vendor-axios-B9ygI19o.js";async function Y(){const{data:o}=await w.get("/announcements");return o}async function Z(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"},re=["src"],ue={class:"actions"},ce={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),I=v(!1),T=v([]);async function k(){I.value=!0;try{T.value=await Y()}catch{T.value=[]}finally{I.value=!1}}function E(){s.value="",_.value="",m.value="",g.value&&(g.value.value="")}function N(){g.value?.click()}function U(){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/")){r.error("请选择图片文件"),l.target.value="";return}B.value=!0;try{const d=await ee(e);if(!d?.success||!d?.url){r.error(d?.error||"上传失败");return}m.value=d.url,r.success("上传成功")}catch{}finally{B.value=!1,l.target.value=""}}}async function z(l){const e=s.value.trim(),d=_.value.trim(),b=m.value.trim();if(!e||!d){r.error("标题和内容不能为空");return}try{const i=await Z({title:e,content:d,image_url:b,is_active:!!l});if(!i?.success){r.error(i?.error||"保存失败");return}r.success("保存成功"),E(),await k()}catch{}}async function L(l){const e=$("div",{class:"announcement-view"},[l.content?$("div",{class:"announcement-view-text"},l.content):null,l.image_url?$("img",{class:"announcement-view-image",src:l.image_url,alt:"公告图片"}):null]);await C.alert(e,l.title||"公告",{confirmButtonText:"关闭",dangerouslyUseHTMLString:!1})}async function R(l){try{await C.confirm("确定启用该公告吗?启用后将自动停用其他公告。","启用公告",{confirmButtonText:"启用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await te(l.id);if(!e?.success){r.error(e?.error||"启用失败");return}r.success("已启用"),await k()}catch{}}async function S(l){try{await C.confirm("确定停用该公告吗?","停用公告",{confirmButtonText:"停用",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await ne(l.id);if(!e?.success){r.error(e?.error||"停用失败");return}r.success("已停用"),await k()}catch{}}async function j(l){try{await C.confirm("确定删除该公告吗?删除后无法恢复。","删除公告",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const e=await ae(l.id);if(!e?.success){r.error(e?.error||"删除失败");return}r.success("已删除"),await k()}catch{}}return K(k),(l,e)=>{const d=p("el-input"),b=p("el-form-item"),i=p("el-button"),q=p("el-form"),F=p("el-card"),y=p("el-table-column"),M=p("el-tag"),G=p("el-table"),H=O("loading");return f(),h("div",le,[e[17]||(e[17]=u("div",{class:"app-page-title"},[u("h2",null,"公告管理")],-1)),n(F,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[9]||(e[9]=u("h3",{class:"section-title"},"创建公告",-1)),n(q,{"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(()=>[u("div",se,[n(i,{icon:Q(J),loading:B.value,onClick:N},{default:t(()=>[...e[4]||(e[4]=[c("上传图片",-1)])]),_:1},8,["icon","loading"]),m.value?(f(),x(i,{key:0,onClick:U},{default:t(()=>[...e[5]||(e[5]=[c("移除",-1)])]),_:1})):A("",!0),m.value?(f(),h("span",oe,D(m.value),1)):A("",!0),u("input",{ref_key:"imageInputRef",ref:g,class:"image-input",type:"file",accept:"image/*",onChange:P},null,544)])]),_:1})]),_:1}),m.value?(f(),h("div",ie,[u("img",{src:m.value,alt:"公告图片预览"},null,8,re)])):A("",!0),u("div",ue,[n(i,{type:"primary",onClick:e[2]||(e[2]=a=>z(!0))},{default:t(()=>[...e[6]||(e[6]=[c("发布并启用",-1)])]),_:1}),n(i,{onClick:e[3]||(e[3]=a=>z(!1))},{default:t(()=>[...e[7]||(e[7]=[c("保存但不启用",-1)])]),_:1}),n(i,{onClick:E},{default:t(()=>[...e[8]||(e[8]=[c("清空",-1)])]),_:1})]),e[10]||(e[10]=u("div",{class:"help"}," 说明:启用公告后,用户登录进入系统将弹窗提示;用户可选择“当次关闭”或“永久关闭本次公告”。 ",-1))]),_:1}),n(F,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:t(()=>[e[16]||(e[16]=u("h3",{class:"section-title"},"公告列表",-1)),u("div",ce,[X((f(),x(G,{data:T.value,style:{width:"100%"}},{default:t(()=>[n(y,{prop:"id",label:"ID",width:"80"}),n(y,{label:"标题","min-width":"240"},{default:t(({row:a})=>[u("span",{class:"ellipsis",title:a.title},D(a.title),9,de)]),_:1}),n(y,{label:"状态",width:"120"},{default:t(({row:a})=>[n(M,{type:a.is_active?"success":"info",effect:"light"},{default:t(()=>[c(D(a.is_active?"启用":"停用"),1)]),_:2},1032,["type"])]),_:1}),n(y,{label:"图片",width:"100"},{default:t(({row:a})=>[a.image_url?(f(),x(M,{key:0,type:"success",effect:"light"},{default:t(()=>[...e[11]||(e[11]=[c("有图",-1)])]),_:1})):(f(),h("span",me,"-"))]),_:1}),n(y,{prop:"created_at",label:"创建时间",width:"180"}),n(y,{label:"操作",width:"260",fixed:"right"},{default:t(({row:a})=>[u("div",fe,[n(i,{size:"small",onClick:V=>L(a)},{default:t(()=>[...e[12]||(e[12]=[c("查看",-1)])]),_:1},8,["onClick"]),a.is_active?(f(),x(i,{key:0,size:"small",onClick:V=>S(a)},{default:t(()=>[...e[13]||(e[13]=[c("停用",-1)])]),_:1},8,["onClick"])):(f(),x(i,{key:1,type:"success",size:"small",onClick:V=>R(a)},{default:t(()=>[...e[14]||(e[14]=[c("启用",-1)])]),_:1},8,["onClick"])),n(i,{type:"danger",size:"small",onClick:V=>j(a)},{default:t(()=>[...e[15]||(e[15]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[H,I.value]])])]),_:1})])}}},ke=W(pe,[["__scopeId","data-v-6f55521c"]]);export{ke as default}; diff --git a/static/admin/assets/EmailPage-DMQiuF7T.js b/static/admin/assets/EmailPage-yqRvXEJ2.js similarity index 99% rename from static/admin/assets/EmailPage-DMQiuF7T.js rename to static/admin/assets/EmailPage-yqRvXEJ2.js index a03929f..7e56fcf 100644 --- a/static/admin/assets/EmailPage-DMQiuF7T.js +++ b/static/admin/assets/EmailPage-yqRvXEJ2.js @@ -1 +1 @@ -import{a as De,c as He,b as Ne,f as Fe,u as Ie}from"./email-UyqcDGmM.js";import{a as U,_ as ze}from"./index-COtE2fCT.js";import{M as me}from"./MetricGrid-Bj2rgY20.js";import{E as B,a as d}from"./vendor-element-B5S5pUKo.js";import{r as m,S as pe,R as Qe,c as G,o as je,aj as c,ap as Ge,n as x,q as f,t as u,F as K,L as t,D as M,E as n,I as p,J as _,G as P,K as te,a3 as ce}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 Je(b){const{data:v}=await U.delete(`/smtp/configs/${b}`);return v}async function We(b,v){const{data:g}=await U.post(`/smtp/configs/${b}/test`,{email:v});return g}async function Xe(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=m(!1),g=m(!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 Ie({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){d.error(s?.error||"更新失败");return}d.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=m(!1),R=m([]),S=m(!1),k=m(!1),J=m(!1),H=m(!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"}]}],q=m("custom"),V=G(()=>N.find(s=>s.key===q.value)||N[0]),be=G(()=>k.value&&J.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,J.value=!1,H.value=!1,q.value="custom"}async function $(){O.value=!0;try{R.value=await Ke()}catch{R.value=[]}finally{O.value=!1}}function ge(){k.value=!1,se(),q.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,J.value=!!s.has_password,H.value=!!s.is_primary,q.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 Ve(){if(!a.host.trim()){d.error("SMTP服务器地址不能为空");return}if(!a.username.trim()){d.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){d.error(i?.error||"更新失败");return}d.success("保存成功")}else{const e={...s};a.password&&(e.password=a.password);const i=await Oe(e);if(!i?.success){d.error(i?.error||"创建失败");return}d.success("创建成功")}S.value=!1,await $()}catch{}}async function we(){if(!k.value||!a.id){d.error("请先保存配置后再测试");return}let s;try{const e=await B.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 We(a.id,s);e?.success?(d.success("测试成功,邮件已发送"),await $()):await B.alert(e?.error||"测试失败","测试失败",{confirmButtonText:"知道了"})}catch{}}async function Se(){if(!(!k.value||!a.id)){try{await B.confirm("确定将该配置设为主配置吗?","设为主配置",{confirmButtonText:"设为主配置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const s=await Xe(a.id);if(!s?.success){d.error(s?.error||"设置失败");return}d.success("已设为主配置"),S.value=!1,await $()}catch{}}}async function Te(){if(k.value){try{await B.confirm("确定取消主配置吗?取消后将按优先级选择可用SMTP。","取消主配置",{confirmButtonText:"取消主配置",cancelButtonText:"保留",type:"warning"})}catch{return}try{const s=await Ye();if(!s?.success){d.error(s?.error||"操作失败");return}d.success("已取消主配置"),S.value=!1,await $()}catch{}}}async function xe(){if(!(!k.value||!a.id)){try{await B.confirm("确定删除该SMTP配置吗?此操作不可恢复。","删除配置",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const s=await Je(a.id);if(!s?.success){d.error(s?.error||"删除失败");return}d.success("已删除"),S.value=!1,await $()}catch{}}}const A=m(!1),h=m({}),W=m(!1),F=m(""),I=m(""),z=m(1),X=m([]),Y=m(0),Z=m(1);function Pe(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 Ce=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){W.value=!0;try{const e={page:s,page_size:fe};F.value&&(e.type=F.value),I.value&&(e.status=I.value);const i=await De(e);X.value=i?.logs||[],Y.value=i?.total||0,z.value=i?.page||s,Z.value=i?.total_pages||1}catch{X.value=[],Y.value=0,Z.value=1}finally{W.value=!1}}async function Ue(){let s;try{const e=await B.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 B.confirm(`确定删除 ${s} 天之前的邮件日志吗?`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await He(s);if(!e?.success){d.error(e?.error||"清理失败");return}d.success(`已清理 ${e.deleted} 条日志`),await D(1)}catch{}}async function Le(){await Promise.all([ae(),$(),Me(),D(1)])}return je(Le),(s,e)=>{const i=c("el-switch"),r=c("el-form-item"),Ee=c("el-divider"),C=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"),w=c("el-option"),ee=c("el-select"),qe=c("el-pagination"),$e=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(),M(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(C,{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(),M(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(),M(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[33]||(e[33]=u("h3",{class:"section-title"},"邮件发送统计",-1)),t(me,{items:Ce.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(w,{label:"全部类型",value:""}),t(w,{label:"注册验证",value:"register"}),t(w,{label:"密码重置",value:"reset"}),t(w,{label:"邮箱绑定",value:"bind"}),t(w,{label:"任务完成",value:"task_complete"}),t(w,{label:"安全告警",value:"security_alert"})]),_:1},8,["modelValue"]),t(ee,{modelValue:I.value,"onUpdate:modelValue":e[8]||(e[8]=l=>I.value=l),style:{width:"120px"},onChange:e[9]||(e[9]=l=>D(1))},{default:n(()=>[t(w,{label:"全部状态",value:""}),t(w,{label:"成功",value:"success"}),t(w,{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(),M(re,{data:X.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(_(Pe(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,W.value]])]),u("div",ml,[t(qe,{"current-page":z.value,"onUpdate:currentPage":e[10]||(e[10]=l=>z.value=l),"page-size":fe,total:Y.value,layout:"prev, pager, next, ->, total",onCurrentChange:D},null,8,["current-page","total"]),u("div",pl,"第 "+_(z.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:we},{default:n(()=>[...e[36]||(e[36]=[p("测试连接",-1)])]),_:1}),k.value&&H.value?(f(),M(T,{key:0,type:"warning",plain:"",onClick:Te},{default:n(()=>[...e[37]||(e[37]=[p("取消主配置",-1)])]),_:1})):P("",!0),k.value&&!H.value?(f(),M(T,{key:1,onClick:Se},{default:n(()=>[...e[38]||(e[38]=[p("设为主配置",-1)])]),_:1})):P("",!0),k.value?(f(),M(T,{key:2,type:"danger",plain:"",onClick:xe},{default:n(()=>[...e[39]||(e[39]=[p("删除配置",-1)])]),_:1})):P("",!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:Ve},{default:n(()=>[...e[41]||(e[41]=[p("保存",-1)])]),_:1})])]),default:n(()=>[t(ie,{"label-width":"120px"},{default:n(()=>[t(r,{label:"名称"},{default:n(()=>[t(C,{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:q.value,"onUpdate:modelValue":e[13]||(e[13]=l=>q.value=l),placeholder:"选择常用邮箱模板",style:{width:"100%"},onChange:ye},{default:n(()=>[(f(),x(te,null,ce(N,l=>t(w,{key:l.key,label:l.label,value:l.key},null,8,["label","value"])),64))]),_:1},8,["modelValue"]),V.value.note||V.value.links&&V.value.links.length?(f(),x("div",fl,[V.value.note?(f(),x("span",_l,_(V.value.note),1)):P("",!0),V.value.links&&V.value.links.length?(f(),x(te,{key:1},[V.value.note?(f(),x("span",bl," · ")):P("",!0),(f(!0),x(te,null,ce(V.value.links,(l,de)=>(f(),x("span",{key:l.url},[t($e,{href:l.url,target:"_blank",type:"primary",underline:!1},{default:n(()=>[p(_(l.label),1)]),_:2},1032,["href"]),de[t(C,{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(C,{modelValue:a.username,"onUpdate:modelValue":e[16]||(e[16]=l=>a.username=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"密码"},{default:n(()=>[t(C,{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(C,{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(C,{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"])])}}},Pl=ze(gl,[["__scopeId","data-v-4f511165"]]);export{Pl as default}; +import{a as De,c as He,b as Ne,f as Fe,u as Ie}from"./email--WygXDwI.js";import{a as U,_ as ze}from"./index-BsqM_wut.js";import{M as me}from"./MetricGrid-Dsqo4YZI.js";import{E as B,a as d}from"./vendor-element-B5S5pUKo.js";import{r as m,S as pe,R as Qe,c as G,o as je,aj as c,ap as Ge,n as x,q as f,t as u,F as K,L as t,D as M,E as n,I as p,J as _,G as P,K as te,a3 as ce}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 Je(b){const{data:v}=await U.delete(`/smtp/configs/${b}`);return v}async function We(b,v){const{data:g}=await U.post(`/smtp/configs/${b}/test`,{email:v});return g}async function Xe(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=m(!1),g=m(!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 Ie({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){d.error(s?.error||"更新失败");return}d.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=m(!1),R=m([]),S=m(!1),k=m(!1),J=m(!1),H=m(!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"}]}],q=m("custom"),V=G(()=>N.find(s=>s.key===q.value)||N[0]),be=G(()=>k.value&&J.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,J.value=!1,H.value=!1,q.value="custom"}async function $(){O.value=!0;try{R.value=await Ke()}catch{R.value=[]}finally{O.value=!1}}function ge(){k.value=!1,se(),q.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,J.value=!!s.has_password,H.value=!!s.is_primary,q.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 Ve(){if(!a.host.trim()){d.error("SMTP服务器地址不能为空");return}if(!a.username.trim()){d.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){d.error(i?.error||"更新失败");return}d.success("保存成功")}else{const e={...s};a.password&&(e.password=a.password);const i=await Oe(e);if(!i?.success){d.error(i?.error||"创建失败");return}d.success("创建成功")}S.value=!1,await $()}catch{}}async function we(){if(!k.value||!a.id){d.error("请先保存配置后再测试");return}let s;try{const e=await B.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 We(a.id,s);e?.success?(d.success("测试成功,邮件已发送"),await $()):await B.alert(e?.error||"测试失败","测试失败",{confirmButtonText:"知道了"})}catch{}}async function Se(){if(!(!k.value||!a.id)){try{await B.confirm("确定将该配置设为主配置吗?","设为主配置",{confirmButtonText:"设为主配置",cancelButtonText:"取消",type:"warning"})}catch{return}try{const s=await Xe(a.id);if(!s?.success){d.error(s?.error||"设置失败");return}d.success("已设为主配置"),S.value=!1,await $()}catch{}}}async function Te(){if(k.value){try{await B.confirm("确定取消主配置吗?取消后将按优先级选择可用SMTP。","取消主配置",{confirmButtonText:"取消主配置",cancelButtonText:"保留",type:"warning"})}catch{return}try{const s=await Ye();if(!s?.success){d.error(s?.error||"操作失败");return}d.success("已取消主配置"),S.value=!1,await $()}catch{}}}async function xe(){if(!(!k.value||!a.id)){try{await B.confirm("确定删除该SMTP配置吗?此操作不可恢复。","删除配置",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const s=await Je(a.id);if(!s?.success){d.error(s?.error||"删除失败");return}d.success("已删除"),S.value=!1,await $()}catch{}}}const A=m(!1),h=m({}),W=m(!1),F=m(""),I=m(""),z=m(1),X=m([]),Y=m(0),Z=m(1);function Pe(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 Ce=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){W.value=!0;try{const e={page:s,page_size:fe};F.value&&(e.type=F.value),I.value&&(e.status=I.value);const i=await De(e);X.value=i?.logs||[],Y.value=i?.total||0,z.value=i?.page||s,Z.value=i?.total_pages||1}catch{X.value=[],Y.value=0,Z.value=1}finally{W.value=!1}}async function Ue(){let s;try{const e=await B.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 B.confirm(`确定删除 ${s} 天之前的邮件日志吗?`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await He(s);if(!e?.success){d.error(e?.error||"清理失败");return}d.success(`已清理 ${e.deleted} 条日志`),await D(1)}catch{}}async function Le(){await Promise.all([ae(),$(),Me(),D(1)])}return je(Le),(s,e)=>{const i=c("el-switch"),r=c("el-form-item"),Ee=c("el-divider"),C=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"),w=c("el-option"),ee=c("el-select"),qe=c("el-pagination"),$e=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(),M(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(C,{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(),M(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(),M(Q,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:n(()=>[e[33]||(e[33]=u("h3",{class:"section-title"},"邮件发送统计",-1)),t(me,{items:Ce.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(w,{label:"全部类型",value:""}),t(w,{label:"注册验证",value:"register"}),t(w,{label:"密码重置",value:"reset"}),t(w,{label:"邮箱绑定",value:"bind"}),t(w,{label:"任务完成",value:"task_complete"}),t(w,{label:"安全告警",value:"security_alert"})]),_:1},8,["modelValue"]),t(ee,{modelValue:I.value,"onUpdate:modelValue":e[8]||(e[8]=l=>I.value=l),style:{width:"120px"},onChange:e[9]||(e[9]=l=>D(1))},{default:n(()=>[t(w,{label:"全部状态",value:""}),t(w,{label:"成功",value:"success"}),t(w,{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(),M(re,{data:X.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(_(Pe(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,W.value]])]),u("div",ml,[t(qe,{"current-page":z.value,"onUpdate:currentPage":e[10]||(e[10]=l=>z.value=l),"page-size":fe,total:Y.value,layout:"prev, pager, next, ->, total",onCurrentChange:D},null,8,["current-page","total"]),u("div",pl,"第 "+_(z.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:we},{default:n(()=>[...e[36]||(e[36]=[p("测试连接",-1)])]),_:1}),k.value&&H.value?(f(),M(T,{key:0,type:"warning",plain:"",onClick:Te},{default:n(()=>[...e[37]||(e[37]=[p("取消主配置",-1)])]),_:1})):P("",!0),k.value&&!H.value?(f(),M(T,{key:1,onClick:Se},{default:n(()=>[...e[38]||(e[38]=[p("设为主配置",-1)])]),_:1})):P("",!0),k.value?(f(),M(T,{key:2,type:"danger",plain:"",onClick:xe},{default:n(()=>[...e[39]||(e[39]=[p("删除配置",-1)])]),_:1})):P("",!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:Ve},{default:n(()=>[...e[41]||(e[41]=[p("保存",-1)])]),_:1})])]),default:n(()=>[t(ie,{"label-width":"120px"},{default:n(()=>[t(r,{label:"名称"},{default:n(()=>[t(C,{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:q.value,"onUpdate:modelValue":e[13]||(e[13]=l=>q.value=l),placeholder:"选择常用邮箱模板",style:{width:"100%"},onChange:ye},{default:n(()=>[(f(),x(te,null,ce(N,l=>t(w,{key:l.key,label:l.label,value:l.key},null,8,["label","value"])),64))]),_:1},8,["modelValue"]),V.value.note||V.value.links&&V.value.links.length?(f(),x("div",fl,[V.value.note?(f(),x("span",_l,_(V.value.note),1)):P("",!0),V.value.links&&V.value.links.length?(f(),x(te,{key:1},[V.value.note?(f(),x("span",bl," · ")):P("",!0),(f(!0),x(te,null,ce(V.value.links,(l,de)=>(f(),x("span",{key:l.url},[t($e,{href:l.url,target:"_blank",type:"primary",underline:!1},{default:n(()=>[p(_(l.label),1)]),_:2},1032,["href"]),de[t(C,{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(C,{modelValue:a.username,"onUpdate:modelValue":e[16]||(e[16]=l=>a.username=l)},null,8,["modelValue"])]),_:1}),t(r,{label:"密码"},{default:n(()=>[t(C,{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(C,{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(C,{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"])])}}},Pl=ze(gl,[["__scopeId","data-v-4f511165"]]);export{Pl as default}; diff --git a/static/admin/assets/FeedbacksPage-DPSbObwj.js b/static/admin/assets/FeedbacksPage-9Z4ULgo9.js similarity index 97% rename from static/admin/assets/FeedbacksPage-DPSbObwj.js rename to static/admin/assets/FeedbacksPage-9Z4ULgo9.js index 594f5a0..3f954d0 100644 --- a/static/admin/assets/FeedbacksPage-DPSbObwj.js +++ b/static/admin/assets/FeedbacksPage-9Z4ULgo9.js @@ -1 +1 @@ -import{_ as $,b as j,r as G,d as L,e as q}from"./index-COtE2fCT.js";import{M as J}from"./MetricGrid-Bj2rgY20.js";import{E as w,a as k}from"./vendor-element-B5S5pUKo.js";import{i as K,r as f,c as O,o as R,aj as i,ap as U,n as x,q as v,t as s,L as t,E as l,K as F,a3 as A,J as d,F as H,D as Q,I as p,G as W}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";const X={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=K("refreshNavBadges",null),u=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=O(()=>[{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(){u.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{u.value=!1}await T?.({pendingFeedbacks:c.value.pending||0})}async function D(n){let a;try{a=(await w.prompt("请输入回复内容","回复反馈",{inputType:"textarea",inputPlaceholder:"回复内容",confirmButtonText:"提交",cancelButtonText:"取消",inputValidator:g=>!!String(g||"").trim(),inputErrorMessage:"回复内容不能为空"})).value}catch{return}try{const m=await G(n.id,String(a||"").trim());k.success(m?.message||"回复成功"),await r()}catch{}}async function E(n){try{await w.confirm("确定要关闭这个反馈吗?","关闭反馈",{confirmButtonText:"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}try{const a=await L(n.id);k.success(a?.message||"反馈已关闭"),await r()}catch{}}async function N(n){try{await w.confirm("确定要删除这个反馈吗?此操作不可恢复!","删除反馈",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const a=await q(n.id);k.success(a?.message||"反馈已删除"),await r()}catch{}}return R(r),(n,a)=>{const m=i("el-option"),g=i("el-select"),o=i("el-table-column"),y=i("el-tooltip"),I=i("el-tag"),h=i("el-button"),z=i("el-table"),P=i("el-card"),S=U("loading");return v(),x("div",X,[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(),x(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(J,{items:M.value,loading:u.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(),Q(z,{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})=>[p(d(e.contact||"-"),1)]),_:1}),t(o,{label:"状态",width:"110"},{default:l(({row:e})=>[t(I,{type:B(e.status).type,effect:"light"},{default:l(()=>[p(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(),x(F,{key:0},[t(h,{type:"primary",size:"small",onClick:C=>D(e)},{default:l(()=>[...a[3]||(a[3]=[p("回复",-1)])]),_:1},8,["onClick"]),t(h,{size:"small",onClick:C=>E(e)},{default:l(()=>[...a[4]||(a[4]=[p("关闭",-1)])]),_:1},8,["onClick"])],64)):W("",!0),t(h,{type:"danger",size:"small",onClick:C=>N(e)},{default:l(()=>[...a[5]||(a[5]=[p("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[S,u.value]])])]),_:1})])}}},fe=$(ie,[["__scopeId","data-v-910fe89b"]]);export{fe as default}; +import{_ as $,b as j,r as G,d as L,e as q}from"./index-BsqM_wut.js";import{M as J}from"./MetricGrid-Dsqo4YZI.js";import{E as w,a as k}from"./vendor-element-B5S5pUKo.js";import{i as K,r as f,c as O,o as R,aj as i,ap as U,n as x,q as v,t as s,L as t,E as l,K as F,a3 as A,J as d,F as H,D as Q,I as p,G as W}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";const X={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=K("refreshNavBadges",null),u=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=O(()=>[{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(){u.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{u.value=!1}await T?.({pendingFeedbacks:c.value.pending||0})}async function D(n){let a;try{a=(await w.prompt("请输入回复内容","回复反馈",{inputType:"textarea",inputPlaceholder:"回复内容",confirmButtonText:"提交",cancelButtonText:"取消",inputValidator:g=>!!String(g||"").trim(),inputErrorMessage:"回复内容不能为空"})).value}catch{return}try{const m=await G(n.id,String(a||"").trim());k.success(m?.message||"回复成功"),await r()}catch{}}async function E(n){try{await w.confirm("确定要关闭这个反馈吗?","关闭反馈",{confirmButtonText:"关闭",cancelButtonText:"取消",type:"warning"})}catch{return}try{const a=await L(n.id);k.success(a?.message||"反馈已关闭"),await r()}catch{}}async function N(n){try{await w.confirm("确定要删除这个反馈吗?此操作不可恢复!","删除反馈",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{const a=await q(n.id);k.success(a?.message||"反馈已删除"),await r()}catch{}}return R(r),(n,a)=>{const m=i("el-option"),g=i("el-select"),o=i("el-table-column"),y=i("el-tooltip"),I=i("el-tag"),h=i("el-button"),z=i("el-table"),P=i("el-card"),S=U("loading");return v(),x("div",X,[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(),x(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(J,{items:M.value,loading:u.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(),Q(z,{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})=>[p(d(e.contact||"-"),1)]),_:1}),t(o,{label:"状态",width:"110"},{default:l(({row:e})=>[t(I,{type:B(e.status).type,effect:"light"},{default:l(()=>[p(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(),x(F,{key:0},[t(h,{type:"primary",size:"small",onClick:C=>D(e)},{default:l(()=>[...a[3]||(a[3]=[p("回复",-1)])]),_:1},8,["onClick"]),t(h,{size:"small",onClick:C=>E(e)},{default:l(()=>[...a[4]||(a[4]=[p("关闭",-1)])]),_:1},8,["onClick"])],64)):W("",!0),t(h,{type:"danger",size:"small",onClick:C=>N(e)},{default:l(()=>[...a[5]||(a[5]=[p("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[S,u.value]])])]),_:1})])}}},fe=$(ie,[["__scopeId","data-v-910fe89b"]]);export{fe as default}; diff --git a/static/admin/assets/LogsPage-C3EIiKOD.js b/static/admin/assets/LogsPage-MDq3eoIe.js similarity index 98% rename from static/admin/assets/LogsPage-C3EIiKOD.js rename to static/admin/assets/LogsPage-MDq3eoIe.js index 738823f..7c27fd8 100644 --- a/static/admin/assets/LogsPage-C3EIiKOD.js +++ b/static/admin/assets/LogsPage-MDq3eoIe.js @@ -1 +1 @@ -import{f as K}from"./users-DXFAixXH.js";import{g as G,h as H}from"./tasks-BpEmoe5G.js";import{_ as Q}from"./index-COtE2fCT.js";import{E as N,a as X}from"./vendor-element-B5S5pUKo.js";import{r as u,c as Z,o as ee,aj as r,ap as te,n as P,q as b,t as f,L as t,E as l,K as ae,a3 as le,D as F,I as m,F as oe,J as d}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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"},B=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(""),$=Z(()=>Math.max(1,Math.ceil((y.value||0)/B)));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 K();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)*B,e={limit:B,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 G(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 j(){h.value="",w.value="",V.value="",x.value="",k.value="",i.value=1,M()}async function R(){let o;try{const e=await N.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 N.confirm(`确定要删除 ${o} 天前的所有日志吗?此操作不可恢复!`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await H(o);X.success(e?.message||"清理成功"),i.value=1,await M()}catch{}}return ee(async()=>{await z(),await M()}),(o,e)=>{const g=r("el-date-picker"),n=r("el-option"),U=r("el-select"),W=r("el-input"),C=r("el-button"),L=r("el-card"),c=r("el-table-column"),D=r("el-tag"),E=r("el-tooltip"),q=r("el-table"),A=r("el-pagination"),J=te("loading");return b(),P("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(U,{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(U,{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(U,{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),P(ae,null,le(T.value,a=>(b(),F(n,{key:a.id,label:a.username,value:String(a.id)},null,8,["label","value"]))),128))]),_:1},8,["modelValue","loading"]),t(W,{modelValue:k.value,"onUpdate:modelValue":e[4]||(e[4]=a=>k.value=a),placeholder:"账号关键字",style:{width:"170px"},clearable:""},null,8,["modelValue"]),t(C,{type:"primary",onClick:O},{default:l(()=>[...e[6]||(e[6]=[m("筛选",-1)])]),_:1}),t(C,{onClick:j},{default:l(()=>[...e[7]||(e[7]=[m("重置",-1)])]),_:1}),t(C,{type:"danger",plain:"",onClick:R},{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,[oe((b(),F(q,{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(),F(E,{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(),F(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(E,{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"])),[[J,s.value]])]),f("div",pe,[t(A,{"current-page":i.value,"onUpdate:currentPage":e[5]||(e[5]=a=>i.value=a),"page-size":B,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})])}}},Ve=Q(fe,[["__scopeId","data-v-8803eb08"]]);export{Ve as default}; +import{f as K}from"./users-BZkLUJZL.js";import{g as G,h as H}from"./tasks-OWsi7T-E.js";import{_ as Q}from"./index-BsqM_wut.js";import{E as N,a as X}from"./vendor-element-B5S5pUKo.js";import{r as u,c as Z,o as ee,aj as r,ap as te,n as P,q as b,t as f,L as t,E as l,K as ae,a3 as le,D as F,I as m,F as oe,J as d}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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"},B=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(""),$=Z(()=>Math.max(1,Math.ceil((y.value||0)/B)));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 K();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)*B,e={limit:B,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 G(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 j(){h.value="",w.value="",V.value="",x.value="",k.value="",i.value=1,M()}async function R(){let o;try{const e=await N.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 N.confirm(`确定要删除 ${o} 天前的所有日志吗?此操作不可恢复!`,"二次确认",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await H(o);X.success(e?.message||"清理成功"),i.value=1,await M()}catch{}}return ee(async()=>{await z(),await M()}),(o,e)=>{const g=r("el-date-picker"),n=r("el-option"),U=r("el-select"),W=r("el-input"),C=r("el-button"),L=r("el-card"),c=r("el-table-column"),D=r("el-tag"),E=r("el-tooltip"),q=r("el-table"),A=r("el-pagination"),J=te("loading");return b(),P("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(U,{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(U,{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(U,{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),P(ae,null,le(T.value,a=>(b(),F(n,{key:a.id,label:a.username,value:String(a.id)},null,8,["label","value"]))),128))]),_:1},8,["modelValue","loading"]),t(W,{modelValue:k.value,"onUpdate:modelValue":e[4]||(e[4]=a=>k.value=a),placeholder:"账号关键字",style:{width:"170px"},clearable:""},null,8,["modelValue"]),t(C,{type:"primary",onClick:O},{default:l(()=>[...e[6]||(e[6]=[m("筛选",-1)])]),_:1}),t(C,{onClick:j},{default:l(()=>[...e[7]||(e[7]=[m("重置",-1)])]),_:1}),t(C,{type:"danger",plain:"",onClick:R},{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,[oe((b(),F(q,{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(),F(E,{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(),F(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(E,{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"])),[[J,s.value]])]),f("div",pe,[t(A,{"current-page":i.value,"onUpdate:currentPage":e[5]||(e[5]=a=>i.value=a),"page-size":B,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})])}}},Ve=Q(fe,[["__scopeId","data-v-8803eb08"]]);export{Ve as default}; diff --git a/static/admin/assets/MetricGrid-Bj2rgY20.js b/static/admin/assets/MetricGrid-Dsqo4YZI.js similarity index 94% rename from static/admin/assets/MetricGrid-Bj2rgY20.js rename to static/admin/assets/MetricGrid-Dsqo4YZI.js index 7352b85..ea17896 100644 --- a/static/admin/assets/MetricGrid-Bj2rgY20.js +++ b/static/admin/assets/MetricGrid-Dsqo4YZI.js @@ -1 +1 @@ -import{_}from"./index-COtE2fCT.js";import{aj as c,n as s,q as t,K as r,a3 as u,y as p,t as o,G as l,L as y,E as h,D as i,H as v,J as n,I as k,x as f}from"./vendor-vue-CVxSw_oJ.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,D)=>{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(v(e.icon)))]),_:2},1024)])):l("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},w=_(N,[["__scopeId","data-v-00e217d4"]]);export{w as M}; +import{_}from"./index-BsqM_wut.js";import{aj as c,n as s,q as t,K as r,a3 as u,y as p,t as o,G as l,L as y,E as h,D as i,H as v,J as n,I as k,x as f}from"./vendor-vue-CVxSw_oJ.js";const b={class:"metric-top"},x={key:0,class:"metric-icon"},g={class:"metric-label"},B={class:"metric-value"},C={key:0,class:"metric-hint app-muted"},N={__name:"MetricGrid",props:{items:{type:Array,default:()=>[]},loading:{type:Boolean,default:!1},minWidth:{type:Number,default:180}},setup(a){return(V,D)=>{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(v(e.icon)))]),_:2},1024)])):l("",!0),o("div",g,n(e?.label||"-"),1)]),o("div",B,[a.loading?(t(),i(m,{key:0,rows:1,animated:""})):(t(),s(r,{key:1},[k(n(e?.value??0),1)],64))]),e?.hint||e?.sub?(t(),s("div",C,n(e?.hint||e?.sub),1)):l("",!0)],2))),128))],4)}}},w=_(N,[["__scopeId","data-v-00e217d4"]]);export{w as M}; diff --git a/static/admin/assets/ReportPage-B5zHPJFA.js b/static/admin/assets/ReportPage-ycVtg2rZ.js similarity index 98% rename from static/admin/assets/ReportPage-B5zHPJFA.js rename to static/admin/assets/ReportPage-ycVtg2rZ.js index b20fbb5..1877ad6 100644 --- a/static/admin/assets/ReportPage-B5zHPJFA.js +++ b/static/admin/assets/ReportPage-ycVtg2rZ.js @@ -1 +1 @@ -import{f as tl,g as al,h as sl,k as ul,j as ol,n as nl,o as rl}from"./vendor-element-B5S5pUKo.js";import{c as il,a as cl,_ as vl,f as dl}from"./index-COtE2fCT.js";import{f as _l}from"./email-UyqcDGmM.js";import{f as ml,a as pl,b as bl,c as fl,d as hl,e as wl}from"./tasks-BpEmoe5G.js";import{f as gl}from"./system-CCwKPotj.js";import{M as kl}from"./MetricGrid-Bj2rgY20.js";import{i as de,r as m,c as a,o as yl,O as Sl,aj as R,n as S,q as k,t as s,L as u,G as D,J as d,K as _e,a3 as me,E as w,I as pe,D as ql,y as xl}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-misc-BeoNyvBp.js";import"./vendor-axios-B9ygI19o.js";const $l=il(async()=>{const{data:P}=await cl.get("/browser_pool/stats");return P},4e3);async function Tl(P={}){return $l.run(P)}const Ll={class:"page-stack"},Rl={class:"report-hero"},Ml={class:"hero-head"},Al={class:"hero-main"},Pl={class:"hero-meta app-muted"},Cl={key:0},Nl={key:1,class:"hero-dot"},Il={key:2},Dl={class:"mobile-report"},El={class:"mobile-module-head"},Wl={class:"mobile-module-title"},Ql={class:"mobile-module-desc app-muted"},Vl={class:"mobile-metrics"},zl={class:"mobile-metric-label app-muted"},Bl={class:"mobile-metric-value"},Ol={key:0,class:"module-extra-actions"},Ul={class:"request-dialog-summary app-muted"},Fl={class:"request-dialog-block"},Gl={class:"table-wrap"},jl={class:"request-dialog-block"},Hl={class:"table-wrap"},Zl={class:"request-dialog-summary app-muted"},Yl={class:"request-dialog-block"},Jl={class:"table-wrap"},Kl={class:"request-dialog-block"},Xl={class:"table-wrap"},et=3,lt=5e3,tt=2e4,at={__name:"ReportPage",setup(P){const be=de("refreshStats",null),fe=de("adminStats",null),E=m(!1),W=m(!1),T=m(""),Q=m(null),b=m(null),p=m(null),y=m(null),g=m(null),C=m(null),L=m(null),f=m(null),v=m(null),r=m(null),V=m(!1),z=m(!1);m("running");function he(){try{T.value=new Date().toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{T.value=""}}function l(t){const e=Number(t);return Number.isFinite(e)?e:0}function we(t){const e=String(t??"").trim();if(!e)return 0;const o=e.endsWith("%")?e.slice(0,-1):e,_=Number(o);return!Number.isFinite(_)||_<0?0:_>1e3?1e3:_}function B(t){return`${Math.round(we(t))}%`}function c(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 O(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 ge(t){const e=String(t??"").trim();return!e||e==="manual"?"手动":e==="scheduled"?"系统定时":e==="batch"?"批量执行":e==="resumed"?"断点续跑":e.startsWith("user_scheduled:")?"用户定时":e}const Y=a(()=>{const t=fe?.value||{},e=l(b.value?.max_concurrent);return[{label:"总用户数",value:l(t.total_users),icon:tl,tone:"blue"},{label:"今日注册",value:l(t.new_users_today),icon:al,tone:"green"},{label:"近7天注册",value:l(t.new_users_7d),icon:sl,tone:"purple"},{label:"总账号数",value:l(t.total_accounts),icon:ul,tone:"cyan"},{label:"VIP用户",value:l(t.vip_users),icon:ol,tone:"orange"},{label:"运行中任务",value:l(b.value?.running_count),icon:nl,tone:"green",sub:e?`并发上限 ${e}`:""},{label:"排队任务",value:l(b.value?.queuing_count),icon:rl,tone:"purple"}]}),h=a(()=>Q.value?.today||{}),q=a(()=>Q.value?.total||{});a(()=>b.value?.running||[]);const N=a(()=>b.value?.queuing||[]),ke=a(()=>l(b.value?.running_count)),ye=a(()=>l(b.value?.queuing_count)),Se=a(()=>{const t=L.value?.workers;return Array.isArray(t)?[...t].sort((e,o)=>l(e?.worker_id)-l(o?.worker_id)):[]}),J=a(()=>l(L.value?.total_workers)),U=a(()=>Se.value.filter(t=>!!t?.has_browser).length),K=a(()=>l(L.value?.idle_workers)),X=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}),G=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(q.value.total_tasks),tone:"blue"},{label:"成功",value:l(q.value.success_tasks),tone:"green"},{label:"失败",value:l(q.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(q.value.total_items),tone:"purple"},{label:"查看附件",value:l(q.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:`${G.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:J.value,tone:"blue"},{label:"活跃 Worker",value:U.value,tone:"green"},{label:"空闲 Worker",value:K.value,tone:"cyan"},{label:"忙碌 Worker",value:F.value,tone:"orange"},{label:"队列",value:X.value,tone:"purple"}]);const qe=a(()=>(f.value?.schedule_enabled??0)===1),xe=a(()=>f.value?.schedule_time||"-"),$e=a(()=>f.value?.schedule_browse_type||"-"),Te=a(()=>String(f.value?.schedule_weekdays||"").trim());a(()=>{const t=Te.value;if(!t)return"";const e={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},o=t.split(",").map(_=>_.trim()).filter(Boolean);return o.length?o.map(_=>e[Number(_)]||_).join("、"):t});const Le=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)),Re=a(()=>l(f.value?.max_concurrent_per_account)),Me=a(()=>l(f.value?.max_screenshot_concurrent)),Ae=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||"-"}`}),Pe=a(()=>Y.value.map(t=>({label:t.label,value:t.sub?`${t.value}(${t.sub})`:t.value}))),Ce=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(q.value.total_tasks)},{label:"累计成功",value:l(q.value.success_tasks)}]),Ne=a(()=>[{label:"运行中",value:ke.value},{label:"排队中",value:ye.value},{label:"并发上限",value:l(b.value?.max_concurrent)||j.value||"-"},{label:"排队首条来源",value:ge(N.value[0]?.source)},{label:"排队首条状态",value:N.value[0]?.detail_status||N.value[0]?.status||"-"},{label:"最长等待",value:N.value[0]?.elapsed_display||"-"}]),Ie=a(()=>[{label:"总发送",value:l(p.value?.total_sent)},{label:"成功",value:l(p.value?.total_success)},{label:"失败",value:l(p.value?.total_failed)},{label:"成功率",value:`${G.value}%`},{label:"注册验证",value:l(p.value?.register_sent)},{label:"重置密码",value:l(p.value?.reset_sent)}]),De=a(()=>[{label:"总反馈",value:l(y.value?.total)},{label:"待处理",value:l(y.value?.pending)},{label:"已回复",value:l(y.value?.replied)}]),Ee=a(()=>[{label:"CPU",value:B(g.value?.cpu_percent)},{label:"内存",value:B(g.value?.memory_percent)},{label:"磁盘",value:B(g.value?.disk_percent)},{label:"容器状态",value:C.value?.status||"-"},{label:"容器名",value:C.value?.container_name||"-"},{label:"容器运行",value:C.value?.uptime||"-"}]),We=a(()=>[{label:"总 Worker",value:J.value},{label:"活跃 Worker",value:U.value},{label:"忙碌 Worker",value:F.value},{label:"空闲 Worker",value:K.value},{label:"任务队列",value:X.value}]),Qe=a(()=>{const t=v.value?.top_paths;return Array.isArray(t)?t.slice(0,3):[]}),Ve=a(()=>{const t=[{label:"总请求",value:l(v.value?.total_requests)},{label:"API请求",value:l(v.value?.api_requests)},{label:"慢请求",value:l(v.value?.slow_requests)},{label:"错误请求",value:l(v.value?.error_requests)}];return Qe.value.forEach((e,o)=>{const _=String(e?.path||"-");t.push({label:`慢接口${o+1}`,value:`${_} · 峰值 ${c(e?.max_ms)}`})}),t}),ze=a(()=>{const t=c(v.value?.avg_duration_ms),e=c(v.value?.max_duration_ms),o=Z(v.value?.last_request_ts),_=c(v.value?.slow_threshold_ms);return`均值 ${t} · 峰值 ${e} · 慢阈 ${_} · 最近 ${o}`}),Be=a(()=>(Array.isArray(r.value?.top_sql)?r.value.top_sql:[]).slice(0,3)),te=a(()=>{const t=l(r.value?.window_seconds);return t<=0?24:Math.max(1,Math.round(t/3600))}),Oe=a(()=>{const t=[{label:`慢SQL(${te.value}h)`,value:l(r.value?.total_slow_queries)},{label:"去重SQL",value:l(r.value?.unique_sql)},{label:"平均耗时",value:c(r.value?.avg_duration_ms)},{label:"峰值耗时",value:c(r.value?.max_duration_ms)}];return Be.value.forEach((e,o)=>{t.push({label:`慢SQL${o+1}`,value:`${c(e?.max_ms)} · ${String(e?.sql||"-")}`})}),t}),Ue=a(()=>{const t=c(r.value?.slow_threshold_ms),e=Z(r.value?.last_slow_ts);return`窗口 ${te.value}h · 慢阈 ${t} · 最近 ${e}`}),Fe=a(()=>(Array.isArray(r.value?.top_sql)?r.value.top_sql:[]).map((e,o)=>({rank:o+1,sql:String(e?.sql||"-"),count:l(e?.count),avg_ms:c(e?.avg_ms),max_ms:c(e?.max_ms),last_seen:O(e?.last_ts),sample_params:String(e?.sample_params||"-")}))),Ge=a(()=>[...Array.isArray(r.value?.recent_slow_sql)?r.value.recent_slow_sql:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:O(e?.time),sql:String(e?.sql||"-"),duration_ms:c(e?.duration_ms),params:String(e?.params||"-")}))),je=a(()=>(Array.isArray(v.value?.top_paths)?v.value.top_paths:[]).map((e,o)=>({rank:o+1,path:String(e?.path||"-"),count:l(e?.count),avg_ms:c(e?.avg_ms),max_ms:c(e?.max_ms),status_5xx:l(e?.status_5xx)}))),He=a(()=>[...Array.isArray(v.value?.recent_slow)?v.value.recent_slow:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:O(e?.time),method:String(e?.method||"-").toUpperCase(),path:String(e?.path||"-"),status:l(e?.status),duration_ms:c(e?.duration_ms)})));function Ze(t){const e=l(t);return e>=500?"danger":e>=400?"warning":e>=300?"info":"success"}function Ye(){V.value=!0}function Je(){z.value=!0}const ae=a(()=>{const t=r.value?.slow_threshold_ms;if(t!=null)return c(t);const e=f.value?.db_slow_query_ms;return e!=null?c(e):"-"}),Ke=a(()=>[{label:"定时任务",value:qe.value?"启用":"关闭"},{label:"执行时间",value:xe.value||"-"},{label:"浏览类型",value:$e.value||"-"},{label:"代理",value:Le.value?"启用":"关闭"},{label:"代理有效期",value:le.value?`${le.value} 分钟`:"-"},{label:"全局并发",value:j.value||"-"},{label:"单账号并发",value:Re.value||"-"},{label:"截图并发",value:Me.value||"-"},{label:"慢SQL阈值",value:ae.value}]),Xe=a(()=>[{key:"overview",title:"平台概览",desc:T.value?`更新 ${T.value}`:"核心指标",tone:"blue",items:Pe.value},{key:"task",title:"任务概览",desc:l(h.value.total_tasks)>0?`今日成功率 ${ee.value}%`:"今日暂无任务",tone:"purple",items:Ce.value},{key:"queue",title:"队列监控",desc:Ae.value,tone:"blue",items:Ne.value},{key:"email",title:"邮件报表",desc:`成功率 ${G.value}%`,tone:"cyan",items:Ie.value},{key:"feedback",title:"反馈概览",desc:`待处理 ${l(y.value?.pending)} 条`,tone:"orange",items:De.value},{key:"resource",title:"系统资源",desc:g.value?.uptime?`运行 ${g.value.uptime}`:"运行状态获取中",tone:"green",items:Ee.value},{key:"request",title:"接口性能",desc:ze.value,tone:"purple",items:Ve.value},{key:"slow_sql",title:"慢SQL监控",desc:Ue.value,tone:"red",items:Oe.value},{key:"worker",title:"截图线程池",desc:`活跃 ${U.value} · 忙碌 ${F.value}`,tone:"cyan",items:We.value},{key:"config",title:"配置概览",desc:"并发 / 代理 / 定时任务",tone:"red",items:Ke.value}]);let se=1;async function ue(t={}){const e=t.showLoading??!0;if(W.value)return;const o=!!t.forceStatsSync,_=o||se%et===0;se+=1,W.value=!0,e&&(E.value=!0);try{const[n,x,I,A,i,$,re,ie,ce,ve]=await Promise.allSettled([ml(),pl(),_l(),dl(),bl(),fl(),Tl(),hl(),wl(),gl()]);n.status==="fulfilled"&&(Q.value=n.value),x.status==="fulfilled"&&(b.value=x.value),I.status==="fulfilled"&&(p.value=I.value),A.status==="fulfilled"&&(y.value=A.value),i.status==="fulfilled"&&(g.value=i.value),$.status==="fulfilled"&&(C.value=$.value),re.status==="fulfilled"&&(L.value=re.value),ie.status==="fulfilled"&&(v.value=ie.value),ce.status==="fulfilled"&&(r.value=ce.value),ve.status==="fulfilled"&&(f.value=ve.value),_&&await be?.({force:o}),he()}finally{W.value=!1,e&&(E.value=!1)}}let M=null;function el(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function ll(){return el()?tt:lt}function oe(){M&&(clearTimeout(M),M=null)}function H(){oe(),M=window.setTimeout(async()=>{M=null,await ue({showLoading:!1}).catch(()=>{}),H()},ll())}function ne(){H()}return yl(()=>{ue({showLoading:!1}).catch(()=>{}).finally(()=>{H()}),window.addEventListener("visibilitychange",ne)}),Sl(()=>{oe(),window.removeEventListener("visibilitychange",ne)}),(t,e)=>{const o=R("el-button"),_=R("el-card"),n=R("el-table-column"),x=R("el-table"),I=R("el-tag"),A=R("el-dialog");return k(),S("div",Ll,[s("section",Rl,[s("div",Ml,[s("div",Al,[e[3]||(e[3]=s("h2",null,"报表中心",-1)),s("div",Pl,[T.value?(k(),S("span",Cl,"更新时间:"+d(T.value),1)):D("",!0),e[2]||(e[2]=s("span",{class:"hero-dot"},"·",-1)),s("span",null,"慢SQL阈值 "+d(ae.value),1),g.value?.uptime?(k(),S("span",Nl,"·")):D("",!0),g.value?.uptime?(k(),S("span",Il,"运行 "+d(g.value.uptime),1)):D("",!0)])])]),u(kl,{class:"hero-overview-grid",items:Y.value,loading:E.value,"min-width":165},null,8,["items","loading"])]),s("section",Dl,[(k(!0),S(_e,null,me(Xe.value,i=>(k(),ql(_,{key:i.key,shadow:"never",class:xl(["mobile-module-card",`mobile-tone-${i.tone}`]),"body-style":{padding:"12px"}},{default:w(()=>[s("div",El,[s("div",Wl,d(i.title),1),s("div",Ql,d(i.desc),1)]),s("div",Vl,[(k(!0),S(_e,null,me(i.items,$=>(k(),S("div",{key:`${i.key}-${$.label}`,class:"mobile-metric-item"},[s("div",zl,d($.label),1),s("div",Bl,d($.value),1)]))),128))]),i.key==="request"||i.key==="slow_sql"?(k(),S("div",Ol,[u(o,{size:"small",type:"primary",plain:"",onClick:$=>i.key==="request"?Ye():Je()},{default:w(()=>[pe(d(i.key==="request"?"查看慢接口详情":"查看慢SQL详情"),1)]),_:2},1032,["onClick"])])):D("",!0)]),_:2},1032,["class"]))),128))]),u(A,{modelValue:V.value,"onUpdate:modelValue":e[0]||(e[0]=i=>V.value=i),title:"慢接口详情",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ul,[s("span",null,"总请求:"+d(l(v.value?.total_requests)),1),s("span",null,"API请求:"+d(l(v.value?.api_requests)),1),s("span",null,"慢请求:"+d(l(v.value?.slow_requests)),1),s("span",null,"错误请求:"+d(l(v.value?.error_requests)),1)]),s("div",Fl,[e[4]||(e[4]=s("div",{class:"request-dialog-title"},"慢接口排行榜",-1)),s("div",Gl,[u(x,{data:je.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",jl,[e[5]||(e[5]=s("div",{class:"request-dialog-title"},"最近慢请求",-1)),s("div",Hl,[u(x,{data:He.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(i=>[u(I,{size:"small",type:Ze(i.row.status)},{default:w(()=>[pe(d(i.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]=i=>z.value=i),title:"慢SQL详情(近24小时)",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Zl,[s("span",null,"慢SQL总数:"+d(l(r.value?.total_slow_queries)),1),s("span",null,"去重SQL:"+d(l(r.value?.unique_sql)),1),s("span",null,"平均耗时:"+d(c(r.value?.avg_duration_ms)),1),s("span",null,"峰值耗时:"+d(c(r.value?.max_duration_ms)),1),s("span",null,"慢阈值:"+d(c(r.value?.slow_threshold_ms)),1)]),s("div",Yl,[e[6]||(e[6]=s("div",{class:"request-dialog-title"},"TOP 慢SQL(按出现次数)",-1)),s("div",Jl,[u(x,{data:Fe.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",Kl,[e[7]||(e[7]=s("div",{class:"request-dialog-title"},"最近慢SQL",-1)),s("div",Xl,[u(x,{data:Ge.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"])])}}},_t=vl(at,[["__scopeId","data-v-d610e788"]]);export{_t as default}; +import{f as tl,g as al,h as sl,k as ul,j as ol,n as nl,o as rl}from"./vendor-element-B5S5pUKo.js";import{c as il,a as cl,_ as vl,f as dl}from"./index-BsqM_wut.js";import{f as _l}from"./email--WygXDwI.js";import{f as ml,a as pl,b as bl,c as fl,d as hl,e as wl}from"./tasks-OWsi7T-E.js";import{f as gl}from"./system-CAzjuaad.js";import{M as kl}from"./MetricGrid-Dsqo4YZI.js";import{i as de,r as m,c as a,o as yl,O as Sl,aj as R,n as S,q as k,t as s,L as u,G as D,J as d,K as _e,a3 as me,E as w,I as pe,D as ql,y as xl}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-misc-BeoNyvBp.js";import"./vendor-axios-B9ygI19o.js";const $l=il(async()=>{const{data:P}=await cl.get("/browser_pool/stats");return P},4e3);async function Tl(P={}){return $l.run(P)}const Ll={class:"page-stack"},Rl={class:"report-hero"},Ml={class:"hero-head"},Al={class:"hero-main"},Pl={class:"hero-meta app-muted"},Cl={key:0},Nl={key:1,class:"hero-dot"},Il={key:2},Dl={class:"mobile-report"},El={class:"mobile-module-head"},Wl={class:"mobile-module-title"},Ql={class:"mobile-module-desc app-muted"},Vl={class:"mobile-metrics"},zl={class:"mobile-metric-label app-muted"},Bl={class:"mobile-metric-value"},Ol={key:0,class:"module-extra-actions"},Ul={class:"request-dialog-summary app-muted"},Fl={class:"request-dialog-block"},Gl={class:"table-wrap"},jl={class:"request-dialog-block"},Hl={class:"table-wrap"},Zl={class:"request-dialog-summary app-muted"},Yl={class:"request-dialog-block"},Jl={class:"table-wrap"},Kl={class:"request-dialog-block"},Xl={class:"table-wrap"},et=3,lt=5e3,tt=2e4,at={__name:"ReportPage",setup(P){const be=de("refreshStats",null),fe=de("adminStats",null),E=m(!1),W=m(!1),T=m(""),Q=m(null),b=m(null),p=m(null),y=m(null),g=m(null),C=m(null),L=m(null),f=m(null),v=m(null),r=m(null),V=m(!1),z=m(!1);m("running");function he(){try{T.value=new Date().toLocaleString("zh-CN",{hour12:!1,timeZone:"Asia/Shanghai"})}catch{T.value=""}}function l(t){const e=Number(t);return Number.isFinite(e)?e:0}function we(t){const e=String(t??"").trim();if(!e)return 0;const o=e.endsWith("%")?e.slice(0,-1):e,_=Number(o);return!Number.isFinite(_)||_<0?0:_>1e3?1e3:_}function B(t){return`${Math.round(we(t))}%`}function c(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 O(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 ge(t){const e=String(t??"").trim();return!e||e==="manual"?"手动":e==="scheduled"?"系统定时":e==="batch"?"批量执行":e==="resumed"?"断点续跑":e.startsWith("user_scheduled:")?"用户定时":e}const Y=a(()=>{const t=fe?.value||{},e=l(b.value?.max_concurrent);return[{label:"总用户数",value:l(t.total_users),icon:tl,tone:"blue"},{label:"今日注册",value:l(t.new_users_today),icon:al,tone:"green"},{label:"近7天注册",value:l(t.new_users_7d),icon:sl,tone:"purple"},{label:"总账号数",value:l(t.total_accounts),icon:ul,tone:"cyan"},{label:"VIP用户",value:l(t.vip_users),icon:ol,tone:"orange"},{label:"运行中任务",value:l(b.value?.running_count),icon:nl,tone:"green",sub:e?`并发上限 ${e}`:""},{label:"排队任务",value:l(b.value?.queuing_count),icon:rl,tone:"purple"}]}),h=a(()=>Q.value?.today||{}),q=a(()=>Q.value?.total||{});a(()=>b.value?.running||[]);const N=a(()=>b.value?.queuing||[]),ke=a(()=>l(b.value?.running_count)),ye=a(()=>l(b.value?.queuing_count)),Se=a(()=>{const t=L.value?.workers;return Array.isArray(t)?[...t].sort((e,o)=>l(e?.worker_id)-l(o?.worker_id)):[]}),J=a(()=>l(L.value?.total_workers)),U=a(()=>Se.value.filter(t=>!!t?.has_browser).length),K=a(()=>l(L.value?.idle_workers)),X=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}),G=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(q.value.total_tasks),tone:"blue"},{label:"成功",value:l(q.value.success_tasks),tone:"green"},{label:"失败",value:l(q.value.failed_tasks),tone:"red"},{label:"浏览内容",value:l(q.value.total_items),tone:"purple"},{label:"查看附件",value:l(q.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:`${G.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:J.value,tone:"blue"},{label:"活跃 Worker",value:U.value,tone:"green"},{label:"空闲 Worker",value:K.value,tone:"cyan"},{label:"忙碌 Worker",value:F.value,tone:"orange"},{label:"队列",value:X.value,tone:"purple"}]);const qe=a(()=>(f.value?.schedule_enabled??0)===1),xe=a(()=>f.value?.schedule_time||"-"),$e=a(()=>f.value?.schedule_browse_type||"-"),Te=a(()=>String(f.value?.schedule_weekdays||"").trim());a(()=>{const t=Te.value;if(!t)return"";const e={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},o=t.split(",").map(_=>_.trim()).filter(Boolean);return o.length?o.map(_=>e[Number(_)]||_).join("、"):t});const Le=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)),Re=a(()=>l(f.value?.max_concurrent_per_account)),Me=a(()=>l(f.value?.max_screenshot_concurrent)),Ae=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||"-"}`}),Pe=a(()=>Y.value.map(t=>({label:t.label,value:t.sub?`${t.value}(${t.sub})`:t.value}))),Ce=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(q.value.total_tasks)},{label:"累计成功",value:l(q.value.success_tasks)}]),Ne=a(()=>[{label:"运行中",value:ke.value},{label:"排队中",value:ye.value},{label:"并发上限",value:l(b.value?.max_concurrent)||j.value||"-"},{label:"排队首条来源",value:ge(N.value[0]?.source)},{label:"排队首条状态",value:N.value[0]?.detail_status||N.value[0]?.status||"-"},{label:"最长等待",value:N.value[0]?.elapsed_display||"-"}]),Ie=a(()=>[{label:"总发送",value:l(p.value?.total_sent)},{label:"成功",value:l(p.value?.total_success)},{label:"失败",value:l(p.value?.total_failed)},{label:"成功率",value:`${G.value}%`},{label:"注册验证",value:l(p.value?.register_sent)},{label:"重置密码",value:l(p.value?.reset_sent)}]),De=a(()=>[{label:"总反馈",value:l(y.value?.total)},{label:"待处理",value:l(y.value?.pending)},{label:"已回复",value:l(y.value?.replied)}]),Ee=a(()=>[{label:"CPU",value:B(g.value?.cpu_percent)},{label:"内存",value:B(g.value?.memory_percent)},{label:"磁盘",value:B(g.value?.disk_percent)},{label:"容器状态",value:C.value?.status||"-"},{label:"容器名",value:C.value?.container_name||"-"},{label:"容器运行",value:C.value?.uptime||"-"}]),We=a(()=>[{label:"总 Worker",value:J.value},{label:"活跃 Worker",value:U.value},{label:"忙碌 Worker",value:F.value},{label:"空闲 Worker",value:K.value},{label:"任务队列",value:X.value}]),Qe=a(()=>{const t=v.value?.top_paths;return Array.isArray(t)?t.slice(0,3):[]}),Ve=a(()=>{const t=[{label:"总请求",value:l(v.value?.total_requests)},{label:"API请求",value:l(v.value?.api_requests)},{label:"慢请求",value:l(v.value?.slow_requests)},{label:"错误请求",value:l(v.value?.error_requests)}];return Qe.value.forEach((e,o)=>{const _=String(e?.path||"-");t.push({label:`慢接口${o+1}`,value:`${_} · 峰值 ${c(e?.max_ms)}`})}),t}),ze=a(()=>{const t=c(v.value?.avg_duration_ms),e=c(v.value?.max_duration_ms),o=Z(v.value?.last_request_ts),_=c(v.value?.slow_threshold_ms);return`均值 ${t} · 峰值 ${e} · 慢阈 ${_} · 最近 ${o}`}),Be=a(()=>(Array.isArray(r.value?.top_sql)?r.value.top_sql:[]).slice(0,3)),te=a(()=>{const t=l(r.value?.window_seconds);return t<=0?24:Math.max(1,Math.round(t/3600))}),Oe=a(()=>{const t=[{label:`慢SQL(${te.value}h)`,value:l(r.value?.total_slow_queries)},{label:"去重SQL",value:l(r.value?.unique_sql)},{label:"平均耗时",value:c(r.value?.avg_duration_ms)},{label:"峰值耗时",value:c(r.value?.max_duration_ms)}];return Be.value.forEach((e,o)=>{t.push({label:`慢SQL${o+1}`,value:`${c(e?.max_ms)} · ${String(e?.sql||"-")}`})}),t}),Ue=a(()=>{const t=c(r.value?.slow_threshold_ms),e=Z(r.value?.last_slow_ts);return`窗口 ${te.value}h · 慢阈 ${t} · 最近 ${e}`}),Fe=a(()=>(Array.isArray(r.value?.top_sql)?r.value.top_sql:[]).map((e,o)=>({rank:o+1,sql:String(e?.sql||"-"),count:l(e?.count),avg_ms:c(e?.avg_ms),max_ms:c(e?.max_ms),last_seen:O(e?.last_ts),sample_params:String(e?.sample_params||"-")}))),Ge=a(()=>[...Array.isArray(r.value?.recent_slow_sql)?r.value.recent_slow_sql:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:O(e?.time),sql:String(e?.sql||"-"),duration_ms:c(e?.duration_ms),params:String(e?.params||"-")}))),je=a(()=>(Array.isArray(v.value?.top_paths)?v.value.top_paths:[]).map((e,o)=>({rank:o+1,path:String(e?.path||"-"),count:l(e?.count),avg_ms:c(e?.avg_ms),max_ms:c(e?.max_ms),status_5xx:l(e?.status_5xx)}))),He=a(()=>[...Array.isArray(v.value?.recent_slow)?v.value.recent_slow:[]].sort((e,o)=>Number(o?.time||0)-Number(e?.time||0)).map(e=>({time_text:O(e?.time),method:String(e?.method||"-").toUpperCase(),path:String(e?.path||"-"),status:l(e?.status),duration_ms:c(e?.duration_ms)})));function Ze(t){const e=l(t);return e>=500?"danger":e>=400?"warning":e>=300?"info":"success"}function Ye(){V.value=!0}function Je(){z.value=!0}const ae=a(()=>{const t=r.value?.slow_threshold_ms;if(t!=null)return c(t);const e=f.value?.db_slow_query_ms;return e!=null?c(e):"-"}),Ke=a(()=>[{label:"定时任务",value:qe.value?"启用":"关闭"},{label:"执行时间",value:xe.value||"-"},{label:"浏览类型",value:$e.value||"-"},{label:"代理",value:Le.value?"启用":"关闭"},{label:"代理有效期",value:le.value?`${le.value} 分钟`:"-"},{label:"全局并发",value:j.value||"-"},{label:"单账号并发",value:Re.value||"-"},{label:"截图并发",value:Me.value||"-"},{label:"慢SQL阈值",value:ae.value}]),Xe=a(()=>[{key:"overview",title:"平台概览",desc:T.value?`更新 ${T.value}`:"核心指标",tone:"blue",items:Pe.value},{key:"task",title:"任务概览",desc:l(h.value.total_tasks)>0?`今日成功率 ${ee.value}%`:"今日暂无任务",tone:"purple",items:Ce.value},{key:"queue",title:"队列监控",desc:Ae.value,tone:"blue",items:Ne.value},{key:"email",title:"邮件报表",desc:`成功率 ${G.value}%`,tone:"cyan",items:Ie.value},{key:"feedback",title:"反馈概览",desc:`待处理 ${l(y.value?.pending)} 条`,tone:"orange",items:De.value},{key:"resource",title:"系统资源",desc:g.value?.uptime?`运行 ${g.value.uptime}`:"运行状态获取中",tone:"green",items:Ee.value},{key:"request",title:"接口性能",desc:ze.value,tone:"purple",items:Ve.value},{key:"slow_sql",title:"慢SQL监控",desc:Ue.value,tone:"red",items:Oe.value},{key:"worker",title:"截图线程池",desc:`活跃 ${U.value} · 忙碌 ${F.value}`,tone:"cyan",items:We.value},{key:"config",title:"配置概览",desc:"并发 / 代理 / 定时任务",tone:"red",items:Ke.value}]);let se=1;async function ue(t={}){const e=t.showLoading??!0;if(W.value)return;const o=!!t.forceStatsSync,_=o||se%et===0;se+=1,W.value=!0,e&&(E.value=!0);try{const[n,x,I,A,i,$,re,ie,ce,ve]=await Promise.allSettled([ml(),pl(),_l(),dl(),bl(),fl(),Tl(),hl(),wl(),gl()]);n.status==="fulfilled"&&(Q.value=n.value),x.status==="fulfilled"&&(b.value=x.value),I.status==="fulfilled"&&(p.value=I.value),A.status==="fulfilled"&&(y.value=A.value),i.status==="fulfilled"&&(g.value=i.value),$.status==="fulfilled"&&(C.value=$.value),re.status==="fulfilled"&&(L.value=re.value),ie.status==="fulfilled"&&(v.value=ie.value),ce.status==="fulfilled"&&(r.value=ce.value),ve.status==="fulfilled"&&(f.value=ve.value),_&&await be?.({force:o}),he()}finally{W.value=!1,e&&(E.value=!1)}}let M=null;function el(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function ll(){return el()?tt:lt}function oe(){M&&(clearTimeout(M),M=null)}function H(){oe(),M=window.setTimeout(async()=>{M=null,await ue({showLoading:!1}).catch(()=>{}),H()},ll())}function ne(){H()}return yl(()=>{ue({showLoading:!1}).catch(()=>{}).finally(()=>{H()}),window.addEventListener("visibilitychange",ne)}),Sl(()=>{oe(),window.removeEventListener("visibilitychange",ne)}),(t,e)=>{const o=R("el-button"),_=R("el-card"),n=R("el-table-column"),x=R("el-table"),I=R("el-tag"),A=R("el-dialog");return k(),S("div",Ll,[s("section",Rl,[s("div",Ml,[s("div",Al,[e[3]||(e[3]=s("h2",null,"报表中心",-1)),s("div",Pl,[T.value?(k(),S("span",Cl,"更新时间:"+d(T.value),1)):D("",!0),e[2]||(e[2]=s("span",{class:"hero-dot"},"·",-1)),s("span",null,"慢SQL阈值 "+d(ae.value),1),g.value?.uptime?(k(),S("span",Nl,"·")):D("",!0),g.value?.uptime?(k(),S("span",Il,"运行 "+d(g.value.uptime),1)):D("",!0)])])]),u(kl,{class:"hero-overview-grid",items:Y.value,loading:E.value,"min-width":165},null,8,["items","loading"])]),s("section",Dl,[(k(!0),S(_e,null,me(Xe.value,i=>(k(),ql(_,{key:i.key,shadow:"never",class:xl(["mobile-module-card",`mobile-tone-${i.tone}`]),"body-style":{padding:"12px"}},{default:w(()=>[s("div",El,[s("div",Wl,d(i.title),1),s("div",Ql,d(i.desc),1)]),s("div",Vl,[(k(!0),S(_e,null,me(i.items,$=>(k(),S("div",{key:`${i.key}-${$.label}`,class:"mobile-metric-item"},[s("div",zl,d($.label),1),s("div",Bl,d($.value),1)]))),128))]),i.key==="request"||i.key==="slow_sql"?(k(),S("div",Ol,[u(o,{size:"small",type:"primary",plain:"",onClick:$=>i.key==="request"?Ye():Je()},{default:w(()=>[pe(d(i.key==="request"?"查看慢接口详情":"查看慢SQL详情"),1)]),_:2},1032,["onClick"])])):D("",!0)]),_:2},1032,["class"]))),128))]),u(A,{modelValue:V.value,"onUpdate:modelValue":e[0]||(e[0]=i=>V.value=i),title:"慢接口详情",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Ul,[s("span",null,"总请求:"+d(l(v.value?.total_requests)),1),s("span",null,"API请求:"+d(l(v.value?.api_requests)),1),s("span",null,"慢请求:"+d(l(v.value?.slow_requests)),1),s("span",null,"错误请求:"+d(l(v.value?.error_requests)),1)]),s("div",Fl,[e[4]||(e[4]=s("div",{class:"request-dialog-title"},"慢接口排行榜",-1)),s("div",Gl,[u(x,{data:je.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",jl,[e[5]||(e[5]=s("div",{class:"request-dialog-title"},"最近慢请求",-1)),s("div",Hl,[u(x,{data:He.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(i=>[u(I,{size:"small",type:Ze(i.row.status)},{default:w(()=>[pe(d(i.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]=i=>z.value=i),title:"慢SQL详情(近24小时)",width:"min(1080px, 96vw)"},{default:w(()=>[s("div",Zl,[s("span",null,"慢SQL总数:"+d(l(r.value?.total_slow_queries)),1),s("span",null,"去重SQL:"+d(l(r.value?.unique_sql)),1),s("span",null,"平均耗时:"+d(c(r.value?.avg_duration_ms)),1),s("span",null,"峰值耗时:"+d(c(r.value?.max_duration_ms)),1),s("span",null,"慢阈值:"+d(c(r.value?.slow_threshold_ms)),1)]),s("div",Yl,[e[6]||(e[6]=s("div",{class:"request-dialog-title"},"TOP 慢SQL(按出现次数)",-1)),s("div",Jl,[u(x,{data:Fe.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",Kl,[e[7]||(e[7]=s("div",{class:"request-dialog-title"},"最近慢SQL",-1)),s("div",Xl,[u(x,{data:Ge.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"])])}}},_t=vl(at,[["__scopeId","data-v-d610e788"]]);export{_t as default}; diff --git a/static/admin/assets/SecurityPage-ClrVJm-9.js b/static/admin/assets/SecurityPage-CXcU2SbL.js similarity index 99% rename from static/admin/assets/SecurityPage-ClrVJm-9.js rename to static/admin/assets/SecurityPage-CXcU2SbL.js index 8174cb6..d05561f 100644 --- a/static/admin/assets/SecurityPage-ClrVJm-9.js +++ b/static/admin/assets/SecurityPage-CXcU2SbL.js @@ -1,4 +1,4 @@ -import{a as g,_ as qe}from"./index-COtE2fCT.js";import{M as Ee}from"./MetricGrid-Bj2rgY20.js";import{E as ee,a as w}from"./vendor-element-B5S5pUKo.js";import{r as d,c as oe,o as ze,aj as v,ap as Ae,n as P,q as c,t as p,L as a,E as l,I as i,K as Ge,a3 as Ke,D as b,F as te,J as f,G as ue}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";async function Oe(){const{data:u}=await g.get("/admin/security/dashboard");return u}async function He(u){const{data:m}=await g.get("/admin/security/threats",{params:u});return m}async function Je(){const{data:u}=await g.get("/admin/security/banned-ips");return u}async function Qe(){const{data:u}=await g.get("/admin/security/banned-users");return u}async function We(u){const{data:m}=await g.post("/admin/security/ban-ip",u);return m}async function Xe(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([]),E=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}),z=d("ip"),k=d(!1),A=d(""),G=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 K(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((E.value||0)/re)));async function N(){V.value=!0;try{L.value=await Oe()}catch{L.value=null}finally{V.value=!1}}async function O(){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 He(n);q.value=e?.items||[],E.value=e?.total||0}catch{q.value=[],E.value=0}finally{ae.value=!1}}async function F(){if(!$.value){$.value=!0;try{const[n,e]=await Promise.allSettled([Je(),Qe()]);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(),O(),F()])}function Se(){C.value=1,O()}function Te(){U.value="",B.value="",C.value=1,O()}function _e(){o.value={kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}}function H(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 We({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 Q=String(o.value.user_id||"").trim(),r=Number.parseInt(Q,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 ee.confirm(`确定解除对 IP ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Xe(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 ee.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",z.value="ip",A.value=e,J())}function ke(n){const e=String(n||"").trim();e&&(m.value="risk",z.value="user",G.value=e,ne())}async function J(){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(G.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"?H("ip",{ip:_.value?.ip,reason:"风险查询手动封禁"}):H("user",{user_id:_.value?.user_id,reason:"风险查询手动封禁"}))}async function Be(){!_.value||!h.value||(h.value==="ip"?(await ye(_.value?.ip),await J()):(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 ee.confirm(`确定清除 IP ${n} 的风险分吗? +import{a as g,_ as qe}from"./index-BsqM_wut.js";import{M as Ee}from"./MetricGrid-Dsqo4YZI.js";import{E as ee,a as w}from"./vendor-element-B5S5pUKo.js";import{r as d,c as oe,o as ze,aj as v,ap as Ae,n as P,q as c,t as p,L as a,E as l,I as i,K as Ge,a3 as Ke,D as b,F as te,J as f,G as ue}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";async function Oe(){const{data:u}=await g.get("/admin/security/dashboard");return u}async function He(u){const{data:m}=await g.get("/admin/security/threats",{params:u});return m}async function Je(){const{data:u}=await g.get("/admin/security/banned-ips");return u}async function Qe(){const{data:u}=await g.get("/admin/security/banned-users");return u}async function We(u){const{data:m}=await g.post("/admin/security/ban-ip",u);return m}async function Xe(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([]),E=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}),z=d("ip"),k=d(!1),A=d(""),G=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 K(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((E.value||0)/re)));async function N(){V.value=!0;try{L.value=await Oe()}catch{L.value=null}finally{V.value=!1}}async function O(){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 He(n);q.value=e?.items||[],E.value=e?.total||0}catch{q.value=[],E.value=0}finally{ae.value=!1}}async function F(){if(!$.value){$.value=!0;try{const[n,e]=await Promise.allSettled([Je(),Qe()]);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(),O(),F()])}function Se(){C.value=1,O()}function Te(){U.value="",B.value="",C.value=1,O()}function _e(){o.value={kind:"ip",ip:"",user_id:"",reason:"",duration_hours:24,permanent:!1}}function H(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 We({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 Q=String(o.value.user_id||"").trim(),r=Number.parseInt(Q,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 ee.confirm(`确定解除对 IP ${e} 的封禁吗?`,"解除封禁",{confirmButtonText:"解除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Xe(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 ee.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",z.value="ip",A.value=e,J())}function ke(n){const e=String(n||"").trim();e&&(m.value="risk",z.value="user",G.value=e,ne())}async function J(){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(G.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"?H("ip",{ip:_.value?.ip,reason:"风险查询手动封禁"}):H("user",{user_id:_.value?.user_id,reason:"风险查询手动封禁"}))}async function Be(){!_.value||!h.value||(h.value==="ip"?(await ye(_.value?.ip),await J()):(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 ee.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 J()}}}const ie=d(!1);async function Re(){try{await ee.confirm(`确定清理过期封禁记录,并衰减风险分吗? diff --git a/static/admin/assets/SettingsPage-COD6KO3P.js b/static/admin/assets/SettingsPage-COD6KO3P.js deleted file mode 100644 index ac04baa..0000000 --- a/static/admin/assets/SettingsPage-COD6KO3P.js +++ /dev/null @@ -1 +0,0 @@ -import{a as m,_ as h}from"./index-COtE2fCT.js";import{a as u,E as x}from"./vendor-element-B5S5pUKo.js";import{r as p,aj as i,n as T,q as E,t as r,L as a,E as o,I as b}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";async function P(l){const{data:s}=await m.put("/admin/username",{new_username:l});return s}async function C(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){u.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${t}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}n.value=!0;try{await P(t),u.success("用户名修改成功,请重新登录"),s.value="",setTimeout(f,1200)}catch{}finally{n.value=!1}}async function B(){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 C(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 E(),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(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:V},{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:B},{default:o(()=>[...e[4]||(e[4]=[b("保存密码",-1)])]),_:1},8,["loading"]),e[6]||(e[6]=r("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},z=h(A,[["__scopeId","data-v-83d3840a"]]);export{z as default}; diff --git a/static/admin/assets/SettingsPage-CUZAbAFF.js b/static/admin/assets/SettingsPage-CUZAbAFF.js new file mode 100644 index 0000000..920c4ac --- /dev/null +++ b/static/admin/assets/SettingsPage-CUZAbAFF.js @@ -0,0 +1 @@ +import{a as g,_ as T}from"./index-BsqM_wut.js";import{a as d,E as x}from"./vendor-element-B5S5pUKo.js";import{r as f,aj as w,n as S,q as U,t as m,L as a,E as s,I as P}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.js";async function E(n){const{data:o}=await g.put("/admin/username",{new_username:n});return o}async function C(n={}){const o=String(n.currentPassword||""),i=String(n.newPassword||""),{data:c}=await g.put("/admin/password",{current_password:o,new_password:i});return c}async function A(){const{data:n}=await g.post("/logout");return n}const N={class:"page-stack"},I={__name:"SettingsPage",setup(n){const o=f(""),i=f(""),c=f(""),v=f(""),t=f(!1);function k(l){const e=String(l||"");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 y(){try{await A()}catch{}finally{window.location.href="/yuyx"}}async function B(){const l=o.value.trim();if(!l){d.error("请输入新用户名");return}try{await x.confirm(`确定将管理员用户名修改为「${l}」吗?修改后需要重新登录。`,"修改用户名",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}t.value=!0;try{await E(l),d.success("用户名修改成功,请重新登录"),o.value="",setTimeout(y,1200)}catch{}finally{t.value=!1}}async function h(){const l=i.value,e=c.value,p=v.value;if(!l){d.error("请输入当前密码");return}if(!e){d.error("请输入新密码");return}const r=k(e);if(!r.ok){d.error(r.message);return}if(e!==p){d.error("两次输入的新密码不一致");return}try{await x.confirm("确定修改管理员密码吗?修改后需要重新登录。","修改密码",{confirmButtonText:"确认修改",cancelButtonText:"取消",type:"warning"})}catch{return}t.value=!0;try{await C({currentPassword:l,newPassword:e}),d.success("密码修改成功,请重新登录"),i.value="",c.value="",v.value="",setTimeout(y,1200)}catch{}finally{t.value=!1}}return(l,e)=>{const p=w("el-input"),r=w("el-form-item"),_=w("el-form"),b=w("el-button"),V=w("el-card");return U(),S("div",N,[e[9]||(e[9]=m("div",{class:"app-page-title"},[m("h2",null,"设置"),m("span",{class:"app-muted"},"管理员账号设置")],-1)),a(V,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[5]||(e[5]=m("h3",{class:"section-title"},"修改管理员用户名",-1)),a(_,{"label-width":"120px"},{default:s(()=>[a(r,{label:"新用户名"},{default:s(()=>[a(p,{modelValue:o.value,"onUpdate:modelValue":e[0]||(e[0]=u=>o.value=u),placeholder:"输入新用户名",disabled:t.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(b,{type:"primary",loading:t.value,onClick:B},{default:s(()=>[...e[4]||(e[4]=[P("保存用户名",-1)])]),_:1},8,["loading"])]),_:1}),a(V,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[e[7]||(e[7]=m("h3",{class:"section-title"},"修改管理员密码",-1)),a(_,{"label-width":"120px"},{default:s(()=>[a(r,{label:"当前密码"},{default:s(()=>[a(p,{modelValue:i.value,"onUpdate:modelValue":e[1]||(e[1]=u=>i.value=u),type:"password","show-password":"",placeholder:"输入当前密码",disabled:t.value},null,8,["modelValue","disabled"])]),_:1}),a(r,{label:"新密码"},{default:s(()=>[a(p,{modelValue:c.value,"onUpdate:modelValue":e[2]||(e[2]=u=>c.value=u),type:"password","show-password":"",placeholder:"输入新密码",disabled:t.value},null,8,["modelValue","disabled"])]),_:1}),a(r,{label:"确认新密码"},{default:s(()=>[a(p,{modelValue:v.value,"onUpdate:modelValue":e[3]||(e[3]=u=>v.value=u),type:"password","show-password":"",placeholder:"再次输入新密码",disabled:t.value},null,8,["modelValue","disabled"])]),_:1})]),_:1}),a(b,{type:"primary",loading:t.value,onClick:h},{default:s(()=>[...e[6]||(e[6]=[P("保存密码",-1)])]),_:1},8,["loading"]),e[8]||(e[8]=m("div",{class:"help"},"建议使用更强密码(至少8位且包含字母与数字)。",-1))]),_:1})])}}},Z=T(I,[["__scopeId","data-v-be652d2b"]]);export{Z as default}; diff --git a/static/admin/assets/SettingsPage-DaB8PeRL.css b/static/admin/assets/SettingsPage-DaB8PeRL.css deleted file mode 100644 index 26ef1b1..0000000 --- a/static/admin/assets/SettingsPage-DaB8PeRL.css +++ /dev/null @@ -1 +0,0 @@ -.page-stack[data-v-83d3840a]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-83d3840a]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-83d3840a]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-83d3840a]{margin-top:10px;font-size:12px;color:var(--app-muted)} diff --git a/static/admin/assets/SettingsPage-NWcEVLn7.css b/static/admin/assets/SettingsPage-NWcEVLn7.css new file mode 100644 index 0000000..22a16c9 --- /dev/null +++ b/static/admin/assets/SettingsPage-NWcEVLn7.css @@ -0,0 +1 @@ +.page-stack[data-v-be652d2b]{display:flex;flex-direction:column;gap:14px;min-width:0}.card[data-v-be652d2b]{border-radius:var(--app-radius);border:1px solid var(--app-border);background:var(--app-card-bg);box-shadow:var(--app-shadow-soft)}.section-title[data-v-be652d2b]{margin:0 0 12px;font-size:15px;font-weight:800;letter-spacing:.2px}.help[data-v-be652d2b]{margin-top:10px;font-size:12px;color:var(--app-muted)} diff --git a/static/admin/assets/SystemPage-CkuZRgDH.js b/static/admin/assets/SystemPage-B2BrKkTP.js similarity index 99% rename from static/admin/assets/SystemPage-CkuZRgDH.js rename to static/admin/assets/SystemPage-B2BrKkTP.js index a016458..2447983 100644 --- a/static/admin/assets/SystemPage-CkuZRgDH.js +++ b/static/admin/assets/SystemPage-B2BrKkTP.js @@ -1,4 +1,4 @@ -import{f as be,u as Z}from"./system-CCwKPotj.js";import{a as P,_ as ge}from"./index-COtE2fCT.js";import{E as ue,a as m}from"./vendor-element-B5S5pUKo.js";import{r as n,c as ke,l as xe,R as we,o as Ue,aj as v,ap as Ce,F as Pe,q as V,n as b,t as s,L as l,E as t,I as g,G as ee,J as le}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 Ee={class:"page-stack"},Ke={class:"config-grid"},Le={class:"row-actions"},Qe={class:"row-actions"},qe={class:"row-actions"},Be={class:"section-head"},Te={class:"status-inline app-muted"},$e={key:0},Me={key:1},Re={key:2},Fe={class:"kdocs-inline"},He={class:"kdocs-range"},he={class:"row-actions"},ze={key:0,class:"help"},Ge={key:1,class:"help"},Oe={class:"kdocs-qr"},je=["src"],Je={__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),E=n(!1),K=n(10),L=n(7),Q=n(!1),q=n(""),B=n(""),T=n(""),$=n(0),M=n("A"),R=n("D"),F=n(0),H=n(0),h=n(!1),z=n(""),p=n({}),x=n(!1),w=n(""),oe=n(!1),G=n(!1),U=n(!1),C=n(!1),O=n("");let j=null;const te=ke(()=>G.value||U.value||C.value);function i(a){if(!a){O.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});O.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,E.value=(a.auto_approve_enabled??0)===1,K.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,q.value=a.kdocs_doc_url||"",B.value=a.kdocs_default_unit||"",T.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(),F.value=a.kdocs_row_start??0,H.value=a.kdocs_row_end??0,h.value=(a.kdocs_admin_notify_enabled??0)===1,z.value=a.kdocs_admin_notify_email||"",p.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(`确定更新并发配置吗? +import{f as be,u as Z}from"./system-CAzjuaad.js";import{a as P,_ as ge}from"./index-BsqM_wut.js";import{E as ue,a as m}from"./vendor-element-B5S5pUKo.js";import{r as n,c as ke,l as xe,R as we,o as Ue,aj as v,ap as Ce,F as Pe,q as V,n as b,t as s,L as l,E as t,I as g,G as ee,J as le}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 Ee={class:"page-stack"},Ke={class:"config-grid"},Le={class:"row-actions"},Qe={class:"row-actions"},qe={class:"row-actions"},Be={class:"section-head"},Te={class:"status-inline app-muted"},$e={key:0},Me={key:1},Re={key:2},Fe={class:"kdocs-inline"},He={class:"kdocs-range"},he={class:"row-actions"},ze={key:0,class:"help"},Ge={key:1,class:"help"},Oe={class:"kdocs-qr"},je=["src"],Je={__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),E=n(!1),K=n(10),L=n(7),Q=n(!1),q=n(""),B=n(""),T=n(""),$=n(0),M=n("A"),R=n("D"),F=n(0),H=n(0),h=n(!1),z=n(""),p=n({}),x=n(!1),w=n(""),oe=n(!1),G=n(!1),U=n(!1),C=n(!1),O=n("");let j=null;const te=ke(()=>G.value||U.value||C.value);function i(a){if(!a){O.value="";return}const e=new Date().toLocaleTimeString("zh-CN",{hour12:!1});O.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,E.value=(a.auto_approve_enabled??0)===1,K.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,q.value=a.kdocs_doc_url||"",B.value=a.kdocs_default_unit||"",T.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(),F.value=a.kdocs_row_start??0,H.value=a.kdocs_row_end??0,h.value=(a.kdocs_admin_notify_enabled??0)===1,z.value=a.kdocs_admin_notify_email||"",p.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} diff --git a/static/admin/assets/UsersPage-CwNX_aN-.js b/static/admin/assets/UsersPage-yptpHEoN.js similarity index 99% rename from static/admin/assets/UsersPage-CwNX_aN-.js rename to static/admin/assets/UsersPage-yptpHEoN.js index cdb32cd..6cffd9c 100644 --- a/static/admin/assets/UsersPage-CwNX_aN-.js +++ b/static/admin/assets/UsersPage-yptpHEoN.js @@ -1 +1 @@ -import{a as W,r as Z,s as F,b as G,c as J,d as H,f as K}from"./users-DXFAixXH.js";import{_ as O}from"./index-COtE2fCT.js";import{E as v,a as h}from"./vendor-element-B5S5pUKo.js";import{i as Q,r as P,o as X,aj as _,ap as Y,n as V,q as r,t as y,L as l,E as s,F as ee,D as f,G as p,J as b,I as c}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 o=a.includes("T")?a:a.replace(" ","T");o=o.replace(/\.(\d{3})\d+/,".$1"),/([zZ]|[+-]\d{2}:\d{2})$/.test(o)||(o=`${o}+08:00`);const u=new Date(o);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 o=/[a-zA-Z]/.test(a),x=/\d/.test(a);return!o||!x?{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"},oe={key:0,class:"app-muted"},le={class:"actions"},ce={__name:"UsersPage",setup(g){const a=Q("refreshStats",null),o=P(!1),x=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 C(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 w(){o.value=!0;try{x.value=await K()}catch{x.value=[]}finally{o.value=!1}}async function U(){await w()}async function z(n){try{await v.confirm(`确定启用用户「${n.username}」吗?启用后用户可正常登录。`,"启用用户",{confirmButtonText:"启用",cancelButtonText:"取消",type:"success"})}catch{return}try{await W(n.id),h.success("用户已启用"),await w(),await a?.({force:!0})}catch{}}async function S(n){try{await v.confirm(`确定禁用用户「${n.username}」吗?禁用后用户将无法登录。`,"禁用用户",{confirmButtonText:"禁用",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Z(n.id),h.success("用户已禁用"),await w(),await a?.({force:!0})}catch{}}async function E(n){try{await v.confirm(`确定删除用户「${n.username}」吗?此操作将删除该用户的所有数据,不可恢复!`,"删除用户",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{await H(n.id),h.success("用户已删除"),await w(),await a?.({force:!0})}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 F(n.id,e);h.success(d?.message||"VIP设置成功"),await w(),await a?.({force:!0})}catch{}}async function N(n){try{await v.confirm(`确定移除用户「${n.username}」的 VIP 吗?`,"移除VIP",{confirmButtonText:"移除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await G(n.id);h.success(e?.message||"VIP已移除"),await w(),await a?.({force:!0})}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 J(n.id,e);h.success(d?.message||"密码重置成功")}catch{}}return X(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"),q=Y("loading");return r(),V("div",te,[e[11]||(e[11]=y("div",{class:"app-page-title"},[y("h2",null,"用户")],-1)),l(R,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[y("div",ne,[ee((r(),f(A,{data:x.value,style:{width:"100%"}},{default:s(()=>[l(i,{prop:"id",label:"ID",width:"80"}),l(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(),V("div",ie,b(t.email),1)):p("",!0),C(t)?(r(),V("div",re,b(C(t)),1)):p("",!0)])]),_:1}),l(i,{label:"状态",width:"120"},{default:s(({row:t})=>[l(d,{type:B(t.status).type,effect:"light"},{default:s(()=>[c(b(B(t.status).label),1)]),_:2},1032,["type"])]),_:1}),l(i,{label:"时间","min-width":"220"},{default:s(({row:t})=>[y("div",null,b(t.created_at),1),t.vip_expire_time?(r(),V("div",oe,"VIP到期: "+b(t.vip_expire_time),1)):p("",!0)]),_:1}),l(i,{label:"操作",width:"280",fixed:"right"},{default:s(({row:t})=>[y("div",le,[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"])),l(L,{trigger:"click"},{dropdown:s(()=>[l(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=>N(t)},{default:s(()=>[...e[8]||(e[8]=[c("移除VIP",-1)])]),_:1},8,["onClick"])):p("",!0)]),_:2},1024)]),default:s(()=>[l(k,{size:"small"},{default:s(()=>[...e[3]||(e[3]=[c("VIP",-1)])]),_:1})]),_:2},1024),l(k,{size:"small",onClick:m=>M(t)},{default:s(()=>[...e[9]||(e[9]=[c("重置密码",-1)])]),_:1},8,["onClick"]),l(k,{type:"danger",size:"small",onClick:m=>E(t)},{default:s(()=>[...e[10]||(e[10]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[q,o.value]])])]),_:1})])}}},ye=O(ce,[["__scopeId","data-v-11f4f270"]]);export{ye as default}; +import{a as W,r as Z,s as F,b as G,c as J,d as H,f as K}from"./users-BZkLUJZL.js";import{_ as O}from"./index-BsqM_wut.js";import{E as v,a as h}from"./vendor-element-B5S5pUKo.js";import{i as Q,r as P,o as X,aj as _,ap as Y,n as V,q as r,t as y,L as l,E as s,F as ee,D as f,G as p,J as b,I as c}from"./vendor-vue-CVxSw_oJ.js";import"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 o=a.includes("T")?a:a.replace(" ","T");o=o.replace(/\.(\d{3})\d+/,".$1"),/([zZ]|[+-]\d{2}:\d{2})$/.test(o)||(o=`${o}+08:00`);const u=new Date(o);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 o=/[a-zA-Z]/.test(a),x=/\d/.test(a);return!o||!x?{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"},oe={key:0,class:"app-muted"},le={class:"actions"},ce={__name:"UsersPage",setup(g){const a=Q("refreshStats",null),o=P(!1),x=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 C(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 w(){o.value=!0;try{x.value=await K()}catch{x.value=[]}finally{o.value=!1}}async function U(){await w()}async function z(n){try{await v.confirm(`确定启用用户「${n.username}」吗?启用后用户可正常登录。`,"启用用户",{confirmButtonText:"启用",cancelButtonText:"取消",type:"success"})}catch{return}try{await W(n.id),h.success("用户已启用"),await w(),await a?.({force:!0})}catch{}}async function S(n){try{await v.confirm(`确定禁用用户「${n.username}」吗?禁用后用户将无法登录。`,"禁用用户",{confirmButtonText:"禁用",cancelButtonText:"取消",type:"warning"})}catch{return}try{await Z(n.id),h.success("用户已禁用"),await w(),await a?.({force:!0})}catch{}}async function E(n){try{await v.confirm(`确定删除用户「${n.username}」吗?此操作将删除该用户的所有数据,不可恢复!`,"删除用户",{confirmButtonText:"删除",cancelButtonText:"取消",type:"error"})}catch{return}try{await H(n.id),h.success("用户已删除"),await w(),await a?.({force:!0})}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 F(n.id,e);h.success(d?.message||"VIP设置成功"),await w(),await a?.({force:!0})}catch{}}async function N(n){try{await v.confirm(`确定移除用户「${n.username}」的 VIP 吗?`,"移除VIP",{confirmButtonText:"移除",cancelButtonText:"取消",type:"warning"})}catch{return}try{const e=await G(n.id);h.success(e?.message||"VIP已移除"),await w(),await a?.({force:!0})}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 J(n.id,e);h.success(d?.message||"密码重置成功")}catch{}}return X(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"),q=Y("loading");return r(),V("div",te,[e[11]||(e[11]=y("div",{class:"app-page-title"},[y("h2",null,"用户")],-1)),l(R,{shadow:"never","body-style":{padding:"16px"},class:"card"},{default:s(()=>[y("div",ne,[ee((r(),f(A,{data:x.value,style:{width:"100%"}},{default:s(()=>[l(i,{prop:"id",label:"ID",width:"80"}),l(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(),V("div",ie,b(t.email),1)):p("",!0),C(t)?(r(),V("div",re,b(C(t)),1)):p("",!0)])]),_:1}),l(i,{label:"状态",width:"120"},{default:s(({row:t})=>[l(d,{type:B(t.status).type,effect:"light"},{default:s(()=>[c(b(B(t.status).label),1)]),_:2},1032,["type"])]),_:1}),l(i,{label:"时间","min-width":"220"},{default:s(({row:t})=>[y("div",null,b(t.created_at),1),t.vip_expire_time?(r(),V("div",oe,"VIP到期: "+b(t.vip_expire_time),1)):p("",!0)]),_:1}),l(i,{label:"操作",width:"280",fixed:"right"},{default:s(({row:t})=>[y("div",le,[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"])),l(L,{trigger:"click"},{dropdown:s(()=>[l(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=>N(t)},{default:s(()=>[...e[8]||(e[8]=[c("移除VIP",-1)])]),_:1},8,["onClick"])):p("",!0)]),_:2},1024)]),default:s(()=>[l(k,{size:"small"},{default:s(()=>[...e[3]||(e[3]=[c("VIP",-1)])]),_:1})]),_:2},1024),l(k,{size:"small",onClick:m=>M(t)},{default:s(()=>[...e[9]||(e[9]=[c("重置密码",-1)])]),_:1},8,["onClick"]),l(k,{type:"danger",size:"small",onClick:m=>E(t)},{default:s(()=>[...e[10]||(e[10]=[c("删除",-1)])]),_:1},8,["onClick"])])]),_:1})]),_:1},8,["data"])),[[q,o.value]])])]),_:1})])}}},ye=O(ce,[["__scopeId","data-v-11f4f270"]]);export{ye as default}; diff --git a/static/admin/assets/email-UyqcDGmM.js b/static/admin/assets/email--WygXDwI.js similarity index 88% rename from static/admin/assets/email-UyqcDGmM.js rename to static/admin/assets/email--WygXDwI.js index ff726db..757a1b2 100644 --- a/static/admin/assets/email-UyqcDGmM.js +++ b/static/admin/assets/email--WygXDwI.js @@ -1 +1 @@ -import{c as s,a as e}from"./index-COtE2fCT.js";const n=s(async()=>{const{data:a}=await e.get("/email/stats");return a},1e4);async function i(){const{data:a}=await e.get("/email/settings");return a}async function r(a){const{data:t}=await e.post("/email/settings",a);return n.clear(),t}async function o(a={}){return n.run(a)}async function l(a){const{data:t}=await e.get("/email/logs",{params:a});return t}async function u(a){const{data:t}=await e.post("/email/logs/cleanup",{days:a});return n.clear(),t}export{l as a,i as b,u as c,o as f,r as u}; +import{c as s,a as e}from"./index-BsqM_wut.js";const n=s(async()=>{const{data:a}=await e.get("/email/stats");return a},1e4);async function i(){const{data:a}=await e.get("/email/settings");return a}async function r(a){const{data:t}=await e.post("/email/settings",a);return n.clear(),t}async function o(a={}){return n.run(a)}async function l(a){const{data:t}=await e.get("/email/logs",{params:a});return t}async function u(a){const{data:t}=await e.post("/email/logs/cleanup",{days:a});return n.clear(),t}export{l as a,i as b,u as c,o as f,r as u}; diff --git a/static/admin/assets/index-COtE2fCT.js b/static/admin/assets/index-BsqM_wut.js similarity index 89% rename from static/admin/assets/index-COtE2fCT.js rename to static/admin/assets/index-BsqM_wut.js index dcaade8..1c25dd0 100644 --- a/static/admin/assets/index-COtE2fCT.js +++ b/static/admin/assets/index-BsqM_wut.js @@ -1,2 +1,2 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./ReportPage-B5zHPJFA.js","./vendor-element-B5S5pUKo.js","./vendor-vue-CVxSw_oJ.js","./vendor-misc-BeoNyvBp.js","./vendor-element-C68yOrAy.css","./email-UyqcDGmM.js","./tasks-BpEmoe5G.js","./system-CCwKPotj.js","./MetricGrid-Bj2rgY20.js","./MetricGrid-yP_dkP6X.css","./vendor-axios-B9ygI19o.js","./ReportPage-BCQBCnjY.css","./UsersPage-CwNX_aN-.js","./users-DXFAixXH.js","./UsersPage-BNDnhJe0.css","./FeedbacksPage-DPSbObwj.js","./FeedbacksPage-mrXjCiV2.css","./LogsPage-C3EIiKOD.js","./LogsPage-D1bozCEo.css","./AnnouncementsPage-CB2ecIAp.js","./AnnouncementsPage-DOwZaaOu.css","./EmailPage-DMQiuF7T.js","./EmailPage-BmPCDPYC.css","./SecurityPage-ClrVJm-9.js","./SecurityPage-DN76ndc_.css","./SystemPage-CkuZRgDH.js","./SystemPage-DYBocGi2.css","./SettingsPage-COD6KO3P.js","./SettingsPage-DaB8PeRL.css"])))=>i.map(i=>d[i]); -import{aj as p,D as S,q as _,ax as _e,ay as he,r as L,c as Y,o as ye,R as ge,E as r,G as z,L as f,t as m,n as D,K as W,a3 as X,H as J,J as A,I as Q,az as be,p as V,aA as ve,aB as we,as as Ee}from"./vendor-vue-CVxSw_oJ.js";import{E as ne,a as ae,d as Se,u as Te,c as ke,l as Pe,b as Re,m as Le,e as Ae,t as Oe,s as xe,i as Ne,z as Be}from"./vendor-element-B5S5pUKo.js";import{a as De}from"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();const se=(e,t)=>{const n=e.__vccOpts||e;for(const[a,s]of t)n[a]=s;return n},Ce={};function Ve(e,t){const n=p("RouterView");return _(),S(n)}const Me=se(Ce,[["render",Ve]]),Ie="modulepreload",$e=function(e,t){return new URL(e,t).href},Z={},T=function(t,n,a){let s=Promise.resolve();if(n&&n.length>0){let k=function(d){return Promise.all(d.map(h=>Promise.resolve(h).then(w=>({status:"fulfilled",value:w}),w=>({status:"rejected",reason:w}))))};const i=document.getElementsByTagName("link"),u=document.querySelector("meta[property=csp-nonce]"),v=u?.nonce||u?.getAttribute("nonce");s=k(n.map(d=>{if(d=$e(d,a),d in Z)return;Z[d]=!0;const h=d.endsWith(".css"),w=h?'[rel="stylesheet"]':"";if(a)for(let P=i.length-1;P>=0;P--){const E=i[P];if(E.href===d&&(!h||E.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${w}`))return;const y=document.createElement("link");if(y.rel=h?"stylesheet":Ie,h||(y.as="script"),y.crossOrigin="",y.href=d,v&&y.setAttribute("nonce",v),document.head.appendChild(y),h)return new Promise((P,E)=>{y.addEventListener("load",P),y.addEventListener("error",()=>E(new Error(`Unable to preload CSS for ${d}`)))})}))}function o(i){const u=new Event("vite:preloadError",{cancelable:!0});if(u.payload=i,window.dispatchEvent(u),!u.defaultPrevented)throw i}return s.then(i=>{for(const u of i||[])u.status==="rejected"&&o(u.reason);return t().catch(o)})};let ee="",te=0;const Fe=new Set([408,425,429,500,502,503,504]),Ue=1,qe=300;function O(e,t,n=1500){const a=Date.now();e===ee&&a-te=Ue)return!1;const a=String(e?.code||"");if(a==="ECONNABORTED"||a==="ERR_NETWORK")return!0;const s=Number(e?.response?.status||0);return Fe.has(s)}function He(e){return new Promise(t=>{window.setTimeout(t,Math.max(0,Number(e||0)))})}async function Ye(e,t){const n=e?.config||{},a=Number(n.__retry_count||0);n.__retry_count=a+1;const s=qe*(a+1);return await He(s),t.request(n)}const b=De.create({baseURL:"/yuyx/api",timeout:3e4,withCredentials:!0});let x=null;async function ze(){return x||(x=ne.prompt("请输入管理员密码进行二次确认","安全确认",{inputType:"password",inputPlaceholder:"管理员密码",confirmButtonText:"确认",cancelButtonText:"取消",inputValidator:e=>!!String(e||"").trim(),inputErrorMessage:"密码不能为空"}).then(async e=>{const t=String(e.value||"").trim();await b.post("/admin/reauth",{password:t}),ae.success("已通过安全确认")}).finally(()=>{x=null}),x)}b.interceptors.request.use(e=>{const t=String(e?.method||"GET").toUpperCase();if(!["GET","HEAD","OPTIONS"].includes(t)){const n=Ge("csrf_token");n&&(e.headers=e.headers||{},e.headers["X-CSRF-Token"]=n)}return e});b.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 ze(),b.request(e.config)}catch{return Promise.reject(e)}return je(e)?Ye(e,b):(t===401?(O("401",a,3e3),(window.location?.pathname||"").startsWith("/yuyx")||(window.location.href="/yuyx")):t===403?O("403",a,5e3):t?O(`http:${t}:${a}`,a):e?.code==="ECONNABORTED"?O("timeout","请求超时",3e3):O(`net:${a}`,a,3e3),Promise.reject(e))});function oe(e,t=0){let n=!1,a=null,s=0,o=null;async function i(v={}){const k=!!v?.force,d=Date.now();return!k&&n&&de()).then(h=>{a=h,n=!0;const w=Math.max(0,Number(t)||0);return s=Date.now()+w,h}).finally(()=>{o=null})),o)}function u(){n=!1,a=null,s=0,o=null}return{run:i,clear:u}}const We=1e4,re=oe(async()=>{const{data:e}=await b.get("/feedbacks",{params:{limit:1,offset:0}});return e?.stats},We);async function Rt(e=""){const{data:t}=await b.get("/feedbacks",{params:e?{status:e}:{}});return t}async function Xe(e={}){return re.run(e)}function M(){re.clear()}async function Lt(e,t){const{data:n}=await b.post(`/feedbacks/${e}/reply`,{reply:t});return M(),n}async function At(e){const{data:t}=await b.post(`/feedbacks/${e}/close`);return M(),t}async function Ot(e){const{data:t}=await b.delete(`/feedbacks/${e}`);return M(),t}const Je=15e3,Qe=oe(async()=>{const{data:e}=await b.get("/stats");return e},Je);async function Ze(e={}){return Qe.run(e)}const et={class:"menu-label"},tt={key:1,class:"menu-label"},nt={class:"header-left"},at={class:"header-right"},st={class:"admin-name"},ot={class:"main-shell"},rt={class:"menu-label"},ct={key:1,class:"menu-label"},lt=6e4,it=18e4,ut={__name:"AdminLayout",setup(e){const t=_e(),n=he(),a=L({}),s=Y(()=>a.value?.admin_username||"");async function o(g={}){a.value=await Ze(g)}const i=L(!1),u=L(0);let v=null;async function k(g=null){if(g&&typeof g=="object"){Object.prototype.hasOwnProperty.call(g,"pendingFeedbacks")&&(u.value=Number(g.pendingFeedbacks||0));return}if(!i.value){i.value=!0;try{const c=await Xe();u.value=Number(c?.pending||0)}finally{i.value=!1}}}function d(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function h(){return d()?it:lt}function w(){v&&(window.clearTimeout(v),v=null)}function y(){w(),v=window.setTimeout(async()=>{v=null,await k().catch(()=>{}),y()},h())}function P(){y()}V("refreshStats",o),V("adminStats",a),V("refreshNavBadges",k);const E=L(!1),R=L(!1);let N;function C(){E.value=!!N?.matches,E.value||(R.value=!1)}ye(async()=>{N=window.matchMedia("(max-width: 768px)"),N.addEventListener?.("change",C),C(),await o(),await k(),y(),window.addEventListener("visibilitychange",P)}),ge(()=>{N?.removeEventListener?.("change",C),w(),window.removeEventListener("visibilitychange",P)});const I=[{path:"/reports",label:"报表",icon:Se},{path:"/users",label:"用户",icon:Te},{path:"/feedbacks",label:"反馈",icon:ke,badgeKey:"feedbacks"},{path:"/logs",label:"任务日志",icon:Pe},{path:"/announcements",label:"公告",icon:Re},{path:"/email",label:"邮件",icon:Le},{path:"/security",label:"安全防护",icon:Ae},{path:"/system",label:"系统配置",icon:Oe},{path:"/settings",label:"设置",icon:xe}],$=Y(()=>t.path);function B(g){return g?.badgeKey&&g.badgeKey==="feedbacks"?Number(u.value||0):0}async function ce(){try{await ne.confirm("确定退出管理员登录吗?","退出登录",{confirmButtonText:"退出",cancelButtonText:"取消",type:"warning"})}catch{return}try{await b.post("/logout")}finally{window.location.href="/yuyx"}}async function F(g){await n.push(g),R.value=!1}return(g,c)=>{const U=p("el-icon"),q=p("el-badge"),G=p("el-menu-item"),K=p("el-menu"),le=p("el-aside"),j=p("el-button"),ie=p("el-header"),ue=p("RouterView"),de=p("el-skeleton"),me=p("el-card"),fe=p("el-main"),H=p("el-container"),pe=p("el-drawer");return _(),S(H,{class:"layout-root"},{default:r(()=>[E.value?z("",!0):(_(),S(le,{key:0,width:"220px",class:"layout-aside"},{default:r(()=>[c[2]||(c[2]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"后台管理"),m("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),f(K,{"default-active":$.value,class:"aside-menu",router:"",onSelect:F},{default:r(()=>[(_(),D(W,null,X(I,l=>f(G,{key:l.path,index:l.path},{default:r(()=>[f(U,null,{default:r(()=>[(_(),S(J(l.icon)))]),_:2},1024),B(l)>0?(_(),S(q,{key:0,value:B(l),max:99,class:"menu-badge"},{default:r(()=>[m("span",et,A(l.label),1)]),_:2},1032,["value"])):(_(),D("span",tt,A(l.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1})),f(H,null,{default:r(()=>[f(ie,{class:"layout-header"},{default:r(()=>[m("div",nt,[E.value?(_(),S(j,{key:0,text:"",class:"header-menu-btn",onClick:c[0]||(c[0]=l=>R.value=!0)},{default:r(()=>[...c[3]||(c[3]=[Q(" 菜单 ",-1)])]),_:1})):z("",!0),c[4]||(c[4]=m("div",{class:"header-title"},"后台管理系统",-1))]),m("div",at,[m("div",st,[c[5]||(c[5]=m("span",{class:"app-muted"},"管理员",-1)),m("strong",null,A(s.value||"-"),1)]),f(j,{type:"primary",plain:"",class:"logout-btn",onClick:ce},{default:r(()=>[...c[6]||(c[6]=[Q("退出",-1)])]),_:1})])]),_:1}),f(fe,{class:"layout-main"},{default:r(()=>[m("div",ot,[(_(),S(be,null,{default:r(()=>[f(ue)]),fallback:r(()=>[f(me,{shadow:"never","body-style":{padding:"16px"},class:"fallback-card"},{default:r(()=>[f(de,{rows:5,animated:""})]),_:1})]),_:1}))])]),_:1})]),_:1}),f(pe,{modelValue:R.value,"onUpdate:modelValue":c[1]||(c[1]=l=>R.value=l),size:"min(82vw, 280px)",direction:"ltr","with-header":!1},{default:r(()=>[c[7]||(c[7]=m("div",{class:"drawer-brand"},[m("div",{class:"brand-title"},"后台管理"),m("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),f(K,{"default-active":$.value,class:"aside-menu",router:"",onSelect:F},{default:r(()=>[(_(),D(W,null,X(I,l=>f(G,{key:l.path,index:l.path},{default:r(()=>[f(U,null,{default:r(()=>[(_(),S(J(l.icon)))]),_:2},1024),B(l)>0?(_(),S(q,{key:0,value:B(l),max:99,class:"menu-badge"},{default:r(()=>[m("span",rt,A(l.label),1)]),_:2},1032,["value"])):(_(),D("span",ct,A(l.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1},8,["modelValue"])]),_:1})}}},dt=se(ut,[["__scopeId","data-v-62384b9b"]]),mt=()=>T(()=>import("./ReportPage-B5zHPJFA.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11]),import.meta.url),ft=()=>T(()=>import("./UsersPage-CwNX_aN-.js"),__vite__mapDeps([12,13,1,2,3,4,10,14]),import.meta.url),pt=()=>T(()=>import("./FeedbacksPage-DPSbObwj.js"),__vite__mapDeps([15,8,2,9,1,3,4,10,16]),import.meta.url),_t=()=>T(()=>import("./LogsPage-C3EIiKOD.js"),__vite__mapDeps([17,13,6,1,2,3,4,10,18]),import.meta.url),ht=()=>T(()=>import("./AnnouncementsPage-CB2ecIAp.js"),__vite__mapDeps([19,1,2,3,4,10,20]),import.meta.url),yt=()=>T(()=>import("./EmailPage-DMQiuF7T.js"),__vite__mapDeps([21,5,8,2,9,1,3,4,10,22]),import.meta.url),gt=()=>T(()=>import("./SecurityPage-ClrVJm-9.js"),__vite__mapDeps([23,8,2,9,1,3,4,10,24]),import.meta.url),bt=()=>T(()=>import("./SystemPage-CkuZRgDH.js"),__vite__mapDeps([25,7,1,2,3,4,10,26]),import.meta.url),vt=()=>T(()=>import("./SettingsPage-COD6KO3P.js"),__vite__mapDeps([27,1,2,3,4,10,28]),import.meta.url),wt=[{path:"/",component:dt,children:[{path:"",redirect:"/reports"},{path:"/pending",redirect:"/reports"},{path:"/stats",redirect:"/reports"},{path:"/reports",name:"reports",component:mt},{path:"/users",name:"users",component:ft},{path:"/feedbacks",name:"feedbacks",component:pt},{path:"/logs",name:"logs",component:_t},{path:"/announcements",name:"announcements",component:ht},{path:"/email",name:"email",component:yt},{path:"/security",name:"security",component:gt},{path:"/system",name:"system",component:bt},{path:"/settings",name:"settings",component:vt}]}],Et=ve({history:we(),routes:wt});Ee(Me).use(Et).use(Ne,{locale:Be}).mount("#app");export{se as _,b as a,Rt as b,oe as c,At as d,Ot as e,Xe as f,Lt as r}; +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./ReportPage-ycVtg2rZ.js","./vendor-element-B5S5pUKo.js","./vendor-vue-CVxSw_oJ.js","./vendor-misc-BeoNyvBp.js","./vendor-element-C68yOrAy.css","./email--WygXDwI.js","./tasks-OWsi7T-E.js","./system-CAzjuaad.js","./MetricGrid-Dsqo4YZI.js","./MetricGrid-yP_dkP6X.css","./vendor-axios-B9ygI19o.js","./ReportPage-BCQBCnjY.css","./UsersPage-yptpHEoN.js","./users-BZkLUJZL.js","./UsersPage-BNDnhJe0.css","./FeedbacksPage-9Z4ULgo9.js","./FeedbacksPage-mrXjCiV2.css","./LogsPage-MDq3eoIe.js","./LogsPage-D1bozCEo.css","./AnnouncementsPage-PdPHO5Q2.js","./AnnouncementsPage-DOwZaaOu.css","./EmailPage-yqRvXEJ2.js","./EmailPage-BmPCDPYC.css","./SecurityPage-CXcU2SbL.js","./SecurityPage-DN76ndc_.css","./SystemPage-B2BrKkTP.js","./SystemPage-DYBocGi2.css","./SettingsPage-CUZAbAFF.js","./SettingsPage-NWcEVLn7.css"])))=>i.map(i=>d[i]); +import{aj as p,D as S,q as _,ax as _e,ay as he,r as L,c as Y,o as ye,R as ge,E as r,G as z,L as f,t as m,n as D,K as W,a3 as X,H as J,J as A,I as Q,az as be,p as V,aA as ve,aB as we,as as Ee}from"./vendor-vue-CVxSw_oJ.js";import{E as ne,a as ae,d as Se,u as Te,c as ke,l as Pe,b as Re,m as Le,e as Ae,t as Oe,s as xe,i as Ne,z as Be}from"./vendor-element-B5S5pUKo.js";import{a as De}from"./vendor-axios-B9ygI19o.js";import"./vendor-misc-BeoNyvBp.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 o of s)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=n(s);fetch(s.href,o)}})();const se=(e,t)=>{const n=e.__vccOpts||e;for(const[a,s]of t)n[a]=s;return n},Ce={};function Ve(e,t){const n=p("RouterView");return _(),S(n)}const Me=se(Ce,[["render",Ve]]),Ie="modulepreload",$e=function(e,t){return new URL(e,t).href},Z={},T=function(t,n,a){let s=Promise.resolve();if(n&&n.length>0){let k=function(d){return Promise.all(d.map(h=>Promise.resolve(h).then(w=>({status:"fulfilled",value:w}),w=>({status:"rejected",reason:w}))))};const i=document.getElementsByTagName("link"),u=document.querySelector("meta[property=csp-nonce]"),v=u?.nonce||u?.getAttribute("nonce");s=k(n.map(d=>{if(d=$e(d,a),d in Z)return;Z[d]=!0;const h=d.endsWith(".css"),w=h?'[rel="stylesheet"]':"";if(a)for(let P=i.length-1;P>=0;P--){const E=i[P];if(E.href===d&&(!h||E.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${d}"]${w}`))return;const y=document.createElement("link");if(y.rel=h?"stylesheet":Ie,h||(y.as="script"),y.crossOrigin="",y.href=d,v&&y.setAttribute("nonce",v),document.head.appendChild(y),h)return new Promise((P,E)=>{y.addEventListener("load",P),y.addEventListener("error",()=>E(new Error(`Unable to preload CSS for ${d}`)))})}))}function o(i){const u=new Event("vite:preloadError",{cancelable:!0});if(u.payload=i,window.dispatchEvent(u),!u.defaultPrevented)throw i}return s.then(i=>{for(const u of i||[])u.status==="rejected"&&o(u.reason);return t().catch(o)})};let ee="",te=0;const Fe=new Set([408,425,429,500,502,503,504]),Ue=1,qe=300;function O(e,t,n=1500){const a=Date.now();e===ee&&a-te=Ue)return!1;const a=String(e?.code||"");if(a==="ECONNABORTED"||a==="ERR_NETWORK")return!0;const s=Number(e?.response?.status||0);return Fe.has(s)}function He(e){return new Promise(t=>{window.setTimeout(t,Math.max(0,Number(e||0)))})}async function Ye(e,t){const n=e?.config||{},a=Number(n.__retry_count||0);n.__retry_count=a+1;const s=qe*(a+1);return await He(s),t.request(n)}const b=De.create({baseURL:"/yuyx/api",timeout:3e4,withCredentials:!0});let x=null;async function ze(){return x||(x=ne.prompt("请输入管理员密码进行二次确认","安全确认",{inputType:"password",inputPlaceholder:"管理员密码",confirmButtonText:"确认",cancelButtonText:"取消",inputValidator:e=>!!String(e||"").trim(),inputErrorMessage:"密码不能为空"}).then(async e=>{const t=String(e.value||"").trim();await b.post("/admin/reauth",{password:t}),ae.success("已通过安全确认")}).finally(()=>{x=null}),x)}b.interceptors.request.use(e=>{const t=String(e?.method||"GET").toUpperCase();if(!["GET","HEAD","OPTIONS"].includes(t)){const n=Ge("csrf_token");n&&(e.headers=e.headers||{},e.headers["X-CSRF-Token"]=n)}return e});b.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 ze(),b.request(e.config)}catch{return Promise.reject(e)}return je(e)?Ye(e,b):(t===401?(O("401",a,3e3),(window.location?.pathname||"").startsWith("/yuyx")||(window.location.href="/yuyx")):t===403?O("403",a,5e3):t?O(`http:${t}:${a}`,a):e?.code==="ECONNABORTED"?O("timeout","请求超时",3e3):O(`net:${a}`,a,3e3),Promise.reject(e))});function oe(e,t=0){let n=!1,a=null,s=0,o=null;async function i(v={}){const k=!!v?.force,d=Date.now();return!k&&n&&de()).then(h=>{a=h,n=!0;const w=Math.max(0,Number(t)||0);return s=Date.now()+w,h}).finally(()=>{o=null})),o)}function u(){n=!1,a=null,s=0,o=null}return{run:i,clear:u}}const We=1e4,re=oe(async()=>{const{data:e}=await b.get("/feedbacks",{params:{limit:1,offset:0}});return e?.stats},We);async function Rt(e=""){const{data:t}=await b.get("/feedbacks",{params:e?{status:e}:{}});return t}async function Xe(e={}){return re.run(e)}function M(){re.clear()}async function Lt(e,t){const{data:n}=await b.post(`/feedbacks/${e}/reply`,{reply:t});return M(),n}async function At(e){const{data:t}=await b.post(`/feedbacks/${e}/close`);return M(),t}async function Ot(e){const{data:t}=await b.delete(`/feedbacks/${e}`);return M(),t}const Je=15e3,Qe=oe(async()=>{const{data:e}=await b.get("/stats");return e},Je);async function Ze(e={}){return Qe.run(e)}const et={class:"menu-label"},tt={key:1,class:"menu-label"},nt={class:"header-left"},at={class:"header-right"},st={class:"admin-name"},ot={class:"main-shell"},rt={class:"menu-label"},ct={key:1,class:"menu-label"},lt=6e4,it=18e4,ut={__name:"AdminLayout",setup(e){const t=_e(),n=he(),a=L({}),s=Y(()=>a.value?.admin_username||"");async function o(g={}){a.value=await Ze(g)}const i=L(!1),u=L(0);let v=null;async function k(g=null){if(g&&typeof g=="object"){Object.prototype.hasOwnProperty.call(g,"pendingFeedbacks")&&(u.value=Number(g.pendingFeedbacks||0));return}if(!i.value){i.value=!0;try{const c=await Xe();u.value=Number(c?.pending||0)}finally{i.value=!1}}}function d(){return typeof document>"u"?!1:document.visibilityState==="hidden"}function h(){return d()?it:lt}function w(){v&&(window.clearTimeout(v),v=null)}function y(){w(),v=window.setTimeout(async()=>{v=null,await k().catch(()=>{}),y()},h())}function P(){y()}V("refreshStats",o),V("adminStats",a),V("refreshNavBadges",k);const E=L(!1),R=L(!1);let N;function C(){E.value=!!N?.matches,E.value||(R.value=!1)}ye(async()=>{N=window.matchMedia("(max-width: 768px)"),N.addEventListener?.("change",C),C(),await o(),await k(),y(),window.addEventListener("visibilitychange",P)}),ge(()=>{N?.removeEventListener?.("change",C),w(),window.removeEventListener("visibilitychange",P)});const I=[{path:"/reports",label:"报表",icon:Se},{path:"/users",label:"用户",icon:Te},{path:"/feedbacks",label:"反馈",icon:ke,badgeKey:"feedbacks"},{path:"/logs",label:"任务日志",icon:Pe},{path:"/announcements",label:"公告",icon:Re},{path:"/email",label:"邮件",icon:Le},{path:"/security",label:"安全防护",icon:Ae},{path:"/system",label:"系统配置",icon:Oe},{path:"/settings",label:"设置",icon:xe}],$=Y(()=>t.path);function B(g){return g?.badgeKey&&g.badgeKey==="feedbacks"?Number(u.value||0):0}async function ce(){try{await ne.confirm("确定退出管理员登录吗?","退出登录",{confirmButtonText:"退出",cancelButtonText:"取消",type:"warning"})}catch{return}try{await b.post("/logout")}finally{window.location.href="/yuyx"}}async function F(g){await n.push(g),R.value=!1}return(g,c)=>{const U=p("el-icon"),q=p("el-badge"),G=p("el-menu-item"),K=p("el-menu"),le=p("el-aside"),j=p("el-button"),ie=p("el-header"),ue=p("RouterView"),de=p("el-skeleton"),me=p("el-card"),fe=p("el-main"),H=p("el-container"),pe=p("el-drawer");return _(),S(H,{class:"layout-root"},{default:r(()=>[E.value?z("",!0):(_(),S(le,{key:0,width:"220px",class:"layout-aside"},{default:r(()=>[c[2]||(c[2]=m("div",{class:"brand"},[m("div",{class:"brand-title"},"后台管理"),m("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),f(K,{"default-active":$.value,class:"aside-menu",router:"",onSelect:F},{default:r(()=>[(_(),D(W,null,X(I,l=>f(G,{key:l.path,index:l.path},{default:r(()=>[f(U,null,{default:r(()=>[(_(),S(J(l.icon)))]),_:2},1024),B(l)>0?(_(),S(q,{key:0,value:B(l),max:99,class:"menu-badge"},{default:r(()=>[m("span",et,A(l.label),1)]),_:2},1032,["value"])):(_(),D("span",tt,A(l.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1})),f(H,null,{default:r(()=>[f(ie,{class:"layout-header"},{default:r(()=>[m("div",nt,[E.value?(_(),S(j,{key:0,text:"",class:"header-menu-btn",onClick:c[0]||(c[0]=l=>R.value=!0)},{default:r(()=>[...c[3]||(c[3]=[Q(" 菜单 ",-1)])]),_:1})):z("",!0),c[4]||(c[4]=m("div",{class:"header-title"},"后台管理系统",-1))]),m("div",at,[m("div",st,[c[5]||(c[5]=m("span",{class:"app-muted"},"管理员",-1)),m("strong",null,A(s.value||"-"),1)]),f(j,{type:"primary",plain:"",class:"logout-btn",onClick:ce},{default:r(()=>[...c[6]||(c[6]=[Q("退出",-1)])]),_:1})])]),_:1}),f(fe,{class:"layout-main"},{default:r(()=>[m("div",ot,[(_(),S(be,null,{default:r(()=>[f(ue)]),fallback:r(()=>[f(me,{shadow:"never","body-style":{padding:"16px"},class:"fallback-card"},{default:r(()=>[f(de,{rows:5,animated:""})]),_:1})]),_:1}))])]),_:1})]),_:1}),f(pe,{modelValue:R.value,"onUpdate:modelValue":c[1]||(c[1]=l=>R.value=l),size:"min(82vw, 280px)",direction:"ltr","with-header":!1},{default:r(()=>[c[7]||(c[7]=m("div",{class:"drawer-brand"},[m("div",{class:"brand-title"},"后台管理"),m("div",{class:"brand-sub app-muted"},"知识管理平台")],-1)),f(K,{"default-active":$.value,class:"aside-menu",router:"",onSelect:F},{default:r(()=>[(_(),D(W,null,X(I,l=>f(G,{key:l.path,index:l.path},{default:r(()=>[f(U,null,{default:r(()=>[(_(),S(J(l.icon)))]),_:2},1024),B(l)>0?(_(),S(q,{key:0,value:B(l),max:99,class:"menu-badge"},{default:r(()=>[m("span",rt,A(l.label),1)]),_:2},1032,["value"])):(_(),D("span",ct,A(l.label),1))]),_:2},1032,["index"])),64))]),_:1},8,["default-active"])]),_:1},8,["modelValue"])]),_:1})}}},dt=se(ut,[["__scopeId","data-v-62384b9b"]]),mt=()=>T(()=>import("./ReportPage-ycVtg2rZ.js"),__vite__mapDeps([0,1,2,3,4,5,6,7,8,9,10,11]),import.meta.url),ft=()=>T(()=>import("./UsersPage-yptpHEoN.js"),__vite__mapDeps([12,13,1,2,3,4,10,14]),import.meta.url),pt=()=>T(()=>import("./FeedbacksPage-9Z4ULgo9.js"),__vite__mapDeps([15,8,2,9,1,3,4,10,16]),import.meta.url),_t=()=>T(()=>import("./LogsPage-MDq3eoIe.js"),__vite__mapDeps([17,13,6,1,2,3,4,10,18]),import.meta.url),ht=()=>T(()=>import("./AnnouncementsPage-PdPHO5Q2.js"),__vite__mapDeps([19,1,2,3,4,10,20]),import.meta.url),yt=()=>T(()=>import("./EmailPage-yqRvXEJ2.js"),__vite__mapDeps([21,5,8,2,9,1,3,4,10,22]),import.meta.url),gt=()=>T(()=>import("./SecurityPage-CXcU2SbL.js"),__vite__mapDeps([23,8,2,9,1,3,4,10,24]),import.meta.url),bt=()=>T(()=>import("./SystemPage-B2BrKkTP.js"),__vite__mapDeps([25,7,1,2,3,4,10,26]),import.meta.url),vt=()=>T(()=>import("./SettingsPage-CUZAbAFF.js"),__vite__mapDeps([27,1,2,3,4,10,28]),import.meta.url),wt=[{path:"/",component:dt,children:[{path:"",redirect:"/reports"},{path:"/pending",redirect:"/reports"},{path:"/stats",redirect:"/reports"},{path:"/reports",name:"reports",component:mt},{path:"/users",name:"users",component:ft},{path:"/feedbacks",name:"feedbacks",component:pt},{path:"/logs",name:"logs",component:_t},{path:"/announcements",name:"announcements",component:ht},{path:"/email",name:"email",component:yt},{path:"/security",name:"security",component:gt},{path:"/system",name:"system",component:bt},{path:"/settings",name:"settings",component:vt}]}],Et=ve({history:we(),routes:wt});Ee(Me).use(Et).use(Ne,{locale:Be}).mount("#app");export{se as _,b as a,Rt as b,oe as c,At as d,Ot as e,Xe as f,Lt as r}; diff --git a/static/admin/assets/system-CCwKPotj.js b/static/admin/assets/system-CAzjuaad.js similarity index 76% rename from static/admin/assets/system-CCwKPotj.js rename to static/admin/assets/system-CAzjuaad.js index e42f60c..fe79dcf 100644 --- a/static/admin/assets/system-CCwKPotj.js +++ b/static/admin/assets/system-CAzjuaad.js @@ -1 +1 @@ -import{c as s,a}from"./index-COtE2fCT.js";const e=s(async()=>{const{data:t}=await a.get("/system/config");return t},15e3);async function o(t={}){return e.run(t)}async function r(t){const{data:n}=await a.post("/system/config",t);return e.clear(),n}export{o as f,r as u}; +import{c as s,a}from"./index-BsqM_wut.js";const e=s(async()=>{const{data:t}=await a.get("/system/config");return t},15e3);async function o(t={}){return e.run(t)}async function r(t){const{data:n}=await a.post("/system/config",t);return e.clear(),n}export{o as f,r as u}; diff --git a/static/admin/assets/tasks-BpEmoe5G.js b/static/admin/assets/tasks-OWsi7T-E.js similarity index 93% rename from static/admin/assets/tasks-BpEmoe5G.js rename to static/admin/assets/tasks-OWsi7T-E.js index 62c9ecc..0e37078 100644 --- a/static/admin/assets/tasks-BpEmoe5G.js +++ b/static/admin/assets/tasks-OWsi7T-E.js @@ -1 +1 @@ -import{c as s,a}from"./index-COtE2fCT.js";const c=s(async()=>{const{data:t}=await a.get("/server/info");return t},3e4),o=s(async()=>{const{data:t}=await a.get("/docker_stats");return t},8e3),u=s(async()=>{const{data:t}=await a.get("/request_metrics");return t},1e4),i=s(async()=>{const{data:t}=await a.get("/slow_sql_metrics");return t},1e4),e=s(async()=>{const{data:t}=await a.get("/task/stats");return t},4e3),r=s(async()=>{const{data:t}=await a.get("/task/running");return t},2e3);async function g(t={}){return c.run(t)}async function y(t={}){return o.run(t)}async function d(t={}){return u.run(t)}async function k(t={}){return i.run(t)}async function l(t={}){return e.run(t)}async function w(t={}){return r.run(t)}async function _(t){const{data:n}=await a.get("/task/logs",{params:t});return n}async function h(t){const{data:n}=await a.post("/task/logs/clear",{days:t});return e.clear(),r.clear(),n}export{w as a,g as b,y as c,d,k as e,l as f,_ as g,h}; +import{c as s,a}from"./index-BsqM_wut.js";const c=s(async()=>{const{data:t}=await a.get("/server/info");return t},3e4),o=s(async()=>{const{data:t}=await a.get("/docker_stats");return t},8e3),u=s(async()=>{const{data:t}=await a.get("/request_metrics");return t},1e4),i=s(async()=>{const{data:t}=await a.get("/slow_sql_metrics");return t},1e4),e=s(async()=>{const{data:t}=await a.get("/task/stats");return t},4e3),r=s(async()=>{const{data:t}=await a.get("/task/running");return t},2e3);async function g(t={}){return c.run(t)}async function y(t={}){return o.run(t)}async function d(t={}){return u.run(t)}async function k(t={}){return i.run(t)}async function l(t={}){return e.run(t)}async function w(t={}){return r.run(t)}async function _(t){const{data:n}=await a.get("/task/logs",{params:t});return n}async function h(t){const{data:n}=await a.post("/task/logs/clear",{days:t});return e.clear(),r.clear(),n}export{w as a,g as b,y as c,d,k as e,l as f,_ as g,h}; diff --git a/static/admin/assets/users-DXFAixXH.js b/static/admin/assets/users-BZkLUJZL.js similarity index 90% rename from static/admin/assets/users-DXFAixXH.js rename to static/admin/assets/users-BZkLUJZL.js index c65d53e..8d16349 100644 --- a/static/admin/assets/users-DXFAixXH.js +++ b/static/admin/assets/users-BZkLUJZL.js @@ -1 +1 @@ -import{a as t}from"./index-COtE2fCT.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-BsqM_wut.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 fad8643..b17dd0a 100644 --- a/static/admin/index.html +++ b/static/admin/index.html @@ -5,7 +5,7 @@ 后台管理 - 知识管理平台 - +