create-yoya-ui 0.1.2 → 0.2.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 (71) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/templates/admin/README.md +54 -2
  4. package/templates/admin/node_modules/.vite/deps/_metadata.json +8 -0
  5. package/templates/admin/node_modules/.vite/deps/package.json +3 -0
  6. package/templates/admin/package.json +4 -2
  7. package/templates/admin/src/api/domain.api.d.ts +10 -0
  8. package/templates/admin/src/api/domain.api.js +31 -0
  9. package/templates/admin/src/api/fetch.api.d.ts +4 -0
  10. package/templates/admin/src/api/fetch.api.js +20 -0
  11. package/templates/admin/src/app/admin-shell.js +51 -0
  12. package/templates/admin/src/app/modules.js +37 -0
  13. package/templates/admin/src/app/router.js +20 -0
  14. package/templates/admin/src/features/dashboard/components/dashboard-chart.js +205 -0
  15. package/templates/admin/src/features/dashboard/pages/dashboard-overview-page.js +197 -0
  16. package/templates/admin/src/features/dicts/api/dict.mgr.d.ts +79 -0
  17. package/templates/admin/src/features/dicts/api/dict.mgr.js +160 -0
  18. package/templates/admin/src/features/dicts/api/dict.mock.js +81 -0
  19. package/templates/admin/src/features/dicts/api/dict.state.d.ts +23 -0
  20. package/templates/admin/src/features/dicts/api/dict.state.js +103 -0
  21. package/templates/admin/src/features/dicts/api/dict.views.d.ts +41 -0
  22. package/templates/admin/src/features/dicts/api/dict.views.js +23 -0
  23. package/templates/admin/src/features/dicts/components/dict-editor-dialog.js +218 -0
  24. package/templates/admin/src/features/dicts/components/dict-item-form-dialog.js +79 -0
  25. package/templates/admin/src/features/dicts/pages/dict-list-page.js +123 -0
  26. package/templates/admin/src/features/dicts/utils/options.js +5 -0
  27. package/templates/admin/src/features/members/api/member.mgr.d.ts +47 -0
  28. package/templates/admin/src/features/members/api/member.mgr.js +95 -0
  29. package/templates/admin/src/features/members/api/member.mock.js +74 -0
  30. package/templates/admin/src/features/members/api/member.req.d.ts +16 -0
  31. package/templates/admin/src/features/members/api/member.req.js +15 -0
  32. package/templates/admin/src/features/members/api/member.state.d.ts +23 -0
  33. package/templates/admin/src/features/members/api/member.state.js +97 -0
  34. package/templates/admin/src/features/members/api/member.views.d.ts +33 -0
  35. package/templates/admin/src/features/members/api/member.views.js +23 -0
  36. package/templates/admin/src/features/members/components/member-form-dialog.js +83 -0
  37. package/templates/admin/src/features/members/components/member-table.js +58 -0
  38. package/templates/admin/src/features/members/components/member-toolbar.js +58 -0
  39. package/templates/admin/src/features/members/pages/member-list-page.js +85 -0
  40. package/templates/admin/src/features/members/utils/options.d.ts +4 -0
  41. package/templates/admin/src/features/members/utils/options.js +6 -0
  42. package/templates/admin/src/features/permissions/api/permission.mgr.d.ts +45 -0
  43. package/templates/admin/src/features/permissions/api/permission.mgr.js +79 -0
  44. package/templates/admin/src/features/permissions/api/permission.mock.js +68 -0
  45. package/templates/admin/src/features/permissions/api/permission.state.d.ts +14 -0
  46. package/templates/admin/src/features/permissions/api/permission.state.js +46 -0
  47. package/templates/admin/src/features/permissions/api/permission.views.d.ts +27 -0
  48. package/templates/admin/src/features/permissions/api/permission.views.js +14 -0
  49. package/templates/admin/src/features/permissions/components/permission-form-dialog.js +98 -0
  50. package/templates/admin/src/features/permissions/pages/permission-list-page.js +98 -0
  51. package/templates/admin/src/features/roles/api/role.mgr.d.ts +57 -0
  52. package/templates/admin/src/features/roles/api/role.mgr.js +101 -0
  53. package/templates/admin/src/features/roles/api/role.mock.js +68 -0
  54. package/templates/admin/src/features/roles/api/role.state.d.ts +23 -0
  55. package/templates/admin/src/features/roles/api/role.state.js +97 -0
  56. package/templates/admin/src/features/roles/api/role.views.d.ts +43 -0
  57. package/templates/admin/src/features/roles/api/role.views.js +24 -0
  58. package/templates/admin/src/features/roles/components/role-form-dialog.js +86 -0
  59. package/templates/admin/src/features/roles/components/role-table.js +57 -0
  60. package/templates/admin/src/features/roles/components/role-toolbar.js +58 -0
  61. package/templates/admin/src/features/roles/pages/role-list-page.js +100 -0
  62. package/templates/admin/src/features/roles/utils/options.js +5 -0
  63. package/templates/admin/src/main.js +17 -165
  64. package/templates/admin/src/ui/app-navbar.js +67 -0
  65. package/templates/admin/src/ui/app-sidebar.js +27 -0
  66. package/templates/admin/src/ui/placeholder-page.js +17 -0
  67. package/templates/admin/src/ui/row-action-button.js +12 -0
  68. package/templates/admin/src/ui/sidebar-menu.js +14 -0
  69. package/templates/admin/tsconfig.json +18 -0
  70. package/templates/basic/package.json +1 -1
  71. package/templates/ssr/package.json +1 -1
@@ -0,0 +1,14 @@
1
+ /** 权限页状态类类型声明(对应 permission.state.js)。 */
2
+ import Permissions from './permission.views.js';
3
+
4
+ type PermissionNodeInstance = InstanceType<typeof Permissions.PermissionNode>;
5
+
6
+ export default class PermissionsPageState {
7
+ constructor();
8
+ tree(): PermissionNodeInstance[];
9
+ subscribe(listener: () => void): () => void;
10
+ load(): Promise<unknown>;
11
+ add(parentId: number | null, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
12
+ edit(id: number, payload: Record<string, unknown>): Promise<Record<string, unknown>>;
13
+ remove(id: number): Promise<void>;
14
+ }
@@ -0,0 +1,46 @@
1
+ // 权限页状态类:持有权限树,动作构造命令并 submit 后写入状态、通知订阅者。
2
+ import PermissionMgr from './permission.mgr.js';
3
+
4
+ export default class PermissionsPageState {
5
+ 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);
17
+ }
18
+
19
+ async load() {
20
+ const result = await new PermissionMgr.QueryTree().submit();
21
+ this._tree = result.data;
22
+ this._emit();
23
+ return result;
24
+ }
25
+
26
+ async add(parentId, payload) {
27
+ await new PermissionMgr.Create(parentId, payload).submit();
28
+ await this.load();
29
+ return payload;
30
+ }
31
+
32
+ async edit(id, payload) {
33
+ await new PermissionMgr.Update(id, payload).submit();
34
+ await this.load();
35
+ return payload;
36
+ }
37
+
38
+ async remove(id) {
39
+ await new PermissionMgr.Remove(id).submit();
40
+ await this.load();
41
+ }
42
+
43
+ _emit() {
44
+ this._listeners.forEach((listener) => listener());
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ /** 权限结果结构类型声明(对应 permission.views.js)。 */
2
+ export type PermissionType = 'menu' | 'button';
3
+
4
+ export class PermissionNode {
5
+ id: number;
6
+ parentId: number | null;
7
+ name: string;
8
+ code: string;
9
+ type: PermissionType;
10
+ sort: number;
11
+ children: PermissionNode[];
12
+ constructor(init?: {
13
+ id: number;
14
+ parentId?: number | null;
15
+ name?: string;
16
+ code?: string;
17
+ type?: PermissionType;
18
+ sort?: number;
19
+ children?: PermissionNode[];
20
+ });
21
+ }
22
+
23
+ declare const Permissions: {
24
+ PermissionNode: typeof PermissionNode;
25
+ };
26
+
27
+ export default Permissions;
@@ -0,0 +1,14 @@
1
+ // 权限结果结构(纯数据)。
2
+ class PermissionNode {
3
+ constructor({ id, parentId = null, name = '', code = '', type = 'menu', sort = 0, children = [] } = {}) {
4
+ this.id = id;
5
+ this.parentId = parentId;
6
+ this.name = name;
7
+ this.code = code;
8
+ this.type = type;
9
+ this.sort = sort;
10
+ this.children = children;
11
+ }
12
+ }
13
+
14
+ export default { PermissionNode };
@@ -0,0 +1,98 @@
1
+ import { vDialog } from '@yoyaflow/yoya-ui';
2
+
3
+ const typeOptions = [
4
+ ['menu', '菜单'],
5
+ ['button', '按钮']
6
+ ];
7
+
8
+ const rowStyle = {
9
+ alignItems: 'center',
10
+ display: 'grid',
11
+ gap: '12px',
12
+ gridTemplateColumns: '88px minmax(0, 1fr)',
13
+ minWidth: '0'
14
+ };
15
+
16
+ export function PermissionFormDialog({ onSubmit }) {
17
+ const dialog = vDialog();
18
+ let editingId = null;
19
+ let parentId = null;
20
+
21
+ function open({ node = null, parentId: nextParentId = null, parentName = '' } = {}) {
22
+ editingId = node?.id ?? null;
23
+ parentId = nextParentId;
24
+ dialog.content((content) => {
25
+ content.vForm((form) => {
26
+ if (parentName) {
27
+ form.p((p) => {
28
+ p.style({
29
+ color: 'var(--yoya-color-text-secondary, #475569)',
30
+ gridColumn: '1 / -1',
31
+ margin: '0',
32
+ padding: '0'
33
+ });
34
+ p.text(`父级:${parentName}`);
35
+ });
36
+ }
37
+ buildFormFields(form, node);
38
+ form.vButton('保存', (btn) => {
39
+ btn.variant('primary');
40
+ btn.style({ gridColumn: '1 / -1', justifySelf: 'end', marginTop: '16px' });
41
+ btn.on('click', () => save(form));
42
+ });
43
+ });
44
+ });
45
+ dialog.open(true);
46
+ }
47
+
48
+ function buildFormFields(form, node) {
49
+ form.styles({
50
+ border: '1px solid var(--yoya-color-border, #d8dee8)',
51
+ borderRadius: '8px',
52
+ display: 'grid',
53
+ gap: '16px',
54
+ gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
55
+ padding: '16px'
56
+ });
57
+ form.vFormItem((item) => {
58
+ item.styles(rowStyle);
59
+ item.label('名称').name('name').required({ message: '请填写名称' });
60
+ item.control((editor) => editor.vInput({ name: 'name', value: node?.name ?? '' }));
61
+ });
62
+ form.vFormItem((item) => {
63
+ item.styles(rowStyle);
64
+ item.label('权限标识').name('code').required({ message: '请填写权限标识' });
65
+ item.control((editor) => editor.vInput({ name: 'code', value: node?.code ?? '' }));
66
+ });
67
+ form.vFormItem((item) => {
68
+ item.styles(rowStyle);
69
+ item.label('类型').name('type');
70
+ item.control((editor) =>
71
+ editor.vSelect({ name: 'type', options: typeOptions, value: node?.type ?? 'menu' })
72
+ );
73
+ });
74
+ form.vFormItem((item) => {
75
+ item.styles(rowStyle);
76
+ item.label('排序').name('sort');
77
+ item.control((editor) => editor.vInput({ name: 'sort', value: String(node?.sort ?? 0) }));
78
+ });
79
+ }
80
+
81
+ async function save(form) {
82
+ if (!form.validate()) {
83
+ return;
84
+ }
85
+ await onSubmit(editingId, parentId, form.values());
86
+ dialog.close();
87
+ }
88
+
89
+ return {
90
+ render() {
91
+ return dialog;
92
+ },
93
+ open,
94
+ close() {
95
+ dialog.close();
96
+ }
97
+ };
98
+ }
@@ -0,0 +1,98 @@
1
+ import { toast, vDialog, vTree, vstack } from '@yoyaflow/yoya-ui';
2
+ import { RowActionButton } from '../../../ui/row-action-button.js';
3
+ import PermissionsPageState from '../api/permission.state.js';
4
+ import { PermissionFormDialog } from '../components/permission-form-dialog.js';
5
+
6
+ export function PermissionListPage() {
7
+ const state = new PermissionsPageState();
8
+ const dialog = PermissionFormDialog({ onSubmit: savePermission });
9
+ const confirmDialog = vDialog();
10
+ const tree = vTree({ ariaLabel: '权限树' });
11
+
12
+ state.subscribe(() => {
13
+ tree.nodes(buildTreeNodes(state.tree()));
14
+ });
15
+ state.load();
16
+
17
+ function buildTreeNodes(nodes) {
18
+ return nodes.map((node) => ({
19
+ id: String(node.id),
20
+ label: node.name,
21
+ expanded: true,
22
+ children: buildTreeNodes(node.children ?? []),
23
+ actions: (actions) => {
24
+ actions.child(
25
+ RowActionButton('新增子权限', (btn) =>
26
+ btn.on('click', () => dialog.open({ parentId: node.id, parentName: node.name }))
27
+ )
28
+ );
29
+ actions.child(
30
+ RowActionButton('编辑', (btn) => btn.on('click', () => dialog.open({ node })))
31
+ );
32
+ actions.child(
33
+ RowActionButton('删除', (btn) => {
34
+ btn.variant('danger');
35
+ btn.on('click', () => askRemove(node));
36
+ })
37
+ );
38
+ }
39
+ }));
40
+ }
41
+
42
+ function askRemove(node) {
43
+ confirmDialog.content((content) => {
44
+ content.p(`确定删除权限「${node.name}」及其子权限?`);
45
+ content.hstack({ gap: '8px' }, (row) => {
46
+ row.spacer();
47
+ row.vButton('取消', (btn) => btn.on('click', () => confirmDialog.close()));
48
+ row.vButton('删除', (btn) => {
49
+ btn.variant('danger');
50
+ btn.on('click', async () => {
51
+ confirmDialog.close();
52
+ await state.remove(node.id);
53
+ toast.success(`已删除 ${node.name}`);
54
+ });
55
+ });
56
+ });
57
+ });
58
+ confirmDialog.open(true);
59
+ }
60
+
61
+ async function savePermission(editingId, parentId, payload) {
62
+ if (editingId === null) {
63
+ await state.add(parentId, payload);
64
+ toast.success('已新增权限');
65
+ } else {
66
+ await state.edit(editingId, payload);
67
+ toast.success('已更新权限');
68
+ }
69
+ }
70
+
71
+ return {
72
+ render() {
73
+ return vstack({ gap: '16px' }, (stack) => {
74
+ stack.h2('权限管理');
75
+ stack.vCard((card) => {
76
+ card.vCardHeader('权限树');
77
+ card.vCardBody((body) => {
78
+ body.vstack({ gap: '12px' }, (content) => {
79
+ content.hstack({ gap: '10px' }, (toolbar) => {
80
+ toolbar.vButton('新增根权限', (btn) => {
81
+ btn.variant('primary');
82
+ btn.on('click', () => dialog.open({}));
83
+ });
84
+ toolbar.vButton('刷新', (btn) => btn.on('click', () => state.load()));
85
+ });
86
+ content.child(tree);
87
+ });
88
+ });
89
+ });
90
+ stack.child(dialog);
91
+ stack.child(confirmDialog);
92
+ });
93
+ },
94
+ refresh() {
95
+ return state.load();
96
+ }
97
+ };
98
+ }
@@ -0,0 +1,57 @@
1
+ /** 角色管理请求命令类型声明(对应 role.mgr.js)。 */
2
+ import { RequestBase } from '@yoyaflow/yoya-ui';
3
+ import Roles, { RoleStatus } from './role.views.js';
4
+
5
+ type RoleListItem = InstanceType<typeof Roles.ListItem>;
6
+ type RoleDetail = InstanceType<typeof Roles.Detail>;
7
+
8
+ declare class Query extends RequestBase {
9
+ constructor(init?: { page?: number; pageSize?: number; keyword?: string; status?: string });
10
+ address(): string;
11
+ params(): { page: number; pageSize: number; keyword: string; status: string };
12
+ toItem(row: Record<string, unknown>): RoleListItem;
13
+ }
14
+
15
+ declare class Create extends RequestBase {
16
+ constructor(init?: {
17
+ name?: string;
18
+ code?: string;
19
+ description?: string;
20
+ status?: RoleStatus;
21
+ sort?: number;
22
+ });
23
+ method(): 'POST';
24
+ address(): string;
25
+ body(): { name: string; code: string; description: string; status: RoleStatus; sort: number };
26
+ }
27
+
28
+ declare class Update extends RequestBase {
29
+ constructor(
30
+ id: number,
31
+ init?: {
32
+ name?: string;
33
+ code?: string;
34
+ description?: string;
35
+ status?: RoleStatus;
36
+ sort?: number;
37
+ }
38
+ );
39
+ method(): 'PUT';
40
+ address(): string;
41
+ body(): { name: string; code: string; description: string; status: RoleStatus; sort: number };
42
+ }
43
+
44
+ declare class Remove extends RequestBase {
45
+ constructor(id: number);
46
+ method(): 'DELETE';
47
+ address(): string;
48
+ }
49
+
50
+ declare const RoleMgr: {
51
+ Query: typeof Query;
52
+ Create: typeof Create;
53
+ Update: typeof Update;
54
+ Remove: typeof Remove;
55
+ };
56
+
57
+ export default RoleMgr;
@@ -0,0 +1,101 @@
1
+ // 角色管理请求命令。
2
+ import { RequestBase } from '@yoyaflow/yoya-ui';
3
+ import Roles from './role.views.js';
4
+
5
+ class Query extends RequestBase {
6
+ constructor({ page = 1, pageSize = 10, keyword = '', status = '' } = {}) {
7
+ super();
8
+ this.page = page;
9
+ this.pageSize = pageSize;
10
+ this.keyword = keyword;
11
+ this.status = status;
12
+ }
13
+
14
+ address() {
15
+ return '/roles';
16
+ }
17
+
18
+ params() {
19
+ return { page: this.page, pageSize: this.pageSize, keyword: this.keyword, status: this.status };
20
+ }
21
+
22
+ toItem(row) {
23
+ return new Roles.ListItem(row);
24
+ }
25
+ }
26
+
27
+ class Create extends RequestBase {
28
+ constructor({ name = '', code = '', description = '', status = 'active', sort = 0 } = {}) {
29
+ super();
30
+ this.name = name;
31
+ this.code = code;
32
+ this.description = description;
33
+ this.status = status;
34
+ this.sort = sort;
35
+ }
36
+
37
+ method() {
38
+ return 'POST';
39
+ }
40
+
41
+ address() {
42
+ return '/roles';
43
+ }
44
+
45
+ body() {
46
+ return {
47
+ name: this.name,
48
+ code: this.code,
49
+ description: this.description,
50
+ status: this.status,
51
+ sort: this.sort
52
+ };
53
+ }
54
+ }
55
+
56
+ class Update extends RequestBase {
57
+ constructor(id, { name = '', code = '', description = '', status = 'active', sort = 0 } = {}) {
58
+ super();
59
+ this.id = id;
60
+ this.name = name;
61
+ this.code = code;
62
+ this.description = description;
63
+ this.status = status;
64
+ this.sort = sort;
65
+ }
66
+
67
+ method() {
68
+ return 'PUT';
69
+ }
70
+
71
+ address() {
72
+ return `/roles/${this.id}`;
73
+ }
74
+
75
+ body() {
76
+ return {
77
+ name: this.name,
78
+ code: this.code,
79
+ description: this.description,
80
+ status: this.status,
81
+ sort: this.sort
82
+ };
83
+ }
84
+ }
85
+
86
+ class Remove extends RequestBase {
87
+ constructor(id) {
88
+ super();
89
+ this.id = id;
90
+ }
91
+
92
+ method() {
93
+ return 'DELETE';
94
+ }
95
+
96
+ address() {
97
+ return `/roles/${this.id}`;
98
+ }
99
+ }
100
+
101
+ export default { Query, Create, Update, Remove };
@@ -0,0 +1,68 @@
1
+ // 角色演示用内存 mock(接入真实后端后删除本文件)。
2
+ import { mockRequest } from '../../../api/domain.api.js';
3
+
4
+ let roles = [
5
+ { id: 1, name: '超级管理员', code: 'super_admin', description: '拥有全部权限', status: 'active', sort: 1 },
6
+ { id: 2, name: '管理员', code: 'admin', description: '后台管理操作', status: 'active', sort: 2 },
7
+ { id: 3, name: '运维', code: 'ops', description: '运维与部署', status: 'active', sort: 3 },
8
+ { id: 4, name: '审计员', code: 'auditor', description: '只读审计', status: 'disabled', sort: 4 },
9
+ { id: 5, name: '访客', code: 'guest', description: '只读访客', status: 'active', sort: 5 },
10
+ { id: 6, name: '开发者', code: 'developer', description: '开发调试', status: 'active', sort: 6 },
11
+ { id: 7, name: '测试', code: 'qa', description: '测试执行', status: 'disabled', sort: 7 },
12
+ { id: 8, name: '客服', code: 'support', description: '客户支持', status: 'active', sort: 8 },
13
+ { id: 9, name: '财务', code: 'finance', description: '财务操作', status: 'active', sort: 9 },
14
+ { id: 10, name: '人事', code: 'hr', description: '人事管理', status: 'active', sort: 10 },
15
+ { id: 11, name: '安全', code: 'security', description: '安全审计', status: 'disabled', sort: 11 },
16
+ { id: 12, name: '数据', code: 'analyst', description: '数据分析', status: 'active', sort: 12 }
17
+ ];
18
+
19
+ const delay = (ms = 200) => new Promise((resolve) => setTimeout(resolve, ms));
20
+
21
+ function filterRoles({ keyword = '', status = '' } = {}) {
22
+ const text = String(keyword).trim().toLowerCase();
23
+ return roles.filter((role) => {
24
+ const matchKeyword =
25
+ !text ||
26
+ role.name.toLowerCase().includes(text) ||
27
+ role.code.toLowerCase().includes(text);
28
+ const matchStatus = !status || role.status === status;
29
+ return matchKeyword && matchStatus;
30
+ });
31
+ }
32
+
33
+ mockRequest('GET', '/roles', async (req) => {
34
+ await delay();
35
+ const { page = 1, pageSize = 10 } = req.params();
36
+ const filtered = filterRoles(req.params());
37
+ const start = (page - 1) * pageSize;
38
+ return {
39
+ ok: true,
40
+ code: '0',
41
+ data: filtered.slice(start, start + pageSize),
42
+ pageNum: page,
43
+ pageSize,
44
+ total: filtered.length
45
+ };
46
+ });
47
+
48
+ mockRequest('POST', '/roles', async (req) => {
49
+ await delay();
50
+ const role = { id: Math.max(0, ...roles.map((item) => item.id)) + 1, ...req.body() };
51
+ roles = [role, ...roles];
52
+ return { ok: true, code: '0', data: role };
53
+ });
54
+
55
+ mockRequest('PUT', /^\/roles\/\d+$/, async (req) => {
56
+ await delay();
57
+ const id = Number(req.address().split('/').pop());
58
+ const role = { ...roles.find((item) => item.id === id), ...req.body(), id };
59
+ roles = roles.map((item) => (item.id === id ? role : item));
60
+ return { ok: true, code: '0', data: role };
61
+ });
62
+
63
+ mockRequest('DELETE', /^\/roles\/\d+$/, async (req) => {
64
+ await delay();
65
+ const id = Number(req.address().split('/').pop());
66
+ roles = roles.filter((item) => item.id !== id);
67
+ return { ok: true, code: '0', data: null };
68
+ });
@@ -0,0 +1,23 @@
1
+ /** 角色页状态类类型声明(对应 role.state.js)。 */
2
+ import Roles from './role.views.js';
3
+
4
+ type RoleListItem = InstanceType<typeof Roles.ListItem>;
5
+
6
+ export default class RolesPageState {
7
+ constructor();
8
+ items(): RoleListItem[];
9
+ keyword(): string;
10
+ status(): string;
11
+ page(): number;
12
+ pageSize(): number;
13
+ total(): number;
14
+ setKeyword(value: string): void;
15
+ setStatus(value: string): void;
16
+ setPage(value: number): void;
17
+ setPageSize(value: number): void;
18
+ subscribe(listener: () => void): () => void;
19
+ load(): Promise<unknown>;
20
+ add(payload: Record<string, unknown>): Promise<Record<string, unknown>>;
21
+ edit(id: number, patch: Record<string, unknown>): Promise<Record<string, unknown>>;
22
+ remove(id: number): Promise<void>;
23
+ }
@@ -0,0 +1,97 @@
1
+ // 角色页状态类。
2
+ import RoleMgr from './role.mgr.js';
3
+
4
+ export default class RolesPageState {
5
+ 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;
37
+ }
38
+
39
+ setKeyword(value) {
40
+ this._keyword = value;
41
+ }
42
+
43
+ setStatus(value) {
44
+ this._status = value;
45
+ }
46
+
47
+ setPage(value) {
48
+ this._page = Math.max(1, Number(value) || 1);
49
+ }
50
+
51
+ 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);
58
+ }
59
+
60
+ async load() {
61
+ const result = await new RoleMgr.Query({
62
+ page: this._page,
63
+ pageSize: this._pageSize,
64
+ keyword: this._keyword,
65
+ status: this._status
66
+ }).submit();
67
+ this._items = result.data;
68
+ this._total = result.total;
69
+ this._emit();
70
+ return result;
71
+ }
72
+
73
+ async add(payload) {
74
+ await new RoleMgr.Create(payload).submit();
75
+ this._page = 1;
76
+ await this.load();
77
+ return payload;
78
+ }
79
+
80
+ async edit(id, patch) {
81
+ await new RoleMgr.Update(id, patch).submit();
82
+ await this.load();
83
+ return patch;
84
+ }
85
+
86
+ async remove(id) {
87
+ await new RoleMgr.Remove(id).submit();
88
+ if (this._items.length === 1 && this._page > 1) {
89
+ this._page -= 1;
90
+ }
91
+ await this.load();
92
+ }
93
+
94
+ _emit() {
95
+ this._listeners.forEach((listener) => listener());
96
+ }
97
+ }
@@ -0,0 +1,43 @@
1
+ /** 角色结果结构类型声明(对应 role.views.js)。 */
2
+ export type RoleStatus = 'active' | 'disabled';
3
+
4
+ export class ListItem {
5
+ id: number;
6
+ name: string;
7
+ code: string;
8
+ description: string;
9
+ status: RoleStatus;
10
+ sort: number;
11
+ constructor(init?: {
12
+ id: number;
13
+ name: string;
14
+ code: string;
15
+ description?: string;
16
+ status?: RoleStatus;
17
+ sort?: number;
18
+ });
19
+ }
20
+
21
+ export class Detail {
22
+ id: number;
23
+ name: string;
24
+ code: string;
25
+ description: string;
26
+ status: RoleStatus;
27
+ sort: number;
28
+ constructor(init?: {
29
+ id: number;
30
+ name: string;
31
+ code: string;
32
+ description?: string;
33
+ status?: RoleStatus;
34
+ sort?: number;
35
+ });
36
+ }
37
+
38
+ declare const Roles: {
39
+ ListItem: typeof ListItem;
40
+ Detail: typeof Detail;
41
+ };
42
+
43
+ export default Roles;