Compare commits
21 Commits
v0.4.3
..
ce3f95b8b9
| Author | SHA1 | Date | |
|---|---|---|---|
| ce3f95b8b9 | |||
| 75f5d26964 | |||
| 4c161b63c6 | |||
| d79c11fb15 | |||
| 3b67df19e7 | |||
| c4ca8edd58 | |||
| d7b055f88b | |||
| 39724e6e73 | |||
| 288d30698a | |||
| a9dc056506 | |||
| 9ef58b5724 | |||
| c9067179ff | |||
| 5e3b5e83a4 | |||
| d4e62b1fb1 | |||
| 7471d9aece | |||
| f0c0d0f487 | |||
| e4e1890da3 | |||
| 470ebac20e | |||
| f284e14630 | |||
| bff955e45b | |||
| 6f7ab6dbc6 |
@@ -0,0 +1,4 @@
|
||||
version=0.5.6
|
||||
source=restored-cloudsearch-existing-source
|
||||
updated_at=2026-05-23T19:14:59+08:00
|
||||
backup=/root/cloudsearch_deploy_backups/source_clean_before_d7e1d90_20260523-191331.tgz
|
||||
@@ -4,7 +4,7 @@ cd "$(dirname "$0")/source_clean"
|
||||
|
||||
VERSION=$(cat ../VERSION)
|
||||
echo "🔨 Building CloudSearch v${VERSION}..."
|
||||
|
||||
cp ../VERSION ./VERSION
|
||||
docker build -t cloudsearch-app:v${VERSION} -t cloudsearch-app:latest .
|
||||
|
||||
echo "✅ Built: cloudsearch-app:v${VERSION} + cloudsearch-app:latest"
|
||||
|
||||
@@ -2,10 +2,13 @@ FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
COPY cloudsearch_transfer/requirements.txt ./requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
COPY cloudsearch_transfer ./cloudsearch_transfer
|
||||
RUN mkdir -p /data
|
||||
|
||||
ENV PORT=9528
|
||||
ENV TRANSFER_CONFIG_PATH=/data/transfer_config.json
|
||||
@@ -13,6 +16,6 @@ ENV TRANSFER_CONFIG_PATH=/data/transfer_config.json
|
||||
EXPOSE 9528
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
||||
CMD ["python", "server.py"]
|
||||
CMD python -c "import os, urllib.request; urllib.request.urlopen('http://127.0.0.1:%s/health' % os.getenv('PORT', '9528'), timeout=3)"
|
||||
|
||||
CMD ["python", "server.py"]
|
||||
CMD ["python", "-m", "cloudsearch_transfer.server"]
|
||||
|
||||
@@ -15,8 +15,8 @@ import logging
|
||||
from typing import List, Dict, Tuple, Optional
|
||||
|
||||
from ..base import BaseCloudDriveAdapter, FileInfo, match_url
|
||||
from ..config import PlatformConfig, TransferConfig
|
||||
from ..errors import TransferError, TransferErrorCode
|
||||
from ...config import PlatformConfig, TransferConfig
|
||||
from ...errors import TransferError, TransferErrorCode
|
||||
|
||||
from .credential import AliyunCredentialManager
|
||||
from .transfer import AliyunTransfer
|
||||
@@ -47,23 +47,29 @@ class AliyunAdapter(BaseCloudDriveAdapter):
|
||||
"Referer": "https://aliyundrive.com",
|
||||
}
|
||||
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig):
|
||||
super().__init__(config, transfer_config)
|
||||
capabilities: Dict[str, bool] = {
|
||||
**BaseCloudDriveAdapter.capabilities,
|
||||
"ensure_dir": True,
|
||||
"save_files": True,
|
||||
"rename": True,
|
||||
"move_files": True,
|
||||
"delete_files": True,
|
||||
}
|
||||
|
||||
# 创建凭证管理器(AliyunCredentialManager)
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig):
|
||||
# BaseCloudDriveAdapter.__init__ calls _setup_session(), so credential
|
||||
# state must exist before super().__init__.
|
||||
refresh_token = config.refresh_token or config.cookie or ""
|
||||
self._credential = AliyunCredentialManager(refresh_token=refresh_token)
|
||||
|
||||
# 初始化 drive_id
|
||||
self._drive_id = ""
|
||||
|
||||
# 创建子模块
|
||||
self._transfer: Optional[AliyunTransfer] = None
|
||||
self._cleanup: Optional[AliyunCleanup] = None
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
def _setup_session(self):
|
||||
"""初始化 session 和凭证"""
|
||||
if self._credential.refresh_token:
|
||||
refresh_token = getattr(self._credential, "refresh_token", "")
|
||||
if refresh_token:
|
||||
# 验证 refresh_token 并获取 drive_id
|
||||
if self._credential.validate():
|
||||
self._drive_id = self._credential.get_drive_id()
|
||||
@@ -164,8 +170,10 @@ class AliyunAdapter(BaseCloudDriveAdapter):
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
|
||||
# 确定目标目录
|
||||
# 确定目标目录:路径先解析/创建为 file_id。
|
||||
to_parent = save_dir if save_dir and save_dir != "/" else "root"
|
||||
if isinstance(to_parent, str) and to_parent.startswith("/"):
|
||||
to_parent = self.ensure_dir(to_parent)
|
||||
|
||||
transfer = self._get_transfer()
|
||||
new_ids = transfer._batch_copy(pwd_id, share_token, file_ids, to_parent)
|
||||
@@ -237,6 +245,74 @@ class AliyunAdapter(BaseCloudDriveAdapter):
|
||||
|
||||
# ─── 扩展功能 ──────────────────────────────────────────
|
||||
|
||||
|
||||
# ─── Optional Drive API capability methods ─────────────────────
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict]:
|
||||
wanted = {p.rstrip("/") or "/" for p in file_paths}
|
||||
found: List[Dict] = []
|
||||
for path in wanted:
|
||||
if path == "/":
|
||||
found.append({"file_path": "/", "fid": "root"})
|
||||
continue
|
||||
parent = path.rsplit("/", 1)[0] or "/"
|
||||
name = path.rsplit("/", 1)[-1]
|
||||
for item in self.get_files(parent):
|
||||
if item.is_dir and item.name == name:
|
||||
found.append({"file_path": path, "fid": item.fid})
|
||||
break
|
||||
return found
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
normalized = "/" + (dir_path or "/").strip("/")
|
||||
if normalized == "/":
|
||||
return "root"
|
||||
current = ""
|
||||
last_fid = "root"
|
||||
for part in [p for p in normalized.split("/") if p]:
|
||||
current = f"{current}/{part}" if current else f"/{part}"
|
||||
matches = self.get_fids([current])
|
||||
if matches:
|
||||
last_fid = matches[0].get("fid") or matches[0].get("file_id") or last_fid
|
||||
continue
|
||||
created = self.mkdir(current)
|
||||
data = created.get("data", created) if isinstance(created, dict) else {}
|
||||
last_fid = data.get("fid") or data.get("file_id") or data.get("id") or last_fid
|
||||
return last_fid
|
||||
|
||||
def mkdir(self, dir_path: str) -> Dict:
|
||||
parent = dir_path.rsplit("/", 1)[0] or "/"
|
||||
name = dir_path.rstrip("/").rsplit("/", 1)[-1]
|
||||
parent_fid = "root" if parent == "/" else self.ensure_dir(parent)
|
||||
url = "https://api.aliyundrive.com/adrive/v2/file/createWithFolders"
|
||||
body = {
|
||||
"drive_id": self._drive_id or self._credential.get_drive_id(),
|
||||
"parent_file_id": parent_fid,
|
||||
"name": name,
|
||||
"type": "folder",
|
||||
"check_name_mode": "refuse",
|
||||
}
|
||||
resp = self._post(url, json_data=body, headers=self._credential.get_headers())
|
||||
data = self._drive_api_json(resp, context="阿里云盘创建目录")
|
||||
return {"code": 0, "status": 200, "data": {"fid": data.get("file_id", ""), **data}}
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict:
|
||||
url = "https://api.aliyundrive.com/v3/file/update"
|
||||
body = {"drive_id": self._drive_id or self._credential.get_drive_id(), "file_id": fid, "name": file_name, "check_name_mode": "refuse"}
|
||||
data = self._drive_api_json(self._post(url, json_data=body, headers=self._credential.get_headers()), context="阿里云盘重命名")
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict:
|
||||
if isinstance(to_pdir_fid, str) and to_pdir_fid.startswith("/"):
|
||||
to_pdir_fid = self.ensure_dir(to_pdir_fid)
|
||||
drive_id = self._drive_id or self._credential.get_drive_id()
|
||||
requests = [{"id": fid, "method": "POST", "url": "/file/move", "headers": {"Content-Type": "application/json"}, "body": {"drive_id": drive_id, "file_id": fid, "to_parent_file_id": to_pdir_fid}} for fid in fids]
|
||||
data = self._drive_api_json(self._post("https://api.aliyundrive.com/adrive/v4/batch", json_data={"requests": requests, "resource": "file"}, headers=self._credential.get_headers()), context="阿里云盘移动文件")
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict:
|
||||
return {"code": 0, "status": 200} if self.delete(fids) else {"code": -1, "status": 500}
|
||||
|
||||
def cleanup_files(self, file_ids: List[str]) -> Dict:
|
||||
"""
|
||||
清理文件(移入回收站),返回详细结果。
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Tuple
|
||||
from typing import List, Tuple, Dict
|
||||
|
||||
from ..base import BaseCloudDriveAdapter, FileInfo
|
||||
from ...config import PlatformConfig, TransferConfig
|
||||
@@ -36,6 +36,15 @@ class BaiduAdapter(BaseCloudDriveAdapter):
|
||||
r'pan\.baidu\.com/s/1([A-Za-z0-9_-]+)',
|
||||
]
|
||||
|
||||
capabilities: Dict[str, bool] = {
|
||||
**BaseCloudDriveAdapter.capabilities,
|
||||
"ensure_dir": True,
|
||||
"save_files": True,
|
||||
"rename": True,
|
||||
"move_files": True,
|
||||
"delete_files": True,
|
||||
}
|
||||
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig):
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
@@ -123,11 +132,13 @@ class BaiduAdapter(BaseCloudDriveAdapter):
|
||||
fs_ids = detail["fs_ids"]
|
||||
filenames = detail.get("filenames", [])
|
||||
|
||||
target_dir = self.ensure_dir(save_dir) if save_dir and save_dir.startswith("/") else (save_dir or "/")
|
||||
|
||||
# ③ 转存
|
||||
self._transfer._transfer_files(shareid, uk, fs_ids, save_dir, bdstoken)
|
||||
self._transfer._transfer_files(shareid, uk, fs_ids, target_dir, bdstoken)
|
||||
|
||||
# ④ 列出目录匹配新 fs_id
|
||||
new_fs_ids = self._transfer._list_and_match(save_dir, filenames, bdstoken)
|
||||
new_fs_ids = self._transfer._list_and_match(target_dir, filenames, bdstoken)
|
||||
|
||||
# 暂存文件信息供 _filter_ads + _create_share 使用
|
||||
self._last_transfer_files = [
|
||||
@@ -248,6 +259,71 @@ class BaiduAdapter(BaseCloudDriveAdapter):
|
||||
|
||||
# ─── 扩展方法 ────────────────────────────────────────────
|
||||
|
||||
|
||||
# ─── Optional Drive API capability methods ─────────────────────
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
normalized = "/" + (dir_path or "/").strip("/")
|
||||
if normalized == "/":
|
||||
return "/"
|
||||
current = ""
|
||||
for part in [p for p in normalized.split("/") if p]:
|
||||
parent = current or "/"
|
||||
current = f"{current}/{part}" if current else f"/{part}"
|
||||
exists = any(item.is_dir and item.name == part for item in self.get_files(parent))
|
||||
if not exists:
|
||||
self.mkdir(current)
|
||||
return normalized
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict]:
|
||||
"""Resolve existing Baidu paths by listing their parent directories."""
|
||||
results: List[Dict] = []
|
||||
for path in file_paths:
|
||||
normalized = "/" + (path or "").strip("/")
|
||||
if normalized == "/":
|
||||
results.append({"file_path": path, "fid": "/", "path": "/"})
|
||||
continue
|
||||
parent, name = normalized.rsplit("/", 1)
|
||||
parent = parent or "/"
|
||||
match = next((item for item in self.get_files(parent) if item.name == name), None)
|
||||
if match:
|
||||
results.append({"file_path": path, "fid": match.fid, "path": normalized})
|
||||
return results
|
||||
|
||||
def mkdir(self, dir_path: str) -> Dict:
|
||||
bdstoken = self.credential.get_bdstoken()
|
||||
url = "https://pan.baidu.com/api/create"
|
||||
params = {"a": "commit", "bdstoken": bdstoken}
|
||||
data = {"path": dir_path, "isdir": 1, "block_list": "[]"}
|
||||
resp = self._post(url, data=data, params=params, headers=self.credential.get_headers())
|
||||
payload = self._drive_api_json(resp, context="百度网盘创建目录")
|
||||
errno = payload.get("errno", 0)
|
||||
if errno not in (0, -8):
|
||||
raise TransferError(TransferErrorCode.NETWORK_ERROR, message=f"百度创建目录失败 errno={errno}", platform=self.PLATFORM_KEY, details=payload)
|
||||
return {"code": 0, "status": 200, "data": {"path": dir_path, **payload}}
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict:
|
||||
return self._filemanager("rename", [{"path": fid, "newname": file_name}])
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict:
|
||||
target = self.ensure_dir(to_pdir_fid) if to_pdir_fid.startswith("/") else to_pdir_fid
|
||||
return self._filemanager("move", [{"path": fid, "dest": target} for fid in fids])
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict:
|
||||
return {"code": 0, "status": 200} if self.delete(fids) else {"code": -1, "status": 500}
|
||||
|
||||
def _filemanager(self, opera: str, filelist: List[Dict]) -> Dict:
|
||||
import json
|
||||
bdstoken = self.credential.get_bdstoken()
|
||||
url = "https://pan.baidu.com/api/filemanager"
|
||||
params = {"opera": opera, "bdstoken": bdstoken}
|
||||
data = {"filelist": json.dumps(filelist, ensure_ascii=False)}
|
||||
payload = self._drive_api_json(self._post(url, data=data, params=params, headers=self.credential.get_headers()), context=f"百度网盘{opera}")
|
||||
errno = payload.get("errno", 0)
|
||||
if errno != 0:
|
||||
raise TransferError(TransferErrorCode.NETWORK_ERROR, message=f"百度文件操作失败 errno={errno}", platform=self.PLATFORM_KEY, details=payload)
|
||||
return {"code": 0, "status": 200, "data": payload}
|
||||
|
||||
def delete_paths(self, paths: List[str]) -> bool:
|
||||
"""便捷删除方法(直接调用 cleanup)"""
|
||||
return self._cleanup.delete_files(paths)
|
||||
|
||||
@@ -76,6 +76,18 @@ class BaseCloudDriveAdapter(ABC):
|
||||
# URL匹配正则(子类覆盖)
|
||||
URL_PATTERNS: List[str] = []
|
||||
|
||||
# 可选 Drive API 能力;子类按需覆盖为 True 并实现对应方法。
|
||||
capabilities: Dict[str, bool] = {
|
||||
"ensure_dir": False,
|
||||
"save_files": False,
|
||||
"poll_task": False,
|
||||
"rename": False,
|
||||
"move_files": False,
|
||||
"delete_files": False,
|
||||
"cleanup_recycle": False,
|
||||
"share_staging_folder": False,
|
||||
}
|
||||
|
||||
# 默认请求头
|
||||
DEFAULT_HEADERS: Dict[str, str] = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
@@ -235,6 +247,40 @@ class BaseCloudDriveAdapter(ABC):
|
||||
"""广告过滤(默认不实现,子类可覆盖)"""
|
||||
return file_ids
|
||||
|
||||
|
||||
# ─── Optional Drive API capability protocol ─────────────────────
|
||||
|
||||
def _unsupported_capability(self, capability: str) -> None:
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"{self.PLATFORM_KEY or self.PLATFORM_NAME} 不支持 Drive API 能力: {capability}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
self._unsupported_capability("ensure_dir")
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict[str, Any]]:
|
||||
self._unsupported_capability("get_fids")
|
||||
|
||||
def mkdir(self, dir_path: str) -> Dict[str, Any]:
|
||||
self._unsupported_capability("mkdir")
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict[str, Any]:
|
||||
self._unsupported_capability("rename")
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict[str, Any]:
|
||||
self._unsupported_capability("move_files")
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict[str, Any]:
|
||||
self._unsupported_capability("delete_files")
|
||||
|
||||
def query_task(self, task_id: str) -> Dict[str, Any]:
|
||||
self._unsupported_capability("poll_task")
|
||||
|
||||
def cleanup_recycle(self, fids: List[str]) -> Dict[str, Any]:
|
||||
self._unsupported_capability("cleanup_recycle")
|
||||
|
||||
# ─── HTTP 工具方法 ─────────────────────────────────────
|
||||
|
||||
def _get(self, url: str, params: dict = None, headers: dict = None,
|
||||
@@ -271,6 +317,28 @@ class BaseCloudDriveAdapter(ABC):
|
||||
raise TransferError(TransferErrorCode.NETWORK_ERROR,
|
||||
message=str(last_exc), platform=self.PLATFORM_KEY)
|
||||
|
||||
|
||||
def _drive_api_json(self, resp: requests.Response, context: str = "网盘 API") -> Dict[str, Any]:
|
||||
"""Validate HTTP response and decode JSON for drive helper APIs."""
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except requests.HTTPError as exc:
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"{context} HTTP错误: {exc}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
details={"status_code": getattr(resp, "status_code", None)},
|
||||
) from exc
|
||||
try:
|
||||
return resp.json()
|
||||
except ValueError as exc:
|
||||
text = getattr(resp, "text", "") or ""
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"{context} JSON解析失败: {text[:200]}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
) from exc
|
||||
|
||||
def _poll_task(self, task_url: str, task_id: str,
|
||||
status_field: str = "status",
|
||||
success_value: Any = 2,
|
||||
@@ -292,10 +360,12 @@ class BaseCloudDriveAdapter(ABC):
|
||||
details={"task_id": task_id})
|
||||
|
||||
try:
|
||||
params = query_params or {}
|
||||
base_params = query_params(attempt) if callable(query_params) else (query_params or {})
|
||||
params = dict(base_params)
|
||||
params["task_id"] = task_id
|
||||
resp = self._get(task_url, params=params, retry=1)
|
||||
data = resp.json().get("data", resp.json())
|
||||
payload = resp.json()
|
||||
data = payload.get("data", payload)
|
||||
|
||||
current_status = data.get(status_field)
|
||||
if current_status == success_value:
|
||||
|
||||
@@ -55,19 +55,21 @@ class QuarkAdapter(BaseCloudDriveAdapter):
|
||||
r"pan\.quark\.cn/s/(\w+)",
|
||||
]
|
||||
|
||||
|
||||
capabilities: Dict[str, bool] = {
|
||||
"ensure_dir": True,
|
||||
"save_files": True,
|
||||
"poll_task": True,
|
||||
"rename": True,
|
||||
"move_files": True,
|
||||
"delete_files": True,
|
||||
"cleanup_recycle": True,
|
||||
"share_staging_folder": False,
|
||||
}
|
||||
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig) -> None:
|
||||
"""初始化夸克适配器。
|
||||
|
||||
Args:
|
||||
config: 平台配置(含 Cookie 等)。
|
||||
transfer_config: 全局转存配置(超时、重试、轮询参数等)。
|
||||
"""
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
# 初始化三个子模块
|
||||
self._credential: QuarkCredentialManager = QuarkCredentialManager(
|
||||
cookie=config.cookie
|
||||
)
|
||||
"""初始化适配器。"""
|
||||
self._credential: QuarkCredentialManager = QuarkCredentialManager(cookie=config.cookie)
|
||||
self._transfer_engine: QuarkTransfer = QuarkTransfer(
|
||||
credential=self._credential,
|
||||
timeout=transfer_config.request_timeout,
|
||||
@@ -78,6 +80,7 @@ class QuarkAdapter(BaseCloudDriveAdapter):
|
||||
credential=self._credential,
|
||||
timeout=transfer_config.request_timeout,
|
||||
)
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 公开接口实现
|
||||
@@ -116,8 +119,9 @@ class QuarkAdapter(BaseCloudDriveAdapter):
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
|
||||
# 目标目录:默认根目录 "0"
|
||||
target_dir: str = save_dir or self.config.save_dir or "0"
|
||||
# 目标目录:支持 fid 或路径;路径会先创建/解析为 fid。
|
||||
requested_dir: str = save_dir or self.config.save_dir or "/"
|
||||
target_dir: str = requested_dir if requested_dir and not str(requested_dir).startswith("/") else self.ensure_dir(requested_dir)
|
||||
|
||||
# 分享密码
|
||||
pwd: str = share_password or self.config.share_password or ""
|
||||
@@ -250,18 +254,13 @@ class QuarkAdapter(BaseCloudDriveAdapter):
|
||||
def _save_files(self, pwd_id: str, detail: dict, save_dir: str) -> List[str]:
|
||||
"""转存文件到自己的夸克网盘(基类 transfer() 流程中的步骤③④)。
|
||||
|
||||
Args:
|
||||
pwd_id: 分享 ID。
|
||||
detail: 分享详情(来自 _get_share_detail)。
|
||||
save_dir: 目标目录 ID。
|
||||
|
||||
Returns:
|
||||
转存后的新文件 ID 列表。
|
||||
save_dir 可传 fid 或路径;路径会先通过 ensure_dir 创建/解析为 fid。
|
||||
"""
|
||||
# 需要 stoken,从 detail 间接获取(重新请求)
|
||||
stoken: str = self._transfer_engine._get_stoken(pwd_id)
|
||||
target_fid = save_dir if save_dir and not str(save_dir).startswith("/") else self.ensure_dir(save_dir or "/")
|
||||
task_id: str = self._transfer_engine._init_save(
|
||||
pwd_id, stoken, detail, to_pdir_fid=save_dir
|
||||
pwd_id, stoken, detail, to_pdir_fid=target_fid
|
||||
)
|
||||
return self._transfer_engine._poll_save_task(task_id)
|
||||
|
||||
@@ -351,6 +350,160 @@ class QuarkAdapter(BaseCloudDriveAdapter):
|
||||
logger.warning("[QuarkAdapter] Cannot fetch file list for ad filtering, skipping")
|
||||
return file_ids
|
||||
|
||||
|
||||
# ─── Drive API capability helpers ─────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _normalize_dir_path(dir_path: str) -> str:
|
||||
path = "/" + str(dir_path or "").strip().strip("/")
|
||||
return "/" if path == "/" else path
|
||||
|
||||
@staticmethod
|
||||
def _api_success(payload: Dict[str, Any]) -> bool:
|
||||
if not isinstance(payload, dict):
|
||||
return False
|
||||
code = payload.get("code")
|
||||
status = payload.get("status")
|
||||
return code == 0 or status in (0, 200)
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
normalized = self._normalize_dir_path(dir_path)
|
||||
if normalized == "/":
|
||||
return "0"
|
||||
|
||||
parts = [part for part in normalized.strip("/").split("/") if part]
|
||||
prefixes = ["/" + "/".join(parts[:idx]) for idx in range(1, len(parts) + 1)]
|
||||
existing = {
|
||||
item.get("file_path"): str(item.get("fid"))
|
||||
for item in self.get_fids(prefixes)
|
||||
if item.get("file_path") and item.get("fid")
|
||||
}
|
||||
|
||||
leaf_fid = existing.get(normalized)
|
||||
if leaf_fid:
|
||||
return leaf_fid
|
||||
|
||||
for prefix in prefixes:
|
||||
if prefix in existing:
|
||||
continue
|
||||
result = self.mkdir(prefix)
|
||||
if self._api_success(result) and result.get("data", {}).get("fid"):
|
||||
existing[prefix] = str(result["data"]["fid"])
|
||||
continue
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"创建目录失败: {result.get('message', result)}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
return existing[normalized]
|
||||
|
||||
def mkdir(self, dir_path: str) -> Dict[str, Any]:
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file"
|
||||
params = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {
|
||||
"pdir_fid": "0",
|
||||
"file_name": "",
|
||||
"dir_path": self._normalize_dir_path(dir_path),
|
||||
"dir_init_lock": False,
|
||||
}
|
||||
return self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="写入网盘目录")
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict[str, Any]:
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file/rename"
|
||||
params = {"pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {"fid": fid, "file_name": file_name}
|
||||
return self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="写入网盘目录")
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict[str, Any]]:
|
||||
pending = [self._normalize_dir_path(p) for p in file_paths]
|
||||
result: List[Dict[str, Any]] = []
|
||||
while pending:
|
||||
batch, pending = pending[:50], pending[50:]
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file/info/path_list"
|
||||
params = {"pr": "ucpro", "fr": "pc"}
|
||||
payload = {"file_path": batch, "namespace": "0"}
|
||||
data = self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="按路径获取文件ID")
|
||||
if not self._api_success(data):
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"获取目录ID失败: {data.get('message', data)}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
result.extend(data.get("data", []))
|
||||
return result
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict[str, Any]:
|
||||
if not fids:
|
||||
return {"code": 0, "message": "无文件需要移动"}
|
||||
last: Dict[str, Any] = {"code": 0, "message": "success"}
|
||||
for offset in range(0, len(fids), 100):
|
||||
batch = fids[offset:offset + 100]
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file/move"
|
||||
params = {"uc_param_str": "", "fr": "pc", "pr": "ucpro"}
|
||||
payload = {"filelist": batch, "to_pdir_fid": to_pdir_fid, "exclude_fids": [], "action_type": 1}
|
||||
last = self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="移动网盘文件")
|
||||
if not self._api_success(last):
|
||||
return last
|
||||
task_id = last.get("data", {}).get("task_id")
|
||||
if task_id:
|
||||
task = self.query_task(task_id)
|
||||
if not self._api_success(task) or task.get("data", {}).get("status") == -1:
|
||||
return {"code": 1, "message": task.get("data", {}).get("message", task.get("message", "移动任务失败")), "data": task.get("data", {})}
|
||||
return {"code": 0, "message": "移动完成", "data": last.get("data", {})}
|
||||
|
||||
def _task_query_params(self, retry_index: int = 0) -> Dict[str, Any]:
|
||||
now_ms = int(time.time() * 1000)
|
||||
return {
|
||||
"pr": "ucpro",
|
||||
"fr": "pc",
|
||||
"uc_param_str": "",
|
||||
"retry_index": retry_index,
|
||||
"__dt": 300,
|
||||
"__t": now_ms,
|
||||
}
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict[str, Any]:
|
||||
if not fids:
|
||||
return {"code": 0, "status": 200}
|
||||
if self.delete(fids):
|
||||
return {"code": 0, "status": 200}
|
||||
return {"code": 1, "status": 500, "message": "删除文件失败"}
|
||||
|
||||
def query_task(self, task_id: str) -> Dict[str, Any]:
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/task"
|
||||
try:
|
||||
data = self._poll_task(url, task_id, query_params=self._task_query_params)
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
except TransferError as exc:
|
||||
return {"code": 1, "status": 500, "message": str(exc), "data": {"status": -1}}
|
||||
|
||||
def recycle_list(self, page: int = 1, size: int = 30) -> List[Dict[str, Any]]:
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file/recycle/list"
|
||||
params = {"_page": page, "_size": size, "pr": "ucpro", "fr": "pc", "uc_param_str": ""}
|
||||
data = self._drive_api_json(self._get(url, params=params, headers=self._credential.get_headers()), context="列出回收站")
|
||||
return data.get("data", {}).get("list", [])
|
||||
|
||||
def recycle_remove(self, record_list: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
url = "https://drive-pc.quark.cn/1/clouddrive/file/recycle/remove"
|
||||
params = {"uc_param_str": "", "fr": "pc", "pr": "ucpro"}
|
||||
payload = {"select_mode": 2, "record_list": record_list}
|
||||
return self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="写入网盘目录")
|
||||
|
||||
def cleanup_recycle(self, fids: List[str]) -> Dict[str, Any]:
|
||||
target_fids = {str(fid) for fid in fids if fid}
|
||||
if not target_fids:
|
||||
return {"code": 0, "message": "无回收站记录需要清理", "data": {"removed": 0}}
|
||||
records = [
|
||||
item for item in self.recycle_list()
|
||||
if str(item.get("fid") or item.get("file_id") or "") in target_fids
|
||||
]
|
||||
if not records:
|
||||
return {"code": 0, "message": "未找到匹配的回收站记录", "data": {"removed": 0}}
|
||||
result = self.recycle_remove(records)
|
||||
if self._api_success(result):
|
||||
result.setdefault("data", {})["removed"] = len(records)
|
||||
return result
|
||||
|
||||
# ─── get_files / delete ────────────────────────────────────
|
||||
|
||||
def get_files(self, parent_fid: str = "0") -> List[FileInfo]:
|
||||
|
||||
@@ -56,19 +56,21 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
r"drive\.uc\.cn/s/(\w+)",
|
||||
]
|
||||
|
||||
|
||||
capabilities: Dict[str, bool] = {
|
||||
"ensure_dir": True,
|
||||
"save_files": True,
|
||||
"poll_task": True,
|
||||
"rename": True,
|
||||
"move_files": True,
|
||||
"delete_files": True,
|
||||
"cleanup_recycle": False,
|
||||
"share_staging_folder": True,
|
||||
}
|
||||
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig) -> None:
|
||||
"""初始化 UC 适配器。
|
||||
|
||||
Args:
|
||||
config: 平台配置(含 Cookie 等)。
|
||||
transfer_config: 全局转存配置(超时、重试、轮询参数等)。
|
||||
"""
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
# 初始化三个子模块
|
||||
self._credential: UcCredentialManager = UcCredentialManager(
|
||||
cookie=config.cookie
|
||||
)
|
||||
"""初始化适配器。"""
|
||||
self._credential: UcCredentialManager = UcCredentialManager(cookie=config.cookie)
|
||||
self._transfer_engine: UcTransfer = UcTransfer(
|
||||
credential=self._credential,
|
||||
timeout=transfer_config.request_timeout,
|
||||
@@ -79,6 +81,7 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
credential=self._credential,
|
||||
timeout=transfer_config.request_timeout,
|
||||
)
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# 公开接口实现
|
||||
@@ -93,21 +96,8 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
|
||||
def transfer(self, share_url: str, save_dir: str = "",
|
||||
share_password: str = "") -> TransferResult:
|
||||
"""执行转存的核心逻辑(覆盖基类实现 UC 专用流程)。
|
||||
|
||||
通过 UcTransfer 引擎执行完整的 7 步流程。
|
||||
|
||||
Args:
|
||||
share_url: UC 分享链接。
|
||||
save_dir: 目标目录,空则使用配置的默认目录。
|
||||
share_password: 新分享的密码。
|
||||
|
||||
Returns:
|
||||
TransferResult 包含转存结果。
|
||||
"""
|
||||
start: float = time.time()
|
||||
|
||||
# 凭证检查
|
||||
if not self._credential.validate():
|
||||
raise TransferError(
|
||||
TransferErrorCode.NOT_LOGIN,
|
||||
@@ -115,18 +105,36 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
|
||||
# 目标目录:默认根目录 "0"
|
||||
target_dir: str = save_dir or self.config.save_dir or "0"
|
||||
|
||||
# 分享密码
|
||||
requested_dir: str = save_dir or self.config.save_dir or "/"
|
||||
target_dir: str = requested_dir if requested_dir and not str(requested_dir).startswith("/") else self.ensure_dir(requested_dir)
|
||||
staging_dir: str = self.get_or_create_share_folder() or target_dir
|
||||
pwd: str = share_password or self.config.share_password or ""
|
||||
|
||||
try:
|
||||
result: Dict[str, Any] = self._transfer_engine.transfer(
|
||||
share_url=share_url,
|
||||
save_dir=target_dir,
|
||||
share_password=pwd,
|
||||
pwd_id, passcode = self._parse_share_url(share_url)
|
||||
stoken: str = self._transfer_engine._get_stoken(pwd_id, passcode)
|
||||
detail: Dict[str, Any] = self._transfer_engine._get_detail(pwd_id, stoken)
|
||||
task_id: str = self._transfer_engine._init_save(
|
||||
pwd_id, stoken, detail, to_pdir_fid=staging_dir
|
||||
)
|
||||
new_fids: List[str] = self._transfer_engine._poll_save_task(task_id)
|
||||
if not new_fids:
|
||||
raise RuntimeError("转存完成但未获取到文件ID")
|
||||
|
||||
if staging_dir != target_dir:
|
||||
move_result = self.move_files(new_fids, target_dir)
|
||||
if not self._api_success(move_result):
|
||||
raise RuntimeError(f"移动到目标目录失败: {move_result.get('message', move_result)}")
|
||||
|
||||
if self.transfer_config.ad_filter_enabled and new_fids:
|
||||
new_fids = self._filter_ads(new_fids)
|
||||
if not new_fids:
|
||||
raise RuntimeError("广告过滤后无可分享文件")
|
||||
|
||||
title: str = detail.get("title", "分享")
|
||||
share_task_id: str = self._transfer_engine._init_share(new_fids, title)
|
||||
share_id: str = self._transfer_engine._poll_share_task(share_task_id)
|
||||
share_url_new, passcode_new = self._transfer_engine._set_password(share_id, pwd)
|
||||
except ValueError as exc:
|
||||
raise TransferError(
|
||||
TransferErrorCode.URL_INVALID,
|
||||
@@ -148,24 +156,13 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
) from exc
|
||||
|
||||
elapsed: int = int((time.time() - start) * 1000)
|
||||
|
||||
# 广告过滤
|
||||
new_fids: List[str] = result.get("new_file_ids", [])
|
||||
if self.transfer_config.ad_filter_enabled and new_fids:
|
||||
new_fids = self._filter_ads(new_fids)
|
||||
if not new_fids:
|
||||
raise TransferError(
|
||||
TransferErrorCode.RESOURCE_EMPTY,
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
|
||||
return TransferResult(
|
||||
success=True,
|
||||
platform=self.PLATFORM_KEY,
|
||||
new_file_id=",".join(new_fids),
|
||||
file_name=result.get("file_name", ""),
|
||||
share_url=result.get("share_url", ""),
|
||||
share_password=result.get("passcode", pwd),
|
||||
file_name=title,
|
||||
share_url=share_url_new,
|
||||
share_password=passcode_new,
|
||||
original_url=share_url,
|
||||
elapsed_ms=elapsed,
|
||||
)
|
||||
@@ -204,8 +201,8 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
files=files,
|
||||
)
|
||||
|
||||
except TransferError:
|
||||
raise
|
||||
except TransferError as exc:
|
||||
return VerifyResult(valid=False, platform=self.PLATFORM_KEY, error=exc)
|
||||
except (ValueError, RuntimeError) as exc:
|
||||
return VerifyResult(
|
||||
valid=False,
|
||||
@@ -344,6 +341,154 @@ class UcAdapter(BaseCloudDriveAdapter):
|
||||
)
|
||||
return file_ids
|
||||
|
||||
|
||||
# ─── Drive API capability helpers ─────────────────────────────
|
||||
|
||||
@staticmethod
|
||||
def _normalize_dir_path(dir_path: str) -> str:
|
||||
path = "/" + str(dir_path or "").strip().strip("/")
|
||||
return "/" if path == "/" else path
|
||||
|
||||
@staticmethod
|
||||
def _api_success(payload: Dict[str, Any]) -> bool:
|
||||
if not isinstance(payload, dict):
|
||||
return False
|
||||
code = payload.get("code")
|
||||
status = payload.get("status")
|
||||
return code == 0 or status in (0, 200)
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
normalized = self._normalize_dir_path(dir_path)
|
||||
if normalized == "/":
|
||||
return "0"
|
||||
|
||||
parts = [part for part in normalized.strip("/").split("/") if part]
|
||||
prefixes = ["/" + "/".join(parts[:idx]) for idx in range(1, len(parts) + 1)]
|
||||
existing = {
|
||||
item.get("file_path"): str(item.get("fid"))
|
||||
for item in self.get_fids(prefixes)
|
||||
if item.get("file_path") and item.get("fid")
|
||||
}
|
||||
|
||||
leaf_fid = existing.get(normalized)
|
||||
if leaf_fid:
|
||||
return leaf_fid
|
||||
|
||||
for prefix in prefixes:
|
||||
if prefix in existing:
|
||||
continue
|
||||
result = self.mkdir(prefix)
|
||||
if self._api_success(result) and result.get("data", {}).get("fid"):
|
||||
existing[prefix] = str(result["data"]["fid"])
|
||||
continue
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"创建目录失败: {result.get('message', result)}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
return existing[normalized]
|
||||
|
||||
def mkdir(self, dir_path: str) -> Dict[str, Any]:
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/file"
|
||||
params = {"pr": "UCBrowser", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {
|
||||
"pdir_fid": "0",
|
||||
"file_name": "",
|
||||
"dir_path": self._normalize_dir_path(dir_path),
|
||||
"dir_init_lock": False,
|
||||
}
|
||||
return self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="写入网盘目录")
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict[str, Any]:
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/file/rename"
|
||||
params = {"pr": "UCBrowser", "fr": "pc", "uc_param_str": ""}
|
||||
payload = {"fid": fid, "file_name": file_name}
|
||||
return self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="写入网盘目录")
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict[str, Any]]:
|
||||
pending = [self._normalize_dir_path(p) for p in file_paths]
|
||||
result: List[Dict[str, Any]] = []
|
||||
while pending:
|
||||
batch, pending = pending[:50], pending[50:]
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/file/info/path_list"
|
||||
params = {"pr": "UCBrowser", "fr": "pc"}
|
||||
payload = {"file_path": batch, "namespace": "0"}
|
||||
data = self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="按路径获取文件ID")
|
||||
if not self._api_success(data):
|
||||
raise TransferError(
|
||||
TransferErrorCode.NETWORK_ERROR,
|
||||
message=f"获取目录ID失败: {data.get('message', data)}",
|
||||
platform=self.PLATFORM_KEY,
|
||||
)
|
||||
result.extend(data.get("data", []))
|
||||
return result
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict[str, Any]:
|
||||
if not fids:
|
||||
return {"code": 0, "message": "无文件需要移动"}
|
||||
last: Dict[str, Any] = {"code": 0, "message": "success"}
|
||||
for offset in range(0, len(fids), 100):
|
||||
batch = fids[offset:offset + 100]
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/file/move"
|
||||
params = {"uc_param_str": "", "fr": "pc", "pr": "UCBrowser"}
|
||||
payload = {"filelist": batch, "to_pdir_fid": to_pdir_fid, "exclude_fids": [], "action_type": 1}
|
||||
last = self._drive_api_json(self._post(url, json_data=payload, params=params, headers=self._credential.get_headers()), context="移动网盘文件")
|
||||
if not self._api_success(last):
|
||||
return last
|
||||
task_id = last.get("data", {}).get("task_id")
|
||||
if task_id:
|
||||
task = self.query_task(task_id)
|
||||
if not self._api_success(task) or task.get("data", {}).get("status") == -1:
|
||||
return {"code": 1, "message": task.get("data", {}).get("message", task.get("message", "移动任务失败")), "data": task.get("data", {})}
|
||||
return {"code": 0, "message": "移动完成", "data": last.get("data", {})}
|
||||
|
||||
def _task_query_params(self, retry_index: int = 0) -> Dict[str, Any]:
|
||||
now_ms = int(time.time() * 1000)
|
||||
return {
|
||||
"pr": "UCBrowser",
|
||||
"fr": "pc",
|
||||
"uc_param_str": "",
|
||||
"retry_index": retry_index,
|
||||
"__dt": 300,
|
||||
"__t": now_ms,
|
||||
}
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict[str, Any]:
|
||||
if not fids:
|
||||
return {"code": 0, "status": 200}
|
||||
if self.delete(fids):
|
||||
return {"code": 0, "status": 200}
|
||||
return {"code": 1, "status": 500, "message": "删除文件失败"}
|
||||
|
||||
def query_task(self, task_id: str) -> Dict[str, Any]:
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/task"
|
||||
try:
|
||||
data = self._poll_task(url, task_id, query_params=self._task_query_params)
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
except TransferError as exc:
|
||||
return {"code": 1, "status": 500, "message": str(exc), "data": {"status": -1}}
|
||||
|
||||
def get_or_create_share_folder(self) -> Optional[str]:
|
||||
if getattr(self, "_share_folder_fid", None):
|
||||
return self._share_folder_fid
|
||||
root = self.ls_dir("0")
|
||||
if self._api_success(root):
|
||||
for item in root.get("data", {}).get("list", []):
|
||||
if item.get("file_name") == "来自:分享" and item.get("dir"):
|
||||
self._share_folder_fid = str(item["fid"])
|
||||
return self._share_folder_fid
|
||||
result = self.mkdir("/来自:分享")
|
||||
if self._api_success(result) and result.get("data", {}).get("fid"):
|
||||
self._share_folder_fid = str(result["data"]["fid"])
|
||||
return self._share_folder_fid
|
||||
return None
|
||||
|
||||
def ls_dir(self, pdir_fid: str) -> Dict[str, Any]:
|
||||
url = "https://pc-api.uc.cn/1/clouddrive/file/sort"
|
||||
params = {"pr": "UCBrowser", "fr": "pc", "pdir_fid": pdir_fid or "0", "_page": 1, "_size": 50, "_fetch_total": 1, "_fetch_sub_dirs": 0, "_sort": "file_type:asc,updated_at:desc"}
|
||||
return self._drive_api_json(self._get(url, params=params, headers=self._credential.get_headers()), context="列出网盘目录")
|
||||
|
||||
|
||||
# ─── get_files / delete ────────────────────────────────────
|
||||
|
||||
def get_files(self, parent_fid: str = "0") -> List[FileInfo]:
|
||||
|
||||
@@ -8,7 +8,7 @@ PLATFORM_KEY = 'xunlei'
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Tuple
|
||||
from typing import List, Optional, Tuple, Dict
|
||||
|
||||
from ..base import (
|
||||
BaseCloudDriveAdapter,
|
||||
@@ -32,11 +32,24 @@ class XunleiAdapter(BaseCloudDriveAdapter):
|
||||
PLATFORM_KEY = "xunlei"
|
||||
URL_PATTERNS = [r"pan\.xunlei\.com/s/([A-Za-z0-9]+)"]
|
||||
|
||||
capabilities: Dict[str, bool] = {
|
||||
**BaseCloudDriveAdapter.capabilities,
|
||||
"ensure_dir": True,
|
||||
"save_files": True,
|
||||
"poll_task": True,
|
||||
"rename": True,
|
||||
"move_files": True,
|
||||
# batchDelete is permanent on Xunlei, so do not advertise it as generic delete_files.
|
||||
"delete_files": False,
|
||||
}
|
||||
|
||||
def __init__(self, config: PlatformConfig, transfer_config: TransferConfig):
|
||||
super().__init__(config, transfer_config)
|
||||
# BaseCloudDriveAdapter.__init__ calls _setup_session(), so credential
|
||||
# state must exist before super().__init__.
|
||||
self._credential = XunleiCredentialManager(config)
|
||||
self._transfer_engine: Optional[XunleiTransfer] = None
|
||||
self._cleanup = XunleiCleanup()
|
||||
self._cleanup = XunleiCleanup(self._credential)
|
||||
super().__init__(config, transfer_config)
|
||||
|
||||
def _setup_session(self):
|
||||
"""初始化 session 认证头"""
|
||||
@@ -54,27 +67,35 @@ class XunleiAdapter(BaseCloudDriveAdapter):
|
||||
"""懒加载转存引擎"""
|
||||
if self._transfer_engine is None:
|
||||
self._transfer_engine = XunleiTransfer(
|
||||
self.session,
|
||||
self._credential,
|
||||
self.config,
|
||||
self.transfer_config,
|
||||
credential=self._credential,
|
||||
timeout=self.transfer_config.request_timeout,
|
||||
poll_interval=self.transfer_config.task_poll_interval,
|
||||
poll_max_attempts=self.transfer_config.task_poll_max_attempts,
|
||||
)
|
||||
self._transfer_engine.session = self.session
|
||||
return self._transfer_engine
|
||||
|
||||
# ─── 抽象方法实现 ──────────────────────────────
|
||||
|
||||
def _get_share_detail(self, pwd_id: str, passcode: str = "") -> dict:
|
||||
self._ensure_auth()
|
||||
return self._transfer.get_share_info(pwd_id, passcode)
|
||||
return self._transfer._get_share_info(pwd_id, passcode)
|
||||
|
||||
def _save_files(self, pwd_id: str, detail: dict, save_dir: str) -> List[str]:
|
||||
self._ensure_auth()
|
||||
return self._transfer.save_files(pwd_id, detail, save_dir)
|
||||
target = self.ensure_dir(save_dir) if save_dir and save_dir.startswith("/") else (save_dir or "")
|
||||
files = detail.get("files", [])
|
||||
file_ids = [f.get("file_id") or f.get("fid") or f.get("id") for f in files if f.get("file_id") or f.get("fid") or f.get("id")]
|
||||
if not file_ids:
|
||||
raise RuntimeError("无法从分享中提取文件ID")
|
||||
task_id = self._transfer._restore_files(pwd_id, detail.get("pass_code_token", ""), file_ids, parent_id=target)
|
||||
mapping = self._transfer._poll_restore_task(task_id)
|
||||
return [mapping.get(fid, "") for fid in file_ids if mapping.get(fid, "")]
|
||||
|
||||
def _create_share(self, file_ids: List[str], title: str,
|
||||
password: str = "") -> Tuple[str, str]:
|
||||
self._ensure_auth()
|
||||
return self._transfer.create_share(file_ids, title, password)
|
||||
return self._transfer._create_share(file_ids, password=password)
|
||||
|
||||
def _extract_file_list(self, detail: dict) -> List[FileInfo]:
|
||||
files = detail.get("files", [])
|
||||
@@ -94,13 +115,80 @@ class XunleiAdapter(BaseCloudDriveAdapter):
|
||||
|
||||
def get_files(self, parent_fid: str = "0") -> List[FileInfo]:
|
||||
self._ensure_auth()
|
||||
return self._transfer.list_files(parent_fid)
|
||||
url = "https://api-pan.xunlei.com/drive/v1/files"
|
||||
params = {"parent_id": "" if parent_fid in ("0", "/") else parent_fid}
|
||||
data = self._drive_api_json(self._get(url, params=params, headers=self._credential.get_headers()), context="迅雷网盘列目录")
|
||||
items = data.get("files", data.get("list", []))
|
||||
return [FileInfo(fid=item.get("id") or item.get("file_id", ""), name=item.get("name", ""), size=item.get("size", 0), is_dir=item.get("kind") == "drive#folder" or item.get("is_dir", False)) for item in items]
|
||||
|
||||
def ensure_dir(self, dir_path: str) -> str:
|
||||
normalized = "/" + (dir_path or "/").strip("/")
|
||||
if normalized == "/":
|
||||
return ""
|
||||
parent_id = ""
|
||||
current = ""
|
||||
for part in [p for p in normalized.split("/") if p]:
|
||||
current = f"{current}/{part}" if current else f"/{part}"
|
||||
existing = next((item for item in self.get_files(parent_id or "0") if item.is_dir and item.name == part), None)
|
||||
if existing:
|
||||
parent_id = existing.fid
|
||||
continue
|
||||
result = self.mkdir(current if not parent_id else part, parent_id=parent_id)
|
||||
parent_id = result.get("data", {}).get("fid", parent_id)
|
||||
return parent_id
|
||||
|
||||
def get_fids(self, file_paths: List[str]) -> List[Dict]:
|
||||
"""Resolve existing file/directory paths without creating anything."""
|
||||
results: List[Dict] = []
|
||||
for path in file_paths:
|
||||
normalized = "/" + (path or "").strip("/")
|
||||
if normalized == "/":
|
||||
results.append({"file_path": path, "fid": ""})
|
||||
continue
|
||||
parent_id = ""
|
||||
found: Optional[FileInfo] = None
|
||||
missing = False
|
||||
parts = [p for p in normalized.split("/") if p]
|
||||
for index, part in enumerate(parts):
|
||||
found = next((item for item in self.get_files(parent_id or "0") if item.name == part), None)
|
||||
if not found:
|
||||
missing = True
|
||||
break
|
||||
if index < len(parts) - 1 and not found.is_dir:
|
||||
missing = True
|
||||
break
|
||||
parent_id = found.fid
|
||||
if not missing and found:
|
||||
results.append({"file_path": path, "fid": found.fid})
|
||||
return results
|
||||
|
||||
def mkdir(self, dir_path: str, parent_id: str = "") -> Dict:
|
||||
name = dir_path.rstrip("/").rsplit("/", 1)[-1]
|
||||
body = {"kind": "drive#folder", "name": name, "parent_id": parent_id or ""}
|
||||
data = self._drive_api_json(self._post("https://api-pan.xunlei.com/drive/v1/files", json_data=body, headers=self._credential.get_headers_with_captcha(action="mkdir")), context="迅雷网盘创建目录")
|
||||
return {"code": 0, "status": 200, "data": {"fid": data.get("id") or data.get("file_id", ""), **data}}
|
||||
|
||||
def rename(self, fid: str, file_name: str) -> Dict:
|
||||
data = self._drive_api_json(self._post(f"https://api-pan.xunlei.com/drive/v1/files/{fid}", json_data={"name": file_name}, headers=self._credential.get_headers()), context="迅雷网盘重命名")
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
|
||||
def move_files(self, fids: List[str], to_pdir_fid: str) -> Dict:
|
||||
target = self.ensure_dir(to_pdir_fid) if to_pdir_fid.startswith("/") else to_pdir_fid
|
||||
data = self._drive_api_json(self._post("https://api-pan.xunlei.com/drive/v1/files:batchMove", json_data={"ids": fids, "parent_id": target}, headers=self._credential.get_headers()), context="迅雷网盘移动文件")
|
||||
return {"code": 0, "status": 200, "data": data}
|
||||
|
||||
def query_task(self, task_id: str) -> Dict:
|
||||
return self.poll_task(task_id)
|
||||
|
||||
def poll_task(self, task_id: str) -> Dict:
|
||||
return {"code": 0, "status": 200, "data": self._transfer._poll_restore_task(task_id)}
|
||||
|
||||
def delete_files(self, fids: List[str]) -> Dict:
|
||||
return {"code": 0, "status": 200} if self.delete(fids) else {"code": -1, "status": 500}
|
||||
|
||||
def delete(self, file_ids: List[str]) -> bool:
|
||||
self._ensure_auth()
|
||||
return self._cleanup.delete_files(
|
||||
self.session, self._credential, file_ids
|
||||
)
|
||||
return self._cleanup.delete_files(file_ids)
|
||||
|
||||
def _get_banned_keywords(self) -> List[str]:
|
||||
return self.config.banned_keywords or self.transfer_config.default_banned_keywords
|
||||
|
||||
@@ -70,7 +70,7 @@ class XunleiTransfer:
|
||||
|
||||
# ─── 步骤 ①:获取分享详情 ─────────────────────────────────────
|
||||
|
||||
def _get_share_info(self, share_id: str) -> Dict[str, Any]:
|
||||
def _get_share_info(self, share_id: str, passcode: str = "") -> Dict[str, Any]:
|
||||
"""步骤①:获取分享详情。
|
||||
|
||||
GET /drive/v1/share?share_id=<share_id>
|
||||
@@ -88,6 +88,8 @@ class XunleiTransfer:
|
||||
"""
|
||||
url = f"{XUNLEI_PAN_API}/drive/v1/share"
|
||||
params: Dict[str, str] = {"share_id": share_id}
|
||||
if passcode:
|
||||
params["pass_code"] = passcode
|
||||
headers = self.credential.get_headers()
|
||||
|
||||
logger.info("[XunleiTransfer] ① Fetching share info for share_id=%s", share_id)
|
||||
@@ -303,6 +305,7 @@ class XunleiTransfer:
|
||||
def _create_share(
|
||||
self,
|
||||
file_ids: List[str],
|
||||
password: str = "",
|
||||
expiration_days: str = "-1",
|
||||
) -> Tuple[str, str]:
|
||||
"""步骤④:创建新分享链接。
|
||||
@@ -331,6 +334,8 @@ class XunleiTransfer:
|
||||
"file_ids": file_ids,
|
||||
"expiration_days": expiration_days,
|
||||
}
|
||||
if password:
|
||||
body["pass_code"] = password
|
||||
# share 操作可能需要 captcha_token
|
||||
headers = self.credential.get_headers_with_captcha(action="share")
|
||||
headers.setdefault("Content-Type", "application/json")
|
||||
@@ -367,7 +372,7 @@ class XunleiTransfer:
|
||||
share_url,
|
||||
pass_code,
|
||||
)
|
||||
return share_url, pass_code
|
||||
return share_url, pass_code or password
|
||||
|
||||
# ─── 公开入口 ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -150,8 +150,8 @@ class ConfigManager:
|
||||
"platforms": {
|
||||
name: {
|
||||
"enabled": cfg.enabled,
|
||||
"cookie": cfg.cookie[:20] + "..." if cfg.cookie else "",
|
||||
"refresh_token": cfg.refresh_token[:20] + "..." if cfg.refresh_token else "",
|
||||
"cookie": cfg.cookie,
|
||||
"refresh_token": cfg.refresh_token,
|
||||
"account_name": cfg.account_name,
|
||||
"save_dir": cfg.save_dir,
|
||||
"share_password": cfg.share_password,
|
||||
|
||||
@@ -7,8 +7,8 @@ import os
|
||||
import uuid
|
||||
import logging
|
||||
from flask import Flask, request, jsonify
|
||||
from config import ConfigManager
|
||||
from orchestration.transfer import TransferOrchestrator
|
||||
from cloudsearch_transfer.config import ConfigManager
|
||||
from cloudsearch_transfer.orchestration.transfer import TransferOrchestrator
|
||||
|
||||
# ─── 初始化 ────────────────────────────────────────────
|
||||
|
||||
@@ -149,6 +149,18 @@ def stats():
|
||||
return jsonify(orchestrator.get_stats())
|
||||
|
||||
|
||||
|
||||
def require_config_auth():
|
||||
token = os.getenv("TRANSFER_API_TOKEN", "")
|
||||
if not token:
|
||||
return jsonify({"error": "TRANSFER_API_TOKEN is required"}), 401
|
||||
supplied = request.headers.get("X-Transfer-Token") or request.headers.get("Authorization", "")
|
||||
if supplied.startswith("Bearer "):
|
||||
supplied = supplied[7:]
|
||||
if supplied != token:
|
||||
return jsonify({"error": "unauthorized"}), 401
|
||||
return None
|
||||
|
||||
# ─── 配置管理 ──────────────────────────────────────────
|
||||
|
||||
@app.route("/api/config/platforms", methods=["GET"])
|
||||
@@ -169,14 +181,17 @@ def get_platforms():
|
||||
@app.route("/api/config/platforms/<name>", methods=["PUT"])
|
||||
def update_platform(name):
|
||||
"""更新平台配置"""
|
||||
auth_error = require_config_auth()
|
||||
if auth_error:
|
||||
return auth_error
|
||||
data = request.get_json() or {}
|
||||
if name not in config.platforms:
|
||||
from config import PlatformConfig
|
||||
from cloudsearch_transfer.config import PlatformConfig
|
||||
config.platforms[name] = PlatformConfig()
|
||||
|
||||
cfg = config.platforms[name]
|
||||
if "enabled" in data:
|
||||
cfg.enabled = data["enabled"]
|
||||
cfg.enabled = bool(data["enabled"])
|
||||
if "cookie" in data:
|
||||
cfg.cookie = data["cookie"]
|
||||
if "refresh_token" in data:
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from cloudsearch_transfer.adapter.aliyun import AliyunAdapter
|
||||
from cloudsearch_transfer.adapter.baidu import BaiduAdapter
|
||||
from cloudsearch_transfer.adapter.xunlei import XunleiAdapter
|
||||
from cloudsearch_transfer.config import PlatformConfig, TransferConfig
|
||||
from cloudsearch_transfer.adapter.base import FileInfo
|
||||
|
||||
|
||||
class DummyCredential:
|
||||
cookie = "k=xxxxxxxx"
|
||||
refresh_token = "rt"
|
||||
def validate(self):
|
||||
return True
|
||||
def get_bdstoken(self):
|
||||
return "bdstoken"
|
||||
def get_drive_id(self):
|
||||
return "drive-id"
|
||||
def get_headers(self):
|
||||
return {}
|
||||
def get_auth_headers(self):
|
||||
return {}
|
||||
|
||||
|
||||
def make_config(**kwargs):
|
||||
data = dict(enabled=True, cookie="k=" + "x" * 80, refresh_token="rt", account_name="test", save_dir="/")
|
||||
data.update(kwargs)
|
||||
return PlatformConfig(**data)
|
||||
|
||||
|
||||
def make_transfer_config():
|
||||
return TransferConfig(request_timeout=1, max_retries=0, ad_filter_enabled=False, task_poll_interval=0, task_poll_max_attempts=2)
|
||||
|
||||
|
||||
class P1DriveApiCapabilityTests(unittest.TestCase):
|
||||
def make_aliyun(self):
|
||||
with patch("cloudsearch_transfer.adapter.aliyun.AliyunCredentialManager", return_value=DummyCredential()):
|
||||
return AliyunAdapter(make_config(), make_transfer_config())
|
||||
|
||||
def make_baidu(self):
|
||||
with patch("cloudsearch_transfer.adapter.baidu.BaiduCredentialManager", return_value=DummyCredential()):
|
||||
return BaiduAdapter(make_config(), make_transfer_config())
|
||||
|
||||
def make_xunlei(self):
|
||||
with patch("cloudsearch_transfer.adapter.xunlei.XunleiCredentialManager", return_value=DummyCredential()):
|
||||
return XunleiAdapter(make_config(), make_transfer_config())
|
||||
|
||||
def test_p1_adapters_declare_callable_core_drive_api_methods(self):
|
||||
expectations = {
|
||||
"aliyun": (self.make_aliyun(), ["ensure_dir", "save_files", "rename", "move_files", "delete_files"]),
|
||||
"baidu": (self.make_baidu(), ["ensure_dir", "save_files", "rename", "move_files", "delete_files"]),
|
||||
"xunlei": (self.make_xunlei(), ["ensure_dir", "save_files", "poll_task", "rename", "move_files"]),
|
||||
}
|
||||
for platform, (adapter, required) in expectations.items():
|
||||
with self.subTest(platform=platform):
|
||||
for capability in required:
|
||||
self.assertTrue(adapter.capabilities.get(capability), f"{platform}.{capability} capability not declared")
|
||||
for method in ["ensure_dir", "mkdir", "rename", "move_files"]:
|
||||
self.assertNotEqual(getattr(type(adapter), method), getattr(adapter.__class__.__mro__[1], method, None), f"{platform}.{method} not overridden")
|
||||
if adapter.capabilities.get("delete_files"):
|
||||
self.assertNotEqual(getattr(type(adapter), "delete_files"), getattr(adapter.__class__.__mro__[1], "delete_files", None), f"{platform}.delete_files not overridden")
|
||||
|
||||
def test_aliyun_save_files_resolves_path_to_file_id_before_copy(self):
|
||||
adapter = self.make_aliyun()
|
||||
adapter.ensure_dir = lambda path: "target-folder-id"
|
||||
captured = {}
|
||||
transfer = adapter._get_transfer()
|
||||
def fake_batch_copy(share_id, share_token, file_ids, to_parent):
|
||||
captured["to_parent"] = to_parent
|
||||
return ["new-file"]
|
||||
transfer._batch_copy = fake_batch_copy
|
||||
detail = {"share_token": "share-token", "files": [{"file_id": "src-file"}]}
|
||||
self.assertEqual(adapter._save_files("share-id", detail, "/Media"), ["new-file"])
|
||||
self.assertEqual(captured["to_parent"], "target-folder-id")
|
||||
|
||||
def test_aliyun_ensure_dir_creates_nested_paths_progressively(self):
|
||||
adapter = self.make_aliyun()
|
||||
existing = {"/A": "fid-A"}
|
||||
created = []
|
||||
adapter.get_fids = lambda paths: [{"file_path": p, "fid": existing[p]} for p in paths if p in existing]
|
||||
def fake_mkdir(path):
|
||||
created.append(path)
|
||||
existing[path] = "fid-" + path.rsplit("/", 1)[-1]
|
||||
return {"code": 0, "data": {"fid": existing[path]}}
|
||||
adapter.mkdir = fake_mkdir
|
||||
self.assertEqual(adapter.ensure_dir("/A/B"), "fid-B")
|
||||
self.assertEqual(created, ["/A/B"])
|
||||
|
||||
def test_baidu_save_files_ensures_path_before_transfer_and_matching(self):
|
||||
adapter = self.make_baidu()
|
||||
adapter.ensure_dir = lambda path: "/CloudSearch/Media"
|
||||
captured = {}
|
||||
adapter.credential.get_bdstoken = lambda: "bdstoken"
|
||||
def fake_transfer_files(shareid, uk, fs_ids, save_dir, token):
|
||||
captured["transfer_dir"] = save_dir
|
||||
adapter._transfer._transfer_files = fake_transfer_files
|
||||
def fake_list_and_match(save_dir, filenames, token):
|
||||
captured["list_dir"] = save_dir
|
||||
return ["1001"]
|
||||
adapter._transfer._list_and_match = fake_list_and_match
|
||||
detail = {"shareid": "sid", "uk": "uk", "fs_ids": ["old"], "filenames": ["demo.mkv"]}
|
||||
self.assertEqual(adapter._save_files("pwd", detail, "/CloudSearch/Media"), ["1001"])
|
||||
self.assertEqual(captured["transfer_dir"], "/CloudSearch/Media")
|
||||
self.assertEqual(captured["list_dir"], "/CloudSearch/Media")
|
||||
|
||||
def test_baidu_ensure_dir_creates_nested_paths_progressively(self):
|
||||
adapter = self.make_baidu()
|
||||
dirs = {"/A"}
|
||||
created = []
|
||||
adapter.get_files = lambda parent="/": [FileInfo(fid=parent.rstrip("/") + "/A", name="A", is_dir=True)] if parent == "/" else []
|
||||
def fake_mkdir(path):
|
||||
created.append(path)
|
||||
dirs.add(path)
|
||||
return {"code": 0, "data": {"path": path}}
|
||||
adapter.mkdir = fake_mkdir
|
||||
self.assertEqual(adapter.ensure_dir("/A/B"), "/A/B")
|
||||
self.assertEqual(created, ["/A/B"])
|
||||
|
||||
def test_xunlei_save_files_resolves_path_and_polls_restore_task(self):
|
||||
adapter = self.make_xunlei()
|
||||
adapter.ensure_dir = lambda path: "target-parent-id"
|
||||
transfer = adapter._transfer
|
||||
transfer._restore_files = lambda share_id, token, fids, parent_id="": "task-1" if parent_id == "target-parent-id" else (_ for _ in ()).throw(AssertionError(parent_id))
|
||||
transfer._poll_restore_task = lambda task_id: {"old-1": "new-1"}
|
||||
detail = {"share_id": "share", "pass_code_token": "pct", "files": [{"id": "old-1", "name": "demo"}]}
|
||||
self.assertEqual(adapter._save_files("share", detail, "/Media"), ["new-1"])
|
||||
|
||||
def test_xunlei_query_task_delegates_to_restore_poll(self):
|
||||
adapter = self.make_xunlei()
|
||||
adapter._transfer._poll_restore_task = lambda task_id: {"old": "new"}
|
||||
self.assertEqual(adapter.query_task("task-1"), {"code": 0, "status": 200, "data": {"old": "new"}})
|
||||
|
||||
def test_xunlei_does_not_advertise_permanent_delete_as_drive_delete_capability(self):
|
||||
adapter = self.make_xunlei()
|
||||
self.assertFalse(adapter.capabilities.get("delete_files"), "迅雷 batchDelete is permanent and must not be advertised as generic delete_files")
|
||||
|
||||
def test_xunlei_get_share_detail_passes_extraction_code_to_transfer_engine(self):
|
||||
adapter = self.make_xunlei()
|
||||
captured = {}
|
||||
def fake_get_share_info(share_id, passcode=""):
|
||||
captured["share_id"] = share_id
|
||||
captured["passcode"] = passcode
|
||||
return {"title": "demo", "files": [{"id": "old"}], "pass_code_token": "pct"}
|
||||
adapter._transfer._get_share_info = fake_get_share_info
|
||||
detail = adapter._get_share_detail("share-id", "abcd")
|
||||
self.assertEqual(detail["title"], "demo")
|
||||
self.assertEqual(captured, {"share_id": "share-id", "passcode": "abcd"})
|
||||
|
||||
def test_xunlei_create_share_forwards_requested_share_password(self):
|
||||
adapter = self.make_xunlei()
|
||||
captured = {}
|
||||
def fake_create_share(file_ids, password=""):
|
||||
captured["file_ids"] = file_ids
|
||||
captured["password"] = password
|
||||
return ("https://pan.xunlei.com/s/new", password)
|
||||
adapter._transfer._create_share = fake_create_share
|
||||
self.assertEqual(adapter._create_share(["fid-1"], "demo", "xy12"), ("https://pan.xunlei.com/s/new", "xy12"))
|
||||
self.assertEqual(captured, {"file_ids": ["fid-1"], "password": "xy12"})
|
||||
|
||||
def test_xunlei_poll_task_alias_matches_declared_capability(self):
|
||||
adapter = self.make_xunlei()
|
||||
adapter._transfer._poll_restore_task = lambda task_id: {"old": "new"}
|
||||
self.assertEqual(adapter.poll_task("task-1"), {"code": 0, "status": 200, "data": {"old": "new"}})
|
||||
|
||||
def test_xunlei_get_fids_is_read_only_and_does_not_create_missing_paths(self):
|
||||
adapter = self.make_xunlei()
|
||||
calls = []
|
||||
def fake_get_files(parent="0"):
|
||||
calls.append(("get_files", parent))
|
||||
if parent in ("0", ""):
|
||||
return [FileInfo(fid="fid-A", name="A", is_dir=True)]
|
||||
if parent == "fid-A":
|
||||
return [FileInfo(fid="fid-B", name="B", is_dir=False)]
|
||||
return []
|
||||
def fail_mkdir(*args, **kwargs):
|
||||
raise AssertionError("get_fids must not create directories")
|
||||
adapter.get_files = fake_get_files
|
||||
adapter.mkdir = fail_mkdir
|
||||
self.assertEqual(adapter.get_fids(["/A/B", "/A/Missing"]), [{"file_path": "/A/B", "fid": "fid-B"}])
|
||||
self.assertEqual(calls, [("get_files", "0"), ("get_files", "fid-A"), ("get_files", "0"), ("get_files", "fid-A")])
|
||||
|
||||
def test_baidu_get_fids_checks_existing_parent_listing(self):
|
||||
adapter = self.make_baidu()
|
||||
calls = []
|
||||
def fake_get_files(parent="/"):
|
||||
calls.append(parent)
|
||||
if parent == "/A":
|
||||
return [FileInfo(fid="1001", name="movie.mkv", is_dir=False)]
|
||||
return []
|
||||
adapter.get_files = fake_get_files
|
||||
self.assertEqual(adapter.get_fids(["/A/movie.mkv", "/A/missing.mkv"]), [{"file_path": "/A/movie.mkv", "fid": "1001", "path": "/A/movie.mkv"}])
|
||||
self.assertEqual(calls, ["/A", "/A"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,308 @@
|
||||
|
||||
import unittest
|
||||
import requests
|
||||
from unittest.mock import patch
|
||||
|
||||
from cloudsearch_transfer.adapter.quark import QuarkAdapter
|
||||
from cloudsearch_transfer.adapter.uc import UcAdapter
|
||||
from cloudsearch_transfer.config import PlatformConfig, TransferConfig
|
||||
from cloudsearch_transfer.adapter.base import BaseCloudDriveAdapter
|
||||
from cloudsearch_transfer.errors import TransferError
|
||||
|
||||
|
||||
class DummyResponse:
|
||||
def __init__(self, payload):
|
||||
self._payload = payload
|
||||
def json(self):
|
||||
return self._payload
|
||||
def raise_for_status(self):
|
||||
return None
|
||||
|
||||
|
||||
def make_adapter(adapter_cls):
|
||||
return adapter_cls(
|
||||
PlatformConfig(enabled=True, cookie="k=" + "x" * 80, account_name="test"),
|
||||
TransferConfig(request_timeout=1, max_retries=0, ad_filter_enabled=False),
|
||||
)
|
||||
|
||||
|
||||
class QuarkUcCapabilityTests(unittest.TestCase):
|
||||
def test_quark_declares_p0_drive_api_capabilities(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
self.assertTrue(adapter.capabilities["ensure_dir"])
|
||||
self.assertTrue(adapter.capabilities["save_files"])
|
||||
self.assertTrue(adapter.capabilities["poll_task"])
|
||||
self.assertTrue(adapter.capabilities["rename"])
|
||||
self.assertTrue(adapter.capabilities["move_files"])
|
||||
self.assertTrue(adapter.capabilities["delete_files"])
|
||||
self.assertTrue(adapter.capabilities["cleanup_recycle"])
|
||||
|
||||
def test_uc_declares_p0_drive_api_capabilities_and_share_staging(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
self.assertTrue(adapter.capabilities["ensure_dir"])
|
||||
self.assertTrue(adapter.capabilities["save_files"])
|
||||
self.assertTrue(adapter.capabilities["poll_task"])
|
||||
self.assertTrue(adapter.capabilities["rename"])
|
||||
self.assertTrue(adapter.capabilities["move_files"])
|
||||
self.assertTrue(adapter.capabilities["delete_files"])
|
||||
self.assertTrue(adapter.capabilities["share_staging_folder"])
|
||||
|
||||
def test_quark_ensure_dir_uses_path_lookup_before_mkdir(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
mkdir_calls = []
|
||||
adapter.get_fids = lambda paths: [{"file_path": "/Media", "fid": "fid-media"}]
|
||||
adapter.mkdir = lambda path: mkdir_calls.append(path) or {"code": 0, "data": {"fid": "new"}}
|
||||
self.assertEqual(adapter.ensure_dir("/Media"), "fid-media")
|
||||
self.assertEqual(mkdir_calls, [])
|
||||
|
||||
def test_quark_ensure_dir_creates_missing_path(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
adapter.get_fids = lambda paths: []
|
||||
adapter.mkdir = lambda path: {"code": 0, "data": {"fid": "created-fid"}}
|
||||
self.assertEqual(adapter.ensure_dir("Media/Shows"), "created-fid")
|
||||
|
||||
def test_uc_share_folder_created_when_missing(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
adapter.ls_dir = lambda parent: {"code": 0, "data": {"list": []}}
|
||||
adapter.mkdir = lambda path: {"code": 0, "data": {"fid": "share-folder-fid"}}
|
||||
self.assertEqual(adapter.get_or_create_share_folder(), "share-folder-fid")
|
||||
|
||||
def test_uc_move_files_polls_task_until_complete(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
calls = []
|
||||
def fake_post(url, json_data=None, params=None, headers=None):
|
||||
calls.append((url, json_data))
|
||||
return DummyResponse({"code": 0, "status": 200, "data": {"task_id": "task-1"}})
|
||||
adapter._post = fake_post
|
||||
adapter.query_task = lambda task_id: {"code": 0, "status": 200, "data": {"status": 2}}
|
||||
result = adapter.move_files(["fid-1", "fid-2"], "target-fid")
|
||||
self.assertEqual(result["code"], 0)
|
||||
self.assertEqual(calls[0][1]["filelist"], ["fid-1", "fid-2"])
|
||||
self.assertEqual(calls[0][1]["to_pdir_fid"], "target-fid")
|
||||
|
||||
def test_quark_save_files_resolves_path_to_fid(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
adapter.ensure_dir = lambda path: "target-fid"
|
||||
adapter._transfer_engine._get_stoken = lambda pwd_id: "stoken"
|
||||
captured = {}
|
||||
adapter._transfer_engine._init_save = lambda pwd_id, stoken, detail, to_pdir_fid: captured.setdefault("to_pdir_fid", to_pdir_fid) or "task-1"
|
||||
adapter._transfer_engine._poll_save_task = lambda task_id: ["new-fid"]
|
||||
self.assertEqual(adapter._save_files("pwd", {"fid": "src"}, "/Media"), ["new-fid"])
|
||||
self.assertEqual(captured["to_pdir_fid"], "target-fid")
|
||||
|
||||
def test_uc_transfer_resolves_path_to_fid(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
adapter._credential.validate = lambda: True
|
||||
adapter.ensure_dir = lambda path: "target-fid"
|
||||
adapter.get_or_create_share_folder = lambda: "target-fid"
|
||||
adapter._parse_share_url = lambda url: ("pwd", "pass")
|
||||
adapter._transfer_engine._get_stoken = lambda pwd_id, passcode="": "stoken"
|
||||
adapter._transfer_engine._get_detail = lambda pwd_id, stoken: {"title": "demo", "fid": "src"}
|
||||
captured = {}
|
||||
def fake_init_save(pwd_id, stoken, detail, to_pdir_fid):
|
||||
captured["save_dir"] = to_pdir_fid
|
||||
return "save-task"
|
||||
adapter._transfer_engine._init_save = fake_init_save
|
||||
adapter._transfer_engine._poll_save_task = lambda task_id: ["new-fid"]
|
||||
adapter._transfer_engine._init_share = lambda fids, title: "share-task"
|
||||
adapter._transfer_engine._poll_share_task = lambda task_id: "share-id"
|
||||
adapter._transfer_engine._set_password = lambda share_id, password: ("https://drive.uc.cn/s/new", password)
|
||||
result = adapter.transfer("https://drive.uc.cn/s/abcdef", save_dir="/Media", share_password="pw")
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(captured["save_dir"], "target-fid")
|
||||
def test_quark_transfer_resolves_path_to_fid(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
adapter.ensure_dir = lambda path: "target-fid"
|
||||
adapter._credential.validate = lambda: True
|
||||
captured = {}
|
||||
def fake_transfer(share_url, save_dir, share_password):
|
||||
captured["save_dir"] = save_dir
|
||||
return {
|
||||
"new_file_ids": ["new-fid"],
|
||||
"file_name": "demo",
|
||||
"share_url": "https://pan.quark.cn/s/new",
|
||||
"passcode": share_password,
|
||||
}
|
||||
adapter._transfer_engine.transfer = fake_transfer
|
||||
result = adapter._transfer("https://pan.quark.cn/s/abcdef", save_dir="/Media", share_password="pw")
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(captured["save_dir"], "target-fid")
|
||||
|
||||
def test_ensure_dir_creates_nested_paths_progressively(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
adapter.get_fids = lambda paths: []
|
||||
created = []
|
||||
def fake_mkdir(path):
|
||||
created.append(path)
|
||||
return {"code": 0, "data": {"fid": "fid-" + path.rsplit("/", 1)[-1]}}
|
||||
adapter.mkdir = fake_mkdir
|
||||
self.assertEqual(adapter.ensure_dir("/A/B"), "fid-B")
|
||||
self.assertEqual(created, ["/A", "/A/B"])
|
||||
|
||||
def test_uc_transfer_uses_staging_folder_then_moves_to_target(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
adapter._credential.validate = lambda: True
|
||||
adapter.ensure_dir = lambda path: "target-fid"
|
||||
adapter.get_or_create_share_folder = lambda: "staging-fid"
|
||||
adapter._parse_share_url = lambda url: ("pwd", "pass")
|
||||
adapter._transfer_engine._get_stoken = lambda pwd_id, passcode="": "stoken"
|
||||
adapter._transfer_engine._get_detail = lambda pwd_id, stoken: {"title": "demo", "fid": "src"}
|
||||
captured = {}
|
||||
def fake_init_save(pwd_id, stoken, detail, to_pdir_fid):
|
||||
captured["save_to"] = to_pdir_fid
|
||||
return "save-task"
|
||||
adapter._transfer_engine._init_save = fake_init_save
|
||||
adapter._transfer_engine._poll_save_task = lambda task_id: ["new-fid"]
|
||||
def fake_move(fids, to_pdir_fid):
|
||||
captured["move_to"] = to_pdir_fid
|
||||
return {"code": 0, "status": 200}
|
||||
adapter.move_files = fake_move
|
||||
adapter._transfer_engine._init_share = lambda fids, title: "share-task"
|
||||
adapter._transfer_engine._poll_share_task = lambda task_id: "share-id"
|
||||
adapter._transfer_engine._set_password = lambda share_id, password: ("https://drive.uc.cn/s/new", password)
|
||||
result = adapter.transfer("https://drive.uc.cn/s/abcdef", save_dir="/Media", share_password="pw")
|
||||
self.assertTrue(result.success)
|
||||
self.assertEqual(captured["save_to"], "staging-fid")
|
||||
self.assertEqual(captured["move_to"], "target-fid")
|
||||
|
||||
def test_api_success_requires_explicit_success_code_or_status(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
self.assertFalse(adapter._api_success({}))
|
||||
self.assertFalse(adapter._api_success({"message": "bad"}))
|
||||
self.assertTrue(adapter._api_success({"code": 0}))
|
||||
self.assertTrue(adapter._api_success({"status": 200}))
|
||||
|
||||
def test_query_task_passes_retry_index_and_timestamp_params(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
captured_params = []
|
||||
attempts = {"count": 0}
|
||||
def fake_get(url, params=None, retry=None):
|
||||
captured_params.append(dict(params))
|
||||
attempts["count"] += 1
|
||||
if attempts["count"] == 1:
|
||||
return DummyResponse({"data": {"status": 1}})
|
||||
return DummyResponse({"data": {"status": 2, "task_id": "task-1"}})
|
||||
adapter._get = fake_get
|
||||
result = adapter.query_task("task-1")
|
||||
self.assertEqual(result["code"], 0)
|
||||
self.assertEqual(captured_params[0]["retry_index"], 0)
|
||||
self.assertEqual(captured_params[1]["retry_index"], 1)
|
||||
self.assertIn("__dt", captured_params[0])
|
||||
self.assertIn("__t", captured_params[0])
|
||||
|
||||
def test_quark_cleanup_recycle_only_removes_matching_fids(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
adapter.recycle_list = lambda: [
|
||||
{"fid": "keep", "record_id": "r-keep"},
|
||||
{"fid": "target", "record_id": "r-target"},
|
||||
]
|
||||
captured = {}
|
||||
def fake_recycle_remove(records):
|
||||
captured["records"] = records
|
||||
return {"code": 0}
|
||||
adapter.recycle_remove = fake_recycle_remove
|
||||
result = adapter.cleanup_recycle(["target"])
|
||||
self.assertEqual(result["code"], 0)
|
||||
self.assertEqual(captured["records"], [{"fid": "target", "record_id": "r-target"}])
|
||||
|
||||
def test_drive_api_json_raises_transfer_error_on_http_error(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
class ErrorResponse(DummyResponse):
|
||||
status_code = 429
|
||||
text = "too many requests"
|
||||
def raise_for_status(self):
|
||||
raise requests.HTTPError("429 Too Many Requests")
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
adapter._drive_api_json(ErrorResponse({"code": 0}), context="限流测试")
|
||||
self.assertIn("限流测试", str(ctx.exception))
|
||||
|
||||
def test_drive_api_json_raises_transfer_error_on_invalid_json(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
class InvalidJsonResponse(DummyResponse):
|
||||
status_code = 200
|
||||
text = "<html>bad gateway</html>"
|
||||
def json(self):
|
||||
raise ValueError("not json")
|
||||
with self.assertRaises(Exception) as ctx:
|
||||
adapter._drive_api_json(InvalidJsonResponse({}), context="JSON测试")
|
||||
self.assertIn("JSON测试", str(ctx.exception))
|
||||
|
||||
|
||||
def test_quark_delete_files_capability_calls_drive_delete(self):
|
||||
adapter = make_adapter(QuarkAdapter)
|
||||
calls = []
|
||||
adapter.delete = lambda fids: calls.append(list(fids)) or True
|
||||
self.assertEqual(adapter.delete_files(["fid-1", "fid-2"]), {"code": 0, "status": 200})
|
||||
self.assertEqual(calls, [["fid-1", "fid-2"]])
|
||||
|
||||
def test_uc_delete_files_capability_calls_drive_delete(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
calls = []
|
||||
adapter.delete = lambda fids: calls.append(list(fids)) or True
|
||||
self.assertEqual(adapter.delete_files(["fid-1"]), {"code": 0, "status": 200})
|
||||
self.assertEqual(calls, [["fid-1"]])
|
||||
|
||||
def test_uc_transfer_filters_ads_after_staging_save(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
adapter.ensure_dir = lambda path: "target-dir"
|
||||
adapter.get_or_create_share_folder = lambda: "staging-dir"
|
||||
adapter._parse_share_url = lambda url: ("pwd-id", "")
|
||||
adapter._filter_ads = lambda fids: [fid for fid in fids if fid != "ad-fid"]
|
||||
adapter.transfer_config.ad_filter_enabled = True
|
||||
adapter._transfer_engine._get_stoken = lambda pwd_id, passcode: "stoken"
|
||||
adapter._transfer_engine._get_detail = lambda pwd_id, stoken: {"title": "Title"}
|
||||
adapter._transfer_engine._init_save = lambda pwd_id, stoken, detail, to_pdir_fid: "save-task"
|
||||
adapter._transfer_engine._poll_save_task = lambda task_id: ["keep-fid", "ad-fid"]
|
||||
adapter.move_files = lambda fids, target: {"code": 0}
|
||||
adapter._transfer_engine._init_share = lambda fids, title: "share-task" if fids == ["keep-fid"] else (_ for _ in ()).throw(AssertionError(f"unfiltered fids: {fids}"))
|
||||
adapter._transfer_engine._poll_share_task = lambda task_id: "share-id"
|
||||
adapter._transfer_engine._set_password = lambda share_id, pwd: ("https://share", pwd)
|
||||
|
||||
result = adapter.transfer("https://drive.uc.cn/s/abc")
|
||||
|
||||
self.assertEqual(result.new_file_id, "keep-fid")
|
||||
|
||||
def test_uc_ls_dir_wraps_http_and_json_errors(self):
|
||||
adapter = make_adapter(UcAdapter)
|
||||
class BadResponse:
|
||||
text = "<html>bad gateway</html>"
|
||||
def raise_for_status(self):
|
||||
raise requests.HTTPError("HTTP 502")
|
||||
def json(self):
|
||||
raise AssertionError("json() should not be called after HTTP error")
|
||||
adapter._get = lambda *args, **kwargs: BadResponse()
|
||||
with self.assertRaises(TransferError):
|
||||
adapter.ls_dir("0")
|
||||
|
||||
def test_base_declares_optional_drive_api_methods(self):
|
||||
required = [
|
||||
"ensure_dir", "get_fids", "mkdir", "rename", "move_files",
|
||||
"delete_files", "query_task", "cleanup_recycle",
|
||||
]
|
||||
for name in required:
|
||||
self.assertTrue(hasattr(BaseCloudDriveAdapter, name), name)
|
||||
|
||||
def test_base_optional_drive_api_methods_raise_transfer_error(self):
|
||||
class MinimalAdapter(BaseCloudDriveAdapter):
|
||||
PLATFORM_KEY = "minimal"
|
||||
def verify(self, share_url):
|
||||
raise NotImplementedError
|
||||
def _get_share_detail(self, pwd_id, passcode=""):
|
||||
raise NotImplementedError
|
||||
def _save_files(self, pwd_id, detail, save_dir):
|
||||
raise NotImplementedError
|
||||
def _create_share(self, file_ids, title, password=""):
|
||||
raise NotImplementedError
|
||||
def get_files(self, parent_fid="0"):
|
||||
raise NotImplementedError
|
||||
def delete(self, file_ids):
|
||||
raise NotImplementedError
|
||||
adapter = MinimalAdapter(PlatformConfig(), TransferConfig())
|
||||
with self.assertRaises(TransferError):
|
||||
adapter.ensure_dir("/Media")
|
||||
with self.assertRaises(TransferError):
|
||||
adapter.move_files(["fid"], "target")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,135 @@
|
||||
import subprocess
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class TransferServerRuntimeTests(unittest.TestCase):
|
||||
def test_transfer_image_imports_server_module_from_app_workdir(self):
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker", "run", "--rm",
|
||||
"-e", "TRANSFER_CONFIG_PATH=/tmp/transfer_config.json",
|
||||
"cloudsearch-transfer:test",
|
||||
"python", "-c",
|
||||
"import cloudsearch_transfer.server as s; print(hasattr(s, 'app'))",
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=20,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("True", result.stdout)
|
||||
|
||||
def test_update_platform_creates_config_with_package_import(self):
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker", "run", "--rm",
|
||||
"-e", "TRANSFER_CONFIG_PATH=/tmp/transfer_config_server_test.json",
|
||||
"cloudsearch-transfer:test",
|
||||
"python", "-c",
|
||||
"""
|
||||
import os
|
||||
os.environ['TRANSFER_API_TOKEN'] = 'test-token'
|
||||
from cloudsearch_transfer.server import app
|
||||
client = app.test_client()
|
||||
resp = client.put('/api/config/platforms/newplatform', headers={'X-Transfer-Token': 'test-token'}, json={'enabled': True, 'save_dir': '/Media'})
|
||||
print(resp.status_code)
|
||||
print(resp.get_data(as_text=True))
|
||||
""",
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=20,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("200", result.stdout)
|
||||
self.assertIn("newplatform", result.stdout)
|
||||
|
||||
def test_dockerfile_healthcheck_uses_http_health_probe(self):
|
||||
dockerfile = Path(__file__).resolve().parents[1] / "Dockerfile"
|
||||
content = dockerfile.read_text()
|
||||
healthcheck_block = content.split("HEALTHCHECK", 1)[1].split("\n\nCMD", 1)[0]
|
||||
self.assertIn("/health", healthcheck_block)
|
||||
self.assertNotIn('python -m cloudsearch_transfer.server', healthcheck_block)
|
||||
|
||||
def test_config_update_requires_api_token_when_configured(self):
|
||||
script = """
|
||||
import os
|
||||
os.environ['TRANSFER_API_TOKEN'] = 'secret-token'
|
||||
os.environ['TRANSFER_CONFIG_PATH'] = '/tmp/transfer_config_auth_test.json'
|
||||
from cloudsearch_transfer.server import app
|
||||
client = app.test_client()
|
||||
unauth = client.put('/api/config/platforms/newplatform', json={'enabled': True})
|
||||
auth = client.put('/api/config/platforms/newplatform', headers={'X-Transfer-Token': 'secret-token'}, json={'enabled': True, 'save_dir': '/Media'})
|
||||
print(unauth.status_code)
|
||||
print(auth.status_code)
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker", "run", "--rm",
|
||||
"-e", "TRANSFER_CONFIG_PATH=/tmp/transfer_config_auth_test.json",
|
||||
"cloudsearch-transfer:test",
|
||||
"python", "-c", script,
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=20,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("401", result.stdout)
|
||||
self.assertIn("200", result.stdout)
|
||||
|
||||
def test_config_update_requires_token_by_default(self):
|
||||
script = """
|
||||
import os
|
||||
os.environ.pop('TRANSFER_API_TOKEN', None)
|
||||
os.environ['TRANSFER_CONFIG_PATH'] = '/tmp/transfer_config_default_auth_test.json'
|
||||
from cloudsearch_transfer.server import app
|
||||
client = app.test_client()
|
||||
resp = client.put('/api/config/platforms/newplatform', json={'enabled': True})
|
||||
print(resp.status_code)
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker", "run", "--rm",
|
||||
"-e", "TRANSFER_CONFIG_PATH=/tmp/transfer_config_default_auth_test.json",
|
||||
"cloudsearch-transfer:test",
|
||||
"python", "-c", script,
|
||||
],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=20,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("401", result.stdout)
|
||||
|
||||
def test_config_save_persists_full_credentials_for_runtime_reload(self):
|
||||
script = """
|
||||
from cloudsearch_transfer.config import ConfigManager, PlatformConfig
|
||||
path = '/tmp/transfer_config_persist_test.json'
|
||||
cfg = ConfigManager(path)
|
||||
cfg.platforms['quark'] = PlatformConfig(enabled=True, cookie='cookie-value-that-is-longer-than-twenty-characters', refresh_token='refresh-token-that-is-longer-than-twenty-characters')
|
||||
cfg.save()
|
||||
reloaded = ConfigManager(path)
|
||||
print(reloaded.platforms['quark'].cookie)
|
||||
print(reloaded.platforms['quark'].refresh_token)
|
||||
"""
|
||||
result = subprocess.run(
|
||||
["docker", "run", "--rm", "cloudsearch-transfer:test", "python", "-c", script],
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
timeout=20,
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("cookie-value-that-is-longer-than-twenty-characters", result.stdout)
|
||||
self.assertIn("refresh-token-that-is-longer-than-twenty-characters", result.stdout)
|
||||
self.assertNotIn("...", result.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -70,6 +70,7 @@ services:
|
||||
- CORS_ORIGIN=${CORS_ORIGIN}
|
||||
|
||||
# ── 数据库 & 缓存 ──
|
||||
- DATA_DIR=/data
|
||||
- DB_PATH=${DB_PATH:-/data/database.sqlite}
|
||||
- REDIS_URL=${REDIS_URL:-redis://redis:6379}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# CloudSearch 测试服部署记录
|
||||
|
||||
- 服务器:泽御云香港测试服,SSH 入口 root@82.158.228.152:18924。
|
||||
- 当前运行来源:`/root/cloudsearch_deploy`。
|
||||
- 主应用目录:`/root/cloudsearch_deploy/source_clean`。
|
||||
- 网盘能力层:`/root/cloudsearch_deploy/cloudsearch_transfer`。
|
||||
- 启动方式:Docker Compose,主容器挂载 `/root/cloudsearch_deploy` 相关目录。
|
||||
- 集成边界:只集成 cloud-auto-save 中可复用的网盘 API 调用能力;不集成签到、收益事件、收益上报,也不长期部署独立 cloud-auto-save 服务。
|
||||
- Video Parser 当前未部署,属于测试环境预期缺口,不作为主服务故障处理。
|
||||
- 本轮隔离点:`backup-before-drive-api-20260522-152138`;开发分支:`feature/cloudsearch-drive-api-20260522-152138`。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s cloudsearch_transfer/tests -p 'test_*.py' -v
|
||||
python3 /tmp/verify_compile.py
|
||||
docker compose ps
|
||||
curl -fsS http://127.0.0.1:3000/health
|
||||
```
|
||||
@@ -1 +1 @@
|
||||
0.4.0
|
||||
0.5.6
|
||||
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
cd "$(dirname "$0")/source_clean"
|
||||
|
||||
VERSION=$(cat ../VERSION)
|
||||
echo "🔨 Building CloudSearch v${VERSION}..."
|
||||
|
||||
docker build -t cloudsearch-app:v${VERSION} -t cloudsearch-app:latest .
|
||||
|
||||
echo "✅ Built: cloudsearch-app:v${VERSION} + cloudsearch-app:latest"
|
||||
echo " Run: docker-compose up -d app"
|
||||
+116
-44
@@ -1,56 +1,128 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$SCRIPT_DIR"
|
||||
# CloudSearch 一键部署
|
||||
# curl -sS https://gitea.timxx.cn/admin/CloudSearch/raw/branch/master/source_clean/deploy.sh | bash
|
||||
#
|
||||
# 环境变量(可选):
|
||||
# CORS_ORIGIN - 域名 (默认自动检测公网IP)
|
||||
# ADMIN_PASSWORD - 管理员密码 (留空自动生成)
|
||||
# JWT_SECRET - JWT密钥 (留空自动生成)
|
||||
# DEPLOY_DIR - 部署目录 (默认 /opt/cloudsearch)
|
||||
|
||||
# 如果 docker-compose.yml 不存在,自动下载
|
||||
if [ ! -f docker-compose.yml ]; then
|
||||
echo "📥 下载 docker-compose.yml..."
|
||||
wget -q https://gitea.timxx.cn/admin/CloudSearch/raw/branch/master/source_clean/docker-compose.yml
|
||||
REPO_URL="https://gitea.timxx.cn/admin/CloudSearch.git"
|
||||
DEPLOY_DIR="${DEPLOY_DIR:-/opt/cloudsearch}"
|
||||
|
||||
info() { echo "[INFO] $*"; }
|
||||
warn() { echo "[WARN] $*"; }
|
||||
err() { echo "[ERROR] $*"; }
|
||||
|
||||
command -v docker &>/dev/null || { err "Docker未安装"; exit 1; }
|
||||
|
||||
# -- CORS_ORIGIN --
|
||||
if [ -z "$CORS_ORIGIN" ]; then
|
||||
PUBLIC_IP=$(curl -s --connect-timeout 3 ifconfig.me 2>/dev/null || curl -s --connect-timeout 3 ip.sb 2>/dev/null || echo "")
|
||||
if [ -n "$PUBLIC_IP" ]; then
|
||||
CORS_ORIGIN="http://${PUBLIC_IP}:9527"
|
||||
else
|
||||
CORS_ORIGIN="http://localhost:9527"
|
||||
fi
|
||||
warn "CORS_ORIGIN=$CORS_ORIGIN (自动检测)"
|
||||
fi
|
||||
|
||||
echo "🔍 检测 Redis..."
|
||||
|
||||
EXISTING_REDIS=$(docker ps --format '{{.Names}}' | grep -i redis | head -1)
|
||||
|
||||
if [ -n "$EXISTING_REDIS" ]; then
|
||||
if docker network inspect cloudsearch-net --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | grep -qw "$EXISTING_REDIS"; then
|
||||
echo "✅ 已有 Redis: $EXISTING_REDIS (已加入 cloudsearch-net),跳过创建"
|
||||
else
|
||||
echo "✅ 已有 Redis: $EXISTING_REDIS,正在加入网络..."
|
||||
docker network connect cloudsearch-net "$EXISTING_REDIS" 2>/dev/null || true
|
||||
echo " ✅ 已加入 cloudsearch-net"
|
||||
fi
|
||||
|
||||
# 检测 Redis 密码
|
||||
REDIS_PASS=$(docker inspect "$EXISTING_REDIS" --format '{{range .Config.Cmd}}{{println .}}{{end}}' 2>/dev/null | grep -A1 'requirepass' | tail -1 || true)
|
||||
if [ -n "$REDIS_PASS" ]; then
|
||||
REDIS_URL="redis://:${REDIS_PASS}@${EXISTING_REDIS}:6379"
|
||||
echo " 🔑 检测到 Redis 密码,已自动配置"
|
||||
else
|
||||
REDIS_URL="redis://${EXISTING_REDIS}:6379"
|
||||
fi
|
||||
PROFILE=""
|
||||
# -- 拉取仓库 --
|
||||
info "拉取仓库..."
|
||||
if [ -d "$DEPLOY_DIR/.git" ]; then
|
||||
cd "$DEPLOY_DIR" && git pull --ff-only origin master 2>/dev/null || true
|
||||
else
|
||||
echo "📦 未检测到 Redis,将自动创建..."
|
||||
REDIS_URL="redis://CloudSearch_Redis:6379"
|
||||
PROFILE="--profile full"
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
git clone --depth 1 "$REPO_URL" "$DEPLOY_DIR"
|
||||
fi
|
||||
cd "$DEPLOY_DIR/source_clean"
|
||||
|
||||
# -- 切换镜像模式 --
|
||||
if grep -q '^ build:' docker-compose.yml 2>/dev/null; then
|
||||
info "切换镜像模式..."
|
||||
sed -i 's/^ build:/ # build:/' docker-compose.yml
|
||||
sed -i 's/^ context:/ # context:/' docker-compose.yml
|
||||
sed -i 's/^ dockerfile:/ # dockerfile:/' docker-compose.yml
|
||||
sed -i 's|^ # image: gitea.timxx.cn/admin/cloudsearch:latest| image: gitea.timxx.cn/admin/cloudsearch:latest|' docker-compose.yml
|
||||
fi
|
||||
|
||||
# 生成 .env
|
||||
cat > .env <<EOF
|
||||
# -- 检测 Redis --
|
||||
info "检测Redis..."
|
||||
EXISTING_REDIS=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -i redis | grep -v CloudSearch | head -1)
|
||||
if [ -n "$EXISTING_REDIS" ]; then
|
||||
REDIS_URL="redis://${EXISTING_REDIS}:6379"
|
||||
info "复用已有Redis: $EXISTING_REDIS"
|
||||
else
|
||||
REDIS_URL="redis://redis:6379"
|
||||
info "使用Compose自带Redis"
|
||||
fi
|
||||
|
||||
# -- 生成密钥 --
|
||||
JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32)}"
|
||||
ADMIN_PASSWORD="${ADMIN_PASSWORD:-$(openssl rand -base64 12 | tr -d '=+/' | head -c 16)}"
|
||||
|
||||
# -- 生成 .env --
|
||||
cat > .env <<ENVEOF
|
||||
CORS_ORIGIN=${CORS_ORIGIN}
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
ADMIN_PASSWORD=${ADMIN_PASSWORD}
|
||||
REDIS_URL=${REDIS_URL}
|
||||
CORS_ORIGIN=https://zy.hk.timxx.cn
|
||||
JWT_SECRET=cloudsearch-jwt-secret-2024
|
||||
ADMIN_PASSWORD=0nL5kLhMIJ1121PYmQb25A
|
||||
LOG_LEVEL=info
|
||||
EOF
|
||||
LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
ENVEOF
|
||||
|
||||
echo ""
|
||||
echo "🚀 启动服务..."
|
||||
docker compose $PROFILE up -d
|
||||
info "管理员: admin / ${ADMIN_PASSWORD}"
|
||||
|
||||
echo ""
|
||||
echo "✅ 部署完成"
|
||||
docker compose ps
|
||||
# -- 部署 --
|
||||
info "拉取镜像..."
|
||||
docker compose pull app 2>/dev/null || true
|
||||
|
||||
info "停止旧服务..."
|
||||
docker compose down --remove-orphans 2>/dev/null || true
|
||||
|
||||
info "启动服务..."
|
||||
docker compose up -d
|
||||
|
||||
# -- 等待就绪 --
|
||||
info "等待服务就绪..."
|
||||
for i in $(seq 1 20); do
|
||||
if curl -s -o /dev/null -w '%{http_code}' http://localhost:9527/health 2>/dev/null | grep -q '200'; then
|
||||
break
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
# -- 强制写入管理员密码 --
|
||||
info "同步管理员密码..."
|
||||
sleep 3
|
||||
docker exec -e ADMIN_PASSWORD="$ADMIN_PASSWORD" CloudSearch_App node -e '
|
||||
var bcrypt = require("bcryptjs");
|
||||
var Database = require("better-sqlite3");
|
||||
var db = new Database("/data/database.sqlite");
|
||||
var pw = process.env.ADMIN_PASSWORD || ""; var hash = bcrypt.hashSync(pw, 10);
|
||||
var existing = db.prepare("SELECT id FROM admins WHERE username = ?").get("admin");
|
||||
if (existing) {
|
||||
db.prepare("UPDATE admins SET password_hash = ? WHERE username = ?").run(hash, "admin");
|
||||
} else {
|
||||
db.prepare("INSERT INTO admins (username, password_hash) VALUES (?, ?)").run("admin", hash);
|
||||
}
|
||||
db.close();
|
||||
' 2>/dev/null && info "密码已同步" || warn "密码同步失败,请稍后重试"
|
||||
|
||||
# -- 验证 --
|
||||
if docker compose ps 2>/dev/null | grep -q 'Up'; then
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo " CloudSearch 部署完成"
|
||||
echo "=============================================="
|
||||
docker compose ps
|
||||
echo ""
|
||||
echo " 管理后台: ${CORS_ORIGIN}/admin/login"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: ${ADMIN_PASSWORD}"
|
||||
else
|
||||
err "启动失败: docker compose logs"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# CloudSearch 单容器网盘能力 P0 实现计划
|
||||
|
||||
> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
|
||||
|
||||
**目标:** 将 UC 网盘转存能力和主应用云盘 driver 分发能力集成进 `source_clean`,由现有 `CloudSearch_App` 单容器构建和运行,不新增长期 transfer 容器。
|
||||
|
||||
**架构:** 在 Node/TypeScript 主应用内新增 `UcDriver` 和轻量 driver factory。`saveFromShare()`、凭据验证、连接测试仍走现有 `cloud_configs`、`save_records`、`/api/save` 路径,只扩展 `uc` 类型,不改变前端主流程。
|
||||
|
||||
**技术栈:** Node 20 fetch、TypeScript commonjs、Express、better-sqlite3、Vitest、Docker Compose。
|
||||
|
||||
---
|
||||
|
||||
## 文件结构
|
||||
|
||||
- 创建:`src/cloud/drivers/uc-api.ts` — UC API 常量、请求头、URL 解析、任务轮询辅助。
|
||||
- 创建:`src/cloud/drivers/uc.driver.ts` — UC Cookie 验证、7 步转存、创建新分享,对齐现有 driver 返回结构。
|
||||
- 创建:`src/cloud/drivers/uc.driver.test.ts` — UC driver 红绿回归测试,mock fetch 验证 API 流程。
|
||||
- 创建:`src/cloud/driver-factory.ts` — 根据 `cloud_type` 创建 driver,避免 `credential.service.ts` 和 `cloud.service.ts` 分散 switch。
|
||||
- 创建:`src/cloud/driver-factory.test.ts` — 验证 `uc` 使用 `UcDriver`,未知类型返回 `null`。
|
||||
- 修改:`src/cloud/cloud.service.ts` — 保存入口支持 `uc`,复用统一 driver factory。
|
||||
- 修改:`src/cloud/credential.service.ts` — Cookie 测试和运行前凭据验证支持 `uc`。
|
||||
|
||||
## 任务 1:写失败测试
|
||||
|
||||
- [ ] 编写 `src/cloud/driver-factory.test.ts`:断言 `createCloudDriver(uc, {cookie})` 产物 class 为 `UcDriver`,`unknown` 为 `null`。
|
||||
- [ ] 编写 `src/cloud/drivers/uc.driver.test.ts`:断言 UC URL 解析、Cookie 长度校验、mock fetch 的完整转存链路。
|
||||
- [ ] 运行:`npm test -- src/cloud/driver-factory.test.ts src/cloud/drivers/uc.driver.test.ts`
|
||||
- [ ] 预期:失败,原因是 `driver-factory` 和 `uc.driver` 不存在。
|
||||
|
||||
## 任务 2:实现 UC driver 和 factory
|
||||
|
||||
- [ ] 新增 `uc-api.ts`:`UC_API_BASE=https://pc-api.uc.cn`、`UC_SHARE_API`、`getUcHeaders()`、`parseUcShareUrl()`。
|
||||
- [ ] 新增 `uc.driver.ts`:实现 `validate()`、`saveFromShare()`,流程为 stoken、detail、save、poll save task、create share、poll share task、set password。
|
||||
- [ ] 新增 `driver-factory.ts`:支持 `quark`、`baidu`、`aliyun`、`uc`。
|
||||
- [ ] 运行同一组测试,预期通过。
|
||||
|
||||
## 任务 3:接入主保存和凭据验证路径
|
||||
|
||||
- [ ] 修改 `cloud.service.ts`:用 factory 创建 driver;`uc` 调用 `saveFromShare()`,返回字段写入现有 `save_records`。
|
||||
- [ ] 修改 `credential.service.ts`:`testCloudConnectionWithCookie(uc)` 和 `getAndValidateCredential(uc)` 使用 `UcDriver.validate()`。
|
||||
- [ ] 新增或扩展测试覆盖 factory 接入。
|
||||
- [ ] 运行:`npm test -- src/cloud/driver-factory.test.ts src/cloud/drivers/uc.driver.test.ts`
|
||||
|
||||
## 任务 4:构建与单容器部署验证
|
||||
|
||||
- [ ] 运行:`npm run build`。
|
||||
- [ ] 运行:`docker compose build app`。
|
||||
- [ ] 运行:`docker compose up -d app`。
|
||||
- [ ] 验证:`docker compose ps` 只有 `app/redis/pansou`,没有 transfer 容器。
|
||||
- [ ] 验证:`curl -fsS http://127.0.0.1:9527/health`。
|
||||
- [ ] 验证:`curl -fsS http://127.0.0.1:9527/api/admin/cloud-types` 能看到 `uc`。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- `CloudSearch_App` 单容器内的主应用构建包含 `UcDriver`。
|
||||
- `/api/save` 对 `target_cloud=uc` 不再返回“暂不支持”。
|
||||
- 无长期 `cloudsearch-transfer` 或 `cloud-auto-save-test` 容器。
|
||||
- 没有收益上报、签到、独立 transfer 服务集成。
|
||||
@@ -0,0 +1,24 @@
|
||||
# CloudSearch 测试服治理说明
|
||||
|
||||
## 当前测试服定位
|
||||
|
||||
`/root/cloudsearch_deploy` 是当前 Docker Compose 实际运行目录。`CloudSearch_App` 的 compose label 与挂载均指向该目录。
|
||||
|
||||
## 健康检查口径
|
||||
|
||||
`Video Parser` 当前仍是待开发/未部署能力。测试服健康判断应区分核心必需组件与可选/开发中能力。
|
||||
|
||||
源码中的 `computeOverallHealth()` 支持通过 `VIDEO_PARSER_REQUIRED=true` 将 Video Parser 纳入必需健康判定;未设置时按可选能力处理。
|
||||
|
||||
## 版本来源
|
||||
|
||||
根目录 `VERSION` 与 `source_clean/VERSION` 当前均为 `0.5.5`。构建脚本会从根目录 `VERSION` 复制到 `source_clean/VERSION` 再构建镜像。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
cd /root/cloudsearch_deploy/source_clean
|
||||
npm run build
|
||||
npm test -- --run src/health/health.test.ts
|
||||
curl -fsS http://127.0.0.1:9527/health
|
||||
```
|
||||
@@ -3,16 +3,51 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta name="robots" content="index,follow" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="CloudSearch - 网盘资源搜索" />
|
||||
<meta property="og:description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta property="og:url" content="https://zy.hk.timxx.cn/" />
|
||||
<meta property="og:image" content="https://zy.hk.timxx.cn/api/uploads/logo/site_logo.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="CloudSearch - 网盘资源搜索" />
|
||||
<meta name="twitter:description" content="随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。" />
|
||||
<meta name="twitter:image" content="https://zy.hk.timxx.cn/api/uploads/logo/site_logo.png" />
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
|
||||
<meta http-equiv="Pragma" content="no-cache" />
|
||||
<meta http-equiv="Expires" content="0" />
|
||||
<link rel="canonical" href="https://zy.hk.timxx.cn/" />
|
||||
<title>CloudSearch - 网盘资源搜索</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<script>
|
||||
(function() {
|
||||
fetch('/api/site-config').then(function(r){return r.json()}).then(function(cfg){
|
||||
if(cfg.site_name) document.title = cfg.site_name + ' - 网盘资源搜索';
|
||||
}).catch(function(){});
|
||||
function setMeta(selector, attr, value) {
|
||||
var el = document.querySelector(selector);
|
||||
if (!el) return;
|
||||
el.setAttribute(attr, value);
|
||||
}
|
||||
function sync(cfg) {
|
||||
var origin = window.location.origin;
|
||||
var siteName = cfg.site_name || 'CloudSearch';
|
||||
var title = siteName + ' - 网盘资源搜索';
|
||||
var description = cfg.site_disclaimer || '随便下 | 资源小站,提供网盘资源搜索、热门榜单和免责声明页面。';
|
||||
var image = cfg.site_logo ? new URL(cfg.site_logo, origin).href : origin + '/api/uploads/logo/site_logo.png';
|
||||
var canonical = origin + '/';
|
||||
document.title = title;
|
||||
setMeta('meta[name="description"]', 'content', description);
|
||||
setMeta('meta[name="robots"]', 'content', 'index,follow');
|
||||
setMeta('meta[property="og:title"]', 'content', title);
|
||||
setMeta('meta[property="og:description"]', 'content', description);
|
||||
setMeta('meta[property="og:url"]', 'content', canonical);
|
||||
setMeta('meta[property="og:image"]', 'content', image);
|
||||
setMeta('meta[name="twitter:title"]', 'content', title);
|
||||
setMeta('meta[name="twitter:description"]', 'content', description);
|
||||
setMeta('meta[name="twitter:image"]', 'content', image);
|
||||
setMeta('link[rel="canonical"]', 'href', canonical);
|
||||
}
|
||||
sync({});
|
||||
fetch('/api/site-config').then(function(r){return r.json()}).then(function(cfg){ sync(cfg || {}); }).catch(function(){});
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
@@ -20,4 +55,4 @@
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
+387
@@ -23,6 +23,7 @@
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.9",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
},
|
||||
@@ -869,6 +870,121 @@
|
||||
"vue": "^3.2.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
||||
"integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "2.1.9",
|
||||
"@vitest/utils": "2.1.9",
|
||||
"chai": "^5.1.2",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
|
||||
"integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/spy": "2.1.9",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"msw": "^2.4.9",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"msw": {
|
||||
"optional": true
|
||||
},
|
||||
"vite": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker/node_modules/estree-walker": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
|
||||
"integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
|
||||
"integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/utils": "2.1.9",
|
||||
"pathe": "^1.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
|
||||
"integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.1.9",
|
||||
"magic-string": "^0.30.12",
|
||||
"pathe": "^1.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
|
||||
"integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tinyspy": "^3.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
|
||||
"integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "2.1.9",
|
||||
"loupe": "^3.1.2",
|
||||
"tinyrainbow": "^1.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@volar/language-core": {
|
||||
"version": "2.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
|
||||
@@ -1086,6 +1202,15 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -1121,6 +1246,15 @@
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cac": {
|
||||
"version": "6.7.14",
|
||||
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
|
||||
"integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
@@ -1141,6 +1275,31 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
|
||||
"integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^2.0.1",
|
||||
"check-error": "^2.1.1",
|
||||
"deep-eql": "^5.0.1",
|
||||
"loupe": "^3.1.0",
|
||||
"pathval": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
|
||||
"integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@@ -1194,6 +1353,23 @@
|
||||
"integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
@@ -1202,6 +1378,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
|
||||
"integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -1294,6 +1479,12 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
|
||||
"integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
@@ -1362,6 +1553,15 @@
|
||||
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
|
||||
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
|
||||
},
|
||||
"node_modules/expect-type": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
|
||||
"integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
@@ -1568,6 +1768,12 @@
|
||||
"lodash-es": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
"integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/magic-string": {
|
||||
"version": "0.30.21",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||
@@ -1623,6 +1829,12 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/muggle-string": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
|
||||
@@ -1698,6 +1910,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
|
||||
"integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
|
||||
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1845,6 +2072,12 @@
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/siginfo": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -1853,6 +2086,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/stackback": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||
"integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
@@ -1877,6 +2122,45 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinyexec": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
|
||||
"integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyrainbow": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
|
||||
"integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tinyspy": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
|
||||
"integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz",
|
||||
@@ -1960,6 +2244,93 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
|
||||
"integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cac": "^6.7.14",
|
||||
"debug": "^4.3.7",
|
||||
"es-module-lexer": "^1.5.4",
|
||||
"pathe": "^1.1.2",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite-node": "vite-node.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
|
||||
"integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@vitest/expect": "2.1.9",
|
||||
"@vitest/mocker": "2.1.9",
|
||||
"@vitest/pretty-format": "^2.1.9",
|
||||
"@vitest/runner": "2.1.9",
|
||||
"@vitest/snapshot": "2.1.9",
|
||||
"@vitest/spy": "2.1.9",
|
||||
"@vitest/utils": "2.1.9",
|
||||
"chai": "^5.1.2",
|
||||
"debug": "^4.3.7",
|
||||
"expect-type": "^1.1.0",
|
||||
"magic-string": "^0.30.12",
|
||||
"pathe": "^1.1.2",
|
||||
"std-env": "^3.8.0",
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.1",
|
||||
"tinypool": "^1.0.1",
|
||||
"tinyrainbow": "^1.2.0",
|
||||
"vite": "^5.0.0",
|
||||
"vite-node": "2.1.9",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"vitest": "vitest.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/vitest"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"@vitest/browser": "2.1.9",
|
||||
"@vitest/ui": "2.1.9",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@edge-runtime/vm": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/browser": {
|
||||
"optional": true
|
||||
},
|
||||
"@vitest/ui": {
|
||||
"optional": true
|
||||
},
|
||||
"happy-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"jsdom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vscode-uri": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
|
||||
@@ -2060,6 +2431,22 @@
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="
|
||||
},
|
||||
"node_modules/why-is-node-running": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"siginfo": "^2.0.0",
|
||||
"stackback": "0.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"why-is-node-running": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"test": "vitest run",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -24,6 +25,7 @@
|
||||
"@vitejs/plugin-vue": "^5.1.0",
|
||||
"typescript": "^5.6.0",
|
||||
"vite": "^5.4.0",
|
||||
"vitest": "^2.1.9",
|
||||
"vue-tsc": "^2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Sitemap: https://zy.hk.timxx.cn/sitemap.xml
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/search</loc>
|
||||
<changefreq>daily</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/disclaimer/</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.4</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/admin/login</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.2</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://zy.hk.timxx.cn/user/login</loc>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.2</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -3,6 +3,49 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { applySeo, resolveSeo } from './seo'
|
||||
|
||||
interface SiteConfig {
|
||||
site_name?: string
|
||||
site_logo?: string
|
||||
site_disclaimer?: string
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
let currentSiteName = 'CloudSearch'
|
||||
let currentSiteLogo = ''
|
||||
let currentSiteDisclaimer = ''
|
||||
|
||||
function refreshSeo() {
|
||||
const origin = typeof window !== 'undefined' ? window.location.origin : ''
|
||||
applySeo(resolveSeo(route.path, route.query as Record<string, unknown>, currentSiteName, origin, currentSiteLogo, currentSiteDisclaimer))
|
||||
}
|
||||
|
||||
async function loadSiteConfig() {
|
||||
try {
|
||||
const resp = await fetch('/api/site-config')
|
||||
if (!resp.ok) return
|
||||
const cfg = (await resp.json()) as SiteConfig
|
||||
if (cfg.site_name) currentSiteName = cfg.site_name
|
||||
if (cfg.site_logo) currentSiteLogo = cfg.site_logo
|
||||
if (cfg.site_disclaimer) currentSiteDisclaimer = cfg.site_disclaimer
|
||||
} catch {
|
||||
// keep defaults
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await loadSiteConfig()
|
||||
refreshSeo()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
() => refreshSeo(),
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -383,6 +383,38 @@ export async function changePassword(
|
||||
return data
|
||||
}
|
||||
|
||||
// ===== 推广平台管理 =====
|
||||
export interface PromotionPlatform {
|
||||
id: number
|
||||
name: string
|
||||
join_url: string
|
||||
sort_order: number
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
export async function getPromotionPlatforms(): Promise<PromotionPlatform[]> {
|
||||
const { data } = await axios.get('/api/promotion-platforms')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getAdminPromotionPlatforms(): Promise<PromotionPlatform[]> {
|
||||
const { data } = await api.get('/admin/promotion-platforms')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createPromotionPlatform(params: { name: string; join_url: string; sort_order?: number }): Promise<PromotionPlatform> {
|
||||
const { data } = await api.post('/admin/promotion-platforms', params)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updatePromotionPlatform(id: number, params: { name: string; join_url: string; sort_order?: number }): Promise<void> {
|
||||
await api.put(`/admin/promotion-platforms/${id}`, params)
|
||||
}
|
||||
|
||||
export async function deletePromotionPlatform(id: number): Promise<void> {
|
||||
await api.delete(`/admin/promotion-platforms/${id}`)
|
||||
}
|
||||
|
||||
export default api
|
||||
export { query as searchQuery }
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<main class="disclaimer-page">
|
||||
<section class="disclaimer-card">
|
||||
<h1>免责声明</h1>
|
||||
<p>CloudSearch 仅聚合和展示公开网络资源线索,不存储、不上传、不分发任何网盘文件内容。</p>
|
||||
<p>搜索结果来自第三方公开页面或接口,资源有效性、合法性与安全性需由用户自行判断。</p>
|
||||
<p>请勿使用本站搜索、保存或传播侵权、违法、违规内容。如权利人认为相关信息侵犯权益,请联系站点管理员处理。</p>
|
||||
<router-link class="back-link" to="/">返回首页</router-link>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disclaimer-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 16px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.disclaimer-card {
|
||||
max-width: 760px;
|
||||
padding: 32px;
|
||||
border-radius: 18px;
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.08);
|
||||
color: #334155;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.disclaimer-card h1 {
|
||||
margin: 0 0 20px;
|
||||
color: #0f172a;
|
||||
}
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
margin-top: 20px;
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div class="home-page">
|
||||
<div class="hero-section">
|
||||
<h1 class="sr-only">网盘资源搜索</h1>
|
||||
<template v-if="configLoaded">
|
||||
<img v-if="siteLogo" :src="siteLogo" :alt="siteName || 'CloudSearch'" class="logo-img" @error="(e: any) => { (e.target as HTMLElement).style.display='none'; siteLogo='' }" />
|
||||
<div v-else class="logo-text">{{ siteName || 'CloudSearch' }}</div>
|
||||
</template>
|
||||
<div class="search-box">
|
||||
<el-input v-model="query" placeholder="搜索网盘资源..." size="large" clearable @keyup.enter="handleSearch">
|
||||
<el-input v-model="query" aria-label="搜索网盘资源" name="q" placeholder="搜索网盘资源..." size="large" clearable @keyup.enter="handleSearch">
|
||||
<template #prefix><el-icon><Search /></el-icon></template>
|
||||
</el-input>
|
||||
<el-button type="primary" size="large" @click="handleSearch" class="search-btn">搜索</el-button>
|
||||
@@ -27,6 +28,7 @@
|
||||
<div class="footer-inner">{{ siteDisclaimer }}</div>
|
||||
<el-button class="footer-btn" size="small" @click="openDisclaimer">免责声明</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,6 +37,7 @@ import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { getCategorizedRankings, getSiteConfig } from '../api'
|
||||
import { createDisclaimerTarget, createSearchPath } from '../utils/home-helpers'
|
||||
|
||||
const router = useRouter()
|
||||
const query = ref('')
|
||||
@@ -50,9 +53,9 @@ const QS = ['学而时习之,不亦说乎。', '温故而知新,可以为师
|
||||
const CS = ['#e74c3c', '#e67e22', '#f39c12', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50', '#16a085', '#d35400', '#c0392b', '#1abc9c', '#3498db', '#9b59b6', '#34495e', '#e91e63', '#009688', '#ff5722', '#795548', '#607d8b', '#673ab7']
|
||||
const SS = [48,42,38,34,30,28,26,24,22,20,18,17,16,15,14,13,12]
|
||||
|
||||
function handleSearch() { const q = query.value.trim(); if(q) router.push('/search?q='+encodeURIComponent(q)) }
|
||||
function searchTag(t:string) { router.push('/search?q='+encodeURIComponent(t)) }
|
||||
function openDisclaimer() { window.open('/disclaimer/', '_blank') }
|
||||
function handleSearch() { const path = createSearchPath(query.value); if(path) router.push(path) }
|
||||
function searchTag(t:string) { const path = createSearchPath(t); if(path) router.push(path) }
|
||||
function openDisclaimer() { window.location.assign(createDisclaimerTarget()) }
|
||||
|
||||
onMounted(async () => {
|
||||
currentQuote.value = QS[Math.floor(Math.random()*QS.length)]
|
||||
@@ -81,6 +84,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
.home-page{min-height:100vh;background:#f5f7fa}
|
||||
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
||||
.hero-section{text-align:center;padding:56px 24px 36px;background:#f5f7fa}
|
||||
.logo-text{font-size:48px;font-weight:700;color:#1d2129;margin-bottom:24px}
|
||||
.logo-img{display:block;max-width:400px;max-height:100px;margin:0 auto 24px}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<main class="not-found-page">
|
||||
<section class="not-found-card">
|
||||
<p class="eyebrow">404</p>
|
||||
<h1>页面不存在</h1>
|
||||
<p class="description">你访问的页面不存在或已被移动。</p>
|
||||
<el-button type="primary" @click="goHome">返回首页</el-button>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
function goHome() {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.not-found-page{min-height:100vh;display:flex;align-items:center;justify-content:center;background:#f5f7fa;padding:24px}
|
||||
.not-found-card{width:min(460px,100%);padding:40px 32px;text-align:center;background:#fff;border-radius:18px;box-shadow:0 12px 32px rgba(31,45,61,.08)}
|
||||
.eyebrow{margin:0 0 8px;color:#409eff;font-size:14px;font-weight:700;letter-spacing:4px}
|
||||
h1{margin:0 0 12px;font-size:28px;color:#1d2129}
|
||||
.description{margin:0 0 24px;color:#606266;line-height:1.7}
|
||||
</style>
|
||||
@@ -307,7 +307,6 @@ async function switchTrendDays(d: number) {
|
||||
}
|
||||
|
||||
// Menu selection
|
||||
const activeMenu = ref('dashboard')
|
||||
const activeSystemSection = ref('')
|
||||
|
||||
// ── Recent saves ──
|
||||
@@ -381,6 +380,7 @@ const sysSectionTitles: Record<string, string> = {
|
||||
'sys-validation': '链接验证配置',
|
||||
'sys-filter': '搜索标题过滤规则',
|
||||
'sys-password': '修改管理员密码',
|
||||
'sys-platforms': '推广平台管理',
|
||||
}
|
||||
|
||||
const pageTitle = computed(() => {
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
<el-menu-item index="sys-password">🔑 修改密码</el-menu-item>
|
||||
<el-menu-item index="sys-notify">📬 消息推送</el-menu-item>
|
||||
<el-menu-item index="sys-daily-report">📊 每日汇报</el-menu-item>
|
||||
<el-menu-item index="sys-platforms">👥 推广平台管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<el-menu-item index="save-records">
|
||||
@@ -102,6 +103,7 @@ const pageTitles: Record<string, string> = {
|
||||
'sys-password': '修改管理员密码',
|
||||
'sys-notify': '消息推送',
|
||||
'sys-daily-report': '每日汇报',
|
||||
'sys-platforms': '推广平台管理',
|
||||
'save-records': '转存日志',
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@
|
||||
</el-form-item>
|
||||
<el-form-item label="白名单目录">
|
||||
<div style="width: 100%;">
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 6px;" v-if="whitelistDirs.length">
|
||||
<el-tag v-for="(dir, i) in whitelistDirs" :key="i" closable size="small" @close="removeWhitelistDir(i)">{{ dir }}</el-tag>
|
||||
</div>
|
||||
<div style="display: flex; gap: 6px;">
|
||||
<el-input v-model="newWhitelistDir" placeholder="输入目录名" size="small" style="width: 160px" @keyup.enter="addWhitelistDir" />
|
||||
<el-button type="primary" size="small" @click="addWhitelistDir">添加</el-button>
|
||||
</div>
|
||||
<div style="display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px;" v-if="whitelistDirs.length">
|
||||
<el-tag v-for="(dir, i) in whitelistDirs" :key="i" closable size="small" @close="removeWhitelistDir(i)">{{ dir }}</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<el-table-column label="转存启用" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
:model-value="row.is_transfer_enabled !== 0"
|
||||
:model-value="row.is_active !== 0"
|
||||
size="small"
|
||||
@change="(val: boolean) => handleToggleTransfer(row, val)"
|
||||
/>
|
||||
@@ -105,7 +105,7 @@
|
||||
<template #default="{ row }">
|
||||
<el-switch
|
||||
:model-value="row.is_primary === 1"
|
||||
:disabled="!row.is_transfer_enabled"
|
||||
:disabled="!row.is_active"
|
||||
size="small"
|
||||
@change="(val: boolean) => handleTogglePrimary(row, val)"
|
||||
/>
|
||||
@@ -386,11 +386,10 @@ async function handleToggleTransfer(row: CloudConfig, enabled: boolean) {
|
||||
cloud_type: row.cloud_type,
|
||||
nickname: row.nickname || '',
|
||||
promotion_account: row.promotion_account || '',
|
||||
is_transfer_enabled: newVal,
|
||||
is_active: row.is_active,
|
||||
is_active: newVal,
|
||||
cookie: undefined, // don't send cookie on toggle-only
|
||||
})
|
||||
row.is_transfer_enabled = newVal
|
||||
row.is_active = newVal
|
||||
ElMessage.success(enabled ? '转存已开启' : '转存已关闭')
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '操作失败')
|
||||
@@ -455,7 +454,7 @@ function openDialog(row: CloudConfig | null) {
|
||||
form.cloud_type = row.cloud_type
|
||||
form.nickname = row.nickname || ''
|
||||
form.promotion_account = row.promotion_account || ''
|
||||
form.is_transfer_enabled = row.is_transfer_enabled !== 0
|
||||
form.is_transfer_enabled = row.is_active !== 0
|
||||
form.cookie = row.cookie || ''
|
||||
form._verifying = false
|
||||
form._storageUsed = ''
|
||||
@@ -506,9 +505,8 @@ async function handleSave() {
|
||||
cloud_type: form.cloud_type as CloudType,
|
||||
nickname: form.nickname,
|
||||
promotion_account: form.promotion_account,
|
||||
is_transfer_enabled: form.is_transfer_enabled ? 1 : 0,
|
||||
is_active: form.is_transfer_enabled ? 1 : 0,
|
||||
cookie: form.cookie || undefined,
|
||||
is_active: 1,
|
||||
})
|
||||
ElMessage.success('配置更新成功')
|
||||
} else {
|
||||
@@ -516,9 +514,8 @@ async function handleSave() {
|
||||
cloud_type: form.cloud_type as CloudType,
|
||||
nickname: form.nickname,
|
||||
promotion_account: form.promotion_account,
|
||||
is_transfer_enabled: form.is_transfer_enabled ? 1 : 0,
|
||||
is_active: form.is_transfer_enabled ? 1 : 0,
|
||||
cookie: form.cookie,
|
||||
is_active: 1,
|
||||
})
|
||||
ElMessage.success('配置保存成功')
|
||||
}
|
||||
|
||||
@@ -754,6 +754,57 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
|
||||
<!-- 👥 推广平台管理 -->
|
||||
<el-card id="section-sys-platforms" v-show="!activeSection || activeSection === 'sys-platforms'">
|
||||
<template #header>
|
||||
<span>👥 推广平台管理</span>
|
||||
</template>
|
||||
<div class="form-tip" style="margin-bottom: 12px;">配置注册页面可选的推广平台,每个平台需提供邀请链接(将自动生成二维码供用户扫码加入)</div>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<label style="font-size: 13px; color: #606266; display: block; margin-bottom: 4px;">二维码标题</label>
|
||||
<el-input v-model="configs.promotion_qr_title" placeholder="扫码加入推广团队" style="max-width: 360px" />
|
||||
<div class="form-tip">注册页面二维码区域的标题文字,保存后生效</div>
|
||||
</div>
|
||||
<el-table :data="promoPlatforms" stripe size="small" empty-text="暂无推广平台">
|
||||
<el-table-column prop="name" label="平台名称" width="160" />
|
||||
<el-table-column prop="join_url" label="邀请链接" min-width="300" show-overflow-tooltip />
|
||||
<el-table-column prop="sort_order" label="排序" width="80" align="center" />
|
||||
<el-table-column label="操作" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" text type="primary" @click="editPromoPlatform(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该平台?" @confirm="deletePromoPlatform(row.id)">
|
||||
<template #reference>
|
||||
<el-button size="small" text type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div style="margin-top: 12px;">
|
||||
<el-button size="small" type="primary" @click="showPromoDialog = true; editingPromoId = null; promoForm.name = ''; promoForm.join_url = ''; promoForm.sort_order = 0">新增平台</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 推广平台弹窗 -->
|
||||
<el-dialog v-model="showPromoDialog" :title="editingPromoId ? '编辑推广平台' : '新增推广平台'" width="480px">
|
||||
<el-form :model="promoForm" label-width="100px">
|
||||
<el-form-item label="平台名称">
|
||||
<el-input v-model="promoForm.name" placeholder="如:蜂小推" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邀请链接">
|
||||
<el-input v-model="promoForm.join_url" placeholder="https://..." />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序">
|
||||
<el-input-number v-model="promoForm.sort_order" :min="0" :max="999" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showPromoDialog = false">取消</el-button>
|
||||
<el-button type="primary" :loading="promoSaving" @click="savePromoPlatform">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<div class="save-bar">
|
||||
<el-button type="primary" size="large" :loading="saving" @click="handleSave">
|
||||
@@ -768,7 +819,7 @@ import { ref, reactive, onMounted, computed, watch } from "vue"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { ElForm } from 'element-plus'
|
||||
import { getSystemConfigs, updateSystemConfigs, changePassword as changePasswordApi, uploadFallbackImage, uploadLogo, updateSetting, getDbStatus, testRedisConnection, testExternalService, testNotifyChannel, getAllNotifierProviders, getCloudConfigs } from "../../api"
|
||||
import { getSystemConfigs, updateSystemConfigs, changePassword as changePasswordApi, uploadFallbackImage, uploadLogo, updateSetting, getDbStatus, testRedisConnection, testExternalService, testNotifyChannel, getAllNotifierProviders, getCloudConfigs, getAdminPromotionPlatforms, createPromotionPlatform, updatePromotionPlatform, deletePromotionPlatform } from "../../api"
|
||||
import { Upload, Loading } from "@element-plus/icons-vue"
|
||||
|
||||
|
||||
@@ -814,6 +865,12 @@ const ipGeoTesting = ref(false)
|
||||
const pansouInfo = ref<any>(null)
|
||||
const pansouInfoLoading = ref(true)
|
||||
const pansouUpdating = ref(false)
|
||||
|
||||
const promoPlatforms = ref<any[]>([])
|
||||
const showPromoDialog = ref(false)
|
||||
const editingPromoId = ref<number | null>(null)
|
||||
const promoForm = reactive({ name: '', join_url: '', sort_order: 0 })
|
||||
const promoSaving = ref(false)
|
||||
const proxyEnabled = computed({
|
||||
get: () => String(configs.search_proxy_enabled) === 'true',
|
||||
set: (val: boolean) => { configs.search_proxy_enabled = val ? 'true' : 'false' },
|
||||
@@ -1263,6 +1320,7 @@ const passwordRules = {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
loadPromoPlatforms();
|
||||
try {
|
||||
rawConfigs.value = await getSystemConfigs()
|
||||
for (const cfg of rawConfigs.value) {
|
||||
@@ -1618,6 +1676,11 @@ async function handleSave() {
|
||||
key: cfg.key,
|
||||
value: String(configs[cfg.key] ?? cfg.value),
|
||||
}))
|
||||
// Always save promotion QR title
|
||||
if (configs.promotion_qr_title !== undefined) {
|
||||
const hasQrTitle = entries.some((e: any) => e.key === 'promotion_qr_title')
|
||||
if (!hasQrTitle) entries.push({ key: 'promotion_qr_title', value: String(configs.promotion_qr_title || '') })
|
||||
}
|
||||
await saveDailyReportConfig()
|
||||
// Add global_notify_config as JSON entry
|
||||
entries.push({
|
||||
@@ -1632,6 +1695,27 @@ async function handleSave() {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
async function loadPromoPlatforms() {
|
||||
try { promoPlatforms.value = await getAdminPromotionPlatforms() } catch { /* */ }
|
||||
}
|
||||
function editPromoPlatform(row: any) {
|
||||
editingPromoId.value = row.id; promoForm.name = row.name; promoForm.join_url = row.join_url; promoForm.sort_order = row.sort_order; showPromoDialog.value = true;
|
||||
}
|
||||
async function savePromoPlatform() {
|
||||
if (!promoForm.name || !promoForm.join_url) { ElMessage.warning("平台名称和邀请链接不能为空"); return; }
|
||||
promoSaving.value = true;
|
||||
try {
|
||||
if (editingPromoId.value) { await updatePromotionPlatform(editingPromoId.value, { ...promoForm }); ElMessage.success("更新成功"); }
|
||||
else { await createPromotionPlatform({ ...promoForm }); ElMessage.success("添加成功"); }
|
||||
showPromoDialog.value = false; editingPromoId.value = null; loadPromoPlatforms();
|
||||
} catch (e: any) { ElMessage.error(e.response?.data?.error || "保存失败"); }
|
||||
finally { promoSaving.value = false; }
|
||||
}
|
||||
async function deletePromoPlatform(id: number) {
|
||||
try { await deletePromotionPlatform(id); ElMessage.success("已删除"); loadPromoPlatforms(); }
|
||||
catch (e: any) { ElMessage.error(e.response?.data?.error || "删除失败"); }
|
||||
}
|
||||
|
||||
|
||||
async function handleChangePassword() {
|
||||
const valid = await passwordFormRef.value?.validate().catch(() => false)
|
||||
|
||||
@@ -0,0 +1,606 @@
|
||||
<template>
|
||||
<div class="user-layout">
|
||||
<!-- Header -->
|
||||
<div class="user-header">
|
||||
<span class="user-title">{{ siteName }} 用户中心</span>
|
||||
<div class="header-right">
|
||||
<span class="user-account">{{ account }}</span>
|
||||
<el-button size="small" @click="handleLogout">退出登录</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-body">
|
||||
<!-- Sidebar -->
|
||||
<div class="user-sidebar">
|
||||
<div
|
||||
v-for="item in menuItems"
|
||||
:key="item.key"
|
||||
:class="['sidebar-item', { active: activeMenu === item.key }]"
|
||||
@click="activeMenu = item.key"
|
||||
>
|
||||
<span>{{ item.icon }} {{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="user-content">
|
||||
<!-- 📋 转存日志 -->
|
||||
<div v-show="activeMenu === 'records'">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>📋 转存日志</span></template>
|
||||
<el-table :data="records" stripe empty-text="暂无转存记录" size="small">
|
||||
<el-table-column prop="source_title" label="资源名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="source_type" label="来源" width="80" />
|
||||
<el-table-column label="状态" width="80">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.status === 'success' ? 'success' : 'danger'" size="small">
|
||||
{{ row.status === 'success' ? '成功' : '失败' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="file_size" label="大小" width="90" />
|
||||
<el-table-column label="耗时" width="90">
|
||||
<template #default="{ row }">
|
||||
{{ row.duration_ms ? (row.duration_ms / 1000).toFixed(1) + 's' : '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" width="170">
|
||||
<template #default="{ row }">{{ row.created_at }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-if="recordsTotal > 0"
|
||||
v-model:current-page="recordsPage"
|
||||
:page-size="recordsPageSize"
|
||||
:total="recordsTotal"
|
||||
layout="prev, pager, next"
|
||||
small
|
||||
style="margin-top: 16px; justify-content: center;"
|
||||
@current-change="loadRecords"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 💾 网盘管理 — 复刻 admin CloudConfig 布局 -->
|
||||
<div v-show="activeMenu === 'drives'">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>💾 我的网盘</span></template>
|
||||
<div style="margin-bottom: 12px;">
|
||||
<el-button type="primary" size="small" @click="openAddDrive">新增配置</el-button>
|
||||
</div>
|
||||
<el-table :data="drives" stripe empty-text="暂无网盘配置" size="small">
|
||||
<el-table-column label="网盘类型" width="110">
|
||||
<template #default="{ row }">
|
||||
<CloudBadge :cloud_type="row.cloud_type" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="nickname" label="昵称" width="140">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.nickname">{{ row.nickname }}</span>
|
||||
<span v-else style="color:#909399;font-size:12px;">未设置</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="promotion_account" label="推广账号" width="160">
|
||||
<template #default="{ row }">
|
||||
<span v-if="row.promotion_account">{{ row.promotion_account }}</span>
|
||||
<span v-else style="color:#909399;font-size:12px;">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="验证" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.verification_status === 'valid'" type="success" size="small">有效</el-tag>
|
||||
<el-tag v-else-if="row.verification_status === 'invalid'" type="danger" size="small">无效</el-tag>
|
||||
<el-tag v-else type="info" size="small">未验证</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="空间" width="200">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.storage_total && row.storage_total !== '-'">
|
||||
<div>{{ row.storage_used || '计算中...' }} / {{ row.storage_total }}</div>
|
||||
</template>
|
||||
<span v-else style="color:#909399">—</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="转存数" width="80" align="center">
|
||||
<template #default="{ row }">{{ row.total_saves > 0 ? row.total_saves + '次' : '-' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button size="small" text type="primary" @click="openEditDrive(row)">编辑</el-button>
|
||||
<el-popconfirm title="确定删除该配置?" @confirm="handleDeleteDrive(row.id)">
|
||||
<template #reference>
|
||||
<el-button size="small" text type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 🔔 消息推送 — 复刻 admin SystemConfig notify 布局 -->
|
||||
<div v-show="activeMenu === 'notify'">
|
||||
<el-card shadow="never">
|
||||
<template #header><span>🔔 消息推送配置</span></template>
|
||||
<el-form label-width="120px" size="small">
|
||||
<el-form-item label="推送通道">
|
||||
<el-select v-model="notifyChannel" style="width: 200px" placeholder="选择推送通道">
|
||||
<el-option label="Feishu Bot" value="feishu" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<template v-if="notifyChannel === 'feishu'">
|
||||
<el-form-item label="Webhook 地址">
|
||||
<el-input v-model="notifyWebhook" placeholder="https://open.feishu.cn/open-apis/bot/v2/hook/xxx" style="width: 420px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="推送事件">
|
||||
<el-checkbox-group v-model="notifyEvents">
|
||||
<el-checkbox label="on_save_success">转存成功</el-checkbox>
|
||||
<el-checkbox label="on_save_fail">转存失败</el-checkbox>
|
||||
<el-checkbox label="on_cookie_expire">Cookie过期</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="savingNotify" @click="saveNotify">保存推送配置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑网盘弹窗 — 复刻 admin CloudConfig dialog -->
|
||||
<el-dialog v-model="showAddDrive" :title="editingDrive ? '编辑网盘配置' : '新增网盘配置'" width="560px">
|
||||
<el-form ref="driveFormRef" :model="driveForm" :rules="driveRules" label-width="120px">
|
||||
<!-- 白名单目录提示 -->
|
||||
<el-alert
|
||||
v-show="whitelistDirs.length > 0"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin-bottom: 18px;"
|
||||
>
|
||||
<template #title>
|
||||
<div style="line-height: 1.6;">
|
||||
<div>🧹 请在网盘主目录内创建:<b>{{ whitelistDirs.join('、') }}</b> 目录</div>
|
||||
<div>并将你的重要文件移至该目录,<b>只有这个目录不会被自动清理</b></div>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
<el-form-item label="网盘类型" prop="cloud_type">
|
||||
<el-select v-model="driveForm.cloud_type" style="width: 100%" :disabled="!!editingDrive" @change="onDriveTypeChange">
|
||||
<el-option
|
||||
v-for="ct in enabledCloudTypes"
|
||||
:key="ct.type"
|
||||
:label="ct.label"
|
||||
:value="ct.type"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Cookie" prop="cookie">
|
||||
<el-input
|
||||
v-model="driveForm.cookie"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
:placeholder="driveCookiePlaceholder"
|
||||
input-style="font-family: monospace; font-size: 12px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<!-- Cookie 获取教程(根据网盘类型切换) -->
|
||||
<el-form-item label=" " v-if="driveForm.cloud_type && driveForm.cloud_type !== ''" class="cookie-tips-item">
|
||||
<div class="cookie-tips" :class="`cookie-tips-${driveForm.cloud_type}`">
|
||||
<div class="cookie-tips-header">
|
||||
<span class="cookie-tips-title">📖 {{ driveCloudTypeLabel }} Cookie 获取教程</span>
|
||||
</div>
|
||||
<ol class="cookie-tips-steps" v-html="driveCookieTutorialHtml"></ol>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="showAddDrive = false; editingDrive = null">取消</el-button>
|
||||
<el-button type="primary" :loading="adding" @click="handleSaveDrive">保存</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
import { CLOUD_LABELS } from '../../types'
|
||||
import { getSystemConfigs } from '../../api'
|
||||
import type { CloudType } from '../../types'
|
||||
import CloudBadge from '../../components/CloudBadge.vue'
|
||||
import type { ElForm } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const account = ref('')
|
||||
const siteName = ref('CloudSearch')
|
||||
|
||||
/** 白名单目录提示 */
|
||||
const systemConfigs = ref<Record<string, string>>({})
|
||||
const whitelistDirs = computed(() => {
|
||||
try {
|
||||
const raw = systemConfigs.value.cleanup_whitelist_dirs || '[]'
|
||||
const arr = JSON.parse(raw)
|
||||
return Array.isArray(arr) && arr.length > 0 ? arr : []
|
||||
} catch { return [] }
|
||||
})
|
||||
|
||||
async function loadSystemConfigs() {
|
||||
try {
|
||||
const { data } = await axios.get('/api/system-configs')
|
||||
const map: Record<string, string> = {}
|
||||
for (const item of data) {
|
||||
map[item.key] = item.value
|
||||
}
|
||||
systemConfigs.value = map
|
||||
} catch (e) { console.error('加载系统配置失败', e) }
|
||||
}
|
||||
|
||||
function userApi() {
|
||||
const token = localStorage.getItem('user_token')
|
||||
return axios.create({
|
||||
baseURL: '/api/user',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const token = localStorage.getItem('user_token')
|
||||
if (!token) { router.push('/user/login'); return }
|
||||
try {
|
||||
const { data } = await userApi().get('/profile')
|
||||
account.value = data.account
|
||||
} catch {
|
||||
localStorage.removeItem('user_token')
|
||||
localStorage.removeItem('user_account')
|
||||
router.push('/user/login')
|
||||
return
|
||||
}
|
||||
// 获取网站名称
|
||||
try {
|
||||
const { data } = await axios.get('/api/site-config')
|
||||
if (data.site_name) siteName.value = data.site_name
|
||||
} catch { /* use default */ }
|
||||
loadSystemConfigs()
|
||||
})
|
||||
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('user_token')
|
||||
localStorage.removeItem('user_account')
|
||||
router.push('/user/login')
|
||||
}
|
||||
|
||||
// ── Menu ──
|
||||
const activeMenu = ref('records')
|
||||
const menuItems = [
|
||||
{ key: 'records', icon: '📋', label: '转存日志' },
|
||||
{ key: 'drives', icon: '💾', label: '网盘管理' },
|
||||
{ key: 'notify', icon: '🔔', label: '消息推送' },
|
||||
]
|
||||
|
||||
// ── 转存日志 ──
|
||||
const records = ref<any[]>([])
|
||||
const recordsTotal = ref(0)
|
||||
const recordsPage = ref(1)
|
||||
const recordsPageSize = ref(20)
|
||||
|
||||
async function loadRecords() {
|
||||
try {
|
||||
const { data } = await userApi().get('/save-records', { params: { page: recordsPage.value, pageSize: recordsPageSize.value } })
|
||||
records.value = data.records
|
||||
recordsTotal.value = data.total
|
||||
} catch { /* */ }
|
||||
}
|
||||
|
||||
// ── 网盘管理 ──
|
||||
const drives = ref<any[]>([])
|
||||
const showAddDrive = ref(false)
|
||||
const editingDrive = ref<any>(null)
|
||||
const adding = ref(false)
|
||||
const driveFormRef = ref<InstanceType<typeof ElForm>>()
|
||||
|
||||
interface EnabledCloudType { type: string; label: string; icon: string; enabled: boolean }
|
||||
const enabledCloudTypes = ref<EnabledCloudType[]>([])
|
||||
|
||||
const defaultDriveForm = () => ({
|
||||
cloud_type: '' as CloudType | '',
|
||||
cookie: '',
|
||||
})
|
||||
|
||||
const driveForm = reactive(defaultDriveForm())
|
||||
|
||||
const driveRules = computed(() => ({
|
||||
cloud_type: [{ required: true, message: '请选择网盘类型', trigger: 'change' }],
|
||||
}))
|
||||
|
||||
const driveCookiePlaceholder = computed(() => {
|
||||
if (!driveForm.cloud_type) return '请先选择网盘类型'
|
||||
const t = driveForm.cloud_type
|
||||
if (t === 'quark' || t === 'baidu') return `请输入 ${CLOUD_LABELS[t] || t} 的完整 Cookie`
|
||||
return editingDrive.value ? '留空则保持原有' : '输入完整 Cookie'
|
||||
})
|
||||
|
||||
const driveCloudTypeLabel = computed(() => {
|
||||
return CLOUD_LABELS[driveForm.cloud_type as CloudType] || driveForm.cloud_type || ''
|
||||
})
|
||||
|
||||
/** Cookie 获取教程 HTML(根据不同网盘类型) */
|
||||
const driveCookieTutorialHtml = computed(() => {
|
||||
const t = driveForm.cloud_type
|
||||
if (!t) return ''
|
||||
const tutorials: Record<string, string> = {
|
||||
quark: `<li>在电脑上打开 <a href="https://pan.quark.cn" target="_blank">pan.quark.cn</a> 并登录你的夸克账号</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → 切换到 <strong>网络 (Network)</strong> 选项卡</li>
|
||||
<li>刷新页面,在请求列表中点击任意一个请求(如 <code>account/info</code>)</li>
|
||||
<li>在右侧 <strong>请求头 (Request Headers)</strong> 中找到 <code>Cookie</code> 字段</li>
|
||||
<li>复制整个 Cookie 值(<b>从开头到结束的完整内容</b>),粘贴到上方输入框</li>
|
||||
<li>点击「<b>保存</b>」按钮提交配置</li>
|
||||
<div class="cookie-tips-note">⚠️ 必须包含 <code>__st=s%...</code> 字段!请复制浏览器请求头的 <b>整个 Cookie</b>(F12 → Network → 请求头 → Cookie 项),不要只复制部分。</div>`,
|
||||
|
||||
baidu: `<li>在电脑上打开 <a href="https://pan.baidu.com" target="_blank">pan.baidu.com</a> 并登录你的百度账号</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → 切换到 <strong>网络 (Network)</strong> 选项卡</li>
|
||||
<li>刷新页面,在请求列表中点击任意一个请求</li>
|
||||
<li>在右侧 <strong>请求头 (Request Headers)</strong> 中找到 <code>Cookie</code> 字段</li>
|
||||
<li>复制整个 Cookie 值,粘贴到上方输入框</li>
|
||||
<li>点击「<b>保存</b>」按钮提交配置</li>
|
||||
<div class="cookie-tips-note">💡 需要包含 <code>BDUSS</code> 和 <code>STOKEN</code></div>`,
|
||||
|
||||
aliyun: `<li>在电脑上打开 <a href="https://www.aliyundrive.com" target="_blank">aliyundrive.com</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>
|
||||
<div class="cookie-tips-note">💡 需包含 <code>token</code> 等有效字段</div>`,
|
||||
|
||||
'115': `<li>在电脑上打开 <a href="https://115.com" target="_blank">115.com</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>
|
||||
<div class="cookie-tips-note">💡 需包含 <code>UID</code>、<code>CID</code>、<code>SEID</code> 等字段</div>`,
|
||||
|
||||
tianyi: `<li>在电脑上打开 <a href="https://cloud.189.cn" target="_blank">cloud.189.cn</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>
|
||||
<div class="cookie-tips-note">💡 需包含 <code>COOKIE_LOGIN_USER</code>、<code>SESSION</code> 等字段</div>`,
|
||||
|
||||
'123pan': `<li>在电脑上打开 <a href="https://www.123pan.com" target="_blank">123pan.com</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>`,
|
||||
|
||||
uc: `<li>在电脑上打开 <a href="https://drive.uc.cn" target="_blank">drive.uc.cn</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>`,
|
||||
|
||||
xunlei: `<li>在电脑上打开 <a href="https://pan.xunlei.com" target="_blank">pan.xunlei.com</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>`,
|
||||
|
||||
pikpak: `<li>在电脑上打开 <a href="https://www.mypikpak.com" target="_blank">mypikpak.com</a> 并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,找到任意请求 → 复制 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>`,
|
||||
}
|
||||
return tutorials[t] || `<li>在电脑上打开该网盘网站并登录</li>
|
||||
<li>按 <code>F12</code> 打开开发者工具 → <strong>网络 (Network)</strong></li>
|
||||
<li>刷新页面,复制任意请求的 <code>Cookie</code></li>
|
||||
<li>粘贴到上方输入框,点击「保存」提交</li>`
|
||||
})
|
||||
|
||||
async function loadEnabledCloudTypes() {
|
||||
try {
|
||||
const { data } = await userApi().get('/enabled-cloud-types')
|
||||
enabledCloudTypes.value = data
|
||||
} catch { enabledCloudTypes.value = [] }
|
||||
}
|
||||
|
||||
async function loadDrives() {
|
||||
try {
|
||||
const { data } = await userApi().get('/cloud-configs')
|
||||
drives.value = data
|
||||
} catch { /* */ }
|
||||
}
|
||||
|
||||
function openAddDrive() {
|
||||
editingDrive.value = null
|
||||
Object.assign(driveForm, defaultDriveForm())
|
||||
showAddDrive.value = true
|
||||
}
|
||||
|
||||
function openEditDrive(row: any) {
|
||||
editingDrive.value = row
|
||||
driveForm.cloud_type = row.cloud_type
|
||||
driveForm.cookie = ''
|
||||
showAddDrive.value = true
|
||||
}
|
||||
|
||||
function onDriveTypeChange() {
|
||||
// placeholder auto-updates via computed
|
||||
}
|
||||
|
||||
async function handleSaveDrive() {
|
||||
const valid = await driveFormRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
adding.value = true
|
||||
try {
|
||||
let verifyNickname = ''
|
||||
let verifyStorageUsed = ''
|
||||
let verifyStorageTotal = ''
|
||||
// 如果有 Cookie,先验证并自动获取昵称和空间信息
|
||||
if (driveForm.cookie) {
|
||||
try {
|
||||
const api = userApi()
|
||||
const { data: verifyResult } = await api.post(`/cloud-configs/${driveForm.cloud_type}/test`, { cookie: driveForm.cookie })
|
||||
if (!verifyResult.success) {
|
||||
ElMessage.error(`Cookie验证失败:${verifyResult.message}`)
|
||||
adding.value = false
|
||||
return
|
||||
}
|
||||
// 自动获取网盘昵称和空间信息
|
||||
if (verifyResult.nickname) verifyNickname = verifyResult.nickname
|
||||
if (verifyResult.storage_used) verifyStorageUsed = verifyResult.storage_used
|
||||
if (verifyResult.storage_total) verifyStorageTotal = verifyResult.storage_total
|
||||
} catch (e: any) {
|
||||
ElMessage.error(`Cookie验证失败:${e.response?.data?.error || '网络错误'}`)
|
||||
adding.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const promotionAccount = account.value // 自动使用当前用户的「推广平台-手机号」
|
||||
if (editingDrive.value) {
|
||||
await userApi().put(`/cloud-configs/${editingDrive.value.id}`, {
|
||||
cloud_type: driveForm.cloud_type,
|
||||
nickname: verifyNickname || undefined,
|
||||
promotion_account: promotionAccount,
|
||||
cookie: driveForm.cookie || undefined,
|
||||
storage_used: verifyStorageUsed || undefined,
|
||||
storage_total: verifyStorageTotal || undefined,
|
||||
})
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
if (!driveForm.cookie) { ElMessage.warning('请填写Cookie'); adding.value = false; return }
|
||||
await userApi().post('/cloud-configs', {
|
||||
cloud_type: driveForm.cloud_type,
|
||||
cookie: driveForm.cookie,
|
||||
nickname: verifyNickname || undefined,
|
||||
promotion_account: promotionAccount,
|
||||
storage_used: verifyStorageUsed || undefined,
|
||||
storage_total: verifyStorageTotal || undefined,
|
||||
})
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
showAddDrive.value = false
|
||||
editingDrive.value = null
|
||||
loadDrives()
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '保存失败')
|
||||
} finally { adding.value = false }
|
||||
}
|
||||
|
||||
async function handleDeleteDrive(id: number) {
|
||||
try {
|
||||
await userApi().delete(`/cloud-configs/${id}`)
|
||||
ElMessage.success('已删除')
|
||||
loadDrives()
|
||||
} catch (e: any) { ElMessage.error(e.response?.data?.error || '删除失败') }
|
||||
}
|
||||
|
||||
// ── 推送配置 ──
|
||||
const notifyChannel = ref('')
|
||||
const notifyWebhook = ref('')
|
||||
const notifyEvents = ref<string[]>([])
|
||||
const savingNotify = ref(false)
|
||||
|
||||
async function loadNotify() {
|
||||
try {
|
||||
const { data } = await userApi().get('/notify-config')
|
||||
const cfg = JSON.parse(data.notifyConfig || '{}')
|
||||
if (cfg.webhook) {
|
||||
notifyChannel.value = 'feishu'
|
||||
notifyWebhook.value = cfg.webhook
|
||||
notifyEvents.value = cfg.events || []
|
||||
}
|
||||
} catch { /* */ }
|
||||
}
|
||||
|
||||
async function saveNotify() {
|
||||
savingNotify.value = true
|
||||
try {
|
||||
const config = notifyChannel.value === 'feishu'
|
||||
? JSON.stringify({ webhook: notifyWebhook.value, events: notifyEvents.value })
|
||||
: '{}'
|
||||
await userApi().put('/notify-config', { notifyConfig: config })
|
||||
ElMessage.success('推送配置已保存')
|
||||
} catch (e: any) { ElMessage.error(e.response?.data?.error || '保存失败') }
|
||||
finally { savingNotify.value = false }
|
||||
}
|
||||
|
||||
watch(activeMenu, (tab) => {
|
||||
if (tab === 'records') loadRecords()
|
||||
else if (tab === 'drives') { loadEnabledCloudTypes(); loadDrives() }
|
||||
else if (tab === 'notify') loadNotify()
|
||||
}, { immediate: true })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-layout { min-height: 100vh; background: #f0f2f5; }
|
||||
.user-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 0 24px; height: 56px; background: #fff; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||
}
|
||||
.user-title { font-size: 16px; font-weight: 600; color: #303133; }
|
||||
.header-right { display: flex; align-items: center; gap: 12px; }
|
||||
.user-account { font-size: 13px; color: #606266; }
|
||||
.user-body { display: flex; min-height: calc(100vh - 56px); }
|
||||
.user-sidebar {
|
||||
width: 180px; background: #fff; border-right: 1px solid #e4e7ed;
|
||||
padding: 12px 0; flex-shrink: 0;
|
||||
}
|
||||
.sidebar-item {
|
||||
padding: 10px 20px; cursor: pointer; font-size: 14px; color: #606266;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.sidebar-item:hover { background: #f5f7fa; color: #303133; }
|
||||
.sidebar-item.active { background: #ecf5ff; color: #409eff; font-weight: 500; border-right: 2px solid #409eff; }
|
||||
.user-content { flex: 1; padding: 20px; overflow: auto; }
|
||||
|
||||
/* ── Cookie tutorial card (same as admin CloudConfig) ── */
|
||||
.cookie-tips-item :deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
.cookie-tips {
|
||||
background: #f8faff;
|
||||
border: 1px solid #e8f0fe;
|
||||
border-radius: 6px;
|
||||
padding: 14px 16px;
|
||||
font-size: 12px;
|
||||
line-height: 1.8;
|
||||
color: #606266;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.cookie-tips-header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.cookie-tips-title {
|
||||
font-weight: 700;
|
||||
color: #409eff;
|
||||
font-size: 13px;
|
||||
}
|
||||
.cookie-tips-steps {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.cookie-tips-steps li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.cookie-tips-steps code {
|
||||
background: #ecf5ff;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||
}
|
||||
.cookie-tips-note {
|
||||
margin-top: 8px;
|
||||
padding: 6px 10px;
|
||||
background: #fffbe6;
|
||||
border: 1px solid #fff3c4;
|
||||
border-radius: 4px;
|
||||
color: #8a6d3b;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cookie-tips-note code {
|
||||
background: #f5f0e0;
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="user-login-page">
|
||||
<div class="login-card">
|
||||
<h1 class="login-title">CloudSearch 用户中心</h1>
|
||||
<p class="login-subtitle">{{ isLogin ? '登录您的账号' : '注册新账号' }}</p>
|
||||
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="0" @keyup.enter="handleSubmit">
|
||||
<el-form-item prop="platform">
|
||||
<el-select v-model="form.platform" placeholder="选择推广平台" size="large" style="width: 100%">
|
||||
<el-option
|
||||
v-for="p in platforms"
|
||||
:key="p.name"
|
||||
:label="p.name"
|
||||
:value="p.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="手机号码" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="form.password" type="password" placeholder="密码" size="large" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="!isLogin" prop="confirmPassword">
|
||||
<el-input v-model="form.confirmPassword" type="password" placeholder="确认密码" size="large" show-password />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" size="large" :loading="loading" style="width: 100%" @click="handleSubmit">
|
||||
{{ isLogin ? '登 录' : '注 册' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="login-switch">
|
||||
{{ isLogin ? '还没有账号?' : '已有账号?' }}
|
||||
<el-button link type="primary" @click="isLogin = !isLogin">
|
||||
{{ isLogin ? '立即注册' : '去登录' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div v-if="platforms.length > 0" class="qr-section">
|
||||
<div class="qr-title">{{ qrTitle }}</div>
|
||||
<div class="qr-grid">
|
||||
<div v-for="p in platforms" :key="p.name" class="qr-item">
|
||||
<div class="qr-label">加入【{{ p.name }}】团队</div>
|
||||
<div class="qr-img-wrap">
|
||||
<img
|
||||
:src="'https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=' + encodeURIComponent(p.join_url)"
|
||||
:alt="'加入' + p.name"
|
||||
class="qr-image"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
|
||||
const router = useRouter()
|
||||
const isLogin = ref(true)
|
||||
const loading = ref(false)
|
||||
const formRef = ref()
|
||||
|
||||
interface Platform {
|
||||
id: number
|
||||
name: string
|
||||
join_url: string
|
||||
sort_order: number
|
||||
}
|
||||
const platforms = ref<Platform[]>([])
|
||||
const qrTitle = ref('扫码加入推广团队')
|
||||
|
||||
const form = reactive({
|
||||
platform: '',
|
||||
phone: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
})
|
||||
|
||||
const validatePhone = (_rule: any, value: string, callback: any) => {
|
||||
if (!value) callback(new Error('请输入手机号码'))
|
||||
else if (!/^1[3-9]\d{9}$/.test(value)) callback(new Error('请输入有效的手机号码'))
|
||||
else callback()
|
||||
}
|
||||
|
||||
const rules = {
|
||||
platform: [{ required: true, message: '请选择推广平台', trigger: 'change' }],
|
||||
phone: [{ validator: validatePhone, trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }, { min: 6, message: '密码至少6位', trigger: 'blur' }],
|
||||
confirmPassword: [{
|
||||
validator: (_rule: any, value: string, callback: any) => {
|
||||
if (!value) callback(new Error('请确认密码'))
|
||||
else if (value !== form.password) callback(new Error('两次密码不一致'))
|
||||
else callback()
|
||||
}, trigger: 'blur'
|
||||
}],
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { data } = await axios.get('/api/promotion-platforms')
|
||||
platforms.value = data.platforms
|
||||
if (data.title) qrTitle.value = data.title
|
||||
} catch { /* */ }
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
const valid = await formRef.value?.validate().catch(() => false)
|
||||
if (!valid) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
if (isLogin.value) {
|
||||
const account = form.platform + '-' + form.phone.trim()
|
||||
const { data } = await axios.post('/api/user/login', {
|
||||
account,
|
||||
password: form.password,
|
||||
})
|
||||
localStorage.setItem('user_token', data.token)
|
||||
localStorage.setItem('user_account', account)
|
||||
ElMessage.success('登录成功')
|
||||
router.push('/user/dashboard')
|
||||
} else {
|
||||
const { data } = await axios.post('/api/user/register', {
|
||||
platform: form.platform,
|
||||
phone: form.phone.trim(),
|
||||
password: form.password,
|
||||
})
|
||||
ElMessage.success(data.message || '注册成功,请登录')
|
||||
isLogin.value = true
|
||||
form.password = ''
|
||||
form.confirmPassword = ''
|
||||
}
|
||||
} catch (e: any) {
|
||||
ElMessage.error(e.response?.data?.error || '操作失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.user-login-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f0f2f5;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.login-card {
|
||||
width: 420px;
|
||||
padding: 40px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
.login-title { text-align: center; font-size: 24px; margin: 0 0 8px; color: #303133; }
|
||||
.login-subtitle { text-align: center; font-size: 14px; color: #909399; margin: 0 0 32px; }
|
||||
.login-switch { text-align: center; font-size: 14px; color: #909399; margin-top: 16px; }
|
||||
|
||||
.qr-section {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
max-width: 900px;
|
||||
}
|
||||
.qr-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.qr-grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.qr-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.qr-label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
.qr-img-wrap {
|
||||
padding: 12px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 6px rgba(0,0,0,0.08);
|
||||
}
|
||||
.qr-image {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createMemoryHistory, createWebHistory } from 'vue-router'
|
||||
|
||||
const routes = [
|
||||
export const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
@@ -16,6 +16,12 @@ const routes = [
|
||||
name: 'result-detail',
|
||||
component: () => import('./pages/ResultDetail.vue'),
|
||||
},
|
||||
{
|
||||
path: '/disclaimer',
|
||||
alias: '/disclaimer/',
|
||||
name: 'disclaimer',
|
||||
component: () => import('./pages/Disclaimer.vue'),
|
||||
},
|
||||
{
|
||||
path: '/admin/login',
|
||||
name: 'admin-login',
|
||||
@@ -57,10 +63,29 @@ const routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/user/login',
|
||||
name: 'user-login',
|
||||
component: () => import('./pages/user/UserLogin.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user/dashboard',
|
||||
name: 'user-dashboard',
|
||||
component: () => import('./pages/user/UserDashboard.vue'),
|
||||
},
|
||||
{
|
||||
path: '/user',
|
||||
redirect: '/user/login',
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'not-found',
|
||||
component: () => import('./pages/NotFound.vue'),
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: typeof window === 'undefined' ? createMemoryHistory() : createWebHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { resolveSeo } from './seo'
|
||||
|
||||
describe('resolveSeo', () => {
|
||||
it('builds home page seo for the site root', () => {
|
||||
const seo = resolveSeo('/', {}, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.title).toBe('随便下 | 资源小站 - 网盘资源搜索')
|
||||
expect(seo.description).toContain('网盘资源搜索')
|
||||
expect(seo.canonical).toBe('https://zy.hk.timxx.cn/')
|
||||
expect(seo.robots).toBe('index,follow')
|
||||
})
|
||||
|
||||
it('builds search page seo from the q parameter', () => {
|
||||
const seo = resolveSeo('/search', { q: '阿凡达' }, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.title).toBe('阿凡达 - 搜索结果 | 随便下 | 资源小站')
|
||||
expect(seo.description).toContain('阿凡达')
|
||||
expect(seo.canonical).toBe('https://zy.hk.timxx.cn/search?q=%E9%98%BF%E5%87%A1%E8%BE%BE')
|
||||
})
|
||||
|
||||
it('marks admin pages as noindex', () => {
|
||||
const seo = resolveSeo('/admin/login', {}, '随便下 | 资源小站', 'https://zy.hk.timxx.cn')
|
||||
expect(seo.robots).toBe('noindex,nofollow')
|
||||
expect(seo.title).toContain('管理后台')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,125 @@
|
||||
export interface SeoState {
|
||||
title: string;
|
||||
description: string;
|
||||
canonical: string;
|
||||
image: string;
|
||||
robots: string;
|
||||
}
|
||||
|
||||
function getSearchQuery(query: Record<string, unknown>): string {
|
||||
const q = query.q;
|
||||
if (typeof q === 'string') return q.trim();
|
||||
const input = query.input;
|
||||
if (typeof input === 'string') return input.trim();
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizePath(pathname: string): string {
|
||||
if (!pathname) return '/';
|
||||
if (!pathname.startsWith('/')) return `/${pathname}`;
|
||||
return pathname;
|
||||
}
|
||||
|
||||
function buildCanonical(origin: string, pathname: string, query: Record<string, unknown>): string {
|
||||
const base = origin ? origin.replace(/\/+$/, '') : '';
|
||||
const normalizedPath = normalizePath(pathname);
|
||||
const params = new URLSearchParams();
|
||||
if (normalizedPath.startsWith('/search')) {
|
||||
const q = getSearchQuery(query);
|
||||
if (q) params.set('q', q);
|
||||
}
|
||||
const suffix = params.toString() ? `?${params.toString()}` : '';
|
||||
return `${base}${normalizedPath}${suffix}` || normalizedPath;
|
||||
}
|
||||
|
||||
export function resolveSeo(
|
||||
pathname: string,
|
||||
query: Record<string, unknown> = {},
|
||||
siteName = 'CloudSearch',
|
||||
origin = '',
|
||||
siteLogo = '',
|
||||
siteDisclaimer = '',
|
||||
): SeoState {
|
||||
const normalizedPath = normalizePath(pathname);
|
||||
const q = getSearchQuery(query);
|
||||
const titleSuffix = `${siteName} - 网盘资源搜索`;
|
||||
let title = titleSuffix;
|
||||
let description = `${siteName} 提供网盘资源搜索、热门榜单和内容聚合。`;
|
||||
let robots = 'index,follow';
|
||||
|
||||
if (normalizedPath.startsWith('/search')) {
|
||||
if (q) {
|
||||
title = `${q} - 搜索结果 | ${siteName}`;
|
||||
description = `搜索 ${q} 的网盘资源结果,查看链接、分类和保存记录。`;
|
||||
} else {
|
||||
title = `搜索结果 | ${siteName}`;
|
||||
description = `${siteName} 的网盘资源搜索结果页。`;
|
||||
}
|
||||
} else if (normalizedPath.startsWith('/result/')) {
|
||||
title = `资源详情 | ${siteName}`;
|
||||
description = `查看资源详情、链接信息和关联内容。`;
|
||||
} else if (normalizedPath.startsWith('/disclaimer')) {
|
||||
title = `免责声明 | ${siteName}`;
|
||||
description = siteDisclaimer || `查看 ${siteName} 的免责声明和使用说明。`;
|
||||
} else if (normalizedPath.startsWith('/admin')) {
|
||||
title = `管理后台 | ${siteName}`;
|
||||
description = `访问 ${siteName} 的管理后台。`;
|
||||
robots = 'noindex,nofollow';
|
||||
} else if (normalizedPath.startsWith('/user')) {
|
||||
title = `用户中心 | ${siteName}`;
|
||||
description = `访问 ${siteName} 的用户中心。`;
|
||||
robots = 'noindex,nofollow';
|
||||
}
|
||||
|
||||
const canonical = buildCanonical(origin, normalizedPath, query);
|
||||
const image = siteLogo
|
||||
? new URL(siteLogo, origin || 'https://zy.hk.timxx.cn').href
|
||||
: `${origin ? origin.replace(/\/+$/, '') : ''}/api/uploads/logo/site_logo.png` || '/api/uploads/logo/site_logo.png';
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
canonical,
|
||||
image,
|
||||
robots,
|
||||
};
|
||||
}
|
||||
|
||||
function setMeta(selector: string, attr: string, value: string): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
let el = document.querySelector(selector) as HTMLMetaElement | HTMLLinkElement | null;
|
||||
if (!el) {
|
||||
el = document.createElement(selector.startsWith('link') ? 'link' : 'meta');
|
||||
if (selector.startsWith('meta[name="')) {
|
||||
const name = selector.match(/meta\[name="([^"]+)"\]/)?.[1];
|
||||
if (name) (el as HTMLMetaElement).setAttribute('name', name);
|
||||
}
|
||||
if (selector.startsWith('meta[property="')) {
|
||||
const property = selector.match(/meta\[property="([^"]+)"\]/)?.[1];
|
||||
if (property) (el as HTMLMetaElement).setAttribute('property', property);
|
||||
}
|
||||
if (selector.startsWith('link[')) {
|
||||
const rel = selector.match(/link\[rel="([^"]+)"\]/)?.[1];
|
||||
if (rel) (el as HTMLLinkElement).setAttribute('rel', rel);
|
||||
}
|
||||
document.head.appendChild(el);
|
||||
}
|
||||
el.setAttribute(attr, value);
|
||||
}
|
||||
|
||||
export function applySeo(seo: SeoState): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
document.title = seo.title;
|
||||
setMeta('meta[name="description"]', 'content', seo.description);
|
||||
setMeta('meta[name="robots"]', 'content', seo.robots);
|
||||
setMeta('meta[property="og:title"]', 'content', seo.title);
|
||||
setMeta('meta[property="og:description"]', 'content', seo.description);
|
||||
setMeta('meta[property="og:type"]', 'content', 'website');
|
||||
setMeta('meta[property="og:url"]', 'content', seo.canonical);
|
||||
setMeta('meta[property="og:image"]', 'content', seo.image);
|
||||
setMeta('meta[name="twitter:card"]', 'content', 'summary_large_image');
|
||||
setMeta('meta[name="twitter:title"]', 'content', seo.title);
|
||||
setMeta('meta[name="twitter:description"]', 'content', seo.description);
|
||||
setMeta('meta[name="twitter:image"]', 'content', seo.image);
|
||||
setMeta('link[rel="canonical"]', 'href', seo.canonical);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createDisclaimerTarget, createSearchPath, getHealthDisplay } from './home-helpers';
|
||||
|
||||
describe('home helpers', () => {
|
||||
it('builds an encoded search path from trimmed user input', () => {
|
||||
expect(createSearchPath(' 周杰伦 演唱会 ')).toBe('/search?q=%E5%91%A8%E6%9D%B0%E4%BC%A6%20%E6%BC%94%E5%94%B1%E4%BC%9A');
|
||||
expect(createSearchPath(' ')).toBeNull();
|
||||
});
|
||||
|
||||
it('uses same-tab navigation for the full disclaimer page', () => {
|
||||
expect(createDisclaimerTarget()).toBe('/disclaimer');
|
||||
});
|
||||
|
||||
it('marks optional video parser gaps as a test-environment note, not a core outage', () => {
|
||||
const display = getHealthDisplay('ok', {
|
||||
videoParser: 'unreachable',
|
||||
videoParserRequired: false,
|
||||
});
|
||||
|
||||
expect(display.isCoreHealthy).toBe(true);
|
||||
expect(display.label).toBe('核心服务正常');
|
||||
expect(display.note).toContain('视频解析');
|
||||
expect(display.note).toContain('测试环境');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
export interface HealthDisplayComponents {
|
||||
videoParser?: string;
|
||||
videoParserRequired?: boolean;
|
||||
}
|
||||
|
||||
export interface HealthDisplay {
|
||||
label: string;
|
||||
isCoreHealthy: boolean;
|
||||
note: string;
|
||||
}
|
||||
|
||||
export function createSearchPath(query: string): string | null {
|
||||
const trimmed = query.trim();
|
||||
if (!trimmed) return null;
|
||||
return `/search?q=${encodeURIComponent(trimmed)}`;
|
||||
}
|
||||
|
||||
export function createDisclaimerTarget(path = '/disclaimer'): string {
|
||||
return path;
|
||||
}
|
||||
|
||||
export function getHealthDisplay(status: string, components: HealthDisplayComponents = {}): HealthDisplay {
|
||||
const isCoreHealthy = status === 'ok' || (!components.videoParserRequired && status === 'degraded');
|
||||
const label = isCoreHealthy ? '核心服务正常' : status === 'degraded' ? '部分服务降级' : '服务异常';
|
||||
|
||||
const videoParserOptionalGap = !components.videoParserRequired
|
||||
&& components.videoParser
|
||||
&& components.videoParser !== 'ok';
|
||||
|
||||
return {
|
||||
label,
|
||||
isCoreHealthy,
|
||||
note: videoParserOptionalGap ? '视频解析仍在开发中,当前测试环境未启用,不影响核心搜索服务。' : '',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { createRouter, createMemoryHistory } from 'vue-router';
|
||||
import { routes } from '../router';
|
||||
|
||||
describe('application routes', () => {
|
||||
function makeRouter() {
|
||||
return createRouter({ history: createMemoryHistory(), routes });
|
||||
}
|
||||
|
||||
it('resolves public and admin routes before the catch-all not-found route', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/').name).toBe('home');
|
||||
expect(router.resolve('/search?q=test').name).toBe('search');
|
||||
expect(router.resolve('/result/abc123').name).toBe('result-detail');
|
||||
expect(router.resolve('/admin/login').name).toBe('admin-login');
|
||||
expect(router.resolve('/admin/dashboard').matched.some(route => route.name === 'admin-dashboard')).toBe(true);
|
||||
});
|
||||
|
||||
it('has an explicit disclaimer route instead of relying on the not-found fallback', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/disclaimer').name).toBe('disclaimer');
|
||||
expect(router.resolve('/disclaimer/').name).toBe('disclaimer');
|
||||
});
|
||||
|
||||
it('resolves unknown frontend routes to the not-found page', () => {
|
||||
const router = makeRouter();
|
||||
|
||||
expect(router.resolve('/nonexistent-ui-audit').name).toBe('not-found');
|
||||
expect(router.resolve('/unknown/path').name).toBe('not-found');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
const KNOWN_PUBLIC_ROUTE_PATTERNS = [
|
||||
/^\/$/,
|
||||
/^\/search\/?$/,
|
||||
/^\/result\/[^/]+\/?$/,
|
||||
/^\/admin\/login\/?$/,
|
||||
/^\/user\/login\/?$/,
|
||||
/^\/user\/dashboard\/?$/,
|
||||
/^\/user\/?$/,
|
||||
];
|
||||
|
||||
export function isKnownPublicRoute(path: string): boolean {
|
||||
const cleanPath = path.split('?')[0].split('#')[0] || '/';
|
||||
return KNOWN_PUBLIC_ROUTE_PATTERNS.some(pattern => pattern.test(cleanPath));
|
||||
}
|
||||
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
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
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as _,a as o,t as m,e,w as t,g as p,q as h,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-WUL9v6H_.js";import{a as G}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),w={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},b=h(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=h(()=>w[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),_("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),_("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),_("span",P,"☁️"))]),o("div",Q,[o("h2",null,m(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[13]||(s[13]=o("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+m(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[14]||(s[14]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,m(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-66b4feae"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-Cez7RKLo.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-BEhBl6RD.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-CBbTXmzV.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-BNH_lgW0.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as _,a as o,t as m,e,w as t,g as p,q as h,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-CBf4dz4k.js";import{a as G}from"./index-DXp3oW3R.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),w={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},b=h(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=h(()=>w[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),_("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),_("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),_("span",P,"☁️"))]),o("div",Q,[o("h2",null,m(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[13]||(s[13]=o("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+m(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[14]||(s[14]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,m(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ls=z(ts,[["__scopeId","data-v-66b4feae"]]);export{ls as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-DG5Z6wYC.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-C7z77r4S.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-xijwxLU3.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-CRy8Zkz5.js";import{a as G}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
.admin-layout[data-v-6f75751a]{display:flex;height:100vh;background:var(--bg-page)}.admin-sidebar[data-v-6f75751a]{width:var(--sidebar-w);flex-shrink:0;display:flex;flex-direction:column;background:linear-gradient(180deg,#111827,#1e293b);position:relative;z-index:10}.sidebar-brand[data-v-6f75751a]{display:flex;align-items:center;gap:10px;padding:20px 20px 16px;border-bottom:1px solid rgba(255,255,255,.06)}.sidebar-logo[data-v-6f75751a]{font-size:28px;line-height:1;flex-shrink:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center}.sidebar-logo-img[data-v-6f75751a]{width:36px;height:36px;object-fit:contain;border-radius:6px}.sidebar-logo-fallback[data-v-6f75751a]{font-size:28px}.sidebar-brand-text h2[data-v-6f75751a]{font-size:16px;font-weight:700;margin:0;color:#fff;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sidebar-brand-text p[data-v-6f75751a]{font-size:11px;margin:2px 0 0;color:#ffffff73;letter-spacing:1px}.sidebar-menu[data-v-6f75751a]{flex:1;display:flex;flex-direction:column;background:transparent!important;border-right:none!important;padding:4px 0}.sidebar-menu[data-v-6f75751a] .el-menu-item,.sidebar-menu[data-v-6f75751a] .el-sub-menu__title{color:#ffffffa6;height:44px;line-height:44px;transition:all .2s ease;margin:0 6px;border-radius:var(--radius-sm)}.sidebar-menu[data-v-6f75751a] .el-menu-item:hover,.sidebar-menu[data-v-6f75751a] .el-sub-menu__title:hover{background:#ffffff14;color:#ffffffe6}.sidebar-menu[data-v-6f75751a] .el-menu-item.is-active{color:#fff;background:linear-gradient(90deg,#409eff40,#6366f126);font-weight:500}.sidebar-menu[data-v-6f75751a] .el-menu-item:after{display:none}.sidebar-menu[data-v-6f75751a] .el-sub-menu .el-menu{background:#0003!important}.sidebar-menu[data-v-6f75751a] .el-sub-menu .el-menu .el-menu-item{padding-left:52px!important;font-size:13px;height:38px;line-height:38px}.sidebar-menu[data-v-6f75751a] .el-icon{font-size:16px}.sidebar-spacer[data-v-6f75751a]{flex:1}.sidebar-version[data-v-6f75751a]{text-align:center;font-size:11px;color:#ffffff40;padding:8px 0;letter-spacing:.5px}.admin-content[data-v-6f75751a]{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}.content-header[data-v-6f75751a]{display:flex;align-items:center;justify-content:space-between;padding:16px 28px;background:var(--bg-card);border-bottom:1px solid var(--border-light);flex-shrink:0}.content-breadcrumb[data-v-6f75751a]{display:flex;align-items:center;gap:8px}.breadcrumb-current[data-v-6f75751a]{font-size:18px;font-weight:700;color:var(--text)}.content-actions[data-v-6f75751a] .el-button{color:var(--text-secondary);gap:4px}.content-body[data-v-6f75751a]{flex:1;overflow-y:auto;padding:24px 28px}.content-body[data-v-6f75751a] .save-bar{position:sticky;bottom:0;z-index:100;background:var(--bg-card);padding:12px 16px;border-radius:var(--radius-lg) var(--radius-lg) 0 0;border:1px solid var(--border);border-bottom:none;box-shadow:0 -2px 12px #00000014;display:flex;justify-content:flex-end;gap:10px;margin-top:24px}
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as _,a as o,t as m,e,w as t,g as p,q as h,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-C_73XAbP.js";import{a as G}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),w={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},b=h(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=h(()=>w[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),_("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),_("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),_("span",P,"☁️"))]),o("div",Q,[o("h2",null,m(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[13]||(s[13]=o("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+m(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[14]||(s[14]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,m(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-66b4feae"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as _,a as o,t as m,e,w as t,g as p,q as h,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-BKVIEIby.js";import{a as G}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),w={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},b=h(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=h(()=>w[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),_("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),_("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),_("span",P,"☁️"))]),o("div",Q,[o("h2",null,m(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[13]||(s[13]=o("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+m(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[14]||(s[14]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,m(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-66b4feae"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-Bl_rtZjE.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as m,a as o,t as _,e,w as t,g as p,q as w,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-DUvV9NL6.js";import{a as G}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),h={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","sys-platforms":"推广平台管理","save-records":"转存日志"},b=w(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=w(()=>h[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),m("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),m("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),m("span",P,"☁️"))]),o("div",Q,[o("h2",null,_(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1}),e(a,{index:"sys-platforms"},{default:t(()=>[...s[13]||(s[13]=[l("👥 推广平台管理",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[14]||(s[14]=o("span",null,"转存日志",-1))]),_:1}),s[16]||(s[16]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+_(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[15]||(s[15]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,_(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[17]||(s[17]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-6f75751a"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as I,o as L,c as _,a as o,t as m,e,w as t,g as p,q as h,h as u,i as g,j as r,C as M,k as l,D as T,G as j,H as A,I as D,J as E,u as H,y as R,_ as z}from"./index-C2VdJ-SC.js";import{a as G}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const J={class:"admin-layout"},W={class:"admin-sidebar"},F={class:"sidebar-brand"},K={class:"sidebar-logo"},O=["src","alt"],P={key:1,class:"sidebar-logo-fallback"},Q={class:"sidebar-brand-text"},U={class:"sidebar-version"},X={class:"admin-content"},Y={class:"content-header"},Z={class:"content-breadcrumb"},$={class:"breadcrumb-current"},ss={class:"content-actions"},es={class:"content-body"},ts=I({__name:"AdminLayout",setup(os){const d=H(),v=R(),f=p(""),c=p(""),y=p(""),w={dashboard:"仪表盘","cloud-configs-toggle":"网盘设置及授权","cloud-configs-cleanup":"存储清理","sys-site":"网站设置","sys-services":"外部服务 & 缓存","sys-strategy":"性能配置","sys-password":"修改管理员密码","sys-notify":"消息推送","sys-daily-report":"每日汇报","save-records":"转存日志"},b=h(()=>{const n=v.name;return n==="admin-cloud-configs"?"cloud-configs-toggle":n==="admin-cleanup"?"cloud-configs-cleanup":n==="admin-system"?v.query.section||"sys-site":n==="admin-save-records"?"save-records":"dashboard"}),k=h(()=>w[b.value]||"仪表盘");function C(n){n==="dashboard"?d.push("/admin/dashboard"):n==="cloud-configs-toggle"?d.push("/admin/cloud-configs"):n==="cloud-configs-cleanup"?d.push("/admin/cleanup"):n.startsWith("sys-")?d.push({path:"/admin/system",query:{section:n}}):n==="save-records"?d.push("/admin/save-records"):n==="logout"&&(localStorage.removeItem("admin_token"),d.push("/admin/login"))}function S(){d.push("/")}return L(async()=>{try{const n=await G();f.value=n.site_name||"",c.value=n.site_logo||""}catch{}try{const s=await(await fetch("/health")).json();y.value=s.version}catch{}}),(n,s)=>{const i=u("el-icon"),a=u("el-menu-item"),x=u("el-sub-menu"),B=u("el-menu"),N=u("el-button"),V=u("router-view");return g(),_("div",J,[o("aside",W,[o("div",F,[o("div",K,[c.value?(g(),_("img",{key:0,src:c.value,alt:f.value||"CloudSearch",class:"sidebar-logo-img",onError:s[0]||(s[0]=q=>{q.target.style.display="none",c.value=""})},null,40,O)):(g(),_("span",P,"☁️"))]),o("div",Q,[o("h2",null,m(f.value||"CloudSearch"),1),s[1]||(s[1]=o("p",null,"管理控制台",-1))])]),e(B,{"default-active":b.value,class:"sidebar-menu",onSelect:C},{default:t(()=>[e(a,{index:"dashboard"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(M))]),_:1}),s[2]||(s[2]=o("span",null,"仪表盘",-1))]),_:1}),e(x,{index:"cloud-configs"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(T))]),_:1}),s[3]||(s[3]=o("span",null,"网盘管理",-1))]),default:t(()=>[e(a,{index:"cloud-configs-toggle"},{default:t(()=>[...s[4]||(s[4]=[l("📋 设置及授权",-1)])]),_:1}),e(a,{index:"cloud-configs-cleanup"},{default:t(()=>[...s[5]||(s[5]=[l("🧹 存储清理",-1)])]),_:1})]),_:1}),e(x,{index:"system"},{title:t(()=>[e(i,null,{default:t(()=>[e(r(j))]),_:1}),s[6]||(s[6]=o("span",null,"系统设置",-1))]),default:t(()=>[e(a,{index:"sys-site"},{default:t(()=>[...s[7]||(s[7]=[l("🌐 网站设置",-1)])]),_:1}),e(a,{index:"sys-services"},{default:t(()=>[...s[8]||(s[8]=[l("🔗 外部服务 & 缓存",-1)])]),_:1}),e(a,{index:"sys-strategy"},{default:t(()=>[...s[9]||(s[9]=[l("⚡ 性能配置",-1)])]),_:1}),e(a,{index:"sys-password"},{default:t(()=>[...s[10]||(s[10]=[l("🔑 修改密码",-1)])]),_:1}),e(a,{index:"sys-notify"},{default:t(()=>[...s[11]||(s[11]=[l("📬 消息推送",-1)])]),_:1}),e(a,{index:"sys-daily-report"},{default:t(()=>[...s[12]||(s[12]=[l("📊 每日汇报",-1)])]),_:1})]),_:1}),e(a,{index:"save-records"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(A))]),_:1}),s[13]||(s[13]=o("span",null,"转存日志",-1))]),_:1}),s[15]||(s[15]=o("div",{class:"sidebar-spacer"},null,-1)),o("div",U,"v"+m(y.value),1),e(a,{index:"logout"},{default:t(()=>[e(i,null,{default:t(()=>[e(r(D))]),_:1}),s[14]||(s[14]=o("span",null,"退出登录",-1))]),_:1})]),_:1},8,["default-active"])]),o("div",X,[o("header",Y,[o("div",Z,[o("span",$,m(k.value),1)]),o("div",ss,[e(N,{text:"",size:"small",onClick:S},{default:t(()=>[e(i,null,{default:t(()=>[e(r(E))]),_:1}),s[16]||(s[16]=l(" 返回前台 ",-1))]),_:1})])]),o("main",es,[e(V)])])])}}}),ds=z(ts,[["__scopeId","data-v-66b4feae"]]);export{ds as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-C7z77r4S.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-DUvV9NL6.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-Bl_rtZjE.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-C_73XAbP.js";import{a as M,f as U}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-DG5Z6wYC.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-BEhBl6RD.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-CBbTXmzV.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-C2VdJ-SC.js";import{a as M,f as U}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-BKVIEIby.js";import{a as M,f as U}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-Cez7RKLo.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-BNH_lgW0.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-WUL9v6H_.js";import{a as M,f as U}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-CRy8Zkz5.js";import{a as M,f as U}from"./index-BC_nIdvI.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-CBf4dz4k.js";import{a as M,f as U}from"./index-DXp3oW3R.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),G=E(z,[["__scopeId","data-v-bd0b6672"]]);export{G as default};
|
||||
@@ -0,0 +1 @@
|
||||
import{d as k,o as C,c as w,a,t as m,e as t,w as i,f as x,b as L,g as d,h as p,i as y,k as N,B as S,E as B,_ as E}from"./index-xijwxLU3.js";import{a as M,f as U}from"./index-iQrvdwZD.js";import"./index-DZ_Kha3d.js";const q={class:"admin-login-page"},A={class:"login-card"},I={class:"login-brand"},K={class:"login-title"},R={key:0,class:"error-msg"},j={class:"login-footer"},z=k({__name:"AdminLogin",setup(D){const _=d(),u=d(!1),c=d(""),g=d(""),v=d("");M().then(l=>{l.site_name&&(g.value=l.site_name)}).catch(()=>{});const s=S({username:"",password:""}),b={username:[{required:!0,message:"请输入用户名",trigger:"blur"}],password:[{required:!0,message:"请输入密码",trigger:"blur"}]};async function h(){var e,r,n;if(await((e=_.value)==null?void 0:e.validate().catch(()=>!1))){u.value=!0,c.value="";try{const o=await U(s.username,s.password);localStorage.setItem("admin_token",o.token),B.success("登录成功"),window.location.href="/admin"}catch(o){c.value=((n=(r=o==null?void 0:o.response)==null?void 0:r.data)==null?void 0:n.message)||(o==null?void 0:o.message)||"登录失败"}finally{u.value=!1}}}return C(async()=>{try{const e=await(await fetch("/health")).json();v.value=e.version||""}catch{}}),(l,e)=>{const r=p("el-input"),n=p("el-form-item"),o=p("el-button"),V=p("el-form");return y(),w("div",q,[e[4]||(e[4]=a("div",{class:"login-bg-pattern"},null,-1)),a("div",A,[a("div",I,[e[2]||(e[2]=a("div",{class:"login-logo"},"☁️",-1)),a("h1",K,m(g.value||"CloudSearch"),1),e[3]||(e[3]=a("p",{class:"login-subtitle"},"管理后台",-1))]),t(V,{ref_key:"formRef",ref:_,model:s,rules:b,"label-width":"0",size:"large",onKeyup:x(h,["enter"])},{default:i(()=>[t(n,{prop:"username"},{default:i(()=>[t(r,{modelValue:s.username,"onUpdate:modelValue":e[0]||(e[0]=f=>s.username=f),placeholder:"用户名","prefix-icon":"User"},null,8,["modelValue"])]),_:1}),t(n,{prop:"password"},{default:i(()=>[t(r,{modelValue:s.password,"onUpdate:modelValue":e[1]||(e[1]=f=>s.password=f),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":""},null,8,["modelValue"])]),_:1}),t(n,null,{default:i(()=>[t(o,{type:"primary",loading:u.value,class:"login-btn",onClick:h},{default:i(()=>[N(m(u.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1})]),_:1},8,["model"]),c.value?(y(),w("p",R,m(c.value),1)):L("",!0),a("p",j,"CloudSearch v"+m(v.value),1)])])}}}),H=E(z,[["__scopeId","data-v-bd0b6672"]]);export{H as default};
|
||||
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
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user