create-yoya-ui 0.4.1 → 0.6.1

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.
Files changed (54) hide show
  1. package/package.json +1 -1
  2. package/templates/admin/RULES.md +22 -9
  3. package/templates/admin/package.json +1 -1
  4. package/templates/admin/src/api/domain.api.d.ts +1 -1
  5. package/templates/admin/src/api/domain.api.js +1 -1
  6. package/templates/admin/src/api/fetch.api.d.ts +1 -1
  7. package/templates/admin/src/features/dashboard/overview/pages/dashboard-overview-page.js +2 -2
  8. package/templates/admin/src/features/system/dicts/api/dict.mgr.d.ts +1 -1
  9. package/templates/admin/src/features/system/dicts/api/dict.mgr.js +1 -1
  10. package/templates/admin/src/features/system/dicts/api/dict.state.d.ts +14 -11
  11. package/templates/admin/src/features/system/dicts/api/dict.state.js +35 -70
  12. package/templates/admin/src/features/system/dicts/api/dict.views.d.ts +36 -23
  13. package/templates/admin/src/features/system/dicts/api/dict.views.js +36 -14
  14. package/templates/admin/src/features/system/dicts/components/dict-editor-dialog.js +53 -73
  15. package/templates/admin/src/features/system/dicts/components/dict-item-form-dialog.js +9 -6
  16. package/templates/admin/src/features/system/dicts/pages/dict-list-page.js +57 -59
  17. package/templates/admin/src/features/system/members/api/member.mgr.d.ts +1 -1
  18. package/templates/admin/src/features/system/members/api/member.mgr.js +1 -1
  19. package/templates/admin/src/features/system/members/api/member.req.d.ts +1 -1
  20. package/templates/admin/src/features/system/members/api/member.req.js +1 -1
  21. package/templates/admin/src/features/system/members/api/member.state.d.ts +10 -8
  22. package/templates/admin/src/features/system/members/api/member.state.js +24 -55
  23. package/templates/admin/src/features/system/members/api/member.views.d.ts +19 -5
  24. package/templates/admin/src/features/system/members/api/member.views.js +22 -8
  25. package/templates/admin/src/features/system/members/components/member-form-dialog.js +10 -6
  26. package/templates/admin/src/features/system/members/components/member-table.js +44 -54
  27. package/templates/admin/src/features/system/members/pages/member-list-page.js +19 -14
  28. package/templates/admin/src/features/system/permissions/api/permission.mgr.d.ts +1 -1
  29. package/templates/admin/src/features/system/permissions/api/permission.mgr.js +1 -1
  30. package/templates/admin/src/features/system/permissions/api/permission.state.d.ts +5 -3
  31. package/templates/admin/src/features/system/permissions/api/permission.state.js +4 -18
  32. package/templates/admin/src/features/system/permissions/api/permission.views.js +2 -1
  33. package/templates/admin/src/features/system/permissions/components/permission-form-dialog.js +1 -1
  34. package/templates/admin/src/features/system/permissions/pages/permission-list-page.js +9 -6
  35. package/templates/admin/src/features/system/roles/api/role.mgr.d.ts +1 -1
  36. package/templates/admin/src/features/system/roles/api/role.mgr.js +1 -1
  37. package/templates/admin/src/features/system/roles/api/role.state.d.ts +10 -8
  38. package/templates/admin/src/features/system/roles/api/role.state.js +22 -55
  39. package/templates/admin/src/features/system/roles/api/role.views.d.ts +24 -15
  40. package/templates/admin/src/features/system/roles/api/role.views.js +22 -9
  41. package/templates/admin/src/features/system/roles/components/role-form-dialog.js +11 -7
  42. package/templates/admin/src/features/system/roles/components/role-table.js +35 -47
  43. package/templates/admin/src/features/system/roles/pages/role-list-page.js +18 -14
  44. package/templates/admin/src/shared/state.rows.js +17 -0
  45. package/templates/admin/src/shell/api/shell.req.js +1 -1
  46. package/templates/admin/src/shell/api/shell.state.js +23 -55
  47. package/templates/admin/src/shell/components/admin-shell.js +4 -15
  48. package/templates/admin/src/shell/components/app-navbar.js +48 -56
  49. package/templates/admin/src/shell/components/app-sidebar.js +20 -31
  50. package/templates/admin/src/shell/components/sidebar-menu.js +1 -1
  51. package/templates/basic/package.json +1 -1
  52. package/templates/ssr/README.md +1 -1
  53. package/templates/ssr/package.json +1 -1
  54. package/templates/ssr/src/server.mjs +3 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-yoya-ui",
3
- "version": "0.4.1",
3
+ "version": "0.6.1",
4
4
  "description": "Scaffold a new yoya-ui project with Vite: basic template (SPA) or SSR template (renderPage + hydrateOrMount).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,6 +24,7 @@ src/
24
24
  - 页面:`<名字>-page.js`(`member-list-page.js`、`todo-approval-page.js`)
25
25
  - 组件:PascalCase(`AdminShell`、`MemberTable`)
26
26
  - 共享 UI 类别文件:`ui.<类别>.js`(`ui.buttons.js`、`ui.pages.js`)
27
+ - 共享纯状态工具:`state.<类别>.js`(`state.rows.js` 的行合并与字段读取)
27
28
 
28
29
  ## 3. api 分层
29
30
 
@@ -31,8 +32,8 @@ src/
31
32
 
32
33
  - `mgr.js`:本域管理请求命令(增删改查等)
33
34
  - `req.js`:对外能力入口(其他模块 / 共享组件调用本域时使用)
34
- - `views.js`:领域结果结构(纯数据类),由命令的 `toItem / toDetail` 映射
35
- - `state.js`:状态类,持有数据与筛选,`subscribe(listener)` 通知视图更新
35
+ - `views.js`:领域结果结构(纯数据类),由命令的 `toItem / toDetail` 映射;**字段按热度分**——行内展示且会变的字段用 `ref` 句柄,key 与不参与原地刷新的字段保持普通值,并提供 `apply(row)` 原地合并
36
+ - `state.js`:状态类,持有数据与筛选;**要驱动视图的字段用 `ref` 持有**(写入即完成通知),`subscribe(listener)` 只留给非视图副作用(埋点 / 持久化)
36
37
  - `mock.js`:演示用内存 mock(接入真实后端后删除)
37
38
 
38
39
  判据:有管理动作用 `mgr.js`;只提供查询给外部用 `req.js`;两者可并存(如 `member.mgr.js` + `member.req.js`)。
@@ -56,6 +57,7 @@ export default {
56
57
  ## 5. 共享组件
57
58
 
58
59
  - **通用 UI**(无业务语义):放 `shared/ui.<类别>.js`
60
+ - **纯状态工具**(无 UI):放 `shared/state.<类别>.js`,如 `state.rows.js` 的 `mergeRowsByKey()` / `fieldValue()`
59
61
  - **业务共享组件**(带业务语义且多模块使用):放 `shared/<组件>/` 自包含子目录(组件 + 状态 + 文案),对外只暴露工厂
60
62
  - **数据获取走 req.js**:共享组件需要业务数据时调用所属域 `api/<域>.req.js` 的公开能力(如 `MemberReq.QueryAvailable().submit()`),不自造数据请求、不注入数据源
61
63
  - **不深层 import**:只允许跨模块引用 `req.js`,不 import 业务域的 pages / components / state 内部实现
@@ -67,17 +69,21 @@ export default {
67
69
  ## 6. 外壳与导航状态
68
70
 
69
71
  - `ShellState` 是导航状态(当前模块 / 当前路径)的**唯一事实源**,持有 router,暴露 `switchModule(module)` / `navigate(path)`
70
- - 路由订阅驱动同步:`router.subscribe → state.syncFromPath(path)`,状态变化时通知订阅者
71
- - 顶栏 / 侧栏从状态**派生高亮**,组件不持有自己的激活状态
72
- - `AdminShell` 只做装配:布局组装 + 状态接线,不直接操作 router
73
- - 组件统一写法:节点在 `render()` 内声明式构建,内部引用(如 sidebar 实例、items Map)只用于动态更新
74
- - 前进 / 后退、同模块内切换路由的高亮同步由状态管道自动完成,不需要手动调用
72
+ - 导航字段(`menus` / `activeModuleKey` / `activePath`)是 `ref`:`router.subscribe → state.syncFromPath(path)` 只把路径写回信号
73
+ - 顶栏 / 侧栏从状态**派生高亮**:菜单区先 `rebuildable()` 再读信号(模块 / 路径变化自动重建),组件不持有自己的激活状态
74
+ - `AdminShell` 只做装配:布局组装,不直接操作 router,也不需要订阅回调转发
75
+ - 组件统一写法:节点在 `render()` 内声明式构建,不保留 `applyState()` 这类手动同步入口;没有额外行为要定义(无内部状态、无对外命令方法)的组件直接返回 ViewNode 即可,不必包一层 `render()`
76
+ - 组件需要对外命令方法时用 `vNode((api) => 视图)`:命令收到 `api` 上(`api.reload = () => { …; return api }`),工厂挂到返回的节点并校验重名;自带边界写 `api.whenFailed`,其余节点级能力(`mountable()` / `rebuildable()`)链在节点上
75
77
 
76
78
  ## 7. 页面规则
77
79
 
78
80
  - **简单 / 占位页**:只放 `pages/<页面>-page.js`,可复用 `shared/ui.pages.js` 的 `PlaceholderPage`
79
81
  - **有数据交互的页面**:展开完整模块(api / components / pages / utils)
80
- - 页面只做编排:组合子组件、绑定事件、调用 state 动作,不写请求逻辑
82
+ - **页面只做编排**:组合子组件、绑定事件、调用 state 动作,不写请求逻辑
83
+ - **状态到位即刷新**:表格 / 文本绑句柄(`rows: state.items`、`vText(handle)`),动作里写 ref 就完成更新;不要写 `state.subscribe(() => table.refresh())`
84
+ - **列表按 key 对账**:表格 `tbody.keyed(rows, (row) => row.id, buildRow)`,行模型刷新走 `mergeRowsByKey()`(复用实例 → 不重建行),行内热字段句柄只刷自己那一格
85
+ - **命令式组件由动作同步**:`vPagination` / `vTree` 这类逐次调用的组件不是信号感知的,数据到位后在动作里 `pagination.update({...})` / `tree.nodes(...)` 同步一次
86
+ - **行模型传入弹窗要读值**:弹窗回填用 `fieldValue(row.name)`,它同时吃句柄(行模型)与普通值(裸行)
81
87
 
82
88
  ## 8. mock 与数据接入
83
89
 
@@ -108,7 +114,14 @@ export default {
108
114
 
109
115
  ## 12. 权限接入
110
116
 
111
- - **会话与权限**:会话请求命令并入 `shell.req.js`(`ShellReq.Me()`),`shell/api/auth.mock.js` 提供 `/auth/me` 演示数据;会话状态融合进 `ShellState`(`user()` / `roles()` / `permissions()`),`loadSession()` 内 `installAccess` 注入全局。
117
+ - **会话与权限**:会话请求命令并入 `shell.req.js`(`ShellReq.Me()`),`shell/api/auth.mock.js` 提供 `/auth/me` 演示数据;会话状态融合进 `ShellState`(普通字段 `user` / `roles` / `permissions`,不参与渲染),`loadSession()` 内 `installAccess` 注入全局。
112
118
  - **菜单 / 路由**:`shell.mock.js` 的每条路由声明 `permCode`(与权限管理页的 code 对齐),`ShellState.load()` 按 `currentAccess().canRead(permCode)` 过滤菜单;无读的路由不注册,直达 URL 落到未找到页。
113
119
  - **按钮 / 操作**:直接在按钮上声明 `.access('system:member:create')` 等裸码,无写权限自动禁用、无读自动隐藏(见 member-toolbar / member-table)。
114
120
  - **体验层**:前端只做显隐 / 禁用,真正的拦截必须由后端按权限码校验。
121
+
122
+ ## 13. 节点写法(易错点)
123
+
124
+ - **没有节点级 `text()`**:追加文本用 `child(content)`;反复替换同一处文本留一个 `vText()` 句柄用 `textContent(next)`;组件自带的 `text()` 与 SVG `<text>` 的 `text()` 不受影响
125
+ - **非必要不提前建节点**:只有需要组件句柄(`refresh()` / `update()` / `open()` 等对外命令方法)时才在 `render()` 外先建再 `child()` 挂载;纯结构节点在 `render()` 里就地组合,不要先存成中间变量再挂回去
126
+ - **句柄进组件 props 要包节点**:`vTd(vText(handle))`、`cell.child(handle)` 建立绑定;`vTd(handle)` 会被当成无效 setup 丢掉
127
+ - **值位置直接接句柄**:`attr('data-count', count)`、`vText(count)`;`count.value` 是快照,写完不再更新
@@ -10,7 +10,7 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
- "@yoyaflow/yoya-ui": "^0.4.0"
13
+ "@yoyaflow/yoya-ui": "^0.6.1"
14
14
  },
15
15
  "devDependencies": {
16
16
  "typescript": "^7.0.2",
@@ -1,5 +1,5 @@
1
1
  /** 领域请求入口类型声明(对应 domain.api.js)。 */
2
- import type { RequestCommand } from '@yoyaflow/yoya-ui';
2
+ import type { RequestCommand } from '@yoyaflow/yoya-ui/api';
3
3
 
4
4
  export function mockRequest(
5
5
  method: string,
@@ -1,5 +1,5 @@
1
1
  // 领域请求入口:统一 Result 解析 + mock / 真实传输切换。
2
- import { configureRequest, Result } from '@yoyaflow/yoya-ui';
2
+ import { configureRequest, Result } from '@yoyaflow/yoya-ui/api';
3
3
  import { fetchSubmit } from './fetch.api.js';
4
4
 
5
5
  const mockHandlers = new Map();
@@ -1,4 +1,4 @@
1
1
  /** 原始传输层类型声明(对应 fetch.api.js)。 */
2
- import type { RequestCommand } from '@yoyaflow/yoya-ui';
2
+ import type { RequestCommand } from '@yoyaflow/yoya-ui/api';
3
3
 
4
4
  export function fetchSubmit(req: RequestCommand): Promise<unknown>;
@@ -144,7 +144,7 @@ export function DashboardOverviewPage() {
144
144
  color: 'var(--yoya-color-text-muted, #64748b)',
145
145
  fontSize: '12px'
146
146
  });
147
- text.text('CPU 使用率');
147
+ text.child('CPU 使用率');
148
148
  });
149
149
  });
150
150
  row.div((box) => {
@@ -155,7 +155,7 @@ export function DashboardOverviewPage() {
155
155
  color: 'var(--yoya-color-text-muted, #64748b)',
156
156
  fontSize: '12px'
157
157
  });
158
- text.text('内存使用率');
158
+ text.child('内存使用率');
159
159
  });
160
160
  });
161
161
  });
@@ -1,5 +1,5 @@
1
1
  /** 字典管理请求命令类型声明(对应 dict.mgr.js)。 */
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Dicts, { DictStatus } from './dict.views.js';
4
4
 
5
5
  type DictTypeInstance = InstanceType<typeof Dicts.DictTypeItem>;
@@ -1,5 +1,5 @@
1
1
  // 字典管理请求命令:字典类型与字典项。
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Dicts from './dict.views.js';
4
4
 
5
5
  class QueryTypes extends RequestBase {
@@ -1,4 +1,6 @@
1
1
  /** 字典页状态类类型声明(对应 dict.state.js)。 */
2
+ import type { SignalHandle } from '@yoyaflow/yoya-ui';
3
+ import type { Result } from '@yoyaflow/yoya-ui/api';
2
4
  import Dicts from './dict.views.js';
3
5
 
4
6
  type DictTypeInstance = InstanceType<typeof Dicts.DictTypeItem>;
@@ -6,19 +8,20 @@ type DictItemInstance = InstanceType<typeof Dicts.DictItem>;
6
8
 
7
9
  export default class DictsPageState {
8
10
  constructor();
9
- types(): DictTypeInstance[];
10
- selectedTypeId(): number | null;
11
- selectedType(): DictTypeInstance | null;
12
- items(): DictItemInstance[];
13
- page(): number;
14
- pageSize(): number;
15
- total(): number;
11
+ /** 视图字段:表格 / 弹窗直接绑句柄,写入即更新 */
12
+ types: SignalHandle<DictTypeInstance[]>;
13
+ items: SignalHandle<DictItemInstance[]>;
14
+ selectedTypeId: SignalHandle<number | null>;
15
+ page: SignalHandle<number>;
16
+ pageSize: SignalHandle<number>;
17
+ total: SignalHandle<number>;
18
+ /** 派生值:当前选中的字典类型 */
19
+ selectedType: SignalHandle<DictTypeInstance | null>;
16
20
  setPage(value: number): void;
17
21
  setPageSize(value: number): void;
18
- subscribe(listener: () => void): () => void;
19
- loadTypes(): Promise<unknown>;
20
- selectType(id: number): void;
21
- loadItems(): Promise<void>;
22
+ loadTypes(): Promise<Result<DictTypeInstance[]>>;
23
+ selectType(id: number | null): Promise<this>;
24
+ loadItems(): Promise<DictItemInstance[]>;
22
25
  addType(payload: Record<string, unknown>): Promise<Record<string, unknown>>;
23
26
  editType(id: number, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
24
27
  removeType(id: number): Promise<void>;
@@ -1,92 +1,64 @@
1
- // 字典页状态类:字典类型 + 当前类型下的字典项。
1
+ // 字典页状态类:字典类型 + 当前类型下的字典值。视图字段都用 ref 持有,
2
+ // 动作写句柄即完成通知——表格绑句柄,页面与弹窗都不需要手动重建。
3
+ import { computed, ref } from '@yoyaflow/yoya-ui';
4
+ import { mergeRowsByKey } from '../../../../shared/state.rows.js';
2
5
  import DictMgr from './dict.mgr.js';
3
6
 
4
7
  export default class DictsPageState {
5
8
  constructor() {
6
- this._types = [];
7
- this._selectedTypeId = null;
8
- this._items = [];
9
- this._page = 1;
10
- this._pageSize = 10;
11
- this._total = 0;
12
- this._listeners = new Set();
13
- }
14
-
15
- types() {
16
- return this._types;
17
- }
18
-
19
- selectedTypeId() {
20
- return this._selectedTypeId;
21
- }
22
-
23
- selectedType() {
24
- return this._types.find((type) => type.id === this._selectedTypeId) ?? null;
25
- }
26
-
27
- items() {
28
- return this._items;
29
- }
30
-
31
- page() {
32
- return this._page;
33
- }
34
-
35
- pageSize() {
36
- return this._pageSize;
37
- }
38
-
39
- total() {
40
- return this._total;
9
+ this.types = ref([]);
10
+ this.items = ref([]);
11
+ this.selectedTypeId = ref(null);
12
+ this.page = ref(1);
13
+ this.pageSize = ref(10);
14
+ this.total = ref(0);
15
+ // 派生值:当前选中的字典类型(多信号组合用 computed)
16
+ this.selectedType = computed(
17
+ () => this.types.value.find((type) => type.id === this.selectedTypeId.value) ?? null
18
+ );
41
19
  }
42
20
 
43
21
  setPage(value) {
44
- this._page = Math.max(1, Number(value) || 1);
22
+ this.page.value = Math.max(1, Number(value) || 1);
45
23
  }
46
24
 
47
25
  setPageSize(value) {
48
- this._pageSize = Math.max(1, Number(value) || 10);
49
- }
50
-
51
- subscribe(listener) {
52
- this._listeners.add(listener);
53
- return () => this._listeners.delete(listener);
26
+ this.pageSize.value = Math.max(1, Number(value) || 10);
54
27
  }
55
28
 
56
29
  async loadTypes() {
57
30
  const result = await DictMgr.QueryTypes({
58
- page: this._page,
59
- pageSize: this._pageSize
31
+ page: this.page.value,
32
+ pageSize: this.pageSize.value
60
33
  }).submit();
61
- this._types = result.data;
62
- this._total = result.total;
63
- if (!this._selectedTypeId && this._types.length > 0) {
64
- this._selectedTypeId = this._types[0].id;
34
+ this.types.value = mergeRowsByKey(this.types.peek(), result.data);
35
+ this.total.value = result.total;
36
+ if (!this.selectedTypeId.value && this.types.peek().length > 0) {
37
+ this.selectedTypeId.value = this.types.peek()[0].id;
65
38
  }
66
39
  await this.loadItems();
67
- this._emit();
68
40
  return result;
69
41
  }
70
42
 
71
43
  async selectType(id) {
72
- this._selectedTypeId = id;
44
+ this.selectedTypeId.value = id;
73
45
  await this.loadItems();
74
- this._emit();
75
46
  return this;
76
47
  }
77
48
 
78
49
  async loadItems() {
79
- if (this._selectedTypeId === null) {
80
- this._items = [];
81
- return;
50
+ if (this.selectedTypeId.value === null) {
51
+ this.items.value = [];
52
+ return [];
82
53
  }
83
- const result = await DictMgr.QueryItems({ typeId: this._selectedTypeId }).submit();
84
- this._items = result.data;
54
+ const result = await DictMgr.QueryItems({ typeId: this.selectedTypeId.value }).submit();
55
+ this.items.value = mergeRowsByKey(this.items.peek(), result.data);
56
+ return this.items.value;
85
57
  }
86
58
 
87
59
  async addType(payload) {
88
60
  await DictMgr.CreateType(payload).submit();
89
- this._page = 1;
61
+ this.page.value = 1;
90
62
  await this.loadTypes();
91
63
  return payload;
92
64
  }
@@ -99,36 +71,29 @@ export default class DictsPageState {
99
71
 
100
72
  async removeType(id) {
101
73
  await DictMgr.RemoveType({ id }).submit();
102
- if (this._selectedTypeId === id) {
103
- this._selectedTypeId = null;
74
+ if (this.selectedTypeId.value === id) {
75
+ this.selectedTypeId.value = null;
104
76
  }
105
- if (this._types.length === 1 && this._page > 1) {
106
- this._page -= 1;
77
+ if (this.types.peek().length === 1 && this.page.value > 1) {
78
+ this.page.value -= 1;
107
79
  }
108
80
  await this.loadTypes();
109
81
  }
110
82
 
111
83
  async addItem(payload) {
112
- await DictMgr.CreateItem({ typeId: this._selectedTypeId, ...payload }).submit();
84
+ await DictMgr.CreateItem({ typeId: this.selectedTypeId.value, ...payload }).submit();
113
85
  await this.loadItems();
114
- this._emit();
115
86
  return payload;
116
87
  }
117
88
 
118
89
  async editItem(id, payload) {
119
90
  await DictMgr.UpdateItem({ id, ...payload }).submit();
120
91
  await this.loadItems();
121
- this._emit();
122
92
  return payload;
123
93
  }
124
94
 
125
95
  async removeItem(id) {
126
96
  await DictMgr.RemoveItem({ id }).submit();
127
97
  await this.loadItems();
128
- this._emit();
129
- }
130
-
131
- _emit() {
132
- this._listeners.forEach((listener) => listener());
133
98
  }
134
99
  }
@@ -1,36 +1,49 @@
1
1
  /** 字典结果结构类型声明(对应 dict.views.js)。 */
2
+ import type { SignalHandle } from '@yoyaflow/yoya-ui';
3
+
4
+ /** 字典状态:与 utils/options.js 的 statusOptions 对齐 */
2
5
  export type DictStatus = 'active' | 'disabled';
3
6
 
7
+ export type DictTypeRow = {
8
+ id: number;
9
+ name?: string;
10
+ code?: string;
11
+ status?: DictStatus;
12
+ remark?: string;
13
+ };
14
+
15
+ export type DictItemRow = {
16
+ id: number;
17
+ typeId?: number | null;
18
+ label?: string;
19
+ value?: string;
20
+ sort?: number;
21
+ status?: DictStatus;
22
+ };
23
+
4
24
  export class DictTypeItem {
25
+ /** key:普通值,不能是句柄 */
5
26
  id: number;
6
- name: string;
7
- code: string;
8
- status: DictStatus;
9
- remark: string;
10
- constructor(init?: {
11
- id: number;
12
- name?: string;
13
- code?: string;
14
- status?: DictStatus;
15
- remark?: string;
16
- });
27
+ /** 行内热字段:句柄写入即刷新对应单元格 */
28
+ name: SignalHandle<string>;
29
+ code: SignalHandle<string>;
30
+ status: SignalHandle<string>;
31
+ remark: SignalHandle<string>;
32
+ constructor(row?: DictTypeRow);
33
+ apply(row: DictTypeRow): this;
17
34
  }
18
35
 
19
36
  export class DictItem {
37
+ /** key:普通值,不能是句柄 */
20
38
  id: number;
39
+ /** 关系字段:不参与原地刷新 */
21
40
  typeId: number | null;
22
- label: string;
23
- value: string;
24
- sort: number;
25
- status: DictStatus;
26
- constructor(init?: {
27
- id: number;
28
- typeId?: number | null;
29
- label?: string;
30
- value?: string;
31
- sort?: number;
32
- status?: DictStatus;
33
- });
41
+ label: SignalHandle<string>;
42
+ value: SignalHandle<string>;
43
+ sort: SignalHandle<number>;
44
+ status: SignalHandle<string>;
45
+ constructor(row?: DictItemRow);
46
+ apply(row: DictItemRow): this;
34
47
  }
35
48
 
36
49
  declare const Dicts: {
@@ -1,22 +1,44 @@
1
- // 字典结果结构(纯数据)。
1
+ // 字典结果结构(纯数据):行内展示且会原地变化的字段用句柄,
2
+ // key 与不参与原地刷新的字段(如关系字段)保持普通值。
3
+ import { ref } from '@yoyaflow/yoya-ui';
4
+ import { fieldValue } from '../../../../shared/state.rows.js';
5
+
2
6
  class DictTypeItem {
3
- constructor({ id, name = '', code = '', status = 'active', remark = '' } = {}) {
4
- this.id = id;
5
- this.name = name;
6
- this.code = code;
7
- this.status = status;
8
- this.remark = remark;
7
+ constructor(row = {}) {
8
+ this.id = row.id; // key:普通值,不能是句柄
9
+ this.name = ref(fieldValue(row.name) ?? '');
10
+ this.code = ref(fieldValue(row.code) ?? '');
11
+ this.status = ref(fieldValue(row.status) ?? 'active');
12
+ this.remark = ref(fieldValue(row.remark) ?? '');
13
+ }
14
+
15
+ /** 合并而非重建:实例身份不变,keyed 复用行节点,只刷句柄绑定的位置。 */
16
+ apply(row) {
17
+ this.name.value = fieldValue(row.name) ?? '';
18
+ this.code.value = fieldValue(row.code) ?? '';
19
+ this.status.value = fieldValue(row.status) ?? 'active';
20
+ this.remark.value = fieldValue(row.remark) ?? '';
21
+ return this;
9
22
  }
10
23
  }
11
24
 
12
25
  class DictItem {
13
- constructor({ id, typeId = null, label = '', value = '', sort = 0, status = 'active' } = {}) {
14
- this.id = id;
15
- this.typeId = typeId;
16
- this.label = label;
17
- this.value = value;
18
- this.sort = sort;
19
- this.status = status;
26
+ constructor(row = {}) {
27
+ this.id = row.id; // key:普通值,不能是句柄
28
+ this.typeId = fieldValue(row.typeId) ?? null; // 关系字段:不参与原地刷新
29
+ this.label = ref(fieldValue(row.label) ?? '');
30
+ this.value = ref(fieldValue(row.value) ?? '');
31
+ this.sort = ref(fieldValue(row.sort) ?? 0);
32
+ this.status = ref(fieldValue(row.status) ?? 'active');
33
+ }
34
+
35
+ apply(row) {
36
+ this.typeId = fieldValue(row.typeId) ?? this.typeId;
37
+ this.label.value = fieldValue(row.label) ?? '';
38
+ this.value.value = fieldValue(row.value) ?? '';
39
+ this.sort.value = fieldValue(row.sort) ?? 0;
40
+ this.status.value = fieldValue(row.status) ?? 'active';
41
+ return this;
20
42
  }
21
43
  }
22
44