feat: 风险分定时衰减 + 密码提示修复 + 浏览器池API + next回跳

1. 风险分衰减定时任务:
   - services/scheduler.py: 每天 CST 04:00 自动执行 decay_scores()
   - 支持 RISK_SCORE_DECAY_TIME_CST 环境变量覆盖

2. 密码长度提示统一为8位:
   - app-frontend/src/pages/RegisterPage.vue
   - app-frontend/src/layouts/AppLayout.vue
   - admin-frontend/src/pages/SettingsPage.vue
   - templates/register.html

3. 浏览器池统计API:
   - GET /yuyx/api/browser_pool/stats
   - 返回 worker 状态、队列等待数等信息
   - browser_pool_worker.py: 增强 get_stats() 方法

4. 登录后支持 next 参数回跳:
   - app-frontend/src/pages/LoginPage.vue: 检查 ?next= 参数
   - 仅允许站内路径(防止开放重定向)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-27 18:28:21 +08:00
parent 3d9dba272e
commit 1b20478a08
49 changed files with 305 additions and 160 deletions

View File

@@ -8,6 +8,14 @@ const username = ref('')
const password = ref('') const password = ref('')
const submitting = ref(false) const submitting = ref(false)
function validateStrongPassword(value) {
const text = String(value || '')
if (text.length < 8) return { ok: false, message: '密码长度至少8位' }
if (text.length > 128) return { ok: false, message: '密码长度不能超过128个字符' }
if (!/[a-zA-Z]/.test(text) || !/\d/.test(text)) return { ok: false, message: '密码必须包含字母和数字' }
return { ok: true, message: '' }
}
async function relogin() { async function relogin() {
try { try {
await logout() await logout()
@@ -54,8 +62,9 @@ async function savePassword() {
ElMessage.error('请输入新密码') ElMessage.error('请输入新密码')
return return
} }
if (value.length < 6) { const check = validateStrongPassword(value)
ElMessage.error('密码至少6个字符') if (!check.ok) {
ElMessage.error(check.message)
return return
} }

View File

@@ -15,6 +15,7 @@ import {
updateEmailNotify, updateEmailNotify,
} from '../api/settings' } from '../api/settings'
import { useUserStore } from '../stores/user' import { useUserStore } from '../stores/user'
import { validateStrongPassword } from '../utils/password'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -292,8 +293,9 @@ async function onChangePassword() {
ElMessage.error('请填写完整信息') ElMessage.error('请填写完整信息')
return return
} }
if (String(newPassword).length < 6) { const passwordCheck = validateStrongPassword(newPassword)
ElMessage.error('新密码至少6位') if (!passwordCheck.ok) {
ElMessage.error(passwordCheck.message)
return return
} }
if (newPassword !== confirmPassword) { if (newPassword !== confirmPassword) {
@@ -562,7 +564,7 @@ async function dismissAnnouncementPermanently() {
<el-form-item label="当前密码"> <el-form-item label="当前密码">
<el-input v-model="passwordForm.current_password" type="password" show-password autocomplete="current-password" /> <el-input v-model="passwordForm.current_password" type="password" show-password autocomplete="current-password" />
</el-form-item> </el-form-item>
<el-form-item label="新密码(至少6位"> <el-form-item label="新密码(至少8位且包含字母和数字">
<el-input v-model="passwordForm.new_password" type="password" show-password autocomplete="new-password" /> <el-input v-model="passwordForm.new_password" type="password" show-password autocomplete="new-password" />
</el-form-item> </el-form-item>
<el-form-item label="确认新密码"> <el-form-item label="确认新密码">

View File

@@ -105,8 +105,14 @@ async function onSubmit() {
need_captcha: needCaptcha.value, need_captcha: needCaptcha.value,
}) })
ElMessage.success('登录成功,正在跳转...') ElMessage.success('登录成功,正在跳转...')
const urlParams = new URLSearchParams(window.location.search || '')
const next = String(urlParams.get('next') || '').trim()
const safeNext = next && next.startsWith('/') && !next.startsWith('//') && !next.startsWith('/\\') ? next : ''
setTimeout(() => { setTimeout(() => {
window.location.href = '/app' const target = safeNext || '/app'
router.push(target).catch(() => {
window.location.href = target
})
}, 300) }, 300)
} catch (e) { } catch (e) {
const status = e?.response?.status const status = e?.response?.status

View File

@@ -4,6 +4,7 @@ import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { fetchEmailVerifyStatus, generateCaptcha, register } from '../api/auth' import { fetchEmailVerifyStatus, generateCaptcha, register } from '../api/auth'
import { validateStrongPassword } from '../utils/password'
const router = useRouter() const router = useRouter()
@@ -68,8 +69,9 @@ async function onSubmit() {
ElMessage.error(errorText.value) ElMessage.error(errorText.value)
return return
} }
if (password.length < 6) { const passwordCheck = validateStrongPassword(password)
errorText.value = '密码至少6个字符' if (!passwordCheck.ok) {
errorText.value = passwordCheck.message || '密码格式不正确'
ElMessage.error(errorText.value) ElMessage.error(errorText.value)
return return
} }
@@ -166,10 +168,10 @@ onMounted(async () => {
v-model="form.password" v-model="form.password"
type="password" type="password"
show-password show-password
placeholder="至少6个字符" placeholder="至少8位且包含字母和数字"
autocomplete="new-password" autocomplete="new-password"
/> />
<div class="hint app-muted">至少6个字符</div> <div class="hint app-muted">至少8位且包含字母和数字</div>
</el-form-item> </el-form-item>
<el-form-item label="确认密码 *"> <el-form-item label="确认密码 *">
<el-input <el-input

View File

@@ -55,6 +55,7 @@ class BrowserWorker(threading.Thread):
self.total_tasks = 0 self.total_tasks = 0
self.failed_tasks = 0 self.failed_tasks = 0
self.pre_warm = pre_warm self.pre_warm = pre_warm
self.last_activity_ts = 0.0
def log(self, message: str): def log(self, message: str):
"""日志输出""" """日志输出"""
@@ -63,35 +64,37 @@ class BrowserWorker(threading.Thread):
else: else:
print(f"[浏览器池][Worker-{self.worker_id}] {message}") print(f"[浏览器池][Worker-{self.worker_id}] {message}")
def _create_browser(self): def _create_browser(self):
"""创建浏览器实例""" """创建浏览器实例"""
try: try:
from playwright.sync_api import sync_playwright from playwright.sync_api import sync_playwright
self.log("正在创建浏览器...") self.log("正在创建浏览器...")
playwright = sync_playwright().start() playwright = sync_playwright().start()
browser = playwright.chromium.launch( browser = playwright.chromium.launch(
headless=True, headless=True,
args=[ args=[
'--no-sandbox', '--no-sandbox',
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
'--disable-dev-shm-usage', '--disable-dev-shm-usage',
'--disable-gpu', '--disable-gpu',
] ]
) )
self.browser_instance = { created_at = time.time()
'playwright': playwright, self.browser_instance = {
'browser': browser, 'playwright': playwright,
'created_at': time.time(), 'browser': browser,
'use_count': 0, 'created_at': created_at,
'worker_id': self.worker_id 'use_count': 0,
} 'worker_id': self.worker_id
self.log(f"浏览器创建成功") }
return True self.last_activity_ts = created_at
self.log(f"浏览器创建成功")
except Exception as e: return True
self.log(f"创建浏览器失败: {e}")
except Exception as e:
self.log(f"创建浏览器失败: {e}")
return False return False
def _close_browser(self): def _close_browser(self):
@@ -136,18 +139,15 @@ class BrowserWorker(threading.Thread):
else: else:
self.log("Worker启动按需模式等待任务时不占用浏览器资源") self.log("Worker启动按需模式等待任务时不占用浏览器资源")
last_activity_time = 0
if self.pre_warm and not self.browser_instance: if self.pre_warm and not self.browser_instance:
if self._create_browser(): self._create_browser()
last_activity_time = time.time()
self.pre_warm = False self.pre_warm = False
while self.running: while self.running:
try: try:
# 允许运行中触发预热(例如池在初始化后调用 warmup # 允许运行中触发预热(例如池在初始化后调用 warmup
if self.pre_warm and not self.browser_instance: if self.pre_warm and not self.browser_instance:
if self._create_browser(): self._create_browser()
last_activity_time = time.time()
self.pre_warm = False self.pre_warm = False
# 从队列获取任务(带超时,以便能响应停止信号和空闲检查) # 从队列获取任务(带超时,以便能响应停止信号和空闲检查)
@@ -156,8 +156,8 @@ class BrowserWorker(threading.Thread):
task = self.task_queue.get(timeout=TASK_QUEUE_TIMEOUT) task = self.task_queue.get(timeout=TASK_QUEUE_TIMEOUT)
except queue.Empty: except queue.Empty:
# 检查是否需要关闭空闲的浏览器 # 检查是否需要关闭空闲的浏览器
if self.browser_instance and last_activity_time > 0: if self.browser_instance and self.last_activity_ts > 0:
idle_time = time.time() - last_activity_time idle_time = time.time() - self.last_activity_ts
if idle_time > BROWSER_IDLE_TIMEOUT: if idle_time > BROWSER_IDLE_TIMEOUT:
self.log(f"空闲{int(idle_time)}秒,关闭浏览器释放资源") self.log(f"空闲{int(idle_time)}秒,关闭浏览器释放资源")
self._close_browser() self._close_browser()
@@ -192,13 +192,13 @@ class BrowserWorker(threading.Thread):
result = task_func(self.browser_instance, *task_args, **task_kwargs) result = task_func(self.browser_instance, *task_args, **task_kwargs)
callback(result, None) callback(result, None)
self.log(f"任务执行成功") self.log(f"任务执行成功")
last_activity_time = time.time() self.last_activity_ts = time.time()
except Exception as e: except Exception as e:
self.log(f"任务执行失败: {e}") self.log(f"任务执行失败: {e}")
callback(None, str(e)) callback(None, str(e))
self.failed_tasks += 1 self.failed_tasks += 1
last_activity_time = time.time() self.last_activity_ts = time.time()
# 任务失败后,检查浏览器健康 # 任务失败后,检查浏览器健康
if not self._check_browser_health(): if not self._check_browser_health():
@@ -329,21 +329,47 @@ class BrowserWorkerPool:
self.log(f"警告任务队列已满maxsize={self.task_queue.maxsize}),拒绝提交任务") self.log(f"警告任务队列已满maxsize={self.task_queue.maxsize}),拒绝提交任务")
return False return False
def get_stats(self) -> Dict[str, Any]: def get_stats(self) -> Dict[str, Any]:
"""获取线程池统计信息""" """获取线程池统计信息"""
idle_count = sum(1 for w in self.workers if w.idle) workers = list(self.workers or [])
total_tasks = sum(w.total_tasks for w in self.workers) idle_count = sum(1 for w in workers if getattr(w, "idle", False))
failed_tasks = sum(w.failed_tasks for w in self.workers) total_tasks = sum(int(getattr(w, "total_tasks", 0) or 0) for w in workers)
failed_tasks = sum(int(getattr(w, "failed_tasks", 0) or 0) for w in workers)
return {
'pool_size': self.pool_size, worker_details = []
'idle_workers': idle_count, for w in workers:
'busy_workers': self.pool_size - idle_count, browser_instance = getattr(w, "browser_instance", None)
'queue_size': self.task_queue.qsize(), browser_use_count = 0
'total_tasks': total_tasks, browser_created_at = None
'failed_tasks': failed_tasks, if isinstance(browser_instance, dict):
'success_rate': f"{(total_tasks - failed_tasks) / total_tasks * 100:.1f}%" if total_tasks > 0 else "N/A" browser_use_count = int(browser_instance.get("use_count", 0) or 0)
} browser_created_at = browser_instance.get("created_at")
worker_details.append(
{
"worker_id": getattr(w, "worker_id", None),
"idle": bool(getattr(w, "idle", False)),
"has_browser": bool(browser_instance),
"total_tasks": int(getattr(w, "total_tasks", 0) or 0),
"failed_tasks": int(getattr(w, "failed_tasks", 0) or 0),
"browser_use_count": browser_use_count,
"browser_created_at": browser_created_at,
"last_active_ts": float(getattr(w, "last_activity_ts", 0) or 0),
"thread_alive": bool(getattr(w, "is_alive", lambda: False)()),
}
)
return {
'pool_size': self.pool_size,
'idle_workers': idle_count,
'busy_workers': max(0, len(workers) - idle_count),
'queue_size': self.task_queue.qsize(),
'total_tasks': total_tasks,
'failed_tasks': failed_tasks,
'success_rate': f"{(total_tasks - failed_tasks) / total_tasks * 100:.1f}%" if total_tasks > 0 else "N/A",
'workers': worker_details,
'timestamp': time.time(),
}
def wait_for_completion(self, timeout: Optional[float] = None): def wait_for_completion(self, timeout: Optional[float] = None):
"""等待所有任务完成""" """等待所有任务完成"""

View File

@@ -317,6 +317,71 @@ def get_system_stats():
return jsonify(stats) return jsonify(stats)
@admin_api_bp.route("/browser_pool/stats", methods=["GET"])
@admin_required
def get_browser_pool_stats():
"""获取浏览器池状态"""
try:
from browser_pool_worker import get_browser_worker_pool
pool = get_browser_worker_pool()
stats = pool.get_stats() or {}
worker_details = []
for w in stats.get("workers") or []:
last_ts = float(w.get("last_active_ts") or 0)
last_active_at = None
if last_ts > 0:
try:
last_active_at = datetime.fromtimestamp(last_ts, tz=BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S")
except Exception:
last_active_at = None
created_ts = w.get("browser_created_at")
created_at = None
if created_ts:
try:
created_at = datetime.fromtimestamp(float(created_ts), tz=BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S")
except Exception:
created_at = None
worker_details.append(
{
"worker_id": w.get("worker_id"),
"idle": bool(w.get("idle")),
"has_browser": bool(w.get("has_browser")),
"total_tasks": int(w.get("total_tasks") or 0),
"failed_tasks": int(w.get("failed_tasks") or 0),
"browser_use_count": int(w.get("browser_use_count") or 0),
"browser_created_at": created_at,
"browser_created_ts": created_ts,
"last_active_at": last_active_at,
"last_active_ts": last_ts,
"thread_alive": bool(w.get("thread_alive")),
}
)
total_workers = len(worker_details) if worker_details else int(stats.get("pool_size") or 0)
return jsonify(
{
"total_workers": total_workers,
"active_workers": int(stats.get("busy_workers") or 0),
"idle_workers": int(stats.get("idle_workers") or 0),
"queue_size": int(stats.get("queue_size") or 0),
"workers": worker_details,
"summary": {
"total_tasks": int(stats.get("total_tasks") or 0),
"failed_tasks": int(stats.get("failed_tasks") or 0),
"success_rate": stats.get("success_rate"),
},
"server_time_cst": get_beijing_now().strftime("%Y-%m-%d %H:%M:%S"),
}
)
except Exception as e:
logger.exception(f"[AdminAPI] 获取浏览器池状态失败: {e}")
return jsonify({"error": "获取浏览器池状态失败"}), 500
@admin_api_bp.route("/docker_stats", methods=["GET"]) @admin_api_bp.route("/docker_stats", methods=["GET"])
@admin_required @admin_required
def get_docker_stats(): def get_docker_stats():

View File

@@ -150,6 +150,16 @@ def scheduled_task_worker() -> None:
"""定时任务工作线程""" """定时任务工作线程"""
import schedule import schedule
def decay_risk_scores():
"""风险分衰减:每天定时执行一次"""
try:
from security.risk_scorer import RiskScorer
RiskScorer().decay_scores()
logger.info("[定时任务] 风险分衰减已执行")
except Exception as e:
logger.exception(f"[定时任务] 风险分衰减执行失败: {e}")
def cleanup_expired_captcha(): def cleanup_expired_captcha():
try: try:
deleted_count = safe_cleanup_expired_captcha() deleted_count = safe_cleanup_expired_captcha()
@@ -362,7 +372,12 @@ def scheduled_task_worker() -> None:
if schedule_time_cst != str(schedule_time_raw or "").strip(): if schedule_time_cst != str(schedule_time_raw or "").strip():
logger.warning(f"[定时任务] 系统定时时间格式无效,已回退到 {schedule_time_cst} (原值: {schedule_time_raw!r})") logger.warning(f"[定时任务] 系统定时时间格式无效,已回退到 {schedule_time_cst} (原值: {schedule_time_raw!r})")
signature = (schedule_enabled, schedule_time_cst) risk_decay_time_raw = os.environ.get("RISK_SCORE_DECAY_TIME_CST", "04:00")
risk_decay_time_cst = _normalize_hhmm(risk_decay_time_raw, default="04:00")
if risk_decay_time_cst != str(risk_decay_time_raw or "").strip():
logger.warning(f"[定时任务] 风险分衰减时间格式无效,已回退到 {risk_decay_time_cst} (原值: {risk_decay_time_raw!r})")
signature = (schedule_enabled, schedule_time_cst, risk_decay_time_cst)
config_changed = schedule_state.get("signature") != signature config_changed = schedule_state.get("signature") != signature
is_first_run = schedule_state.get("signature") is None is_first_run = schedule_state.get("signature") is None
if (not force) and (not config_changed): if (not force) and (not config_changed):
@@ -374,6 +389,8 @@ def scheduled_task_worker() -> None:
cleanup_time_cst = "03:00" cleanup_time_cst = "03:00"
schedule.every().day.at(cleanup_time_cst).do(cleanup_old_data) schedule.every().day.at(cleanup_time_cst).do(cleanup_old_data)
schedule.every().day.at(risk_decay_time_cst).do(decay_risk_scores)
schedule.every().hour.do(cleanup_expired_captcha) schedule.every().hour.do(cleanup_expired_captcha)
quota_reset_time_cst = "00:00" quota_reset_time_cst = "00:00"
@@ -381,6 +398,7 @@ def scheduled_task_worker() -> None:
if is_first_run: if is_first_run:
logger.info(f"[定时任务] 已设置数据清理任务: 每天 CST {cleanup_time_cst}") logger.info(f"[定时任务] 已设置数据清理任务: 每天 CST {cleanup_time_cst}")
logger.info(f"[定时任务] 已设置风险分衰减: 每天 CST {risk_decay_time_cst}")
logger.info(f"[定时任务] 已设置验证码清理任务: 每小时执行一次") logger.info(f"[定时任务] 已设置验证码清理任务: 每小时执行一次")
logger.info(f"[定时任务] 已设置SMTP配额重置: 每天 CST {quota_reset_time_cst}") logger.info(f"[定时任务] 已设置SMTP配额重置: 每天 CST {quota_reset_time_cst}")

View File

@@ -1,34 +1,34 @@
{ {
"_email-D-nWLD-A.js": { "_email-DSz2K4-y.js": {
"file": "assets/email-D-nWLD-A.js", "file": "assets/email-DSz2K4-y.js",
"name": "email", "name": "email",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_system-CJ2QU_TO.js": { "_system-C_4M4EtK.js": {
"file": "assets/system-CJ2QU_TO.js", "file": "assets/system-C_4M4EtK.js",
"name": "system", "name": "system",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_tasks-CEM7_KIT.js": { "_tasks-yIlAy2Ne.js": {
"file": "assets/tasks-CEM7_KIT.js", "file": "assets/tasks-yIlAy2Ne.js",
"name": "tasks", "name": "tasks",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_users-CA0gIT8G.js": { "_users-BNPg4OEj.js": {
"file": "assets/users-CA0gIT8G.js", "file": "assets/users-BNPg4OEj.js",
"name": "users", "name": "users",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"index.html": { "index.html": {
"file": "assets/index-akVRSJTL.js", "file": "assets/index-Dx-1XhY8.js",
"name": "index", "name": "index",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
@@ -48,7 +48,7 @@
] ]
}, },
"src/pages/AnnouncementsPage.vue": { "src/pages/AnnouncementsPage.vue": {
"file": "assets/AnnouncementsPage-7ij3KbUN.js", "file": "assets/AnnouncementsPage-DP-v4_4f.js",
"name": "AnnouncementsPage", "name": "AnnouncementsPage",
"src": "src/pages/AnnouncementsPage.vue", "src": "src/pages/AnnouncementsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -60,12 +60,12 @@
] ]
}, },
"src/pages/EmailPage.vue": { "src/pages/EmailPage.vue": {
"file": "assets/EmailPage-CuPwCZn-.js", "file": "assets/EmailPage-4etTfx9H.js",
"name": "EmailPage", "name": "EmailPage",
"src": "src/pages/EmailPage.vue", "src": "src/pages/EmailPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_email-D-nWLD-A.js", "_email-DSz2K4-y.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -73,7 +73,7 @@
] ]
}, },
"src/pages/FeedbacksPage.vue": { "src/pages/FeedbacksPage.vue": {
"file": "assets/FeedbacksPage-CSDNvoUn.js", "file": "assets/FeedbacksPage-BlN6FFbD.js",
"name": "FeedbacksPage", "name": "FeedbacksPage",
"src": "src/pages/FeedbacksPage.vue", "src": "src/pages/FeedbacksPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -85,13 +85,13 @@
] ]
}, },
"src/pages/LogsPage.vue": { "src/pages/LogsPage.vue": {
"file": "assets/LogsPage-DDxhRTa7.js", "file": "assets/LogsPage-adLViVmd.js",
"name": "LogsPage", "name": "LogsPage",
"src": "src/pages/LogsPage.vue", "src": "src/pages/LogsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-CA0gIT8G.js", "_users-BNPg4OEj.js",
"_tasks-CEM7_KIT.js", "_tasks-yIlAy2Ne.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -99,22 +99,22 @@
] ]
}, },
"src/pages/ReportPage.vue": { "src/pages/ReportPage.vue": {
"file": "assets/ReportPage-CoI2Nht-.js", "file": "assets/ReportPage-DxDL6AXa.js",
"name": "ReportPage", "name": "ReportPage",
"src": "src/pages/ReportPage.vue", "src": "src/pages/ReportPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_email-D-nWLD-A.js", "_email-DSz2K4-y.js",
"_tasks-CEM7_KIT.js", "_tasks-yIlAy2Ne.js",
"_system-CJ2QU_TO.js" "_system-C_4M4EtK.js"
], ],
"css": [ "css": [
"assets/ReportPage-CW7RwLmI.css" "assets/ReportPage-CW7RwLmI.css"
] ]
}, },
"src/pages/SecurityPage.vue": { "src/pages/SecurityPage.vue": {
"file": "assets/SecurityPage-CQQKpFcS.js", "file": "assets/SecurityPage-BkxWxQhW.js",
"name": "SecurityPage", "name": "SecurityPage",
"src": "src/pages/SecurityPage.vue", "src": "src/pages/SecurityPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -126,7 +126,7 @@
] ]
}, },
"src/pages/SettingsPage.vue": { "src/pages/SettingsPage.vue": {
"file": "assets/SettingsPage-BpSZamEk.js", "file": "assets/SettingsPage-D_cVneyv.js",
"name": "SettingsPage", "name": "SettingsPage",
"src": "src/pages/SettingsPage.vue", "src": "src/pages/SettingsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -134,16 +134,16 @@
"index.html" "index.html"
], ],
"css": [ "css": [
"assets/SettingsPage-DGdwb4W2.css" "assets/SettingsPage-DKTq8S2K.css"
] ]
}, },
"src/pages/SystemPage.vue": { "src/pages/SystemPage.vue": {
"file": "assets/SystemPage-DUY6QC8Y.js", "file": "assets/SystemPage-Cph4odbt.js",
"name": "SystemPage", "name": "SystemPage",
"src": "src/pages/SystemPage.vue", "src": "src/pages/SystemPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_system-CJ2QU_TO.js", "_system-C_4M4EtK.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -151,12 +151,12 @@
] ]
}, },
"src/pages/UsersPage.vue": { "src/pages/UsersPage.vue": {
"file": "assets/UsersPage-hj_Nb-9c.js", "file": "assets/UsersPage-19tzoQBx.js",
"name": "UsersPage", "name": "UsersPage",
"src": "src/pages/UsersPage.vue", "src": "src/pages/UsersPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_users-CA0gIT8G.js", "_users-BNPg4OEj.js",
"index.html" "index.html"
], ],
"css": [ "css": [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
import{P 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,K as i,J as b}from"./index-akVRSJTL.js";async function P(o){const{data:t}=await m.put("/admin/username",{new_username:o});return t}async function C(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 P(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 C(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})])}}},M=T(N,[["__scopeId","data-v-2f4b840f"]]);export{M as default};

View File

@@ -1 +0,0 @@
.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

@@ -0,0 +1 @@
.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{P as m,_ as B,r as p,e as u,f as T,g as P,h as r,j as a,w as l,p as x,K as i,J as b}from"./index-Dx-1XhY8.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

@@ -1,4 +1,4 @@
import{f as ae,u as S,e as te}from"./system-CJ2QU_TO.js";import{P as D,_ as oe,r as s,c as ne,o as ue,e as d,H as se,I as re,g as V,f as F,h as n,j as l,w as t,p as c,A as E,m as $,F as de,q as ie,n as me,J as T,K as p}from"./index-akVRSJTL.js";async function pe(){const{data:f}=await D.get("/proxy/config");return f}async function ce(f){const{data:v}=await D.post("/proxy/config",f);return v}async function ve(f){const{data:v}=await D.post("/proxy/test",f);return v}const ye={class:"page-stack"},_e={class:"app-page-title"},fe={class:"row-actions"},xe={class:"row-actions"},be={__name:"SystemPage",setup(f){const v=s(!1),g=s(2),w=s(1),k=s(3),i=s(!1),P=s("02:00"),x=s("应读"),y=s(["1","2","3","4","5","6","7"]),b=s(!1),_=s(""),C=s(3),B=s(!1),N=s(10),I=s(7),j=[{label:"周一",value:"1"},{label:"周二",value:"2"},{label:"周三",value:"3"},{label:"周四",value:"4"},{label:"周五",value:"5"},{label:"周六",value:"6"},{label:"周日",value:"7"}],L={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},O=ne(()=>(y.value||[]).map(a=>L[Number(a)]||a).join("、"));function W(a){return String(a)==="注册前未读"?"注册前未读":"应读"}async function H(){v.value=!0;try{const[a,e]=await Promise.all([ae(),pe()]);g.value=a.max_concurrent_global??2,w.value=a.max_concurrent_per_account??1,k.value=a.max_screenshot_concurrent??3,i.value=(a.schedule_enabled??0)===1,P.value=a.schedule_time||"02:00",x.value=W(a.schedule_browse_type);const u=String(a.schedule_weekdays||"1,2,3,4,5,6,7").split(",").map(m=>m.trim()).filter(Boolean);y.value=u.length?u:["1","2","3","4","5","6","7"],B.value=(a.auto_approve_enabled??0)===1,N.value=a.auto_approve_hourly_limit??10,I.value=a.auto_approve_vip_days??7,b.value=(e.proxy_enabled??0)===1,_.value=e.proxy_api_url||"",C.value=e.proxy_expire_minutes??3}catch{}finally{v.value=!1}}async function q(){const a={max_concurrent_global:Number(g.value),max_concurrent_per_account:Number(w.value),max_screenshot_concurrent:Number(k.value)};try{await T.confirm(`确定更新并发配置吗? import{f as ae,u as S,e as te}from"./system-C_4M4EtK.js";import{P as D,_ as oe,r as s,c as ne,o as ue,e as d,H as se,I as re,g as V,f as F,h as n,j as l,w as t,p as c,A as E,m as $,F as de,q as ie,n as me,J as T,K as p}from"./index-Dx-1XhY8.js";async function pe(){const{data:f}=await D.get("/proxy/config");return f}async function ce(f){const{data:v}=await D.post("/proxy/config",f);return v}async function ve(f){const{data:v}=await D.post("/proxy/test",f);return v}const ye={class:"page-stack"},_e={class:"app-page-title"},fe={class:"row-actions"},xe={class:"row-actions"},be={__name:"SystemPage",setup(f){const v=s(!1),g=s(2),w=s(1),k=s(3),i=s(!1),P=s("02:00"),x=s("应读"),y=s(["1","2","3","4","5","6","7"]),b=s(!1),_=s(""),C=s(3),B=s(!1),N=s(10),I=s(7),j=[{label:"周一",value:"1"},{label:"周二",value:"2"},{label:"周三",value:"3"},{label:"周四",value:"4"},{label:"周五",value:"5"},{label:"周六",value:"6"},{label:"周日",value:"7"}],L={1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"},O=ne(()=>(y.value||[]).map(a=>L[Number(a)]||a).join("、"));function W(a){return String(a)==="注册前未读"?"注册前未读":"应读"}async function H(){v.value=!0;try{const[a,e]=await Promise.all([ae(),pe()]);g.value=a.max_concurrent_global??2,w.value=a.max_concurrent_per_account??1,k.value=a.max_screenshot_concurrent??3,i.value=(a.schedule_enabled??0)===1,P.value=a.schedule_time||"02:00",x.value=W(a.schedule_browse_type);const u=String(a.schedule_weekdays||"1,2,3,4,5,6,7").split(",").map(m=>m.trim()).filter(Boolean);y.value=u.length?u:["1","2","3","4","5","6","7"],B.value=(a.auto_approve_enabled??0)===1,N.value=a.auto_approve_hourly_limit??10,I.value=a.auto_approve_vip_days??7,b.value=(e.proxy_enabled??0)===1,_.value=e.proxy_api_url||"",C.value=e.proxy_expire_minutes??3}catch{}finally{v.value=!1}}async function q(){const a={max_concurrent_global:Number(g.value),max_concurrent_per_account:Number(w.value),max_screenshot_concurrent:Number(k.value)};try{await T.confirm(`确定更新并发配置吗?
全局并发数: ${a.max_concurrent_global} 全局并发数: ${a.max_concurrent_global}
单账号并发数: ${a.max_concurrent_per_account} 单账号并发数: ${a.max_concurrent_per_account}

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="./vite.svg" /> <link rel="icon" type="image/svg+xml" href="./vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>后台管理 - 知识管理平台</title> <title>后台管理 - 知识管理平台</title>
<script type="module" crossorigin src="./assets/index-akVRSJTL.js"></script> <script type="module" crossorigin src="./assets/index-Dx-1XhY8.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-_5Ec1Hmd.css"> <link rel="stylesheet" crossorigin href="./assets/index-_5Ec1Hmd.css">
</head> </head>
<body> <body>

View File

@@ -1,20 +1,20 @@
{ {
"_accounts-CTfB-Ncr.js": { "_accounts-sBYX3ZUA.js": {
"file": "assets/accounts-CTfB-Ncr.js", "file": "assets/accounts-sBYX3ZUA.js",
"name": "accounts", "name": "accounts",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"_auth-WsWSY0rn.js": { "_auth-GozuPnLT.js": {
"file": "assets/auth-WsWSY0rn.js", "file": "assets/auth-GozuPnLT.js",
"name": "auth", "name": "auth",
"imports": [ "imports": [
"index.html" "index.html"
] ]
}, },
"index.html": { "index.html": {
"file": "assets/index-CEOd73lG.js", "file": "assets/index-Dzjm38x5.js",
"name": "index", "name": "index",
"src": "index.html", "src": "index.html",
"isEntry": true, "isEntry": true,
@@ -28,16 +28,16 @@
"src/pages/ScreenshotsPage.vue" "src/pages/ScreenshotsPage.vue"
], ],
"css": [ "css": [
"assets/index-CD3NfpmF.css" "assets/index-Dfx-VTPl.css"
] ]
}, },
"src/pages/AccountsPage.vue": { "src/pages/AccountsPage.vue": {
"file": "assets/AccountsPage-D3IUho1c.js", "file": "assets/AccountsPage-ZOKJprfz.js",
"name": "AccountsPage", "name": "AccountsPage",
"src": "src/pages/AccountsPage.vue", "src": "src/pages/AccountsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_accounts-CTfB-Ncr.js", "_accounts-sBYX3ZUA.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -45,51 +45,51 @@
] ]
}, },
"src/pages/LoginPage.vue": { "src/pages/LoginPage.vue": {
"file": "assets/LoginPage-x4LlIM56.js", "file": "assets/LoginPage-BrSs7jRF.js",
"name": "LoginPage", "name": "LoginPage",
"src": "src/pages/LoginPage.vue", "src": "src/pages/LoginPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_auth-WsWSY0rn.js" "_auth-GozuPnLT.js"
], ],
"css": [ "css": [
"assets/LoginPage-C_sxX_84.css" "assets/LoginPage-CnwOLKJz.css"
] ]
}, },
"src/pages/RegisterPage.vue": { "src/pages/RegisterPage.vue": {
"file": "assets/RegisterPage-BBedBh-y.js", "file": "assets/RegisterPage-TozgjjGu.js",
"name": "RegisterPage", "name": "RegisterPage",
"src": "src/pages/RegisterPage.vue", "src": "src/pages/RegisterPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_auth-WsWSY0rn.js" "_auth-GozuPnLT.js"
], ],
"css": [ "css": [
"assets/RegisterPage-yylt2w7b.css" "assets/RegisterPage-BOcNcW5D.css"
] ]
}, },
"src/pages/ResetPasswordPage.vue": { "src/pages/ResetPasswordPage.vue": {
"file": "assets/ResetPasswordPage--Vqm02p7.js", "file": "assets/ResetPasswordPage-CLFescRq.js",
"name": "ResetPasswordPage", "name": "ResetPasswordPage",
"src": "src/pages/ResetPasswordPage.vue", "src": "src/pages/ResetPasswordPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"index.html", "index.html",
"_auth-WsWSY0rn.js" "_auth-GozuPnLT.js"
], ],
"css": [ "css": [
"assets/ResetPasswordPage-DybfLMAw.css" "assets/ResetPasswordPage-DybfLMAw.css"
] ]
}, },
"src/pages/SchedulesPage.vue": { "src/pages/SchedulesPage.vue": {
"file": "assets/SchedulesPage-Cln6Gk1v.js", "file": "assets/SchedulesPage-CvQTwIDx.js",
"name": "SchedulesPage", "name": "SchedulesPage",
"src": "src/pages/SchedulesPage.vue", "src": "src/pages/SchedulesPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
"imports": [ "imports": [
"_accounts-CTfB-Ncr.js", "_accounts-sBYX3ZUA.js",
"index.html" "index.html"
], ],
"css": [ "css": [
@@ -97,7 +97,7 @@
] ]
}, },
"src/pages/ScreenshotsPage.vue": { "src/pages/ScreenshotsPage.vue": {
"file": "assets/ScreenshotsPage-BeyAIC93.js", "file": "assets/ScreenshotsPage-BO_6agG8.js",
"name": "ScreenshotsPage", "name": "ScreenshotsPage",
"src": "src/pages/ScreenshotsPage.vue", "src": "src/pages/ScreenshotsPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,
@@ -109,7 +109,7 @@
] ]
}, },
"src/pages/VerifyResultPage.vue": { "src/pages/VerifyResultPage.vue": {
"file": "assets/VerifyResultPage-Ci85Um-V.js", "file": "assets/VerifyResultPage-DAOtFHKf.js",
"name": "VerifyResultPage", "name": "VerifyResultPage",
"src": "src/pages/VerifyResultPage.vue", "src": "src/pages/VerifyResultPage.vue",
"isDynamicEntry": true, "isDynamicEntry": true,

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
.auth-wrap[data-v-c04a6b1b]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px}.auth-card[data-v-c04a6b1b]{width:100%;max-width:420px;border-radius:var(--app-radius);border:1px solid var(--app-border);box-shadow:var(--app-shadow)}.brand[data-v-c04a6b1b]{margin-bottom:14px}.brand-title[data-v-c04a6b1b]{font-size:18px;font-weight:900}.brand-sub[data-v-c04a6b1b]{margin-top:4px;font-size:12px}.links[data-v-c04a6b1b]{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:2px 0 10px;flex-wrap:wrap}.submit-btn[data-v-c04a6b1b]{width:100%}.foot[data-v-c04a6b1b]{margin-top:14px;display:flex;align-items:center;justify-content:center;gap:6px}.dialog-form[data-v-c04a6b1b]{margin-top:10px}.captcha-row[data-v-c04a6b1b]{display:flex;align-items:center;gap:10px;width:100%}.captcha-img[data-v-c04a6b1b]{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-c04a6b1b]{height:38px}}

View File

@@ -0,0 +1 @@
.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}}

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
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-CEOd73lG.js";import{g as z,f as F,b as G}from"./auth-WsWSY0rn.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(""),b=p(""),h=p(!1),l=p(""),_=p(""),V=p(""),K=B(()=>v.value?"邮箱 *":"邮箱(可选)"),P=B(()=>v.value?"必填,用于账号验证":"选填,用于找回密码和接收通知");async function w(){try{const u=await z();b.value=u?.session_id||"",f.value=u?.captcha_image||"",a.captcha=""}catch{b.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}h.value=!0;try{const c=await G({username:u,password:e,email:s,captcha_session:b.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{h.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:h.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-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 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-Dzjm38x5.js";import{g as F,f as G,b as J}from"./auth-GozuPnLT.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-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

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

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 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-Dzjm38x5.js";import{c as J}from"./auth-GozuPnLT.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-CEOd73lG.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-Dzjm38x5.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-CEOd73lG.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-Dzjm38x5.js";async function o(t={}){const{data:a}=await c.get("/accounts",{params:t});return a}async function u(t){const{data:a}=await c.post("/accounts",t);return a}async function r(t,a){const{data:n}=await c.put(`/accounts/${t}`,a);return n}async function e(t){const{data:a}=await c.delete(`/accounts/${t}`);return a}async function i(t,a){const{data:n}=await c.put(`/accounts/${t}/remark`,a);return n}async function p(t,a){const{data:n}=await c.post(`/accounts/${t}/start`,a);return n}async function d(t){const{data:a}=await c.post(`/accounts/${t}/stop`,{});return a}async function f(t){const{data:a}=await c.post("/accounts/batch/start",t);return a}async function w(t){const{data:a}=await c.post("/accounts/batch/stop",t);return a}async function y(){const{data:t}=await c.post("/accounts/clear",{});return t}async function A(t,a={}){const{data:n}=await c.post(`/accounts/${t}/screenshot`,a);return n}export{w as a,f as b,y as c,d,e,o as f,u as g,i as h,p as s,A as t,r as u};

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -4,8 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
<title>知识管理平台</title> <title>知识管理平台</title>
<script type="module" crossorigin src="./assets/index-CEOd73lG.js"></script> <script type="module" crossorigin src="./assets/index-Dzjm38x5.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-CD3NfpmF.css"> <link rel="stylesheet" crossorigin href="./assets/index-Dfx-VTPl.css">
</head> </head>
<body> <body>
<noscript>该页面需要启用 JavaScript 才能使用。</noscript> <noscript>该页面需要启用 JavaScript 才能使用。</noscript>

View File

@@ -2044,8 +2044,13 @@
return; return;
} }
if (newPassword.length < 6) { if (newPassword.length < 8) {
showNotification('密码至少6个字符', 'error'); showNotification('密码长度至少8位', 'error');
return;
}
if (!/[a-zA-Z]/.test(newPassword) || !/\d/.test(newPassword)) {
showNotification('密码必须包含字母和数字', 'error');
return; return;
} }
@@ -2772,11 +2777,16 @@
// 管理员直接重置用户密码 // 管理员直接重置用户密码
async function resetUserPassword(userId) { async function resetUserPassword(userId) {
const newPassword = prompt('请输入新密码(至少6位:'); const newPassword = prompt('请输入新密码(至少8位且包含字母和数字:');
if (!newPassword) return; if (!newPassword) return;
if (newPassword.length < 6) { if (newPassword.length < 8) {
showNotification('密码长度至少6位', 'error'); showNotification('密码长度至少8位', 'error');
return;
}
if (!/[a-zA-Z]/.test(newPassword) || !/\d/.test(newPassword)) {
showNotification('密码必须包含字母和数字', 'error');
return; return;
} }

View File

@@ -814,7 +814,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">新密码</label> <label class="form-label">新密码</label>
<input type="password" class="form-input" id="newPassword" placeholder="请输入新密码(至少6位"> <input type="password" class="form-input" id="newPassword" placeholder="请输入新密码(至少8位且包含字母和数字">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">确认新密码</label> <label class="form-label">确认新密码</label>
@@ -2190,7 +2190,8 @@
const confirmPwd = document.getElementById('confirmPassword').value; const confirmPwd = document.getElementById('confirmPassword').value;
if (!currentPwd) { showToast('请输入当前密码', 'error'); return; } if (!currentPwd) { showToast('请输入当前密码', 'error'); return; }
if (!newPwd || newPwd.length < 6) { showToast('新密码至少6位', 'error'); return; } if (!newPwd || newPwd.length < 8) { showToast('新密码至少8位', 'error'); return; }
if (!/[a-zA-Z]/.test(newPwd) || !/\d/.test(newPwd)) { showToast('密码必须包含字母和数字', 'error'); return; }
if (newPwd !== confirmPwd) { showToast('两次密码不一致', 'error'); return; } if (newPwd !== confirmPwd) { showToast('两次密码不一致', 'error'); return; }
fetch('/api/user/password', { fetch('/api/user/password', {

View File

@@ -163,8 +163,8 @@
<div class="form-group"> <div class="form-group">
<label for="password">密码 *</label> <label for="password">密码 *</label>
<input type="password" id="password" name="password" required minlength="6"> <input type="password" id="password" name="password" required minlength="8">
<small>至少6个字符</small> <small>至少8位</small>
</div> </div>
<div class="form-group"> <div class="form-group">
@@ -239,8 +239,14 @@
return; return;
} }
if (password.length < 6) { if (password.length < 8) {
errorDiv.textContent = '密码至少6个字符'; errorDiv.textContent = '密码长度至少8位';
errorDiv.style.display = 'block';
return;
}
if (!/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
errorDiv.textContent = '密码必须包含字母和数字';
errorDiv.style.display = 'block'; errorDiv.style.display = 'block';
return; return;
} }