mirror of
https://github.com/jiangrui1994/CloudSaver.git
synced 2026-01-10 23:28:46 +08:00
Merge remote-tracking branch 'origin/dev'
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
logs/
|
||||||
dist/
|
dist/
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
@@ -15,12 +15,14 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
|
"inversify": "^7.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"sequelize": "^6.37.5",
|
"sequelize": "^6.37.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"tunnel": "^0.0.6"
|
"tunnel": "^0.0.6",
|
||||||
|
"winston": "^3.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
|
|||||||
@@ -1,76 +1,57 @@
|
|||||||
// filepath: /d:/code/CloudDiskDown/backend/src/app.ts
|
// filepath: /d:/code/CloudDiskDown/backend/src/app.ts
|
||||||
import "./types/express";
|
import "./types/express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import cors from "cors";
|
import { container } from "./inversify.config";
|
||||||
import cookieParser from "cookie-parser";
|
import { TYPES } from "./core/types";
|
||||||
|
import { DatabaseService } from "./services/DatabaseService";
|
||||||
|
import { setupMiddlewares } from "./middleware";
|
||||||
import routes from "./routes/api";
|
import routes from "./routes/api";
|
||||||
|
import { logger } from "./utils/logger";
|
||||||
import { errorHandler } from "./middleware/errorHandler";
|
import { errorHandler } from "./middleware/errorHandler";
|
||||||
import sequelize from "./config/database";
|
class App {
|
||||||
import { authMiddleware } from "./middleware/auth";
|
private app = express();
|
||||||
import GlobalSetting from "./models/GlobalSetting";
|
private databaseService = container.get<DatabaseService>(TYPES.DatabaseService);
|
||||||
import Searcher from "./services/Searcher";
|
|
||||||
|
|
||||||
const app = express();
|
constructor() {
|
||||||
|
this.setupExpress();
|
||||||
app.use(
|
|
||||||
cors({
|
|
||||||
origin: "*",
|
|
||||||
credentials: true,
|
|
||||||
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
||||||
allowedHeaders: ["Content-Type", "Authorization", "Cookie"],
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
app.use(cookieParser());
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
// 应用 token 验证中间件,排除登录和注册接口
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
if (
|
|
||||||
req.path === "/user/login" ||
|
|
||||||
req.path === "/user/register" ||
|
|
||||||
req.path.includes("tele-images")
|
|
||||||
) {
|
|
||||||
return next();
|
|
||||||
}
|
}
|
||||||
authMiddleware(req, res, next);
|
|
||||||
|
private setupExpress(): void {
|
||||||
|
// 设置中间件
|
||||||
|
setupMiddlewares(this.app);
|
||||||
|
|
||||||
|
// 设置路由
|
||||||
|
this.app.use("/", routes);
|
||||||
|
this.app.use(errorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
try {
|
||||||
|
// 初始化数据库
|
||||||
|
await this.databaseService.initialize();
|
||||||
|
logger.info("数据库初始化成功");
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
const port = process.env.PORT || 8009;
|
||||||
|
this.app.listen(port, () => {
|
||||||
|
logger.info(`
|
||||||
|
🚀 服务器启动成功
|
||||||
|
🌍 监听端口: ${port}
|
||||||
|
🔧 运行环境: ${process.env.NODE_ENV || "development"}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("服务器启动失败:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并启动应用
|
||||||
|
const application = new App();
|
||||||
|
application.start().catch((error) => {
|
||||||
|
logger.error("应用程序启动失败:", error);
|
||||||
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use("/", routes);
|
export default application;
|
||||||
|
|
||||||
const initializeGlobalSettings = async (): Promise<void> => {
|
|
||||||
const settings = await GlobalSetting.findOne();
|
|
||||||
if (!settings) {
|
|
||||||
await GlobalSetting.create({
|
|
||||||
httpProxyHost: "127.0.0.1",
|
|
||||||
httpProxyPort: 7890,
|
|
||||||
isProxyEnabled: true,
|
|
||||||
CommonUserCode: 9527,
|
|
||||||
AdminUserCode: 230713,
|
|
||||||
});
|
|
||||||
console.log("Global settings initialized with default values.");
|
|
||||||
}
|
|
||||||
await Searcher.updateAxiosInstance();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 错误处理
|
|
||||||
app.use(errorHandler);
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8009;
|
|
||||||
|
|
||||||
// 在同步前禁用外键约束,同步后重新启用
|
|
||||||
sequelize
|
|
||||||
.query("PRAGMA foreign_keys = OFF") // 禁用外键
|
|
||||||
.then(() => sequelize.sync({ alter: true }))
|
|
||||||
.then(() => sequelize.query("PRAGMA foreign_keys = ON")) // 重新启用外键
|
|
||||||
.then(() => {
|
|
||||||
app.listen(PORT, async () => {
|
|
||||||
await initializeGlobalSettings();
|
|
||||||
console.log(`Server is running on port ${PORT}`);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Database sync failed:", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
export default app;
|
|
||||||
|
|||||||
@@ -25,6 +25,18 @@ interface Config {
|
|||||||
channels: Channel[];
|
channels: Channel[];
|
||||||
};
|
};
|
||||||
cloudPatterns: CloudPatterns;
|
cloudPatterns: CloudPatterns;
|
||||||
|
app: {
|
||||||
|
port: number;
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
database: {
|
||||||
|
type: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
jwt: {
|
||||||
|
secret: string;
|
||||||
|
expiresIn: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从环境变量读取频道配置
|
// 从环境变量读取频道配置
|
||||||
@@ -56,13 +68,24 @@ const getTeleChannels = (): Channel[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
|
app: {
|
||||||
|
port: parseInt(process.env.PORT || "8009"),
|
||||||
|
env: process.env.NODE_ENV || "development",
|
||||||
|
},
|
||||||
|
database: {
|
||||||
|
type: "sqlite",
|
||||||
|
path: "./data/database.sqlite",
|
||||||
|
},
|
||||||
|
jwt: {
|
||||||
|
secret: process.env.JWT_SECRET || "your-secret-key",
|
||||||
|
expiresIn: "6h",
|
||||||
|
},
|
||||||
jwtSecret: process.env.JWT_SECRET || "uV7Y$k92#LkF^q1b!",
|
jwtSecret: process.env.JWT_SECRET || "uV7Y$k92#LkF^q1b!",
|
||||||
|
|
||||||
telegram: {
|
telegram: {
|
||||||
baseUrl: process.env.TELEGRAM_BASE_URL || "https://t.me/s",
|
baseUrl: process.env.TELEGRAM_BASE_URL || "https://t.me/s",
|
||||||
channels: getTeleChannels(),
|
channels: getTeleChannels(),
|
||||||
},
|
},
|
||||||
|
|
||||||
cloudPatterns: {
|
cloudPatterns: {
|
||||||
baiduPan: /https?:\/\/(?:pan|yun)\.baidu\.com\/[^\s<>"]+/g,
|
baiduPan: /https?:\/\/(?:pan|yun)\.baidu\.com\/[^\s<>"]+/g,
|
||||||
tianyi: /https?:\/\/cloud\.189\.cn\/[^\s<>"]+/g,
|
tianyi: /https?:\/\/cloud\.189\.cn\/[^\s<>"]+/g,
|
||||||
@@ -70,6 +93,7 @@ export const config: Config = {
|
|||||||
// pan115有两个域名 115.com 和 anxia.com 和 115cdn.com
|
// pan115有两个域名 115.com 和 anxia.com 和 115cdn.com
|
||||||
pan115: /https?:\/\/(?:115|anxia|115cdn)\.com\/s\/[^\s<>"]+/g,
|
pan115: /https?:\/\/(?:115|anxia|115cdn)\.com\/s\/[^\s<>"]+/g,
|
||||||
// 修改为匹配所有以123开头的域名
|
// 修改为匹配所有以123开头的域名
|
||||||
|
// eslint-disable-next-line no-useless-escape
|
||||||
pan123: /https?:\/\/(?:www\.)?123[^\/\s<>"]+\.com\/s\/[^\s<>"]+/g,
|
pan123: /https?:\/\/(?:www\.)?123[^\/\s<>"]+\.com\/s\/[^\s<>"]+/g,
|
||||||
quark: /https?:\/\/pan\.quark\.cn\/[^\s<>"]+/g,
|
quark: /https?:\/\/pan\.quark\.cn\/[^\s<>"]+/g,
|
||||||
yidong: /https?:\/\/caiyun\.139\.com\/[^\s<>"]+/g,
|
yidong: /https?:\/\/caiyun\.139\.com\/[^\s<>"]+/g,
|
||||||
|
|||||||
32
backend/src/controllers/BaseCloudController.ts
Normal file
32
backend/src/controllers/BaseCloudController.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
import { ICloudStorageService } from "@/types/services";
|
||||||
|
|
||||||
|
export abstract class BaseCloudController extends BaseController {
|
||||||
|
constructor(protected cloudService: ICloudStorageService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getShareInfo(req: Request, res: Response): Promise<void> {
|
||||||
|
await this.handleRequest(req, res, async () => {
|
||||||
|
const { shareCode, receiveCode } = req.query;
|
||||||
|
// await this.cloudService.setCookie(req);
|
||||||
|
return await this.cloudService.getShareInfo(shareCode as string, receiveCode as string);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFolderList(req: Request, res: Response): Promise<void> {
|
||||||
|
await this.handleRequest(req, res, async () => {
|
||||||
|
const { parentCid } = req.query;
|
||||||
|
await this.cloudService.setCookie(req);
|
||||||
|
return await this.cloudService.getFolderList(parentCid as string);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveFile(req: Request, res: Response): Promise<void> {
|
||||||
|
await this.handleRequest(req, res, async () => {
|
||||||
|
await this.cloudService.setCookie(req);
|
||||||
|
return await this.cloudService.saveSharedFile(req.body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
24
backend/src/controllers/BaseController.ts
Normal file
24
backend/src/controllers/BaseController.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { ApiResponse } from "../core/ApiResponse";
|
||||||
|
interface ApiResponseData<T> {
|
||||||
|
data?: T;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class BaseController {
|
||||||
|
protected async handleRequest<T>(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
action: () => Promise<ApiResponseData<T> | void>
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const result = await action();
|
||||||
|
if (result) {
|
||||||
|
res.json(ApiResponse.success(result.data, result.message));
|
||||||
|
}
|
||||||
|
} catch (error: unknown) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "未知错误";
|
||||||
|
res.status(200).json(ApiResponse.error(errorMessage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +1,11 @@
|
|||||||
import { Request, Response } from "express";
|
|
||||||
import { Cloud115Service } from "../services/Cloud115Service";
|
import { Cloud115Service } from "../services/Cloud115Service";
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
import { injectable, inject } from "inversify";
|
||||||
import UserSetting from "../models/UserSetting";
|
import { TYPES } from "../core/types";
|
||||||
|
import { BaseCloudController } from "./BaseCloudController";
|
||||||
|
|
||||||
const cloud115 = new Cloud115Service();
|
@injectable()
|
||||||
const setCookie = async (req: Request): Promise<void> => {
|
export class Cloud115Controller extends BaseCloudController {
|
||||||
const userId = req.user?.userId;
|
constructor(@inject(TYPES.Cloud115Service) cloud115Service: Cloud115Service) {
|
||||||
const userSetting = await UserSetting.findOne({
|
super(cloud115Service);
|
||||||
where: { userId },
|
|
||||||
});
|
|
||||||
if (userSetting && userSetting.dataValues.cloud115Cookie) {
|
|
||||||
cloud115.setCookie(userSetting.dataValues.cloud115Cookie);
|
|
||||||
} else {
|
|
||||||
throw new Error("请先设置115网盘cookie");
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const cloud115Controller = {
|
|
||||||
async getShareInfo(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const { shareCode, receiveCode } = req.query;
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await cloud115.getShareInfo(shareCode as string, receiveCode as string);
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
sendError(res, { message: (error as Error).message || "获取分享信息失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFolderList(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const { parentCid } = req.query;
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await cloud115.getFolderList(parentCid as string);
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: (error as Error).message || "获取目录列表失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveFile(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const { shareCode, receiveCode, fileId, folderId } = req.body;
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await cloud115.saveSharedFile({
|
|
||||||
shareCode,
|
|
||||||
receiveCode,
|
|
||||||
fileId,
|
|
||||||
cid: folderId,
|
|
||||||
});
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: (error as Error).message || "保存文件失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Cloud115ServiceInstance = cloud115;
|
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import DoubanService from "../services/DoubanService";
|
import { injectable, inject } from "inversify";
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
import { TYPES } from "../core/types";
|
||||||
|
import { DoubanService } from "../services/DoubanService";
|
||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
|
||||||
const doubanService = new DoubanService();
|
@injectable()
|
||||||
|
export class DoubanController extends BaseController {
|
||||||
|
constructor(@inject(TYPES.DoubanService) private doubanService: DoubanService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
export const doubanController = {
|
|
||||||
async getDoubanHotList(req: Request, res: Response): Promise<void> {
|
async getDoubanHotList(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
await this.handleRequest(req, res, async () => {
|
||||||
const { type = "movie", tag = "热门", page_limit = "50", page_start = "0" } = req.query;
|
const { type = "movie", tag = "热门", page_limit = "50", page_start = "0" } = req.query;
|
||||||
const result = await doubanService.getHotList({
|
const result = await this.doubanService.getHotList({
|
||||||
type: type as string,
|
type: type as string,
|
||||||
tag: tag as string,
|
tag: tag as string,
|
||||||
page_limit: page_limit as string,
|
page_limit: page_limit as string,
|
||||||
page_start: page_start as string,
|
page_start: page_start as string,
|
||||||
});
|
});
|
||||||
sendSuccess(res, result);
|
return result;
|
||||||
} catch (error) {
|
});
|
||||||
sendError(res, { message: "获取热门列表失败" });
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,52 +1,12 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
|
import { injectable, inject } from "inversify";
|
||||||
|
import { TYPES } from "../core/types";
|
||||||
import { QuarkService } from "../services/QuarkService";
|
import { QuarkService } from "../services/QuarkService";
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
import { BaseCloudController } from "./BaseCloudController";
|
||||||
import UserSetting from "../models/UserSetting";
|
|
||||||
|
|
||||||
const quark = new QuarkService();
|
@injectable()
|
||||||
|
export class QuarkController extends BaseCloudController {
|
||||||
const setCookie = async (req: Request): Promise<void> => {
|
constructor(@inject(TYPES.QuarkService) quarkService: QuarkService) {
|
||||||
const userId = req.user?.userId;
|
super(quarkService);
|
||||||
const userSetting = await UserSetting.findOne({
|
|
||||||
where: { userId },
|
|
||||||
});
|
|
||||||
if (userSetting && userSetting.dataValues.quarkCookie) {
|
|
||||||
quark.setCookie(userSetting.dataValues.quarkCookie);
|
|
||||||
} else {
|
|
||||||
throw new Error("请先设置夸克网盘cookie");
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export const quarkController = {
|
|
||||||
async getShareInfo(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const { pwdId, passcode } = req.query;
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await quark.getShareInfo(pwdId as string, passcode as string);
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: "获取分享信息失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getFolderList(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
const { parentCid } = req.query;
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await quark.getFolderList(parentCid as string);
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: (error as Error).message || "获取目录列表失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveFile(req: Request, res: Response): Promise<void> {
|
|
||||||
try {
|
|
||||||
await setCookie(req);
|
|
||||||
const result = await quark.saveSharedFile(req.body);
|
|
||||||
sendSuccess(res, result);
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: (error as Error).message || "保存文件失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import Searcher from "../services/Searcher";
|
import { injectable, inject } from "inversify";
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
import { TYPES } from "../core/types";
|
||||||
|
import { Searcher } from "../services/Searcher";
|
||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ResourceController extends BaseController {
|
||||||
|
constructor(@inject(TYPES.Searcher) private searcher: Searcher) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
export const resourceController = {
|
|
||||||
async search(req: Request, res: Response): Promise<void> {
|
async search(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
await this.handleRequest(req, res, async () => {
|
||||||
const { keyword, channelId = "", lastMessageId = "" } = req.query; // Remove `: string` from here
|
const { keyword, channelId = "", lastMessageId = "" } = req.query;
|
||||||
const result = await Searcher.searchAll(
|
return await this.searcher.searchAll(
|
||||||
keyword as string,
|
keyword as string,
|
||||||
channelId as string,
|
channelId as string,
|
||||||
lastMessageId as string
|
lastMessageId as string
|
||||||
);
|
);
|
||||||
sendSuccess(res, result);
|
});
|
||||||
} catch (error) {
|
}
|
||||||
sendError(res, {
|
}
|
||||||
message: (error as Error).message || "搜索资源失败",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,59 +1,28 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
import { injectable, inject } from "inversify";
|
||||||
import Searcher from "../services/Searcher";
|
import { TYPES } from "../core/types";
|
||||||
import UserSetting from "../models/UserSetting";
|
import { SettingService } from "../services/SettingService";
|
||||||
import GlobalSetting from "../models/GlobalSetting";
|
import { BaseController } from "./BaseController";
|
||||||
import { iamgesInstance } from "./teleImages";
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingController extends BaseController {
|
||||||
|
constructor(@inject(TYPES.SettingService) private settingService: SettingService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
export const settingController = {
|
|
||||||
async get(req: Request, res: Response): Promise<void> {
|
async get(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
await this.handleRequest(req, res, async () => {
|
||||||
const userId = req.user?.userId;
|
const userId = req.user?.userId;
|
||||||
const role = req.user?.role;
|
const role = Number(req.user?.role);
|
||||||
if (userId !== null) {
|
return await this.settingService.getSettings(userId, role);
|
||||||
let userSettings = await UserSetting.findOne({ where: { userId } });
|
});
|
||||||
if (!userSettings) {
|
}
|
||||||
userSettings = {
|
|
||||||
userId: userId,
|
|
||||||
cloud115Cookie: "",
|
|
||||||
quarkCookie: "",
|
|
||||||
} as UserSetting;
|
|
||||||
if (userSettings) {
|
|
||||||
await UserSetting.create(userSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const globalSetting = await GlobalSetting.findOne();
|
|
||||||
sendSuccess(res, {
|
|
||||||
data: {
|
|
||||||
userSettings,
|
|
||||||
globalSetting: role === 1 ? globalSetting : null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sendError(res, { message: "用户ID无效" });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("获取设置失败:" + error);
|
|
||||||
sendError(res, { message: (error as Error).message || "获取设置失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async save(req: Request, res: Response): Promise<void> {
|
async save(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
await this.handleRequest(req, res, async () => {
|
||||||
const userId = req.user?.userId;
|
const userId = req.user?.userId;
|
||||||
const role = req.user?.role;
|
const role = Number(req.user?.role);
|
||||||
if (userId !== null) {
|
return await this.settingService.saveSettings(userId, role, req.body);
|
||||||
const { userSettings, globalSetting } = req.body;
|
});
|
||||||
await UserSetting.update(userSettings, { where: { userId } });
|
}
|
||||||
if (role === 1 && globalSetting) await GlobalSetting.update(globalSetting, { where: {} });
|
}
|
||||||
Searcher.updateAxiosInstance();
|
|
||||||
iamgesInstance.updateProxyConfig();
|
|
||||||
sendSuccess(res, {
|
|
||||||
message: "保存成功",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.log("保存设置失败:" + error);
|
|
||||||
sendError(res, { message: (error as Error).message || "保存设置失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
18
backend/src/controllers/sponsors.ts
Normal file
18
backend/src/controllers/sponsors.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { injectable, inject } from "inversify";
|
||||||
|
import { TYPES } from "../core/types";
|
||||||
|
import { SponsorsService } from "../services/SponsorsService";
|
||||||
|
import { BaseController } from "./BaseController";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SponsorsController extends BaseController {
|
||||||
|
constructor(@inject(TYPES.SponsorsService) private sponsorsService: SponsorsService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(req: Request, res: Response): Promise<void> {
|
||||||
|
await this.handleRequest(req, res, async () => {
|
||||||
|
return await this.sponsorsService.getSponsors();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,78 +1,30 @@
|
|||||||
import axios, { AxiosInstance } from "axios";
|
import { Request, Response } from "express";
|
||||||
import e, { Request, Response } from "express";
|
import { injectable, inject } from "inversify";
|
||||||
import tunnel from "tunnel";
|
import { TYPES } from "../core/types";
|
||||||
import GlobalSetting from "../models/GlobalSetting";
|
import { ImageService } from "../services/ImageService";
|
||||||
import { GlobalSettingAttributes } from "../models/GlobalSetting";
|
import { BaseController } from "./BaseController";
|
||||||
|
|
||||||
export class ImageControll {
|
@injectable()
|
||||||
private axiosInstance: AxiosInstance | null = null;
|
export class ImageController extends BaseController {
|
||||||
private settings: GlobalSetting | null = null;
|
constructor(@inject(TYPES.ImageService) private imageService: ImageService) {
|
||||||
|
super();
|
||||||
constructor() {
|
|
||||||
this.initializeAxiosInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeAxiosInstance(): Promise<void> {
|
async getImages(req: Request, res: Response): Promise<void> {
|
||||||
try {
|
await this.handleRequest(req, res, async () => {
|
||||||
this.settings = await GlobalSetting.findOne();
|
const url = decodeURIComponent((req.query.url as string) || "");
|
||||||
} catch (error) {
|
const response = await this.imageService.getImages(url);
|
||||||
console.error("Error fetching global settings:", error);
|
|
||||||
}
|
// 设置正确的响应头
|
||||||
const globalSetting = this.settings?.dataValues || ({} as GlobalSettingAttributes);
|
res.setHeader("Content-Type", response.headers["content-type"]);
|
||||||
this.axiosInstance = axios.create({
|
res.setHeader("Cache-Control", "no-cache");
|
||||||
timeout: 3000,
|
|
||||||
httpsAgent: globalSetting.isProxyEnabled
|
// 确保清除任何可能导致304响应的头信息
|
||||||
? tunnel.httpsOverHttp({
|
res.removeHeader("etag");
|
||||||
proxy: {
|
res.removeHeader("last-modified");
|
||||||
host: globalSetting.httpProxyHost,
|
|
||||||
port: globalSetting.httpProxyPort,
|
// 直接传输图片数据
|
||||||
headers: {
|
response.data.pipe(res);
|
||||||
"Proxy-Authorization": "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: undefined,
|
|
||||||
withCredentials: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
public async updateProxyConfig(): Promise<void> {
|
|
||||||
try {
|
|
||||||
this.settings = await GlobalSetting.findOne();
|
|
||||||
const globalSetting = this.settings?.dataValues || ({} as GlobalSettingAttributes);
|
|
||||||
if (this.axiosInstance) {
|
|
||||||
this.axiosInstance.defaults.httpsAgent = globalSetting.isProxyEnabled
|
|
||||||
? tunnel.httpsOverHttp({
|
|
||||||
proxy: {
|
|
||||||
host: globalSetting.httpProxyHost,
|
|
||||||
port: globalSetting.httpProxyPort,
|
|
||||||
headers: {
|
|
||||||
"Proxy-Authorization": "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
: undefined;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error updating proxy config:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getImages(req: Request, res: Response, url: string): Promise<void> {
|
|
||||||
try {
|
|
||||||
const response = await this.axiosInstance?.get(url, { responseType: "stream" });
|
|
||||||
res.set("Content-Type", response?.headers["content-type"]);
|
|
||||||
response?.data.pipe(res);
|
|
||||||
} catch (error) {
|
|
||||||
res.status(500).send("Image fetch error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const iamgesInstance = new ImageControll();
|
|
||||||
|
|
||||||
export const imageControll = {
|
|
||||||
getImages: async (req: Request, res: Response): Promise<void> => {
|
|
||||||
const url = req.query.url as string;
|
|
||||||
iamgesInstance.getImages(req, res, url);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,62 +1,26 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import bcrypt from "bcrypt";
|
import { injectable, inject } from "inversify";
|
||||||
import jwt from "jsonwebtoken";
|
import { TYPES } from "../core/types";
|
||||||
import GlobalSetting from "../models/GlobalSetting";
|
import { UserService } from "../services/UserService";
|
||||||
import User from "../models/User";
|
import { BaseController } from "./BaseController";
|
||||||
import { config } from "../config";
|
|
||||||
import { sendSuccess, sendError } from "../utils/response";
|
@injectable()
|
||||||
|
export class UserController extends BaseController {
|
||||||
|
constructor(@inject(TYPES.UserService) private userService: UserService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
const isValidInput = (input: string): boolean => {
|
|
||||||
// 检查是否包含空格或汉字
|
|
||||||
const regex = /^[^\s\u4e00-\u9fa5]+$/;
|
|
||||||
return regex.test(input);
|
|
||||||
};
|
|
||||||
export const userController = {
|
|
||||||
async register(req: Request, res: Response): Promise<void> {
|
async register(req: Request, res: Response): Promise<void> {
|
||||||
const { username, password, registerCode } = req.body;
|
await this.handleRequest(req, res, async () => {
|
||||||
const globalSetting = await GlobalSetting.findOne();
|
const { username, password, registerCode } = req.body;
|
||||||
const registerCodeList = [
|
return await this.userService.register(username, password, registerCode);
|
||||||
globalSetting?.dataValues.CommonUserCode,
|
});
|
||||||
globalSetting?.dataValues.AdminUserCode,
|
}
|
||||||
];
|
|
||||||
if (!registerCode || !registerCodeList.includes(Number(registerCode))) {
|
|
||||||
return sendError(res, { message: "注册码错误" });
|
|
||||||
}
|
|
||||||
// 验证输入
|
|
||||||
if (!isValidInput(username) || !isValidInput(password)) {
|
|
||||||
return sendError(res, { message: "用户名、密码或注册码不能包含空格或汉字" });
|
|
||||||
}
|
|
||||||
// 检查用户名是否已存在
|
|
||||||
const existingUser = await User.findOne({ where: { username } });
|
|
||||||
if (existingUser) {
|
|
||||||
return sendError(res, { message: "用户名已存在" });
|
|
||||||
}
|
|
||||||
const hashedPassword = await bcrypt.hash(password, 10);
|
|
||||||
try {
|
|
||||||
const role = registerCodeList.findIndex((x) => x === Number(registerCode));
|
|
||||||
const user = await User.create({ username, password: hashedPassword, role });
|
|
||||||
sendSuccess(res, {
|
|
||||||
data: user,
|
|
||||||
message: "用户注册成功",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
sendError(res, { message: (error as Error).message || "用户注册失败" });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async login(req: Request, res: Response): Promise<void> {
|
async login(req: Request, res: Response): Promise<void> {
|
||||||
const { username, password } = req.body;
|
await this.handleRequest(req, res, async () => {
|
||||||
const user = await User.findOne({ where: { username } });
|
const { username, password } = req.body;
|
||||||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
return await this.userService.login(username, password);
|
||||||
return sendError(res, { message: "用户名或密码错误" });
|
|
||||||
}
|
|
||||||
const token = jwt.sign({ userId: user.userId, role: user.role }, config.jwtSecret, {
|
|
||||||
expiresIn: "6h",
|
|
||||||
});
|
});
|
||||||
sendSuccess(res, {
|
}
|
||||||
data: {
|
}
|
||||||
token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
21
backend/src/core/ApiResponse.ts
Normal file
21
backend/src/core/ApiResponse.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
export class ApiResponse<T> {
|
||||||
|
success: boolean;
|
||||||
|
data?: T;
|
||||||
|
message?: string;
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
private constructor(success: boolean, code: number, data?: T, message?: string) {
|
||||||
|
this.success = success;
|
||||||
|
this.code = code;
|
||||||
|
this.data = data;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
static success<T>(data?: T, message = "操作成功"): ApiResponse<T> {
|
||||||
|
return new ApiResponse(true, 0, data, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static error(message: string, code = 10000): ApiResponse<null> {
|
||||||
|
return new ApiResponse(false, code, null, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
backend/src/core/types.ts
Normal file
20
backend/src/core/types.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export const TYPES = {
|
||||||
|
DatabaseService: Symbol.for("DatabaseService"),
|
||||||
|
Cloud115Service: Symbol.for("Cloud115Service"),
|
||||||
|
QuarkService: Symbol.for("QuarkService"),
|
||||||
|
Searcher: Symbol.for("Searcher"),
|
||||||
|
DoubanService: Symbol.for("DoubanService"),
|
||||||
|
ImageService: Symbol.for("ImageService"),
|
||||||
|
SettingService: Symbol.for("SettingService"),
|
||||||
|
UserService: Symbol.for("UserService"),
|
||||||
|
SponsorsService: Symbol.for("SponsorsService"),
|
||||||
|
|
||||||
|
Cloud115Controller: Symbol.for("Cloud115Controller"),
|
||||||
|
QuarkController: Symbol.for("QuarkController"),
|
||||||
|
ResourceController: Symbol.for("ResourceController"),
|
||||||
|
DoubanController: Symbol.for("DoubanController"),
|
||||||
|
ImageController: Symbol.for("ImageController"),
|
||||||
|
SettingController: Symbol.for("SettingController"),
|
||||||
|
UserController: Symbol.for("UserController"),
|
||||||
|
SponsorsController: Symbol.for("SponsorsController"),
|
||||||
|
};
|
||||||
45
backend/src/inversify.config.ts
Normal file
45
backend/src/inversify.config.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { Container } from "inversify";
|
||||||
|
import { TYPES } from "./core/types";
|
||||||
|
|
||||||
|
// Services
|
||||||
|
import { DatabaseService } from "./services/DatabaseService";
|
||||||
|
import { Cloud115Service } from "./services/Cloud115Service";
|
||||||
|
import { QuarkService } from "./services/QuarkService";
|
||||||
|
import { Searcher } from "./services/Searcher";
|
||||||
|
import { DoubanService } from "./services/DoubanService";
|
||||||
|
import { UserService } from "./services/UserService";
|
||||||
|
import { ImageService } from "./services/ImageService";
|
||||||
|
import { SettingService } from "./services/SettingService";
|
||||||
|
import { SponsorsService } from "./services/SponsorsService";
|
||||||
|
// Controllers
|
||||||
|
import { Cloud115Controller } from "./controllers/cloud115";
|
||||||
|
import { QuarkController } from "./controllers/quark";
|
||||||
|
import { ResourceController } from "./controllers/resource";
|
||||||
|
import { DoubanController } from "./controllers/douban";
|
||||||
|
import { ImageController } from "./controllers/teleImages";
|
||||||
|
import { SettingController } from "./controllers/setting";
|
||||||
|
import { UserController } from "./controllers/user";
|
||||||
|
import { SponsorsController } from "./controllers/sponsors";
|
||||||
|
const container = new Container();
|
||||||
|
|
||||||
|
// Services
|
||||||
|
container.bind<DatabaseService>(TYPES.DatabaseService).to(DatabaseService).inSingletonScope();
|
||||||
|
container.bind<Cloud115Service>(TYPES.Cloud115Service).to(Cloud115Service).inSingletonScope();
|
||||||
|
container.bind<QuarkService>(TYPES.QuarkService).to(QuarkService).inSingletonScope();
|
||||||
|
container.bind<Searcher>(TYPES.Searcher).to(Searcher).inSingletonScope();
|
||||||
|
container.bind<ImageService>(TYPES.ImageService).to(ImageService).inSingletonScope();
|
||||||
|
container.bind<SettingService>(TYPES.SettingService).to(SettingService).inSingletonScope();
|
||||||
|
container.bind<DoubanService>(TYPES.DoubanService).to(DoubanService).inSingletonScope();
|
||||||
|
container.bind<UserService>(TYPES.UserService).to(UserService).inSingletonScope();
|
||||||
|
container.bind<SponsorsService>(TYPES.SponsorsService).to(SponsorsService).inSingletonScope();
|
||||||
|
// Controllers
|
||||||
|
container.bind<Cloud115Controller>(TYPES.Cloud115Controller).to(Cloud115Controller);
|
||||||
|
container.bind<QuarkController>(TYPES.QuarkController).to(QuarkController);
|
||||||
|
container.bind<ResourceController>(TYPES.ResourceController).to(ResourceController);
|
||||||
|
container.bind<DoubanController>(TYPES.DoubanController).to(DoubanController);
|
||||||
|
container.bind<ImageController>(TYPES.ImageController).to(ImageController);
|
||||||
|
container.bind<SettingController>(TYPES.SettingController).to(SettingController);
|
||||||
|
container.bind<UserController>(TYPES.UserController).to(UserController);
|
||||||
|
container.bind<SponsorsController>(TYPES.SponsorsController).to(SponsorsController);
|
||||||
|
|
||||||
|
export { container };
|
||||||
@@ -16,7 +16,7 @@ export const authMiddleware = async (
|
|||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<void | Response> => {
|
): Promise<void | Response> => {
|
||||||
if (req.path === "/user/login" || req.path === "/user/register") {
|
if (req.path === "/user/login" || req.path === "/user/register" || req.path === "/tele-images/") {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
backend/src/middleware/cors.ts
Normal file
15
backend/src/middleware/cors.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
|
export const cors = () => {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||||
|
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization, Cookie");
|
||||||
|
res.header("Access-Control-Allow-Credentials", "true");
|
||||||
|
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
return res.sendStatus(200);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,7 +5,6 @@ interface CustomError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const errorHandler = (err: CustomError, req: Request, res: Response): void => {
|
export const errorHandler = (err: CustomError, req: Request, res: Response): void => {
|
||||||
console.error(err);
|
|
||||||
res.status(err.status || 500).json({
|
res.status(err.status || 500).json({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message || "服务器内部错误",
|
error: err.message || "服务器内部错误",
|
||||||
|
|||||||
14
backend/src/middleware/index.ts
Normal file
14
backend/src/middleware/index.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Application } from "express";
|
||||||
|
import express from "express";
|
||||||
|
import { authMiddleware } from "./auth";
|
||||||
|
import { requestLogger } from "./requestLogger";
|
||||||
|
import { rateLimiter } from "./rateLimiter";
|
||||||
|
import { cors } from "./cors";
|
||||||
|
|
||||||
|
export const setupMiddlewares = (app: Application) => {
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cors());
|
||||||
|
app.use(requestLogger());
|
||||||
|
app.use(rateLimiter());
|
||||||
|
app.use(authMiddleware);
|
||||||
|
};
|
||||||
27
backend/src/middleware/rateLimiter.ts
Normal file
27
backend/src/middleware/rateLimiter.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
|
||||||
|
const requestCounts = new Map<string, { count: number; timestamp: number }>();
|
||||||
|
const WINDOW_MS = 60 * 1000; // 1分钟窗口
|
||||||
|
const MAX_REQUESTS = 600; // 每个IP每分钟最多60个请求
|
||||||
|
|
||||||
|
export const rateLimiter = () => {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const ip = req.ip || req.socket.remoteAddress || "unknown";
|
||||||
|
const now = Date.now();
|
||||||
|
const record = requestCounts.get(ip) || { count: 0, timestamp: now };
|
||||||
|
|
||||||
|
if (now - record.timestamp > WINDOW_MS) {
|
||||||
|
record.count = 0;
|
||||||
|
record.timestamp = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
record.count++;
|
||||||
|
requestCounts.set(ip, record);
|
||||||
|
|
||||||
|
if (record.count > MAX_REQUESTS) {
|
||||||
|
return res.status(429).json({ message: "请求过于频繁,请稍后再试" });
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
||||||
23
backend/src/middleware/requestLogger.ts
Normal file
23
backend/src/middleware/requestLogger.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
|
const excludePaths = ["/tele-images/"];
|
||||||
|
|
||||||
|
export const requestLogger = () => {
|
||||||
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const start = Date.now();
|
||||||
|
res.on("finish", () => {
|
||||||
|
if (excludePaths.includes(req.path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
logger.info({
|
||||||
|
method: req.method,
|
||||||
|
path: req.path,
|
||||||
|
status: res.statusCode,
|
||||||
|
duration: `${duration}ms`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,36 +1,55 @@
|
|||||||
import express from "express";
|
import { Router } from "express";
|
||||||
import { cloud115Controller } from "../controllers/cloud115";
|
import { container } from "../inversify.config";
|
||||||
import { quarkController } from "../controllers/quark";
|
import { TYPES } from "../core/types";
|
||||||
import { resourceController } from "../controllers/resource";
|
import { Cloud115Controller } from "../controllers/cloud115";
|
||||||
import { doubanController } from "../controllers/douban";
|
import { QuarkController } from "../controllers/quark";
|
||||||
import { imageControll } from "../controllers/teleImages";
|
import { ResourceController } from "../controllers/resource";
|
||||||
import settingRoutes from "./setting";
|
import { DoubanController } from "../controllers/douban";
|
||||||
import userRoutes from "./user";
|
import { ImageController } from "../controllers/teleImages";
|
||||||
|
import { SettingController } from "../controllers/setting";
|
||||||
|
import { UserController } from "../controllers/user";
|
||||||
|
import { SponsorsController } from "../controllers/sponsors";
|
||||||
|
|
||||||
const router = express.Router();
|
const router = Router();
|
||||||
|
|
||||||
|
// 获取控制器实例
|
||||||
|
const cloud115Controller = container.get<Cloud115Controller>(TYPES.Cloud115Controller);
|
||||||
|
const quarkController = container.get<QuarkController>(TYPES.QuarkController);
|
||||||
|
const resourceController = container.get<ResourceController>(TYPES.ResourceController);
|
||||||
|
const doubanController = container.get<DoubanController>(TYPES.DoubanController);
|
||||||
|
const imageController = container.get<ImageController>(TYPES.ImageController);
|
||||||
|
const settingController = container.get<SettingController>(TYPES.SettingController);
|
||||||
|
const userController = container.get<UserController>(TYPES.UserController);
|
||||||
|
const sponsorsController = container.get<SponsorsController>(TYPES.SponsorsController);
|
||||||
|
|
||||||
// 用户相关路由
|
// 用户相关路由
|
||||||
router.use("/user", userRoutes);
|
router.post("/user/login", (req, res) => userController.login(req, res));
|
||||||
|
router.post("/user/register", (req, res) => userController.register(req, res));
|
||||||
|
|
||||||
router.use("/tele-images", imageControll.getImages);
|
// 图片相关路由
|
||||||
|
router.get("/tele-images", (req, res) => imageController.getImages(req, res));
|
||||||
|
|
||||||
// 设置相关路由
|
// 设置相关路由
|
||||||
router.use("/setting", settingRoutes);
|
router.get("/setting/get", (req, res) => settingController.get(req, res));
|
||||||
|
router.post("/setting/save", (req, res) => settingController.save(req, res));
|
||||||
|
|
||||||
// 资源搜索
|
// 资源搜索
|
||||||
router.get("/search", resourceController.search);
|
router.get("/search", (req, res) => resourceController.search(req, res));
|
||||||
|
|
||||||
|
// 获取赞助者列表
|
||||||
|
router.get("/sponsors", (req, res) => sponsorsController.get(req, res));
|
||||||
|
|
||||||
// 115网盘相关
|
// 115网盘相关
|
||||||
router.get("/cloud115/share-info", cloud115Controller.getShareInfo);
|
router.get("/cloud115/share-info", (req, res) => cloud115Controller.getShareInfo(req, res));
|
||||||
router.get("/cloud115/folders", cloud115Controller.getFolderList);
|
router.get("/cloud115/folders", (req, res) => cloud115Controller.getFolderList(req, res));
|
||||||
router.post("/cloud115/save", cloud115Controller.saveFile);
|
router.post("/cloud115/save", (req, res) => cloud115Controller.saveFile(req, res));
|
||||||
|
|
||||||
// 夸克网盘相关
|
// 夸克网盘相关
|
||||||
router.get("/quark/share-info", quarkController.getShareInfo);
|
router.get("/quark/share-info", (req, res) => quarkController.getShareInfo(req, res));
|
||||||
router.get("/quark/folders", quarkController.getFolderList);
|
router.get("/quark/folders", (req, res) => quarkController.getFolderList(req, res));
|
||||||
router.post("/quark/save", quarkController.saveFile);
|
router.post("/quark/save", (req, res) => quarkController.saveFile(req, res));
|
||||||
|
|
||||||
// 获取豆瓣热门列表
|
// 获取豆瓣热门列表
|
||||||
router.get("/douban/hot", doubanController.getDoubanHotList);
|
router.get("/douban/hot", (req, res) => doubanController.getDoubanHotList(req, res));
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import express from "express";
|
|
||||||
import { settingController } from "../controllers/setting";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.get("/get", settingController.get);
|
|
||||||
router.post("/save", settingController.save);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
// backend/src/routes/user.ts
|
|
||||||
import express from "express";
|
|
||||||
import { userController } from "../controllers/user";
|
|
||||||
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.post("/register", userController.register);
|
|
||||||
router.post("/login", userController.login);
|
|
||||||
|
|
||||||
export default router;
|
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import { AxiosHeaders, AxiosInstance } from "axios"; // 导入 AxiosHeaders
|
import { AxiosHeaders, AxiosInstance } from "axios"; // 导入 AxiosHeaders
|
||||||
import { createAxiosInstance } from "../utils/axiosInstance";
|
import { createAxiosInstance } from "../utils/axiosInstance";
|
||||||
import { Logger } from "../utils/logger";
|
import { ShareInfoResponse, FolderListResponse, SaveFileParams } from "../types/cloud";
|
||||||
import { ShareInfoResponse } from "../types/cloud115";
|
import { injectable } from "inversify";
|
||||||
|
import { Request } from "express";
|
||||||
|
import UserSetting from "../models/UserSetting";
|
||||||
|
import { ICloudStorageService } from "@/types/services";
|
||||||
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
interface Cloud115ListItem {
|
interface Cloud115ListItem {
|
||||||
cid: string;
|
cid: string;
|
||||||
@@ -15,16 +19,12 @@ interface Cloud115FolderItem {
|
|||||||
ns: number;
|
ns: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cloud115PathItem {
|
@injectable()
|
||||||
cid: string;
|
export class Cloud115Service implements ICloudStorageService {
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Cloud115Service {
|
|
||||||
private api: AxiosInstance;
|
private api: AxiosInstance;
|
||||||
private cookie: string = "";
|
private cookie: string = "";
|
||||||
|
|
||||||
constructor(cookie?: string) {
|
constructor() {
|
||||||
this.api = createAxiosInstance(
|
this.api = createAxiosInstance(
|
||||||
"https://webapi.115.com",
|
"https://webapi.115.com",
|
||||||
AxiosHeaders.from({
|
AxiosHeaders.from({
|
||||||
@@ -44,19 +44,23 @@ export class Cloud115Service {
|
|||||||
"Accept-Language": "zh-CN,zh;q=0.9",
|
"Accept-Language": "zh-CN,zh;q=0.9",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (cookie) {
|
|
||||||
this.setCookie(cookie);
|
|
||||||
} else {
|
|
||||||
console.log("请注意:115网盘需要提供cookie进行身份验证");
|
|
||||||
}
|
|
||||||
this.api.interceptors.request.use((config) => {
|
this.api.interceptors.request.use((config) => {
|
||||||
config.headers.cookie = cookie || this.cookie;
|
config.headers.cookie = this.cookie;
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setCookie(cookie: string): void {
|
async setCookie(req: Request): Promise<void> {
|
||||||
this.cookie = cookie;
|
const userId = req.user?.userId;
|
||||||
|
const userSetting = await UserSetting.findOne({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
if (userSetting && userSetting.dataValues.cloud115Cookie) {
|
||||||
|
this.cookie = userSetting.dataValues.cloud115Cookie;
|
||||||
|
} else {
|
||||||
|
throw new Error("请先设置115网盘cookie");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getShareInfo(shareCode: string, receiveCode = ""): Promise<ShareInfoResponse> {
|
async getShareInfo(shareCode: string, receiveCode = ""): Promise<ShareInfoResponse> {
|
||||||
@@ -71,19 +75,21 @@ export class Cloud115Service {
|
|||||||
});
|
});
|
||||||
if (response.data?.state && response.data.data?.list?.length > 0) {
|
if (response.data?.state && response.data.data?.list?.length > 0) {
|
||||||
return {
|
return {
|
||||||
data: response.data.data.list.map((item: Cloud115ListItem) => ({
|
data: {
|
||||||
fileId: item.cid,
|
list: response.data.data.list.map((item: Cloud115ListItem) => ({
|
||||||
fileName: item.n,
|
fileId: item.cid,
|
||||||
fileSize: item.s,
|
fileName: item.n,
|
||||||
})),
|
fileSize: item.s,
|
||||||
|
})),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
logger.error("未找到文件信息:", response.data);
|
||||||
|
throw new Error("未找到文件信息");
|
||||||
}
|
}
|
||||||
throw new Error("未找到文件信息");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFolderList(
|
async getFolderList(parentCid = "0"): Promise<FolderListResponse> {
|
||||||
parentCid = "0"
|
|
||||||
): Promise<{ data: { cid: string; name: string; path: Cloud115PathItem[] }[] }> {
|
|
||||||
const response = await this.api.get("/files", {
|
const response = await this.api.get("/files", {
|
||||||
params: {
|
params: {
|
||||||
aid: 1,
|
aid: 1,
|
||||||
@@ -114,32 +120,27 @@ export class Cloud115Service {
|
|||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
Logger.error("获取目录列表失败:", response.data.error);
|
logger.error("获取目录列表失败:", response.data.error);
|
||||||
throw new Error("获取115pan目录列表失败:" + response.data.error);
|
throw new Error("获取115pan目录列表失败:" + response.data.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSharedFile(params: {
|
async saveSharedFile(params: SaveFileParams): Promise<{ message: string; data: unknown }> {
|
||||||
cid: string;
|
|
||||||
shareCode: string;
|
|
||||||
receiveCode: string;
|
|
||||||
fileId: string;
|
|
||||||
}): Promise<{ message: string; data: unknown }> {
|
|
||||||
const param = new URLSearchParams({
|
const param = new URLSearchParams({
|
||||||
cid: params.cid,
|
cid: params.folderId || "",
|
||||||
share_code: params.shareCode,
|
share_code: params.shareCode || "",
|
||||||
receive_code: params.receiveCode,
|
receive_code: params.receiveCode || "",
|
||||||
file_id: params.fileId,
|
file_id: params.fids?.[0] || "",
|
||||||
});
|
});
|
||||||
const response = await this.api.post("/share/receive", param.toString());
|
const response = await this.api.post("/share/receive", param.toString());
|
||||||
Logger.info("保存文件:", response.data);
|
logger.info("保存文件:", response.data);
|
||||||
if (response.data.state) {
|
if (response.data.state) {
|
||||||
return {
|
return {
|
||||||
message: response.data.error,
|
message: response.data.error,
|
||||||
data: response.data.data,
|
data: response.data.data,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
Logger.error("保存文件失败:", response.data.error);
|
logger.error("保存文件失败:", response.data.error);
|
||||||
throw new Error("保存115pan文件失败:" + response.data.error);
|
throw new Error("保存115pan文件失败:" + response.data.error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
62
backend/src/services/DatabaseService.ts
Normal file
62
backend/src/services/DatabaseService.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { Sequelize, QueryTypes } from "sequelize";
|
||||||
|
import GlobalSetting from "../models/GlobalSetting";
|
||||||
|
import { Searcher } from "./Searcher";
|
||||||
|
import sequelize from "../config/database";
|
||||||
|
|
||||||
|
// 全局设置默认值
|
||||||
|
const DEFAULT_GLOBAL_SETTINGS = {
|
||||||
|
httpProxyHost: "127.0.0.1",
|
||||||
|
httpProxyPort: 7890,
|
||||||
|
isProxyEnabled: false,
|
||||||
|
CommonUserCode: 9527,
|
||||||
|
AdminUserCode: 230713,
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DatabaseService {
|
||||||
|
private sequelize: Sequelize;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.sequelize = sequelize;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.sequelize.query("PRAGMA foreign_keys = OFF");
|
||||||
|
await this.cleanupBackupTables();
|
||||||
|
await this.sequelize.sync({ alter: true });
|
||||||
|
await this.sequelize.query("PRAGMA foreign_keys = ON");
|
||||||
|
await this.initializeGlobalSettings();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`数据库初始化失败: ${(error as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initializeGlobalSettings(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const settings = await GlobalSetting.findOne();
|
||||||
|
if (!settings) {
|
||||||
|
await GlobalSetting.create(DEFAULT_GLOBAL_SETTINGS);
|
||||||
|
console.log("✅ Global settings initialized with default values.");
|
||||||
|
}
|
||||||
|
await Searcher.updateAxiosInstance();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to initialize global settings:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async cleanupBackupTables(): Promise<void> {
|
||||||
|
const backupTables = await this.sequelize.query<{ name: string }>(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%\\_backup%' ESCAPE '\\'",
|
||||||
|
{ type: QueryTypes.SELECT }
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const table of backupTables) {
|
||||||
|
if (table?.name) {
|
||||||
|
await this.sequelize.query(`DROP TABLE IF EXISTS ${table.name}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他数据库相关方法
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ interface DoubanSubject {
|
|||||||
is_new: boolean;
|
is_new: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DoubanService {
|
export class DoubanService {
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
private api: AxiosInstance;
|
private api: AxiosInstance;
|
||||||
|
|
||||||
@@ -62,5 +62,3 @@ class DoubanService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DoubanService;
|
|
||||||
|
|||||||
68
backend/src/services/ImageService.ts
Normal file
68
backend/src/services/ImageService.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { injectable } from "inversify";
|
||||||
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
import tunnel from "tunnel";
|
||||||
|
import GlobalSetting from "../models/GlobalSetting";
|
||||||
|
import { GlobalSettingAttributes } from "../models/GlobalSetting";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ImageService {
|
||||||
|
private axiosInstance: AxiosInstance | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 移除构造函数中的初始化,改为懒加载
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureAxiosInstance(): Promise<AxiosInstance> {
|
||||||
|
if (!this.axiosInstance) {
|
||||||
|
const settings = await GlobalSetting.findOne();
|
||||||
|
const globalSetting = settings?.dataValues || ({} as GlobalSettingAttributes);
|
||||||
|
|
||||||
|
this.axiosInstance = axios.create({
|
||||||
|
timeout: 30000,
|
||||||
|
headers: {
|
||||||
|
Accept: "image/*, */*",
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
},
|
||||||
|
withCredentials: false,
|
||||||
|
maxRedirects: 5,
|
||||||
|
httpsAgent: globalSetting.isProxyEnabled
|
||||||
|
? tunnel.httpsOverHttp({
|
||||||
|
proxy: {
|
||||||
|
host: globalSetting.httpProxyHost,
|
||||||
|
port: globalSetting.httpProxyPort,
|
||||||
|
headers: {
|
||||||
|
"Proxy-Authorization": "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.axiosInstance.interceptors.response.use(
|
||||||
|
(response) => response,
|
||||||
|
(error) => {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.axiosInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAxiosInstance(): Promise<void> {
|
||||||
|
this.axiosInstance = null;
|
||||||
|
await this.ensureAxiosInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getImages(url: string): Promise<any> {
|
||||||
|
const axiosInstance = await this.ensureAxiosInstance();
|
||||||
|
|
||||||
|
return await axiosInstance.get(url, {
|
||||||
|
responseType: "stream",
|
||||||
|
validateStatus: (status) => status >= 200 && status < 300,
|
||||||
|
headers: {
|
||||||
|
Referer: new URL(url).origin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
import { AxiosInstance, AxiosHeaders } from "axios";
|
import { AxiosInstance, AxiosHeaders } from "axios";
|
||||||
import { Logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
import { createAxiosInstance } from "../utils/axiosInstance";
|
import { createAxiosInstance } from "../utils/axiosInstance";
|
||||||
|
import { injectable } from "inversify";
|
||||||
|
import { Request } from "express";
|
||||||
|
import UserSetting from "../models/UserSetting";
|
||||||
|
import {
|
||||||
|
ShareInfoResponse,
|
||||||
|
FolderListResponse,
|
||||||
|
QuarkFolderItem,
|
||||||
|
SaveFileParams,
|
||||||
|
} from "../types/cloud";
|
||||||
|
import { ICloudStorageService } from "@/types/services";
|
||||||
|
|
||||||
interface QuarkShareInfo {
|
interface QuarkShareInfo {
|
||||||
stoken?: string;
|
stoken?: string;
|
||||||
@@ -14,17 +24,12 @@ interface QuarkShareInfo {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QuarkFolderItem {
|
@injectable()
|
||||||
fid: string;
|
export class QuarkService implements ICloudStorageService {
|
||||||
file_name: string;
|
|
||||||
file_type: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class QuarkService {
|
|
||||||
private api: AxiosInstance;
|
private api: AxiosInstance;
|
||||||
private cookie: string = "";
|
private cookie: string = "";
|
||||||
|
|
||||||
constructor(cookie?: string) {
|
constructor() {
|
||||||
this.api = createAxiosInstance(
|
this.api = createAxiosInstance(
|
||||||
"https://drive-h.quark.cn",
|
"https://drive-h.quark.cn",
|
||||||
AxiosHeaders.from({
|
AxiosHeaders.from({
|
||||||
@@ -41,22 +46,26 @@ export class QuarkService {
|
|||||||
"sec-fetch-site": "same-site",
|
"sec-fetch-site": "same-site",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (cookie) {
|
|
||||||
this.setCookie(cookie);
|
|
||||||
} else {
|
|
||||||
console.log("请注意:夸克网盘需要提供cookie进行身份验证");
|
|
||||||
}
|
|
||||||
this.api.interceptors.request.use((config) => {
|
this.api.interceptors.request.use((config) => {
|
||||||
config.headers.cookie = cookie || this.cookie;
|
config.headers.cookie = this.cookie;
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setCookie(cookie: string): void {
|
async setCookie(req: Request): Promise<void> {
|
||||||
this.cookie = cookie;
|
const userId = req.user?.userId;
|
||||||
|
const userSetting = await UserSetting.findOne({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
if (userSetting && userSetting.dataValues.quarkCookie) {
|
||||||
|
this.cookie = userSetting.dataValues.quarkCookie;
|
||||||
|
} else {
|
||||||
|
throw new Error("请先设置夸克网盘cookie");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getShareInfo(pwdId: string, passcode = ""): Promise<{ data: QuarkShareInfo }> {
|
async getShareInfo(pwdId: string, passcode = ""): Promise<ShareInfoResponse> {
|
||||||
const response = await this.api.post(
|
const response = await this.api.post(
|
||||||
`/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc&uc_param_str=&__dt=994&__t=${Date.now()}`,
|
`/1/clouddrive/share/sharepage/token?pr=ucpro&fr=pc&uc_param_str=&__dt=994&__t=${Date.now()}`,
|
||||||
{
|
{
|
||||||
@@ -76,7 +85,7 @@ export class QuarkService {
|
|||||||
throw new Error("获取夸克分享信息失败");
|
throw new Error("获取夸克分享信息失败");
|
||||||
}
|
}
|
||||||
|
|
||||||
async getShareList(pwdId: string, stoken: string): Promise<QuarkShareInfo> {
|
async getShareList(pwdId: string, stoken: string): Promise<ShareInfoResponse["data"]> {
|
||||||
const response = await this.api.get("/1/clouddrive/share/sharepage/detail", {
|
const response = await this.api.get("/1/clouddrive/share/sharepage/detail", {
|
||||||
params: {
|
params: {
|
||||||
pr: "ucpro",
|
pr: "ucpro",
|
||||||
@@ -117,9 +126,7 @@ export class QuarkService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFolderList(
|
async getFolderList(parentCid = "0"): Promise<FolderListResponse> {
|
||||||
parentCid = "0"
|
|
||||||
): Promise<{ data: { cid: string; name: string; path: [] }[] }> {
|
|
||||||
const response = await this.api.get("/1/clouddrive/file/sort", {
|
const response = await this.api.get("/1/clouddrive/file/sort", {
|
||||||
params: {
|
params: {
|
||||||
pr: "ucpro",
|
pr: "ucpro",
|
||||||
@@ -148,24 +155,25 @@ export class QuarkService {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const message = "获取夸克目录列表失败:" + response.data.error;
|
const message = "获取夸克目录列表失败:" + response.data.error;
|
||||||
Logger.error(message);
|
logger.error(message);
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveSharedFile(params: {
|
async saveSharedFile(params: SaveFileParams): Promise<{ message: string; data: unknown }> {
|
||||||
fid_list: string[];
|
const quarkParams = {
|
||||||
fid_token_list: string[];
|
fid_list: params.fids,
|
||||||
to_pdir_fid: string;
|
fid_token_list: params.fidTokens,
|
||||||
pwd_id: string;
|
to_pdir_fid: params.folderId,
|
||||||
stoken: string;
|
pwd_id: params.shareCode,
|
||||||
pdir_fid: string;
|
stoken: params.receiveCode,
|
||||||
scene: string;
|
pdir_fid: "0",
|
||||||
}): Promise<{ message: string; data: unknown }> {
|
scene: "link",
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
const response = await this.api.post(
|
const response = await this.api.post(
|
||||||
`/1/clouddrive/share/sharepage/save?pr=ucpro&fr=pc&uc_param_str=&__dt=208097&__t=${Date.now()}`,
|
`/1/clouddrive/share/sharepage/save?pr=ucpro&fr=pc&uc_param_str=&__dt=208097&__t=${Date.now()}`,
|
||||||
params
|
quarkParams
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ import GlobalSetting from "../models/GlobalSetting";
|
|||||||
import { GlobalSettingAttributes } from "../models/GlobalSetting";
|
import { GlobalSettingAttributes } from "../models/GlobalSetting";
|
||||||
import * as cheerio from "cheerio";
|
import * as cheerio from "cheerio";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
import { Logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
import { injectable } from "inversify";
|
||||||
|
|
||||||
interface sourceItem {
|
interface sourceItem {
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
@@ -20,20 +21,23 @@ interface sourceItem {
|
|||||||
cloudType?: string;
|
cloudType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@injectable()
|
||||||
export class Searcher {
|
export class Searcher {
|
||||||
private axiosInstance: AxiosInstance | null = null;
|
private static instance: Searcher;
|
||||||
|
private api: AxiosInstance | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.initializeAxiosInstance();
|
this.initAxiosInstance();
|
||||||
|
Searcher.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeAxiosInstance(isUpdate = false): Promise<void> {
|
private async initAxiosInstance(isUpdate: boolean = false) {
|
||||||
let settings = null;
|
let globalSetting = {} as GlobalSettingAttributes;
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
settings = await GlobalSetting.findOne();
|
const settings = await GlobalSetting.findOne();
|
||||||
|
globalSetting = settings?.dataValues || ({} as GlobalSettingAttributes);
|
||||||
}
|
}
|
||||||
const globalSetting = settings?.dataValues || ({} as GlobalSettingAttributes);
|
this.api = createAxiosInstance(
|
||||||
this.axiosInstance = createAxiosInstance(
|
|
||||||
config.telegram.baseUrl,
|
config.telegram.baseUrl,
|
||||||
AxiosHeaders.from({
|
AxiosHeaders.from({
|
||||||
accept:
|
accept:
|
||||||
@@ -56,8 +60,9 @@ export class Searcher {
|
|||||||
: undefined
|
: undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
public async updateAxiosInstance() {
|
|
||||||
await this.initializeAxiosInstance(true);
|
public static async updateAxiosInstance(): Promise<void> {
|
||||||
|
await Searcher.instance.initAxiosInstance(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractCloudLinks(text: string): { links: string[]; cloudType: string } {
|
private extractCloudLinks(text: string): { links: string[]; cloudType: string } {
|
||||||
@@ -67,7 +72,7 @@ export class Searcher {
|
|||||||
const matches = text.match(pattern);
|
const matches = text.match(pattern);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
links.push(...matches);
|
links.push(...matches);
|
||||||
cloudType = Object.keys(config.cloudPatterns)[index];
|
if (!cloudType) cloudType = Object.keys(config.cloudPatterns)[index];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@@ -111,7 +116,7 @@ export class Searcher {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(`搜索频道 ${channel.name} 失败:`, error);
|
logger.error(`搜索频道 ${channel.name} 失败:`, error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,10 +130,10 @@ export class Searcher {
|
|||||||
|
|
||||||
async searchInWeb(url: string) {
|
async searchInWeb(url: string) {
|
||||||
try {
|
try {
|
||||||
if (!this.axiosInstance) {
|
if (!this.api) {
|
||||||
throw new Error("Axios instance is not initialized");
|
throw new Error("Axios instance is not initialized");
|
||||||
}
|
}
|
||||||
const response = await this.axiosInstance.get(url);
|
const response = await this.api.get(url);
|
||||||
const html = response.data;
|
const html = response.data;
|
||||||
const $ = cheerio.load(html);
|
const $ = cheerio.load(html);
|
||||||
const items: sourceItem[] = [];
|
const items: sourceItem[] = [];
|
||||||
@@ -205,7 +210,7 @@ export class Searcher {
|
|||||||
});
|
});
|
||||||
return { items: items, channelLogo };
|
return { items: items, channelLogo };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(`搜索错误: ${url}`, error);
|
logger.error(`搜索错误: ${url}`, error);
|
||||||
return {
|
return {
|
||||||
items: [],
|
items: [],
|
||||||
channelLogo: "",
|
channelLogo: "",
|
||||||
|
|||||||
59
backend/src/services/SettingService.ts
Normal file
59
backend/src/services/SettingService.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { injectable, inject } from "inversify";
|
||||||
|
import { TYPES } from "../core/types";
|
||||||
|
import UserSetting from "../models/UserSetting";
|
||||||
|
import GlobalSetting from "../models/GlobalSetting";
|
||||||
|
import { Searcher } from "./Searcher";
|
||||||
|
import { ImageService } from "./ImageService";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SettingService {
|
||||||
|
constructor(@inject(TYPES.ImageService) private imageService: ImageService) {}
|
||||||
|
|
||||||
|
async getSettings(userId: string | undefined, role: number | undefined) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error("用户ID无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
let userSettings = await UserSetting.findOne({ where: { userId: userId.toString() } });
|
||||||
|
if (!userSettings) {
|
||||||
|
userSettings = await UserSetting.create({
|
||||||
|
userId: userId.toString(),
|
||||||
|
cloud115Cookie: "",
|
||||||
|
quarkCookie: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const globalSetting = await GlobalSetting.findOne();
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
userSettings,
|
||||||
|
globalSetting: role === 1 ? globalSetting : null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveSettings(userId: string | undefined, role: number | undefined, settings: any) {
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error("用户ID无效");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userSettings, globalSetting } = settings;
|
||||||
|
await UserSetting.update(userSettings, { where: { userId: userId.toString() } });
|
||||||
|
|
||||||
|
if (role === 1 && globalSetting) {
|
||||||
|
await GlobalSetting.update(globalSetting, { where: {} });
|
||||||
|
}
|
||||||
|
await this.updateSettings();
|
||||||
|
return { message: "保存成功" };
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSettings(/* 参数 */): Promise<void> {
|
||||||
|
// ... 其他代码 ...
|
||||||
|
|
||||||
|
// 修改这一行,使用注入的实例方法而不是静态方法
|
||||||
|
await this.imageService.updateAxiosInstance();
|
||||||
|
await Searcher.updateAxiosInstance();
|
||||||
|
|
||||||
|
// ... 其他代码 ...
|
||||||
|
}
|
||||||
|
}
|
||||||
25
backend/src/services/SponsorsService.ts
Normal file
25
backend/src/services/SponsorsService.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { injectable } from "inversify";
|
||||||
|
import { createAxiosInstance } from "../utils/axiosInstance";
|
||||||
|
import { AxiosInstance } from "axios";
|
||||||
|
import sponsors from "../sponsors/sponsors.json";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class SponsorsService {
|
||||||
|
private axiosInstance: AxiosInstance;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.axiosInstance = createAxiosInstance("http://oss.jiangmuxin.cn/cloudsaver/");
|
||||||
|
}
|
||||||
|
async getSponsors() {
|
||||||
|
try {
|
||||||
|
const response = await this.axiosInstance.get("sponsors.json");
|
||||||
|
return {
|
||||||
|
data: response.data.sponsors,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
data: sponsors.sponsors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
backend/src/services/UserService.ts
Normal file
63
backend/src/services/UserService.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { injectable } from "inversify";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { config } from "../config";
|
||||||
|
import User from "../models/User";
|
||||||
|
import GlobalSetting from "../models/GlobalSetting";
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class UserService {
|
||||||
|
private isValidInput(input: string): boolean {
|
||||||
|
// 检查是否包含空格或汉字
|
||||||
|
const regex = /^[^\s\u4e00-\u9fa5]+$/;
|
||||||
|
return regex.test(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(username: string, password: string, registerCode: string) {
|
||||||
|
const globalSetting = await GlobalSetting.findOne();
|
||||||
|
const registerCodeList = [
|
||||||
|
globalSetting?.dataValues.CommonUserCode,
|
||||||
|
globalSetting?.dataValues.AdminUserCode,
|
||||||
|
];
|
||||||
|
if (!registerCode || !registerCodeList.includes(Number(registerCode))) {
|
||||||
|
throw new Error("注册码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
if (!this.isValidInput(username) || !this.isValidInput(password)) {
|
||||||
|
throw new Error("用户名、密码或注册码不能包含空格或汉字");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
const existingUser = await User.findOne({ where: { username } });
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error("用户名已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hashedPassword = await bcrypt.hash(password, 10);
|
||||||
|
const role = registerCodeList.findIndex((x) => x === Number(registerCode));
|
||||||
|
const user = await User.create({ username, password: hashedPassword, role });
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: user,
|
||||||
|
message: "用户注册成功",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(username: string, password: string) {
|
||||||
|
const user = await User.findOne({ where: { username } });
|
||||||
|
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||||
|
throw new Error("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = jwt.sign({ userId: user.userId, role: user.role }, config.jwtSecret, {
|
||||||
|
expiresIn: "6h",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
token,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
54
backend/src/sponsors/sponsors.json
Normal file
54
backend/src/sponsors/sponsors.json
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"sponsors": [
|
||||||
|
{
|
||||||
|
"name": "立本狗头",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks1.jpg",
|
||||||
|
"message": "怒搓楼上狗头! "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "帝国鼻屎",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks2.jpg",
|
||||||
|
"message": "芜湖起飞! "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "雷霆222",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks3.jpg",
|
||||||
|
"message": "把我弄帅点 "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "黑田奈奈子",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks4.jpg",
|
||||||
|
"message": "流年笑掷 未来可期 ",
|
||||||
|
"link": "https://github.com/htnanako"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "原野🐇",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks5.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "我摆烂!",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks6.jpg",
|
||||||
|
"message": "人生苦短,及时行乐,卷什么卷,随缘摆烂 "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "田培",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks7.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "River",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks8.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "午夜学徒",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks9.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "阿潘",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks10.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "闹闹黑",
|
||||||
|
"avatar": "http://oss.jiangmuxin.cn/cloudsaver/sponsors/thanks11.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
83
backend/src/types/cloud.ts
Normal file
83
backend/src/types/cloud.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
export interface ShareInfoResponse {
|
||||||
|
data: {
|
||||||
|
list: ShareInfoItem[];
|
||||||
|
fileSize?: number;
|
||||||
|
pwdId?: string;
|
||||||
|
stoken?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetShareInfoParams {
|
||||||
|
shareCode: string;
|
||||||
|
receiveCode?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShareInfoItem {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
fileSize?: number;
|
||||||
|
fileIdToken?: string;
|
||||||
|
}
|
||||||
|
export interface FolderListResponse {
|
||||||
|
data: {
|
||||||
|
cid: string;
|
||||||
|
name: string;
|
||||||
|
path: { cid: string; name: string }[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveFileParams {
|
||||||
|
shareCode: string; // 分享code
|
||||||
|
receiveCode?: string; // 分享文件的密码
|
||||||
|
folderId?: string; // 文件夹id
|
||||||
|
fids?: string[]; // 存储文件id
|
||||||
|
fidTokens?: string[]; // 存储文件token
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaveFileResponse {
|
||||||
|
message: string;
|
||||||
|
data: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ShareFileInfo {
|
||||||
|
shareCode: string;
|
||||||
|
receiveCode?: string;
|
||||||
|
fileId: string;
|
||||||
|
cid?: string;
|
||||||
|
fid_list?: string[];
|
||||||
|
fid_token_list?: string[];
|
||||||
|
to_pdir_fid?: string;
|
||||||
|
pwd_id?: string;
|
||||||
|
stoken?: string;
|
||||||
|
pdir_fid?: string;
|
||||||
|
scene?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuarkShareFileInfo {
|
||||||
|
fid_list: string[];
|
||||||
|
fid_token_list: string[];
|
||||||
|
to_pdir_fid: string;
|
||||||
|
pwd_id: string;
|
||||||
|
stoken: string;
|
||||||
|
pdir_fid: string;
|
||||||
|
scene: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuarkShareInfo {
|
||||||
|
stoken?: string;
|
||||||
|
pwdId?: string;
|
||||||
|
fileSize?: number;
|
||||||
|
list: {
|
||||||
|
fid: string;
|
||||||
|
file_name: string;
|
||||||
|
file_type: number;
|
||||||
|
share_fid_token: string;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QuarkFolderItem {
|
||||||
|
fid: string;
|
||||||
|
file_name: string;
|
||||||
|
file_type: number;
|
||||||
|
}
|
||||||
15
backend/src/types/index.ts
Normal file
15
backend/src/types/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export interface Config {
|
||||||
|
app: {
|
||||||
|
port: number;
|
||||||
|
env: string;
|
||||||
|
};
|
||||||
|
database: {
|
||||||
|
type: string;
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
jwt: {
|
||||||
|
secret: string;
|
||||||
|
expiresIn: string;
|
||||||
|
};
|
||||||
|
// ... 其他配置类型
|
||||||
|
}
|
||||||
9
backend/src/types/services.ts
Normal file
9
backend/src/types/services.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Request } from "express";
|
||||||
|
import { ShareInfoResponse, FolderListResponse, SaveFileParams } from "./cloud";
|
||||||
|
|
||||||
|
export interface ICloudStorageService {
|
||||||
|
setCookie(req: Request): Promise<void>;
|
||||||
|
getShareInfo(shareCode: string, receiveCode?: string): Promise<ShareInfoResponse>;
|
||||||
|
getFolderList(parentCid?: string): Promise<FolderListResponse>;
|
||||||
|
saveSharedFile(params: SaveFileParams): Promise<any>;
|
||||||
|
}
|
||||||
@@ -8,12 +8,11 @@ interface ProxyConfig {
|
|||||||
|
|
||||||
export function createAxiosInstance(
|
export function createAxiosInstance(
|
||||||
baseURL: string,
|
baseURL: string,
|
||||||
headers: AxiosRequestHeaders,
|
headers?: AxiosRequestHeaders,
|
||||||
useProxy: boolean = false,
|
useProxy: boolean = false,
|
||||||
proxyConfig?: ProxyConfig
|
proxyConfig?: ProxyConfig
|
||||||
): AxiosInstance {
|
): AxiosInstance {
|
||||||
let agent;
|
let agent;
|
||||||
console.log(proxyConfig);
|
|
||||||
if (useProxy && proxyConfig) {
|
if (useProxy && proxyConfig) {
|
||||||
agent = tunnel.httpsOverHttp({
|
agent = tunnel.httpsOverHttp({
|
||||||
proxy: proxyConfig,
|
proxy: proxyConfig,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Response, NextFunction } from "express";
|
import { Response, NextFunction } from "express";
|
||||||
import { Logger } from "../utils/logger";
|
import { logger } from "../utils/logger";
|
||||||
|
|
||||||
interface CustomError {
|
interface CustomError {
|
||||||
name?: string;
|
name?: string;
|
||||||
@@ -13,6 +13,6 @@ export default function handleError(
|
|||||||
message: string,
|
message: string,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
Logger.error(message, error);
|
logger.error(message, error);
|
||||||
next(error || { success: false, message });
|
next(error || { success: false, message });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,21 @@
|
|||||||
type LogLevel = "info" | "success" | "warn" | "error";
|
import winston from "winston";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
export const Logger = {
|
const logger = winston.createLogger({
|
||||||
info(...args: any[]) {
|
level: config.app.env === "development" ? "debug" : "info",
|
||||||
this.log("info", ...args);
|
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
||||||
},
|
transports: [
|
||||||
|
new winston.transports.File({ filename: "logs/error.log", level: "error" }),
|
||||||
|
new winston.transports.File({ filename: "logs/combined.log" }),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
success(...args: any[]) {
|
if (config.app.env !== "production") {
|
||||||
this.log("success", ...args);
|
logger.add(
|
||||||
},
|
new winston.transports.Console({
|
||||||
|
format: winston.format.simple(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
warn(...args: any[]) {
|
export { logger };
|
||||||
this.log("warn", ...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
error(...args: any[]) {
|
|
||||||
this.log("error", ...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
log(level: LogLevel, ...args: any[]) {
|
|
||||||
const timestamp = new Date().toISOString();
|
|
||||||
const prefix = `[${timestamp}] [${level.toUpperCase()}]`;
|
|
||||||
|
|
||||||
switch (level) {
|
|
||||||
case "success":
|
|
||||||
console.log(prefix, ...args);
|
|
||||||
break;
|
|
||||||
case "warn":
|
|
||||||
console.warn(prefix, ...args);
|
|
||||||
break;
|
|
||||||
case "error":
|
|
||||||
console.error(prefix, ...args);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(prefix, ...args);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -15,7 +15,9 @@
|
|||||||
"typeRoots": ["./node_modules/@types", "./src/types"],
|
"typeRoots": ["./node_modules/@types", "./src/types"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"]
|
"@/*": ["src/*"]
|
||||||
}
|
},
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cloud-disk-web",
|
"name": "cloud-disk-web",
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cloud-disk-web",
|
"name": "cloud-disk-web",
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"element-plus": "^2.6.1",
|
"element-plus": "^2.6.1",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "cloud-saver-web",
|
"name": "cloud-saver-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
@@ -12,8 +12,10 @@
|
|||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"element-plus": "^2.6.1",
|
"element-plus": "^2.6.1",
|
||||||
|
"gsap": "^3.12.7",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
|
"typeit": "^8.8.7",
|
||||||
"vant": "^4.9.17",
|
"vant": "^4.9.17",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import type { ShareInfoResponse, Folder, Save115FileParams } from "@/types";
|
import type { ShareInfoResponse, Folder, SaveFileParams, GetShareInfoParams } from "@/types";
|
||||||
|
|
||||||
export const cloud115Api = {
|
export const cloud115Api = {
|
||||||
async getShareInfo(shareCode: string, receiveCode = "") {
|
async getShareInfo(params: GetShareInfoParams) {
|
||||||
const { data } = await request.get<ShareInfoResponse>("/api/cloud115/share-info", {
|
const { data } = await request.get<ShareInfoResponse>("/api/cloud115/share-info", {
|
||||||
params: { shareCode, receiveCode },
|
params,
|
||||||
});
|
});
|
||||||
return data as ShareInfoResponse;
|
return data as ShareInfoResponse;
|
||||||
},
|
},
|
||||||
@@ -16,7 +16,7 @@ export const cloud115Api = {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveFile(params: Save115FileParams) {
|
async saveFile(params: SaveFileParams) {
|
||||||
const res = await request.post("/api/cloud115/save", params);
|
const res = await request.post("/api/cloud115/save", params);
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
import type { ShareInfoResponse, Folder, SaveQuarkFileParams } from "@/types";
|
import type { ShareInfoResponse, Folder, SaveFileParams, GetShareInfoParams } from "@/types";
|
||||||
|
|
||||||
export const quarkApi = {
|
export const quarkApi = {
|
||||||
async getShareInfo(pwdId: string, passcode = "") {
|
async getShareInfo(params: GetShareInfoParams) {
|
||||||
const { data } = await request.get<ShareInfoResponse>("/api/quark/share-info", {
|
const { data } = await request.get<ShareInfoResponse>("/api/quark/share-info", {
|
||||||
params: { pwdId, passcode },
|
params,
|
||||||
});
|
});
|
||||||
return data as ShareInfoResponse;
|
return data as ShareInfoResponse;
|
||||||
},
|
},
|
||||||
@@ -16,7 +16,7 @@ export const quarkApi = {
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveFile(params: SaveQuarkFileParams) {
|
async saveFile(params: SaveFileParams) {
|
||||||
return await request.post("/api/quark/save", params);
|
return await request.post("/api/quark/save", params);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,4 +7,7 @@ export const userApi = {
|
|||||||
register: (data: { username: string; password: string; registerCode: string }) => {
|
register: (data: { username: string; password: string; registerCode: string }) => {
|
||||||
return request.post<{ token: string }>("/api/user/register", data);
|
return request.post<{ token: string }>("/api/user/register", data);
|
||||||
},
|
},
|
||||||
|
getSponsors: () => {
|
||||||
|
return request.get("/api/sponsors?timestamp=" + Date.now());
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
BIN
frontend/src/assets/images/default.png
Normal file
BIN
frontend/src/assets/images/default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -45,12 +45,7 @@
|
|||||||
|
|
||||||
<!-- GitHub 链接 -->
|
<!-- GitHub 链接 -->
|
||||||
<div class="pc-aside__footer">
|
<div class="pc-aside__footer">
|
||||||
<a
|
<a :href="PROJECT_GITHUB" target="_blank" rel="noopener noreferrer" class="github-link">
|
||||||
href="https://github.com/jiangrui1994/CloudSaver"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="github-link"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
height="20"
|
height="20"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@@ -76,6 +71,7 @@ import { computed } from "vue";
|
|||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { Search, Film, Setting, Link } from "@element-plus/icons-vue";
|
import { Search, Film, Setting, Link } from "@element-plus/icons-vue";
|
||||||
import logo from "@/assets/images/logo.png";
|
import logo from "@/assets/images/logo.png";
|
||||||
|
import { PROJECT_GITHUB } from "@/constants/project";
|
||||||
import pkg from "../../package.json";
|
import pkg from "../../package.json";
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
@@ -134,6 +130,12 @@ const menuList: MenuItem[] = [
|
|||||||
router: "/setting",
|
router: "/setting",
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
index: "4",
|
||||||
|
title: "鸣谢",
|
||||||
|
icon: Link,
|
||||||
|
router: "/thanks",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 计算当前激活的菜单
|
// 计算当前激活的菜单
|
||||||
|
|||||||
@@ -12,12 +12,8 @@
|
|||||||
<div class="detail-cover">
|
<div class="detail-cover">
|
||||||
<el-image
|
<el-image
|
||||||
class="cover-image"
|
class="cover-image"
|
||||||
:src="
|
:src="getProxyImageUrl(currentResource.image as string)"
|
||||||
userStore.imagesSource === 'proxy'
|
:fit="currentResource.image ? 'cover' : 'contain'"
|
||||||
? `/tele-images/?url=${encodeURIComponent(currentResource.image as string)}`
|
|
||||||
: currentResource.image
|
|
||||||
"
|
|
||||||
fit="cover"
|
|
||||||
/>
|
/>
|
||||||
<el-tag
|
<el-tag
|
||||||
class="cloud-type"
|
class="cloud-type"
|
||||||
@@ -36,7 +32,6 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="detail-description" v-html="currentResource.content" />
|
<div class="detail-description" v-html="currentResource.content" />
|
||||||
<div v-if="currentResource.tags?.length" class="detail-tags">
|
<div v-if="currentResource.tags?.length" class="detail-tags">
|
||||||
<span class="tags-label">标签:</span>
|
|
||||||
<div class="tags-list">
|
<div class="tags-list">
|
||||||
<el-tag
|
<el-tag
|
||||||
v-for="tag in currentResource.tags"
|
v-for="tag in currentResource.tags"
|
||||||
@@ -52,7 +47,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="dialog-footer">
|
<div class="dialog-footer">
|
||||||
<el-button type="primary" @click="currentResource && handleSave(currentResource)"
|
<el-button type="primary" plain @click="currentResource && handleJump(currentResource)"
|
||||||
|
>跳转</el-button
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="currentResource?.isSupportSave"
|
||||||
|
type="primary"
|
||||||
|
@click="currentResource && handleSave(currentResource)"
|
||||||
>转存</el-button
|
>转存</el-button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +61,10 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<div v-for="group in store.resources" :key="group.id" class="resource-group">
|
<div v-for="group in store.resources" :key="group.id" class="resource-group">
|
||||||
<div class="group-header" @click="group.displayList = !group.displayList">
|
<div
|
||||||
|
:class="{ 'group-header': true, 'is-active': group.displayList }"
|
||||||
|
@click="group.displayList = !group.displayList"
|
||||||
|
>
|
||||||
<el-link
|
<el-link
|
||||||
class="group-title"
|
class="group-title"
|
||||||
:href="`https://t.me/s/${group.id}`"
|
:href="`https://t.me/s/${group.id}`"
|
||||||
@@ -69,14 +73,10 @@
|
|||||||
@click.stop
|
@click.stop
|
||||||
>
|
>
|
||||||
<el-image
|
<el-image
|
||||||
:src="
|
:src="getProxyImageUrl(group.channelInfo.channelLogo)"
|
||||||
userStore.imagesSource === 'proxy'
|
:fit="group.channelInfo.channelLogo ? 'cover' : 'contain'"
|
||||||
? `/tele-images/?url=${encodeURIComponent(group.channelInfo.channelLogo)}`
|
|
||||||
: group.channelInfo.channelLogo
|
|
||||||
"
|
|
||||||
class="channel-logo"
|
class="channel-logo"
|
||||||
scroll-container="#pc-resources-content"
|
scroll-container="#pc-resources-content"
|
||||||
fit="cover"
|
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<span>{{ group.channelInfo.name }}</span>
|
<span>{{ group.channelInfo.name }}</span>
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
</el-link>
|
</el-link>
|
||||||
|
|
||||||
<el-tooltip effect="dark" :content="group.displayList ? '收起' : '展开'" placement="top">
|
<el-tooltip effect="dark" :content="group.displayList ? '收起' : '展开'" placement="top">
|
||||||
<el-button class="toggle-btn" type="text" @click="group.displayList = !group.displayList">
|
<el-button class="toggle-btn" type="text">
|
||||||
<el-icon :class="{ 'is-active': group.displayList }">
|
<el-icon :class="{ 'is-active': group.displayList }">
|
||||||
<ArrowDown />
|
<ArrowDown />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
@@ -92,7 +92,7 @@
|
|||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-show="group.displayList" class="group-content">
|
<div v-if="group.displayList" class="group-content">
|
||||||
<div class="card-grid">
|
<div class="card-grid">
|
||||||
<el-card
|
<el-card
|
||||||
v-for="resource in group.list"
|
v-for="resource in group.list"
|
||||||
@@ -105,12 +105,8 @@
|
|||||||
<el-image
|
<el-image
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="cover-image"
|
class="cover-image"
|
||||||
:src="
|
:src="getProxyImageUrl(resource.image as string)"
|
||||||
userStore.imagesSource === 'proxy'
|
:fit="resource.image ? 'cover' : 'contain'"
|
||||||
? `/tele-images/?url=${encodeURIComponent(resource.image as string)}`
|
|
||||||
: resource.image
|
|
||||||
"
|
|
||||||
fit="cover"
|
|
||||||
:alt="resource.title"
|
:alt="resource.title"
|
||||||
@click="showResourceDetail(resource)"
|
@click="showResourceDetail(resource)"
|
||||||
/>
|
/>
|
||||||
@@ -142,7 +138,6 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div v-if="resource.tags?.length" class="card-tags">
|
<div v-if="resource.tags?.length" class="card-tags">
|
||||||
<span class="tags-label">标签:</span>
|
|
||||||
<div class="tags-list">
|
<div class="tags-list">
|
||||||
<el-tag
|
<el-tag
|
||||||
v-for="tag in resource.tags"
|
v-for="tag in resource.tags"
|
||||||
@@ -156,7 +151,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<el-button type="primary" @click="handleSave(resource)">转存</el-button>
|
<el-button type="primary" plain @click="handleJump(resource)">跳转</el-button>
|
||||||
|
<el-button
|
||||||
|
v-if="resource.isSupportSave"
|
||||||
|
type="primary"
|
||||||
|
@click="handleSave(resource)"
|
||||||
|
>转存</el-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -179,15 +180,14 @@ import { useResourceStore } from "@/stores/resource";
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import type { ResourceItem, TagColor } from "@/types";
|
import type { ResourceItem, TagColor } from "@/types";
|
||||||
import { ArrowDown, Plus } from "@element-plus/icons-vue";
|
import { ArrowDown, Plus } from "@element-plus/icons-vue";
|
||||||
import { useUserSettingStore } from "@/stores/userSetting";
|
import { getProxyImageUrl } from "@/utils/image";
|
||||||
|
|
||||||
const userStore = useUserSettingStore();
|
|
||||||
const store = useResourceStore();
|
const store = useResourceStore();
|
||||||
|
|
||||||
const showDetail = ref(false);
|
const showDetail = ref(false);
|
||||||
const currentResource = ref<ResourceItem | null>(null);
|
const currentResource = ref<ResourceItem | null>(null);
|
||||||
|
|
||||||
const emit = defineEmits(["save", "loadMore", "searchMovieforTag"]);
|
const emit = defineEmits(["save", "loadMore", "jump", "searchMovieforTag"]);
|
||||||
|
|
||||||
const handleSave = (resource: ResourceItem) => {
|
const handleSave = (resource: ResourceItem) => {
|
||||||
if (showDetail.value) {
|
if (showDetail.value) {
|
||||||
@@ -196,6 +196,10 @@ const handleSave = (resource: ResourceItem) => {
|
|||||||
emit("save", resource);
|
emit("save", resource);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleJump = (resource: ResourceItem) => {
|
||||||
|
emit("jump", resource);
|
||||||
|
};
|
||||||
|
|
||||||
const showResourceDetail = (resource: ResourceItem) => {
|
const showResourceDetail = (resource: ResourceItem) => {
|
||||||
currentResource.value = resource;
|
currentResource.value = resource;
|
||||||
showDetail.value = true;
|
showDetail.value = true;
|
||||||
@@ -244,10 +248,14 @@ const handleLoadMore = (channelId: string) => {
|
|||||||
backdrop-filter: var(--theme-blur);
|
backdrop-filter: var(--theme-blur);
|
||||||
-webkit-backdrop-filter: var(--theme-blur);
|
-webkit-backdrop-filter: var(--theme-blur);
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
border-radius: var(--theme-radius) var(--theme-radius) 0 0;
|
border-radius: var(--theme-radius);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-radius: var(--theme-radius) var(--theme-radius) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
.group-title {
|
.group-title {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -306,7 +314,7 @@ const handleLoadMore = (channelId: string) => {
|
|||||||
// 卡片网格
|
// 卡片网格
|
||||||
.card-grid {
|
.card-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
grid-auto-rows: min-content;
|
grid-auto-rows: min-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,45 +5,27 @@
|
|||||||
:data="store.resources"
|
:data="store.resources"
|
||||||
style="width: 100%"
|
style="width: 100%"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:default-expand-all="true"
|
:default-expand-all="false"
|
||||||
>
|
>
|
||||||
<el-table-column type="expand">
|
<el-table-column type="expand">
|
||||||
<template #default="props">
|
<template #default="props">
|
||||||
<el-table :data="props.row.list" style="width: 100%">
|
<el-table :data="props.row.list" style="width: 100%">
|
||||||
<el-table-column label="图片" width="180">
|
<el-table-column label="图片" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-image
|
<el-image
|
||||||
v-if="row.image"
|
v-if="row.image"
|
||||||
class="table-item-image"
|
class="table-item-image"
|
||||||
:src="
|
:src="getProxyImageUrl(row.image as string)"
|
||||||
userStore.imagesSource === 'proxy'
|
:fit="row.image ? 'cover' : 'contain'"
|
||||||
? `/tele-images/?url=${encodeURIComponent(row.image as string)}`
|
width="30"
|
||||||
: row.image
|
height="60"
|
||||||
"
|
|
||||||
hide-on-click-modal
|
|
||||||
:preview-src-list="[
|
|
||||||
`${location.origin}${
|
|
||||||
userStore.imagesSource === 'proxy'
|
|
||||||
? '/tele-images/?url=' + encodeURIComponent(row.image as string)
|
|
||||||
: row.image
|
|
||||||
}`,
|
|
||||||
]"
|
|
||||||
:zoom-rate="1.2"
|
|
||||||
:max-scale="7"
|
|
||||||
:min-scale="0.2"
|
|
||||||
:initial-index="4"
|
|
||||||
preview-teleported
|
|
||||||
:z-index="999"
|
|
||||||
fit="cover"
|
|
||||||
width="60"
|
|
||||||
height="90"
|
|
||||||
/>
|
/>
|
||||||
<el-icon v-else size="20"><Close /></el-icon>
|
<el-icon v-else size="20"><Close /></el-icon>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="title" label="标题" width="180">
|
<el-table-column prop="title" label="标题" width="280">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-link :href="row.cloudLinks[0]" target="_blank">
|
<el-link :href="row.cloudLinks[0]" target="_blank" style="font-weight: bold">
|
||||||
{{ row.title }}
|
{{ row.title }}
|
||||||
</el-link>
|
</el-link>
|
||||||
</template>
|
</template>
|
||||||
@@ -78,7 +60,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="180">
|
<el-table-column label="操作" width="180">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button @click="handleSave(row)">转存</el-button>
|
<el-button type="primary" plain @click="handleJump(row)">跳转</el-button>
|
||||||
|
<el-button v-if="row.isSupportSave" @click="handleSave(row)">转存</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -93,13 +76,9 @@
|
|||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="group-header">
|
<div class="group-header">
|
||||||
<el-image
|
<el-image
|
||||||
:src="
|
:src="getProxyImageUrl(row.channelInfo.channelLogo as string)"
|
||||||
userStore.imagesSource === 'proxy'
|
|
||||||
? `/tele-images/?url=${encodeURIComponent(row.channelInfo.channelLogo as string)}`
|
|
||||||
: row.channelInfo.channelLogo
|
|
||||||
"
|
|
||||||
class="channel-logo"
|
class="channel-logo"
|
||||||
fit="cover"
|
:fit="row.channelInfo.channelLogo ? 'cover' : 'contain'"
|
||||||
lazy
|
lazy
|
||||||
/>
|
/>
|
||||||
<span>{{ row.channelInfo.name }}</span>
|
<span>{{ row.channelInfo.name }}</span>
|
||||||
@@ -113,20 +92,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useResourceStore } from "@/stores/resource";
|
import { useResourceStore } from "@/stores/resource";
|
||||||
import type { Resource, TagColor } from "@/types";
|
import type { Resource, TagColor } from "@/types";
|
||||||
import { computed } from "vue";
|
import { getProxyImageUrl } from "@/utils/image";
|
||||||
import { useUserSettingStore } from "@/stores/userSetting";
|
|
||||||
const userStore = useUserSettingStore();
|
|
||||||
|
|
||||||
const store = useResourceStore();
|
const store = useResourceStore();
|
||||||
|
const emit = defineEmits(["save", "loadMore", "searchMovieforTag", "jump"]);
|
||||||
const emit = defineEmits(["save", "loadMore", "searchMovieforTag"]);
|
|
||||||
|
|
||||||
const location = computed(() => window.location);
|
|
||||||
|
|
||||||
const handleSave = (resource: Resource) => {
|
const handleSave = (resource: Resource) => {
|
||||||
emit("save", resource);
|
emit("save", resource);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleJump = (resource: Resource) => {
|
||||||
|
emit("jump", resource);
|
||||||
|
};
|
||||||
|
|
||||||
// 添加加载更多处理函数
|
// 添加加载更多处理函数
|
||||||
const handleLoadMore = (channelId: string) => {
|
const handleLoadMore = (channelId: string) => {
|
||||||
emit("loadMore", channelId);
|
emit("loadMore", channelId);
|
||||||
@@ -156,9 +134,8 @@ const searchMovieforTag = (tag: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table-item-image {
|
.table-item-image {
|
||||||
border-radius: 20px;
|
border-radius: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 220px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-count {
|
.item-count {
|
||||||
@@ -181,8 +158,8 @@ const searchMovieforTag = (tag: string) => {
|
|||||||
margin: 15px 0;
|
margin: 15px 0;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
line-clamp: 4;
|
line-clamp: 2;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 2;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: all;
|
white-space: all;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<!-- 左侧图片 -->
|
<!-- 左侧图片 -->
|
||||||
<div class="content__image">
|
<div class="content__image">
|
||||||
<van-image
|
<van-image
|
||||||
:src="`/tele-images/?url=${encodeURIComponent(item.image as string)}`"
|
:src="getProxyImageUrl(item.image as string)"
|
||||||
fit="cover"
|
:fit="item.image ? 'cover' : 'contain'"
|
||||||
lazy-load
|
lazy-load
|
||||||
/>
|
/>
|
||||||
<!-- 来源标签移到图片左上角 -->
|
<!-- 来源标签移到图片左上角 -->
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<!-- 右侧信息 -->
|
<!-- 右侧信息 -->
|
||||||
<div class="content__info">
|
<div class="content__info">
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<div class="info__title" @click="openUrl(item.cloudLinks[0])">
|
<div class="info__title" @click="copyUrl(item.cloudLinks[0])">
|
||||||
{{ item.title }}
|
{{ item.title }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -50,9 +50,17 @@
|
|||||||
|
|
||||||
<!-- 转存按钮 -->
|
<!-- 转存按钮 -->
|
||||||
<div class="info__action">
|
<div class="info__action">
|
||||||
<van-button type="primary" size="mini" round @click="handleSave(item)">
|
<van-button type="primary" size="mini" round plain @click="handleJump(item)">
|
||||||
转存
|
跳转
|
||||||
</van-button>
|
</van-button>
|
||||||
|
<van-button
|
||||||
|
v-if="item.isSupportSave"
|
||||||
|
type="primary"
|
||||||
|
size="mini"
|
||||||
|
round
|
||||||
|
@click="handleSave(item)"
|
||||||
|
>转存</van-button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,7 +72,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { useResourceStore } from "@/stores/resource";
|
import { useResourceStore } from "@/stores/resource";
|
||||||
|
import { showNotify } from "vant";
|
||||||
import type { ResourceItem } from "@/types";
|
import type { ResourceItem } from "@/types";
|
||||||
|
import { getProxyImageUrl } from "@/utils/image";
|
||||||
|
|
||||||
// Props 定义
|
// Props 定义
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -74,6 +84,7 @@ const props = defineProps<{
|
|||||||
// 事件定义
|
// 事件定义
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "save", resource: ResourceItem): void;
|
(e: "save", resource: ResourceItem): void;
|
||||||
|
(e: "jump", resource: ResourceItem): void;
|
||||||
(e: "searchMovieforTag", tag: string): void;
|
(e: "searchMovieforTag", tag: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
@@ -100,8 +111,32 @@ const handleSave = (resource: ResourceItem) => {
|
|||||||
emit("save", resource);
|
emit("save", resource);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openUrl = (url: string) => {
|
const handleJump = (resource: ResourceItem) => {
|
||||||
window.open(url);
|
emit("jump", resource);
|
||||||
|
};
|
||||||
|
|
||||||
|
const copyUrl = async (url: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(url);
|
||||||
|
showNotify({
|
||||||
|
type: "success",
|
||||||
|
message: "链接已复制到剪贴板",
|
||||||
|
duration: 1500,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.value = url;
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(input);
|
||||||
|
|
||||||
|
showNotify({
|
||||||
|
type: "success",
|
||||||
|
message: "链接已复制到剪贴板",
|
||||||
|
duration: 1500,
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchMovieforTag = (tag: string) => {
|
const searchMovieforTag = (tag: string) => {
|
||||||
@@ -187,7 +222,7 @@ const toggleExpand = (id: string) => {
|
|||||||
@include text-ellipsis(2);
|
@include text-ellipsis(2);
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
color: var(--theme-theme);
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
2
frontend/src/constants/project.ts
Normal file
2
frontend/src/constants/project.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const PROJECT_NAME = "Cloudsaver";
|
||||||
|
export const PROJECT_GITHUB = "https://github.com/jiangrui1994/cloudsaver";
|
||||||
@@ -3,6 +3,7 @@ import { createPinia } from "pinia";
|
|||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
import "element-plus/dist/index.css";
|
import "element-plus/dist/index.css";
|
||||||
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
import * as ElementPlusIconsVue from "@element-plus/icons-vue";
|
||||||
|
import zhCn from "element-plus/es/locale/lang/zh-cn";
|
||||||
import { isMobileDevice } from "@/utils/index";
|
import { isMobileDevice } from "@/utils/index";
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import { Lazyload } from "vant";
|
import { Lazyload } from "vant";
|
||||||
@@ -22,7 +23,9 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(Lazyload);
|
app.use(Lazyload);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
app.use(ElementPlus);
|
app.use(ElementPlus, {
|
||||||
|
locale: zhCn,
|
||||||
|
});
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: "setting",
|
name: "setting",
|
||||||
component: () => import("@/views/mobile/Setting.vue"),
|
component: () => import("@/views/mobile/Setting.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/thanks",
|
||||||
|
name: "thanks",
|
||||||
|
redirect: "/resource",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ const routes: RouteRecordRaw[] = [
|
|||||||
name: "setting",
|
name: "setting",
|
||||||
component: () => import("@/views/Setting.vue"),
|
component: () => import("@/views/Setting.vue"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/thanks",
|
||||||
|
name: "thanks",
|
||||||
|
component: () => import("@/views/Thanks.vue"),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import { quarkApi } from "@/api/quark";
|
|||||||
import type {
|
import type {
|
||||||
Resource,
|
Resource,
|
||||||
ShareInfoResponse,
|
ShareInfoResponse,
|
||||||
Save115FileParams,
|
|
||||||
SaveQuarkFileParams,
|
|
||||||
ShareInfo,
|
ShareInfo,
|
||||||
ResourceItem,
|
ResourceItem,
|
||||||
|
GetShareInfoParams,
|
||||||
|
SaveFileParams,
|
||||||
|
ShareFileInfoAndFolder,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
|
|
||||||
@@ -24,46 +25,40 @@ const lastResource = (
|
|||||||
) as StorageListObject;
|
) as StorageListObject;
|
||||||
|
|
||||||
// 定义云盘驱动配置类型
|
// 定义云盘驱动配置类型
|
||||||
interface CloudDriveConfig<
|
interface CloudDriveConfig {
|
||||||
T extends Record<string, string>,
|
|
||||||
P extends Save115FileParams | SaveQuarkFileParams,
|
|
||||||
> {
|
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
regex: RegExp;
|
regex: RegExp;
|
||||||
api: {
|
api: {
|
||||||
getShareInfo: (parsedCode: T) => Promise<ShareInfoResponse>;
|
getShareInfo: (params: GetShareInfoParams) => Promise<ShareInfoResponse>;
|
||||||
saveFile: (params: P) => Promise<{ code: number; message?: string }>;
|
saveFile: (params: SaveFileParams) => Promise<{ code: number; message?: string }>;
|
||||||
};
|
};
|
||||||
parseShareCode: (match: RegExpMatchArray) => T;
|
parseShareCode: (match: RegExpMatchArray) => GetShareInfoParams;
|
||||||
getSaveParams: (shareInfo: ShareInfoResponse, folderId: string) => P;
|
getSaveParams: (shareInfoAndFolder: ShareFileInfoAndFolder) => SaveFileParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 云盘类型配置
|
// 云盘类型配置
|
||||||
export const CLOUD_DRIVES: [
|
export const CLOUD_DRIVES: CloudDriveConfig[] = [
|
||||||
CloudDriveConfig<{ shareCode: string; receiveCode: string }, Save115FileParams>,
|
|
||||||
CloudDriveConfig<{ pwdId: string }, SaveQuarkFileParams>,
|
|
||||||
] = [
|
|
||||||
{
|
{
|
||||||
name: "115网盘",
|
name: "115网盘",
|
||||||
type: "pan115",
|
type: "pan115",
|
||||||
regex: /(?:115|anxia|115cdn)\.com\/s\/([^?]+)(?:\?password=([^&#]+))?/,
|
regex: /(?:115|anxia|115cdn)\.com\/s\/([^?]+)(?:\?password=([^&#]+))?/,
|
||||||
api: {
|
api: {
|
||||||
getShareInfo: (parsedCode: { shareCode: string; receiveCode: string }) =>
|
getShareInfo: (params: GetShareInfoParams) => cloud115Api.getShareInfo(params),
|
||||||
cloud115Api.getShareInfo(parsedCode.shareCode, parsedCode.receiveCode),
|
saveFile: async (params: SaveFileParams) => {
|
||||||
saveFile: async (params: Save115FileParams) => {
|
return await cloud115Api.saveFile(params);
|
||||||
return await cloud115Api.saveFile(params as Save115FileParams);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parseShareCode: (match: RegExpMatchArray) => ({
|
parseShareCode: (match: RegExpMatchArray) => ({
|
||||||
shareCode: match[1],
|
shareCode: match[1],
|
||||||
receiveCode: match[2] || "",
|
receiveCode: match[2] || "",
|
||||||
}),
|
}),
|
||||||
getSaveParams: (shareInfo: ShareInfoResponse, folderId: string) => ({
|
getSaveParams: (shareInfoAndFolder: ShareFileInfoAndFolder) => ({
|
||||||
shareCode: shareInfo.shareCode || "",
|
shareCode: shareInfoAndFolder.shareCode || "",
|
||||||
receiveCode: shareInfo.receiveCode || "",
|
receiveCode: shareInfoAndFolder.receiveCode || "",
|
||||||
fileId: shareInfo.list[0].fileId,
|
fileId: shareInfoAndFolder.shareInfo.list[0].fileId,
|
||||||
folderId,
|
folderId: shareInfoAndFolder.folderId,
|
||||||
|
fids: shareInfoAndFolder.shareInfo.list.map((item: { fileId?: string }) => item.fileId || ""),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -71,22 +66,20 @@ export const CLOUD_DRIVES: [
|
|||||||
type: "quark",
|
type: "quark",
|
||||||
regex: /pan\.quark\.cn\/s\/([a-zA-Z0-9]+)/,
|
regex: /pan\.quark\.cn\/s\/([a-zA-Z0-9]+)/,
|
||||||
api: {
|
api: {
|
||||||
getShareInfo: (parsedCode: { pwdId: string }) => quarkApi.getShareInfo(parsedCode.pwdId),
|
getShareInfo: (params) => quarkApi.getShareInfo(params),
|
||||||
saveFile: async (params: SaveQuarkFileParams) => {
|
saveFile: async (params: SaveFileParams) => {
|
||||||
return await quarkApi.saveFile(params as SaveQuarkFileParams);
|
return await quarkApi.saveFile(params);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parseShareCode: (match: RegExpMatchArray) => ({ pwdId: match[1] }),
|
parseShareCode: (match: RegExpMatchArray) => ({ shareCode: match[1] }),
|
||||||
getSaveParams: (shareInfo: ShareInfoResponse, folderId: string) => ({
|
getSaveParams: (shareInfoAndFolder: ShareFileInfoAndFolder) => ({
|
||||||
fid_list: shareInfo.list.map((item: { fileId?: string }) => item.fileId || ""),
|
fids: shareInfoAndFolder.shareInfo.list.map((item: { fileId?: string }) => item.fileId || ""),
|
||||||
fid_token_list: shareInfo.list.map(
|
fidTokens: shareInfoAndFolder.shareInfo.list.map(
|
||||||
(item: { fileIdToken?: string }) => item.fileIdToken || ""
|
(item: { fileIdToken?: string }) => item.fileIdToken || ""
|
||||||
),
|
),
|
||||||
to_pdir_fid: folderId,
|
folderId: shareInfoAndFolder.folderId,
|
||||||
pwd_id: shareInfo.pwdId || "",
|
shareCode: shareInfoAndFolder.shareInfo.pwdId || "",
|
||||||
stoken: shareInfo.stoken || "",
|
receiveCode: shareInfoAndFolder.shareInfo.stoken || "",
|
||||||
pdir_fid: "0",
|
|
||||||
scene: "link",
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -135,7 +128,16 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
}
|
}
|
||||||
let { data = [] } = await resourceApi.search(keyword || "", channelId, lastMessageId);
|
let { data = [] } = await resourceApi.search(keyword || "", channelId, lastMessageId);
|
||||||
this.keyword = keyword || "";
|
this.keyword = keyword || "";
|
||||||
data = data.filter((item) => item.list.length > 0);
|
data = data
|
||||||
|
.filter((item) => item.list.length > 0)
|
||||||
|
.map((x) => ({
|
||||||
|
...x,
|
||||||
|
list: x.list.map((item) => ({
|
||||||
|
...item,
|
||||||
|
isSupportSave: CLOUD_DRIVES.some((drive) => drive.regex.test(item.cloudLinks[0])),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
console.log(data);
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
const findedIndex = this.resources.findIndex((item) => item.id === data[0]?.id);
|
const findedIndex = this.resources.findIndex((item) => item.id === data[0]?.id);
|
||||||
if (findedIndex !== -1) {
|
if (findedIndex !== -1) {
|
||||||
@@ -188,39 +190,32 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
async saveResourceToDrive(
|
async saveResourceToDrive(
|
||||||
resource: ResourceItem,
|
resource: ResourceItem,
|
||||||
folderId: string,
|
folderId: string,
|
||||||
drive:
|
drive: CloudDriveConfig
|
||||||
| CloudDriveConfig<{ shareCode: string; receiveCode: string }, Save115FileParams>
|
|
||||||
| CloudDriveConfig<{ pwdId: string }, SaveQuarkFileParams>
|
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const link = resource.cloudLinks.find((link) => drive.regex.test(link));
|
const link = resource.cloudLinks.find((link) => drive.regex.test(link));
|
||||||
if (!link) return;
|
if (!link) return;
|
||||||
|
|
||||||
const match = link.match(drive.regex);
|
const match = link.match(drive.regex);
|
||||||
if (!match) throw new Error("链接解析失败");
|
if (!match) throw new Error("链接解析失败");
|
||||||
|
const parsedCode = drive.parseShareCode(match);
|
||||||
|
|
||||||
const shareInfo = {
|
const shareInfo = {
|
||||||
...this.shareInfo,
|
...this.shareInfo,
|
||||||
list: this.resourceSelect.filter((x) => x.isChecked),
|
list: this.resourceSelect.filter((x) => x.isChecked),
|
||||||
};
|
};
|
||||||
|
console.log(shareInfo);
|
||||||
|
|
||||||
if (this.is115Drive(drive)) {
|
const params = drive.getSaveParams({
|
||||||
const params = drive.getSaveParams(shareInfo, folderId);
|
shareInfo,
|
||||||
const result = await drive.api.saveFile(params);
|
...parsedCode,
|
||||||
|
folderId,
|
||||||
|
});
|
||||||
|
const result = await drive.api.saveFile(params);
|
||||||
|
|
||||||
if (result.code === 0) {
|
if (result.code === 0) {
|
||||||
ElMessage.success(`${drive.name} 转存成功`);
|
ElMessage.success(`${drive.name} 转存成功`);
|
||||||
} else {
|
|
||||||
ElMessage.error(result.message);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const params = drive.getSaveParams(shareInfo, folderId);
|
ElMessage.error(result.message);
|
||||||
const result = await drive.api.saveFile(params);
|
|
||||||
|
|
||||||
if (result.code === 0) {
|
|
||||||
ElMessage.success(`${drive.name} 转存成功`);
|
|
||||||
} else {
|
|
||||||
ElMessage.error(result.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -236,18 +231,7 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
if (!match) throw new Error("链接解析失败");
|
if (!match) throw new Error("链接解析失败");
|
||||||
|
|
||||||
const parsedCode = matchedDrive.parseShareCode(match);
|
const parsedCode = matchedDrive.parseShareCode(match);
|
||||||
let shareInfo = this.is115Drive(matchedDrive)
|
const shareInfo = await matchedDrive.api.getShareInfo(parsedCode);
|
||||||
? await matchedDrive.api.getShareInfo(
|
|
||||||
parsedCode as { shareCode: string; receiveCode: string }
|
|
||||||
)
|
|
||||||
: await matchedDrive.api.getShareInfo(parsedCode as { pwdId: string });
|
|
||||||
|
|
||||||
if (Array.isArray(shareInfo)) {
|
|
||||||
shareInfo = {
|
|
||||||
list: shareInfo,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shareInfo?.list?.length) {
|
if (shareInfo?.list?.length) {
|
||||||
this.resources = [
|
this.resources = [
|
||||||
{
|
{
|
||||||
@@ -266,6 +250,7 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
cloudType: matchedDrive.type,
|
cloudType: matchedDrive.type,
|
||||||
channel: matchedDrive.name,
|
channel: matchedDrive.name,
|
||||||
pubDate: "",
|
pubDate: "",
|
||||||
|
isSupportSave: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -295,30 +280,15 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
if (!match) throw new Error("链接解析失败");
|
if (!match) throw new Error("链接解析失败");
|
||||||
|
|
||||||
const parsedCode = drive.parseShareCode(match);
|
const parsedCode = drive.parseShareCode(match);
|
||||||
let shareInfo = {} as ShareInfoResponse;
|
|
||||||
this.setLoadTree(true);
|
this.setLoadTree(true);
|
||||||
if (this.is115Drive(drive)) {
|
let shareInfo = await drive.api.getShareInfo(parsedCode);
|
||||||
shareInfo = await drive.api.getShareInfo(
|
console.log(shareInfo);
|
||||||
parsedCode as { shareCode: string; receiveCode: string }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
shareInfo = this.is115Drive(drive)
|
|
||||||
? await drive.api.getShareInfo(parsedCode as { shareCode: string; receiveCode: string })
|
|
||||||
: await drive.api.getShareInfo(parsedCode as { pwdId: string });
|
|
||||||
}
|
|
||||||
this.setLoadTree(false);
|
this.setLoadTree(false);
|
||||||
if (shareInfo) {
|
if (shareInfo) {
|
||||||
if (Array.isArray(shareInfo)) {
|
shareInfo = {
|
||||||
shareInfo = {
|
...shareInfo,
|
||||||
list: shareInfo,
|
...parsedCode,
|
||||||
...parsedCode,
|
};
|
||||||
};
|
|
||||||
} else {
|
|
||||||
shareInfo = {
|
|
||||||
...shareInfo,
|
|
||||||
...parsedCode,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.shareInfo = shareInfo;
|
this.shareInfo = shareInfo;
|
||||||
this.setSelectedResource(this.shareInfo.list.map((x) => ({ ...x, isChecked: true })));
|
this.setSelectedResource(this.shareInfo.list.map((x) => ({ ...x, isChecked: true })));
|
||||||
return true;
|
return true;
|
||||||
@@ -333,13 +303,5 @@ export const useResourceStore = defineStore("resource", {
|
|||||||
console.error(message, error);
|
console.error(message, error);
|
||||||
ElMessage.error(error instanceof Error ? error.message : message);
|
ElMessage.error(error instanceof Error ? error.message : message);
|
||||||
},
|
},
|
||||||
|
|
||||||
is115Drive(
|
|
||||||
drive:
|
|
||||||
| CloudDriveConfig<{ shareCode: string; receiveCode: string }, Save115FileParams>
|
|
||||||
| CloudDriveConfig<{ pwdId: string }, SaveQuarkFileParams>
|
|
||||||
): drive is CloudDriveConfig<{ shareCode: string; receiveCode: string }, Save115FileParams> {
|
|
||||||
return drive.type === "pan115";
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export interface ResourceItem {
|
|||||||
cloudType: string;
|
cloudType: string;
|
||||||
messageId?: string;
|
messageId?: string;
|
||||||
isLastMessage?: boolean;
|
isLastMessage?: boolean;
|
||||||
|
isSupportSave?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Resource {
|
export interface Resource {
|
||||||
@@ -33,13 +34,25 @@ export interface ShareInfo {
|
|||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ShareInfoItem {
|
||||||
|
fileId: string;
|
||||||
|
fileName: string;
|
||||||
|
fileSize?: number;
|
||||||
|
fileIdToken?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ShareInfoResponse {
|
export interface ShareInfoResponse {
|
||||||
list: ShareInfo[];
|
list: ShareInfoItem[];
|
||||||
|
fileSize?: number;
|
||||||
pwdId?: string;
|
pwdId?: string;
|
||||||
stoken?: string;
|
stoken?: string;
|
||||||
shareCode?: string;
|
}
|
||||||
|
|
||||||
|
export interface ShareFileInfoAndFolder {
|
||||||
|
shareInfo: ShareInfoResponse;
|
||||||
|
folderId: string;
|
||||||
|
shareCode: string;
|
||||||
receiveCode?: string;
|
receiveCode?: string;
|
||||||
fileSize?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Folder {
|
export interface Folder {
|
||||||
@@ -49,10 +62,16 @@ export interface Folder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SaveFileParams {
|
export interface SaveFileParams {
|
||||||
|
shareCode: string; // 分享code
|
||||||
|
receiveCode?: string; // 分享文件的密码
|
||||||
|
folderId: string; // 文件夹id
|
||||||
|
fids: string[]; // 存储文件id
|
||||||
|
fidTokens?: string[]; // 存储文件token
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetShareInfoParams {
|
||||||
shareCode: string;
|
shareCode: string;
|
||||||
receiveCode: string;
|
receiveCode?: string;
|
||||||
fileId: string;
|
|
||||||
folderId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiResponse<T = unknown> {
|
export interface ApiResponse<T = unknown> {
|
||||||
|
|||||||
10
frontend/src/utils/image.ts
Normal file
10
frontend/src/utils/image.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useUserSettingStore } from "@/stores/userSetting";
|
||||||
|
import defaultImage from "@/assets/images/default.png";
|
||||||
|
|
||||||
|
export const getProxyImageUrl = (originalUrl: string): string => {
|
||||||
|
const userStore = useUserSettingStore();
|
||||||
|
if (!originalUrl) return defaultImage;
|
||||||
|
return userStore.imagesSource === "proxy"
|
||||||
|
? `/tele-images/?url=${encodeURIComponent(originalUrl)}`
|
||||||
|
: originalUrl;
|
||||||
|
};
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
:is="userStore.displayStyle === 'table' ? ResourceTable : ResourceCard"
|
:is="userStore.displayStyle === 'table' ? ResourceTable : ResourceCard"
|
||||||
v-if="resourceStore.resources.length > 0"
|
v-if="resourceStore.resources.length > 0"
|
||||||
@load-more="handleLoadMore"
|
@load-more="handleLoadMore"
|
||||||
|
@jump="handleJump"
|
||||||
@search-moviefor-tag="searchMovieforTag"
|
@search-moviefor-tag="searchMovieforTag"
|
||||||
@save="handleSave"
|
@save="handleSave"
|
||||||
/>
|
/>
|
||||||
@@ -230,6 +231,10 @@ const handleLoadMore = (channelId: string) => {
|
|||||||
resourceStore.searchResources("", true, channelId);
|
resourceStore.searchResources("", true, channelId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleJump = (resource: ResourceItem) => {
|
||||||
|
window.open(resource.cloudLinks[0], "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
const searchMovieforTag = (tag: string) => {
|
const searchMovieforTag = (tag: string) => {
|
||||||
router.push({ path: "/resource", query: { keyword: tag } });
|
router.push({ path: "/resource", query: { keyword: tag } });
|
||||||
};
|
};
|
||||||
|
|||||||
829
frontend/src/views/Thanks.vue
Normal file
829
frontend/src/views/Thanks.vue
Normal file
@@ -0,0 +1,829 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="containerRef" class="thanks-container">
|
||||||
|
<div ref="titleRef" class="title">感谢Ta们对项目的赞赏</div>
|
||||||
|
|
||||||
|
<!-- 添加说明文字 -->
|
||||||
|
<div class="description">
|
||||||
|
<p>感谢每一位支持者的信任与鼓励</p>
|
||||||
|
<p>正是你们的支持让这个项目能够持续发展</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div ref="sponsorsContainer" class="sponsors-container">
|
||||||
|
<div
|
||||||
|
v-for="(sponsor, index) in randomizedSponsors"
|
||||||
|
:key="sponsor.name"
|
||||||
|
ref="avatarRefs"
|
||||||
|
class="sponsor-avatar"
|
||||||
|
@mouseenter="handleMouseEnter(index)"
|
||||||
|
@mouseleave="handleMouseLeave"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref="avatarWrapperRefs"
|
||||||
|
class="avatar-wrapper"
|
||||||
|
:class="{
|
||||||
|
active: activeIndex === index,
|
||||||
|
'has-link': sponsor.link,
|
||||||
|
}"
|
||||||
|
@click="handleAvatarClick(sponsor.link)"
|
||||||
|
>
|
||||||
|
<div class="avatar-inner">
|
||||||
|
<div class="avatar-overlay"></div>
|
||||||
|
<img :src="sponsor.avatar" :alt="sponsor.name" class="avatar-img" />
|
||||||
|
<div class="name-tag">
|
||||||
|
{{ sponsor.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="activeIndex === index && sponsor.message" class="dialog-box">
|
||||||
|
<div class="dialog-content">
|
||||||
|
<div :id="`typeIt-${index}`" class="type-it-container"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加赞赏按钮 -->
|
||||||
|
<a
|
||||||
|
:href="PROJECT_GITHUB + '?tab=readme-ov-file#支持项目'"
|
||||||
|
target="_blank"
|
||||||
|
class="sponsor-button"
|
||||||
|
@mouseenter="handleButtonHover"
|
||||||
|
@mouseleave="handleButtonLeave"
|
||||||
|
>
|
||||||
|
<div class="button-content">
|
||||||
|
<svg class="heart-icon" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>赞赏支持</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, nextTick, computed, onBeforeUnmount } from "vue";
|
||||||
|
import TypeIt from "typeit";
|
||||||
|
import { userApi } from "@/api/user";
|
||||||
|
import gsap from "gsap";
|
||||||
|
import { PROJECT_GITHUB } from "@/constants/project";
|
||||||
|
// 赞助者数据
|
||||||
|
const sponsors = ref([]);
|
||||||
|
|
||||||
|
const getSponsors = async () => {
|
||||||
|
const res = await userApi.getSponsors();
|
||||||
|
sponsors.value = res.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 随机排序赞助者
|
||||||
|
const randomizedSponsors = computed(() => {
|
||||||
|
// 有sort的按照sort排序并排在前面,没有的按照随机排序
|
||||||
|
const sortedSponsors = [...sponsors.value]
|
||||||
|
.filter((item) => item.sort)
|
||||||
|
.sort((a, b) => a.sort - b.sort);
|
||||||
|
const randomSponsors = [...sponsors.value]
|
||||||
|
.filter((item) => !item.sort)
|
||||||
|
.sort(() => Math.random() - 0.5);
|
||||||
|
return [...sortedSponsors, ...randomSponsors];
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerRef = ref(null);
|
||||||
|
const sponsorsContainer = ref(null);
|
||||||
|
const activeIndex = ref(null);
|
||||||
|
const avatarRefs = ref([]);
|
||||||
|
const avatarWrapperRefs = ref([]);
|
||||||
|
let typeItInstance = null;
|
||||||
|
const activeCenter = ref({ x: 0, y: 0 });
|
||||||
|
const titleRef = ref(null);
|
||||||
|
|
||||||
|
// 添加头像动画时间轴的引用
|
||||||
|
const avatarTimelines = ref([]);
|
||||||
|
|
||||||
|
// 使用 requestAnimationFrame 优化动画更新
|
||||||
|
let rafId = null;
|
||||||
|
|
||||||
|
// 添加一个变量来跟踪当前激活的头像
|
||||||
|
let currentHoverIndex = null;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getSponsors();
|
||||||
|
|
||||||
|
// 修改页面入场动画
|
||||||
|
const tl = gsap.timeline({
|
||||||
|
defaults: { ease: "power3.out" },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 同时执行所有元素的动画
|
||||||
|
tl.from([titleRef.value, sponsorsContainer.value, ...avatarWrapperRefs.value], {
|
||||||
|
y: -20,
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.6,
|
||||||
|
stagger: {
|
||||||
|
amount: 0.3,
|
||||||
|
from: "start",
|
||||||
|
},
|
||||||
|
ease: "back.out(1.2)",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加可见性变化监听
|
||||||
|
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
|
// 添加窗口失焦事件处理
|
||||||
|
window.addEventListener("blur", handleMouseLeave);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 修改 handleVisibilityChange 函数
|
||||||
|
const handleVisibilityChange = () => {
|
||||||
|
if (document.hidden) {
|
||||||
|
// 页面不可见时清理资源
|
||||||
|
if (typeItInstance) {
|
||||||
|
typeItInstance.destroy();
|
||||||
|
typeItInstance = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改鼠标移入处理函数
|
||||||
|
const handleMouseEnter = (() => {
|
||||||
|
let timeout;
|
||||||
|
return async (index) => {
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
currentHoverIndex = index;
|
||||||
|
activeIndex.value = index;
|
||||||
|
|
||||||
|
timeout = setTimeout(async () => {
|
||||||
|
// 确保这是最新的hover状态
|
||||||
|
if (currentHoverIndex !== index) return;
|
||||||
|
|
||||||
|
const activeAvatar = avatarWrapperRefs.value[index];
|
||||||
|
if (activeAvatar) {
|
||||||
|
const rect = activeAvatar.getBoundingClientRect();
|
||||||
|
activeCenter.value = {
|
||||||
|
x: rect.left + rect.width / 2,
|
||||||
|
y: rect.top + rect.height / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暂停所有浮动动画
|
||||||
|
avatarTimelines.value.forEach((timeline) => {
|
||||||
|
if (timeline) {
|
||||||
|
timeline.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateAvatarsEffect(index);
|
||||||
|
|
||||||
|
await nextTick();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 初始化打字效果
|
||||||
|
if (typeItInstance) {
|
||||||
|
typeItInstance.destroy();
|
||||||
|
typeItInstance = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const typeItElement = document.getElementById(`typeIt-${index}`);
|
||||||
|
if (typeItElement) {
|
||||||
|
typeItInstance = new TypeIt(typeItElement, {
|
||||||
|
strings: randomizedSponsors.value[index].message,
|
||||||
|
speed: 20,
|
||||||
|
waitUntilVisible: true,
|
||||||
|
}).go();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("TypeIt初始化错误:", error);
|
||||||
|
}
|
||||||
|
}, 16);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 更新所有头像效果
|
||||||
|
const updateAvatarsEffect = (activeIndex) => {
|
||||||
|
if (!avatarWrapperRefs.value || activeCenter.value.x === 0) return;
|
||||||
|
|
||||||
|
if (rafId) {
|
||||||
|
cancelAnimationFrame(rafId);
|
||||||
|
}
|
||||||
|
|
||||||
|
rafId = requestAnimationFrame(() => {
|
||||||
|
avatarWrapperRefs.value.forEach((wrapper, index) => {
|
||||||
|
const inner = wrapper.querySelector(".avatar-inner");
|
||||||
|
const avatarContainer = wrapper.closest(".sponsor-avatar");
|
||||||
|
|
||||||
|
if (index === activeIndex) {
|
||||||
|
gsap.to(inner, {
|
||||||
|
scale: 1.2,
|
||||||
|
y: -15,
|
||||||
|
zIndex: 10,
|
||||||
|
duration: 0.2,
|
||||||
|
ease: "back.out(1.5)",
|
||||||
|
force3D: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
gsap.to(avatarContainer, {
|
||||||
|
filter: "drop-shadow(0 20px 30px rgba(0, 0, 0, 0.25))",
|
||||||
|
duration: 0.2,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeOverlay = wrapper.querySelector(".avatar-overlay");
|
||||||
|
gsap.to(activeOverlay, {
|
||||||
|
opacity: 0,
|
||||||
|
duration: 0.15,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = wrapper.getBoundingClientRect();
|
||||||
|
const centerX = rect.left + rect.width / 2;
|
||||||
|
const centerY = rect.top + rect.height / 2;
|
||||||
|
const deltaX = activeCenter.value.x - centerX;
|
||||||
|
const deltaY = activeCenter.value.y - centerY;
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
if (distance < 0.1) return;
|
||||||
|
|
||||||
|
const maxDistance = 400;
|
||||||
|
const strength = Math.max(0, 1 - distance / maxDistance);
|
||||||
|
|
||||||
|
// 计算吸引力效果
|
||||||
|
const attractionStrength = Math.pow(strength, 1.5);
|
||||||
|
const moveX = (deltaX / distance) * 30 * attractionStrength;
|
||||||
|
const moveY = (deltaY / distance) * 30 * attractionStrength;
|
||||||
|
|
||||||
|
// 计算旋转角度
|
||||||
|
const rotateX = -Math.atan2(deltaY, distance) * (180 / Math.PI) * strength;
|
||||||
|
const rotateY = Math.atan2(deltaX, distance) * (180 / Math.PI) * strength;
|
||||||
|
|
||||||
|
// 应用变换效果
|
||||||
|
gsap.to(inner, {
|
||||||
|
scale: 1 + 0.05 * strength,
|
||||||
|
x: moveX,
|
||||||
|
y: moveY,
|
||||||
|
rotationX: rotateX,
|
||||||
|
rotationY: rotateY,
|
||||||
|
duration: 0.2,
|
||||||
|
ease: "power2.out",
|
||||||
|
force3D: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新阴影效果
|
||||||
|
const shadowOffsetX = (deltaX / distance) * 15 * strength;
|
||||||
|
const shadowOffsetY = Math.max(6, (deltaY / distance) * 20 * strength + 6);
|
||||||
|
const shadowBlur = 12 + 18 * strength;
|
||||||
|
const shadowOpacity = 0.15 + 0.1 * strength;
|
||||||
|
|
||||||
|
gsap.to(avatarContainer, {
|
||||||
|
filter: `drop-shadow(${shadowOffsetX}px ${shadowOffsetY}px ${shadowBlur}px rgba(0, 0, 0, ${shadowOpacity}))`,
|
||||||
|
duration: 0.2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 修改鼠标移出处理函数
|
||||||
|
const handleMouseLeave = () => {
|
||||||
|
currentHoverIndex = null;
|
||||||
|
activeIndex.value = null;
|
||||||
|
activeCenter.value = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
if (!avatarWrapperRefs.value) return;
|
||||||
|
|
||||||
|
avatarWrapperRefs.value.forEach((wrapper) => {
|
||||||
|
const inner = wrapper.querySelector(".avatar-inner");
|
||||||
|
if (inner) {
|
||||||
|
gsap.killTweensOf(inner);
|
||||||
|
|
||||||
|
gsap.to(inner, {
|
||||||
|
scale: 1,
|
||||||
|
y: 0,
|
||||||
|
x: 0,
|
||||||
|
rotation: 0,
|
||||||
|
rotationX: 0,
|
||||||
|
rotationY: 0,
|
||||||
|
duration: 0.2,
|
||||||
|
ease: "power2.out",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarContainer = wrapper.closest(".sponsor-avatar");
|
||||||
|
if (avatarContainer) {
|
||||||
|
gsap.killTweensOf(avatarContainer);
|
||||||
|
|
||||||
|
gsap.to(avatarContainer, {
|
||||||
|
filter: "drop-shadow(0 6px 12px rgba(0, 0, 0, 0.15))",
|
||||||
|
duration: 0.2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayElement = wrapper.querySelector(".avatar-overlay");
|
||||||
|
if (overlayElement) {
|
||||||
|
gsap.to(overlayElement, {
|
||||||
|
opacity: 1,
|
||||||
|
duration: 0.15,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeItInstance) {
|
||||||
|
typeItInstance.destroy();
|
||||||
|
typeItInstance = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加点击处理函数
|
||||||
|
const handleAvatarClick = (link) => {
|
||||||
|
if (link) {
|
||||||
|
window.open(link, "_blank");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件卸载时清理
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
window.removeEventListener("blur", handleMouseLeave);
|
||||||
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||||
|
|
||||||
|
// 清理打字实例
|
||||||
|
if (typeItInstance) {
|
||||||
|
typeItInstance.destroy();
|
||||||
|
typeItInstance = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 添加按钮悬浮效果
|
||||||
|
const handleButtonHover = () => {
|
||||||
|
gsap.to(".sponsor-button", {
|
||||||
|
scale: 1.05,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "power2.out",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonLeave = () => {
|
||||||
|
gsap.to(".sponsor-button", {
|
||||||
|
scale: 1,
|
||||||
|
duration: 0.3,
|
||||||
|
ease: "power2.out",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.thanks-container {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: calc(100vh - 100px);
|
||||||
|
overflow: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: linear-gradient(135deg, #f6f8fd 0%, #f1f4f9 100%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(40px);
|
||||||
|
opacity: 0.5;
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-1 {
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: linear-gradient(45deg, rgba(142, 68, 173, 0.2), rgba(91, 177, 235, 0.2));
|
||||||
|
top: -200px;
|
||||||
|
left: -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-2 {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: linear-gradient(45deg, rgba(91, 177, 235, 0.2), rgba(142, 68, 173, 0.2));
|
||||||
|
bottom: -150px;
|
||||||
|
right: -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-3 {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
background: linear-gradient(45deg, rgba(241, 196, 15, 0.1), rgba(142, 68, 173, 0.1));
|
||||||
|
top: 40%;
|
||||||
|
left: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 装饰层 */
|
||||||
|
.decoration-layer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-dot {
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background: rgba(142, 68, 173, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: floatingDot 8s ease-in-out infinite;
|
||||||
|
animation-delay: var(--delay);
|
||||||
|
will-change: transform;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatingDot {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translate(100px, 50px);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translate(50px, 100px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translate(-50px, 50px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 50px;
|
||||||
|
color: #2c3e50;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 700;
|
||||||
|
background: linear-gradient(45deg, #8e44ad, #3498db);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsors-container {
|
||||||
|
width: 70%;
|
||||||
|
max-width: 1200px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
|
||||||
|
grid-gap: 40px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
opacity: 1; /* 确保容器默认可见 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor-avatar {
|
||||||
|
position: relative;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.15));
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-inner {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 4px solid #ffffff;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
filter 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(255, 255, 255, 0.2) 0%,
|
||||||
|
rgba(255, 255, 255, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 0.1) 100%
|
||||||
|
);
|
||||||
|
opacity: 1;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 3;
|
||||||
|
mix-blend-mode: overlay; /* 添加混合模式增强效果 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper.active .avatar-inner {
|
||||||
|
transform: scale(1.2) translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper.has-link {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper.has-link::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(45deg, #ff3366, #ff6b6b, #4ecdc4, #45b7d1, #96e6a1);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
z-index: -1;
|
||||||
|
filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper.has-link:hover::before {
|
||||||
|
opacity: 0.8;
|
||||||
|
animation: borderGlow 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glow-effect {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: transparent;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-wrapper.has-link:hover .glow-effect {
|
||||||
|
border-color: rgba(255, 255, 255, 0.5);
|
||||||
|
box-shadow:
|
||||||
|
0 0 20px rgba(255, 255, 255, 0.3),
|
||||||
|
inset 0 0 20px rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes borderGlow {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 确保激活状态下的发光效果仍然可见 */
|
||||||
|
.avatar-wrapper.active.has-link::before {
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
object-fit: cover;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-box {
|
||||||
|
position: absolute;
|
||||||
|
top: -120px; /* 稍微上调对话框位置 */
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 24px -1px rgba(0, 0, 0, 0.1),
|
||||||
|
0 2px 8px -1px rgba(0, 0, 0, 0.06),
|
||||||
|
inset 0 0 0 1px rgba(255, 255, 255, 0.5),
|
||||||
|
0 0 40px rgba(142, 68, 173, 0.05);
|
||||||
|
min-width: 180px;
|
||||||
|
z-index: 111;
|
||||||
|
opacity: 0;
|
||||||
|
animation: dialogFadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content {
|
||||||
|
position: relative;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #2c3e50;
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改引号装饰的样式 */
|
||||||
|
.dialog-content::before,
|
||||||
|
.dialog-content::after {
|
||||||
|
content: '"';
|
||||||
|
position: absolute;
|
||||||
|
font-size: 28px;
|
||||||
|
color: #8e44ad;
|
||||||
|
opacity: 0.15;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content::before {
|
||||||
|
left: -15px;
|
||||||
|
top: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-content::after {
|
||||||
|
right: -15px;
|
||||||
|
bottom: -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化淡入动画,使其更加流畅 */
|
||||||
|
@keyframes dialogFadeIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-50%) translateY(10px) scale(0.98);
|
||||||
|
filter: blur(1px);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(-50%) translateY(0) scale(1);
|
||||||
|
filter: blur(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化打字效果容器样式 */
|
||||||
|
.type-it-container {
|
||||||
|
min-height: 24px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加打字光标样式 */
|
||||||
|
.ti-cursor {
|
||||||
|
color: #8e44ad;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-tag {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -10px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
opacity: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: 20;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 15px;
|
||||||
|
padding: 0px 10px;
|
||||||
|
white-space: nowrap; /* 防止文字换行 */
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加新的样式 */
|
||||||
|
.description {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 600px;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description p {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-text {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 60px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-text p {
|
||||||
|
margin: 8px 0;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor-button {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 40px;
|
||||||
|
right: 40px;
|
||||||
|
background: linear-gradient(45deg, #ff3366, #ff6b6b);
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 30px;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
box-shadow:
|
||||||
|
0 4px 15px rgba(255, 51, 102, 0.3),
|
||||||
|
0 2px 8px rgba(255, 51, 102, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sponsor-button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow:
|
||||||
|
0 6px 20px rgba(255, 51, 102, 0.4),
|
||||||
|
0 3px 10px rgba(255, 51, 102, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heart-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
fill: currentColor;
|
||||||
|
animation: heartBeat 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes heartBeat {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
14% {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
28% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
42% {
|
||||||
|
transform: scale(1.3);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加响应式样式 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sponsor-button {
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description,
|
||||||
|
.bottom-text {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 添加悬浮状态的阴影效果 */
|
||||||
|
.sponsor-avatar:hover {
|
||||||
|
filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.15));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 修改激活状态的阴影效果 */
|
||||||
|
.sponsor-avatar:has(.avatar-wrapper.active) {
|
||||||
|
filter: drop-shadow(0 15px 25px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,64 +11,140 @@
|
|||||||
<h1 class="login__title">Cloud Saver</h1>
|
<h1 class="login__title">Cloud Saver</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 登录表单 -->
|
<!-- 添加 Tab 切换 -->
|
||||||
<van-form class="login__form" @submit="handleSubmit">
|
<van-tabs v-model:active="activeTab" class="login__tabs">
|
||||||
<van-cell-group inset class="login__form-group">
|
<!-- 登录面板 -->
|
||||||
<!-- 用户名输入框 -->
|
<van-tab title="登录" name="login">
|
||||||
<van-field
|
<van-form class="login__form" @submit="handleLogin">
|
||||||
v-model="formData.username"
|
<van-cell-group inset class="login__form-group">
|
||||||
name="username"
|
<!-- 用户名输入框 -->
|
||||||
label="用户名"
|
<van-field
|
||||||
placeholder="请输入用户名"
|
v-model="loginForm.username"
|
||||||
:rules="[{ required: true, message: '请填写用户名' }]"
|
name="username"
|
||||||
autocomplete="username"
|
label="用户名"
|
||||||
@keyup.enter="focusPassword"
|
placeholder="请输入用户名"
|
||||||
>
|
:rules="[{ required: true, message: '请填写用户名' }]"
|
||||||
<template #left-icon>
|
autocomplete="username"
|
||||||
<van-icon name="user-o" />
|
@keyup.enter="focusLoginPassword"
|
||||||
</template>
|
>
|
||||||
</van-field>
|
<template #left-icon>
|
||||||
|
<van-icon name="user-o" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
<!-- 密码输入框 -->
|
<!-- 密码输入框 -->
|
||||||
<van-field
|
<van-field
|
||||||
ref="passwordRef"
|
ref="loginPasswordRef"
|
||||||
v-model="formData.password"
|
v-model="loginForm.password"
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
label="密码"
|
label="密码"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
:rules="[{ required: true, message: '请填写密码' }]"
|
:rules="[{ required: true, message: '请填写密码' }]"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
@keyup.enter="handleSubmit"
|
@keyup.enter="handleLogin"
|
||||||
>
|
>
|
||||||
<template #left-icon>
|
<template #left-icon>
|
||||||
<van-icon name="lock" />
|
<van-icon name="lock" />
|
||||||
</template>
|
</template>
|
||||||
</van-field>
|
</van-field>
|
||||||
|
|
||||||
<!-- 优化记住密码选项 -->
|
<!-- 优化记住密码选项 -->
|
||||||
<div class="login__remember">
|
<div class="login__remember">
|
||||||
<van-checkbox v-model="rememberPassword" class="remember-checkbox">
|
<van-checkbox v-model="rememberPassword" class="remember-checkbox">
|
||||||
记住密码
|
记住密码
|
||||||
</van-checkbox>
|
</van-checkbox>
|
||||||
</div>
|
</div>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
|
|
||||||
<!-- 登录按钮 -->
|
<!-- 登录按钮 -->
|
||||||
<div class="login__submit">
|
<div class="login__submit">
|
||||||
<van-button
|
<van-button
|
||||||
:loading="isLoading"
|
:loading="isLoading"
|
||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
round
|
round
|
||||||
block
|
block
|
||||||
type="primary"
|
type="primary"
|
||||||
native-type="submit"
|
native-type="submit"
|
||||||
class="login__button"
|
class="login__button"
|
||||||
>
|
>
|
||||||
{{ isLoading ? "登录中..." : "登录" }}
|
{{ isLoading ? "登录中..." : "登录" }}
|
||||||
</van-button>
|
</van-button>
|
||||||
</div>
|
</div>
|
||||||
</van-form>
|
</van-form>
|
||||||
|
</van-tab>
|
||||||
|
|
||||||
|
<!-- 注册面板 -->
|
||||||
|
<van-tab title="注册" name="register">
|
||||||
|
<van-form class="login__form" @submit="handleRegister">
|
||||||
|
<van-cell-group inset class="login__form-group">
|
||||||
|
<van-field
|
||||||
|
v-model="registerForm.username"
|
||||||
|
name="username"
|
||||||
|
label="用户名"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
:rules="usernameRules"
|
||||||
|
>
|
||||||
|
<template #left-icon>
|
||||||
|
<van-icon name="user-o" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="registerForm.password"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
label="密码"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
:rules="passwordRules"
|
||||||
|
>
|
||||||
|
<template #left-icon>
|
||||||
|
<van-icon name="lock" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="registerForm.confirmPassword"
|
||||||
|
type="password"
|
||||||
|
name="confirmPassword"
|
||||||
|
label="确认密码"
|
||||||
|
placeholder="请确认密码"
|
||||||
|
:rules="confirmPasswordRules"
|
||||||
|
>
|
||||||
|
<template #left-icon>
|
||||||
|
<van-icon name="lock" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
|
<van-field
|
||||||
|
v-model="registerForm.registerCode"
|
||||||
|
name="registerCode"
|
||||||
|
label="注册码"
|
||||||
|
placeholder="请输入注册码"
|
||||||
|
:rules="registerCodeRules"
|
||||||
|
>
|
||||||
|
<template #left-icon>
|
||||||
|
<van-icon name="certificate" />
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
</van-cell-group>
|
||||||
|
|
||||||
|
<div class="login__submit">
|
||||||
|
<van-button
|
||||||
|
:loading="isLoading"
|
||||||
|
:disabled="isLoading"
|
||||||
|
round
|
||||||
|
block
|
||||||
|
type="primary"
|
||||||
|
native-type="submit"
|
||||||
|
class="login__button"
|
||||||
|
>
|
||||||
|
{{ isLoading ? "注册中..." : "注册" }}
|
||||||
|
</van-button>
|
||||||
|
</div>
|
||||||
|
</van-form>
|
||||||
|
</van-tab>
|
||||||
|
</van-tabs>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -77,7 +153,7 @@
|
|||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { showNotify } from "vant";
|
import { showNotify } from "vant";
|
||||||
import type { FieldInstance } from "vant";
|
import type { FieldInstance, FieldRule } from "vant";
|
||||||
import { userApi } from "@/api/user";
|
import { userApi } from "@/api/user";
|
||||||
import logo from "@/assets/images/logo.png";
|
import logo from "@/assets/images/logo.png";
|
||||||
import { STORAGE_KEYS } from "@/constants/storage";
|
import { STORAGE_KEYS } from "@/constants/storage";
|
||||||
@@ -88,21 +164,42 @@ interface LoginForm {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RegisterForm {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
registerCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const formData = ref<LoginForm>({
|
const activeTab = ref("login");
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const loginPasswordRef = ref<FieldInstance>();
|
||||||
|
const rememberPassword = ref(false);
|
||||||
|
|
||||||
|
const loginForm = ref<LoginForm>({
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
});
|
});
|
||||||
const isLoading = ref(false);
|
|
||||||
const passwordRef = ref<FieldInstance>();
|
const registerForm = ref<RegisterForm>({
|
||||||
const rememberPassword = ref(false);
|
username: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
registerCode: "",
|
||||||
|
});
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
// 方法定义
|
// 方法定义
|
||||||
const focusPassword = () => {
|
const focusLoginPassword = () => {
|
||||||
passwordRef.value?.focus();
|
loginPasswordRef.value?.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 表单验证
|
||||||
|
const validateConfirmPassword = (value: string) => {
|
||||||
|
return value === registerForm.value.password;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 在组件加载时检查是否有保存的账号密码
|
// 在组件加载时检查是否有保存的账号密码
|
||||||
@@ -110,22 +207,22 @@ onMounted(() => {
|
|||||||
const savedUsername = localStorage.getItem(STORAGE_KEYS.USERNAME);
|
const savedUsername = localStorage.getItem(STORAGE_KEYS.USERNAME);
|
||||||
const savedPassword = localStorage.getItem(STORAGE_KEYS.PASSWORD);
|
const savedPassword = localStorage.getItem(STORAGE_KEYS.PASSWORD);
|
||||||
if (savedUsername && savedPassword) {
|
if (savedUsername && savedPassword) {
|
||||||
formData.value.username = savedUsername;
|
loginForm.value.username = savedUsername;
|
||||||
formData.value.password = savedPassword;
|
loginForm.value.password = savedPassword;
|
||||||
rememberPassword.value = true;
|
rememberPassword.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
// 登录处理
|
||||||
|
const handleLogin = async () => {
|
||||||
try {
|
try {
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
const res = await userApi.login(formData.value);
|
const res = await userApi.login(loginForm.value);
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
// 处理记住密码
|
|
||||||
if (rememberPassword.value) {
|
if (rememberPassword.value) {
|
||||||
localStorage.setItem(STORAGE_KEYS.USERNAME, formData.value.username);
|
localStorage.setItem(STORAGE_KEYS.USERNAME, loginForm.value.username);
|
||||||
localStorage.setItem(STORAGE_KEYS.PASSWORD, formData.value.password);
|
localStorage.setItem(STORAGE_KEYS.PASSWORD, loginForm.value.password);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem(STORAGE_KEYS.USERNAME);
|
localStorage.removeItem(STORAGE_KEYS.USERNAME);
|
||||||
localStorage.removeItem(STORAGE_KEYS.PASSWORD);
|
localStorage.removeItem(STORAGE_KEYS.PASSWORD);
|
||||||
@@ -134,22 +231,65 @@ const handleSubmit = async () => {
|
|||||||
localStorage.setItem(STORAGE_KEYS.TOKEN, res.data.token);
|
localStorage.setItem(STORAGE_KEYS.TOKEN, res.data.token);
|
||||||
await router.push("/");
|
await router.push("/");
|
||||||
} else {
|
} else {
|
||||||
showNotify({
|
showNotify({ type: "danger", message: res.message || "登录失败" });
|
||||||
type: "danger",
|
|
||||||
message: res.message || "登录失败",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showNotify({
|
showNotify({ type: "danger", message: "登录失败" });
|
||||||
type: "danger",
|
|
||||||
message: "登录失败",
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 注册处理
|
||||||
|
const handleRegister = async () => {
|
||||||
|
try {
|
||||||
|
isLoading.value = true;
|
||||||
|
const res = await userApi.register({
|
||||||
|
username: registerForm.value.username,
|
||||||
|
password: registerForm.value.password,
|
||||||
|
registerCode: registerForm.value.registerCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.code === 0) {
|
||||||
|
showNotify({ type: "success", message: "注册成功" });
|
||||||
|
// 自动填充登录表单
|
||||||
|
loginForm.value.username = registerForm.value.username;
|
||||||
|
loginForm.value.password = registerForm.value.password;
|
||||||
|
activeTab.value = "login";
|
||||||
|
// 清空注册表单
|
||||||
|
registerForm.value = {
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: "",
|
||||||
|
registerCode: "",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
showNotify({ type: "danger", message: res.message || "注册失败" });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showNotify({ type: "danger", message: "注册失败" });
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义验证规则
|
||||||
|
const usernameRules: FieldRule[] = [
|
||||||
|
{ required: true, message: "请填写用户名" },
|
||||||
|
{ pattern: /.{3,}/, message: "用户名至少3个字符" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const passwordRules: FieldRule[] = [
|
||||||
|
{ required: true, message: "请填写密码" },
|
||||||
|
{ pattern: /.{6,}/, message: "密码至少6个字符" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const confirmPasswordRules: FieldRule[] = [
|
||||||
|
{ required: true, message: "请确认密码" },
|
||||||
|
{ validator: validateConfirmPassword, message: "两次密码不一致" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const registerCodeRules: FieldRule[] = [{ required: true, message: "请填写注册码" }];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -164,8 +304,8 @@ const handleSubmit = async () => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
background: url("@/assets/images/mobile-login-bg.png") no-repeat;
|
background: url("@/assets/images/mobile-login-bg.png") no-repeat;
|
||||||
background-size: cover;
|
background-size: 100% auto;
|
||||||
background-position: center;
|
filter: blur(1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 主内容区
|
// 主内容区
|
||||||
@@ -207,6 +347,7 @@ const handleSubmit = async () => {
|
|||||||
// 表单
|
// 表单
|
||||||
&__form {
|
&__form {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__form-group {
|
&__form-group {
|
||||||
@@ -231,6 +372,32 @@ const handleSubmit = async () => {
|
|||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border-top: 0.5px solid #f5f5f5;
|
border-top: 0.5px solid #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__tabs {
|
||||||
|
:deep() {
|
||||||
|
.van-tabs__wrap {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-tabs__nav {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-tab {
|
||||||
|
color: var(--theme-color);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-tab--active {
|
||||||
|
color: var(--theme-theme);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.van-tabs__line {
|
||||||
|
background-color: var(--theme-theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vant 组件样式优化
|
// Vant 组件样式优化
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
<ResourceCard
|
<ResourceCard
|
||||||
:current-channel-id="currentTab"
|
:current-channel-id="currentTab"
|
||||||
@save="handleSave"
|
@save="handleSave"
|
||||||
|
@jump="handleJump"
|
||||||
@search-moviefor-tag="searchMovieforTag"
|
@search-moviefor-tag="searchMovieforTag"
|
||||||
/>
|
/>
|
||||||
</van-tab>
|
</van-tab>
|
||||||
@@ -192,6 +193,10 @@ const handleSave = async (resource: ResourceItem) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleJump = (resource: ResourceItem) => {
|
||||||
|
window.open(resource.cloudLinks[0], "_blank");
|
||||||
|
};
|
||||||
|
|
||||||
const handleFolderSelect = (folders: Folder[] | null) => {
|
const handleFolderSelect = (folders: Folder[] | null) => {
|
||||||
if (!currentResource.value) return;
|
if (!currentResource.value) return;
|
||||||
currentFolderPath.value = folders;
|
currentFolderPath.value = folders;
|
||||||
|
|||||||
@@ -48,20 +48,35 @@
|
|||||||
<van-cell-group inset>
|
<van-cell-group inset>
|
||||||
<van-field
|
<van-field
|
||||||
v-model="localUserSettings.cloud115Cookie"
|
v-model="localUserSettings.cloud115Cookie"
|
||||||
|
:type="showCloud115Cookie ? 'text' : 'password'"
|
||||||
label="115网盘"
|
label="115网盘"
|
||||||
type="textarea"
|
|
||||||
rows="2"
|
rows="2"
|
||||||
autosize
|
autosize
|
||||||
placeholder="请输入115网盘Cookie"
|
placeholder="请输入115网盘Cookie"
|
||||||
/>
|
>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-icon
|
||||||
|
:name="showCloud115Cookie ? 'eye-o' : 'closed-eye'"
|
||||||
|
@click="showCloud115Cookie = !showCloud115Cookie"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
|
|
||||||
<van-field
|
<van-field
|
||||||
v-model="localUserSettings.quarkCookie"
|
v-model="localUserSettings.quarkCookie"
|
||||||
|
:type="showQuarkCookie ? 'text' : 'password'"
|
||||||
label="夸克网盘"
|
label="夸克网盘"
|
||||||
type="textarea"
|
|
||||||
rows="2"
|
rows="2"
|
||||||
autosize
|
autosize
|
||||||
placeholder="请输入夸克网盘Cookie"
|
placeholder="请输入夸克网盘Cookie"
|
||||||
/>
|
>
|
||||||
|
<template #right-icon>
|
||||||
|
<van-icon
|
||||||
|
:name="showQuarkCookie ? 'eye-o' : 'closed-eye'"
|
||||||
|
@click="showQuarkCookie = !showQuarkCookie"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</van-field>
|
||||||
</van-cell-group>
|
</van-cell-group>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -122,6 +137,10 @@ const localUserSettings = ref<UserSettingAttributes>({
|
|||||||
quarkCookie: "",
|
quarkCookie: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 添加显示/隐藏密码的状态
|
||||||
|
const showCloud115Cookie = ref(false);
|
||||||
|
const showQuarkCookie = ref(false);
|
||||||
|
|
||||||
// 监听 store 变化
|
// 监听 store 变化
|
||||||
watch(
|
watch(
|
||||||
() => settingStore.globalSetting,
|
() => settingStore.globalSetting,
|
||||||
@@ -252,4 +271,15 @@ const handleProxyHostChange = (val: string) => {
|
|||||||
:deep(.van-cell-group--inset) {
|
:deep(.van-cell-group--inset) {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加图标样式
|
||||||
|
:deep(.van-field__right-icon) {
|
||||||
|
padding: 0 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--theme-color);
|
||||||
|
|
||||||
|
.van-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# nginx.conf
|
# nginx.conf
|
||||||
user nginx; # 定义 Nginx 进程的运行用户
|
user root; # 定义 Nginx 进程的运行用户
|
||||||
worker_processes 1; # 设置 Nginx 进程数
|
worker_processes 1; # 设置 Nginx 进程数
|
||||||
|
|
||||||
events {
|
events {
|
||||||
|
|||||||
312
package-lock.json
generated
312
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "cloud-saver",
|
"name": "cloud-saver",
|
||||||
"version": "0.2.2",
|
"version": "0.2.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "cloud-saver",
|
"name": "cloud-saver",
|
||||||
"version": "0.2.2",
|
"version": "0.2.4",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"frontend",
|
"frontend",
|
||||||
"backend"
|
"backend"
|
||||||
@@ -38,12 +38,14 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"express": "^4.18.3",
|
"express": "^4.18.3",
|
||||||
|
"inversify": "^7.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"rss-parser": "^3.13.0",
|
"rss-parser": "^3.13.0",
|
||||||
"sequelize": "^6.37.5",
|
"sequelize": "^6.37.5",
|
||||||
"socket.io": "^4.8.1",
|
"socket.io": "^4.8.1",
|
||||||
"sqlite3": "^5.1.7",
|
"sqlite3": "^5.1.7",
|
||||||
"tunnel": "^0.0.6"
|
"tunnel": "^0.0.6",
|
||||||
|
"winston": "^3.17.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
@@ -60,13 +62,15 @@
|
|||||||
},
|
},
|
||||||
"frontend": {
|
"frontend": {
|
||||||
"name": "cloud-saver-web",
|
"name": "cloud-saver-web",
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@element-plus/icons-vue": "^2.3.1",
|
"@element-plus/icons-vue": "^2.3.1",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.6.7",
|
||||||
"element-plus": "^2.6.1",
|
"element-plus": "^2.6.1",
|
||||||
|
"gsap": "^3.12.7",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
|
"typeit": "^8.8.7",
|
||||||
"vant": "^4.9.17",
|
"vant": "^4.9.17",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-router": "^4.3.0"
|
"vue-router": "^4.3.0"
|
||||||
@@ -1718,6 +1722,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@colors/colors": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.1.90"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cspotcode/source-map-support": {
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
"version": "0.8.1",
|
"version": "0.8.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||||
@@ -1740,6 +1753,17 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@dabh/diagnostics": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"colorspace": "1.1.x",
|
||||||
|
"enabled": "2.0.x",
|
||||||
|
"kuler": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@element-plus/icons-vue": {
|
"node_modules/@element-plus/icons-vue": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
"resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
|
||||||
@@ -2321,6 +2345,55 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/@inversifyjs/common": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@inversifyjs/common/-/common-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-Qj5BELk11AfI2rgZEAaLPmOftmQRLLmoCXgAjmaF0IngQN5vHomVT5ML7DZ3+CA5fgGcEVMcGbUDAun+Rz+oNg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@inversifyjs/container": {
|
||||||
|
"version": "1.5.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@inversifyjs/container/-/container-1.5.4.tgz",
|
||||||
|
"integrity": "sha512-mHAaWjAQb8m6TJksm5EJXW/kPcZFVEc1UKkWv5OnLbwbU0QvxM2UbEsuXzusGVHcrNY4TQp9Uh2wkRY6TN2WJg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inversifyjs/common": "1.5.0",
|
||||||
|
"@inversifyjs/core": "5.0.0",
|
||||||
|
"@inversifyjs/reflect-metadata-utils": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"reflect-metadata": "~0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inversifyjs/core": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@inversifyjs/core/-/core-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-axOl+VZFGVA3nAMbs6RuHhQ8HvgO6/tKjlWJk4Nt0rUqed+1ksak4p5yZNtown1Kdm0GV2Oc57qLqqWd943hgA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inversifyjs/common": "1.5.0",
|
||||||
|
"@inversifyjs/prototype-utils": "0.1.0",
|
||||||
|
"@inversifyjs/reflect-metadata-utils": "1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inversifyjs/prototype-utils": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@inversifyjs/prototype-utils/-/prototype-utils-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-lNz1yyajMRDXBHLvJsYYX81FcmeD15e5Qz1zAZ/3zeYTl+u7ZF/GxNRKJzNOloeMPMtuR8BnvzHA1SZxjR+J9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inversifyjs/common": "1.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@inversifyjs/reflect-metadata-utils": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@inversifyjs/reflect-metadata-utils/-/reflect-metadata-utils-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-jmuAuC3eL1GnFAYfJGJOMKRDL9q1mgzOyrban6zxfM8Yg1FUHsj25h27bW2G7p8X1Amvhg3MLkaOuogszkrofA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"reflect-metadata": "0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/cliui": {
|
"node_modules/@isaacs/cliui": {
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
@@ -3634,6 +3707,12 @@
|
|||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/triple-beam": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
@@ -3657,6 +3736,12 @@
|
|||||||
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
|
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-animations-js": {
|
||||||
|
"version": "2.2.16",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/web-animations-js/-/web-animations-js-2.2.16.tgz",
|
||||||
|
"integrity": "sha512-ATELeWMFwj8eQiH0KmvsCl1V2lu/qx/CjOBmv4ADSZS5u8r4reMyjCXtxG7khqyiwH3IOMNdrON/Ugn94OUcRA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/web-bluetooth": {
|
"node_modules/@types/web-bluetooth": {
|
||||||
"version": "0.0.16",
|
"version": "0.0.16",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
|
||||||
@@ -4356,7 +4441,6 @@
|
|||||||
"version": "3.2.6",
|
"version": "3.2.6",
|
||||||
"resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
|
"resolved": "https://registry.npmmirror.com/async/-/async-3.2.6.tgz",
|
||||||
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
"integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/async-function": {
|
"node_modules/async-function": {
|
||||||
@@ -5033,6 +5117,16 @@
|
|||||||
"resolved": "frontend",
|
"resolved": "frontend",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/color": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^1.9.3",
|
||||||
|
"color-string": "^1.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
@@ -5050,9 +5144,18 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/color-string": {
|
||||||
|
"version": "1.9.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/color-string/-/color-string-1.9.1.tgz",
|
||||||
|
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "^1.0.0",
|
||||||
|
"simple-swizzle": "^0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-support": {
|
"node_modules/color-support": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz",
|
"resolved": "https://registry.npmmirror.com/color-support/-/color-support-1.1.3.tgz",
|
||||||
@@ -5062,6 +5165,31 @@
|
|||||||
"color-support": "bin.js"
|
"color-support": "bin.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/color/node_modules/color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color/node_modules/color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/colorspace": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmmirror.com/colorspace/-/colorspace-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color": "^3.1.3",
|
||||||
|
"text-hex": "1.0.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -5677,6 +5805,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/enabled": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/enabled/-/enabled-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/encodeurl": {
|
"node_modules/encodeurl": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||||
@@ -6458,6 +6592,12 @@
|
|||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fecha": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/file-entry-cache": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
||||||
@@ -6648,6 +6788,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/fn.name": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/fn.name/-/fn.name-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.9",
|
"version": "1.15.9",
|
||||||
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
@@ -7080,6 +7226,12 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/gsap": {
|
||||||
|
"version": "3.12.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/gsap/-/gsap-3.12.7.tgz",
|
||||||
|
"integrity": "sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==",
|
||||||
|
"license": "Standard 'no charge' license: https://gsap.com/standard-license. Club GSAP members get more: https://gsap.com/licensing/. Why GreenSock doesn't employ an MIT license: https://gsap.com/why-license/"
|
||||||
|
},
|
||||||
"node_modules/has-bigints": {
|
"node_modules/has-bigints": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz",
|
||||||
@@ -7428,6 +7580,20 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/inversify": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/inversify/-/inversify-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-f8SlUTgecMZGr/rsFK36PD84/mH0+sp0/P/TuiGo3CcJywmF5kgoXeE2RW5IjaIt1SlqS5c5V9RuQc1+B8mw4Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@inversifyjs/common": "1.5.0",
|
||||||
|
"@inversifyjs/container": "1.5.4",
|
||||||
|
"@inversifyjs/core": "5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"reflect-metadata": "~0.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ip-address": {
|
"node_modules/ip-address": {
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz",
|
"resolved": "https://registry.npmmirror.com/ip-address/-/ip-address-9.0.5.tgz",
|
||||||
@@ -7809,7 +7975,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -8152,6 +8317,12 @@
|
|||||||
"json-buffer": "3.0.1"
|
"json-buffer": "3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/kuler": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/kuler/-/kuler-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/leven": {
|
"node_modules/leven": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz",
|
"resolved": "https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz",
|
||||||
@@ -8311,6 +8482,23 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/logform": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/logform/-/logform-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@colors/colors": "1.6.0",
|
||||||
|
"@types/triple-beam": "^1.3.2",
|
||||||
|
"fecha": "^4.2.0",
|
||||||
|
"ms": "^2.1.1",
|
||||||
|
"safe-stable-stringify": "^2.3.1",
|
||||||
|
"triple-beam": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lru-cache": {
|
"node_modules/lru-cache": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.3",
|
||||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
|
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
@@ -9483,6 +9671,15 @@
|
|||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/one-time": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/one-time/-/one-time-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fn.name": "1.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.4",
|
"version": "0.9.4",
|
||||||
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
"resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
|
||||||
@@ -10197,6 +10394,13 @@
|
|||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reflect-metadata": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
"resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
|
||||||
@@ -10570,6 +10774,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/safe-stable-stringify": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safer-buffer": {
|
"node_modules/safer-buffer": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
"resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||||
@@ -11023,6 +11236,21 @@
|
|||||||
"simple-concat": "^1.0.0"
|
"simple-concat": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/simple-swizzle": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-arrayish": "^0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/simple-update-notifier": {
|
"node_modules/simple-update-notifier": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
|
||||||
@@ -11397,6 +11625,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stack-trace": {
|
||||||
|
"version": "0.0.10",
|
||||||
|
"resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz",
|
||||||
|
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -11842,6 +12079,12 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/text-hex": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/text-hex/-/text-hex-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/text-table": {
|
"node_modules/text-table": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
|
||||||
@@ -11938,6 +12181,15 @@
|
|||||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/triple-beam": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ts-api-utils": {
|
"node_modules/ts-api-utils": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
"resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
|
||||||
@@ -12140,6 +12392,16 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typeit": {
|
||||||
|
"version": "8.8.7",
|
||||||
|
"resolved": "https://registry.npmmirror.com/typeit/-/typeit-8.8.7.tgz",
|
||||||
|
"integrity": "sha512-sSVpy+cjeFP6Z+fZqiHzUSShg5yYFeJEt/Qut/bX945+Axyq+Yq+GPOuuk+sofoccSv8nNX/ibOOHkbki2mEpg==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-animations-js": "^2.2.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "5.8.2",
|
"version": "5.8.2",
|
||||||
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.2.tgz",
|
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.8.2.tgz",
|
||||||
@@ -12999,6 +13261,42 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/winston": {
|
||||||
|
"version": "3.17.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/winston/-/winston-3.17.0.tgz",
|
||||||
|
"integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@colors/colors": "^1.6.0",
|
||||||
|
"@dabh/diagnostics": "^2.0.2",
|
||||||
|
"async": "^3.2.3",
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"one-time": "^1.0.0",
|
||||||
|
"readable-stream": "^3.4.0",
|
||||||
|
"safe-stable-stringify": "^2.3.1",
|
||||||
|
"stack-trace": "0.0.x",
|
||||||
|
"triple-beam": "^1.3.0",
|
||||||
|
"winston-transport": "^4.9.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/winston-transport": {
|
||||||
|
"version": "4.9.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/winston-transport/-/winston-transport-4.9.0.tgz",
|
||||||
|
"integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"logform": "^2.7.0",
|
||||||
|
"readable-stream": "^3.6.2",
|
||||||
|
"triple-beam": "^1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wkx": {
|
"node_modules/wkx": {
|
||||||
"version": "0.5.0",
|
"version": "0.5.0",
|
||||||
"resolved": "https://registry.npmmirror.com/wkx/-/wkx-0.5.0.tgz",
|
"resolved": "https://registry.npmmirror.com/wkx/-/wkx-0.5.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "cloud-saver",
|
"name": "cloud-saver",
|
||||||
"version": "0.2.3",
|
"version": "0.2.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"frontend",
|
"frontend",
|
||||||
|
|||||||
236
pnpm-lock.yaml
generated
236
pnpm-lock.yaml
generated
@@ -62,6 +62,9 @@ importers:
|
|||||||
express:
|
express:
|
||||||
specifier: ^4.18.3
|
specifier: ^4.18.3
|
||||||
version: 4.21.2
|
version: 4.21.2
|
||||||
|
inversify:
|
||||||
|
specifier: ^7.1.0
|
||||||
|
version: 7.1.0(reflect-metadata@0.2.2)
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: ^9.0.2
|
specifier: ^9.0.2
|
||||||
version: 9.0.2
|
version: 9.0.2
|
||||||
@@ -80,6 +83,9 @@ importers:
|
|||||||
tunnel:
|
tunnel:
|
||||||
specifier: ^0.0.6
|
specifier: ^0.0.6
|
||||||
version: 0.0.6
|
version: 0.0.6
|
||||||
|
winston:
|
||||||
|
specifier: ^3.17.0
|
||||||
|
version: 3.17.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/bcrypt':
|
'@types/bcrypt':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
@@ -123,12 +129,18 @@ importers:
|
|||||||
element-plus:
|
element-plus:
|
||||||
specifier: ^2.6.1
|
specifier: ^2.6.1
|
||||||
version: 2.9.5(vue@3.5.13(typescript@5.8.2))
|
version: 2.9.5(vue@3.5.13(typescript@5.8.2))
|
||||||
|
gsap:
|
||||||
|
specifier: ^3.12.7
|
||||||
|
version: 3.12.7
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.1.7
|
specifier: ^2.1.7
|
||||||
version: 2.3.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
|
version: 2.3.1(typescript@5.8.2)(vue@3.5.13(typescript@5.8.2))
|
||||||
socket.io-client:
|
socket.io-client:
|
||||||
specifier: ^4.8.1
|
specifier: ^4.8.1
|
||||||
version: 4.8.1
|
version: 4.8.1
|
||||||
|
typeit:
|
||||||
|
specifier: ^8.8.7
|
||||||
|
version: 8.8.7
|
||||||
vant:
|
vant:
|
||||||
specifier: ^4.9.17
|
specifier: ^4.9.17
|
||||||
version: 4.9.17(vue@3.5.13(typescript@5.8.2))
|
version: 4.9.17(vue@3.5.13(typescript@5.8.2))
|
||||||
@@ -673,6 +685,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
|
resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@colors/colors@1.6.0':
|
||||||
|
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||||
|
engines: {node: '>=0.1.90'}
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -681,6 +697,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
'@dabh/diagnostics@2.0.3':
|
||||||
|
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
|
||||||
|
|
||||||
'@element-plus/icons-vue@2.3.1':
|
'@element-plus/icons-vue@2.3.1':
|
||||||
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
|
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -867,6 +886,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
|
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
|
||||||
deprecated: Use @eslint/object-schema instead
|
deprecated: Use @eslint/object-schema instead
|
||||||
|
|
||||||
|
'@inversifyjs/common@1.5.0':
|
||||||
|
resolution: {integrity: sha512-Qj5BELk11AfI2rgZEAaLPmOftmQRLLmoCXgAjmaF0IngQN5vHomVT5ML7DZ3+CA5fgGcEVMcGbUDAun+Rz+oNg==}
|
||||||
|
|
||||||
|
'@inversifyjs/container@1.5.4':
|
||||||
|
resolution: {integrity: sha512-mHAaWjAQb8m6TJksm5EJXW/kPcZFVEc1UKkWv5OnLbwbU0QvxM2UbEsuXzusGVHcrNY4TQp9Uh2wkRY6TN2WJg==}
|
||||||
|
peerDependencies:
|
||||||
|
reflect-metadata: ~0.2.2
|
||||||
|
|
||||||
|
'@inversifyjs/core@5.0.0':
|
||||||
|
resolution: {integrity: sha512-axOl+VZFGVA3nAMbs6RuHhQ8HvgO6/tKjlWJk4Nt0rUqed+1ksak4p5yZNtown1Kdm0GV2Oc57qLqqWd943hgA==}
|
||||||
|
|
||||||
|
'@inversifyjs/prototype-utils@0.1.0':
|
||||||
|
resolution: {integrity: sha512-lNz1yyajMRDXBHLvJsYYX81FcmeD15e5Qz1zAZ/3zeYTl+u7ZF/GxNRKJzNOloeMPMtuR8BnvzHA1SZxjR+J9w==}
|
||||||
|
|
||||||
|
'@inversifyjs/reflect-metadata-utils@1.1.0':
|
||||||
|
resolution: {integrity: sha512-jmuAuC3eL1GnFAYfJGJOMKRDL9q1mgzOyrban6zxfM8Yg1FUHsj25h27bW2G7p8X1Amvhg3MLkaOuogszkrofA==}
|
||||||
|
peerDependencies:
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -1262,6 +1300,9 @@ packages:
|
|||||||
'@types/serve-static@1.15.7':
|
'@types/serve-static@1.15.7':
|
||||||
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==}
|
||||||
|
|
||||||
|
'@types/triple-beam@1.3.5':
|
||||||
|
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7':
|
'@types/trusted-types@2.0.7':
|
||||||
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
|
||||||
|
|
||||||
@@ -1271,6 +1312,9 @@ packages:
|
|||||||
'@types/validator@13.12.2':
|
'@types/validator@13.12.2':
|
||||||
resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==}
|
resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==}
|
||||||
|
|
||||||
|
'@types/web-animations-js@2.2.16':
|
||||||
|
resolution: {integrity: sha512-ATELeWMFwj8eQiH0KmvsCl1V2lu/qx/CjOBmv4ADSZS5u8r4reMyjCXtxG7khqyiwH3IOMNdrON/Ugn94OUcRA==}
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.16':
|
'@types/web-bluetooth@0.0.16':
|
||||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||||
|
|
||||||
@@ -1682,10 +1726,19 @@ packages:
|
|||||||
color-name@1.1.4:
|
color-name@1.1.4:
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
color-string@1.9.1:
|
||||||
|
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||||
|
|
||||||
color-support@1.1.3:
|
color-support@1.1.3:
|
||||||
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
color@3.2.1:
|
||||||
|
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
|
||||||
|
|
||||||
|
colorspace@1.1.4:
|
||||||
|
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
|
||||||
|
|
||||||
combined-stream@1.0.8:
|
combined-stream@1.0.8:
|
||||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -1927,6 +1980,9 @@ packages:
|
|||||||
emoji-regex@9.2.2:
|
emoji-regex@9.2.2:
|
||||||
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
|
||||||
|
|
||||||
|
enabled@2.0.0:
|
||||||
|
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
|
||||||
|
|
||||||
encodeurl@1.0.2:
|
encodeurl@1.0.2:
|
||||||
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -2134,6 +2190,9 @@ packages:
|
|||||||
picomatch:
|
picomatch:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
fecha@4.2.3:
|
||||||
|
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
|
||||||
|
|
||||||
file-entry-cache@6.0.1:
|
file-entry-cache@6.0.1:
|
||||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||||
engines: {node: ^10.12.0 || >=12.0.0}
|
engines: {node: ^10.12.0 || >=12.0.0}
|
||||||
@@ -2163,6 +2222,9 @@ packages:
|
|||||||
flatted@3.3.3:
|
flatted@3.3.3:
|
||||||
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
|
||||||
|
|
||||||
|
fn.name@1.1.0:
|
||||||
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
|
|
||||||
follow-redirects@1.15.9:
|
follow-redirects@1.15.9:
|
||||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -2295,6 +2357,9 @@ packages:
|
|||||||
graphemer@1.4.0:
|
graphemer@1.4.0:
|
||||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||||
|
|
||||||
|
gsap@3.12.7:
|
||||||
|
resolution: {integrity: sha512-V4GsyVamhmKefvcAKaoy0h6si0xX7ogwBoBSs2CTJwt7luW0oZzC0LhdkyuKV8PJAXr7Yaj8pMjCKD4GJ+eEMg==}
|
||||||
|
|
||||||
has-bigints@1.1.0:
|
has-bigints@1.1.0:
|
||||||
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2414,6 +2479,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
inversify@7.1.0:
|
||||||
|
resolution: {integrity: sha512-f8SlUTgecMZGr/rsFK36PD84/mH0+sp0/P/TuiGo3CcJywmF5kgoXeE2RW5IjaIt1SlqS5c5V9RuQc1+B8mw4Q==}
|
||||||
|
peerDependencies:
|
||||||
|
reflect-metadata: ~0.2.2
|
||||||
|
|
||||||
ip-address@9.0.5:
|
ip-address@9.0.5:
|
||||||
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@@ -2429,6 +2499,9 @@ packages:
|
|||||||
is-arrayish@0.2.1:
|
is-arrayish@0.2.1:
|
||||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||||
|
|
||||||
|
is-arrayish@0.3.2:
|
||||||
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
|
||||||
is-async-function@2.1.1:
|
is-async-function@2.1.1:
|
||||||
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
|
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -2631,6 +2704,9 @@ packages:
|
|||||||
keyv@4.5.4:
|
keyv@4.5.4:
|
||||||
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
|
kuler@2.0.0:
|
||||||
|
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||||
|
|
||||||
leven@3.1.0:
|
leven@3.1.0:
|
||||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2702,6 +2778,10 @@ packages:
|
|||||||
lodash@4.17.21:
|
lodash@4.17.21:
|
||||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||||
|
|
||||||
|
logform@2.7.0:
|
||||||
|
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
@@ -2961,6 +3041,9 @@ packages:
|
|||||||
once@1.4.0:
|
once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
|
one-time@1.0.0:
|
||||||
|
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
@@ -3196,6 +3279,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||||
engines: {node: '>= 14.18.0'}
|
engines: {node: '>= 14.18.0'}
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2:
|
||||||
|
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -3292,6 +3378,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
safe-stable-stringify@2.5.0:
|
||||||
|
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
@@ -3434,6 +3524,9 @@ packages:
|
|||||||
simple-get@4.0.1:
|
simple-get@4.0.1:
|
||||||
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
|
||||||
|
|
||||||
|
simple-swizzle@0.2.2:
|
||||||
|
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
simple-update-notifier@2.0.0:
|
||||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3513,6 +3606,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
|
resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
stack-trace@0.0.10:
|
||||||
|
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
|
||||||
|
|
||||||
statuses@2.0.1:
|
statuses@2.0.1:
|
||||||
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -3619,6 +3715,9 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
text-hex@1.0.0:
|
||||||
|
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
|
||||||
|
|
||||||
text-table@0.2.0:
|
text-table@0.2.0:
|
||||||
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
|
||||||
|
|
||||||
@@ -3647,6 +3746,10 @@ packages:
|
|||||||
tr46@1.0.1:
|
tr46@1.0.1:
|
||||||
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
|
||||||
|
|
||||||
|
triple-beam@1.4.1:
|
||||||
|
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||||
|
engines: {node: '>= 14.0.0'}
|
||||||
|
|
||||||
ts-api-utils@1.4.3:
|
ts-api-utils@1.4.3:
|
||||||
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
|
resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==}
|
||||||
engines: {node: '>=16'}
|
engines: {node: '>=16'}
|
||||||
@@ -3709,6 +3812,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
typeit@8.8.7:
|
||||||
|
resolution: {integrity: sha512-sSVpy+cjeFP6Z+fZqiHzUSShg5yYFeJEt/Qut/bX945+Axyq+Yq+GPOuuk+sofoccSv8nNX/ibOOHkbki2mEpg==}
|
||||||
|
|
||||||
typescript@5.8.2:
|
typescript@5.8.2:
|
||||||
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
|
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
@@ -3973,6 +4079,14 @@ packages:
|
|||||||
wide-align@1.1.5:
|
wide-align@1.1.5:
|
||||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||||
|
|
||||||
|
winston-transport@4.9.0:
|
||||||
|
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
|
winston@3.17.0:
|
||||||
|
resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==}
|
||||||
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
wkx@0.5.0:
|
wkx@0.5.0:
|
||||||
resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==}
|
resolution: {integrity: sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==}
|
||||||
|
|
||||||
@@ -4744,12 +4858,20 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.25.9
|
'@babel/helper-string-parser': 7.25.9
|
||||||
'@babel/helper-validator-identifier': 7.25.9
|
'@babel/helper-validator-identifier': 7.25.9
|
||||||
|
|
||||||
|
'@colors/colors@1.6.0': {}
|
||||||
|
|
||||||
'@cspotcode/source-map-support@0.8.1':
|
'@cspotcode/source-map-support@0.8.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.9
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
|
|
||||||
'@ctrl/tinycolor@3.6.1': {}
|
'@ctrl/tinycolor@3.6.1': {}
|
||||||
|
|
||||||
|
'@dabh/diagnostics@2.0.3':
|
||||||
|
dependencies:
|
||||||
|
colorspace: 1.1.4
|
||||||
|
enabled: 2.0.0
|
||||||
|
kuler: 2.0.0
|
||||||
|
|
||||||
'@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.8.2))':
|
'@element-plus/icons-vue@2.3.1(vue@3.5.13(typescript@5.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
vue: 3.5.13(typescript@5.8.2)
|
vue: 3.5.13(typescript@5.8.2)
|
||||||
@@ -4872,6 +4994,31 @@ snapshots:
|
|||||||
|
|
||||||
'@humanwhocodes/object-schema@2.0.3': {}
|
'@humanwhocodes/object-schema@2.0.3': {}
|
||||||
|
|
||||||
|
'@inversifyjs/common@1.5.0': {}
|
||||||
|
|
||||||
|
'@inversifyjs/container@1.5.4(reflect-metadata@0.2.2)':
|
||||||
|
dependencies:
|
||||||
|
'@inversifyjs/common': 1.5.0
|
||||||
|
'@inversifyjs/core': 5.0.0(reflect-metadata@0.2.2)
|
||||||
|
'@inversifyjs/reflect-metadata-utils': 1.1.0(reflect-metadata@0.2.2)
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
|
||||||
|
'@inversifyjs/core@5.0.0(reflect-metadata@0.2.2)':
|
||||||
|
dependencies:
|
||||||
|
'@inversifyjs/common': 1.5.0
|
||||||
|
'@inversifyjs/prototype-utils': 0.1.0
|
||||||
|
'@inversifyjs/reflect-metadata-utils': 1.1.0(reflect-metadata@0.2.2)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- reflect-metadata
|
||||||
|
|
||||||
|
'@inversifyjs/prototype-utils@0.1.0':
|
||||||
|
dependencies:
|
||||||
|
'@inversifyjs/common': 1.5.0
|
||||||
|
|
||||||
|
'@inversifyjs/reflect-metadata-utils@1.1.0(reflect-metadata@0.2.2)':
|
||||||
|
dependencies:
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
|
||||||
'@isaacs/cliui@8.0.2':
|
'@isaacs/cliui@8.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width: 5.1.2
|
string-width: 5.1.2
|
||||||
@@ -5221,6 +5368,8 @@ snapshots:
|
|||||||
'@types/node': 20.17.23
|
'@types/node': 20.17.23
|
||||||
'@types/send': 0.17.4
|
'@types/send': 0.17.4
|
||||||
|
|
||||||
|
'@types/triple-beam@1.3.5': {}
|
||||||
|
|
||||||
'@types/trusted-types@2.0.7': {}
|
'@types/trusted-types@2.0.7': {}
|
||||||
|
|
||||||
'@types/tunnel@0.0.7':
|
'@types/tunnel@0.0.7':
|
||||||
@@ -5229,6 +5378,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/validator@13.12.2': {}
|
'@types/validator@13.12.2': {}
|
||||||
|
|
||||||
|
'@types/web-animations-js@2.2.16': {}
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.16': {}
|
'@types/web-bluetooth@0.0.16': {}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1)(typescript@5.8.2)':
|
'@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.8.2))(eslint@8.57.1)(typescript@5.8.2)':
|
||||||
@@ -5773,8 +5924,23 @@ snapshots:
|
|||||||
|
|
||||||
color-name@1.1.4: {}
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
color-string@1.9.1:
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.4
|
||||||
|
simple-swizzle: 0.2.2
|
||||||
|
|
||||||
color-support@1.1.3: {}
|
color-support@1.1.3: {}
|
||||||
|
|
||||||
|
color@3.2.1:
|
||||||
|
dependencies:
|
||||||
|
color-convert: 1.9.3
|
||||||
|
color-string: 1.9.1
|
||||||
|
|
||||||
|
colorspace@1.1.4:
|
||||||
|
dependencies:
|
||||||
|
color: 3.2.1
|
||||||
|
text-hex: 1.0.0
|
||||||
|
|
||||||
combined-stream@1.0.8:
|
combined-stream@1.0.8:
|
||||||
dependencies:
|
dependencies:
|
||||||
delayed-stream: 1.0.0
|
delayed-stream: 1.0.0
|
||||||
@@ -5999,6 +6165,8 @@ snapshots:
|
|||||||
|
|
||||||
emoji-regex@9.2.2: {}
|
emoji-regex@9.2.2: {}
|
||||||
|
|
||||||
|
enabled@2.0.0: {}
|
||||||
|
|
||||||
encodeurl@1.0.2: {}
|
encodeurl@1.0.2: {}
|
||||||
|
|
||||||
encodeurl@2.0.0: {}
|
encodeurl@2.0.0: {}
|
||||||
@@ -6343,6 +6511,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
picomatch: 4.0.2
|
picomatch: 4.0.2
|
||||||
|
|
||||||
|
fecha@4.2.3: {}
|
||||||
|
|
||||||
file-entry-cache@6.0.1:
|
file-entry-cache@6.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
flat-cache: 3.2.0
|
flat-cache: 3.2.0
|
||||||
@@ -6382,6 +6552,8 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.3.3: {}
|
flatted@3.3.3: {}
|
||||||
|
|
||||||
|
fn.name@1.1.0: {}
|
||||||
|
|
||||||
follow-redirects@1.15.9: {}
|
follow-redirects@1.15.9: {}
|
||||||
|
|
||||||
for-each@0.3.5:
|
for-each@0.3.5:
|
||||||
@@ -6541,6 +6713,8 @@ snapshots:
|
|||||||
|
|
||||||
graphemer@1.4.0: {}
|
graphemer@1.4.0: {}
|
||||||
|
|
||||||
|
gsap@3.12.7: {}
|
||||||
|
|
||||||
has-bigints@1.1.0: {}
|
has-bigints@1.1.0: {}
|
||||||
|
|
||||||
has-flag@3.0.0: {}
|
has-flag@3.0.0: {}
|
||||||
@@ -6658,6 +6832,13 @@ snapshots:
|
|||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
|
|
||||||
|
inversify@7.1.0(reflect-metadata@0.2.2):
|
||||||
|
dependencies:
|
||||||
|
'@inversifyjs/common': 1.5.0
|
||||||
|
'@inversifyjs/container': 1.5.4(reflect-metadata@0.2.2)
|
||||||
|
'@inversifyjs/core': 5.0.0(reflect-metadata@0.2.2)
|
||||||
|
reflect-metadata: 0.2.2
|
||||||
|
|
||||||
ip-address@9.0.5:
|
ip-address@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
jsbn: 1.1.0
|
jsbn: 1.1.0
|
||||||
@@ -6674,6 +6855,8 @@ snapshots:
|
|||||||
|
|
||||||
is-arrayish@0.2.1: {}
|
is-arrayish@0.2.1: {}
|
||||||
|
|
||||||
|
is-arrayish@0.3.2: {}
|
||||||
|
|
||||||
is-async-function@2.1.1:
|
is-async-function@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
async-function: 1.0.0
|
async-function: 1.0.0
|
||||||
@@ -6874,6 +7057,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
json-buffer: 3.0.1
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
|
kuler@2.0.0: {}
|
||||||
|
|
||||||
leven@3.1.0: {}
|
leven@3.1.0: {}
|
||||||
|
|
||||||
levn@0.4.1:
|
levn@0.4.1:
|
||||||
@@ -6935,6 +7120,15 @@ snapshots:
|
|||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
|
|
||||||
|
logform@2.7.0:
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@types/triple-beam': 1.3.5
|
||||||
|
fecha: 4.2.3
|
||||||
|
ms: 2.1.3
|
||||||
|
safe-stable-stringify: 2.5.0
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@5.1.1:
|
lru-cache@5.1.1:
|
||||||
@@ -7219,6 +7413,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
wrappy: 1.0.2
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
one-time@1.0.0:
|
||||||
|
dependencies:
|
||||||
|
fn.name: 1.1.0
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
deep-is: 0.1.4
|
deep-is: 0.1.4
|
||||||
@@ -7449,6 +7647,8 @@ snapshots:
|
|||||||
|
|
||||||
readdirp@4.1.2: {}
|
readdirp@4.1.2: {}
|
||||||
|
|
||||||
|
reflect-metadata@0.2.2: {}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.8
|
call-bind: 1.0.8
|
||||||
@@ -7580,6 +7780,8 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-regex: 1.2.1
|
is-regex: 1.2.1
|
||||||
|
|
||||||
|
safe-stable-stringify@2.5.0: {}
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
sass@1.85.1:
|
sass@1.85.1:
|
||||||
@@ -7736,6 +7938,10 @@ snapshots:
|
|||||||
once: 1.4.0
|
once: 1.4.0
|
||||||
simple-concat: 1.0.1
|
simple-concat: 1.0.1
|
||||||
|
|
||||||
|
simple-swizzle@0.2.2:
|
||||||
|
dependencies:
|
||||||
|
is-arrayish: 0.3.2
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
simple-update-notifier@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 7.7.1
|
semver: 7.7.1
|
||||||
@@ -7852,6 +8058,8 @@ snapshots:
|
|||||||
minipass: 3.3.6
|
minipass: 3.3.6
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
stack-trace@0.0.10: {}
|
||||||
|
|
||||||
statuses@2.0.1: {}
|
statuses@2.0.1: {}
|
||||||
|
|
||||||
string-width@4.2.3:
|
string-width@4.2.3:
|
||||||
@@ -7997,6 +8205,8 @@ snapshots:
|
|||||||
commander: 2.20.3
|
commander: 2.20.3
|
||||||
source-map-support: 0.5.21
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
|
text-hex@1.0.0: {}
|
||||||
|
|
||||||
text-table@0.2.0: {}
|
text-table@0.2.0: {}
|
||||||
|
|
||||||
tinyglobby@0.2.12:
|
tinyglobby@0.2.12:
|
||||||
@@ -8020,6 +8230,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode: 2.3.1
|
punycode: 2.3.1
|
||||||
|
|
||||||
|
triple-beam@1.4.1: {}
|
||||||
|
|
||||||
ts-api-utils@1.4.3(typescript@5.8.2):
|
ts-api-utils@1.4.3(typescript@5.8.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.8.2
|
typescript: 5.8.2
|
||||||
@@ -8096,6 +8308,10 @@ snapshots:
|
|||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
reflect.getprototypeof: 1.0.10
|
reflect.getprototypeof: 1.0.10
|
||||||
|
|
||||||
|
typeit@8.8.7:
|
||||||
|
dependencies:
|
||||||
|
'@types/web-animations-js': 2.2.16
|
||||||
|
|
||||||
typescript@5.8.2: {}
|
typescript@5.8.2: {}
|
||||||
|
|
||||||
ufo@1.5.4: {}
|
ufo@1.5.4: {}
|
||||||
@@ -8373,6 +8589,26 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
string-width: 4.2.3
|
string-width: 4.2.3
|
||||||
|
|
||||||
|
winston-transport@4.9.0:
|
||||||
|
dependencies:
|
||||||
|
logform: 2.7.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
|
||||||
|
winston@3.17.0:
|
||||||
|
dependencies:
|
||||||
|
'@colors/colors': 1.6.0
|
||||||
|
'@dabh/diagnostics': 2.0.3
|
||||||
|
async: 3.2.6
|
||||||
|
is-stream: 2.0.1
|
||||||
|
logform: 2.7.0
|
||||||
|
one-time: 1.0.0
|
||||||
|
readable-stream: 3.6.2
|
||||||
|
safe-stable-stringify: 2.5.0
|
||||||
|
stack-trace: 0.0.10
|
||||||
|
triple-beam: 1.4.1
|
||||||
|
winston-transport: 4.9.0
|
||||||
|
|
||||||
wkx@0.5.0:
|
wkx@0.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 20.17.23
|
'@types/node': 20.17.23
|
||||||
|
|||||||
Reference in New Issue
Block a user