This commit is contained in:
longdayi 2025-02-05 15:10:40 +08:00
parent bcd67a61ac
commit 43ca9881d8
32 changed files with 677 additions and 394 deletions

View File

@ -0,0 +1,19 @@
temperature: 0.5
maxTokens: 8192
---
<system>
请扮演一名经验丰富的高级软件开发工程师,根据用户提供的指令创建、改进或扩展代码功能。
输入要求:
1. 用户将提供目标文件名或需要实现的功能描述。
2. 输入中可能包括文件路径、代码风格要求,以及与功能相关的具体业务逻辑或技术细节。
任务描述:
1. 根据提供的文件名或功能需求,编写符合规范的代码文件或代码片段。
2. 如果已有文件,检查并基于现有实现完善功能或修复问题。
3. 遵循约定的开发框架、语言标准和最佳实践。
4. 注重代码可维护性,添加适当的注释,确保逻辑清晰。
输出要求:
1. 仅返回生成的代码或文件内容。
2. 全程使用中文注释
3. 尽量避免硬编码和不必要的复杂性,以提高代码的可重用性和效率。
4. 如功能涉及外部接口或工具调用,请确保通过注释给出清晰的说明和依赖。
</system>

View File

@ -4,11 +4,9 @@ maxTokens: 8192
<system>
角色定位:
- 高级软件开发工程师
- 代码文档化与知识传播专家
注释目标:
1. 顶部注释
- 模块/文件整体功能描述
- 使用场景
2. 类注释
- 核心功能概述
- 设计模式解析
@ -22,12 +20,9 @@ maxTokens: 8192
- 逐行解释代码意图
- 关键语句原理阐述
- 高级语言特性解读
- 潜在的设计考量
注释风格要求:
- 全程使用中文
- 专业、清晰、通俗易懂
- 面向初学者的知识传递
- 保持技术严谨性
输出约束:
- 仅返回添加注释后的代码
- 注释与代码完美融合

View File

@ -1,92 +1,91 @@
{
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "~32.3.2",
"@ag-grid-community/core": "~32.3.2",
"@ag-grid-community/react": "~32.3.2",
"@ag-grid-enterprise/clipboard": "~32.3.2",
"@ag-grid-enterprise/column-tool-panel": "~32.3.2",
"@ag-grid-enterprise/core": "~32.3.2",
"@ag-grid-enterprise/filter-tool-panel": "~32.3.2",
"@ag-grid-enterprise/master-detail": "~32.3.2",
"@ag-grid-enterprise/menu": "~32.3.2",
"@ag-grid-enterprise/range-selection": "~32.3.2",
"@ag-grid-enterprise/server-side-row-model": "~32.3.2",
"@ag-grid-enterprise/set-filter": "~32.3.2",
"@ag-grid-enterprise/status-bar": "~32.3.2",
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.26.25",
"@heroicons/react": "^2.2.0",
"@hookform/resolvers": "^3.9.1",
"@nice/client": "workspace:^",
"@nice/common": "workspace:^",
"@nice/iconer": "workspace:^",
"mind-elixir": "workspace:^",
"@nice/ui": "workspace:^",
"@tanstack/query-async-storage-persister": "^5.51.9",
"@tanstack/react-query": "^5.51.21",
"@tanstack/react-query-persist-client": "^5.51.9",
"@trpc/client": "11.0.0-rc.456",
"@trpc/react-query": "11.0.0-rc.456",
"@trpc/server": "11.0.0-rc.456",
"@xyflow/react": "^12.3.6",
"ag-grid-community": "~32.3.2",
"ag-grid-enterprise": "~32.3.2",
"ag-grid-react": "~32.3.2",
"antd": "^5.19.3",
"axios": "^1.7.2",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"d3-dag": "^1.1.0",
"d3-hierarchy": "^3.1.2",
"dayjs": "^1.11.12",
"elkjs": "^0.9.3",
"framer-motion": "^11.15.0",
"hls.js": "^1.5.18",
"idb-keyval": "^6.2.1",
"mitt": "^3.0.1",
"quill": "2.0.3",
"react": "18.2.0",
"react-beautiful-dnd": "^13.1.1",
"react-dom": "18.2.0",
"react-dropzone": "^14.3.5",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.4.1",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.24.1",
"superjson": "^2.2.1",
"tailwind-merge": "^2.6.0",
"uuid": "^10.0.0",
"yjs": "^13.6.20",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "18.2.38",
"@types/react-dom": "18.2.15",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
}
"name": "web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "~32.3.2",
"@ag-grid-community/core": "~32.3.2",
"@ag-grid-community/react": "~32.3.2",
"@ag-grid-enterprise/clipboard": "~32.3.2",
"@ag-grid-enterprise/column-tool-panel": "~32.3.2",
"@ag-grid-enterprise/core": "~32.3.2",
"@ag-grid-enterprise/filter-tool-panel": "~32.3.2",
"@ag-grid-enterprise/master-detail": "~32.3.2",
"@ag-grid-enterprise/menu": "~32.3.2",
"@ag-grid-enterprise/range-selection": "~32.3.2",
"@ag-grid-enterprise/server-side-row-model": "~32.3.2",
"@ag-grid-enterprise/set-filter": "~32.3.2",
"@ag-grid-enterprise/status-bar": "~32.3.2",
"@ant-design/icons": "^5.4.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@floating-ui/react": "^0.26.25",
"@heroicons/react": "^2.2.0",
"@hookform/resolvers": "^3.9.1",
"@nice/client": "workspace:^",
"@nice/common": "workspace:^",
"@nice/iconer": "workspace:^",
"@nice/utils": "workspace:^",
"mind-elixir": "workspace:^",
"@nice/ui": "workspace:^",
"@tanstack/query-async-storage-persister": "^5.51.9",
"@tanstack/react-query": "^5.51.21",
"@tanstack/react-query-persist-client": "^5.51.9",
"@trpc/client": "11.0.0-rc.456",
"@trpc/react-query": "11.0.0-rc.456",
"@trpc/server": "11.0.0-rc.456",
"@xyflow/react": "^12.3.6",
"ag-grid-community": "~32.3.2",
"ag-grid-enterprise": "~32.3.2",
"ag-grid-react": "~32.3.2",
"antd": "^5.19.3",
"axios": "^1.7.2",
"browser-image-compression": "^2.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"d3-dag": "^1.1.0",
"d3-hierarchy": "^3.1.2",
"dayjs": "^1.11.12",
"elkjs": "^0.9.3",
"framer-motion": "^11.15.0",
"hls.js": "^1.5.18",
"idb-keyval": "^6.2.1",
"mitt": "^3.0.1",
"quill": "2.0.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.54.2",
"react-hot-toast": "^2.4.1",
"react-resizable": "^3.0.5",
"react-router-dom": "^6.24.1",
"superjson": "^2.2.1",
"tailwind-merge": "^2.6.0",
"uuid": "^10.0.0",
"yjs": "^13.6.20",
"zod": "^3.23.8"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
"@types/react": "18.2.38",
"@types/react-dom": "18.2.15",
"@vitejs/plugin-react-swc": "^3.5.0",
"autoprefixer": "^10.4.20",
"eslint": "^9.9.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.9",
"globals": "^15.9.0",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.1",
"vite": "^5.4.1"
}
}

View File

@ -45,13 +45,6 @@ export function useTusUpload() {
onError: (error: Error) => void,
fileKey: string // 添加文件唯一标识
) => {
// if (!file || !file.name || !file.type) {
// const error = new Error("不可上传该类型文件");
// setUploadError(error.message);
// onError(error);
// return;
// }
setIsUploading(true);
setUploadProgress((prev) => ({ ...prev, [fileKey]: 0 }));
setUploadError(null);

View File

@ -3,10 +3,9 @@ export * from "./enum"
export * from "./types"
export * from "./utils"
export * from "./constants"
export * from "./select"
export * from "./collaboration"
export * from "./db"
// export * from "./generated"
export * from "@prisma/client"
export * from "./upload"
export * from "./tool"
export * from "./tool"
export * from "./models"

View File

@ -0,0 +1,7 @@
import { Course, Enrollment } from "@prisma/client";
import { SectionDto } from "./section";
export type CourseDto = Course & {
enrollments: Enrollment[];
sections: SectionDto[];
};

View File

@ -0,0 +1,15 @@
import { Department } from "@prisma/client";
import { TreeDataNode} from "../types";
import { StaffDto } from "./staff";
import { TermDto } from "./term";
export interface DeptSimpleTreeNode extends TreeDataNode {
hasStaff?: boolean;
}
export type DepartmentDto = Department & {
parent: DepartmentDto;
children: DepartmentDto[];
hasChildren: boolean;
staffs: StaffDto[];
terms: TermDto[];
};

View File

@ -0,0 +1,9 @@
export * from "./course"
export * from "./department"
export * from "./message"
export * from "./staff"
export * from "./term"
export * from "./post"
export * from "./rbac"
export * from "./section"
export * from "./select"

View File

@ -0,0 +1,7 @@
import { Message, Staff } from "@prisma/client";
export type MessageDto = Message & {
readed: boolean;
receivers: Staff[];
sender: Staff;
};

View File

@ -0,0 +1,35 @@
import { Post, Department, Staff } from "@prisma/client";
import { StaffDto } from "./staff";
export type PostComment = {
id: string;
type: string;
title: string;
content: string;
authorId: string;
domainId: string;
referenceId: string;
resources: string[];
createdAt: Date;
updatedAt: Date;
parentId: string;
author: {
id: string;
showname: string;
username: string;
avatar: string;
};
};
export type PostDto = Post & {
readed: boolean;
readedCount: number;
author: StaffDto;
limitedComments: PostComment[];
commentsCount: number;
perms?: {
delete: boolean;
// edit: boolean;
};
watchableDepts: Department[];
watchableStaffs: Staff[];
};

View File

@ -0,0 +1,22 @@
import { RoleMap } from "@prisma/client";
import { StaffDto } from "./staff";
export interface ResPerm {
instruction?: boolean;
createProgress?: boolean;
requestCancel?: boolean;
acceptCancel?: boolean;
conclude?: boolean;
createRisk?: boolean;
editIndicator?: boolean;
editMethod?: boolean;
editOrg?: boolean;
edit?: boolean;
delete?: boolean;
read?: boolean;
}
export type RoleMapDto = RoleMap & {
staff: StaffDto;
};

View File

@ -0,0 +1,5 @@
import { Lecture, Section } from "@prisma/client";
export type SectionDto = Section & {
lectures: Lecture[];
};

View File

@ -0,0 +1,38 @@
import { Staff, Department } from "@prisma/client";
import { RolePerms } from "../enum";
export type StaffRowModel = {
avatar: string;
dept_name: string;
officer_id: string;
phone_number: string;
showname: string;
username: string;
};
export type UserProfile = Staff & {
permissions: RolePerms[];
deptIds: string[];
parentDeptIds: string[];
domain: Department;
department: Department;
};
export type StaffDto = Staff & {
domain?: Department;
department?: Department;
};
export interface AuthDto {
token: string;
staff: StaffDto;
refreshToken: string;
perms: string[];
}
export interface JwtPayload {
sub: string;
username: string;
}
export interface TokenPayload {
id: string;
phoneNumber: string;
name: string;
}

View File

@ -0,0 +1,8 @@
import { Term } from "@prisma/client";
import { ResPerm } from "./rbac";
export type TermDto = Term & {
permissions: ResPerm;
children: TermDto[];
hasChildren: boolean;
};

View File

@ -1,15 +1,3 @@
import type {
Staff,
Department,
Term,
Message,
Post,
RoleMap,
Section,
Lecture,
Course,
Enrollment,
} from "@prisma/client";
import { SocketMsgType, RolePerms } from "./enum";
import { RowRequestSchema } from "./schema";
import { z } from "zod";
@ -18,44 +6,11 @@ export interface SocketMessage<T = any> {
type: SocketMsgType;
payload?: T;
}
export interface DataNode {
title: any;
key: string;
hasChildren?: boolean;
children?: DataNode[];
value: string;
data?: any;
isLeaf?: boolean;
}
export interface JwtPayload {
sub: string;
username: string;
}
export type AppLocalSettings = {
urgent?: number;
important?: number;
exploreTime?: Date;
};
export type StaffDto = Staff & {
domain?: Department;
department?: Department;
};
export interface AuthDto {
token: string;
staff: StaffDto;
refreshToken: string;
perms: string[];
}
export type UserProfile = Staff & {
permissions: RolePerms[];
deptIds: string[];
parentDeptIds: string[];
domain: Department;
department: Department;
};
export interface DataNode {
title: any;
key: string;
@ -70,99 +25,6 @@ export interface TreeDataNode extends DataNode {
isLeaf?: boolean;
pId?: string;
}
export interface DeptSimpleTreeNode extends TreeDataNode {
hasStaff?: boolean;
}
export type StaffRowModel = {
avatar: string;
dept_name: string;
officer_id: string;
phone_number: string;
showname: string;
username: string;
};
export interface TokenPayload {
id: string;
phoneNumber: string;
name: string;
}
export interface ResPerm {
instruction?: boolean;
createProgress?: boolean;
requestCancel?: boolean;
acceptCancel?: boolean;
conclude?: boolean;
createRisk?: boolean;
editIndicator?: boolean;
editMethod?: boolean;
editOrg?: boolean;
edit?: boolean;
delete?: boolean;
read?: boolean;
}
export type MessageDto = Message & {
readed: boolean;
receivers: Staff[];
sender: Staff;
};
export type PostComment = {
id: string;
type: string;
title: string;
content: string;
authorId: string;
domainId: string;
referenceId: string;
resources: string[];
createdAt: Date;
updatedAt: Date;
parentId: string;
author: {
id: string;
showname: string;
username: string;
avatar: string;
};
};
export type PostDto = Post & {
readed: boolean;
readedCount: number;
author: StaffDto;
limitedComments: PostComment[];
commentsCount: number;
perms?: {
delete: boolean;
// edit: boolean;
};
watchableDepts: Department[];
watchableStaffs: Staff[];
};
export type TermDto = Term & {
permissions: ResPerm;
children: TermDto[];
hasChildren: boolean;
};
export type DepartmentDto = Department & {
parent: DepartmentDto;
children: DepartmentDto[];
hasChildren: boolean;
staffs: StaffDto[];
terms: TermDto[];
};
export type RoleMapDto = RoleMap & {
staff: StaffDto;
};
export type SectionDto = Section & {
lectures: Lecture[];
};
export type CourseDto = Course & {
enrollments: Enrollment[];
sections: SectionDto[];
};
export interface BaseSetting {
appConfig?: {
splashScreen?: string;

View File

@ -1,5 +1,5 @@
import { Staff } from "@prisma/client";
import { TermDto, TreeDataNode } from "./types";
import { TreeDataNode } from "./types";
export function findNodeByKey(
nodes: TreeDataNode[],
targetKey: string
@ -318,7 +318,7 @@ export const mapPropertiesToObjects = <T, K extends keyof T>(array: T[], key: K)
* @returns {Array<Record<string, T>>}
*/
export const mapArrayToObjectArray = <T>(
array: T[],
array: T[],
key: string = 'id'
): Array<Record<string, T>> => {
return array.map(item => ({ [key]: item }));

View File

View File

View File

View File

View File

View File

View File

@ -38,4 +38,4 @@ export const getCompressedImageUrl = (originalUrl: string): string => {
const lastSlashIndex = cleanUrl.lastIndexOf("/");
return `${cleanUrl.slice(0, lastSlashIndex)}/compressed/${cleanUrl.slice(lastSlashIndex + 1).replace(/\.[^.]+$/, ".webp")}`;
};
export * from "./types";
export * from "./type-utils";

View File

@ -0,0 +1,99 @@
/***
*/
/***
* @paramvalue
* @parammin
* @parammax
*/export const clamp = (value: number, min: number, max: number): number => {
return Math.min(Math.max(value, min), max);
};
/**
*
* @param value要四舍五入的值* @param decimals
*/export const round = (value: number, decimals: number = 0): number => {
const multiplier = Math.pow(10, decimals);
return Math.round(value * multiplier) / multiplier;
};/**
* * @param min * @param max * @param isInteger ,false
*/
export const random = (min: number, max: number, isInteger: boolean = false): number => {
const value = Math.random() * (max - min) + min;
return isInteger ? Math.floor(value) : value;
};
/*** * @param numbers
*/export const average = (numbers: number[]): number => {
if (numbers.length === 0) return 0;
return numbers.reduce((sum, num) => sum + num, 0) / numbers.length;
};
/**
*
* @param numbers
*/export const sum = (numbers: number[]): number => {
return numbers.reduce((sum, num) => sum + num, 0);
};
/**
*
* @paramdegrees
*/
export const degreesToRadians = (degrees: number): number => {
return degrees * (Math.PI / 180);
};/**
*
* @param radians弧度
*/
export const radiansToDegrees = (radians: number): number => {
return radians * (180 / Math.PI);
};/**
*
* @param value */
export const formatThousands = (value: number): string => {
return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
};
/*** * @param value
*@param total
*@param decimals
*/
export const percentage = (value: number, total: number, decimals: number = 2): number => {
if (total === 0) return 0; return round((value / total) * 100, decimals);
};
/***
* @param a第一个数值
* @paramb
*@param epsilon ,0.000001
*/
export const approximatelyEqual = (a: number, b: number, epsilon: number = 0.000001): boolean => {
return Math.abs(a - b) < epsilon;
};
/**
* (K, M, B等)
* @param value
*/
export const formatUnit = (value: number): string => {
const units = ["", "K", "M", "B", "T"];
const order = Math.floor(Math.log10(Math.abs(value)) / 3); if (order < 0) return value.toString();
const unit = units[order]; const scaled = value / Math.pow(10, order * 3);
return `${round(scaled, 1)}${unit}`;
};
/**
* * @param x1 x坐标
*@param y1 y坐标
* @param x2 x坐标
* @param y2 y坐标*/
export const distance = (x1: number, y1: number, x2: number, y2: number): number => {
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
};/**
* 线* @param start
* @param end
* @param t (0-1)*/
export const lerp = (start: number, end: number, t: number): number => {
return start + (end - start) * clamp(t, 0, 1);
};

View File

@ -0,0 +1,59 @@
// 优雅实现对象操作工具函数集合
/**
*
* @param input
* @returns /
*/
export function deepClone<T>(input: T): T {
if (input === null || typeof input !== 'object') return input;
if (Array.isArray(input)) return input.map(deepClone) as unknown as T;
const result = {} as T;
for (const key in input) {
if (Object.prototype.hasOwnProperty.call(input, key)) {
result[key] = deepClone(input[key]);
}
}
return result;
}
/**
*
* @param objects
* @returns
*/
export function mergeObjects<T>(...objects: Partial<T>[]): T {
return objects.reduce((acc: Partial<T>, obj) => {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
if (typeof acc[key] === 'object' && typeof obj[key] === 'object') {
acc[key] = mergeObjects(acc[key], obj[key]);
} else {
acc[key] = obj[key];
}
}
}
return acc;
}, {} as Partial<T>) as T;
}
/**
*
* @param obj
* @param path
* @param defaultValue
* @returns
*/
export function getNestedValue<T, U>(obj: T, path: string, defaultValue: U): U | unknown {
return path.split('.').reduce((acc: any, key: string) => acc && acc[key], obj) ?? defaultValue;
}
/**
*
* @param obj
* @returns
*/
export function isEmptyObject(obj: Record<string, unknown>): boolean {
return Object.keys(obj).length === 0;
}

View File

@ -0,0 +1,85 @@
/**
*
*
*/
/**
* ( min max)
* @param min ()
* @param max ()
*/
export const getRandomInt = (min: number, max: number): number => {
const minimum = Math.ceil(min);
const maximum = Math.floor(max);
return Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
};
/**
*
* @param length
* @param chars 使 ()
*/
export const getRandomString = (length: number, chars: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'): string => {
let result = '';
const charsLength = chars.length;
for (let i = 0; i < length; i++) {
result += chars.charAt(Math.floor(Math.random() * charsLength));
}
return result;
};
/**
* (true false)
*/
export const getRandomBoolean = (): boolean => {
return Math.random() >= 0.5;
};
/**
*
* @param array
*/
export const getRandomFromArray = <T>(array: T[]): T => {
return array[Math.floor(Math.random() * array.length)];
};
/**
*
* @param array
*/
export const shuffleArray = <T>(array: T[]): T[] => {
const shuffled = [...array]; // 创建一个副本以免修改原数组
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
};
/**
* ( "#a1b2c3")
*/
export const getRandomHexColor = (): string => {
return `#${Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0')}`;
};
/**
* RGBA颜色
* @param alpha ( 0-1)
*/
export const getRandomRGBAColor = (alpha: number = 1): string => {
const r = getRandomInt(0, 255);
const g = getRandomInt(0, 255);
const b = getRandomInt(0, 255);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
/**
* ( Promise resolve)
* @param min ()
* @param max ()
*/
export const randomDelay = (min: number, max: number): Promise<void> => {
const delay = getRandomInt(min, max);
return new Promise(resolve => setTimeout(resolve, delay));
};

View File

@ -0,0 +1,80 @@
/**
*
*
*/
/**
*
*/
export const isEmptyOrWhitespace = (str: string): boolean => {
return !str || str.trim().length === 0;
};
/**
*
*/
export const capitalize = (str: string): string => {
if (!str) return '';
return str.charAt(0).toUpperCase() + str.slice(1);
};
/**
* (camelCase)
*/
export const toCamelCase = (str: string): string => {
return str
.toLowerCase()
.replace(/[-_ ]+(\w)/g, (_, group: string) => group.toUpperCase());
};
/**
* (snake_case)
*/
export const toSnakeCase = (str: string): string => {
return str
.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`)
.replace(/[\s\-]+/g, '_')
.replace(/^_/, '')
.toLowerCase();
};
/**
* (kebab-case)
*/
export const toKebabCase = (str: string): string => {
return str
.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`)
.replace(/[\s_]+/g, '-')
.replace(/^-/, '')
.toLowerCase();
};
/**
* 使 "..."
*/
export const truncate = (str: string, length: number): string => {
if (str.length <= length) return str;
return str.slice(0, length) + '...';
};
/**
*
*/
export const charFrequency = (str: string): Record<string, number> => {
return str.split('').reduce((acc: Record<string, number>, char: string) => {
acc[char] = (acc[char] || 0) + 1;
return acc;
}, {});
};
/**
* JSON
*/
export const isValidJSON = (str: string): boolean => {
try {
JSON.parse(str);
return true;
} catch {
return false;
}
};

View File

@ -0,0 +1,94 @@
/**
*
* TypeScript
*/
// --------------------------- 类型工具 ---------------------------
/**
*
*/
export type PickKeys<T, K extends keyof T> = {
[P in K]: T[P];
};
/**
*
*/
export type OmitKeys<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
/**
*
*/
export type PartialByKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
/**
*
*/
export type RequiredByKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
/**
*
*/
export type Nullable<T> = T | null;
/**
* undefined
*/
export type Optional<T> = T | undefined;
/**
* ()
*/
export const assertType = <T>(value: T): T => value;
/**
*
*/
export type ArrayElement<A> = A extends readonly (infer T)[] ? T : never;
/**
*
*/
export type ValueOf<T> = T[keyof T];
/**
*
*/
export type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
/**
*
*/
export type DeepRequired<T> = {
[K in keyof T]-?: T[K] extends object ? DeepRequired<T[K]> : T[K];
};
// --------------------------- 工具函数 ---------------------------
/**
* (null undefined)
* @param val
*/
export const isNullOrUndefined = (val: unknown): val is null | undefined => {
return val === null || val === undefined;
};
/**
*
* @param val
*/
export const isObject = (val: unknown): val is Record<string, unknown> => {
return typeof val === 'object' && val !== null && !Array.isArray(val);
};
/**
* ()
* @param obj
*/
export const isEmptyObject = (obj: Record<string, unknown>): boolean => {
return Object.keys(obj).length === 0;
};
export type NonVoid<T> = T extends void ? never : T;

View File

@ -1 +0,0 @@
export type NonVoid<T> = T extends void ? never : T;

View File

View File

@ -377,15 +377,9 @@ importers:
react:
specifier: 18.2.0
version: 18.2.0
react-beautiful-dnd:
specifier: ^13.1.1
version: 13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-dropzone:
specifier: ^14.3.5
version: 14.3.5(react@18.2.0)
react-hook-form:
specifier: ^7.54.2
version: 7.54.2(react@18.2.0)
@ -3077,9 +3071,6 @@ packages:
'@types/graceful-fs@4.1.9':
resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
'@types/hoist-non-react-statics@3.3.6':
resolution: {integrity: sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==}
'@types/http-errors@2.0.4':
resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==}
@ -3155,9 +3146,6 @@ packages:
'@types/react-dom@18.2.15':
resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==}
'@types/react-redux@7.1.34':
resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==}
'@types/react@18.2.38':
resolution: {integrity: sha512-cBBXHzuPtQK6wNthuVMV6IjHAFkdl/FOPFIlkd81/Cd1+IqkHu/A+w4g43kaQQoYHik/ruaQBDL72HyCy1vuMw==}
@ -3710,10 +3698,6 @@ packages:
asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
attr-accept@2.2.5:
resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
engines: {node: '>=4'}
autoprefixer@10.4.20:
resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==}
engines: {node: ^10 || ^12 || >=14}
@ -4192,9 +4176,6 @@ packages:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
css-box-model@1.2.1:
resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==}
cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
@ -4764,10 +4745,6 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
file-selector@2.1.2:
resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
engines: {node: '>= 12'}
filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
@ -5037,9 +5014,6 @@ packages:
hls.js@1.5.18:
resolution: {integrity: sha512-znxR+2jecWluu/0KOBqUcvVyAB5tLff10vjMGrpAlz1eFY+ZhF1bY3r82V+Bk7WJdk03iTjtja9KFFz5BrqjSA==}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@ -5848,9 +5822,6 @@ packages:
resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==}
engines: {node: '>= 4.0.0'}
memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
meow@8.1.2:
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
engines: {node: '>=10'}
@ -6423,9 +6394,6 @@ packages:
resolution: {integrity: sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==}
engines: {npm: '>=8.2.3'}
raf-schd@4.0.3:
resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==}
randombytes@2.1.0:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
@ -6665,13 +6633,6 @@ packages:
react: '>=16.9.0'
react-dom: '>=16.9.0'
react-beautiful-dnd@13.1.1:
resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==}
deprecated: 'react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672'
peerDependencies:
react: ^16.8.5 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0
react-dom@18.2.0:
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:
@ -6688,12 +6649,6 @@ packages:
react: '>= 16.3.0'
react-dom: '>= 16.3.0'
react-dropzone@14.3.5:
resolution: {integrity: sha512-9nDUaEEpqZLOz5v5SUcFA0CjM4vq8YbqO0WRls+EYT7+DvxUdzDPKNCPLqGfj3YL9MsniCLCD4RFA6M95V6KMQ==}
engines: {node: '>= 10.13'}
peerDependencies:
react: '>= 16.8 || 18.0.0'
react-hook-form@7.54.2:
resolution: {integrity: sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg==}
engines: {node: '>=18.0.0'}
@ -6716,24 +6671,9 @@ packages:
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-is@17.0.2:
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-redux@7.2.9:
resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==}
peerDependencies:
react: ^16.8.3 || ^17 || ^18
react-dom: '*'
react-native: '*'
peerDependenciesMeta:
react-dom:
optional: true
react-native:
optional: true
react-refresh@0.14.2:
resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==}
engines: {node: '>=0.10.0'}
@ -6801,9 +6741,6 @@ packages:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
redux@4.2.1:
resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==}
reflect-metadata@0.2.2:
resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
@ -7339,9 +7276,6 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
@ -7580,11 +7514,6 @@ packages:
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
use-memo-one@1.1.3:
resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0
use-sync-external-store@1.4.0:
resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==}
peerDependencies:
@ -10687,11 +10616,6 @@ snapshots:
dependencies:
'@types/node': 20.17.12
'@types/hoist-non-react-statics@3.3.6':
dependencies:
'@types/react': 18.2.38
hoist-non-react-statics: 3.3.2
'@types/http-errors@2.0.4': {}
'@types/istanbul-lib-coverage@2.0.6': {}
@ -10761,13 +10685,6 @@ snapshots:
dependencies:
'@types/react': 18.3.18
'@types/react-redux@7.1.34':
dependencies:
'@types/hoist-non-react-statics': 3.3.6
'@types/react': 18.2.38
hoist-non-react-statics: 3.3.2
redux: 4.2.1
'@types/react@18.2.38':
dependencies:
'@types/prop-types': 15.7.14
@ -11553,8 +11470,6 @@ snapshots:
asynckit@0.4.0: {}
attr-accept@2.2.5: {}
autoprefixer@10.4.20(postcss@8.4.49):
dependencies:
browserslist: 4.24.3
@ -12088,10 +12003,6 @@ snapshots:
shebang-command: 2.0.0
which: 2.0.2
css-box-model@1.2.1:
dependencies:
tiny-invariant: 1.3.3
cssesc@3.0.0: {}
csstype@3.1.3: {}
@ -12762,10 +12673,6 @@ snapshots:
dependencies:
flat-cache: 4.0.1
file-selector@2.1.2:
dependencies:
tslib: 2.8.1
filelist@1.0.4:
dependencies:
minimatch: 5.1.6
@ -13045,10 +12952,6 @@ snapshots:
hls.js@1.5.18: {}
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
hosted-git-info@2.8.9: {}
hosted-git-info@4.1.0:
@ -14023,8 +13926,6 @@ snapshots:
dependencies:
fs-monkey: 1.0.6
memoize-one@5.2.1: {}
meow@8.1.2:
dependencies:
'@types/minimist': 1.2.5
@ -14548,8 +14449,6 @@ snapshots:
parchment: 3.0.0
quill-delta: 5.1.0
raf-schd@4.0.3: {}
randombytes@2.1.0:
dependencies:
safe-buffer: 5.2.1
@ -14883,20 +14782,6 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-beautiful-dnd@13.1.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.26.0
css-box-model: 1.2.1
memoize-one: 5.2.1
raf-schd: 4.0.3
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-redux: 7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
redux: 4.2.1
use-memo-one: 1.1.3(react@18.2.0)
transitivePeerDependencies:
- react-native
react-dom@18.2.0(react@18.2.0):
dependencies:
loose-envify: 1.4.0
@ -14916,13 +14801,6 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-dropzone@14.3.5(react@18.2.0):
dependencies:
attr-accept: 2.2.5
file-selector: 2.1.2
prop-types: 15.8.1
react: 18.2.0
react-hook-form@7.54.2(react@18.2.0):
dependencies:
react: 18.2.0
@ -14941,22 +14819,8 @@ snapshots:
react-is@16.13.1: {}
react-is@17.0.2: {}
react-is@18.3.1: {}
react-redux@7.2.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.26.0
'@types/react-redux': 7.1.34
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.2.0
react-is: 17.0.2
optionalDependencies:
react-dom: 18.2.0(react@18.2.0)
react-refresh@0.14.2: {}
react-resizable@3.0.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
@ -15037,10 +14901,6 @@ snapshots:
dependencies:
redis-errors: 1.2.0
redux@4.2.1:
dependencies:
'@babel/runtime': 7.26.0
reflect-metadata@0.2.2: {}
regenerator-runtime@0.14.1: {}
@ -15661,8 +15521,6 @@ snapshots:
through@2.3.8: {}
tiny-invariant@1.3.3: {}
tinycolor2@1.6.0: {}
tinyexec@0.3.2: {}
@ -15899,10 +15757,6 @@ snapshots:
querystringify: 2.2.0
requires-port: 1.0.0
use-memo-one@1.1.3(react@18.2.0):
dependencies:
react: 18.2.0
use-sync-external-store@1.4.0(react@18.2.0):
dependencies:
react: 18.2.0