remove deprecated api

This commit is contained in:
Timi
2026-08-07 10:46:14 +08:00
parent efea624376
commit f0ba55fe25
18 changed files with 194 additions and 306 deletions
+38
View File
@@ -0,0 +1,38 @@
# 2026-08-06 工作日志
## TypeScript enum 重构(全量消除 enum
用户决定改用纯联合类型,自行修改依赖方。全项目 18 处 enum 分两类处理:
### 改为纯联合类型(字符串枚举,仅作类型用)
- `Attachment.ts` AttachmentBizType、`Template.ts` TemplateBizType
- `Model.ts` RunEnv / OrderType / ImageTypeImageType 值为 ir-auto 等,与 key 不同)
- `Comment.ts` CommentReplyBizType / CommentBizType
- `User.ts` UserAttachType(在 UserAPI.ts 有值引用,已同步改为字符串字面量,import 改 type-only
### 改为 const 对象 + 派生类型(数字枚举或运行时值引用,纯联合不可行)
- `User.ts` LoginType、`Article.ts` ArticleType / ArticleAttachType / ArticleSoftwareDownloadType / ArticleSoftwareRuntime(数字枚举,保留数值避免破坏数据契约)
- `deviceStore.ts` Breakpoints(数字,用于比较 width < Breakpoints.SM
- `IOSize.ts` Unit(用 Object.keys/values + Unit.KB 值引用)
- `Prismjs.ts` PrismjsType / PrismjsViewerMap 键值引用)
- `directives/Popup.ts` PopupTypeswitch case 值引用)
### 结论
- `vue-tsc --noEmit` 通过(exit 0
- ESLint 因项目缺 eslint.config.js(仍用旧 .eslintrcESLint9 不支持)无法运行,属项目既有配置问题
- 判断依据:纯联合无运行时存在,凡 `Object.keys(enum)``map.set(Enum.X)``case Enum.X``Enum.X` 作值的都无法纯联合,必须 const 对象
## Storage.ts 设计修正
- `getString` 返回 `string | null`(对齐 `localStorage.getItem` 契约),不再 throw
- `getJSON` 返回 `unknown`,键不存在时返回 `null`,值无法解析时才抛错
- `getObject` 返回 `T | null`,不再 throw(修正原 JSDoc 已写 `T | undefined` 但代码 throw 的矛盾)
- `is` / `not` 键不存在时自然返回 `false` / `true`,不再崩
- `has` 去掉死代码 `!== undefined`localStorage.getItem 只返回 string|null),单次查询
- `vue-tsc --noEmit` 通过
## Toolkit.ts debounce 修复
- **Bug 修复**`cancel()` 原先只清定时器不重置 `leading` 状态,导致立即模式下 cancel 后首次调用丢失 leading 触发
- **类型修复**:返回类型从 `T & { cancel(): void }` 改为 `((...args: Parameters<T>) => void) & { cancel(): void }`,防抖函数实际返回 void 而非 T 的返回值
- 补充完整 JSDoc(含 `@example`),说明立即模式(leading + trailing)和延迟模式(trailing only)的行为差异
- 状态变量 `immediate``leading`,参数 `defaultImmediate``immediate`,语义更清晰
- 去掉 `eslint-disable-next-line` 和多余注释
- `vue-tsc --noEmit` 通过
+10 -10
View File
@@ -1,5 +1,5 @@
import {Attachment, BizUpdateReq, TempFileResp} from "../types"; import { Attachment, BizIdUpdate, TempFile } from "../types";
import {axios} from "./BaseAPI"; import { axios } from "./BaseAPI";
import CommonAPI from "./CommonAPI"; import CommonAPI from "./CommonAPI";
const BASE_URI = "/attach"; const BASE_URI = "/attach";
@@ -11,7 +11,7 @@ const BASE_URI = "/attach";
* @param ttl 有效期 * @param ttl 有效期
* @returns 临时附件列表 * @returns 临时附件列表
*/ */
async function uploadTemp(file: File | File[], ttl?: string): Promise<TempFileResp[]> { async function upload(file: File | File[], ttl?: string): Promise<TempFile[]> {
const formData = new FormData(); const formData = new FormData();
const fileList = Array.isArray(file) ? file : [file]; const fileList = Array.isArray(file) ? file : [file];
for (const item of fileList) { for (const item of fileList) {
@@ -28,8 +28,8 @@ async function uploadTemp(file: File | File[], ttl?: string): Promise<TempFileRe
* *
* @param req 更新请求 * @param req 更新请求
*/ */
async function updateByBiz(req: BizUpdateReq): Promise<void> { async function updateByBizId(req: BizIdUpdate): Promise<void> {
return await axios.post(`${BASE_URI}/update/biz`, req); return await axios.post(`${BASE_URI}/update/biz/id`, req);
} }
/** /**
@@ -39,8 +39,8 @@ async function updateByBiz(req: BizUpdateReq): Promise<void> {
* @param bizId 业务 ID * @param bizId 业务 ID
* @param attachTypeList 附件类型列表 * @param attachTypeList 附件类型列表
*/ */
async function listByBiz(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> { async function listByBizId(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> {
return await axios.get(`${BASE_URI}/list/biz`, { return await axios.get(`${BASE_URI}/list/biz/id`, {
params: { params: {
bizType, bizType,
bizId, bizId,
@@ -50,9 +50,9 @@ async function listByBiz(bizType: string, bizId: string, attachTypeList?: string
} }
export default { export default {
uploadTemp, upload,
updateByBiz, updateByBizId,
listByBiz, listByBizId,
getReadURL: CommonAPI.getAttachmentReadAPI, getReadURL: CommonAPI.getAttachmentReadAPI,
getTempReadURL: CommonAPI.getAttachmentTempReadAPI getTempReadURL: CommonAPI.getAttachmentTempReadAPI
}; };
+1 -11
View File
@@ -1,4 +1,4 @@
import { CaptchaData, Comment, CommentReply, Page, PageResult } from "../types"; import { CaptchaData, Comment, Page, PageResult } from "../types";
import { axios } from "./BaseAPI"; import { axios } from "./BaseAPI";
const BASE_URI = "/comment"; const BASE_URI = "/comment";
@@ -11,17 +11,7 @@ async function create(captchaData: CaptchaData<Comment>): Promise<void> {
return axios.post(`${BASE_URI}/create`, captchaData); return axios.post(`${BASE_URI}/create`, captchaData);
} }
async function pageReply(page: Page<CommentReply>): Promise<PageResult<CommentReply>> {
return axios.post(`${BASE_URI}/reply/list`, page);
}
async function createReply(captchaData: CaptchaData<CommentReply>): Promise<void> {
return axios.post(`${BASE_URI}/reply/create`, captchaData);
}
export default { export default {
page, page,
create, create,
createReply,
pageReply
}; };
+2 -7
View File
@@ -1,5 +1,5 @@
import {CaptchaResult, Setting, TemplateBizType} from "../types"; import { CaptchaResult, Setting } from "../types";
import {axios} from "./BaseAPI"; import { axios } from "./BaseAPI";
function getBaseURI(): string { function getBaseURI(): string {
const baseURL = axios.defaults.baseURL; const baseURL = axios.defaults.baseURL;
@@ -19,10 +19,6 @@ const getAttachmentReadAPI = (id: string) => `${getBaseURI()}/attach/read/${id}`
const getAttachmentTempReadAPI = (id: string) => `${getBaseURI()}/attach/temp/read?id=${id}`; const getAttachmentTempReadAPI = (id: string) => `${getBaseURI()}/attach/temp/read?id=${id}`;
async function getTemplate(bizType: TemplateBizType, code: string): Promise<string> {
return axios.get(`/template?bizType=${bizType}&bizCode=${code}`);
}
async function settingMap(map: Record<string, string[]>): Promise<Map<string, Map<string, Setting>>> { async function settingMap(map: Record<string, string[]>): Promise<Map<string, Map<string, Setting>>> {
const raw = await axios.post("/setting/map", map); const raw = await axios.post("/setting/map", map);
const moduleMap = new Map<string, Map<string, Setting>>(); const moduleMap = new Map<string, Map<string, Setting>>();
@@ -47,6 +43,5 @@ export default {
captcha, captcha,
getAttachmentReadAPI, getAttachmentReadAPI,
getAttachmentTempReadAPI, getAttachmentTempReadAPI,
getTemplate,
settingMap settingMap
}; };
+4 -5
View File
@@ -1,5 +1,4 @@
import type {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User} from "../types"; import type {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User, UserAttachType} from "../types";
import {UserAttachType} from "../types";
import {axios} from "./BaseAPI"; import {axios} from "./BaseAPI";
import CommonAPI from "./CommonAPI"; import CommonAPI from "./CommonAPI";
@@ -55,17 +54,17 @@ async function view(id: string): Promise<User> {
function getAvatarURL(user?: UserAttachmentOwner) { function getAvatarURL(user?: UserAttachmentOwner) {
if (user?.attachmentList) { if (user?.attachmentList) {
return findAttachmentByType(user.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]); return findAttachmentByType(user.attachmentList, "AVATAR");
} }
} }
function getWrapperURL(user?: UserAttachmentOwner) { function getWrapperURL(user?: UserAttachmentOwner) {
if (user?.attachmentList) { if (user?.attachmentList) {
return findAttachmentByType(user.attachmentList, [UserAttachType.WRAPPER, UserAttachType.DEFAULT_WRAPPER]); return findAttachmentByType(user.attachmentList, "WRAPPER");
} }
} }
function findAttachmentByType(attachmentList: readonly Attachment[], types: UserAttachType[]) { function findAttachmentByType(attachmentList: readonly Attachment[], ...types: UserAttachType[]) {
for (let i = 0; i < attachmentList.length; i++) { for (let i = 0; i < attachmentList.length; i++) {
const attachType = attachmentList[i].attachType as UserAttachType | undefined; const attachType = attachmentList[i].attachType as UserAttachType | undefined;
const id = attachmentList[i].id; const id = attachmentList[i].id;
+7 -7
View File
@@ -32,23 +32,23 @@ const isShortScreen = ref(false);
const isMobileLayout = ref(false); const isMobileLayout = ref(false);
/** 断点配置,单位:px */ /** 断点配置,单位:px */
enum Breakpoints { const Breakpoints = {
/** 超小设备 */ /** 超小设备 */
XS = 480, XS: 480,
/** 手机 */ /** 手机 */
SM = 650, SM: 650,
/** 平板 */ /** 平板 */
MD = 768, MD: 768,
/** 笔记本 */ /** 笔记本 */
LG = 1024, LG: 1024,
/** 大屏幕 */ /** 大屏幕 */
XL = 1440 XL: 1440
} } as const;
Resizer.addListener("DEVICE_SIZE", (width, height) => { Resizer.addListener("DEVICE_SIZE", (width, height) => {
screenWidth.value = width; screenWidth.value = width;
+7 -68
View File
@@ -2,12 +2,12 @@ import type { Attachment } from "./Attachment";
import type { Model } from "./Model"; import type { Model } from "./Model";
// 文章 // 文章
export type Article<E extends ArticleMusicExtendData | ArticleSoftwareExtendData> = { export type Article<A> = {
title?: string; title?: string;
type: ArticleType; type: ArticleType;
digest?: string; digest?: string;
data?: string; data?: string;
extendData?: E; args?: A;
reads: number; reads: number;
likes: number; likes: number;
showComment: boolean; showComment: boolean;
@@ -15,74 +15,13 @@ export type Article<E extends ArticleMusicExtendData | ArticleSoftwareExtendData
canRanking: boolean; canRanking: boolean;
} & Model; } & Model;
export type ArticleView<E extends ArticleMusicExtendData | ArticleSoftwareExtendData> = { export type ArticleView<A> = {
comments?: number; comments?: number;
attachmentList: Attachment[]; attachmentList: Attachment[];
} & Article<E>; } & Article<A>;
export enum ArticleType { export const ArticleType = "COMMON";
/** 公版 */ export type ArticleType = (typeof ArticleType)[keyof typeof ArticleType];
PUBLIC,
/** 音乐 */ export const ArticleAttachType = "COVER";
MUSIC,
/** 软件 */
SOFTWARE
}
export enum ArticleAttachType {
COVER,
}
export type ArticleMusicExtendData = {
title: string;
list: ArticleMusicItem[];
info: {
key: string;
value: string;
}[];
}
export type ArticleSoftwareExtendData = {
url?: string;
downloads: ArticleSoftwareDownload[];
format: string;
runtime: ArticleSoftwareRuntime[];
size: number;
version: string;
password?: string;
}
export enum ArticleSoftwareDownloadType {
TIMI_MONGO,
TIMI_COS,
URL
}
export enum ArticleSoftwareRuntime {
JVM,
WINDOWS,
LINUX,
MAC_OS
}
export type ArticleSoftwareDownload = {
type: ArticleSoftwareDownloadType;
value: string;
}
export type ArticleMusicItem = {
title: string;
audio?: string;
video?: string;
}
+6 -17
View File
@@ -1,10 +1,10 @@
import { Model } from "./Model"; import { Model } from "./Model";
export type Attachment = { export type Attachment = {
bizType: AttachmentBizType; bizType?: AttachmentBizType;
bizId: string; bizId?: string;
attachType?: string; attachType?: string;
mongoId: string; mongoId?: string;
title?: string; title?: string;
name?: string; name?: string;
mimeType?: string; mimeType?: string;
@@ -17,26 +17,15 @@ export type Attachment = {
tempFileId?: string; tempFileId?: string;
} & Model; } & Model;
export type TempFileResp = { export type TempFile = {
id: string; id: string;
expireAt?: number; expireAt?: number;
}; };
export type BizUpdateReq = { export type BizIdUpdate = {
bizType: AttachmentBizType; bizType: AttachmentBizType;
bizId: string; bizId: string;
items: Partial<Attachment>[]; items: Partial<Attachment>[];
}; };
export enum AttachmentBizType { export type AttachmentBizType = | "USER" | "GAO_CUSTOMER" | "GAO_REGISTER_RECORD" | "TEMP_FILE" | "MIRROR";
USER = "USER",
GAO_CUSTOMER = "GAO_CUSTOMER",
GAO_REGISTER_RECORD = "GAO_REGISTER_RECORD",
TEMP_FILE = "TEMP_FILE",
MIRROR = "MIRROR"
}
+3 -32
View File
@@ -13,10 +13,10 @@ export type Comment = {
user?: User; user?: User;
/** 回复列表 */ /** 回复列表 */
replies?: CommentReply[]; replies?: Comment[];
/** 回复分页 */ /** 回复分页 */
repliesPage?: Page<CommentReply>; repliesPage?: Page<Comment>;
/** 用于绑定组件当前页下标 */ /** 用于绑定组件当前页下标 */
repliesCurrent?: number; repliesCurrent?: number;
@@ -31,33 +31,4 @@ export type Comment = {
repository?: object; repository?: object;
} & Model } & Model
export type CommentReply = { export type CommentBizType = "ARTICLE" | "GIT_ISSUE" | "GIT_MERGE";
replyId?: number;
commentId?: number;
senderId?: number;
senderNick?: string;
receiverId?: number;
receiverNick?: string;
content?: string;
comment?: Comment;
sender?: User;
receiver?: User;
} & Model;
export enum CommentReplyBizType {
COMMENT = "COMMENT",
SENDER = "SENDER",
RECEIVER = "RECEIVER"
}
export enum CommentBizType {
ARTICLE = "ARTICLE",
GIT_ISSUE = "GIT_ISSUE",
GIT_MERGE = "GIT_MERGE",
}
+7 -17
View File
@@ -1,9 +1,3 @@
export enum RunEnv {
DEV = "DEV",
DEV_SSL = "DEV_SSL",
PROD = "PROD"
}
// 基本实体模型 // 基本实体模型
export type Model = { export type Model = {
id?: string; id?: string;
@@ -23,17 +17,17 @@ export type ApiResponse<T> = {
} }
export type Page<T> = { export type Page<T> = {
index: number;
size: number;
orderMap?: { [K in keyof T]?: OrderType }; orderMap?: { [K in keyof T]?: OrderType };
equalsExample?: Partial<T>; equalsExample?: Partial<T>;
likesExample?: Partial<T>; likesExample?: Partial<T>;
} & BasePage;
export type BasePage = {
index: number;
size: number;
} }
export enum OrderType { export type OrderType = "ASC" | "DESC";
ASC = "ASC",
DESC = "DESC"
}
export type PageResult<T> = { export type PageResult<T> = {
total: number; total: number;
@@ -53,11 +47,7 @@ export type CaptchaResult = {
data: string; data: string;
} }
export enum ImageType { export type ImageType = "IR-AUTO" | "IR-SMOOTH" | "IR-PIXELATED";
AUTO = "ir-auto",
SMOOTH = "ir-smooth",
PIXELATED = "ir-pixelated"
}
export type KeyValue<V, K = string> = { export type KeyValue<V, K = string> = {
key: K; key: K;
-6
View File
@@ -1,6 +0,0 @@
export enum TemplateBizType {
GIT = "GIT",
FOREVER_MC = "FOREVER_MC"
}
+1 -18
View File
@@ -32,18 +32,7 @@ export type User = {
phoneNoVerified?: boolean; phoneNoVerified?: boolean;
} & Model; } & Model;
export enum UserAttachType { export type UserAttachType = "AVATAR" | "WRAPPER";
AVATAR = "AVATAR",
WRAPPER = "WRAPPER",
LICENSE = "LICENSE",
DEFAULT_AVATAR = "DEFAULT_AVATAR",
DEFAULT_WRAPPER = "DEFAULT_WRAPPER"
}
export type RegisterRequest = { export type RegisterRequest = {
name: string; name: string;
@@ -76,12 +65,6 @@ export type LoginUser = {
user?: User; user?: User;
}; };
export enum LoginType {
ALERT,
IFRAME,
REDIRECT
}
export type UserLevelType = { export type UserLevelType = {
exp: number; // 经验数值,和 UserData.exp 一样 exp: number; // 经验数值,和 UserData.exp 一样
value: number; // 经验对应等级,[0, 8] value: number; // 经验对应等级,[0, 8]
-1
View File
@@ -8,7 +8,6 @@ export * from "./Model";
export * from "./Permission"; export * from "./Permission";
export * from "./Role"; export * from "./Role";
export * from "./User"; export * from "./User";
export * from "./Template";
export * from "./Comment"; export * from "./Comment";
export * from "./Developer"; export * from "./Developer";
+12 -35
View File
@@ -1,28 +1,7 @@
import Text from "./Text"; import Text from "./Text";
export enum Unit { export const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB"] as const;
export type Unit = (typeof UNITS)[number];
/** B */
B = "B",
/** KB */
KB = "KB",
/** MB */
MB = "MB",
/** GB */
GB = "GB",
/** TB */
TB = "TB",
/** PB */
PB = "PB",
/** EB */
EB = "EB"
}
/** 储存单位 */ /** 储存单位 */
export default class IOSize { export default class IOSize {
@@ -48,8 +27,6 @@ export default class IOSize {
/** 1 EB */ /** 1 EB */
public static EB = IOSize.PB << 10; public static EB = IOSize.PB << 10;
public static Unit = Unit;
/** /**
* <p>格式化一个储存容量,保留两位小数 * <p>格式化一个储存容量,保留两位小数
* <pre> * <pre>
@@ -66,7 +43,7 @@ export default class IOSize {
if (size === undefined || size === null) { if (size === undefined || size === null) {
return "0 B"; return "0 B";
} }
const units = Object.keys(Unit); const units = Object.values(UNITS);
if (0 < size) { if (0 < size) {
for (let i = 0; i < units.length; i++, size /= 1024) { for (let i = 0; i < units.length; i++, size /= 1024) {
const unit = units[i]; const unit = units[i];
@@ -118,31 +95,31 @@ export default class IOSize {
let unit: Unit; let unit: Unit;
// 先尝试精确匹配枚举 // 先尝试精确匹配枚举
if (Object.values(Unit).includes(unitStr as Unit)) { if (Object.values(UNITS).includes(unitStr as Unit)) {
unit = unitStr as Unit; unit = unitStr as Unit;
} else { } else {
// 处理单字母单位缩写(K/M/G/T/P/E // 处理单字母单位缩写(K/M/G/T/P/E
switch (unitStr.charAt(0)) { switch (unitStr.charAt(0)) {
case "K": case "K":
unit = Unit.KB; unit = "KB";
break; break;
case "M": case "M":
unit = Unit.MB; unit = "MB";
break; break;
case "G": case "G":
unit = Unit.GB; unit = "GB";
break; break;
case "T": case "T":
unit = Unit.TB; unit = "TB";
break; break;
case "P": case "P":
unit = Unit.PB; unit = "PB";
break; break;
case "E": case "E":
unit = Unit.EB; unit = "EB";
break; break;
case "B": case "B":
unit = Unit.B; unit = "B";
break; break;
default: default:
throw new Error("Unknown unit: " + unitStr); throw new Error("Unknown unit: " + unitStr);
@@ -173,7 +150,7 @@ export default class IOSize {
if (val === undefined || val === null) { if (val === undefined || val === null) {
return 0; return 0;
} }
const units = Object.values(Unit); const units = Object.values(UNITS);
const ordinal = units.indexOf(unit); const ordinal = units.indexOf(unit);
return Math.round(val * Math.pow(1024, ordinal)); return Math.round(val * Math.pow(1024, ordinal));
} }
+29 -25
View File
@@ -1,23 +1,25 @@
export enum PrismjsType { export const PrismjsType = {
PlainText = "PlainText", PlainText: "PlainText",
Markdown = "Markdown", Markdown: "Markdown",
JavaScript = "JavaScript", JavaScript: "JavaScript",
TypeScript = "TypeScript", TypeScript: "TypeScript",
Initialization = "Initialization", Initialization: "Initialization",
PHP = "PHP", PHP: "PHP",
SQL = "SQL", SQL: "SQL",
XML = "XML", XML: "XML",
CSS = "CSS", CSS: "CSS",
VUE = "VUE", VUE: "VUE",
LESS = "LESS", LESS: "LESS",
Markup = "Markup", Markup: "Markup",
YAML = "YAML", YAML: "YAML",
Json = "Json", Json: "Json",
Java = "Java", Java: "Java",
Properties = "Properties", Properties: "Properties",
NginxConf = "NginxConf", NginxConf: "NginxConf",
ApacheConf = "ApacheConf" ApacheConf: "ApacheConf"
} } as const;
export type PrismjsType = (typeof PrismjsType)[keyof typeof PrismjsType];
export type PrismjsProperties = { export type PrismjsProperties = {
@@ -26,14 +28,16 @@ export type PrismjsProperties = {
viewer: PrismjsViewer; viewer: PrismjsViewer;
} }
export enum PrismjsViewer { export const PrismjsViewer = {
MARKDOWN = "MARKDOWN", MARKDOWN: "MARKDOWN",
CODE = "CODE", CODE: "CODE",
TEXT = "TEXT", TEXT: "TEXT"
} } as const;
export type PrismjsViewer = (typeof PrismjsViewer)[keyof typeof PrismjsViewer];
export default class Prismjs { export default class Prismjs {
+15 -20
View File
@@ -1,7 +1,7 @@
export default class Storage { export default class Storage {
/** /**
* 获取为布尔值 * 获取为布尔值,键不存在时返回 false
* *
* @param key 键 * @param key 键
* @returns 布尔值 * @returns 布尔值
@@ -11,7 +11,7 @@ export default class Storage {
} }
/** /**
* 获取为布尔值并取反 * 获取为布尔值并取反,键不存在时返回 true
* *
* @param key 键 * @param key 键
* @returns 布尔值 * @returns 布尔值
@@ -25,13 +25,11 @@ export default class Storage {
* *
* @template T 对象类型 * @template T 对象类型
* @param key 键 * @param key 键
* @returns {T | undefined} 返回对象 * @returns 对象,键不存在时返回 null
*/ */
public static getObject<T>(key: string): T { public static getObject<T>(key: string): T | null {
if (this.has(key)) { const value = this.getJSON(key);
return this.getJSON(key) as T; return value === null ? null : value as T;
}
throw Error(`not found ${key}`);
} }
/** /**
@@ -54,24 +52,21 @@ export default class Storage {
* 获取为 JSON * 获取为 JSON
* *
* @param key 键 * @param key 键
* @returns JSON 对象 * @returns JSON 对象,键不存在时返回 null;值无法解析时抛错
*/ */
public static getJSON(key: string) { public static getJSON(key: string): unknown {
return JSON.parse(this.getString(key)); const value = this.getString(key);
return value === null ? null : JSON.parse(value);
} }
/** /**
* 获取为字符串(其他获取方式一般经过这个方法,找不到配置或配置值无效时会抛错) * 获取为字符串
* *
* @param key 键 * @param key 键
* @returns 字符串 * @returns 字符串,键不存在时返回 null
*/ */
public static getString(key: string): string { public static getString(key: string): string | null {
const value = localStorage.getItem(key); return localStorage.getItem(key);
if (value) {
return value;
}
throw new Error(`not found: ${key}, ${value}`);
} }
/** /**
@@ -81,7 +76,7 @@ export default class Storage {
* @returns true 为存在 * @returns true 为存在
*/ */
public static has(key: string): boolean { public static has(key: string): boolean {
return localStorage.getItem(key) !== undefined && localStorage.getItem(key) !== null; return localStorage.getItem(key) !== null;
} }
/** /**
+46 -16
View File
@@ -232,43 +232,73 @@ export default class Toolkit {
return Object.keys(e)[Object.values(e).indexOf(value)]; return Object.keys(e)[Object.values(e).indexOf(value)];
} }
// 防抖 /**
// eslint-disable-next-line * 防抖函数
public static debounce<T extends (...args: any[]) => any>(callback: T, defaultImmediate = true, delay = 600): T & { *
cancel(): void * 支持两种模式:
} { * - **立即模式**`immediate = true`,默认):首次调用立即执行,连续调用在延迟结束后再执行最后一次(leading + trailing
let timerId: ReturnType<typeof setTimeout> | null = null; // 存储定时器 * - **延迟模式**`immediate = false`):所有调用在延迟结束后执行最后一次(trailing only
let immediate = defaultImmediate; *
// 定义一个 cancel 办法,用于勾销防抖 * @param callback 回调函数
* @param immediate 是否首次调用立即执行,默认 true
* @param delay 延迟毫秒数,默认 600
* @returns 防抖后的函数,附带 `cancel()` 方法用于取消 pending 执行并重置状态
*
* @example
* ```ts
* // 立即模式(默认):首次立即触发,连续调用后在延迟结束时再触发一次
* const fn = Toolkit.debounce((val: string) => {
* console.log(val);
* });
* fn("a"); // 立即输出 "a"
* fn("b"); // 600ms 后输出 "b"trailing
* fn.cancel(); // 取消 pending 的 trailing 调用,重置为初始状态
*
* // 延迟模式:连续调用只在停止后触发最后一次
* const fn2 = Toolkit.debounce((val: string) => {
* console.log(val);
* }, false);
* fn2("a"); // 不输出
* fn2("b"); // 600ms 后输出 "b"
* ```
*/
public static debounce<T extends (...args: any[]) => any>(
callback: T,
immediate = true,
delay = 300
): ((...args: Parameters<T>) => void) & { cancel(): void } {
let timerId: ReturnType<typeof setTimeout> | null = null;
let leading = immediate;
const cancel = (): void => { const cancel = (): void => {
if (timerId) { if (timerId) {
clearTimeout(timerId); clearTimeout(timerId);
timerId = null; timerId = null;
} }
leading = immediate;
}; };
const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void { const debounced = function (this: ThisParameterType<T>, ...args: Parameters<T>): void {
const context = this; const context = this;
if (timerId) { if (timerId) {
cancel(); clearTimeout(timerId);
timerId = null;
} }
if (immediate) { if (leading) {
callback.apply(context, args); callback.apply(context, args);
immediate = false; leading = false;
timerId = setTimeout(() => { timerId = setTimeout(() => {
immediate = defaultImmediate; leading = immediate;
}, delay); }, delay);
} else { } else {
// 设置定时器,在延迟时间后执行指标函数
timerId = setTimeout(() => { timerId = setTimeout(() => {
callback.apply(context, args); callback.apply(context, args);
immediate = defaultImmediate; leading = immediate;
}, delay); }, delay);
} }
}; };
// 将 cancel 方法附加到 debounced 函数上
(debounced as any).cancel = cancel; (debounced as any).cancel = cancel;
return debounced as T & { cancel(): void }; return debounced as ((...args: Parameters<T>) => void) & { cancel(): void };
} }
public static toUserLevel(exp?: number): UserLevelType { public static toUserLevel(exp?: number): UserLevelType {
+6 -11
View File
@@ -1,12 +1,7 @@
import type { Directive, DirectiveBinding } from "vue"; import type { Directive, DirectiveBinding } from "vue";
import Toolkit from "../Toolkit"; import Toolkit from "../Toolkit";
export enum PopupType { export type PopupType = "TEXT" | "IMG" | "HTML" | "ELEMENT";
TEXT,
IMG,
HTML,
EL
}
/** */ /** */
export type PopupConfig = { export type PopupConfig = {
@@ -30,7 +25,7 @@ const VPopup: Directive = {
config = binding.value as PopupConfig; config = binding.value as PopupConfig;
} else { } else {
config = { config = {
type: PopupType.TEXT, type: "TEXT",
value: binding.value as any as string, value: binding.value as any as string,
canShow: () => true canShow: () => true
}; };
@@ -58,24 +53,24 @@ const VPopup: Directive = {
popup.appendChild(el); popup.appendChild(el);
} }
switch (config.type) { switch (config.type) {
case PopupType.TEXT: case "TEXT":
// 文本 // 文本
el = document.createElement("div"); el = document.createElement("div");
el.className = "text"; el.className = "text";
el.textContent = config.value as string; el.textContent = config.value as string;
popup.appendChild(el); popup.appendChild(el);
break; break;
case PopupType.IMG: case "IMG":
// 图片 // 图片
el = document.createElement("img"); el = document.createElement("img");
(el as HTMLImageElement).src = config.value as string; (el as HTMLImageElement).src = config.value as string;
popup.appendChild(el); popup.appendChild(el);
break; break;
case PopupType.HTML: case "HTML":
// HTML 字符串 // HTML 字符串
popup.appendChild(Toolkit.toDOM(config.value as string)); popup.appendChild(Toolkit.toDOM(config.value as string));
break; break;
case PopupType.EL: case "ELEMENT":
// DOM 节点 // DOM 节点
if (config.value instanceof HTMLElement) { if (config.value instanceof HTMLElement) {
const valueEl = config.value as HTMLElement; const valueEl = config.value as HTMLElement;