diff --git a/package.json b/package.json index 6f00cff..3688b06 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "module": "./dist/timi-web.mjs", "style": "./dist/timi-web.css", "private": false, - "version": "0.0.22", + "version": "0.0.23", "license": "MIT", "scripts": { "dev": "vite", diff --git a/src/api/UserAPI.ts b/src/api/UserAPI.ts index 1456bcc..1e79796 100644 --- a/src/api/UserAPI.ts +++ b/src/api/UserAPI.ts @@ -1,8 +1,12 @@ -import {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User, UserAttachType} from "../types"; +import type {Attachment, CaptchaData, LoginRequest, LoginResponse, RegisterRequest, UpdatePasswordRequest, User} from "../types"; +import {UserAttachType} from "../types"; import {axios} from "./BaseAPI"; import CommonAPI from "./CommonAPI"; const BASE_URI = "/user"; +type UserAttachmentOwner = { + attachmentList?: readonly Attachment[]; +}; async function register(req: CaptchaData): Promise { return await axios.post(`${BASE_URI}/register`, req); @@ -31,7 +35,7 @@ async function logout(): Promise { return await axios.post(`${BASE_URI}/logout`); } -async function updateCurrent(req: User): Promise { +async function updateCurrent(req: Partial): Promise { return await axios.post(`${BASE_URI}/current/update`, req); } @@ -49,19 +53,19 @@ async function view(id: string): Promise { return await axios.post(`${BASE_URI}/view/${id}`); } -function getAvatarURL(user?: User) { +function getAvatarURL(user?: UserAttachmentOwner) { if (user?.attachmentList) { return findAttachmentByType(user.attachmentList, [UserAttachType.AVATAR, UserAttachType.DEFAULT_AVATAR]); } } -function getWrapperURL(user?: User) { +function getWrapperURL(user?: UserAttachmentOwner) { if (user?.attachmentList) { return findAttachmentByType(user.attachmentList, [UserAttachType.WRAPPER, UserAttachType.DEFAULT_WRAPPER]); } } -function findAttachmentByType(attachmentList: Attachment[], types: UserAttachType[]) { +function findAttachmentByType(attachmentList: readonly Attachment[], types: UserAttachType[]) { for (let i = 0; i < attachmentList.length; i++) { const attachType = attachmentList[i].attachType as UserAttachType | undefined; const id = attachmentList[i].id; diff --git a/src/store/userStore.ts b/src/store/userStore.ts index faf40dc..69d161b 100644 --- a/src/store/userStore.ts +++ b/src/store/userStore.ts @@ -16,12 +16,35 @@ export type AccessMatchMode = "and" | "or"; let _storageKey = "loginUser"; -function normalizeAccessList(list?: string[]): string[] { +type AccessItem = string | { + moduleCode?: string; + code?: string; +}; + +function normalizeAccessItem(item?: AccessItem): string { + if (!item) { + return ""; + } + + if (typeof item === "string") { + return item.trim(); + } + + const code = item.code?.trim(); + if (!code) { + return ""; + } + + const moduleCode = item.moduleCode?.trim(); + return moduleCode && !code.includes(":") ? `${moduleCode}:${code}` : code; +} + +function normalizeAccessList(list?: AccessItem[]): string[] { if (!list?.length) { return []; } return list - .map(item => item?.trim()) + .map(normalizeAccessItem) .filter((item): item is string => !!item); } diff --git a/src/types/Model.ts b/src/types/Model.ts index 7b5ad88..72a4cdd 100644 --- a/src/types/Model.ts +++ b/src/types/Model.ts @@ -23,8 +23,8 @@ export type Page = { index: number; size: number; orderMap?: { [K in keyof T]?: OrderType }; - equalsExample?: T; - likesExample?: T; + equalsExample?: Partial; + likesExample?: Partial; } export enum OrderType { diff --git a/src/utils/Scroller.ts b/src/utils/Scroller.ts index 5df2c38..0725915 100644 --- a/src/utils/Scroller.ts +++ b/src/utils/Scroller.ts @@ -7,6 +7,15 @@ export type ScrollListener = { bottom: number; } +export type ScrollBottomListenerOptions = { + + /** 距离底部小于等于该值时触发 */ + distance?: number; + + /** 返回 true 时才触发 */ + canLoad?: () => boolean; +} + export default class Scroller { private static instance: Scroller; @@ -53,6 +62,26 @@ export default class Scroller { Scroller.getInstance().listeners.set(name, listener); } + /** + * 添加触底监听 + * + * @param name 事件名 + * @param listener 触底回调 + * @param options 触底配置 + */ + public static addBottomListener(name: string, listener: (event: ScrollListener) => void | Promise, options: ScrollBottomListenerOptions = {}) { + const distance = options.distance ?? 120; + Scroller.addListener(name, event => { + if (distance < event.bottom) { + return; + } + if (options.canLoad && !options.canLoad()) { + return; + } + void listener(event); + }); + } + /** * 移除监听 * diff --git a/src/utils/Text.ts b/src/utils/Text.ts index ba60eb0..920aaa4 100644 --- a/src/utils/Text.ts +++ b/src/utils/Text.ts @@ -30,6 +30,10 @@ export default class Text { return template.replace(/\$\{(\w+)}/g, (_, key) => variables[key]); } + public static pad(value: number | string, length = 2, fill = "0"): string { + return value.toString().padStart(length, fill); + } + public static unitCompact(val: number | string | null | undefined, unit: string, fixed = 0): string { return this.unit(val, unit, fixed, true); }; diff --git a/src/utils/Time.ts b/src/utils/Time.ts index 7a75f00..29b8d11 100644 --- a/src/utils/Time.ts +++ b/src/utils/Time.ts @@ -1,4 +1,5 @@ import Toolkit from "./Toolkit"; +import Text from "./Text"; export default class Time { @@ -15,6 +16,25 @@ export default class Time { return new Date().getTime(); } + public static toTimestamp(date: Date): number { + return date.getTime(); + } + + public static toDateObject(unix?: number): Date | undefined { + if (!unix) { + return undefined; + } + return new Date(unix); + } + + public static formatDate(date: Date): string { + return `${date.getFullYear()}-${Text.pad(date.getMonth() + 1)}-${Text.pad(date.getDate())}`; + } + + public static formatDateTime(date: Date): string { + return `${this.formatDate(date)} ${Text.pad(date.getHours())}:${Text.pad(date.getMinutes())}`; + } + /** * Unix 时间戳转日期 * @@ -22,8 +42,7 @@ export default class Time { */ public static toDate(unix?: number): string { if (!unix) return ""; - const d = new Date(unix); - return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`; + return this.formatDate(new Date(unix)); } /** @@ -34,13 +53,13 @@ export default class Time { public static toTime(unix?: number): string { if (!unix) return ""; const d = new Date(unix); - return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`; + return `${Text.pad(d.getHours())}:${Text.pad(d.getMinutes())}`; } public static toShortTime(unix?: number): string { if (!unix) return ""; const d = new Date(unix); - return `${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`; + return `${Text.pad(d.getMinutes())}:${Text.pad(d.getSeconds())}`; } /** @@ -131,9 +150,9 @@ export default class Time { const minutes = Math.floor((seconds % 3600) / 60); const second = seconds % 60; if (0 < hours) { - return `${hours}:${minutes.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`; + return `${hours}:${Text.pad(minutes)}:${Text.pad(second)}`; } - return `${minutes.toString().padStart(2, "0")}:${second.toString().padStart(2, "0")}`; + return `${Text.pad(minutes)}:${Text.pad(second)}`; } /** diff --git a/src/utils/Toolkit.ts b/src/utils/Toolkit.ts index f0c728a..2bde6eb 100644 --- a/src/utils/Toolkit.ts +++ b/src/utils/Toolkit.ts @@ -398,4 +398,15 @@ export default class Toolkit { } return JSON.stringify(JSON.parse(jsonStr), null, 4); } + + public static downloadBlob(blob: Blob, fileName: string): void { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = fileName; + document.body.appendChild(link); + link.click(); + link.remove(); + window.setTimeout(() => URL.revokeObjectURL(url), 1000); + } }