create-yoya-ui 0.4.0 → 0.6.0

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 (56) hide show
  1. package/package.json +3 -2
  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
  55. package/templates/admin/node_modules/.vite/deps/_metadata.json +0 -8
  56. package/templates/admin/node_modules/.vite/deps/package.json +0 -3
@@ -1,16 +1,30 @@
1
- // 领域结果结构(纯数据):类不单独 export,统一由默认导出命名空间提供。
1
+ // 领域结果结构(纯数据):字段按热度分——行内会展示、会原地变化的用句柄,
2
+ // key 与不参与原地刷新的字段保持普通值。类不单独 export,由默认导出命名空间提供。
3
+ import { ref } from '@yoyaflow/yoya-ui';
4
+ import { fieldValue } from '../../../../shared/state.rows.js';
5
+
2
6
  class ListItem {
3
- constructor({ id, name, email, role, status }) {
4
- this.id = id;
5
- this.name = name;
6
- this.email = email;
7
- this.role = role;
8
- this.status = status;
7
+ constructor(row = {}) {
8
+ this.id = row.id; // key:普通值,不能是句柄
9
+ this.name = ref(fieldValue(row.name) ?? ''); // 热字段:句柄 → 只刷那一格
10
+ this.email = ref(fieldValue(row.email) ?? '');
11
+ this.role = ref(fieldValue(row.role) ?? 'viewer');
12
+ this.status = ref(fieldValue(row.status) ?? 'active');
13
+ }
14
+
15
+ /** 合并而非重建:实例身份不变,keyed 复用行节点,只刷句柄绑定的位置。 */
16
+ apply(row) {
17
+ this.name.value = fieldValue(row.name) ?? '';
18
+ this.email.value = fieldValue(row.email) ?? '';
19
+ this.role.value = fieldValue(row.role) ?? 'viewer';
20
+ this.status.value = fieldValue(row.status) ?? 'active';
21
+ return this;
9
22
  }
10
23
  }
11
24
 
25
+ // 详情结构不参与列表对账,按普通值持有即可。
12
26
  class Detail {
13
- constructor({ id, name, email, role, status, remark = '' }) {
27
+ constructor({ id, name, email, role, status, remark = '' } = {}) {
14
28
  this.id = id;
15
29
  this.name = name;
16
30
  this.email = email;
@@ -1,4 +1,5 @@
1
1
  import { vDialog } from '@yoyaflow/yoya-ui';
2
+ import { fieldValue } from '../../../../shared/state.rows.js';
2
3
  import { roleOptions, statusOptions } from '../utils/options.js';
3
4
 
4
5
  const rowStyle = {
@@ -14,6 +15,11 @@ export function MemberFormDialog({ onSubmit }) {
14
15
  let editingId = null;
15
16
 
16
17
  function buildFormFields(form, member) {
18
+ const name = fieldValue(member?.name) ?? '';
19
+ const email = fieldValue(member?.email) ?? '';
20
+ const role = fieldValue(member?.role) ?? 'viewer';
21
+ const status = fieldValue(member?.status) ?? 'active';
22
+
17
23
  form.styles({
18
24
  border: '1px solid var(--yoya-color-border, #d8dee8)',
19
25
  borderRadius: '8px',
@@ -25,19 +31,17 @@ export function MemberFormDialog({ onSubmit }) {
25
31
  form.vFormItem((item) => {
26
32
  item.styles(rowStyle);
27
33
  item.label('姓名').name('name').required({ message: '请填写姓名' });
28
- item.control((editor) => editor.vInput({ name: 'name', value: member?.name ?? '' }));
34
+ item.control((editor) => editor.vInput({ name: 'name', value: name }));
29
35
  });
30
36
  form.vFormItem((item) => {
31
37
  item.styles(rowStyle);
32
38
  item.label('邮箱').name('email').required({ message: '请填写邮箱' });
33
- item.control((editor) => editor.vInput({ name: 'email', value: member?.email ?? '' }));
39
+ item.control((editor) => editor.vInput({ name: 'email', value: email }));
34
40
  });
35
41
  form.vFormItem((item) => {
36
42
  item.styles(rowStyle);
37
43
  item.label('角色').name('role');
38
- item.control((editor) =>
39
- editor.vSelect({ name: 'role', options: roleOptions, value: member?.role ?? 'viewer' })
40
- );
44
+ item.control((editor) => editor.vSelect({ name: 'role', options: roleOptions, value: role }));
41
45
  });
42
46
  form.vFormItem((item) => {
43
47
  item.styles(rowStyle);
@@ -46,7 +50,7 @@ export function MemberFormDialog({ onSubmit }) {
46
50
  editor.vSelect({
47
51
  name: 'status',
48
52
  options: statusOptions,
49
- value: member?.status ?? 'active'
53
+ value: status
50
54
  })
51
55
  );
52
56
  });
@@ -1,64 +1,54 @@
1
- import { vTable } from '@yoyaflow/yoya-ui';
1
+ import { computed, vTable, vText, vTr } from '@yoyaflow/yoya-ui';
2
2
  import { RowActionButton } from '../../../../shared/ui.buttons.js';
3
3
  import { statusText } from '../utils/options.js';
4
4
 
5
+ /**
6
+ * 成员表格:rows 是状态里的 ref 句柄,写入即按 key 对账——
7
+ * 能复用的行不重建,行内热字段只刷自己那一格,组件不需要对外暴露 refresh()。
8
+ */
5
9
  export function MemberTable({ rows, onEdit, onRemove }) {
6
- let tbody = null;
7
-
8
- function renderRows() {
9
- if (!tbody) {
10
- return;
11
- }
12
-
13
- tbody.children().forEach((child) => child.destroy());
14
- rows().forEach((member) => {
15
- tbody.vTr((row) => {
16
- row.vTd(member.name);
17
- row.vTd(member.email);
18
- row.vTd(member.role);
19
- row.vTd(statusText[member.status] ?? member.status);
20
- row.vTd((cell) => {
21
- cell.hstack({ gap: '8px' }, (actions) => {
22
- actions.child(
23
- RowActionButton('编辑', (btn) => {
24
- btn.access('system:member:update');
25
- btn.on('click', () => onEdit(member));
26
- })
27
- );
28
- actions.child(
29
- RowActionButton('删除', (btn) => {
30
- btn.variant('danger');
31
- btn.access('system:member:remove');
32
- btn.on('click', () => onRemove(member));
33
- })
34
- );
35
- });
36
- });
10
+ return vTable((table) => {
11
+ table.vThead((head) => {
12
+ head.vTr((row) => {
13
+ row.vTh('姓名');
14
+ row.vTh('邮箱');
15
+ row.vTh('角色');
16
+ row.vTh('状态');
17
+ row.vTh('操作');
37
18
  });
38
19
  });
39
- }
20
+ table.vTbody((body) => {
21
+ body.keyed(
22
+ rows,
23
+ (member) => member.id,
24
+ (member) => buildRow(member)
25
+ );
26
+ });
27
+ });
40
28
 
41
- return {
42
- render() {
43
- return vTable((table) => {
44
- table.vThead((head) => {
45
- head.vTr((row) => {
46
- row.vTh('姓名');
47
- row.vTh('邮箱');
48
- row.vTh('角色');
49
- row.vTh('状态');
50
- row.vTh('操作');
51
- });
52
- });
53
- table.vTbody((body) => {
54
- tbody = body;
55
- renderRows();
29
+ function buildRow(member) {
30
+ return vTr((row) => {
31
+ row.vTd(vText(member.name));
32
+ row.vTd(vText(member.email));
33
+ row.vTd(vText(member.role));
34
+ row.vTd(vText(computed(() => statusText[member.status.value] ?? member.status.value)));
35
+ row.vTd((cell) => {
36
+ cell.hstack({ gap: '8px' }, (actions) => {
37
+ actions.child(
38
+ RowActionButton('编辑', (btn) => {
39
+ btn.access('system:member:update');
40
+ btn.on('click', () => onEdit(member));
41
+ })
42
+ );
43
+ actions.child(
44
+ RowActionButton('删除', (btn) => {
45
+ btn.variant('danger');
46
+ btn.access('system:member:remove');
47
+ btn.on('click', () => onRemove(member));
48
+ })
49
+ );
56
50
  });
57
51
  });
58
- },
59
- refresh() {
60
- renderRows();
61
- return this;
62
- }
63
- };
52
+ });
53
+ }
64
54
  }
@@ -4,13 +4,13 @@ import { MemberToolbar } from '../components/member-toolbar.js';
4
4
  import { MemberTable } from '../components/member-table.js';
5
5
  import { MemberFormDialog } from '../components/member-form-dialog.js';
6
6
 
7
- // 页面只做编排:组合工具栏、表格、弹窗与分页,逻辑在组件 / service / state 中
7
+ // 页面只做编排:状态用 ref 持有视图字段,表格绑句柄后写入即刷新,不需要订阅接线。
8
8
  export function MemberListPage() {
9
9
  const state = new MembersPageState();
10
10
  const dialog = MemberFormDialog({ onSubmit: saveMember });
11
11
  const toolbar = MemberToolbar({ onSearch: applyFilters, onAdd: () => dialog.open(null) });
12
12
  const table = MemberTable({
13
- rows: () => state.items(),
13
+ rows: state.items,
14
14
  onEdit: (member) => dialog.open(member),
15
15
  onRemove: removeMemberAction
16
16
  });
@@ -20,22 +20,26 @@ export function MemberListPage() {
20
20
  onChange({ page: nextPage, pageSize: nextSize }) {
21
21
  state.setPage(nextPage);
22
22
  state.setPageSize(nextSize);
23
- state.load();
23
+ load();
24
24
  }
25
25
  });
26
26
 
27
- state.subscribe(() => {
28
- table.refresh();
27
+ load();
28
+
29
+ // vPagination 是命令式组件(不是信号感知的):数据到位后由动作同步一次
30
+ async function load() {
31
+ await state.load();
29
32
  syncPagination();
30
- });
31
- state.load();
33
+ }
32
34
 
33
35
  function syncPagination() {
36
+ const total = state.total.value;
37
+ const pageSize = state.pageSize.value;
34
38
  pagination.update({
35
- page: state.page(),
36
- pageSize: state.pageSize(),
37
- total: state.total(),
38
- totalPages: Math.max(1, Math.ceil(state.total() / state.pageSize()))
39
+ page: state.page.value,
40
+ pageSize,
41
+ total,
42
+ totalPages: Math.max(1, Math.ceil(total / pageSize))
39
43
  });
40
44
  }
41
45
 
@@ -43,12 +47,12 @@ export function MemberListPage() {
43
47
  state.setKeyword(values.keyword ?? '');
44
48
  state.setStatus(values.status ?? '');
45
49
  state.setPage(1);
46
- state.load();
50
+ load();
47
51
  }
48
52
 
49
53
  async function removeMemberAction(member) {
50
54
  await state.remove(member.id);
51
- toast.success(`已删除 ${member.name}`);
55
+ toast.success(`已删除 ${member.name.value}`);
52
56
  }
53
57
 
54
58
  async function saveMember(editingId, payload) {
@@ -78,8 +82,9 @@ export function MemberListPage() {
78
82
  stack.child(dialog);
79
83
  });
80
84
  },
85
+ // 页面也是组件:父级 / 路由可以调用实例方法刷新
81
86
  refresh() {
82
- return state.load();
87
+ return load();
83
88
  }
84
89
  };
85
90
  }
@@ -1,5 +1,5 @@
1
1
  /** 权限管理请求命令类型声明(对应 permission.mgr.js)。 */
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Permissions, { PermissionType } from './permission.views.js';
4
4
 
5
5
  type PermissionNodeInstance = InstanceType<typeof Permissions.PermissionNode>;
@@ -1,5 +1,5 @@
1
1
  // 权限管理请求命令:查询树、新增、编辑、删除。
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Permissions from './permission.views.js';
4
4
 
5
5
  class QueryTree extends RequestBase {
@@ -1,13 +1,15 @@
1
1
  /** 权限页状态类类型声明(对应 permission.state.js)。 */
2
+ import type { SignalHandle } from '@yoyaflow/yoya-ui';
3
+ import type { Result } from '@yoyaflow/yoya-ui/api';
2
4
  import Permissions from './permission.views.js';
3
5
 
4
6
  type PermissionNodeInstance = InstanceType<typeof Permissions.PermissionNode>;
5
7
 
6
8
  export default class PermissionsPageState {
7
9
  constructor();
8
- tree(): PermissionNodeInstance[];
9
- subscribe(listener: () => void): () => void;
10
- load(): Promise<unknown>;
10
+ /** 视图字段:权限树整体重建,页面读值后喂给 vTree */
11
+ tree: SignalHandle<PermissionNodeInstance[]>;
12
+ load(): Promise<Result<PermissionNodeInstance[]>>;
11
13
  add(parentId: number | null, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
12
14
  edit(id: number, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
13
15
  remove(id: number): Promise<void>;
@@ -1,25 +1,15 @@
1
- // 权限页状态类:持有权限树,动作构造命令并 submit 后写入状态、通知订阅者。
1
+ // 权限页状态类:权限树用 ref 持有,动作写句柄即完成通知。
2
+ import { ref } from '@yoyaflow/yoya-ui';
2
3
  import PermissionMgr from './permission.mgr.js';
3
4
 
4
5
  export default class PermissionsPageState {
5
6
  constructor() {
6
- this._tree = [];
7
- this._listeners = new Set();
8
- }
9
-
10
- tree() {
11
- return this._tree;
12
- }
13
-
14
- subscribe(listener) {
15
- this._listeners.add(listener);
16
- return () => this._listeners.delete(listener);
7
+ this.tree = ref([]);
17
8
  }
18
9
 
19
10
  async load() {
20
11
  const result = await PermissionMgr.QueryTree().submit();
21
- this._tree = result.data;
22
- this._emit();
12
+ this.tree.value = result.data;
23
13
  return result;
24
14
  }
25
15
 
@@ -39,8 +29,4 @@ export default class PermissionsPageState {
39
29
  await PermissionMgr.Remove({ id }).submit();
40
30
  await this.load();
41
31
  }
42
-
43
- _emit() {
44
- this._listeners.forEach((listener) => listener());
45
- }
46
32
  }
@@ -1,4 +1,5 @@
1
- // 权限结果结构(纯数据)。
1
+ // 权限结果结构(纯数据):权限树是层级结构,只做整树重建(vTree 是命令式组件),
2
+ // 所以节点字段保持普通值;要做行级原地刷新时再按需升级为句柄。
2
3
  class PermissionNode {
3
4
  constructor({
4
5
  id,
@@ -31,7 +31,7 @@ export function PermissionFormDialog({ onSubmit }) {
31
31
  margin: '0',
32
32
  padding: '0'
33
33
  });
34
- p.text(`父级:${parentName}`);
34
+ p.child(`父级:${parentName}`);
35
35
  });
36
36
  }
37
37
  buildFormFields(form, node);
@@ -8,10 +8,13 @@ export function PermissionListPage() {
8
8
  const dialog = PermissionFormDialog({ onSubmit: savePermission });
9
9
  const tree = vTree({ ariaLabel: '权限树' });
10
10
 
11
- state.subscribe(() => {
12
- tree.nodes(buildTreeNodes(state.tree()));
13
- });
14
- state.load();
11
+ load();
12
+
13
+ // vTree 是命令式组件(整树重建,不做行级对账):数据到位后由动作把 ref 值喂给它
14
+ async function load() {
15
+ await state.load();
16
+ tree.nodes(buildTreeNodes(state.tree.value));
17
+ }
15
18
 
16
19
  function buildTreeNodes(nodes) {
17
20
  return nodes.map((node) => ({
@@ -75,7 +78,7 @@ export function PermissionListPage() {
75
78
  btn.variant('primary');
76
79
  btn.on('click', () => dialog.open({}));
77
80
  });
78
- toolbar.vButton('刷新', (btn) => btn.on('click', () => state.load()));
81
+ toolbar.vButton('刷新', (btn) => btn.on('click', () => load()));
79
82
  });
80
83
  content.child(tree);
81
84
  });
@@ -85,7 +88,7 @@ export function PermissionListPage() {
85
88
  });
86
89
  },
87
90
  refresh() {
88
- return state.load();
91
+ return load();
89
92
  }
90
93
  };
91
94
  }
@@ -1,5 +1,5 @@
1
1
  /** 角色管理请求命令类型声明(对应 role.mgr.js)。 */
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Roles, { RoleStatus } from './role.views.js';
4
4
 
5
5
  type RoleListItem = InstanceType<typeof Roles.ListItem>;
@@ -1,5 +1,5 @@
1
1
  // 角色管理请求命令。
2
- import { RequestBase } from '@yoyaflow/yoya-ui';
2
+ import { RequestBase } from '@yoyaflow/yoya-ui/api';
3
3
  import Roles from './role.views.js';
4
4
 
5
5
  class Query extends RequestBase {
@@ -1,22 +1,24 @@
1
1
  /** 角色页状态类类型声明(对应 role.state.js)。 */
2
+ import type { SignalHandle } from '@yoyaflow/yoya-ui';
3
+ import type { Result } from '@yoyaflow/yoya-ui/api';
2
4
  import Roles from './role.views.js';
3
5
 
4
6
  type RoleListItem = InstanceType<typeof Roles.ListItem>;
5
7
 
6
8
  export default class RolesPageState {
7
9
  constructor();
8
- items(): RoleListItem[];
9
- keyword(): string;
10
- status(): string;
11
- page(): number;
12
- pageSize(): number;
13
- total(): number;
10
+ /** 视图字段:页面与组件直接绑句柄,写入即更新 */
11
+ items: SignalHandle<RoleListItem[]>;
12
+ total: SignalHandle<number>;
13
+ page: SignalHandle<number>;
14
+ pageSize: SignalHandle<number>;
15
+ keyword: SignalHandle<string>;
16
+ status: SignalHandle<string>;
14
17
  setKeyword(value: string): void;
15
18
  setStatus(value: string): void;
16
19
  setPage(value: number): void;
17
20
  setPageSize(value: number): void;
18
- subscribe(listener: () => void): () => void;
19
- load(): Promise<unknown>;
21
+ load(): Promise<Result<RoleListItem[]>>;
20
22
  add(payload: Record<string, unknown>): Promise<Record<string, unknown>>;
21
23
  edit(id: number, patch: Record<string, unknown>): Promise<Record<string, unknown>>;
22
24
  remove(id: number): Promise<void>;
@@ -1,78 +1,49 @@
1
- // 角色页状态类。
1
+ // 角色页状态类:视图字段用 ref 持有,动作写句柄即完成通知。
2
+ import { ref } from '@yoyaflow/yoya-ui';
3
+ import { mergeRowsByKey } from '../../../../shared/state.rows.js';
2
4
  import RoleMgr from './role.mgr.js';
3
5
 
4
6
  export default class RolesPageState {
5
7
  constructor() {
6
- this._items = [];
7
- this._keyword = '';
8
- this._status = '';
9
- this._page = 1;
10
- this._pageSize = 10;
11
- this._total = 0;
12
- this._listeners = new Set();
13
- }
14
-
15
- items() {
16
- return this._items;
17
- }
18
-
19
- keyword() {
20
- return this._keyword;
21
- }
22
-
23
- status() {
24
- return this._status;
25
- }
26
-
27
- page() {
28
- return this._page;
29
- }
30
-
31
- pageSize() {
32
- return this._pageSize;
33
- }
34
-
35
- total() {
36
- return this._total;
8
+ this.items = ref([]);
9
+ this.total = ref(0);
10
+ this.page = ref(1);
11
+ this.pageSize = ref(10);
12
+ this.keyword = ref('');
13
+ this.status = ref('');
37
14
  }
38
15
 
39
16
  setKeyword(value) {
40
- this._keyword = value;
17
+ this.keyword.value = value;
41
18
  }
42
19
 
43
20
  setStatus(value) {
44
- this._status = value;
21
+ this.status.value = value;
45
22
  }
46
23
 
47
24
  setPage(value) {
48
- this._page = Math.max(1, Number(value) || 1);
25
+ this.page.value = Math.max(1, Number(value) || 1);
49
26
  }
50
27
 
51
28
  setPageSize(value) {
52
- this._pageSize = Math.max(1, Number(value) || 10);
53
- }
54
-
55
- subscribe(listener) {
56
- this._listeners.add(listener);
57
- return () => this._listeners.delete(listener);
29
+ this.pageSize.value = Math.max(1, Number(value) || 10);
58
30
  }
59
31
 
60
32
  async load() {
61
33
  const result = await RoleMgr.Query({
62
- page: this._page,
63
- pageSize: this._pageSize,
64
- keyword: this._keyword,
65
- status: this._status
34
+ page: this.page.value,
35
+ pageSize: this.pageSize.value,
36
+ keyword: this.keyword.value,
37
+ status: this.status.value
66
38
  }).submit();
67
- this._items = result.data;
68
- this._total = result.total;
69
- this._emit();
39
+ this.items.value = mergeRowsByKey(this.items.peek(), result.data);
40
+ this.total.value = result.total;
70
41
  return result;
71
42
  }
72
43
 
73
44
  async add(payload) {
74
45
  await RoleMgr.Create(payload).submit();
75
- this._page = 1;
46
+ this.page.value = 1;
76
47
  await this.load();
77
48
  return payload;
78
49
  }
@@ -85,13 +56,9 @@ export default class RolesPageState {
85
56
 
86
57
  async remove(id) {
87
58
  await RoleMgr.Remove({ id }).submit();
88
- if (this._items.length === 1 && this._page > 1) {
89
- this._page -= 1;
59
+ if (this.items.peek().length === 1 && this.page.value > 1) {
60
+ this.page.value -= 1;
90
61
  }
91
62
  await this.load();
92
63
  }
93
-
94
- _emit() {
95
- this._listeners.forEach((listener) => listener());
96
- }
97
64
  }
@@ -1,21 +1,30 @@
1
1
  /** 角色结果结构类型声明(对应 role.views.js)。 */
2
+ import type { SignalHandle } from '@yoyaflow/yoya-ui';
3
+
4
+ /** 角色状态:与 utils/options.js 的 statusOptions 对齐 */
2
5
  export type RoleStatus = 'active' | 'disabled';
3
6
 
7
+ export type RoleListRow = {
8
+ id: number;
9
+ name?: string;
10
+ code?: string;
11
+ description?: string;
12
+ status?: RoleStatus;
13
+ sort?: number;
14
+ };
15
+
4
16
  export class ListItem {
17
+ /** key:普通值,不能是句柄 */
5
18
  id: number;
6
- name: string;
7
- code: string;
8
- description: string;
9
- status: RoleStatus;
19
+ /** 行内热字段:句柄写入即刷新对应单元格 */
20
+ name: SignalHandle<string>;
21
+ code: SignalHandle<string>;
22
+ description: SignalHandle<string>;
23
+ status: SignalHandle<string>;
24
+ /** 冷字段:表格不展示,随行结构变化刷新 */
10
25
  sort: number;
11
- constructor(init?: {
12
- id: number;
13
- name: string;
14
- code: string;
15
- description?: string;
16
- status?: RoleStatus;
17
- sort?: number;
18
- });
26
+ constructor(row?: RoleListRow);
27
+ apply(row: RoleListRow): this;
19
28
  }
20
29
 
21
30
  export class Detail {
@@ -23,14 +32,14 @@ export class Detail {
23
32
  name: string;
24
33
  code: string;
25
34
  description: string;
26
- status: RoleStatus;
35
+ status: string;
27
36
  sort: number;
28
- constructor(init?: {
37
+ constructor(init: {
29
38
  id: number;
30
39
  name: string;
31
40
  code: string;
32
41
  description?: string;
33
- status?: RoleStatus;
42
+ status?: string;
34
43
  sort?: number;
35
44
  });
36
45
  }