This commit is contained in:
2024-08-10 20:35:02 +08:00
parent 88233c58e3
commit 90f6ed0bc3
50 changed files with 5333 additions and 0 deletions

30
.gitignore vendored Normal file
View File

@@ -0,0 +1,30 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
coverage
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.tsbuildinfo

View File

@@ -0,0 +1,10 @@
export default {
OK: {
code: 0,
msg: 'ok'
},
SERVER_ERROR: {
code: -1,
msg: '服务器错误'
}
}

View File

@@ -0,0 +1,8 @@
{
"mysql":{
"host":"server.tonesc.cn",
"user":"root",
"password":"245565",
"database":"tonecn"
}
}

View File

View File

@@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
import STANDARDRESPONSE from "../../STANDARDRESPONSE";
import Logger from "../../plugs/Logger";
import MySQLConnection from "../../plugs/database/MySQLConnection";
Logger.info('[API][/download/list] 加载成功')
router.get('/list', async (req: any, res: any) => {
let listRes = await MySQLConnection.execute('SELECT `id`,`recommand`,`title`,`describe`,`addition`,`icon_src` FROM `resource` WHERE `type` = "download"');
if(!listRes){
res.json(STANDARDRESPONSE.SERVER_ERROR)
return;
}
res.json({
...STANDARDRESPONSE.OK,
data: listRes
})
});
export default router;

View File

@@ -0,0 +1,34 @@
const express = require('express');
const router = express.Router();
const { Logger } = require('@plugs/Logger');
const { STANDARDRESPONSE } = require('@root/STANDARDRESPONSE')
const { RotationVerificationService } = require('@plugs/Service/RotationVerificationService');
Logger.info(`[API][/hmv/checkRotationVerification] 加载成功`);
// 验证旋转验证传入RVSession和用户旋转角度返回验证结果
router.post('/checkRotationVerification', async (req, res) => {
let { session, angle } = req.body;
// session为RVSession(32位UUID)angle为用户旋转角度
if(!session || !angle || session.length != 32){
Logger.warn(`[API][/hmv/checkRotationVerification]参数缺失或错误(session=${session}, angle=${angle})`);
res.json({...STANDARDRESPONSE.PARAMETER_MISSING});
return;
}
let result = await RotationVerificationService.check(session,angle);
switch(result){
case 0:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]已过期,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_EXPIRED, endpoint: result});
return;
case -1:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证次数已达上限,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_MAX_ATTEMPTS, endpoint: result});
return;
case -2:
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证角度差异过大,验证未通过`);
res.json({...STANDARDRESPONSE.ROTATION_VERIFICATION_ANGLE_DIFFERENCE, endpoint: result});
return;
}
Logger.info(`[API][/hmv/checkRotationVerification] session[${session}]验证通过`);
res.json({...STANDARDRESPONSE.OK});
})
module.exports = router;

View File

@@ -0,0 +1,45 @@
import Logger from "@plugs/Logger";
import STANDARDRESPONSE from "STANDARDRESPONSE";
const express = require('express');
const router = express.Router();
const sharp = require('sharp');
const fs = require('fs');
const path = require("path");
const { v4: uuidv4 } = require('uuid');
Logger.info(`[API][/hmv/rotationVerification] 加载成功`);
// 获取人机验证过程的图片返回一个RVSession用于后续的验证
router.get('/rotationVerification', async (req, res) => {
// 读取图像文件
try {
// 获取随机图片路径
const directoryPath = __dirname.slice(0, -8) + "/assets/RotationVerificationPicture/"
let files = fs.readdirSync(directoryPath);
files = files.filter(file => path.extname(file).toLowerCase() === '.jpg');
let filePath= directoryPath + files[Math.floor(Math.random() * files.length)]
Logger.info("[API][/hmv/rotationVerification] 获取人机验证图片:" + filePath)
// 生成session和角度信息
let session_uuid = uuidv4().replace(/-/g,'');// 32 个字符
const rotateDeg = 60 + Math.floor(Math.random() * 240 );// 限制角度范围 60 ~ 300
// 加入验证池
let RVAddRes = await RotationVerificationService.add(session_uuid, rotateDeg);
if(!RVAddRes){
Logger.err("[API][/hmv/rotationVerification] 添加验证信息到验证池中失败")
res.json({...STANDARDRESPONSE.SERVER_ERROR});
return;
}
// 图片旋转处理
const rotatedImage = await sharp(filePath)
.rotate(-rotateDeg)
.toBuffer();
// 转换为Base64编码
let imageBase64 = "data:image/jpg;base64," + rotatedImage.toString('base64');
Logger.info(`[API][/hmv/rotationVerification] session[${session_uuid}] 验证码图片已生成,角度[${rotateDeg}]`)
res.json({...STANDARDRESPONSE.OK,'data': imageBase64,'session': session_uuid})
} catch (error) {
Logger.err("[API][/hmv/rotationVerification] 获取人机验证图片错误:" + error)
res.json({...STANDARDRESPONSE.SERVER_ERROR})
}
})
module.exports = router;

View File

@@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
import STANDARDRESPONSE from "../../STANDARDRESPONSE";
import Logger from "../../plugs/Logger";
import MySQLConnection from "../../plugs/database/MySQLConnection";
Logger.info('[API][/resource/list] 加载成功')
router.get('/list', async (req: any, res: any) => {
let listRes = await MySQLConnection.execute('SELECT `id`,`recommand`,`title`,`describe`,`addition`,`icon_src`,`src` FROM `resource` WHERE `type` = "resource"');
if(!listRes){
res.json(STANDARDRESPONSE.SERVER_ERROR)
return;
}
res.json({
...STANDARDRESPONSE.OK,
data: listRes
})
});
export default router;

33
Server/index.ts Normal file
View File

@@ -0,0 +1,33 @@
import Logger from "./plugs/Logger";
Logger.info('====== 服务器开始启动 ======');
require('./plugs/Settingconfig');
require('./plugs/database/MySQLConnection');
require('./plugs/database/RedisConnection');
const express = require('express');
const cors = require('cors')
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors({
origin: ['http://localhost:5173'],
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type','Authorization','Access-Control-Allow-Origin'],
credentials: true,
}))
app.use((req: any, res: any, next: Function) => {
Logger.info(`[API][${req.method}][${req.url}] 被请求`);
next();
});
// 注册接口
import resourceList from './apis/resource/list';
import downloadList from './apis/download/list';
app.use('/resource', resourceList);
app.use('/download', downloadList);
app.listen(8080, () => {
Logger.info('[Server] API服务已开放在http://localhost:8080');
});

8
Server/nodemon.json Normal file
View File

@@ -0,0 +1,8 @@
{
"watch": ["*.ts"],
"execMap": {
"ts": "ts-node"
},
"ignore": ["*.test.ts"],
"ext": "ts"
}

25
Server/package.json Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/node": "^20.12.12",
"nodemon": "^3.1.0",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2",
"ioredis": "^5.4.1",
"mysql2": "^3.9.7"
}
}

24
Server/plugs/Logger.ts Normal file
View File

@@ -0,0 +1,24 @@
class _Logger{
constructor(){
}
formatTime(): string{
let date = new Date();
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
}
info(msg: string){
console.log(`[${this.formatTime()}][INFO] ${msg}`);
}
warn(msg: string){
console.log(`[${this.formatTime()}][WARN] ${msg}`);
}
error(msg: string){
console.log(`[${this.formatTime()}][ERROR] ${msg}`);
}
}
let Logger = new _Logger();
export default Logger;

View File

@@ -0,0 +1,54 @@
// 读取上级目录的Settingconfig.json文件
import * as fs from 'fs';
import * as path from 'path';
import Logger from './Logger';
interface MySqlConfig {
host: string;
user: string;
password: string;
database: string;
}
interface ServiceRotationVerificationType{
AllowMaxTryCount: number;
AllowMaxAngleDiff: number;
ExpriedTimeSec: number;
}
interface ServiceType {
rotationVerification: ServiceRotationVerificationType
}
interface SettingConfigType {
mysql: MySqlConfig;
service: ServiceType;
}
const SettingConfig: SettingConfigType = {
mysql:{
host: "server.tonesc.cn",
user: "root",
password: "245565",
database: "tonecn"
},
service: {
rotationVerification: {
AllowMaxTryCount: 5,// 最大允许尝试次数
AllowMaxAngleDiff: 10,// 允许的最大角度差异
ExpriedTimeSec: 60// 单次session过期时间,单位秒
}
}
};
// (async () => {
// const filePath = path.join(__dirname, '..', 'Settingconfig.json');
// let readRes = fs.readFileSync(filePath, 'utf-8');
// try {
// let config = JSON.parse(readRes);
// Object.assign(SettingConfig, config);
// } catch (error) {
// Logger.error(`配置文件出错 ${error}`)
// throw new Error('配置文件格式错误');
// }
// })()
export default SettingConfig

View File

@@ -0,0 +1,69 @@
// MYSQL数据库连接池
// 版本v0.1
const mysql = require('mysql2/promise');
import Logger from "../Logger";
import SettingConfig from "../Settingconfig";
class MySQLConnectPool{
pool: any;
constructor()
{
this.pool = this.createConnectPool();
Logger.info("[MySQL] 数据库连接池已创建")
setTimeout(async () => {
let res = await this.testConnection();
if(res)
Logger.info("[MySQL] 数据库测试成功")
else
Logger.error("[MySQL] 数据库测试失败")
}, 10);
}
// 内部函数,无需手动调用
createConnectPool(){
return mysql.createPool({
host: SettingConfig.mysql.host,
database: SettingConfig.mysql.database,
user: SettingConfig.mysql.user,
password: SettingConfig.mysql.password,
waitForConnections:true,
connectionLimit:10,
queueLimit:0
})
}
// 内部函数,无需手动调用
async testConnection(){
try {
let res = await this.execute("SELECT 1 + 1 As result");
if(res[0].result == 2)
return 1;
else
return 0;
} catch (error) {
Logger.error(`[MYSQL] 数据库测试发生了错误:`+error);
return 0;
}
}
// 执行SQL语句
async execute(sql: string,values = undefined){
let connection;
try {
connection = await this.pool.getConnection();
let [rows, fields] = await connection.execute(sql,values);
return rows;
} catch (error) {
Logger.error("[MYSQL] 数据库发生错误:"+error);
return undefined;
} finally{
if(connection)
connection.release();
}
}
}
let MySQLConnection = new MySQLConnectPool();
export default MySQLConnection;

View File

@@ -0,0 +1,98 @@
const Redis = require('ioredis');
import Logger from "../Logger";
class _RedisConnection{
pool: any;
constructor(){
this.pool = new Redis({
host: 'server.tonesc.cn',
password: '2Pj4Ss9al3mS1',
connectionPoolSize: 10,
});
Logger.info('[Redis] 数据库连接池已创建')
setTimeout(async () => {
let res = await this.set('redis_test', '1');
if(res)
Logger.info('[Redis] 数据库测试成功')
else
Logger.error('[Redis] 数据库测试失败')
}, 10);
}
/**
* 设置键值对
* @param {*} key
* @param {*} value
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
set(key: string, value: string){
return new Promise((resolve) => {
this.pool.set(key, value, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 设置键时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
/**
* 获取键值对成功返回值失败返回undefined
* @param {string} key
* @returns { Promise<String> } 成功返回值失败返回undefined
*/
get(key: string): Promise<string> {
return new Promise((resolve) => {
this.pool.get(key, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 获取键时发生错误:' + err);
throw new Error('Redis连接错误')
} else {
resolve(value);
}
})
})
}
/**
* 删除键值对成功返回true失败返回false
* @param {string} key
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
del(key: string){
return new Promise((resolve) => {
this.pool.del(key, (err: any, value: any) => {
if (err) {
Logger.error('[Redis] 删除键时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
/**
* 设置键的过期时间
* @param {*} key
* @param {*} seconds
* @returns { Promise<Boolean> } 成功返回true失败返回false
*/
expire(key: any, seconds: number){
return new Promise((resolve) => {
this.pool.expire(key, seconds, (err: number, value: number) => {
if (err) {
Logger.error('[Redis] 设置键过期时发生错误:' + err);
resolve(false);
} else {
resolve(true);
}
})
})
}
}
const RedisConnection = new _RedisConnection();
export default RedisConnection;

View File

@@ -0,0 +1,100 @@
import Logger from "@plugs/Logger";
import SettingConfig from "@plugs/Settingconfig";
import RedisConnection from "@plugs/database/RedisConnection";
class RotationVerificationService{
constructor(){
Logger.info('[Service][RotationVerificationService] 旋转图像验证服务已启动')
}
async #get(session: string){
// 通过session获取单个存储对象无需手动调用
try {
let result: = await RedisConnection.get('RotationVerificationService:' + session);
return JSON.parse(result);// 由于RVService的值为JSON字符串因此需要解析
} catch (error) {
// 不一定是数据库出错,有可能是已过期
Logger.warn(`[Service][RotationVerificationService] 获取session[${session}]时发生错误:${error}`);
return undefined;
}
}
async #remove(session: string){
// 删除session无需手动调用
let res = await RedisConnection.del('RotationVerificationService:' + session);
if(!res)
Logger.err(`[Service][RotationVerificationService] 删除session[${session}]失败`);
}
/**
* 将session和角度信息存储便于后续验证
* @param {*} session
* @param {*} rotateDeg
*/
async add(session: string,rotateDeg: number){
let result = {
rotateDeg: rotateDeg,
tryCount: 0,
isPassed: false
}
let res = await RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
if(!res)
{
Logger.err(`[Service][RotationVerificationService] 存储session[${session}]失败`);
return false;
}
// 设置过期时间
RedisConnection.expire('RotationVerificationService:' + session, this.ExpriedTimeSec);
return true;
}
/**
* 查询该session是否已通过验证使用后自动实效
* @param {*} session
* @returns {Boolean} 验证通过结果
*/
async isPassed(session){
// 通过后有效期60s
let result = await this.#get(session);
if(!result)
return false;
let res = result.isPassed;
if(res)
// 验证通过该session失效防止重复验证
this.#remove(session);
return res;
}
/**
* 检查session和角度信息
* @param {*} session
* @param {*} rotateDeg
* @returns {Number} 0,已过期(或未加入验证池) -1,超过最大尝试次数 1,验证通过 -2,角度差异过大
*/
async check(session,rotateDeg){
let result = await this.#get(session);
if(!result)
return 0;// 已过期(或未加入验证池)
if(Math.abs(result.rotateDeg - rotateDeg) <= this.AllowMaxAngleDiff)
{
// 验证通过标识为已通过并设定有效期为60s
result.isPassed = true;
await RedisConnection.del('RotationVerificationService:' + session);
await RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
RedisConnection.expire('RotationVerificationService:' + session, this.ExpriedTimeSec);
return 1;
}
result.tryCount++;// 浪费一次尝试次数
if(result.tryCount >= this.AllowMaxTryCount)
{
this.#remove(session);
return -1;// 超过最大尝试次数
}
// 保存尝试次数
RedisConnection.set('RotationVerificationService:' + session, JSON.stringify(result));
return -2;// 角度差异过大
}
}
let _RotationVerificationService = new RotationVerificationService();
module.exports.RotationVerificationService = _RotationVerificationService;

944
Server/pnpm-lock.yaml generated Normal file
View File

@@ -0,0 +1,944 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
cors:
specifier: ^2.8.5
version: 2.8.5
express:
specifier: ^4.19.2
version: 4.19.2
ioredis:
specifier: ^5.4.1
version: 5.4.1
mysql2:
specifier: ^3.9.7
version: 3.9.7
devDependencies:
'@types/node':
specifier: ^20.12.12
version: 20.12.12
nodemon:
specifier: ^3.1.0
version: 3.1.0
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.12.12)(typescript@5.4.5)
typescript:
specifier: ^5.4.5
version: 5.4.5
packages:
/@cspotcode/source-map-support@0.8.1:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@ioredis/commands@1.2.0:
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
dev: false
/@jridgewell/resolve-uri@3.1.2:
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
engines: {node: '>=6.0.0'}
dev: true
/@jridgewell/sourcemap-codec@1.4.15:
resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
dev: true
/@jridgewell/trace-mapping@0.3.9:
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
dependencies:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@tsconfig/node10@1.0.11:
resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
dev: true
/@tsconfig/node12@1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14@1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16@1.0.4:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true
/@types/node@20.12.12:
resolution: {integrity: sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==}
dependencies:
undici-types: 5.26.5
dev: true
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
/acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
engines: {node: '>=0.4.0'}
hasBin: true
dev: true
/anymatch@3.1.3:
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.1
dev: true
/arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false
/balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
dev: true
/body-parser@1.20.2:
resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.11.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/braces@3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
dev: false
/call-bind@1.0.7:
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.2.4
set-function-length: 1.2.2
dev: false
/chokidar@3.6.0:
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.3
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.3
normalize-path: 3.0.0
readdirp: 3.6.0
optionalDependencies:
fsevents: 2.3.3
dev: true
/cluster-key-slot@1.1.2:
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
engines: {node: '>=0.10.0'}
dev: false
/concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
dependencies:
safe-buffer: 5.2.1
dev: false
/content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
dev: false
/cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
dev: false
/cookie@0.6.0:
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
engines: {node: '>= 0.6'}
dev: false
/cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: false
/debug@4.3.4(supports-color@5.5.0):
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
supports-color: 5.5.0
/define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
es-errors: 1.3.0
gopd: 1.0.1
dev: false
/denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
dev: false
/depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dev: false
/diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
dev: false
/es-define-property@1.0.0:
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.2.4
dev: false
/es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
dev: false
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
dev: false
/express@4.19.2:
resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==}
engines: {node: '>= 0.10.0'}
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.2
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.6.0
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.2.0
fresh: 0.5.2
http-errors: 2.0.0
merge-descriptors: 1.0.1
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.7
proxy-addr: 2.0.7
qs: 6.11.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.18.0
serve-static: 1.15.0
setprototypeof: 1.2.0
statuses: 2.0.1
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: false
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/finalhandler@1.2.0:
resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
engines: {node: '>= 0.8'}
dependencies:
debug: 2.6.9
encodeurl: 1.0.2
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.1
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
dev: false
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
dev: false
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
dev: false
/generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
dependencies:
is-property: 1.0.2
dev: false
/get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
has-proto: 1.0.3
has-symbols: 1.0.3
hasown: 2.0.2
dev: false
/glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.3
dev: true
/gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies:
get-intrinsic: 1.2.4
dev: false
/has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
/has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies:
es-define-property: 1.0.0
dev: false
/has-proto@1.0.3:
resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
engines: {node: '>= 0.4'}
dev: false
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
dev: false
/hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
dev: false
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
dev: false
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/ignore-by-default@1.0.1:
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
dev: true
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
/ioredis@5.4.1:
resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
engines: {node: '>=12.22.0'}
dependencies:
'@ioredis/commands': 1.2.0
cluster-key-slot: 1.1.2
debug: 4.3.4(supports-color@5.5.0)
denque: 2.1.0
lodash.defaults: 4.2.0
lodash.isarguments: 3.1.0
redis-errors: 1.2.0
redis-parser: 3.0.0
standard-as-callback: 2.1.0
transitivePeerDependencies:
- supports-color
dev: false
/ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
dev: false
/is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.3.0
dev: true
/is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
dev: true
/is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/is-property@1.0.2:
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
dev: false
/lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
dev: false
/lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
dev: false
/long@5.2.3:
resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==}
dev: false
/lru-cache@7.18.3:
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
engines: {node: '>=12'}
dev: false
/lru-cache@8.0.5:
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
engines: {node: '>=16.14'}
dev: false
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: false
/merge-descriptors@1.0.1:
resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
dev: false
/methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
dev: false
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
dev: false
/minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
dependencies:
brace-expansion: 1.1.11
dev: true
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: false
/mysql2@3.9.7:
resolution: {integrity: sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw==}
engines: {node: '>= 8.0'}
dependencies:
denque: 2.1.0
generate-function: 2.3.1
iconv-lite: 0.6.3
long: 5.2.3
lru-cache: 8.0.5
named-placeholders: 1.1.3
seq-queue: 0.0.5
sqlstring: 2.3.3
dev: false
/named-placeholders@1.1.3:
resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==}
engines: {node: '>=12.0.0'}
dependencies:
lru-cache: 7.18.3
dev: false
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
dev: false
/nodemon@3.1.0:
resolution: {integrity: sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==}
engines: {node: '>=10'}
hasBin: true
dependencies:
chokidar: 3.6.0
debug: 4.3.4(supports-color@5.5.0)
ignore-by-default: 1.0.1
minimatch: 3.1.2
pstree.remy: 1.1.8
semver: 7.6.2
simple-update-notifier: 2.0.0
supports-color: 5.5.0
touch: 3.1.1
undefsafe: 2.0.5
dev: true
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
/object-inspect@1.13.1:
resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==}
dev: false
/on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: false
/path-to-regexp@0.1.7:
resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
dev: false
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
dev: true
/proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
dev: false
/pstree.remy@1.1.8:
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
dev: true
/qs@6.11.0:
resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.6
dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
dev: false
/raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: false
/readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.1
dev: true
/redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
dev: false
/redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
dependencies:
redis-errors: 1.2.0
dev: false
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/semver@7.6.2:
resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==}
engines: {node: '>=10'}
hasBin: true
dev: true
/send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
engines: {node: '>= 0.8.0'}
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/seq-queue@0.0.5:
resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==}
dev: false
/serve-static@1.15.0:
resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
engines: {node: '>= 0.8.0'}
dependencies:
encodeurl: 1.0.2
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.18.0
transitivePeerDependencies:
- supports-color
dev: false
/set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
dependencies:
define-data-property: 1.1.4
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.2.4
gopd: 1.0.1
has-property-descriptors: 1.0.2
dev: false
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false
/side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.7
es-errors: 1.3.0
get-intrinsic: 1.2.4
object-inspect: 1.13.1
dev: false
/simple-update-notifier@2.0.0:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
dependencies:
semver: 7.6.2
dev: true
/sqlstring@2.3.3:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
dev: false
/standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
dev: false
/statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
dev: false
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
dependencies:
has-flag: 3.0.0
/to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
dev: false
/touch@3.1.1:
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
hasBin: true
dev: true
/ts-node@10.9.2(@types/node@20.12.12)(typescript@5.4.5):
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
hasBin: true
peerDependencies:
'@swc/core': '>=1.2.50'
'@swc/wasm': '>=1.2.50'
'@types/node': '*'
typescript: '>=2.7'
peerDependenciesMeta:
'@swc/core':
optional: true
'@swc/wasm':
optional: true
dependencies:
'@cspotcode/source-map-support': 0.8.1
'@tsconfig/node10': 1.0.11
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.4
'@types/node': 20.12.12
acorn: 8.11.3
acorn-walk: 8.3.2
arg: 4.1.3
create-require: 1.1.1
diff: 4.0.2
make-error: 1.3.6
typescript: 5.4.5
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
dev: false
/typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
engines: {node: '>=14.17'}
hasBin: true
dev: true
/undefsafe@2.0.5:
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
dev: true
/undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: true
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
dev: false
/utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
dev: false
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true

14
Server/tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@plugs/*": ["plugs/*"],
"@apis/*": ["apis/*"]
}
}
}

3
tonecn/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

33
tonecn/README.md Normal file
View File

@@ -0,0 +1,33 @@
# tonecn
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Type Support for `.vue` Imports in TS
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
## Customize configuration
See [Vite Configuration Reference](https://vitejs.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Type-Check, Compile and Minify for Production
```sh
npm run build
```

9
tonecn/auto-imports.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const ElMessage: typeof import('element-plus/es')['ElMessage']
}

22
tonecn/components.d.ts vendored Normal file
View File

@@ -0,0 +1,22 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
Agreement: typeof import('./src/components/Common/Agreement.vue')['default']
BlogToolBar: typeof import('./src/components/Blog/BlogToolBar.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElInput: typeof import('element-plus/es')['ElInput']
ElPopover: typeof import('element-plus/es')['ElPopover']
Footer: typeof import('./src/components/Common/Footer.vue')['default']
Header: typeof import('./src/components/Common/Header.vue')['default']
RotationVerification: typeof import('./src/components/Common/RotationVerification.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
}

1
tonecn/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

13
tonecn/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

32
tonecn/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "tonecn",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --build --force"
},
"dependencies": {
"axios": "^1.6.8",
"element-plus": "^2.7.3",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
},
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^20.12.5",
"@vitejs/plugin-vue": "^5.0.4",
"@vue/tsconfig": "^0.5.1",
"npm-run-all2": "^6.1.2",
"typescript": "~5.4.0",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.0",
"vite": "^5.2.8",
"vue-tsc": "^2.0.11"
}
}

1497
tonecn/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

BIN
tonecn/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

15
tonecn/src/App.vue Normal file
View File

@@ -0,0 +1,15 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
import HeaderVue from '@/components/Common/Header.vue'
import FooterVue from '@/components/Common/Footer.vue'
</script>
<template>
<header>
<HeaderVue />
</header>
<main>
<RouterView />
<FooterVue />
</main>
</template>

BIN
tonecn/src/assets/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

View File

@@ -0,0 +1,15 @@
body{
margin: 0 0;
padding: 0 0;
font-family: Arial, Helvetica, sans-serif;
}
#app {
margin: 0 0;
padding: 0 0;
}
a {
text-decoration: none;
color: #000;
}

View File

@@ -0,0 +1,201 @@
<script setup>
import { Star, Edit, StarFilled, Watch } from '@element-plus/icons-vue'
import { ref, onMounted, onUnmounted, watch } from 'vue';
import FingerprintJS from '@fingerprintjs/fingerprintjs';
import { useRoute } from 'vue-router';
import { request } from '@/lib/request';
import { ElMessage, ElMessageBox } from 'element-plus';
let showToolBar = defineModel();
const emit = defineEmits(['loadComment'])
watch(showToolBar, (newData, oldData) => {
if (newData)
isScrollingUp.value = true;
})
const route = useRoute();
const uuid = route.params.uuid;
let input_comment = ref('')
let input_comment_name = ref('')
let isStarted = ref(false)
let fgId;
let CommentDialogVisible = ref(false)
let isScrollingUp = ref(true);
onMounted(async () => {
window.addEventListener('wheel', handleWheel);
const fp = await FingerprintJS.load();
const result = await fp.get();
fgId = result.visitorId;
});
onUnmounted(() => {
window.removeEventListener('wheel', handleWheel);
});
function handleWheel(event) {
// deltaY > 0 表示向下滚动deltaY < 0 表示向上滚动
isScrollingUp.value = event.deltaY < 0;
showToolBar.value = isScrollingUp.value;
}
let blogLike = async () => {
if (isStarted.value) {
ElMessage({
message: '你已经点过赞啦!',
type: 'success',
})
return;
}
let res = await ServerAPI.async_getRequest(`BlogLike?uuid=${uuid}&fgId=${fgId}`);
try {
if (res && res.status == 'OK') {
if (res.code == 1) {
// 点赞成功
isStarted.value = true;
ElMessage({
message: '点赞成功!',
type: 'success',
})
} else if (res.code == 0) {
// 重复点赞
isStarted.value = true;
ElMessage({
message: '你已经点过赞啦!',
type: 'success',
})
}
} else {
throw new Error('点赞失败')
}
} catch (error) {
// 网络错误等原因
console.log(error)
ElMessage({
message: '点赞失败',
type: 'error',
})
}
}
// 评论按钮触发事件
let blogComment = async () => {
if (!input_comment.value) {
ElMessage({
message: '请先填写评论内容呀~',
type: 'warning'
})
return;
}
if (input_comment.value.length >= 65535) {
ElMessage({
message: '评论内容太长啦~',
type: 'warning'
})
return;
}
CommentDialogVisible.value = true;
}
// 提交评论
let sendComment = async () => {
if (input_comment_name.value.length >= 255) {
ElMessage({
message: '昵称太长啦~',
type: 'warning'
})
return;
}
let res = await ServerAPI.async_postRequest(`PostBlogComment`, {
'uuid': uuid,
'fgId': fgId,
'comment': input_comment.value,
'comment_name': input_comment_name.value ? input_comment_name.value : '匿名',
});
try {
if (res && res.status == 'OK') {
if (res.code == 1) {
// 评论成功
ElMessage({
message: '评论成功!',
type: 'success',
})
CommentDialogVisible.value = false;
// 重新加载评论区内容
emit('loadComment');
return;
} else if (res.code == 0) {
// 评论3次机会用完
ElMessage({
message: '您的评论次数已达上限',
type: 'warning',
})
CommentDialogVisible.value = false;
return;
}
}
throw new Error('评论失败')
} catch (error) {
// 网络错误等原因
console.log(error)
ElMessage({
message: '评论失败',
type: 'error',
})
CommentDialogVisible.value = false;
}
}
</script>
<template>
<transition name="el-zoom-in-bottom">
<div class="tool-bar-container" v-show="isScrollingUp">
<div class="tool-bar">
<el-input v-model="input_comment" autosize type="textarea" class="input-comment"
placeholder="快来留下你的评论吧~" clearable />
<el-button type="primary" :icon="Edit" class="button" circle @click="blogComment"></el-button>
<el-button type="danger" :icon="isStarted ? StarFilled : Star" class="button" @click="blogLike"
circle></el-button>
</div>
<el-dialog v-model="CommentDialogVisible" title="提示">
<div style="display: flex;flex-direction: column;gap: 5px;">
<div>每篇文章最多可发布3条评论确认要现在发布吗</div>
<div>另外您可选择留下昵称</div>
<el-input v-model="input_comment_name" placeholder="昵称(可选)" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="CommentDialogVisible = false">取消</el-button>
<el-button type="primary" @click="sendComment">确认</el-button>
</div>
</template>
</el-dialog>
</div>
</transition>
</template>
<style scoped>
.tool-bar-container {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
/* height: 60px; */
background-color: #ffffff;
box-shadow: 0px 0px 9px 1px #ddd;
display: flex;
justify-content: center;
align-items: center;
}
.tool-bar {
width: 100%;
max-width: 600px;
display: flex;
align-items: center;
justify-content: center;
padding: 12px 20px;
}
.input-comment {
margin-right: 12px;
}
@media screen and (max-width: 570px) {
.button {
background-color: none;
}
}
</style>

View File

@@ -0,0 +1,78 @@
<script setup lang="ts">
</script>
<template>
<div @click="$emit('closeAgreement')" class="agreement-bcc">
<div class="agreement-container" onclick="event.stopPropagation()">
<div class="title">网站使用协议</div>
<li class="content">欢迎使用本网站在使用本网站之前请仔细阅读以下使用协议</li>
<li class="content">关于网站本网站是个人创建和维护的主要用于收藏各类资源和工具编写关于合法计算机技术的日记并提供与我交流的联系方式</li>
<li class="content">资源收藏声明本网站的个人收藏资源个人收藏工具栏目包含但不限于第三方链接这些资源仅供个人学习和参考本站对这些第三方链接及其内容不作任何形式的推广或认可链接内容观点或相关信息归原作者或所有者所有与本网站无关</li>
<li class="content">风险和责任访问者在使用这些第三方链接时应自行判断其内容的适用性并自行承担相关风险本网站及其所有者不承担因使用这些链接而产生的任何直接或间接损失的责任</li>
<li class="content">内容和交流本网站的日记部分包含对合法计算机技术的个人见解和经验分享访问者可以通过提供的联系方式与我就计算机技术话题进行合法交流</li>
<li class="content">版权和知识产权本网站的内容包括文本图像和代码除非另有声明均为本网站所有者个人创作并拥有版权未经许可不得复制分发或以其他方式使用这些内容</li>
<li class="content">同意协议通过使用或浏览本网站您表示您已阅读理解并同意遵守本协议的条款如果您不同意本协议的任何部分<u>停止使用</u>本网站</li>
<li class="content">变更和更新本网站的所有者保留随时更新或修改本使用协议的权利任何此类更改将在本网站上发布并生效</li>
<div class="subtitle">本使用协议的目的是确保网站的有效运行并保护访问者及网站所有者的合法权益感谢您的理解和支持</div>
</div>
</div>
</template>
<style scoped>
.agreement-bcc{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.15);
z-index: 1000;
display: flex;
align-items: center;
}
.agreement-container{
height: 75%;
width: 80%;
max-width: 700px;
padding: 40px;
border-radius: 12px;
box-sizing: border-box;
margin: auto;
background-color: #fff;
overflow-y: scroll;
transition: all 0.4s;
}
.agreement-container>.title{
font-size: 42px;
padding-bottom: 10px;
border-bottom: 2px solid #ddd;
margin-bottom: 20px;
transition: all 0.4s;
}
.agreement-container>.subtitle{
font-size: 18px;
color: #666;
margin-top: 20px;
transition: all 0.4s;
}
.agreement-container>.content{
font-size: 14px;
font-weight: 600;
margin: 15px 0;
line-height: 20px;
}
.agreement-container>.content>u{
text-decoration-thickness: 2px;
text-underline-offset: 3px;
}
@media screen and (max-width: 450px) {
.agreement-container{
padding: 25px;
}
.agreement-container>.title{
font-size: 28px;
}
.agreement-container>.subtitle{
font-size: 14px;
}
}
</style>

View File

@@ -0,0 +1,162 @@
<script setup lang="ts">
import copyText from '@/lib/copyText';
let copyTextwithMsg = (text : string) => {
if(copyText(text)){
ElMessage({
message: '复制成功',
type: 'success',
})
}else{
ElMessage({
message: '复制失败',
type: 'error',
})
}
}
</script>
<template>
<div class="footer-divider"></div>
<div class="footer-container">
<div class="left-container">
<a href="https://beian.miit.gov.cn/">
<div class="fillings">备案号渝ICP备2023009516号-1</div>
</a>
<div class="copyright">Copyright ©2020-2024 TONE All Rights Reserved.</div>
</div>
<div class="right-container">
<el-popover trigger="click" placement="top" :width="160">
<p>QQ号3341154833</p>
<div style="text-align: center; margin: 0">
<el-button size="small" type="primary" @click="copyTextwithMsg('3341154833')">复制</el-button>
</div>
<template #reference>
<div class="icon">
<svg t="1705909811918" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="5851" width="24" height="24">
<path
d="M824.8 613.2c-16-51.4-34.4-94.6-62.7-165.3C766.5 262.2 689.3 112 511.5 112 331.7 112 256.2 265.2 261 447.9c-28.4 70.8-46.7 113.7-62.7 165.3-34 109.5-23 154.8-14.6 155.8 18 2.2 70.1-82.4 70.1-82.4 0 49 25.2 112.9 79.8 159-26.4 8.1-85.7 29.9-71.6 53.8 11.4 19.3 196.2 12.3 249.5 6.3 53.3 6 238.1 13 249.5-6.3 14.1-23.8-45.3-45.7-71.6-53.8 54.6-46.2 79.8-110.1 79.8-159 0 0 52.1 84.6 70.1 82.4 8.5-1.1 19.5-46.4-14.5-155.8z"
p-id="5852" fill="#8a8a8a"></path>
</svg>
</div>
</template>
</el-popover>
<el-popover trigger="click" placement="top" :width="130">
<p>微信号tone0121</p>
<div style="text-align: center; margin: 0">
<el-button size="small" type="primary" @click="copyTextwithMsg('tone0121')">复制</el-button>
</div>
<template #reference>
<div class="icon">
<svg t="1705909888071" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="7135" width="24" height="24">
<path
d="M664.250054 368.541681c10.015098 0 19.892049 0.732687 29.67281 1.795902-26.647917-122.810047-159.358451-214.077703-310.826188-214.077703-169.353083 0-308.085774 114.232694-308.085774 259.274068 0 83.708494 46.165436 152.460344 123.281791 205.78483l-30.80868 91.730191 107.688651-53.455469c38.558178 7.53665 69.459978 15.308661 107.924012 15.308661 9.66308 0 19.230993-0.470721 28.752858-1.225921-6.025227-20.36584-9.521864-41.723264-9.521864-63.862493C402.328693 476.632491 517.908058 368.541681 664.250054 368.541681zM498.62897 285.87389c23.200398 0 38.557154 15.120372 38.557154 38.061874 0 22.846334-15.356756 38.156018-38.557154 38.156018-23.107277 0-46.260603-15.309684-46.260603-38.156018C452.368366 300.994262 475.522716 285.87389 498.62897 285.87389zM283.016307 362.090758c-23.107277 0-46.402843-15.309684-46.402843-38.156018 0-22.941502 23.295566-38.061874 46.402843-38.061874 23.081695 0 38.46301 15.120372 38.46301 38.061874C321.479317 346.782098 306.098002 362.090758 283.016307 362.090758zM945.448458 606.151333c0-121.888048-123.258255-221.236753-261.683954-221.236753-146.57838 0-262.015505 99.348706-262.015505 221.236753 0 122.06508 115.437126 221.200938 262.015505 221.200938 30.66644 0 61.617359-7.609305 92.423993-15.262612l84.513836 45.786813-23.178909-76.17082C899.379213 735.776599 945.448458 674.90216 945.448458 606.151333zM598.803483 567.994292c-15.332197 0-30.807656-15.096836-30.807656-30.501688 0-15.190981 15.47546-30.477129 30.807656-30.477129 23.295566 0 38.558178 15.286148 38.558178 30.477129C637.361661 552.897456 622.099049 567.994292 598.803483 567.994292zM768.25071 567.994292c-15.213493 0-30.594809-15.096836-30.594809-30.501688 0-15.190981 15.381315-30.477129 30.594809-30.477129 23.107277 0 38.558178 15.286148 38.558178 30.477129C806.808888 552.897456 791.357987 567.994292 768.25071 567.994292z"
fill="#8a8a8a" p-id="7136"></path>
</svg>
</div>
</template>
</el-popover>
<el-popover trigger="click" placement="top" :width="180">
<p>邮箱号3341154833@qq.com</p>
<div style="text-align: center; margin: 0">
<el-button size="small" type="primary" @click="copyTextwithMsg('3341154833@qq.com')">复制</el-button>
</div>
<template #reference>
<div class="icon">
<svg t="1705909952800" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="8336" width="24" height="24">
<path
d="M677.288 536.903l-167.315 156.052-170.532-156.052-246.146 230.058h831.748l-247.756-230.058zM954.002 287.541v423.114l-238.1-209.145 238.1-213.969zM64.336 290.756l231.666 212.361-231.666 207.534v-419.895zM93.294 234.45l418.287 389.33 413.461-389.33h-831.748z"
fill="#8a8a8a" p-id="8337"></path>
</svg>
</div>
</template>
</el-popover>
<a class="icon" href="https://github.com/tonecn">
<svg t="1705909977594" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="9342" width="24" height="24">
<path
d="M511.957333 21.333333C241.024 21.333333 21.333333 240.981333 21.333333 512c0 216.832 140.544 400.725333 335.573334 465.664 24.490667 4.394667 32.256-10.069333 32.256-23.082667 0-11.690667 0.256-44.245333 0-85.205333-136.448 29.610667-164.736-64.64-164.736-64.64-22.314667-56.704-54.4-71.765333-54.4-71.765333-44.586667-30.464 3.285333-29.824 3.285333-29.824 49.194667 3.413333 75.178667 50.517333 75.178667 50.517333 43.776 75.008 114.816 53.333333 142.762666 40.789333 4.522667-31.658667 17.152-53.376 31.189334-65.536-108.970667-12.458667-223.488-54.485333-223.488-242.602666 0-53.546667 19.114667-97.322667 50.517333-131.669334-5.034667-12.330667-21.930667-62.293333 4.778667-129.834666 0 0 41.258667-13.184 134.912 50.346666a469.802667 469.802667 0 0 1 122.88-16.554666c41.642667 0.213333 83.626667 5.632 122.88 16.554666 93.653333-63.488 134.784-50.346667 134.784-50.346666 26.752 67.541333 9.898667 117.504 4.864 129.834666 31.402667 34.346667 50.474667 78.122667 50.474666 131.669334 0 188.586667-114.730667 230.016-224.042666 242.090666 17.578667 15.232 33.578667 44.672 33.578666 90.453334v135.850666c0 13.141333 7.936 27.605333 32.853334 22.869334C862.250667 912.597333 1002.666667 728.746667 1002.666667 512 1002.666667 240.981333 783.018667 21.333333 511.957333 21.333333z"
p-id="9343" fill="#8a8a8a"></path>
</svg>
</a>
</div>
</div>
</template>
<style scoped>
.footer-divider {
height: 1px;
width: 100%;
background-color: #eee;
}
.footer-container {
/* height: 100%; */
height: 100px;
width: 100%;
margin: 0 auto;
max-width: 1170px;
display: flex;
justify-content: space-between;
align-items: center;
}
.left-container {
font-size: 12px;
margin-left: 25px;
cursor: default;
}
.fillings {
margin-bottom: 5px;
cursor: pointer;
}
.right-container {
margin-right: 25px;
display: flex;
}
.right-container>.icon {
cursor: pointer;
margin: 0 8px;
width: 30px;
height: 30px;
/* background-color: #aaa; */
border: 1.5px solid #eee;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
transition: background-color 0.3s;
}
.right-container>.icon:hover {
background-color: #eee;
}
.right-container>.icon>.icon {
width: 20px;
height: 20px;
}
@media screen and (max-width: 550px) {
.footer-container {
flex-direction: column;
align-items: center;
justify-content: flex-start;
margin-bottom: 20px;
}
.left-container {
margin: 20px 0px;
margin-bottom: 10px;
text-align: center;
}
.right-container {
margin: 0 0;
}
}
</style>

View File

@@ -0,0 +1,180 @@
<script setup lang="ts">
</script>
<template>
<div class="main-container">
<div class="header-container">
<div class="header-left">
<div>
<RouterLink :to="{ name: 'home' }" v-if="$route.name == 'home'">
<div class="emoji">🍭</div>
</RouterLink>
<RouterLink :to="{ name: 'home' }" v-else>
<div class="title">特恩(TONE)</div>
</RouterLink>
</div>
<div class="more" tabindex="0" id="header-more">
<svg t="1705913460674" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="10513" width="20" height="20">
<path
d="M150.528 431.104q37.888 0 58.368 24.064t20.48 51.712l0 11.264q0 34.816-17.92 58.88t-59.904 24.064l-7.168 0q-38.912 0-61.952-21.504t-23.04-59.392l0-14.336q0-13.312 5.632-26.624t15.872-24.064 25.6-17.408 33.792-6.656l10.24 0zM519.168 431.104q37.888 0 58.368 24.064t20.48 51.712l0 11.264q0 34.816-17.92 58.88t-59.904 24.064l-7.168 0q-38.912 0-61.952-21.504t-23.04-59.392l0-14.336q0-13.312 5.632-26.624t15.872-24.064 25.6-17.408 33.792-6.656l10.24 0zM887.808 431.104q37.888 0 58.368 24.064t20.48 51.712l0 11.264q0 34.816-17.92 58.88t-59.904 24.064l-7.168 0q-38.912 0-61.952-21.504t-23.04-59.392l0-14.336q0-13.312 5.632-26.624t15.872-24.064 25.6-17.408 33.792-6.656l10.24 0z"
p-id="10514"></path>
</svg>
</div>
</div>
<div class="header-right header-right-hidden" id="header-right">
<div class="link-list">
<RouterLink :to="{ name: 'resource' }" v-show="true">
<div class="link" :class="{ 'link-chosen': $route.name === 'resource' }">资源</div>
</RouterLink>
<RouterLink :to="{ name: 'download' }" v-show="true">
<div class="link" :class="{ 'link-chosen': $route.name === 'download' }">下载</div>
</RouterLink>
<RouterLink :to="{ name: 'blog' }" v-show="true">
<div class="link" :class="{ 'link-chosen': $route.name === 'blog' }">博客</div>
</RouterLink>
<RouterLink :to="{ name: 'login' }" v-show="true">
<div class="link" :class="{ 'link-chosen': $route.name === 'login' || $route.name === 'dashboard'}">控制台</div>
</RouterLink>
</div>
</div>
</div>
<div class="header-divider"></div>
</div>
<div class="main-container-block"></div>
</template>
<style scoped>
.main-container {
/* height: 69px; */
z-index: 500;
width: 100%;
position: fixed;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
background-color: rgba(255, 255, 255, 0.6);
}
.header-container {
/* height: 100%; */
width: 100%;
margin: 0 auto;
padding: 22px 0;
max-width: 1170px;
display: flex;
justify-content: space-between;
align-items: center;
transition: padding 0.2s;
}
.header-left,
.header-right {
margin: 0 25px;
}
.header-left .emoji {
font-size: 25px;
cursor: pointer;
}
.header-left .title {
/* 因为棒棒糖的高度为35需要保持统一 */
height: 35px;
line-height: 35px;
font-size: 18px;
font-weight: 600;
letter-spacing: 5px;
cursor: pointer;
color: #333;
}
.header-left .more {
/* 因为棒棒糖的高度为35需要保持统一 */
height: 35px;
display: none;
}
.link-list {
display: flex;
justify-content: flex-end;
align-items: center;
}
.link-list .link {
margin: 0 20px;
cursor: pointer;
color: #666;
transition: color 0.2s;
border-bottom: 3px solid rgba(0, 0, 0, 0);
}
.link-list .link:hover {
color: #000;
}
.link-list .link-chosen {
border-bottom: 3px solid #e03ebf;
}
.header-divider {
height: 1px;
width: 100%;
background-color: #eee;
}
.main-container-block {
height: 80px;
width: 100%;
transition: height 0.2s;
/* background-color: #fff; */
}
@media screen and (max-width: 800px) {
.header-container {
padding: 15px 0;
}
.main-container-block {
height: 65px;
}
.header-container {
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
}
.header-left {
width: 100%;
display: flex;
justify-content: space-between;
}
.header-left .more {
display: flex;
align-content: center;
flex-wrap: wrap;
margin-right: 50px;
cursor: pointer;
}
.header-right-hidden {
display: none;
}
.header-right {
/* width: 100%; */
margin-top: 15px;
}
.link-list {
display: flex;
flex-direction: column;
margin: 20px 10px;
}
.link-list .link {
font-size: 23px;
margin-top: 10px;
}
}
</style>

View File

@@ -0,0 +1,259 @@
<script setup>
import ServerSDK from '@/assets/ServerSDK';
import { onBeforeMount, onMounted, ref } from 'vue';
let imageBase64 = ref('');
const emit = defineEmits(['fail', 'success'])
let onVerifying = ref(false);
let onVerifyFail = ref(false);
onBeforeMount(async () => {
try {
let res = await ServerSDK.GetRotationVerification();
imageBase64.value = res;
} catch (error) {
console.log("获取图像验证码失败:"+error);
emit('fail');
}
})
onMounted(() => {
let slider = document.getElementById('RV-slider');
let imageEle = document.getElementById('RV-image');
let isDragging = false;// 正在移动标志位
let silderMoveable = true;// 是否可以开始移动标识位
let startX;
let deltaX;// 拖动距离
slider.addEventListener('mousedown', function(e) {
if(silderMoveable)
{
isDragging = true;
startX = e.clientX;
onVerifyFail.value = false;
}
})
slider.addEventListener('touchstart', function(e) {
if(silderMoveable) {
e.preventDefault();
isDragging = true;
startX = e.touches[0].clientX;
onVerifyFail.value = false;
}
}, { passive: false });
document.addEventListener('mousemove', function(e) {
if (isDragging) {
silderMoveable = false;// 产生了位移,则需等待验证后才可再次移动
deltaX = e.clientX - startX;
// 对位移距离限幅
if(deltaX < 0)
deltaX = 0;
if(deltaX > 200)
deltaX = 200;
// 调整滑块条位置样式
slider.style.transform = ` translateX(${deltaX}px)`;
// 调整图片旋转样式 200 -> 360映射 y = 9/5x
imageEle.style.transform = `rotate(${deltaX*9/5}deg)`
}
});
document.addEventListener('touchmove', function(e) {
if (isDragging) {
e.preventDefault();
silderMoveable = false;
deltaX = e.touches[0].clientX - startX;
if(deltaX < 0) deltaX = 0;
if(deltaX > 200) deltaX = 200;
slider.style.transform = `translateX(${deltaX}px)`;
imageEle.style.transform = `rotate(${deltaX*9/5}deg)`;
}
}, { passive: false });
document.addEventListener('mouseup', async () => {
if(isDragging)
{
isDragging = false;
if(!deltaX)
{
silderMoveable = true;// 位移距离为0无需验证可以继续移动
}else{
try {
onVerifying.value = true;
let res = await ServerSDK.CheckRotationVerification(deltaX * 9 / 5);
switch (res) {
case 1:
// 验证成功
emit('success');
break;
case -2:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
case -1:
console.log('验证次数过多,请重试')
emit('fail');
break;
default:
// 大概率是0过期和不存在
console.log('验证session过期或不存在')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:"+error);
emit('fail');
}finally{
onVerifying.value = false;
}
}
}
});
document.addEventListener('touchend', async () => {
if(isDragging)
{
isDragging = false;
if(!deltaX)
{
silderMoveable = true;// 位移距离为0无需验证可以继续移动
}else{
try {
onVerifying.value = true;
let res = await ServerSDK.CheckRotationVerification(deltaX * 9 / 5);
switch (res) {
case 1:
// 验证成功
emit('success');
break;
case -2:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
case -1:
console.log('验证次数过多,请重试')
emit('fail');
break;
default:
// 大概率是0过期和不存在
console.log('验证session过期或不存在')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:"+error);
emit('fail');
}finally{
onVerifying.value = false;
}
}
}
});
})
</script>
<template>
<transition name="el-fade-in-linear">
<div class="verification-bcc" @click="$emit('fail')">
<div class="verification-container" onclick="event.stopPropagation()">
<div class="title">安全验证</div>
<div class="subtitle">{{ onVerifying ? "正在验证,请稍后..." : (onVerifyFail?"验证失败,请再试一次":"拖动滑块,使图片角度为水平") }}</div>
<div class="image-container">
<img :src="imageBase64" alt="" id="RV-image">
</div>
<div class="slide-container">
<div class="slider" id="RV-slider">
<svg t="1706696449802" class="icon" viewBox="0 0 1024 1024" fill="#666" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1924" width="18" height="18"><path d="M567.32505 547.18536c20.970614-21.479197 20.970614-56.307424 0-77.790714L185.251168 77.115332c-20.971637-21.47715-54.975079-21.47715-75.948763 0-20.973684 21.484314-20.973684 56.30947 0 77.793784l344.188016 353.383446-344.188016 353.384469c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.971637 21.482267 54.975079 21.482267 75.948763 0l382.072858-392.280337 0.001024-0.004094zM440.60802 154.908092l344.18597 353.383446-344.18597 353.385493c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.972661 21.482267 54.975079 21.482267 75.949786 0l382.074905-392.281361c20.966521-21.478174 20.966521-56.307424 0-77.790714L516.555759 77.115332c-20.972661-21.47715-54.975079-21.47715-75.949786 0-20.971637 21.48329-20.971637 56.30947 0.002047 77.79276z" p-id="1925"></path></svg>
</div>
</div>
</div>
</div>
</transition>
</template>
<style scoped>
.verification-bcc{
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
background-color: #00000022;
z-index: 2000;
display: flex;
justify-content: center;
align-items: center;
}
.verification-container{
width: 300px;
height: 400px;
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
border-radius: 15px;
}
.verification-container>.title{
margin-top: 25px;
color: #888;
}
.verification-container>.subtitle{
margin-top: 8px;
}
.verification-container>.image-container{
display: flex;
margin-top: 28px;
width: 160px;
height: 160px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.verification-container>.image-container>img{
width: 226px;
height: 226px;
transform: rotate(0deg);
background-color: gray;
}
.verification-container>.slide-container{
width: 240px;
height: 40px;
border-radius: 20px;
background-color: #eeeeee;
margin-top: 25px;
display: flex;
align-items: center;
}
.verification-container>.slide-container>.slider{
width: 45px;
height: 45px;
background-color: #fff;
border-radius: 50%;
box-shadow: 0px 1px 5px 3px #ccc;
text-align: center;
line-height: 50px;
cursor: pointer;
position: relative;
transform: translateX(0);
}
</style>

View File

@@ -0,0 +1,27 @@
/**
*
* @param textToCopy 需要被复制的文本
* @returns 成功返回 true, 不成功返回 false
*/
const copyText = (textToCopy: string): boolean => {
// 创建一个<textarea>元素,将文本放入其中
const textArea = document.createElement("textarea");
textArea.value = textToCopy;
// 将<textarea>元素添加到DOM中
document.body.appendChild(textArea);
// 选择<textarea>元素中的文本
textArea.select();
let copyStatus = false; // true成功, false失败
try {
// 尝试将选定的文本复制到剪贴板
document.execCommand("copy");
copyStatus = true;
} catch (err) {
console.error("复制文本失败:", err);
}
// 从DOM中移除<textarea>元素
document.body.removeChild(textArea);
return copyStatus;
}
export default copyText;

23
tonecn/src/lib/request.ts Normal file
View File

@@ -0,0 +1,23 @@
import axios from "axios";
axios.defaults.baseURL = "http://localhost:8080";
interface ResponseData<T> {
code: number;
msg: string;
data: T;
}
axios.interceptors.response.use((response) => {
// 确保响应数据符合ResponseData接口的结构
const responseData: ResponseData<any> = response.data;
// 根据code值做不同的处理
if (responseData.code === 200) { // 假设200为成功的code
return responseData.data; // 当成功时只返回data字段
} else {
// 当不成功时抛出整个responseData或创建一个Error对象
throw new Error(`请求错误: ${responseData.msg}`);
}
});
export { axios as request };

14
tonecn/src/main.ts Normal file
View File

@@ -0,0 +1,14 @@
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,42 @@
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/resource',
name: 'resource',
component: () => import('../views/Resource.vue')
},
{
path: '/download',
name: 'download',
component: () => import('../views/Download.vue')
},
{
path: '/blog',
name: 'blog',
component: () => import('../views/Blog.vue')
},
{
path: '/console',
name: 'console',
component: () => import('../views/Console/Console.vue'),
children: [
{
path: 'login',
name: 'login',
component: () => import('../views/Console/Login.vue')
},
],
}
]
})
export default router

View File

@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

110
tonecn/src/views/Blog.vue Normal file
View File

@@ -0,0 +1,110 @@
<script setup>
// import ServerAPI from '@/assets/ServerAPI';
// import router from '@/router';
// import { nextTick, onBeforeMount, onMounted, reactive, ref } from 'vue';
// let load_fail = ref(false);
// let load_ok = ref(false);
// let urlTo = (url) => {
// window.location.href = url;
// }
// let blog_list = reactive([])
// onBeforeMount(async () => {
// let res = await ServerAPI.async_getRequest('GetBlogList');
// try {
// if(res.status == 'OK')
// {
// blog_list.push(...res.data);
// load_ok.value = true;
// }else{
// throw new Error('获取列表失败');
// }
// } catch (error) {
// console.log('获取列表发生错误:' + error)
// load_fail.value = true;
// }
// })
// let formatTimestamp = (timestamp) => {
// const date = new Date(timestamp);
// // 获取日期的各个部分
// const year = date.getFullYear();
// const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份是从0开始的
// const day = String(date.getDate()).padStart(2, '0');
// const hours = String(date.getHours()).padStart(2, '0');
// const minutes = String(date.getMinutes()).padStart(2, '0');
// const seconds = String(date.getSeconds()).padStart(2, '0');
// // 组装最终的字符串
// if(hours == '00' && minutes == '00' && seconds == '00')
// return `${year}/${month}/${day}`;
// else
// return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
// }
// let blogto = (uuid) => {
// // router.push({name:'blogcontent',params: { 'uuid': uuid }})
// let url = '/blogcontent/' + uuid;
// window.open(url, '_blank');
// }
</script>
<template>
<div class="bcc"></div>
<div class="content-container">
<el-empty description="加载失败,刷新后重试" style="margin: 0 auto;" v-if="load_fail"/>
<div style="gap: 30px;display: flex;flex-direction: column;" v-else>
<div class="coding" v-if="!load_ok">加载中请稍后...</div>
<el-empty description="暂无数据" style="margin: 0 auto;" v-if="load_ok && !blog_list.length"/>
<div class="blog-container" v-for="item of blog_list">
<div class="title" @click="blogto(item.uuid)">{{ item.title }}</div>
<div class="description">{{ item.description }}</div>
<div class="publish-time">{{ formatTimestamp(+item.publish_time) }}</div>
</div>
</div>
</div>
</template>
<style scoped>
.bcc{
position: fixed;
z-index: -1;
background-color: #f6f8f9;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.content-container{
display: flex;
flex-direction: column;
max-width: 800px;
margin: 50px auto;
padding: 0 20px;
}
.coding{
text-align: center;
margin-top: 20px;
margin-bottom: 300px;
}
.blog-container{
margin: 0 auto;
max-width: 400px;
width: 100%;
display: flex;
flex-direction: column;
}
.blog-container .title{
font-size: 26px;
font-weight: 600;
cursor: pointer;
display: block;
}
.blog-container .title:hover{
text-decoration: underline;
text-decoration-thickness: 3px;
}
.blog-container .description{
color: #666;
}
.blog-container .publish-time{
color: #888;
margin-top: 15px;
font-size: 14px;
}
</style>

View File

@@ -0,0 +1,6 @@
<script setup lang="ts">
</script>
<template>
<router-view></router-view>
</template>

View File

@@ -0,0 +1,296 @@
<script setup>
// import { ref, watch } from 'vue';
// import { ElMessage } from 'element-plus'
// import VerificationProcessVue from '@/components/RotationVerification.vue';
// import router from '@/router';
// import ServerAPI from '@/assets/ServerAPI';
// import ServerSDK from '@/assets/ServerSDK';
// let phone = ref("");
// let code = ref("");
// let sendSIMCodeCoolingTime = ref(-1);
// let ShowVerificationProcessVue = ref(false);
// // 监听phone使其保持3-4-4分割格式
// watch(phone,(newData)=>{
// newData = newData.replace(/\D/g,'');
// let newDatalen = newData.split('').length;
// if(newDatalen >= 4 && newDatalen <= 7)
// {
// let part1 = newData.slice(0, 3);
// let part2 = newData.slice(3);
// newData = part1+ " " +part2;
// }else if(newDatalen >= 8){
// let part1 = newData.slice(0, 3);
// let part2 = newData.slice(3, 7);
// let part3 = newData.slice(7);
// newData = part1 + " " + part2 + " " + part3;
// }
// phone.value = newData;
// })
// // 监听code使其只能是数字
// watch(code,(newData)=>{
// code.value = newData.replace(/\D/g,'');
// })
// // 按钮 - 发送验证码,先获取图像验证码,发短信由其他函数触发
// let sendSIMCode = async () => {
// // 冷却判定
// if(sendSIMCodeCoolingTime.value >= 0)
// {
// // 还在冷却中,不允许发送
// ElMessage({
// message: '冷却中,稍后再来获取吧',
// type: 'warning',
// });
// return;
// }
// // 手机号合法性验证
// const regex = /^1[3-9]\d{9}$/;
// if(!regex.test(phone.value.replace(/\D/g,'')))
// {
// ElMessage({
// message: '请输入正确的手机号',
// type: 'warning',
// });
// return;
// }
// // 准备发送验证码
// // 滑动验证码验证
// ShowVerificationProcessVue.value = true;
// }
// // 按钮 - 登录
// let login = async () => {
// // 是否已经发送验证码
// if(sendSIMCodeCoolingTime.value == -1)
// {
// // 其他情况(冷却倒数,冷却结束)都可以发送
// ElMessage({
// message: '请先获取验证码',
// type: 'warning',
// });
// return;
// }
// // 手机号合法性验证
// const regex = /^1[3-9]\d{9}$/;
// if(!regex.test(phone.value.replace(/\D/g,'')))
// {
// ElMessage({
// message: '请输入正确的手机号',
// type: 'warning',
// });
// return;
// }
// // 验证码合法性验证
// const regex2 = /\d{6}$/;
// if(!regex2.test(code.value))
// {
// ElMessage({
// message: '请输入正确的验证码',
// type: 'warning',
// });
// return;
// }
// // ...可以登录
// // console.log(localStorage.getItem('login_session'))
// let loginRes = await ServerSDK.Login(
// phone.value.replace(/\D/g,''),
// code.value
// )
// if(loginRes == 1)
// {
// ElMessage({
// message: '登录成功',
// type: 'success',
// });
// setTimeout(() => {
// router.push({name: 'console_console'});
// }, 1000);
// }else if(loginRes == 0){
// ElMessage({
// message: '验证码已过期',
// type: 'warning',
// });
// }else if(loginRes == -1){
// ElMessage({
// message: '验证码已失效',
// type: 'warning',
// });
// }else if(loginRes == -2){
// ElMessage({
// message: '验证码错误',
// type: 'warning',
// });
// }else{
// ElMessage({
// message: '登录失败,请重试',
// type: 'error',
// });
// }
// }
// // 短信验证码发送成功,修改界面交互
// let verify_success = async () => {
// // 验证成功,等待接收验证码后登录
// let sendSIMCodeRes = await ServerAPI.async_postRequest('SendSMSCode',{
// 'phone': phone.value.replace(/\D/g,''),
// 'session': localStorage.getItem('RVSession')
// })
// console.log(sendSIMCodeRes)
// if(sendSIMCodeRes && sendSIMCodeRes.status == 'OK')
// {
// // 发送成功,等待接收验证码后登录
// ElMessage({
// message: '发送成功',
// type: 'success',
// });
// }else{
// // 发送失败
// ElMessage({
// message: '发送失败,请重试',
// type: 'error',
// });
// }
// ShowVerificationProcessVue.value = false;// 关闭验证码界面
// sendSIMCodeCoolingTime.value = 59;// 冷却倒计时
// let SIMCodeCoolingTimer = setInterval(() => {
// sendSIMCodeCoolingTime.value--;
// if(sendSIMCodeCoolingTime.value <= 0)
// {
// clearInterval(SIMCodeCoolingTimer);
// sendSIMCodeCoolingTime.value = -2;
// }
// }, 1000);
// }
// // 图像验证码验证失败,短信验证码未发送
// let verify_fail = () => {
// ShowVerificationProcessVue.value = false;
// ElMessage({
// message: '发送失败,请重试',
// type: 'error',
// });
// }
// let onDev = () => {
// ElMessage({
// message: '抱歉,当前内容暂未开放',
// type: 'warning',
// });
// }
</script>
<template>
<div class="bcc"></div>
<div class="main-container">
<h1>登录到控制台</h1>
<el-popover
placement="bottom"
:width="300"
trigger="click"
>
<!-- <p>控制台是特恩(TONE)网页中的一个控制器界面您可以使用其中开放的一些控制器功能普通访客可通过登录后的界面暂存合法文件图片视频文档音频压缩包等管理员可通过登录后编辑本网页中的内容资源工具日记等详情请见<strong style="cursor: pointer;">特恩(TONE)控制台使用协议</strong></p> -->
<p>控制台是特恩(TONE)网页中的一个控制器界面如果您是管理员可通过登录后编辑本网页中的内容资源工具日记等详情请见<strong style="cursor: pointer;" @click="onDev">特恩(TONE)控制台使用协议</strong></p>
<template #reference>
<h3>控制台是什么</h3>
</template>
</el-popover>
<div class="form-container">
<el-input
v-model="phone"
class="input"
placeholder="请输入手机号"
maxlength="13"
inputmode="tel"
clearable>
<template #prepend> <div class="phone-prepend">+86</div></template>
</el-input>
<el-input
v-model="code"
class="input"
maxlength="6"
inputmode="numeric"
placeholder="验证码">
<template #append>
<el-button class="sendSIMCode-button" :class="{ 'sendSIMCode-button-cooling' : (sendSIMCodeCoolingTime > 0) }" @click="sendSIMCode">{{ sendSIMCodeCoolingTime > 0 ? `${sendSIMCodeCoolingTime}秒后再获取` : "获取验证码"}}</el-button>
</template>
</el-input>
<el-button class="login-botton" @click="login">登录</el-button>
</div>
</div>
<VerificationProcessVue v-if="ShowVerificationProcessVue" @fail="verify_fail" @success="verify_success" :phone="phone" />
</template>
<style scoped>
.bcc{
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
background-color: #f6f8f9;
}
.main-container{
padding: 30px;
margin-bottom: 160px;
}
.main-container>h1{
text-align: center;
font-size: 42px;
font-weight: 400;
margin-top: 50px;
cursor: default;
}
.main-container>h3{
text-align: center;
cursor: pointer;
}
.main-container .form-container{
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.phone-prepend{
width: 20px;
margin-left: -10px;
cursor: default;
text-align: center;
font-size: 15px;
}
.form-container .input{
width: 260px;
height: 35px;
font-size: 16px;
margin-top: 10px;
}
.input .sendSIMCode-button{
font-size: 14px;
font-weight: 400;
color: #333;
width: 120px;
}
.input .sendSIMCode-button:hover{
color: #333;
}
.input .sendSIMCode-button-cooling{
color: #888;
}
.input .sendSIMCode-button-cooling:hover{
color: #888;
}
.login-botton{
margin-top: 10px;
width: 260px;
height: 35px;
font-weight: 500;
}
.login-botton:hover{
background-color: #fff;
border-color: rgb(220, 223, 230);
color: #222;
}
.login-botton:focus{
background-color: #fff;
border-color: rgb(220, 223, 230);
color: #606266;
}
</style>

View File

@@ -0,0 +1,266 @@
<script setup>
// import { onBeforeMount, reactive, ref } from 'vue';
// import Agreement from '../components/agreement.vue'
// import ServerAPI from '@/assets/ServerAPI';
// let urlTo = (url) => {
// // window.location.href = url;
// window.open(url, '_blank');
// }
// let showAgreement = ref(false);
// let ResourceDatas = reactive({});
// let loadStatus = ref(0);// 0加载中1加载成功2加载失败
// onBeforeMount(async ()=>{
// let res = await ServerAPI.async_getRequest('GetDownloadList');
// try {
// if(res.status == 'OK')
// {
// Object.assign(ResourceDatas, res.data);
// loadStatus.value = 1;
// }else{
// console.log("获取资源失败:" + res.data)
// loadStatus.value = 2;
// }
// } catch (error) {
// console.log("获取资源失败:" + error)
// loadStatus.value = 2;
// }
// })
</script>
<template>
<div class="main-container">
<div class="bcc"></div>
<div class="title">一些可以直接下载的工具</div>
<div class="subtitle" v-if="loadStatus != 2">请在浏览此部分内容前阅读并同意<a
@click="showAgreement = true">网站使用协议</a>继续使用或浏览表示您接受协议条款</div>
<div class="load-fail" v-if="loadStatus == 2">加载失败请刷新界面重试</div>
<div class="load-fail" v-if="loadStatus == 0">加载中请稍后...</div>
<div class="content-container" v-if="loadStatus == 1">
<div class="content" @click="urlTo(`${item.src}`)" v-for="item of ResourceDatas">
<div class="icon-container">
<img :src="item.icon_src" alt="" class="icon">
</div>
<div class="describe-container">
<div class="title">{{ item.title }}</div>
<div class="describe">{{ item.describe }}</div>
<div class="lable-container">
<div class="lable" :class="{ 'lable-OS': lable.type === 'OS' }" v-for="lable of item.addition.lables">{{
lable.text }}</div>
</div>
</div>
<div class="lable-relative" v-if="item.addition.lable.text">
<div class="lable" :class="{ 'lable-2': (item.addition.lable.class.indexOf('lable-2') != -1) }">{{
item.addition.lable.text }}</div>
</div>
</div>
<div class="content content-hidden"></div>
</div>
</div>
<Agreement v-if="showAgreement" @closeAgreement="showAgreement = false" />
</template>
<style scoped>
.main-container {
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
.bcc {
position: fixed;
z-index: -1;
background-color: #f6f8f9;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.main-container>.title {
margin-top: 80px;
font-size: 42px;
cursor: default;
transition: all 0.4s;
}
.main-container>.subtitle {
margin-top: 20px;
color: #666;
cursor: default;
font-size: 12px;
padding: 0 20px;
}
.main-container>.subtitle>a {
color: #222;
cursor: pointer;
transition: all 0.3s;
border-bottom: 1px solid #f7f8f9;
}
.main-container>.subtitle>a:hover {
border-bottom: 1px solid #222;
}
.load-fail {
text-align: center;
margin-top: 80px;
cursor: default;
margin-bottom: 380px;
transition: all 0.4s;
}
.content-container {
display: flex;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
max-width: 1000px;
margin: 50px auto;
}
.content {
width: 380px;
/* height: 135px; */
margin: 20px 30px;
padding: 30px;
background-color: #fff;
border-radius: 15px;
display: flex;
justify-content: space-between;
box-sizing: border-box;
box-shadow: 0px 2px 5px 0px #ccc;
transition: all 0.4s;
overflow: hidden;
}
.content>.lable-relative {
position: relative;
width: 0;
height: 0;
bottom: 18px;
right: 38px;
text-align: center;
}
.content>.lable-relative>.lable {
width: 100px;
height: 18px;
line-height: 18px;
background-color: rgba(255, 25, 0, 0.7);
color: #fff;
font-size: 12px;
font-weight: 600;
transform: rotate(45deg);
}
.content>.lable-relative>.lable-2 {
background-color: rgba(255, 128, 0, 0.7);
}
.content-hidden {
opacity: 0;
height: 1px;
margin: 0 30px;
padding: 0 30px;
}
.content:hover {
box-shadow: 0px 5px 12px 0px #ccc;
}
.content .icon-container .icon {
width: 80px;
height: 80px;
margin-left: 10px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
box-shadow: 0px 1px 4px 1px #ccc;
border-radius: 10px;
transition: all 0.4s;
}
.content .describe-container {
margin-left: 20px;
/* width: 200px; */
flex: 1;
cursor: default;
}
.describe-container .title {
font-size: 23px;
font-weight: 600;
}
.describe-container .describe {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.describe-container .lable-container {
display: flex;
gap: 5px;
margin-top: 10px;
flex-wrap: wrap;
}
.describe-container .lable-container .lable {
font-size: 10px;
font-weight: 500;
color: #666;
background-color: #eceef1;
padding: 1px 6px;
border-radius: 20px;
}
.describe-container .lable-container .lable-OS {
background-color: #d6eeff;
}
@media screen and (min-width: 1200px) {
.content {
width: 410px;
margin-left: 40px;
margin-right: 40px;
}
}
@media screen and (max-width: 550px) {
.main-container>.title {
font-size: 28px;
margin-top: 50px;
}
.content-container {
flex-direction: column;
margin: 20px 0;
}
.load-fail {
margin-top: 40px;
margin-bottom: 450px;
}
}
@media screen and (max-width: 450px) {
.content {
width: 100%;
margin-left: 0;
margin-right: 0;
border-radius: 0;
margin-top: 5px;
}
.content .icon-container .icon {
width: 70px;
height: 70px;
}
.describe-container .title {
font-size: 20px;
}
}
</style>

View File

@@ -0,0 +1,154 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
let containerHeight = ref('800px');
onMounted(() => {
// 主盒子高度根据初始视窗高度自适应
containerHeight.value = window.innerHeight > 500 ? window.innerHeight - 90 + 'px' : '390px';
// 界面特效字体
let nameElement = document.getElementById("my-name");
if( nameElement == null){
console.error('未找到元素my-name')
return;
}
let colorNum = 66;
let colorNumReverse = false;
setInterval(() => {
if(colorNumReverse)
{
colorNum--;
if(colorNum<=66)
colorNumReverse = !colorNumReverse;
}else{
colorNum++;
if(colorNum>=255)
colorNumReverse = !colorNumReverse;
}
nameElement.style.backgroundImage = `linear-gradient(45deg, rgb(${colorNum}, 66, ${255 - (66 - colorNum)}), rgb(${255 - (66 - colorNum)}, 66, ${colorNum}))`;
}, 20);
})
</script>
<template>
<div class="bcc"></div>
<div class="main-container" id="home-main">
<img src="../assets/logo.jpg" alt="" class="logo">
<div class="name" id="my-name">特恩(TONE)</div>
<div class="self-introduction">一名计算机类专业在校本科大二学生</div>
<div class="button-container">
<a href="https://space.bilibili.com/474156211">
<button class="button" id="button-resource">哔哩哔哩</button>
</a>
<a href="https://github.com/tonecn">
<button class="button button-style2" id="button-code">GitHub</button>
</a>
</div>
</div>
</template>
<style scoped>
.bcc {
background-color: #fafafa;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.main-container {
margin: 0 auto;
max-width: 800px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: height 0.2s;
height: v-bind(containerHeight);
}
.logo {
border-radius: 50%;
width: 150px;
}
.name {
font-size: 42px;
font-weight: 800;
margin-top: 25px;
cursor: default;
background: linear-gradient(45deg, rgb(66, 66, 255), rgb(255, 66, 66));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: font-size 0.2s;
}
.self-introduction {
margin-top: 10px;
font-size: 23px;
cursor: default;
color: #888;
transition: font-size 0.2s;
}
.button-container {
margin-top: 28px;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.button {
cursor: pointer;
width: 130px;
height: 46px;
background-color: #2591f0;
color: #fff;
border: none;
font-size: 16px;
border-radius: 23px;
margin: 0 10px;
transition: background-color 0.2s;
box-shadow: 0px 3px 3px 0px rgba(0, 0, 0, 0.2);
}
.button-style2 {
background-color: #e87f29;
}
#button-resource:hover {
background-color: #1d74c0;
}
#button-code:hover {
background-color: #d5782c;
}
@media screen and (max-width: 470px) {
.name {
font-size: 34px;
}
.self-introduction {
font-size: 16px;
}
.button-container {
flex-direction: column;
gap: 10px;
margin-bottom: 60px;
}
.button {
width: 180px;
height: 40px;
}
}
@media screen and (max-width: 800px) {
.main-container {
height: calc(v-bind(containerHeight) + 15px);
}
}
</style>

View File

@@ -0,0 +1,257 @@
<script setup lang="ts">
import { request } from '@/lib/request';
import Agreement from '@/components/Common/Agreement.vue';
import { ref, onMounted, reactive } from 'vue';
let showAgreement = ref(false);
let loadStatus = ref(0);// 0加载中 1加载成功 2加载失败
let ResourceDatas: any[] = reactive([])
onMounted(async () => {
// 用于获取数据的函数
let res = await request.get('resource/list');
console.log(res)
if (res && res.code == 0) {
loadStatus.value = 1;
ResourceDatas.push(...res.data)
} else {
loadStatus.value = 2;
}
})
</script>
<template>
<div class="main-container">
<div class="bcc"></div>
<div class="title">精心挑选并收藏的资源</div>
<div class="subtitle" v-if="loadStatus != 2">请在浏览此部分内容前阅读并同意<a
@click="showAgreement = true">网站使用协议</a>继续使用或浏览表示您接受协议条款</div>
<div class="load-fail" v-if="loadStatus == 2">加载失败请刷新界面重试</div>
<div class="load-fail" v-if="loadStatus == 0">加载中请稍后...</div>
<div class="content-container" v-if="loadStatus == 1">
<div class="content" v-for="item of ResourceDatas">
<div class="icon-container">
<img :src="item.icon_src" alt="" class="icon">
</div>
<div class="describe-container">
<div class="title">{{ item.title }}</div>
<div class="describe">{{ item.describe }}</div>
<div class="lable-container">
<div class="lable" :class="{ 'lable-OS': lable.type === 'OS' }" v-for="lable of item.addition.lables">{{
lable.text }}</div>
</div>
</div>
<div class="lable-relative" v-if="item.addition.lable.text">
<div class="lable" :class="{ 'lable-2': (item.addition.lable.class.indexOf('lable-2') != -1) }">{{
item.addition.lable.text }}</div>
</div>
</div>
<div class="content content-hidden"></div>
</div>
</div>
<Agreement v-if="showAgreement" @closeAgreement="showAgreement = false" />
</template>
<style scoped>
.main-container {
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
.bcc {
position: fixed;
z-index: -1;
background-color: #f6f8f9;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
.main-container>.title {
margin-top: 80px;
font-size: 42px;
cursor: default;
transition: all 0.4s;
}
.main-container>.subtitle {
margin-top: 20px;
color: #666;
cursor: default;
font-size: 12px;
padding: 0 20px;
}
.main-container>.subtitle>a {
color: #222;
cursor: pointer;
transition: all 0.3s;
border-bottom: 1px solid #f7f8f9;
}
.main-container>.subtitle>a:hover {
border-bottom: 1px solid #222;
}
.load-fail {
text-align: center;
margin-top: 80px;
cursor: default;
margin-bottom: 380px;
transition: all 0.4s;
}
.content-container {
display: flex;
justify-content: center;
align-items: flex-start;
flex-wrap: wrap;
max-width: 1000px;
margin: 50px auto;
}
.content {
width: 380px;
/* height: 135px; */
margin: 20px 30px;
padding: 30px;
background-color: #fff;
border-radius: 15px;
display: flex;
justify-content: space-between;
box-sizing: border-box;
box-shadow: 0px 2px 5px 0px #ccc;
transition: all 0.4s;
overflow: hidden;
}
.content>.lable-relative {
position: relative;
width: 0;
height: 0;
bottom: 18px;
right: 38px;
text-align: center;
}
.content>.lable-relative>.lable {
width: 100px;
height: 18px;
line-height: 18px;
background-color: rgba(255, 25, 0, 0.7);
color: #fff;
font-size: 12px;
font-weight: 600;
transform: rotate(45deg);
}
.content>.lable-relative>.lable-2 {
background-color: rgba(255, 128, 0, 0.7);
}
.content-hidden {
opacity: 0;
height: 1px;
margin: 0 30px;
padding: 0 30px;
}
.content:hover {
box-shadow: 0px 5px 12px 0px #ccc;
}
.content .icon-container .icon {
width: 80px;
height: 80px;
margin-left: 10px;
margin-top: 10px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
box-shadow: 0px 1px 4px 1px #ccc;
border-radius: 10px;
transition: all 0.4s;
}
.content .describe-container {
margin-left: 20px;
/* width: 200px; */
flex: 1;
cursor: default;
}
.describe-container .title {
font-size: 23px;
font-weight: 600;
}
.describe-container .describe {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.describe-container .lable-container {
display: flex;
gap: 5px;
margin-top: 10px;
flex-wrap: wrap;
}
.describe-container .lable-container .lable {
font-size: 10px;
font-weight: 500;
color: #666;
background-color: #eceef1;
padding: 1px 6px;
border-radius: 20px;
}
.describe-container .lable-container .lable-OS {
background-color: #d6eeff;
}
@media screen and (min-width: 1200px) {
.content {
width: 410px;
margin-left: 40px;
margin-right: 40px;
}
}
@media screen and (max-width: 550px) {
.main-container>.title {
font-size: 28px;
margin-top: 50px;
}
.content-container {
flex-direction: column;
margin: 20px 0;
}
.load-fail {
margin-top: 40px;
margin-bottom: 450px;
}
}
@media screen and (max-width: 450px) {
.content {
width: 100%;
margin-left: 0;
margin-right: 0;
border-radius: 0;
margin-top: 5px;
}
.content .icon-container .icon {
width: 70px;
height: 70px;
}
.describe-container .title {
font-size: 20px;
}
}
</style>

17
tonecn/tsconfig.app.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@views/*": ["./src/views/*"],
"@assets/*": ["./src/assets/*"],
}
}
}

11
tonecn/tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"files": [],
"references": [
{
"path": "./tsconfig.node.json"
},
{
"path": "./tsconfig.app.json"
}
]
}

19
tonecn/tsconfig.node.json Normal file
View File

@@ -0,0 +1,19 @@
{
"extends": "@tsconfig/node20/tsconfig.json",
"include": [
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"nightwatch.conf.*",
"playwright.config.*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"module": "ESNext",
"moduleResolution": "Bundler",
"types": ["node"]
}
}

25
tonecn/vite.config.ts Normal file
View File

@@ -0,0 +1,25 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
})