Compare commits

..

1 Commits

Author SHA1 Message Date
Yu Yon
53c78e8e3c feat: 添加安全模块 + Dockerfile添加curl支持健康检查
主要更新:
- 新增 security/ 安全模块 (风险评估、威胁检测、蜜罐等)
- Dockerfile 添加 curl 以支持 Docker 健康检查
- 前端页面更新 (管理后台、用户端)
- 数据库迁移和 schema 更新
- 新增 kdocs 上传服务
- 添加安全相关测试用例

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-08 17:48:33 +08:00
77 changed files with 2362 additions and 377 deletions

View File

@@ -10,7 +10,7 @@ ENV TZ=Asia/Shanghai
# 安装 wkhtmltopdf包含 wkhtmltoimage与中文字体
RUN apt-get update && \
apt-get install -y --no-install-recommends wkhtmltopdf fonts-noto-cjk && \
apt-get install -y --no-install-recommends wkhtmltopdf curl fonts-noto-cjk && \
rm -rf /var/lib/apt/lists/*
# 配置 pip 使用国内镜像源

136
README.md
View File

@@ -1,49 +1,31 @@
# 知识管理平台自动化工具 - Docker部署版
这是一个基于 Docker 的知识管理平台自动化工具支持多用户、定时任务、代理IP、VIP管理、金山文档集成等功能。
这是一个基于 Docker 的知识管理平台自动化工具支持多用户、定时任务、代理IP、VIP管理等功能。
---
## 项目简介
本项目是一个 **Docker 容器化应用**,使用 Flask + Vue 3 + Requests + wkhtmltoimage + SQLite 构建,提供:
本项目是一个 **Docker 容器化应用**,使用 Flask + Requests + wkhtmltopdf + SQLite 构建,提供:
### 核心功能
- 多用户注册登录系统(支持邮箱绑定与验证
- 自动化浏览任务(纯 HTTP API 模拟,速度快)
- 智能截图系统wkhtmltoimage支持线程池
- 用户自定义定时任务(支持随机延迟)
- VIP 用户管理(账号数量限制、优先队列)
### 集成功能
- **金山文档集成** - 自动上传截图到在线表格,支持姓名搜索匹配
- **邮件通知** - 任务完成通知、密码重置、邮箱验证
- **代理IP支持** - 动态代理API集成
### 安全功能
- 威胁检测引擎JNDI/SQL注入/XSS/命令注入检测)
- IP/用户风险评分系统
- 自动黑名单机制
- 登录设备指纹追踪
### 管理功能
- 现代化 Vue 3 SPA 后台管理界面
- 公告系统(支持图片)
- Bug 反馈系统
- 任务日志与统计
- 多用户注册登录系统
- 自动化任务HTTP 模拟
- 定时任务调度
- 截图管理
- VIP用户管理
- 代理IP支持
- 后台管理系统
---
## 技术栈
- **后端**: Python 3.11+, Flask, Flask-SocketIO
- **前端**: Vue 3 + Vite + Element Plus (SPA)
- **数据库**: SQLite + 连接池
- **自动化**: Requests + BeautifulSoup (浏览)
- **截图**: wkhtmltoimage
- **金山文档**: Playwright (表格操作/上传)
- **后端**: Python 3.8+, Flask
- **数据库**: SQLite
- **自动化**: Requests + BeautifulSoup
- **截图**: wkhtmltopdf / wkhtmltoimage
- **容器化**: Docker + Docker Compose
- **实时通信**: Socket.IO (WebSocket)
- **前端**: HTML + JavaScript + Socket.IO
---
@@ -53,46 +35,30 @@
zsglpt/
├── app.py # 启动/装配入口
├── routes/ # 路由层Blueprint
│ ├── api_*.py # API 路由
│ ├── admin_api/ # 管理后台 API
│ └── pages.py # 页面路由
├── services/ # 业务服务层
│ ├── tasks.py # 任务调度器
│ ├── screenshots.py # 截图服务
│ ├── kdocs_uploader.py # 金山文档上传服务
│ └── schedule_*.py # 定时任务相关
├── security/ # 安全防护模块
│ ├── threat_detector.py # 威胁检测引擎
│ ├── risk_scorer.py # 风险评分
│ ├── blacklist.py # 黑名单管理
│ └── middleware.py # 安全中间件
├── realtime/ # SocketIO 事件与推送
├── database.py # 数据库稳定门面(对外 API
├── db/ # DB 分域实现 + schema/migrations
├── db_pool.py # 数据库连接池
├── api_browser.py # Requests 自动化(主浏览流程)
├── browser_pool_worker.py # wkhtmltoimage 截图线程池
├── browser_pool_worker.py # 截图 WorkerPool
├── app_config.py # 配置管理
├── app_logger.py # 日志系统
├── app_security.py # 安全工具函数
├── password_utils.py # 密码哈希工具
├── app_security.py # 安全模块
├── password_utils.py # 密码工具
├── crypto_utils.py # 加解密工具
├── email_service.py # 邮件服务SMTP
├── email_service.py # 邮件服务
├── requirements.txt # Python依赖
├── requirements-dev.txt # 开发依赖(不进生产镜像)
├── pyproject.toml # ruff/pytest 配置
├── pyproject.toml # ruff/black/pytest 配置
├── Dockerfile # Docker镜像构建文件
├── docker-compose.yml # Docker编排文件
├── templates/ # HTML模板SPA 入口
├── app.html # 用户端 SPA 入口
├── admin.html # 管理端 SPA 入口
│ └── email/ # 邮件模板
├── app-frontend/ # 用户端 Vue 源码
── admin-frontend/ # 管理端 Vue 源码
├── static/ # 前端构建产物
│ ├── app/ # 用户端 SPA 资源
│ └── admin/ # 管理端 SPA 资源
└── tests/ # 测试用例
├── templates/ # HTML模板SPA fallback
├── app-frontend/ # 用户端前端源码(可选保留)
├── admin-frontend/ # 后台前端源码(可选保留)
└── static/ # 前端构建产物(运行时使用)
├── app/ # 用户端 SPA
── admin/ # 后台 SPA
```
---
@@ -711,9 +677,9 @@ docker logs knowledge-automation-multiuser | grep "数据库"
---
**文档版本**: v2.0
**更新日期**: 2026-01-08
**适用版本**: Docker多用户版 + Vue SPA
**文档版本**: v1.0
**更新日期**: 2025-10-29
**适用版本**: Docker多用户版
---
@@ -744,49 +710,3 @@ docker logs -f knowledge-automation-multiuser
```
完成!🎉
---
## 更新日志
### v2.0 (2026-01-08)
#### 新功能
- **金山文档集成**: 自动上传截图到金山文档表格
- 支持姓名搜索匹配单元格
- 支持配置有效行范围
- 支持覆盖已有图片
- 离线状态监控与邮件通知
- **Vue 3 SPA 前端**: 用户端和管理端全面升级为现代化单页应用
- Element Plus UI 组件库
- 实时任务状态更新
- 响应式设计
- **用户自定义定时任务**: 用户可创建自己的定时任务
- 支持多时间段配置
- 支持随机延迟
- 支持选择指定账号
- **安全防护系统**:
- 威胁检测引擎JNDI/SQL注入/XSS/命令注入)
- IP/用户风险评分
- 自动黑名单机制
- **邮件通知系统**:
- 任务完成通知
- 密码重置邮件
- 邮箱验证
- **公告系统**: 支持图片的系统公告
- **Bug反馈系统**: 用户可提交问题反馈
#### 优化
- **截图线程池**: wkhtmltoimage 截图支持多线程并发
- 线程池管理,按需启动
- 空闲自动释放资源
- **二次登录机制**: 刷新"上次登录时间"显示
- **API 预热**: 启动时预热连接,减少首次请求延迟
- **数据库连接池**: 提高并发性能
### v1.0 (2025-10-29)
- 初始版本
- 多用户系统
- 基础自动化任务
- 定时任务调度
- 代理IP支持

View File

@@ -0,0 +1,17 @@
import { api } from './client'
export async function fetchPasswordResets() {
const { data } = await api.get('/password_resets')
return data
}
export async function approvePasswordReset(requestId) {
const { data } = await api.post(`/password_resets/${requestId}/approve`)
return data
}
export async function rejectPasswordReset(requestId) {
const { data } = await api.post(`/password_resets/${requestId}/reject`)
return data
}

View File

@@ -0,0 +1,26 @@
import { api } from './client'
export async function fetchUpdateStatus() {
const { data } = await api.get('/update/status')
return data
}
export async function fetchUpdateResult() {
const { data } = await api.get('/update/result')
return data
}
export async function fetchUpdateLog(params = {}) {
const { data } = await api.get('/update/log', { params })
return data
}
export async function requestUpdateCheck() {
const { data } = await api.post('/update/check', {})
return data
}
export async function requestUpdateRun(payload = {}) {
const { data } = await api.post('/update/run', payload)
return data
}

View File

@@ -38,8 +38,6 @@ const kdocsSheetName = ref('')
const kdocsSheetIndex = ref(0)
const kdocsUnitColumn = ref('A')
const kdocsImageColumn = ref('D')
const kdocsRowStart = ref(0)
const kdocsRowEnd = ref(0)
const kdocsAdminNotifyEnabled = ref(false)
const kdocsAdminNotifyEmail = ref('')
const kdocsStatus = ref({})
@@ -134,8 +132,6 @@ async function loadAll() {
kdocsSheetIndex.value = system.kdocs_sheet_index ?? 0
kdocsUnitColumn.value = (system.kdocs_unit_column || 'A').toUpperCase()
kdocsImageColumn.value = (system.kdocs_image_column || 'D').toUpperCase()
kdocsRowStart.value = system.kdocs_row_start ?? 0
kdocsRowEnd.value = system.kdocs_row_end ?? 0
kdocsAdminNotifyEnabled.value = (system.kdocs_admin_notify_enabled ?? 0) === 1
kdocsAdminNotifyEmail.value = system.kdocs_admin_notify_email || ''
kdocsStatus.value = kdocsInfo || {}
@@ -257,8 +253,6 @@ async function saveKdocsConfig() {
kdocs_sheet_index: Number(kdocsSheetIndex.value) || 0,
kdocs_unit_column: kdocsUnitColumn.value.trim().toUpperCase(),
kdocs_image_column: kdocsImageColumn.value.trim().toUpperCase(),
kdocs_row_start: Number(kdocsRowStart.value) || 0,
kdocs_row_end: Number(kdocsRowEnd.value) || 0,
kdocs_admin_notify_enabled: kdocsAdminNotifyEnabled.value ? 1 : 0,
kdocs_admin_notify_email: kdocsAdminNotifyEmail.value.trim(),
}
@@ -571,15 +565,6 @@ onMounted(loadAll)
<el-input v-model="kdocsImageColumn" placeholder="D" style="max-width: 120px" />
</el-form-item>
<el-form-item label="有效行范围">
<div style="display: flex; align-items: center; gap: 8px;">
<el-input-number v-model="kdocsRowStart" :min="0" :max="10000" placeholder="起始行" style="width: 120px" />
<span></span>
<el-input-number v-model="kdocsRowEnd" :min="0" :max="10000" placeholder="结束行" style="width: 120px" />
</div>
<div class="help">限制上传的行范围 50-1000 表示不限制用于防止重名导致误传到其他县区</div>
</el-form-item>
<el-form-item label="管理员通知">
<el-switch v-model="kdocsAdminNotifyEnabled" />
</el-form-item>

View File

@@ -39,8 +39,3 @@ export async function updateKdocsSettings(payload) {
const { data } = await publicApi.post('/user/kdocs', payload)
return data
}
export async function fetchKdocsStatus() {
const { data } = await publicApi.get('/kdocs/status')
return data
}

View File

@@ -15,7 +15,7 @@ import {
updateAccount,
updateAccountRemark,
} from '../api/accounts'
import { fetchKdocsSettings, fetchKdocsStatus, updateKdocsSettings } from '../api/settings'
import { fetchKdocsSettings, updateKdocsSettings } from '../api/settings'
import { fetchRunStats } from '../api/stats'
import { useSocket } from '../composables/useSocket'
import { useUserStore } from '../stores/user'
@@ -61,14 +61,6 @@ watch(batchEnableScreenshot, (value) => {
const kdocsAutoUpload = ref(false)
const kdocsSettingsLoading = ref(false)
// KDocs 在线状态
const kdocsStatus = reactive({
enabled: false,
online: false,
message: '',
})
const kdocsStatusLoading = ref(false)
const addOpen = ref(false)
const editOpen = ref(false)
const upgradeOpen = ref(false)
@@ -213,22 +205,6 @@ async function loadKdocsSettings() {
}
}
async function loadKdocsStatus() {
kdocsStatusLoading.value = true
try {
const data = await fetchKdocsStatus()
kdocsStatus.enabled = Boolean(data?.enabled)
kdocsStatus.online = Boolean(data?.online)
kdocsStatus.message = data?.message || ''
} catch {
kdocsStatus.enabled = false
kdocsStatus.online = false
kdocsStatus.message = ''
} finally {
kdocsStatusLoading.value = false
}
}
async function onToggleKdocsAutoUpload(value) {
kdocsSettingsLoading.value = true
try {
@@ -566,8 +542,6 @@ watch(shouldPollStats, (running, prevRunning) => {
syncStatsPolling(prevRunning)
})
let kdocsStatusTimer = null
onMounted(async () => {
if (!userStore.vipInfo) {
userStore.refreshVipInfo().catch(() => {
@@ -579,18 +553,13 @@ onMounted(async () => {
await refreshAccounts()
await loadKdocsSettings()
await loadKdocsStatus()
await refreshStats()
syncStatsPolling()
// 每60秒刷新 KDocs 状态
kdocsStatusTimer = window.setInterval(() => loadKdocsStatus(), 60_000)
})
onBeforeUnmount(() => {
if (unbindSocket) unbindSocket()
stopStatsPolling()
if (kdocsStatusTimer) window.clearInterval(kdocsStatusTimer)
})
</script>
@@ -681,9 +650,6 @@ onBeforeUnmount(() => {
@change="onToggleKdocsAutoUpload"
/>
<span class="app-muted">表格(测试)</span>
<el-tag v-if="kdocsStatus.enabled" :type="kdocsStatus.online ? 'success' : 'warning'" size="small" effect="plain">
{{ kdocsStatus.online ? ' 就绪' : ' 离线' }}
</el-tag>
</div>
<div class="toolbar-right">

214
browser_installer.py Executable file
View File

@@ -0,0 +1,214 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
浏览器自动下载安装模块
检测本地是否有Playwright浏览器如果没有则自动下载安装
"""
import os
import sys
import shutil
import subprocess
from pathlib import Path
# 设置浏览器安装路径支持Docker和本地环境
# Docker环境: PLAYWRIGHT_BROWSERS_PATH环境变量已设置为 /ms-playwright
# 本地环境: 使用Playwright默认路径
if 'PLAYWRIGHT_BROWSERS_PATH' in os.environ:
BROWSERS_PATH = os.environ['PLAYWRIGHT_BROWSERS_PATH']
else:
# Windows: %USERPROFILE%\AppData\Local\ms-playwright
# Linux: ~/.cache/ms-playwright
if sys.platform == 'win32':
BROWSERS_PATH = str(Path.home() / "AppData" / "Local" / "ms-playwright")
else:
BROWSERS_PATH = str(Path.home() / ".cache" / "ms-playwright")
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = BROWSERS_PATH
class BrowserInstaller:
"""浏览器安装器"""
def __init__(self, log_callback=None):
"""
初始化安装器
Args:
log_callback: 日志回调函数
"""
self.log_callback = log_callback
def log(self, message):
"""输出日志"""
if self.log_callback:
self.log_callback(message)
else:
try:
print(message)
except UnicodeEncodeError:
# 如果打印Unicode字符失败替换特殊字符
safe_message = message.replace('', '[OK]').replace('', '[X]')
print(safe_message)
def check_playwright_installed(self):
"""检查Playwright是否已安装"""
try:
import playwright
self.log("✓ Playwright已安装")
return True
except ImportError:
self.log("✗ Playwright未安装")
return False
def check_chromium_installed(self):
"""检查Chromium浏览器是否已安装"""
try:
from playwright.sync_api import sync_playwright
# 尝试启动浏览器检查是否可用
with sync_playwright() as p:
try:
# 使用超时快速检查
browser = p.chromium.launch(headless=True, timeout=5000)
browser.close()
self.log("✓ Chromium浏览器已安装且可用")
return True
except Exception as e:
error_msg = str(e)
self.log(f"✗ Chromium浏览器不可用: {error_msg}")
# 检查是否是路径不存在的错误
if "Executable doesn't exist" in error_msg:
self.log("检测到浏览器文件缺失,需要重新安装")
return False
except Exception as e:
self.log(f"✗ 检查浏览器时出错: {str(e)}")
return False
def install_chromium(self):
"""安装Chromium浏览器"""
try:
self.log("正在安装 Chromium 浏览器...")
# 查找 playwright 可执行文件
playwright_cli = None
possible_paths = [
os.path.join(os.path.dirname(sys.executable), "Scripts", "playwright.exe"),
os.path.join(os.path.dirname(sys.executable), "playwright.exe"),
os.path.join(os.path.dirname(sys.executable), "Scripts", "playwright"),
os.path.join(os.path.dirname(sys.executable), "playwright"),
"playwright", # 系统PATH中
]
for path in possible_paths:
if os.path.exists(path) or shutil.which(path):
playwright_cli = path
break
# 如果找到了 playwright CLI直接调用
if playwright_cli:
self.log(f"使用 Playwright CLI: {playwright_cli}")
result = subprocess.run(
[playwright_cli, "install", "chromium"],
capture_output=True,
text=True,
timeout=300
)
else:
# 检测是否是 Nuitka 编译的程序
is_nuitka = hasattr(sys, 'frozen') or '__compiled__' in globals()
if is_nuitka:
self.log("检测到 Nuitka 编译环境")
self.log("✗ 无法找到 playwright CLI 工具")
self.log("请手动运行: playwright install chromium")
return False
else:
# 使用 python -m
result = subprocess.run(
[sys.executable, "-m", "playwright", "install", "chromium"],
capture_output=True,
text=True,
timeout=300
)
if result.returncode == 0:
self.log("✓ Chromium浏览器安装成功")
return True
else:
self.log(f"✗ 浏览器安装失败: {result.stderr}")
return False
except subprocess.TimeoutExpired:
self.log("✗ 浏览器安装超时")
return False
except Exception as e:
self.log(f"✗ 浏览器安装出错: {str(e)}")
return False
def auto_install(self):
"""
自动检测并安装所需环境
Returns:
是否成功安装或已安装
"""
self.log("=" * 60)
self.log("检查浏览器环境...")
self.log("=" * 60)
# 1. 检查Playwright是否安装
if not self.check_playwright_installed():
self.log("✗ Playwright未安装无法继续")
self.log("请确保程序包含 Playwright 库")
return False
# 2. 检查Chromium浏览器是否安装
if not self.check_chromium_installed():
self.log("\n未检测到Chromium浏览器开始自动安装...")
# 安装浏览器
if not self.install_chromium():
self.log("✗ 浏览器安装失败")
self.log("\n您可以尝试以下方法:")
self.log("1. 手动执行: playwright install chromium")
self.log("2. 检查网络连接后重试")
self.log("3. 检查防火墙设置")
return False
self.log("\n" + "=" * 60)
self.log("✓ 浏览器环境检查完成,一切就绪!")
self.log("=" * 60 + "\n")
return True
def check_and_install_browser(log_callback=None):
"""
便捷函数:检查并安装浏览器
Args:
log_callback: 日志回调函数
Returns:
是否成功
"""
installer = BrowserInstaller(log_callback)
return installer.auto_install()
# 测试代码
if __name__ == "__main__":
print("浏览器自动安装工具")
print("=" * 60)
installer = BrowserInstaller()
success = installer.auto_install()
if success:
print("\n✓ 安装成功!您现在可以运行主程序了。")
else:
print("\n✗ 安装失败,请查看上方错误信息。")
print("=" * 60)

View File

@@ -7,62 +7,48 @@ services:
ports:
- "51232:51233"
volumes:
- ./data:/app/data # 数据库持久化
- ./logs:/app/logs # 日志持久化
- ./截图:/app/截图 # 截图持久化
- /etc/localtime:/etc/localtime:ro # 时区同步
- ./static:/app/static # 静态文件(实时更新)
- ./templates:/app/templates # 模板文件(实时更新)
- ./app.py:/app/app.py # 主程序(实时更新)
- ./database.py:/app/database.py # 数据库模块(实时更新)
- ./data:/app/data
- ./logs:/app/logs
- ./截图:/app/截图
- ./playwright:/ms-playwright
- /etc/localtime:/etc/localtime:ro
- ./static:/app/static
- ./templates:/app/templates
- ./app.py:/app/app.py
- ./database.py:/app/database.py
# 代码热更新
- ./services:/app/services
- ./routes:/app/routes
- ./db:/app/db
- ./security:/app/security
- ./realtime:/app/realtime
- ./api_browser.py:/app/api_browser.py
- ./app_config.py:/app/app_config.py
- ./app_logger.py:/app/app_logger.py
- ./app_security.py:/app/app_security.py
- ./browser_pool_worker.py:/app/browser_pool_worker.py
- ./crypto_utils.py:/app/crypto_utils.py
- ./db_pool.py:/app/db_pool.py
- ./email_service.py:/app/email_service.py
- ./password_utils.py:/app/password_utils.py
- ./playwright_automation.py:/app/playwright_automation.py
- ./task_checkpoint.py:/app/task_checkpoint.py
dns:
- 223.5.5.5
- 114.114.114.114
- 119.29.29.29
environment:
- TZ=Asia/Shanghai
- PYTHONUNBUFFERED=1
# Flask 配置
- PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
- FLASK_ENV=production
- FLASK_DEBUG=false
# 服务器配置
- SERVER_HOST=0.0.0.0
- SERVER_PORT=51233
# 数据库配置
- DB_FILE=data/app_data.db
- DB_POOL_SIZE=5
- SYSTEM_CONFIG_CACHE_TTL_SECONDS=30
# 并发控制配置
- MAX_CONCURRENT_GLOBAL=2
- MAX_CONCURRENT_PER_ACCOUNT=1
- MAX_CONCURRENT_CONTEXTS=100
# 安全配置
- SESSION_LIFETIME_HOURS=24
- SESSION_COOKIE_SECURE=false
- MAX_CAPTCHA_ATTEMPTS=5
- MAX_IP_ATTEMPTS_PER_HOUR=10
# 日志配置
- LOG_LEVEL=INFO
- LOG_FILE=logs/app.log
- API_DIAGNOSTIC_LOG=0
- API_DIAGNOSTIC_SLOW_MS=0
# 状态推送节流(秒)
- STATUS_PUSH_INTERVAL_SECONDS=2
# wkhtmltoimage 截图配置
- WKHTMLTOIMAGE_FULL_PAGE=0
# 知识管理平台配置
- ZSGL_LOGIN_URL=https://postoa.aidunsoft.com/admin/login.aspx
- ZSGL_INDEX_URL_PATTERN=index.aspx
- PAGE_LOAD_TIMEOUT=60000
restart: unless-stopped
shm_size: 2gb # 为Chromium分配共享内存
# 内存和CPU资源限制
mem_limit: 4g # 硬限制:最大4GB内存
mem_reservation: 2g # 软限制:预留2GB
cpus: '2.0' # 限制使用2个CPU核心
# 健康检查(可选)
shm_size: 2gb
mem_limit: 4g
mem_reservation: 2g
cpus: '2.0'
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:51233 || exit 1"]
interval: 5m

1591
playwright_automation.py Executable file

File diff suppressed because it is too large Load Diff

180
routes/admin_api/update.py Normal file
View File

@@ -0,0 +1,180 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import os
import uuid
from flask import jsonify, request, session
from routes.admin_api import admin_api_bp
from routes.decorators import admin_required
from services.time_utils import get_beijing_now
from services.update_files import (
ensure_update_dirs,
get_update_job_log_path,
get_update_request_path,
get_update_result_path,
get_update_status_path,
load_json_file,
sanitize_job_id,
tail_text_file,
write_json_atomic,
)
def _request_ip() -> str:
try:
return request.headers.get("X-Forwarded-For", "").split(",")[0].strip() or request.remote_addr or ""
except Exception:
return ""
def _make_job_id(prefix: str = "upd") -> str:
now_str = get_beijing_now().strftime("%Y%m%d_%H%M%S")
rand = uuid.uuid4().hex[:8]
return f"{prefix}_{now_str}_{rand}"
def _has_pending_request() -> bool:
try:
return os.path.exists(get_update_request_path())
except Exception:
return False
def _parse_bool_field(data: dict, key: str) -> bool | None:
if not isinstance(data, dict) or key not in data:
return None
value = data.get(key)
if isinstance(value, bool):
return value
if isinstance(value, int):
if value in (0, 1):
return bool(value)
raise ValueError(f"{key} 必须是 0/1 或 true/false")
if isinstance(value, str):
text = value.strip().lower()
if text in ("1", "true", "yes", "y", "on"):
return True
if text in ("0", "false", "no", "n", "off", ""):
return False
raise ValueError(f"{key} 必须是 0/1 或 true/false")
if value is None:
return None
raise ValueError(f"{key} 必须是 0/1 或 true/false")
@admin_api_bp.route("/update/status", methods=["GET"])
@admin_required
def get_update_status_api():
"""读取宿主机 Update-Agent 写入的 update/status.json。"""
ensure_update_dirs()
status_path = get_update_status_path()
data, err = load_json_file(status_path)
if err:
return jsonify({"ok": False, "error": f"读取 status 失败: {err}", "data": data}), 200
if not data:
return jsonify({"ok": False, "error": "未发现更新状态Update-Agent 可能未运行)"}), 200
data.setdefault("update_available", False)
return jsonify({"ok": True, "data": data}), 200
@admin_api_bp.route("/update/result", methods=["GET"])
@admin_required
def get_update_result_api():
"""读取 update/result.json最近一次更新执行结果"""
ensure_update_dirs()
result_path = get_update_result_path()
data, err = load_json_file(result_path)
if err:
return jsonify({"ok": False, "error": f"读取 result 失败: {err}", "data": data}), 200
if not data:
return jsonify({"ok": True, "data": None}), 200
return jsonify({"ok": True, "data": data}), 200
@admin_api_bp.route("/update/log", methods=["GET"])
@admin_required
def get_update_log_api():
"""读取 update/jobs/<job_id>.log 的末尾内容(用于后台展示进度)。"""
ensure_update_dirs()
job_id = sanitize_job_id(request.args.get("job_id"))
if not job_id:
# 若未指定,则尝试用 result.json 的 job_id
result_data, _ = load_json_file(get_update_result_path())
job_id = sanitize_job_id(result_data.get("job_id") if isinstance(result_data, dict) else None)
if not job_id:
return jsonify({"ok": True, "job_id": None, "log": "", "truncated": False}), 200
max_bytes = request.args.get("max_bytes", "200000")
try:
max_bytes_i = int(max_bytes)
except Exception:
max_bytes_i = 200_000
max_bytes_i = max(10_000, min(2_000_000, max_bytes_i))
log_path = get_update_job_log_path(job_id)
text, truncated = tail_text_file(log_path, max_bytes=max_bytes_i)
return jsonify({"ok": True, "job_id": job_id, "log": text, "truncated": truncated}), 200
@admin_api_bp.route("/update/check", methods=["POST"])
@admin_required
def request_update_check_api():
"""请求宿主机 Update-Agent 立刻执行一次检查更新。"""
ensure_update_dirs()
if _has_pending_request():
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
job_id = _make_job_id(prefix="chk")
payload = {
"job_id": job_id,
"action": "check",
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
"requested_by": session.get("admin_username") or "",
"requested_ip": _request_ip(),
}
write_json_atomic(get_update_request_path(), payload)
return jsonify({"success": True, "job_id": job_id}), 200
@admin_api_bp.route("/update/run", methods=["POST"])
@admin_required
def request_update_run_api():
"""请求宿主机 Update-Agent 执行一键更新并重启服务。"""
ensure_update_dirs()
if _has_pending_request():
return jsonify({"error": "已有更新请求正在处理中,请稍后再试"}), 409
data = request.json or {}
try:
build_no_cache = _parse_bool_field(data, "build_no_cache")
if build_no_cache is None:
build_no_cache = _parse_bool_field(data, "no_cache")
build_pull = _parse_bool_field(data, "build_pull")
if build_pull is None:
build_pull = _parse_bool_field(data, "pull")
except ValueError as e:
return jsonify({"error": str(e)}), 400
job_id = _make_job_id(prefix="upd")
payload = {
"job_id": job_id,
"action": "update",
"requested_at": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
"requested_by": session.get("admin_username") or "",
"requested_ip": _request_ip(),
"build_no_cache": bool(build_no_cache) if build_no_cache is not None else False,
"build_pull": bool(build_pull) if build_pull is not None else False,
}
write_json_atomic(get_update_request_path(), payload)
return jsonify(
{
"success": True,
"job_id": job_id,
"message": "已提交更新请求服务将重启页面可能短暂不可用请等待1-2分钟后刷新",
}
), 200

112
services/browser_manager.py Normal file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import threading
import time
from typing import Optional
from app_logger import get_logger
from browser_installer import check_and_install_browser
from playwright_automation import PlaywrightBrowserManager
logger = get_logger("browser_manager")
_browser_manager: Optional[PlaywrightBrowserManager] = None
_lock = threading.Lock()
_cond = threading.Condition(_lock)
_init_in_progress = False
_init_error: Optional[str] = None
_init_thread: Optional[threading.Thread] = None
def get_browser_manager() -> Optional[PlaywrightBrowserManager]:
return _browser_manager
def is_browser_manager_ready() -> bool:
return _browser_manager is not None
def get_browser_manager_init_error() -> Optional[str]:
return _init_error
def init_browser_manager(*, block: bool = True, timeout: Optional[float] = None) -> bool:
global _browser_manager
global _init_in_progress, _init_error
deadline = time.monotonic() + float(timeout) if timeout is not None else None
with _cond:
if _browser_manager is not None:
return True
if _init_in_progress:
if not block:
return False
while _init_in_progress:
if deadline is None:
_cond.wait(timeout=0.5)
continue
remaining = deadline - time.monotonic()
if remaining <= 0:
break
_cond.wait(timeout=min(0.5, remaining))
return _browser_manager is not None
_init_in_progress = True
_init_error = None
ok = False
error: Optional[str] = None
manager: Optional[PlaywrightBrowserManager] = None
try:
logger.info("正在初始化Playwright浏览器管理器...")
if not check_and_install_browser(log_callback=lambda msg, account_id=None: logger.info(str(msg))):
error = "浏览器环境检查失败"
logger.error("浏览器环境检查失败!")
ok = False
else:
manager = PlaywrightBrowserManager(
headless=True,
log_callback=lambda msg, account_id=None: logger.info(str(msg)),
)
ok = True
logger.info("Playwright浏览器管理器创建成功")
except Exception as exc:
error = f"{type(exc).__name__}: {exc}"
logger.exception("初始化Playwright浏览器管理器时发生异常")
ok = False
with _cond:
if ok and manager is not None:
_browser_manager = manager
else:
_init_error = error or "初始化失败"
_init_in_progress = False
_cond.notify_all()
return ok
def init_browser_manager_async() -> None:
"""异步初始化浏览器环境,避免阻塞 Web 请求/服务启动。"""
global _init_thread
def _worker():
try:
init_browser_manager(block=True)
except Exception:
logger.exception("异步初始化浏览器管理器失败")
with _cond:
if _browser_manager is not None:
return
if _init_thread and _init_thread.is_alive():
return
if _init_in_progress:
return
_init_thread = threading.Thread(target=_worker, daemon=True, name="browser-manager-init")
_init_thread.start()

View File

@@ -1,34 +1,34 @@
{
"_email-C4xyG93p.js": {
"file": "assets/email-C4xyG93p.js",
"_email-BsKBHU5S.js": {
"file": "assets/email-BsKBHU5S.js",
"name": "email",
"imports": [
"index.html"
]
},
"_system-C6kBIFhi.js": {
"file": "assets/system-C6kBIFhi.js",
"name": "system",
"imports": [
"index.html"
]
},
"_tasks-dxahzB_w.js": {
"file": "assets/tasks-dxahzB_w.js",
"_tasks-DpslJtm_.js": {
"file": "assets/tasks-DpslJtm_.js",
"name": "tasks",
"imports": [
"index.html"
]
},
"_users-ecMaaAFD.js": {
"file": "assets/users-ecMaaAFD.js",
"_update-DcFD-YxU.js": {
"file": "assets/update-DcFD-YxU.js",
"name": "update",
"imports": [
"index.html"
]
},
"_users-CC9BckjT.js": {
"file": "assets/users-CC9BckjT.js",
"name": "users",
"imports": [
"index.html"
]
},
"index.html": {
"file": "assets/index-DKH_HvPt.js",
"file": "assets/index-CdjS44Uj.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -39,16 +39,15 @@
"src/pages/LogsPage.vue",
"src/pages/AnnouncementsPage.vue",
"src/pages/EmailPage.vue",
"src/pages/SecurityPage.vue",
"src/pages/SystemPage.vue",
"src/pages/SettingsPage.vue"
],
"css": [
"assets/index-_5Ec1Hmd.css"
"assets/index-EWm4DZW8.css"
]
},
"src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-kpoSCxEP.js",
"file": "assets/AnnouncementsPage-Djmq3Wb7.js",
"name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true,
@@ -56,24 +55,24 @@
"index.html"
],
"css": [
"assets/AnnouncementsPage-BhIwmMSX.css"
"assets/AnnouncementsPage-CjcC-aWD.css"
]
},
"src/pages/EmailPage.vue": {
"file": "assets/EmailPage-CEtsoP5P.js",
"file": "assets/EmailPage-q6nJlTue.js",
"name": "EmailPage",
"src": "src/pages/EmailPage.vue",
"isDynamicEntry": true,
"imports": [
"_email-C4xyG93p.js",
"_email-BsKBHU5S.js",
"index.html"
],
"css": [
"assets/EmailPage-BH6ksrcc.css"
"assets/EmailPage-BxzHc6tN.css"
]
},
"src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-ByHln3Ce.js",
"file": "assets/FeedbacksPage-Drw6uvSR.js",
"name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true,
@@ -85,13 +84,13 @@
]
},
"src/pages/LogsPage.vue": {
"file": "assets/LogsPage-vZFAwgb-.js",
"file": "assets/LogsPage-DQd9IS3I.js",
"name": "LogsPage",
"src": "src/pages/LogsPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-ecMaaAFD.js",
"_tasks-dxahzB_w.js",
"_users-CC9BckjT.js",
"_tasks-DpslJtm_.js",
"index.html"
],
"css": [
@@ -99,34 +98,22 @@
]
},
"src/pages/ReportPage.vue": {
"file": "assets/ReportPage--ClMBhif.js",
"file": "assets/ReportPage-Dnk3wsl3.js",
"name": "ReportPage",
"src": "src/pages/ReportPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_email-C4xyG93p.js",
"_tasks-dxahzB_w.js",
"_system-C6kBIFhi.js"
"_email-BsKBHU5S.js",
"_tasks-DpslJtm_.js",
"_update-DcFD-YxU.js"
],
"css": [
"assets/ReportPage-Q8rCsG8A.css"
]
},
"src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-DBhX0IuO.js",
"name": "SecurityPage",
"src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html"
],
"css": [
"assets/SecurityPage-Dv9jYTtC.css"
"assets/ReportPage-TpqQWWvU.css"
]
},
"src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-D91FOriC.js",
"file": "assets/SettingsPage-YOW1Apwk.js",
"name": "SettingsPage",
"src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true,
@@ -134,33 +121,33 @@
"index.html"
],
"css": [
"assets/SettingsPage-DKTq8S2K.css"
"assets/SettingsPage-DGdwb4W2.css"
]
},
"src/pages/SystemPage.vue": {
"file": "assets/SystemPage-DVj-4Lnp.js",
"file": "assets/SystemPage-DCcH_SAQ.js",
"name": "SystemPage",
"src": "src/pages/SystemPage.vue",
"isDynamicEntry": true,
"imports": [
"_system-C6kBIFhi.js",
"_update-DcFD-YxU.js",
"index.html"
],
"css": [
"assets/SystemPage-C8GQyKcD.css"
"assets/SystemPage-BjTkcmTG.css"
]
},
"src/pages/UsersPage.vue": {
"file": "assets/UsersPage-C_vL5-r3.js",
"file": "assets/UsersPage-DhTO_5zp.js",
"name": "UsersPage",
"src": "src/pages/UsersPage.vue",
"isDynamicEntry": true,
"imports": [
"_users-ecMaaAFD.js",
"_users-CC9BckjT.js",
"index.html"
],
"css": [
"assets/UsersPage-CC4Unpwt.css"
"assets/UsersPage-CbiPbpuj.css"
]
}
}

View File

@@ -1 +0,0 @@
.page-stack[data-v-cad97d6b]{display:flex;flex-direction:column;gap:12px}.card[data-v-cad97d6b]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-cad97d6b]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-cad97d6b]{margin-top:10px;font-size:12px;color:var(--app-muted)}.image-preview[data-v-cad97d6b]{margin:6px 0 2px;display:flex;justify-content:flex-start}.image-preview img[data-v-cad97d6b]{max-width:280px;max-height:160px;border-radius:8px;border:1px solid var(--app-border);object-fit:contain}.image-upload-row[data-v-cad97d6b]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.image-input[data-v-cad97d6b]{display:none}.image-url[data-v-cad97d6b]{font-size:12px;color:var(--app-muted);word-break:break-all}.announcement-view[data-v-cad97d6b]{display:flex;flex-direction:column;gap:12px}.announcement-view-text[data-v-cad97d6b]{white-space:pre-wrap;line-height:1.6;font-size:14px}.announcement-view-image[data-v-cad97d6b]{max-width:100%;max-height:320px;border-radius:10px;border:1px solid var(--app-border);object-fit:contain}.table-wrap[data-v-cad97d6b]{overflow-x:auto}.ellipsis[data-v-cad97d6b]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-cad97d6b]{display:flex;flex-wrap:wrap;gap:8px}

View File

@@ -0,0 +1 @@
.page-stack[data-v-a7b3418e]{display:flex;flex-direction:column;gap:12px}.card[data-v-a7b3418e]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-a7b3418e]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-a7b3418e]{margin-top:10px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-a7b3418e]{overflow-x:auto}.ellipsis[data-v-a7b3418e]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.actions[data-v-a7b3418e]{display:flex;flex-wrap:wrap;gap:8px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-7a7e1e9d]{display:flex;flex-direction:column;gap:12px}.toolbar[data-v-7a7e1e9d]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-7a7e1e9d]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-head[data-v-7a7e1e9d]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-7a7e1e9d]{margin:0;font-size:14px;font-weight:800}.help[data-v-7a7e1e9d]{margin-top:8px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-7a7e1e9d]{overflow-x:auto}.stat-card[data-v-7a7e1e9d]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-value[data-v-7a7e1e9d]{font-size:20px;font-weight:900;line-height:1.1}.stat-label[data-v-7a7e1e9d]{margin-top:6px;font-size:12px;color:var(--app-muted)}.ok[data-v-7a7e1e9d]{color:#047857}.err[data-v-7a7e1e9d]{color:#b91c1c}.sub-stats[data-v-7a7e1e9d]{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.ellipsis[data-v-7a7e1e9d]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-7a7e1e9d]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-7a7e1e9d]{font-size:12px}.dialog-actions[data-v-7a7e1e9d]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.spacer[data-v-7a7e1e9d]{flex:1}

View File

@@ -0,0 +1 @@
.page-stack[data-v-ff849557]{display:flex;flex-direction:column;gap:12px}.toolbar[data-v-ff849557]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.card[data-v-ff849557]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-head[data-v-ff849557]{display:flex;align-items:baseline;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.section-title[data-v-ff849557]{margin:0;font-size:14px;font-weight:800}.help[data-v-ff849557]{margin-top:8px;font-size:12px;color:var(--app-muted)}.table-wrap[data-v-ff849557]{overflow-x:auto}.stat-card[data-v-ff849557]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-value[data-v-ff849557]{font-size:20px;font-weight:900;line-height:1.1}.stat-label[data-v-ff849557]{margin-top:6px;font-size:12px;color:var(--app-muted)}.ok[data-v-ff849557]{color:#047857}.err[data-v-ff849557]{color:#b91c1c}.sub-stats[data-v-ff849557]{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.ellipsis[data-v-ff849557]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pagination[data-v-ff849557]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-ff849557]{font-size:12px}.dialog-actions[data-v-ff849557]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.spacer[data-v-ff849557]{flex:1}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-22d57053]{display:flex;flex-direction:column;gap:12px}.toolbar[data-v-22d57053]{display:flex;gap:10px;align-items:center;flex-wrap:wrap}.stats-row[data-v-22d57053]{margin-bottom:2px}.card[data-v-22d57053]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.sub-card[data-v-22d57053]{margin-top:12px;border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-card[data-v-22d57053]{border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.stat-value[data-v-22d57053]{font-size:22px;font-weight:800;line-height:1.1}.stat-label[data-v-22d57053]{margin-top:6px;font-size:12px;color:var(--app-muted)}.filters[data-v-22d57053]{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:12px}.table-wrap[data-v-22d57053]{overflow-x:auto}.ellipsis[data-v-22d57053]{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.mono[data-v-22d57053]{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.pagination[data-v-22d57053]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap}.page-hint[data-v-22d57053]{font-size:12px}.inner-tabs[data-v-22d57053]{margin-top:6px}.risk-head[data-v-22d57053]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px;flex-wrap:wrap}.risk-title[data-v-22d57053]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.dialog-actions[data-v-22d57053]{display:flex;align-items:center;gap:10px}.spacer[data-v-22d57053]{flex:1}

View File

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

View File

@@ -0,0 +1 @@
.page-stack[data-v-2f4b840f]{display:flex;flex-direction:column;gap:12px}.card[data-v-2f4b840f]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-2f4b840f]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-2f4b840f]{margin-top:10px;font-size:12px;color:var(--app-muted)}

View File

@@ -1 +0,0 @@
.page-stack[data-v-12a26d11]{display:flex;flex-direction:column;gap:12px}.card[data-v-12a26d11]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-12a26d11]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-12a26d11]{margin-top:10px;font-size:12px;color:var(--app-muted)}

View File

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

View File

@@ -0,0 +1 @@
.page-stack[data-v-d88590f1]{display:flex;flex-direction:column;gap:12px}.card[data-v-d88590f1]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-d88590f1]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-d88590f1]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-d88590f1]{display:flex;flex-wrap:wrap;gap:10px}

View File

@@ -1 +0,0 @@
.page-stack[data-v-b359577d]{display:flex;flex-direction:column;gap:12px}.card[data-v-b359577d]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-b359577d]{margin:0 0 12px;font-size:14px;font-weight:800}.kdocs-qr[data-v-b359577d]{display:flex;flex-direction:column;align-items:center;gap:12px}.kdocs-qr img[data-v-b359577d]{width:260px;max-width:100%;border:1px solid var(--app-border);border-radius:8px;padding:8px;background:#fff}.help[data-v-b359577d]{margin-top:6px;font-size:12px;color:var(--app-muted)}.row-actions[data-v-b359577d]{display:flex;flex-wrap:wrap;gap:10px}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page-stack[data-v-d73d2b82]{display:flex;flex-direction:column;gap:12px}.card[data-v-d73d2b82]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-d73d2b82]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-d73d2b82]{margin-top:10px;font-size:12px}.table-wrap[data-v-d73d2b82]{overflow-x:auto}.user-block[data-v-d73d2b82]{display:flex;flex-direction:column;gap:2px}.user-main[data-v-d73d2b82]{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}.user-sub[data-v-d73d2b82]{font-size:12px}.vip-sub[data-v-d73d2b82]{font-size:12px;color:#7c3aed}.actions[data-v-d73d2b82]{display:flex;flex-wrap:wrap;gap:8px}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.page-stack[data-v-84b2f73a]{display:flex;flex-direction:column;gap:12px}.card[data-v-84b2f73a]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.section-title[data-v-84b2f73a]{margin:0 0 12px;font-size:14px;font-weight:800}.help[data-v-84b2f73a]{margin-top:10px;font-size:12px}.table-wrap[data-v-84b2f73a]{overflow-x:auto}.user-block[data-v-84b2f73a]{display:flex;flex-direction:column;gap:2px}.user-main[data-v-84b2f73a]{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}.user-sub[data-v-84b2f73a]{font-size:12px}.vip-sub[data-v-84b2f73a]{font-size:12px;color:#7c3aed}.actions[data-v-84b2f73a]{display:flex;flex-wrap:wrap;gap:8px}

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{a}from"./index-DKH_HvPt.js";async function s(){const{data:t}=await a.get("/system/config");return t}async function c(t){const{data:e}=await a.post("/system/config",t);return e}async function o(){const{data:t}=await a.post("/schedule/execute",{});return t}export{o as e,s as f,c as u};

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,20 +1,24 @@
{
"_accounts-DOetW5YJ.js": {
"file": "assets/accounts-DOetW5YJ.js",
"_accounts-BXD0We06.js": {
"file": "assets/accounts-BXD0We06.js",
"name": "accounts",
"imports": [
"index.html"
]
},
"_auth-Hh1F6LOW.js": {
"file": "assets/auth-Hh1F6LOW.js",
"_auth-cf7b3Gq2.js": {
"file": "assets/auth-cf7b3Gq2.js",
"name": "auth",
"imports": [
"index.html"
]
},
"_password-7ryi82gE.js": {
"file": "assets/password-7ryi82gE.js",
"name": "password"
},
"index.html": {
"file": "assets/index-7hTgh8K-.js",
"file": "assets/index-DhsLPY8p.js",
"name": "index",
"src": "index.html",
"isEntry": true,
@@ -28,68 +32,70 @@
"src/pages/ScreenshotsPage.vue"
],
"css": [
"assets/index-BVjJVlht.css"
"assets/index-CD3NfpmF.css"
]
},
"src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-D8z2pFq6.js",
"file": "assets/AccountsPage-38dq1Ex4.js",
"name": "AccountsPage",
"src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DOetW5YJ.js",
"_accounts-BXD0We06.js",
"index.html"
],
"css": [
"assets/AccountsPage-CMc3b3Am.css"
"assets/AccountsPage-CkDdMK5Q.css"
]
},
"src/pages/LoginPage.vue": {
"file": "assets/LoginPage-CdYvfyH1.js",
"file": "assets/LoginPage-B_fgHOTT.js",
"name": "LoginPage",
"src": "src/pages/LoginPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-Hh1F6LOW.js"
"_auth-cf7b3Gq2.js",
"_password-7ryi82gE.js"
],
"css": [
"assets/LoginPage-CnwOLKJz.css"
"assets/LoginPage-8DI6Rf67.css"
]
},
"src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-9AQGZ_pd.js",
"file": "assets/RegisterPage-B_Z92PVI.js",
"name": "RegisterPage",
"src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-Hh1F6LOW.js"
"_auth-cf7b3Gq2.js"
],
"css": [
"assets/RegisterPage-BOcNcW5D.css"
"assets/RegisterPage-yylt2w7b.css"
]
},
"src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage-Dii_hXu1.js",
"file": "assets/ResetPasswordPage-2f8v-5j9.js",
"name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true,
"imports": [
"index.html",
"_auth-Hh1F6LOW.js"
"_auth-cf7b3Gq2.js",
"_password-7ryi82gE.js"
],
"css": [
"assets/ResetPasswordPage-DybfLMAw.css"
]
},
"src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-DH01Lsib.js",
"file": "assets/SchedulesPage-VLwHd9Sa.js",
"name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true,
"imports": [
"_accounts-DOetW5YJ.js",
"_accounts-BXD0We06.js",
"index.html"
],
"css": [
@@ -97,7 +103,7 @@
]
},
"src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-omyYT14c.js",
"file": "assets/ScreenshotsPage-Dtd_MXUX.js",
"name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true,
@@ -109,7 +115,7 @@
]
},
"src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-C15J2JVk.js",
"file": "assets/VerifyResultPage-8_v-5_kc.js",
"name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true,

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.page[data-v-f1b86f5d]{display:flex;flex-direction:column;gap:12px}.stat-card[data-v-f1b86f5d],.panel[data-v-f1b86f5d]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-label[data-v-f1b86f5d]{font-size:12px}.stat-value[data-v-f1b86f5d]{margin-top:6px;font-size:22px;font-weight:900;letter-spacing:.2px}.stat-suffix[data-v-f1b86f5d]{margin-left:6px;font-size:12px;font-weight:600}.upgrade-banner[data-v-f1b86f5d]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.upgrade-actions[data-v-f1b86f5d]{margin-top:10px}.panel-head[data-v-f1b86f5d]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-f1b86f5d]{font-size:16px;font-weight:900}.panel-actions[data-v-f1b86f5d]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.toolbar[data-v-f1b86f5d]{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;border:1px dashed rgba(17,24,39,.14);border-radius:12px;background:#f6f7fb99}.toolbar-left[data-v-f1b86f5d],.toolbar-middle[data-v-f1b86f5d],.toolbar-right[data-v-f1b86f5d]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.toolbar-right[data-v-f1b86f5d]{margin-left:auto;justify-content:flex-end}.grid[data-v-f1b86f5d]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;align-items:start}.account-card[data-v-f1b86f5d]{border-radius:14px;border:1px solid var(--app-border)}.card-top[data-v-f1b86f5d]{display:flex;gap:10px}.card-check[data-v-f1b86f5d]{padding-top:2px}.card-main[data-v-f1b86f5d]{min-width:0;flex:1}.card-title[data-v-f1b86f5d]{display:flex;align-items:center;justify-content:space-between;gap:10px}.card-name[data-v-f1b86f5d]{font-size:14px;font-weight:900;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-sub[data-v-f1b86f5d]{margin-top:6px;font-size:12px;line-height:1.4;word-break:break-word}.progress[data-v-f1b86f5d]{margin-top:12px}.progress-meta[data-v-f1b86f5d]{margin-top:6px;display:flex;justify-content:space-between;gap:10px;font-size:12px}.card-controls[data-v-f1b86f5d]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.card-buttons[data-v-f1b86f5d]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.vip-body[data-v-f1b86f5d]{padding:12px 0 0}.vip-tip[data-v-f1b86f5d]{margin-top:10px;font-size:13px;line-height:1.6}@media(max-width:480px){.grid[data-v-f1b86f5d]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-f1b86f5d]{width:100%;justify-content:flex-end}.toolbar-left[data-v-f1b86f5d],.toolbar-middle[data-v-f1b86f5d],.toolbar-right[data-v-f1b86f5d]{width:100%}.toolbar-right[data-v-f1b86f5d]{margin-left:0;justify-content:flex-end}}

View File

@@ -0,0 +1 @@
.page[data-v-b4e85160]{display:flex;flex-direction:column;gap:12px}.stat-card[data-v-b4e85160],.panel[data-v-b4e85160]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.stat-label[data-v-b4e85160]{font-size:12px}.stat-value[data-v-b4e85160]{margin-top:6px;font-size:22px;font-weight:900;letter-spacing:.2px}.stat-suffix[data-v-b4e85160]{margin-left:6px;font-size:12px;font-weight:600}.upgrade-banner[data-v-b4e85160]{border-radius:var(--app-radius);border:1px solid var(--app-border)}.upgrade-actions[data-v-b4e85160]{margin-top:10px}.panel-head[data-v-b4e85160]{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;flex-wrap:wrap}.panel-title[data-v-b4e85160]{font-size:16px;font-weight:900}.panel-actions[data-v-b4e85160]{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end}.toolbar[data-v-b4e85160]{display:flex;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;border:1px dashed rgba(17,24,39,.14);border-radius:12px;background:#f6f7fb99}.toolbar-left[data-v-b4e85160],.toolbar-middle[data-v-b4e85160],.toolbar-right[data-v-b4e85160]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.toolbar-right[data-v-b4e85160]{margin-left:auto;justify-content:flex-end}.grid[data-v-b4e85160]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px;align-items:start}.account-card[data-v-b4e85160]{border-radius:14px;border:1px solid var(--app-border)}.card-top[data-v-b4e85160]{display:flex;gap:10px}.card-check[data-v-b4e85160]{padding-top:2px}.card-main[data-v-b4e85160]{min-width:0;flex:1}.card-title[data-v-b4e85160]{display:flex;align-items:center;justify-content:space-between;gap:10px}.card-name[data-v-b4e85160]{font-size:14px;font-weight:900;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-sub[data-v-b4e85160]{margin-top:6px;font-size:12px;line-height:1.4;word-break:break-word}.progress[data-v-b4e85160]{margin-top:12px}.progress-meta[data-v-b4e85160]{margin-top:6px;display:flex;justify-content:space-between;gap:10px;font-size:12px}.card-controls[data-v-b4e85160]{margin-top:12px;display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.card-buttons[data-v-b4e85160]{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}.vip-body[data-v-b4e85160]{padding:12px 0 0}.vip-tip[data-v-b4e85160]{margin-top:10px;font-size:13px;line-height:1.6}@media(max-width:480px){.grid[data-v-b4e85160]{grid-template-columns:1fr}}@media(max-width:768px){.panel-actions[data-v-b4e85160]{width:100%;justify-content:flex-end}.toolbar-left[data-v-b4e85160],.toolbar-middle[data-v-b4e85160],.toolbar-right[data-v-b4e85160]{width:100%}.toolbar-right[data-v-b4e85160]{margin-left:0;justify-content:flex-end}}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.auth-wrap[data-v-50df591d]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-50df591d]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-50df591d]{margin-bottom:14px}.brand-title[data-v-50df591d]{font-size:18px;font-weight:900}.brand-sub[data-v-50df591d]{margin-top:4px;font-size:12px}.links[data-v-50df591d]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:2px 0 10px;flex-wrap:wrap}.submit-btn[data-v-50df591d]{width:100%}.foot[data-v-50df591d]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}.dialog-form[data-v-50df591d]{margin-top:10px}.captcha-row[data-v-50df591d]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-50df591d]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:480px){.captcha-img[data-v-50df591d]{height:38px}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.auth-wrap[data-v-9eb557e5]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-9eb557e5]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-9eb557e5]{margin-bottom:14px}.brand-title[data-v-9eb557e5]{font-size:18px;font-weight:900}.brand-sub[data-v-9eb557e5]{margin-top:4px;font-size:12px}.links[data-v-9eb557e5]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:2px 0 10px;flex-wrap:wrap}.submit-btn[data-v-9eb557e5]{width:100%}.foot[data-v-9eb557e5]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}.dialog-form[data-v-9eb557e5]{margin-top:10px}.captcha-row[data-v-9eb557e5]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-9eb557e5]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}@media(max-width:480px){.captcha-img[data-v-9eb557e5]{height:38px}}

View File

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

View File

@@ -1 +0,0 @@
.auth-wrap[data-v-a9d7804f]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-a9d7804f]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-a9d7804f]{margin-bottom:14px}.brand-title[data-v-a9d7804f]{font-size:18px;font-weight:900}.brand-sub[data-v-a9d7804f]{margin-top:4px;font-size:12px}.alert[data-v-a9d7804f]{margin-bottom:12px}.hint[data-v-a9d7804f]{margin-top:6px;font-size:12px}.captcha-row[data-v-a9d7804f]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-a9d7804f]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}.submit-btn[data-v-a9d7804f]{width:100%;margin-top:4px}.actions[data-v-a9d7804f]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}

View File

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

View File

@@ -0,0 +1 @@
.auth-wrap[data-v-75731a6d]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-75731a6d]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-75731a6d]{margin-bottom:14px}.brand-title[data-v-75731a6d]{font-size:18px;font-weight:900}.brand-sub[data-v-75731a6d]{margin-top:4px;font-size:12px}.alert[data-v-75731a6d]{margin-bottom:12px}.hint[data-v-75731a6d]{margin-top:6px;font-size:12px}.captcha-row[data-v-75731a6d]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-75731a6d]{height:40px;border:1px solid var(--app-border);border-radius:8px;cursor:pointer;-webkit-user-select:none;user-select:none}.submit-btn[data-v-75731a6d]{width:100%;margin-top:4px}.actions[data-v-75731a6d]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
function s(t){const e=String(t||"");return e.length<8?{ok:!1,message:"密码长度至少8位"}:!/[a-zA-Z]/.test(e)||!/\d/.test(e)?{ok:!1,message:"密码必须包含字母和数字"}:{ok:!0,message:""}}export{s as v};