This commit is contained in:
Timi
2026-08-01 17:35:48 +08:00
parent 2cf33d1003
commit efea624376
6 changed files with 132 additions and 39 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
"module": "./dist/timi-web.mjs", "module": "./dist/timi-web.mjs",
"style": "./dist/timi-web.css", "style": "./dist/timi-web.css",
"private": false, "private": false,
"version": "0.0.23", "version": "0.0.24",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
+6 -3
View File
@@ -1,5 +1,5 @@
import { axios } from "./BaseAPI"; import { axios } from "./BaseAPI";
import type { Page, PageResult, Permission, Role, RolePayload, UserRoleAuthorizeReq } from "../types"; import type { ModuleCode, Page, PageResult, Permission, Role, RolePayload, UserRoleAuthorizeReq } from "../types";
const BASE_URI = "/user/role"; const BASE_URI = "/user/role";
@@ -27,9 +27,12 @@ async function remove(id: string): Promise<void> {
}); });
} }
async function authorizedList(userId: string): Promise<Role[]> { async function authorizedList(userId: string, moduleCode?: ModuleCode): Promise<Role[]> {
return axios.post(`${BASE_URI}/authorized/list`, undefined, { return axios.post(`${BASE_URI}/authorized/list`, undefined, {
params: { userId } params: {
userId,
moduleCode
}
}); });
} }
+5 -5
View File
@@ -103,23 +103,23 @@ textarea {
-webkit-filter: grayscale(1); -webkit-filter: grayscale(1);
} }
.bold { .text-bold {
font-weight: bold !important; font-weight: bold !important;
} }
.italic { .text-italic {
font-style: italic !important; font-style: italic !important;
} }
.delete { .text-delete {
text-decoration: line-through !important; text-decoration: line-through !important;
} }
.underline { .text-underline {
text-decoration: underline !important; text-decoration: underline !important;
} }
.not-underline { .text-not-underline {
text-decoration: none !important; text-decoration: none !important;
} }
+4 -1
View File
@@ -15,7 +15,10 @@ export type Model = {
export type ApiResponse<T> = { export type ApiResponse<T> = {
code: number; code: number;
msg?: string; message: string;
messageCode: string;
success: boolean;
fail: boolean;
data: T; data: T;
} }
+2 -2
View File
@@ -62,13 +62,13 @@ const applyDefaultInterceptors = (instance: AxiosInstance) => {
status: axiosResp?.status, status: axiosResp?.status,
response: axiosResp, response: axiosResp,
serverResponse: serverResp, serverResponse: serverResp,
error: serverResp.msg error: serverResp.message
}); });
if (globalErrorCallback) { if (globalErrorCallback) {
globalErrorCallback(serverResp); globalErrorCallback(serverResp);
} }
// 由调用方处理 // 由调用方处理
return Promise.reject(serverResp.msg); return Promise.reject(serverResp.message);
} }
} }
notifyResponseObserver({ notifyResponseObserver({
+108 -21
View File
@@ -3,6 +3,7 @@ export type ScrollListener = {
source: Event; source: Event;
viewWidth: number; viewWidth: number;
viewHeight: number; viewHeight: number;
scrollHeight: number;
top: number; top: number;
bottom: number; bottom: number;
} }
@@ -14,35 +15,64 @@ export type ScrollBottomListenerOptions = {
/** 返回 true 时才触发 */ /** 返回 true 时才触发 */
canLoad?: () => boolean; canLoad?: () => boolean;
/** 自定义滚动容器,默认 window */
getElement?: () => HTMLElement | null | undefined;
} }
export default class Scroller { export default class Scroller {
private static instance: Scroller; private static instance: Scroller;
listeners = new Map<string, Function>(); listeners = new Map<string, {
target: HTMLElement | Window;
listener: EventListener;
}>();
private constructor() { private constructor() {}
window.addEventListener("scroll", source => { private static waitForLayout(): Promise<void> {
// 滚动距离 return new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => resolve());
});
});
}
private static getScrollState(element?: HTMLElement | null, source?: Event): ScrollListener {
if (element) {
return {
source: source || new Event("scroll"),
viewWidth: element.clientWidth,
viewHeight: element.clientHeight,
scrollHeight: element.scrollHeight,
top: element.scrollTop,
bottom: element.scrollHeight - element.scrollTop - element.clientHeight
};
}
const top = document.body.scrollTop || document.documentElement.scrollTop; const top = document.body.scrollTop || document.documentElement.scrollTop;
// 可视高度
const viewWidth = document.documentElement.clientWidth || document.body.clientWidth; const viewWidth = document.documentElement.clientWidth || document.body.clientWidth;
// 可视高度
const viewHeight = document.documentElement.clientHeight || document.body.clientHeight; const viewHeight = document.documentElement.clientHeight || document.body.clientHeight;
// 滚动高度 const scrollHeight = document.documentElement.scrollHeight || document.body.scrollHeight;
const sH = document.documentElement.scrollHeight || document.body.scrollHeight; return {
// 触发事件 source: source || new Event("scroll"),
const bottom = sH - top - viewHeight;
this.listeners.forEach(listener => listener({
source,
viewWidth, viewWidth,
viewHeight, viewHeight,
scrollHeight,
top, top,
bottom bottom: scrollHeight - top - viewHeight
} as ScrollListener)); };
}, true); }
private static bindTarget(name: string, target: HTMLElement | Window, listener: EventListener) {
target.addEventListener("scroll", listener, {
passive: true
});
Scroller.getInstance().listeners.set(name, {
target,
listener
});
} }
private static getInstance(): Scroller { private static getInstance(): Scroller {
@@ -58,8 +88,24 @@ export default class Scroller {
* @param name 事件名 * @param name 事件名
* @param listener 监听事件 * @param listener 监听事件
*/ */
public static addListener(name: string, listener: (event: ScrollListener) => void) { public static addListener(name: string, listener: (event: ScrollListener) => void, getElement?: () => HTMLElement | null | undefined) {
Scroller.getInstance().listeners.set(name, listener); Scroller.removeListener(name);
const targetElement = getElement?.();
if (getElement && !targetElement) {
return;
}
const target = targetElement || window;
const eventListener: EventListener = source => {
if (!targetElement) {
const eventTarget = source.target;
const isWindowScrollTarget = eventTarget === document || eventTarget === document.documentElement || eventTarget === document.body;
if (!isWindowScrollTarget) {
return;
}
}
listener(Scroller.getScrollState(targetElement, source));
};
Scroller.bindTarget(name, target, eventListener);
} }
/** /**
@@ -71,15 +117,51 @@ export default class Scroller {
*/ */
public static addBottomListener(name: string, listener: (event: ScrollListener) => void | Promise<void>, options: ScrollBottomListenerOptions = {}) { public static addBottomListener(name: string, listener: (event: ScrollListener) => void | Promise<void>, options: ScrollBottomListenerOptions = {}) {
const distance = options.distance ?? 120; const distance = options.distance ?? 120;
Scroller.addListener(name, event => { let loading = false;
if (distance < event.bottom) { let armed = true;
const triggerLoad = async (event: ScrollListener): Promise<void> => {
const inRange = event.bottom <= distance;
if (!inRange) {
armed = true;
return;
}
if (loading) {
return;
}
if (!armed) {
return; return;
} }
if (options.canLoad && !options.canLoad()) { if (options.canLoad && !options.canLoad()) {
return; return;
} }
void listener(event); armed = false;
}); loading = true;
try {
let currentEvent = event;
while (true) {
const previousScrollHeight = currentEvent.scrollHeight;
await listener(currentEvent);
await Scroller.waitForLayout();
if (options.canLoad && !options.canLoad()) {
break;
}
currentEvent = Scroller.getScrollState(options.getElement?.());
if (distance < currentEvent.bottom) {
armed = true;
break;
}
if (currentEvent.scrollHeight <= previousScrollHeight) {
armed = true;
break;
}
}
} finally {
loading = false;
}
};
Scroller.addListener(name, event => {
void triggerLoad(event);
}, options.getElement);
} }
/** /**
@@ -88,6 +170,11 @@ export default class Scroller {
* @param name 事件名 * @param name 事件名
*/ */
public static removeListener(name: string) { public static removeListener(name: string) {
const entry = Scroller.getInstance().listeners.get(name);
if (!entry) {
return;
}
entry.target.removeEventListener("scroll", entry.listener);
Scroller.getInstance().listeners.delete(name); Scroller.getInstance().listeners.delete(name);
} }