@finesoft/create-app 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +22 -26
- package/package.json +14 -5
- package/templates/react/README.md +35 -0
- package/templates/react/index.html +2 -3
- package/templates/react/package.json +12 -8
- package/templates/react/src/App.tsx +7 -15
- package/templates/react/src/app-definition.ts +35 -0
- package/templates/react/src/components/Layout.tsx +5 -52
- package/templates/react/src/components/Loading.tsx +1 -1
- package/templates/react/src/components/Navigation.tsx +10 -37
- package/templates/react/src/components/ProductCard.tsx +5 -23
- package/templates/react/src/config.ts +2 -0
- package/templates/react/src/lib/controllers/about.ts +18 -13
- package/templates/react/src/lib/controllers/error.ts +11 -7
- package/templates/react/src/lib/controllers/home.ts +36 -41
- package/templates/react/src/lib/controllers/product-detail.ts +71 -40
- package/templates/react/src/lib/controllers/search.ts +33 -54
- package/templates/react/src/lib/data/products.ts +36 -0
- package/templates/react/src/lib/mappers/product.ts +3 -3
- package/templates/react/src/lib/models/product.ts +11 -1
- package/templates/react/src/lib/services/api-client.ts +7 -4
- package/templates/react/src/main.ts +34 -0
- package/templates/react/src/pages/About.tsx +2 -4
- package/templates/react/src/pages/Home.tsx +6 -15
- package/templates/react/src/pages/NotFound.tsx +4 -14
- package/templates/react/src/pages/ProductDetail.tsx +5 -23
- package/templates/react/src/pages/Search.tsx +5 -7
- package/templates/react/src/ssr.ts +11 -0
- package/templates/react/src/styles.css +71 -0
- package/templates/react/src/views.ts +12 -0
- package/templates/react/tsconfig.json +14 -9
- package/templates/react/vite.config.ts +9 -13
- package/templates/react-minimal/README.md +39 -0
- package/templates/react-minimal/index.html +2 -3
- package/templates/react-minimal/package.json +12 -8
- package/templates/react-minimal/src/App.tsx +40 -85
- package/templates/react-minimal/src/app-definition.ts +48 -0
- package/templates/react-minimal/src/config.ts +2 -0
- package/templates/react-minimal/src/lib/controllers/detail.ts +19 -18
- package/templates/react-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/react-minimal/src/lib/controllers/home.ts +19 -24
- package/templates/react-minimal/src/lib/controllers/notes.ts +7 -5
- package/templates/react-minimal/src/lib/locale.ts +16 -0
- package/templates/react-minimal/src/lib/models/page.ts +27 -0
- package/templates/react-minimal/src/lib/navigation.ts +11 -0
- package/templates/react-minimal/src/locales/en-US.json +5 -0
- package/templates/react-minimal/src/locales/zh-Hans.json +5 -0
- package/templates/react-minimal/src/main.ts +43 -0
- package/templates/react-minimal/src/pages/Detail.tsx +20 -0
- package/templates/react-minimal/src/pages/Home.tsx +38 -0
- package/templates/react-minimal/src/pages/NotFound.tsx +11 -0
- package/templates/react-minimal/src/pages/Notes.tsx +21 -0
- package/templates/react-minimal/src/ssr.ts +11 -0
- package/templates/react-minimal/src/styles.css +74 -0
- package/templates/react-minimal/src/views.ts +6 -0
- package/templates/react-minimal/src/vite-env.d.ts +3 -0
- package/templates/react-minimal/tsconfig.json +14 -9
- package/templates/react-minimal/vite.config.ts +9 -7
- package/templates/svelte/README.md +35 -0
- package/templates/svelte/index.html +1 -2
- package/templates/svelte/package.json +9 -5
- package/templates/svelte/src/App.svelte +8 -80
- package/templates/svelte/src/app-definition.ts +35 -0
- package/templates/svelte/src/components/Layout.svelte +2 -2
- package/templates/svelte/src/components/Loading.svelte +1 -1
- package/templates/svelte/src/components/Navigation.svelte +5 -66
- package/templates/svelte/src/components/ProductCard.svelte +3 -24
- package/templates/svelte/src/config.ts +2 -0
- package/templates/svelte/src/lib/controllers/about.ts +18 -13
- package/templates/svelte/src/lib/controllers/error.ts +11 -7
- package/templates/svelte/src/lib/controllers/home.ts +36 -41
- package/templates/svelte/src/lib/controllers/product-detail.ts +71 -40
- package/templates/svelte/src/lib/controllers/search.ts +33 -54
- package/templates/svelte/src/lib/data/products.ts +36 -0
- package/templates/svelte/src/lib/mappers/product.ts +3 -3
- package/templates/svelte/src/lib/models/product.ts +12 -1
- package/templates/svelte/src/lib/services/api-client.ts +7 -4
- package/templates/svelte/src/main.ts +28 -24
- package/templates/svelte/src/pages/About.svelte +2 -2
- package/templates/svelte/src/pages/Home.svelte +3 -15
- package/templates/svelte/src/pages/NotFound.svelte +3 -12
- package/templates/svelte/src/pages/ProductDetail.svelte +4 -21
- package/templates/svelte/src/pages/Search.svelte +2 -10
- package/templates/svelte/src/ssr.ts +10 -9
- package/templates/svelte/src/styles.css +71 -0
- package/templates/svelte/src/views.ts +12 -0
- package/templates/svelte/svelte.config.js +2 -0
- package/templates/svelte/tsconfig.json +13 -8
- package/templates/svelte/vite.config.ts +9 -13
- package/templates/svelte-minimal/README.md +39 -0
- package/templates/svelte-minimal/index.html +1 -2
- package/templates/svelte-minimal/package.json +9 -5
- package/templates/svelte-minimal/src/App.svelte +49 -61
- package/templates/svelte-minimal/src/app-definition.ts +48 -0
- package/templates/svelte-minimal/src/config.ts +2 -0
- package/templates/svelte-minimal/src/lib/controllers/detail.ts +23 -0
- package/templates/svelte-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/svelte-minimal/src/lib/controllers/home.ts +24 -23
- package/templates/svelte-minimal/src/lib/controllers/notes.ts +18 -0
- package/templates/svelte-minimal/src/lib/locale.ts +16 -0
- package/templates/svelte-minimal/src/lib/models/page.ts +27 -0
- package/templates/svelte-minimal/src/lib/navigation.ts +11 -0
- package/templates/svelte-minimal/src/locales/en-US.json +1 -3
- package/templates/svelte-minimal/src/locales/zh-Hans.json +1 -3
- package/templates/svelte-minimal/src/main.ts +37 -19
- package/templates/svelte-minimal/src/pages/Detail.svelte +11 -0
- package/templates/svelte-minimal/src/pages/Home.svelte +17 -21
- package/templates/svelte-minimal/src/pages/NotFound.svelte +10 -0
- package/templates/svelte-minimal/src/pages/Notes.svelte +11 -0
- package/templates/svelte-minimal/src/ssr.ts +9 -17
- package/templates/svelte-minimal/src/styles.css +74 -0
- package/templates/svelte-minimal/src/views.ts +6 -0
- package/templates/svelte-minimal/src/vite-env.d.ts +3 -0
- package/templates/svelte-minimal/svelte.config.js +2 -0
- package/templates/svelte-minimal/tsconfig.json +13 -8
- package/templates/svelte-minimal/vite.config.ts +9 -10
- package/templates/vue/README.md +35 -0
- package/templates/vue/index.html +1 -2
- package/templates/vue/package.json +9 -5
- package/templates/vue/src/App.vue +7 -22
- package/templates/vue/src/app-definition.ts +35 -0
- package/templates/vue/src/components/Layout.vue +4 -6
- package/templates/vue/src/components/Loading.vue +1 -1
- package/templates/vue/src/components/Navigation.vue +10 -43
- package/templates/vue/src/components/ProductCard.vue +4 -28
- package/templates/vue/src/config.ts +2 -0
- package/templates/vue/src/lib/controllers/about.ts +18 -13
- package/templates/vue/src/lib/controllers/error.ts +11 -7
- package/templates/vue/src/lib/controllers/home.ts +36 -41
- package/templates/vue/src/lib/controllers/product-detail.ts +71 -40
- package/templates/vue/src/lib/controllers/search.ts +33 -54
- package/templates/vue/src/lib/data/products.ts +36 -0
- package/templates/vue/src/lib/mappers/product.ts +3 -3
- package/templates/vue/src/lib/models/product.ts +12 -1
- package/templates/vue/src/lib/services/api-client.ts +7 -7
- package/templates/vue/src/main.ts +28 -43
- package/templates/vue/src/pages/About.vue +3 -5
- package/templates/vue/src/pages/Home.vue +5 -23
- package/templates/vue/src/pages/NotFound.vue +4 -13
- package/templates/vue/src/pages/ProductDetail.vue +5 -23
- package/templates/vue/src/pages/Search.vue +4 -18
- package/templates/vue/src/ssr.ts +5 -18
- package/templates/vue/src/styles.css +71 -0
- package/templates/vue/src/views.ts +12 -0
- package/templates/vue/tsconfig.json +14 -9
- package/templates/vue/vite.config.ts +9 -13
- package/templates/vue-minimal/README.md +39 -0
- package/templates/vue-minimal/index.html +1 -2
- package/templates/vue-minimal/package.json +9 -5
- package/templates/vue-minimal/src/App.vue +46 -43
- package/templates/vue-minimal/src/app-definition.ts +48 -0
- package/templates/vue-minimal/src/config.ts +2 -0
- package/templates/vue-minimal/src/lib/controllers/detail.ts +19 -18
- package/templates/vue-minimal/src/lib/controllers/error.ts +15 -0
- package/templates/vue-minimal/src/lib/controllers/home.ts +19 -24
- package/templates/vue-minimal/src/lib/controllers/notes.ts +7 -5
- package/templates/vue-minimal/src/lib/locale.ts +16 -0
- package/templates/vue-minimal/src/lib/models/page.ts +27 -0
- package/templates/vue-minimal/src/lib/navigation.ts +11 -0
- package/templates/vue-minimal/src/locales/en-US.json +5 -0
- package/templates/vue-minimal/src/locales/zh-Hans.json +5 -0
- package/templates/vue-minimal/src/main.ts +35 -70
- package/templates/vue-minimal/src/pages/Detail.vue +20 -0
- package/templates/vue-minimal/src/pages/Home.vue +34 -0
- package/templates/vue-minimal/src/pages/NotFound.vue +13 -0
- package/templates/vue-minimal/src/pages/Notes.vue +22 -0
- package/templates/vue-minimal/src/ssr.ts +7 -46
- package/templates/vue-minimal/src/styles.css +74 -0
- package/templates/vue-minimal/src/views.ts +6 -0
- package/templates/vue-minimal/src/vite-env.d.ts +3 -0
- package/templates/vue-minimal/tsconfig.json +14 -9
- package/templates/vue-minimal/vite.config.ts +9 -7
- package/templates/react/src/actions.ts +0 -14
- package/templates/react/src/bootstrap.ts +0 -36
- package/templates/react/src/components/PageRenderer.tsx +0 -41
- package/templates/react/src/main.tsx +0 -49
- package/templates/react/src/ssr.tsx +0 -23
- package/templates/react-minimal/src/bootstrap.ts +0 -39
- package/templates/react-minimal/src/main.tsx +0 -99
- package/templates/react-minimal/src/ssr.tsx +0 -51
- package/templates/react-minimal/src/views/DetailView.tsx +0 -28
- package/templates/react-minimal/src/views/HomeView.tsx +0 -27
- package/templates/react-minimal/src/views/NotesView.tsx +0 -11
- package/templates/svelte/src/actions.ts +0 -14
- package/templates/svelte/src/bootstrap.ts +0 -36
- package/templates/svelte/src/components/PageRenderer.svelte +0 -22
- package/templates/svelte/src/lib/framework-svelte.ts +0 -34
- package/templates/svelte/src/lib/render.ts +0 -15
- package/templates/svelte-minimal/src/bootstrap.ts +0 -6
- package/templates/svelte-minimal/src/lib/framework-svelte.ts +0 -34
- package/templates/svelte-minimal/src/lib/render.ts +0 -12
- package/templates/vue/src/actions.ts +0 -14
- package/templates/vue/src/bootstrap.ts +0 -42
- package/templates/vue/src/components/PageRenderer.vue +0 -26
- package/templates/vue-minimal/src/bootstrap.ts +0 -39
- package/templates/vue-minimal/src/views/DetailView.vue +0 -29
- package/templates/vue-minimal/src/views/HomeView.vue +0 -26
- package/templates/vue-minimal/src/views/NotesView.vue +0 -12
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
resolveIslandsShell,
|
|
3
|
-
startBrowserApp,
|
|
4
|
-
type AppHandle,
|
|
5
|
-
type BasePage,
|
|
6
|
-
type MountEntry,
|
|
7
|
-
type SessionStateProvider,
|
|
8
|
-
} from "@finesoft/front";
|
|
9
|
-
import type { ComponentType } from "react";
|
|
10
|
-
import { flushSync } from "react-dom";
|
|
11
|
-
import { createRoot, hydrateRoot } from "react-dom/client";
|
|
12
|
-
import App, { type NameStore } from "./App";
|
|
13
|
-
import { bootstrap, navigation } from "./bootstrap";
|
|
14
|
-
import DetailView from "./views/DetailView";
|
|
15
|
-
import HomeView from "./views/HomeView";
|
|
16
|
-
import NotesView from "./views/NotesView";
|
|
17
|
-
|
|
18
|
-
/** name 全局切片的极小外部 store —— React 无内建响应式,跨组件共享需显式 store(mount 前建好交给 provider + App)。 */
|
|
19
|
-
function createNameStore(): NameStore {
|
|
20
|
-
let value = "";
|
|
21
|
-
const listeners = new Set<() => void>();
|
|
22
|
-
return {
|
|
23
|
-
get: () => value,
|
|
24
|
-
set: (v) => {
|
|
25
|
-
value = v;
|
|
26
|
-
listeners.forEach((l) => l());
|
|
27
|
-
},
|
|
28
|
-
subscribe: (l) => {
|
|
29
|
-
listeners.add(l);
|
|
30
|
-
return () => listeners.delete(l);
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
const nameStore = createNameStore();
|
|
35
|
-
|
|
36
|
-
/** 全局切片(app-wide):用户名字 —— 跨 tab、跨重载都在(对标 SwiftUI @SceneStorage)。 */
|
|
37
|
-
const profileProvider: SessionStateProvider = {
|
|
38
|
-
key: "profile",
|
|
39
|
-
capture: () => ({ name: nameStore.get() }),
|
|
40
|
-
restore: (data) => nameStore.set((data as { name?: string }).name ?? ""),
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/** islands 闭包引用:mount 回调赋值,islands(mount 后挂)读时已就绪。 */
|
|
44
|
-
let controller: AppHandle | undefined;
|
|
45
|
-
|
|
46
|
-
/** intent → 视图组件。islands 按 entry 挂为独立 React root。 */
|
|
47
|
-
const VIEWS: Record<string, ComponentType<{ page: BasePage; controller?: AppHandle }>> = {
|
|
48
|
-
home: HomeView,
|
|
49
|
-
detail: DetailView,
|
|
50
|
-
notes: NotesView,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const mountEntry: MountEntry = (entry, container) => {
|
|
54
|
-
const View = VIEWS[entry.intent] ?? HomeView;
|
|
55
|
-
const element = <View page={entry.page} controller={controller} />;
|
|
56
|
-
// 首屏 island 已由 SSR 渲入容器(编排器收养并置 hydrate:true)→ 水合;否则新建。
|
|
57
|
-
if (entry.hydrate) {
|
|
58
|
-
const root = hydrateRoot(container, element);
|
|
59
|
-
return { unmount: () => root.unmount() };
|
|
60
|
-
}
|
|
61
|
-
const root = createRoot(container);
|
|
62
|
-
// flushSync 强制同步提交:domRestore 在 fs:enter 后用 rAF 回填 data-restore-root 字段,
|
|
63
|
-
// 而 React 的 root.render() 默认异步提交 DOM,rAF 会赢得竞态、字段元素尚不存在 → 回填扑空
|
|
64
|
-
// (从根路径恢复到深层 URL 时,该 island 是客户端新挂、非 SSR,必踩)。同步提交贴合 domRestore
|
|
65
|
-
// 假设的「挂载即 DOM 就绪」契约(Vue/Svelte 的 .mount() 天然同步满足)。
|
|
66
|
-
flushSync(() => root.render(element));
|
|
67
|
-
return { unmount: () => root.unmount() };
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
void startBrowserApp({
|
|
71
|
-
bootstrap,
|
|
72
|
-
mount(target, ctx) {
|
|
73
|
-
controller = ctx.app; // islands(mount 后挂)与 chrome 共用
|
|
74
|
-
// 方案 C:chrome 挂到 sibling chrome-root(不含 outlet);outlet 由框架编排器独占。
|
|
75
|
-
const { chromeRoot, hydrate } = resolveIslandsShell(target);
|
|
76
|
-
const initialSnapshot = ctx.navigation?.getSnapshot() ?? null;
|
|
77
|
-
const element = (
|
|
78
|
-
<App
|
|
79
|
-
initialSnapshot={initialSnapshot}
|
|
80
|
-
nav={ctx.navigation}
|
|
81
|
-
controller={ctx.app}
|
|
82
|
-
nameStore={nameStore}
|
|
83
|
-
/>
|
|
84
|
-
);
|
|
85
|
-
if (hydrate) hydrateRoot(chromeRoot, element);
|
|
86
|
-
else createRoot(chromeRoot).render(element);
|
|
87
|
-
return () => undefined;
|
|
88
|
-
},
|
|
89
|
-
callbacks: {
|
|
90
|
-
onNavigate() {},
|
|
91
|
-
onModal() {},
|
|
92
|
-
},
|
|
93
|
-
// 结构化导航 + islands:每屏 per-entry 挂为独立 root、保活。
|
|
94
|
-
navigation: { ...navigation.toBrowserConfig(), mountEntry },
|
|
95
|
-
// 重载 DOM 自动恢复:data-restore-root 内字段/滚动自动捕获回填。
|
|
96
|
-
domRestore: true,
|
|
97
|
-
// 会话恢复:注册全局切片 provider;导航位置 + 作用域状态由框架自动捕获。
|
|
98
|
-
session: { providers: [profileProvider] },
|
|
99
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createSSRNavigationRender,
|
|
3
|
-
renderIslandsHtml,
|
|
4
|
-
serializeServerData,
|
|
5
|
-
type BasePage,
|
|
6
|
-
type ResolvedEntry,
|
|
7
|
-
} from "@finesoft/front";
|
|
8
|
-
import type { ComponentType } from "react";
|
|
9
|
-
import { renderToString } from "react-dom/server";
|
|
10
|
-
import App from "./App";
|
|
11
|
-
import { bootstrap, navigation } from "./bootstrap";
|
|
12
|
-
import DetailView from "./views/DetailView";
|
|
13
|
-
import HomeView from "./views/HomeView";
|
|
14
|
-
import NotesView from "./views/NotesView";
|
|
15
|
-
|
|
16
|
-
const VIEWS: Record<string, ComponentType<{ page: BasePage }>> = {
|
|
17
|
-
home: HomeView,
|
|
18
|
-
detail: DetailView,
|
|
19
|
-
notes: NotesView,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* islands 架构 SSR(方案 C):
|
|
24
|
-
* - chrome(App,header + tabbar)渲进 `<div data-fs-chrome>`。
|
|
25
|
-
* - 可见 island 内容由 `renderIslandsHtml` 渲进 sibling `<main data-fs-outlet>`,客户端按 key 收养水合。
|
|
26
|
-
*
|
|
27
|
-
* chrome 水合 props parity:客户端 mount 时 navigation.getSnapshot() 即此 URL 推导 snapshot
|
|
28
|
-
* (tree 一致)→ chrome 水合无失配,且 nav bar 首屏即被 SSR 渲出。name 仍默认 ""(会话恢复在
|
|
29
|
-
* mount 后、水合后才生效)。nav/controller/nameStore 仅事件与订阅用,不影响 SSR DOM,可省。
|
|
30
|
-
*/
|
|
31
|
-
export const render = createSSRNavigationRender({
|
|
32
|
-
bootstrap,
|
|
33
|
-
getErrorPage(status, message) {
|
|
34
|
-
return { id: "error", pageType: "error", title: `Error ${status}`, description: message };
|
|
35
|
-
},
|
|
36
|
-
async renderApp(page, _framework, snapshot) {
|
|
37
|
-
const chromeHtml = renderToString(<App initialSnapshot={snapshot} />);
|
|
38
|
-
const islandsHtml = await renderIslandsHtml(snapshot, (entry: ResolvedEntry) => {
|
|
39
|
-
const View = VIEWS[entry.intent] ?? HomeView;
|
|
40
|
-
return renderToString(<View page={entry.page} />);
|
|
41
|
-
});
|
|
42
|
-
return {
|
|
43
|
-
html: `<div data-fs-chrome>${chromeHtml}</div><main data-fs-outlet>${islandsHtml}</main>`,
|
|
44
|
-
head: `<title>${page.title}</title>`,
|
|
45
|
-
css: "",
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
navigation: navigation.toSSRDefinition(),
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
export { serializeServerData };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type { BasePage } from "@finesoft/front";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Detail:由 push("detail", { id }) 进入。
|
|
5
|
-
*
|
|
6
|
-
* 零样板保活:标一个 data-restore-root,里面的裸 <input>(非受控)即自动:
|
|
7
|
-
* - in-session:push 走、pop 回来值还在(islands 保活,实例没销毁)
|
|
8
|
-
* - 重载:sessionStorage 回填(domRestore),合成事件驱动可能的受控绑定
|
|
9
|
-
* 注意必须用**非受控** input(无 value/onChange)——否则 React 会盖掉 domRestore 的命令式写值。
|
|
10
|
-
*/
|
|
11
|
-
export default function DetailView({ page }: { page: BasePage }) {
|
|
12
|
-
return (
|
|
13
|
-
<section>
|
|
14
|
-
<h1 style={{ margin: "0 0 0.25rem" }}>{page.title}</h1>
|
|
15
|
-
<p style={{ color: "#666", margin: "0 0 1rem" }}>{page.description}</p>
|
|
16
|
-
<div data-restore-root>
|
|
17
|
-
<label style={{ display: "block", marginTop: "1rem" }}>
|
|
18
|
-
Draft note for this screen:
|
|
19
|
-
<input
|
|
20
|
-
name="note"
|
|
21
|
-
placeholder="kept while alive; restored on reload"
|
|
22
|
-
style={{ width: "100%" }}
|
|
23
|
-
/>
|
|
24
|
-
</label>
|
|
25
|
-
</div>
|
|
26
|
-
</section>
|
|
27
|
-
);
|
|
28
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { AppHandle, BasePage } from "@finesoft/front";
|
|
2
|
-
import type { FeedPage } from "../lib/controllers/home";
|
|
3
|
-
|
|
4
|
-
/** Home(feed):列表项点击 push 进 detail。 */
|
|
5
|
-
export default function HomeView({ page, controller }: { page: BasePage; controller?: AppHandle }) {
|
|
6
|
-
const feed = page.pageType === "home" ? (page as FeedPage) : null;
|
|
7
|
-
return (
|
|
8
|
-
<section>
|
|
9
|
-
<h1 style={{ margin: "0 0 0.25rem" }}>{page.title}</h1>
|
|
10
|
-
<p style={{ color: "#666", margin: "0 0 1rem" }}>{page.description}</p>
|
|
11
|
-
{feed && (
|
|
12
|
-
<ul style={{ listStyle: "none", padding: 0, display: "grid", gap: "0.5rem" }}>
|
|
13
|
-
{feed.items.map((item) => (
|
|
14
|
-
<li key={item.id}>
|
|
15
|
-
<button
|
|
16
|
-
style={{ width: "100%", textAlign: "left" }}
|
|
17
|
-
onClick={() => void controller?.push("detail", { id: item.id })}
|
|
18
|
-
>
|
|
19
|
-
{item.title} →
|
|
20
|
-
</button>
|
|
21
|
-
</li>
|
|
22
|
-
))}
|
|
23
|
-
</ul>
|
|
24
|
-
)}
|
|
25
|
-
</section>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { BasePage } from "@finesoft/front";
|
|
2
|
-
|
|
3
|
-
/** Notes:第二个 tab,纯展示(切 tab 保活由 islands 负责)。 */
|
|
4
|
-
export default function NotesView({ page }: { page: BasePage }) {
|
|
5
|
-
return (
|
|
6
|
-
<section>
|
|
7
|
-
<h1 style={{ margin: "0 0 0.25rem" }}>{page.title}</h1>
|
|
8
|
-
<p style={{ color: "#666", margin: "0 0 1rem" }}>{page.description}</p>
|
|
9
|
-
</section>
|
|
10
|
-
);
|
|
11
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { makeExternalUrlAction, makeFlowAction, type Action } from "@finesoft/front";
|
|
2
|
-
|
|
3
|
-
/** 创建指向商品详情的 FlowAction */
|
|
4
|
-
export function productDetailAction(id: string): Action {
|
|
5
|
-
return makeFlowAction(`/products/${id}`);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** 导航 Actions */
|
|
9
|
-
export const NAV_ACTIONS = {
|
|
10
|
-
home: makeFlowAction("/"),
|
|
11
|
-
search: makeFlowAction("/search"),
|
|
12
|
-
about: makeFlowAction("/about"),
|
|
13
|
-
github: makeExternalUrlAction("https://github.com/nicepkg/finesoft"),
|
|
14
|
-
} as const;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type Framework, defineRoutes, int, route } from "@finesoft/front";
|
|
2
|
-
import { AboutController } from "./lib/controllers/about";
|
|
3
|
-
import { HomeController } from "./lib/controllers/home";
|
|
4
|
-
import { ProductDetailController } from "./lib/controllers/product-detail";
|
|
5
|
-
import { SearchController } from "./lib/controllers/search";
|
|
6
|
-
import { authGuard } from "./lib/guards/auth";
|
|
7
|
-
import { seoGuard } from "./lib/guards/seo";
|
|
8
|
-
|
|
9
|
-
export function bootstrap(framework: Framework): void {
|
|
10
|
-
framework.afterLoad(seoGuard);
|
|
11
|
-
|
|
12
|
-
defineRoutes(framework, [
|
|
13
|
-
{ path: "/", intentId: "home", controller: new HomeController() },
|
|
14
|
-
route("/products/:id", {
|
|
15
|
-
intentId: "product-detail",
|
|
16
|
-
controller: new ProductDetailController(),
|
|
17
|
-
params: { id: int() },
|
|
18
|
-
}),
|
|
19
|
-
{
|
|
20
|
-
path: "/search",
|
|
21
|
-
intentId: "search",
|
|
22
|
-
controller: new SearchController(),
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
path: "/about",
|
|
26
|
-
intentId: "about",
|
|
27
|
-
controller: new AboutController(),
|
|
28
|
-
renderMode: "csr",
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
path: "/admin",
|
|
32
|
-
intentId: "home",
|
|
33
|
-
beforeLoad: [authGuard],
|
|
34
|
-
},
|
|
35
|
-
]);
|
|
36
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { AppPage } from "../lib/models/product";
|
|
3
|
-
import About from "../pages/About.svelte";
|
|
4
|
-
import Home from "../pages/Home.svelte";
|
|
5
|
-
import NotFound from "../pages/NotFound.svelte";
|
|
6
|
-
import ProductDetail from "../pages/ProductDetail.svelte";
|
|
7
|
-
import Search from "../pages/Search.svelte";
|
|
8
|
-
|
|
9
|
-
let { page }: { page: AppPage } = $props();
|
|
10
|
-
|
|
11
|
-
const pageComponents: Record<string, any> = {
|
|
12
|
-
home: Home,
|
|
13
|
-
product: ProductDetail,
|
|
14
|
-
search: Search,
|
|
15
|
-
about: About,
|
|
16
|
-
error: NotFound,
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const PageComponent = $derived(pageComponents[page.pageType] ?? NotFound);
|
|
20
|
-
</script>
|
|
21
|
-
|
|
22
|
-
<PageComponent {page} />
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Svelte Context 集成 — Framework 实例的 Svelte context 传递
|
|
3
|
-
*
|
|
4
|
-
* 在根组件(App.svelte)中调用 setFrameworkContext,
|
|
5
|
-
* 子组件通过 getPerform 获取 perform 函数,无需逐层传递 onaction 回调。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Action } from "@finesoft/front";
|
|
9
|
-
import { Framework } from "@finesoft/front";
|
|
10
|
-
import { getContext, setContext } from "svelte";
|
|
11
|
-
|
|
12
|
-
const FRAMEWORK_KEY = "framework";
|
|
13
|
-
|
|
14
|
-
/** 在根组件初始化时设置 Framework 实例到 Svelte context */
|
|
15
|
-
export function setFrameworkContext(framework: Framework): void {
|
|
16
|
-
setContext(FRAMEWORK_KEY, framework);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** 获取 Framework 实例;SSR 或未设置时返回 undefined */
|
|
20
|
-
export function getFramework(): Framework | undefined {
|
|
21
|
-
return getContext<Framework | undefined>(FRAMEWORK_KEY);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 获取 perform 函数,供导航/操作类组件在事件处理器中调用。
|
|
26
|
-
* 在组件初始化阶段(<script> 顶层)调用,不能在事件回调内调用。
|
|
27
|
-
*
|
|
28
|
-
* @returns perform 函数;SSR 或无 context 时返回 undefined
|
|
29
|
-
*/
|
|
30
|
-
export function getPerform(): ((action: Action) => Promise<void>) | undefined {
|
|
31
|
-
const fw = getFramework();
|
|
32
|
-
if (!fw) return undefined;
|
|
33
|
-
return (action: Action) => fw.perform(action);
|
|
34
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { BasePage, Framework } from "@finesoft/front";
|
|
2
|
-
import { render } from "svelte/server";
|
|
3
|
-
import App from "../App.svelte";
|
|
4
|
-
import type { AppPage } from "./models/product";
|
|
5
|
-
|
|
6
|
-
export function renderApp(page: BasePage, framework: Framework) {
|
|
7
|
-
const result = render(App, { props: { page: page as AppPage, framework } });
|
|
8
|
-
return {
|
|
9
|
-
html: result.body ?? "",
|
|
10
|
-
head: `<title>${page.title}</title><meta name="description" content="${
|
|
11
|
-
page.description ?? ""
|
|
12
|
-
}">${result.head ?? ""}`,
|
|
13
|
-
css: "",
|
|
14
|
-
};
|
|
15
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import { defineRoutes, type Framework } from "@finesoft/front";
|
|
2
|
-
import { HomeController } from "./lib/controllers/home";
|
|
3
|
-
|
|
4
|
-
export function bootstrap(framework: Framework): void {
|
|
5
|
-
defineRoutes(framework, [{ path: "/", intentId: "home", controller: new HomeController() }]);
|
|
6
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Svelte Context 集成 — Framework 实例的 Svelte context 传递
|
|
3
|
-
*
|
|
4
|
-
* 在根组件(App.svelte)中调用 setFrameworkContext,
|
|
5
|
-
* 子组件通过 getPerform 获取 perform 函数,无需逐层传递 onaction 回调。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { Action } from "@finesoft/front";
|
|
9
|
-
import { Framework } from "@finesoft/front";
|
|
10
|
-
import { getContext, setContext } from "svelte";
|
|
11
|
-
|
|
12
|
-
const FRAMEWORK_KEY = "framework";
|
|
13
|
-
|
|
14
|
-
/** 在根组件初始化时设置 Framework 实例到 Svelte context */
|
|
15
|
-
export function setFrameworkContext(framework: Framework): void {
|
|
16
|
-
setContext(FRAMEWORK_KEY, framework);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/** 获取 Framework 实例;SSR 或未设置时返回 undefined */
|
|
20
|
-
export function getFramework(): Framework | undefined {
|
|
21
|
-
return getContext<Framework | undefined>(FRAMEWORK_KEY);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 获取 perform 函数,供导航/操作类组件在事件处理器中调用。
|
|
26
|
-
* 在组件初始化阶段(<script> 顶层)调用,不能在事件回调内调用。
|
|
27
|
-
*
|
|
28
|
-
* @returns perform 函数;SSR 或无 context 时返回 undefined
|
|
29
|
-
*/
|
|
30
|
-
export function getPerform(): ((action: Action) => Promise<void>) | undefined {
|
|
31
|
-
const fw = getFramework();
|
|
32
|
-
if (!fw) return undefined;
|
|
33
|
-
return (action: Action) => fw.perform(action);
|
|
34
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { BasePage, Framework } from "@finesoft/front";
|
|
2
|
-
import { render } from "svelte/server";
|
|
3
|
-
import App from "../App.svelte";
|
|
4
|
-
|
|
5
|
-
export function renderApp(page: BasePage, framework: Framework) {
|
|
6
|
-
const result = render(App, { props: { page, framework } });
|
|
7
|
-
return {
|
|
8
|
-
html: result.body ?? "",
|
|
9
|
-
head: `<title>${page.title}</title>${result.head ?? ""}`,
|
|
10
|
-
css: "",
|
|
11
|
-
};
|
|
12
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { makeExternalUrlAction, makeFlowAction, type Action } from "@finesoft/front";
|
|
2
|
-
|
|
3
|
-
/** 创建指向商品详情的 FlowAction */
|
|
4
|
-
export function productDetailAction(id: string): Action {
|
|
5
|
-
return makeFlowAction(`/products/${id}`);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/** 导航 Actions */
|
|
9
|
-
export const NAV_ACTIONS = {
|
|
10
|
-
home: makeFlowAction("/"),
|
|
11
|
-
search: makeFlowAction("/search"),
|
|
12
|
-
about: makeFlowAction("/about"),
|
|
13
|
-
github: makeExternalUrlAction("https://github.com/nicepkg/finesoft"),
|
|
14
|
-
} as const;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { type Framework, defineRoutes, int, route } from "@finesoft/front";
|
|
2
|
-
import { AboutController } from "./lib/controllers/about";
|
|
3
|
-
import { HomeController } from "./lib/controllers/home";
|
|
4
|
-
import { ProductDetailController } from "./lib/controllers/product-detail";
|
|
5
|
-
import { SearchController } from "./lib/controllers/search";
|
|
6
|
-
import { authGuard } from "./lib/guards/auth";
|
|
7
|
-
import { seoGuard } from "./lib/guards/seo";
|
|
8
|
-
|
|
9
|
-
export function bootstrap(framework: Framework): void {
|
|
10
|
-
// Global afterLoad guard — SEO URL normalization
|
|
11
|
-
framework.afterLoad(seoGuard);
|
|
12
|
-
|
|
13
|
-
defineRoutes(framework, [
|
|
14
|
-
// SSR routes (default)
|
|
15
|
-
{ path: "/", intentId: "home", controller: new HomeController() },
|
|
16
|
-
route("/products/:id", {
|
|
17
|
-
intentId: "product-detail",
|
|
18
|
-
controller: new ProductDetailController(),
|
|
19
|
-
params: { id: int() },
|
|
20
|
-
}),
|
|
21
|
-
{
|
|
22
|
-
path: "/search",
|
|
23
|
-
intentId: "search",
|
|
24
|
-
controller: new SearchController(),
|
|
25
|
-
},
|
|
26
|
-
|
|
27
|
-
// CSR-only route
|
|
28
|
-
{
|
|
29
|
-
path: "/about",
|
|
30
|
-
intentId: "about",
|
|
31
|
-
controller: new AboutController(),
|
|
32
|
-
renderMode: "csr",
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
// Guarded route — requires auth cookie
|
|
36
|
-
{
|
|
37
|
-
path: "/admin",
|
|
38
|
-
intentId: "home",
|
|
39
|
-
beforeLoad: [authGuard],
|
|
40
|
-
},
|
|
41
|
-
]);
|
|
42
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { Action } from "@finesoft/front";
|
|
3
|
-
import type { AppPage } from "../lib/models/product";
|
|
4
|
-
import About from "../pages/About.vue";
|
|
5
|
-
import Home from "../pages/Home.vue";
|
|
6
|
-
import NotFound from "../pages/NotFound.vue";
|
|
7
|
-
import ProductDetail from "../pages/ProductDetail.vue";
|
|
8
|
-
import Search from "../pages/Search.vue";
|
|
9
|
-
|
|
10
|
-
const { page, onAction } = defineProps<{
|
|
11
|
-
page: AppPage;
|
|
12
|
-
onAction?: (action: Action) => void;
|
|
13
|
-
}>();
|
|
14
|
-
|
|
15
|
-
const pageComponents: Record<string, unknown> = {
|
|
16
|
-
home: Home,
|
|
17
|
-
product: ProductDetail,
|
|
18
|
-
search: Search,
|
|
19
|
-
about: About,
|
|
20
|
-
error: NotFound,
|
|
21
|
-
};
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<component :is="pageComponents[page.pageType] ?? NotFound" :page="page" :on-action="onAction" />
|
|
26
|
-
</template>
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { Framework, defineNavigation, defineRoutes, leaf, stack, tabs } from "@finesoft/front";
|
|
2
|
-
import { DetailController } from "./lib/controllers/detail";
|
|
3
|
-
import { HomeController } from "./lib/controllers/home";
|
|
4
|
-
import { NotesController } from "./lib/controllers/notes";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 同一份 bootstrap 在浏览器与服务器都会执行:注册 controllers + 路由。
|
|
8
|
-
* 路由让 URL 能 resolve(SSR 首屏 / codec / 扁平回退都需要)。
|
|
9
|
-
*/
|
|
10
|
-
export function bootstrap(framework: Framework): void {
|
|
11
|
-
defineRoutes(framework, [
|
|
12
|
-
{ path: "/", intentId: "home", controller: new HomeController() },
|
|
13
|
-
{ path: "/item/:id", intentId: "detail", controller: new DetailController() },
|
|
14
|
-
{ path: "/notes", intentId: "notes", controller: new NotesController() },
|
|
15
|
-
]);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* 结构化导航:一个 TabView(Home / Notes),每个 tab 是一个 NavigationStack。
|
|
20
|
-
* - Home 栈可 push 到 detail(栈深 +1),返回 pop(栈深 -1)。
|
|
21
|
-
* - 切 tab 保活另一分支的栈深与作用域状态(对标 SwiftUI TabView)。
|
|
22
|
-
*
|
|
23
|
-
* `initial` 用 **URL→树 工厂**(而非静态树):这样深链 / 刷新 `/item/2` 能重建出
|
|
24
|
-
* 「Home 栈压着 detail」的结构,active-leaf codec 也把它回写成 `/item/2`,刷新不再跳回 `/`。
|
|
25
|
-
* (静态树会忽略 URL、永远从 home 起,刷新被改回 `/`。)
|
|
26
|
-
*/
|
|
27
|
-
export const navigation = defineNavigation({
|
|
28
|
-
initial: (url) => {
|
|
29
|
-
const path = url.split("?")[0].split("#")[0];
|
|
30
|
-
const detail = /^\/item\/(.+)$/.exec(path);
|
|
31
|
-
const home = detail
|
|
32
|
-
? stack([leaf("home"), leaf("detail", { id: detail[1] })])
|
|
33
|
-
: stack(leaf("home"));
|
|
34
|
-
return tabs({
|
|
35
|
-
active: path === "/notes" ? "notes" : "home",
|
|
36
|
-
branches: { home, notes: stack(leaf("notes")) },
|
|
37
|
-
});
|
|
38
|
-
},
|
|
39
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { BasePage } from "@finesoft/front";
|
|
3
|
-
|
|
4
|
-
defineProps<{ page: BasePage }>();
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<section>
|
|
9
|
-
<h1 style="margin: 0 0 0.25rem">{{ page.title }}</h1>
|
|
10
|
-
<p style="color: #666; margin: 0 0 1rem">{{ page.description }}</p>
|
|
11
|
-
|
|
12
|
-
<!--
|
|
13
|
-
零样板保活:标一个 data-restore-root,里面的裸 <input> 即自动:
|
|
14
|
-
- in-session:push 走、pop 回来值还在(islands 保活,实例没销毁)
|
|
15
|
-
- 重载:sessionStorage 回填(domRestore),合成事件驱动可能的受控绑定
|
|
16
|
-
对比重构前:需手写 entryKey + watch + getScoped/setScoped —— 现在一行不写。
|
|
17
|
-
-->
|
|
18
|
-
<div data-restore-root>
|
|
19
|
-
<label style="display: block; margin-top: 1rem">
|
|
20
|
-
Draft note for this screen:
|
|
21
|
-
<input
|
|
22
|
-
name="note"
|
|
23
|
-
placeholder="kept while alive; restored on reload"
|
|
24
|
-
style="width: 100%"
|
|
25
|
-
/>
|
|
26
|
-
</label>
|
|
27
|
-
</div>
|
|
28
|
-
</section>
|
|
29
|
-
</template>
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from "vue";
|
|
3
|
-
import type { BasePage } from "@finesoft/front";
|
|
4
|
-
import type { FeedPage } from "../lib/controllers/home";
|
|
5
|
-
import type { AppController } from "../main";
|
|
6
|
-
|
|
7
|
-
const { page, controller } = defineProps<{ page: BasePage; controller?: AppController }>();
|
|
8
|
-
const feed = computed(() => (page.pageType === "home" ? (page as FeedPage) : null));
|
|
9
|
-
</script>
|
|
10
|
-
|
|
11
|
-
<template>
|
|
12
|
-
<section>
|
|
13
|
-
<h1 style="margin: 0 0 0.25rem">{{ page.title }}</h1>
|
|
14
|
-
<p style="color: #666; margin: 0 0 1rem">{{ page.description }}</p>
|
|
15
|
-
<ul v-if="feed" style="list-style: none; padding: 0; display: grid; gap: 0.5rem">
|
|
16
|
-
<li v-for="item in feed.items" :key="item.id">
|
|
17
|
-
<button
|
|
18
|
-
style="width: 100%; text-align: left"
|
|
19
|
-
@click="controller?.push('detail', { id: item.id })"
|
|
20
|
-
>
|
|
21
|
-
{{ item.title }} →
|
|
22
|
-
</button>
|
|
23
|
-
</li>
|
|
24
|
-
</ul>
|
|
25
|
-
</section>
|
|
26
|
-
</template>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type { BasePage } from "@finesoft/front";
|
|
3
|
-
|
|
4
|
-
defineProps<{ page: BasePage }>();
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<section>
|
|
9
|
-
<h1 style="margin: 0 0 0.25rem">{{ page.title }}</h1>
|
|
10
|
-
<p style="color: #666; margin: 0 0 1rem">{{ page.description }}</p>
|
|
11
|
-
</section>
|
|
12
|
-
</template>
|