fix: add page perfect

This commit is contained in:
xion 2024-11-30 02:21:33 +08:00
parent ebf0b230b1
commit 4148d162e4
6 changed files with 177 additions and 9 deletions

16
demo/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo Page</title>
</head>
<body>
<h1>Welcome to the Demo Page</h1>
<p>This is a basic HTML template.</p>
<script src="./src/index.ts" type="module"></script>
</body>
</html>

16
demo/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "demo",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"dev": "vite"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me>",
"license": "MIT",
"type": "module",
"dependencies": {
"@kevisual/store": "link:.."
}
}

17
demo/src/index.ts Normal file
View File

@ -0,0 +1,17 @@
console.log('index.js');
import { Page } from '@kevisual/store/page';
const page = new Page({
isListen: true,
});
page.addPage('/:id', 'user');
page.subscribe('user', (params, state) => {
console.log('user', params, 'state', state);
return;
});
// page.navigate('/c', { id: 3 });
// page.navigate('/c', { id: 2 });
// page.refresh();

View File

@ -1,6 +1,6 @@
{ {
"name": "@kevisual/store", "name": "@kevisual/store",
"version": "0.0.1-alpha.3", "version": "0.0.1-alpha.4",
"main": "dist/store.js", "main": "dist/store.js",
"module": "dist/store.js", "module": "dist/store.js",
"types": "dist/store.d.ts", "types": "dist/store.d.ts",

View File

@ -1,10 +1,12 @@
import { getPathKey } from '@/utils/path-key.ts'; import { getPathKey } from '@/utils/path-key.ts';
import { match } from 'path-to-regexp'; import { match } from 'path-to-regexp';
import deepEqual from 'fast-deep-equal';
type PageOptions = { type PageOptions = {
path?: string; path?: string;
key?: string; key?: string;
basename?: string; basename?: string;
isListen?: boolean;
}; };
type PageModule = { type PageModule = {
// 模块的key如 user定义模块的唯一标识 // 模块的key如 user定义模块的唯一标识
@ -12,31 +14,80 @@ type PageModule = {
// 模块的path路径如 /user/:idmatch的时候会用到 // 模块的path路径如 /user/:idmatch的时候会用到
path: string; path: string;
}; };
type CallFn = (params: Record<string, any>, state?: any, e?: any) => any;
type CallbackInfo = {
fn: CallFn;
id: string;
} & PageModule;
export class Page { export class Page {
pageModule = new Map<string, PageModule>(); pageModule = new Map<string, PageModule>();
// pathname的第一个路径
path: string; path: string;
// pathname的第二个路径
key: string; key: string;
basename: string; basename: string;
isListen: boolean;
callbacks = [] as CallbackInfo[];
constructor(opts?: PageOptions) { constructor(opts?: PageOptions) {
const pathKey = getPathKey(); const pathKey = getPathKey();
this.path = opts?.path ?? pathKey.path; this.path = opts?.path ?? pathKey.path;
this.key = opts?.key ?? pathKey.key; this.key = opts?.key ?? pathKey.key;
this.basename = opts?.basename ?? `/${this.path}/${this.key}`; this.basename = opts?.basename ?? `/${this.path}/${this.key}`;
const isListen = opts?.isListen ?? true;
if (isListen) {
this.listen();
} }
}
popstate(event?: PopStateEvent, manual?: boolean) {
const pathname = window.location.pathname;
// manual 为true时表示手动调用不需要检查是否相等
this.callbacks.forEach((item) => {
const result = this.check(item.key, pathname);
result && item.fn?.(result.params, event.state, { event, manual });
});
}
listen() {
this.isListen = true;
window.addEventListener('popstate', this.popstate.bind(this), false);
}
unlisten() {
this.isListen = false;
window.removeEventListener('popstate', this.popstate.bind(this), false);
}
/**
*
* @param path
* @param key
*/
addPage(path: string, key?: string) { addPage(path: string, key?: string) {
this.pageModule.set(key, { path, key }); this.pageModule.set(key, { path, key });
} }
check(key: string, pathname?: string): boolean | Record<string, string> { addObjectPages(pages: Record<string, string>) {
if (!key) { Object.keys(pages).forEach((key) => {
console.error('key is required'); this.addPage(pages[key], key);
return; });
} }
addPages(pages: { path: string; key?: string }[]) {
pages.forEach((item) => {
this.addPage(item.path, item.key);
});
}
/**
*
* @param key
* @param pathname
* @returns
*/
check(key: string, pathname?: string): false | { pathname: string; params: Record<string, any> } {
const has = this.pageModule.has(key); const has = this.pageModule.has(key);
if (!has) { if (!has) {
console.error(`PageModule ${key} not found`); console.error(`PageModule ${key} not found`);
return; return;
} }
const pageModule = this.pageModule.get(key); const pageModule = this.pageModule.get(key);
if (!pageModule) {
return false;
}
const location = window.location; const location = window.location;
const _pathname = pathname || location.pathname; const _pathname = pathname || location.pathname;
const cur = _pathname.replace(this.basename, ''); const cur = _pathname.replace(this.basename, '');
@ -46,23 +97,90 @@ export class Page {
if (result) { if (result) {
result.path; result.path;
params = result.params; params = result.params;
return params; return { pathname: _pathname, params };
} }
return false; return false;
} }
/** /**
* * path监听事件
* @returns * @param key
* @param fn
* @param opts
* @returns
*/
subscribe(key: string, fn?: CallFn, opts?: { pathname?: string; runImmediately?: boolean; id?: string }) {
const runImmediately = opts?.runImmediately ?? true; // 默认立即执行
const id = opts?.id ?? Math.random().toString(36).slice(2);
const path = this.pageModule.get(key)?.path;
if (!path) {
console.error(`PageModule ${key} not found`);
return () => {};
}
this.callbacks.push({ key, fn, id: id, path });
if (runImmediately) {
this.popstate({ state: window.history.state } as any, true);
}
return () => {
this.callbacks = this.callbacks.filter((item) => item.id !== id);
};
}
/**
* basename
* @returns
*/ */
getPath() { getPath() {
const location = window.location; const location = window.location;
const pathname = location.pathname; const pathname = location.pathname;
return pathname.replace(this.basename, ''); return pathname.replace(this.basename, '');
} }
getAppPath() {
return this.path;
}
getAppKey() {
return this.key;
}
decodePath(path: string) { decodePath(path: string) {
return decodeURIComponent(path); return decodeURIComponent(path);
} }
encodePath(path: string) { encodePath(path: string) {
return encodeURIComponent(path); return encodeURIComponent(path);
} }
/**
* state pathname都相等popstate
* @param path
* @param state
* @param check
* @returns
*/
navigate(path: string | number, state?: any, check?: boolean) {
if (typeof path === 'number') {
window.history.go(path);
return;
}
const location = window.location;
const pathname = location.pathname;
const newPathname = new URL(this.basename + path, location.href).pathname;
if (pathname === newPathname) {
if (deepEqual(state, window.history.state)) {
return;
}
}
window.history.pushState(state, '', this.basename + path);
let _check = check ?? true;
if (_check) {
this.popstate({ state } as any, true);
}
}
replace(path: string, state?: any, check?: boolean) {
let _check = check ?? true;
window.history.replaceState(state, '', this.basename + path);
if (_check) {
this.popstate({ state } as any, true);
}
}
refresh() {
const state = window.history.state;
this.popstate({ state } as any, true);
}
} }

View File

@ -62,4 +62,5 @@ export class StoreManager {
export const store = new StoreManager(); export const store = new StoreManager();
export { shallow, deepEqual }; export { shallow, deepEqual };