create-yoya-ui 0.4.1 → 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.
- package/package.json +1 -1
- package/templates/admin/RULES.md +22 -9
- package/templates/admin/package.json +1 -1
- package/templates/admin/src/api/domain.api.d.ts +1 -1
- package/templates/admin/src/api/domain.api.js +1 -1
- package/templates/admin/src/api/fetch.api.d.ts +1 -1
- package/templates/admin/src/features/dashboard/overview/pages/dashboard-overview-page.js +2 -2
- package/templates/admin/src/features/system/dicts/api/dict.mgr.d.ts +1 -1
- package/templates/admin/src/features/system/dicts/api/dict.mgr.js +1 -1
- package/templates/admin/src/features/system/dicts/api/dict.state.d.ts +14 -11
- package/templates/admin/src/features/system/dicts/api/dict.state.js +35 -70
- package/templates/admin/src/features/system/dicts/api/dict.views.d.ts +36 -23
- package/templates/admin/src/features/system/dicts/api/dict.views.js +36 -14
- package/templates/admin/src/features/system/dicts/components/dict-editor-dialog.js +53 -73
- package/templates/admin/src/features/system/dicts/components/dict-item-form-dialog.js +9 -6
- package/templates/admin/src/features/system/dicts/pages/dict-list-page.js +57 -59
- package/templates/admin/src/features/system/members/api/member.mgr.d.ts +1 -1
- package/templates/admin/src/features/system/members/api/member.mgr.js +1 -1
- package/templates/admin/src/features/system/members/api/member.req.d.ts +1 -1
- package/templates/admin/src/features/system/members/api/member.req.js +1 -1
- package/templates/admin/src/features/system/members/api/member.state.d.ts +10 -8
- package/templates/admin/src/features/system/members/api/member.state.js +24 -55
- package/templates/admin/src/features/system/members/api/member.views.d.ts +19 -5
- package/templates/admin/src/features/system/members/api/member.views.js +22 -8
- package/templates/admin/src/features/system/members/components/member-form-dialog.js +10 -6
- package/templates/admin/src/features/system/members/components/member-table.js +44 -54
- package/templates/admin/src/features/system/members/pages/member-list-page.js +19 -14
- package/templates/admin/src/features/system/permissions/api/permission.mgr.d.ts +1 -1
- package/templates/admin/src/features/system/permissions/api/permission.mgr.js +1 -1
- package/templates/admin/src/features/system/permissions/api/permission.state.d.ts +5 -3
- package/templates/admin/src/features/system/permissions/api/permission.state.js +4 -18
- package/templates/admin/src/features/system/permissions/api/permission.views.js +2 -1
- package/templates/admin/src/features/system/permissions/components/permission-form-dialog.js +1 -1
- package/templates/admin/src/features/system/permissions/pages/permission-list-page.js +9 -6
- package/templates/admin/src/features/system/roles/api/role.mgr.d.ts +1 -1
- package/templates/admin/src/features/system/roles/api/role.mgr.js +1 -1
- package/templates/admin/src/features/system/roles/api/role.state.d.ts +10 -8
- package/templates/admin/src/features/system/roles/api/role.state.js +22 -55
- package/templates/admin/src/features/system/roles/api/role.views.d.ts +24 -15
- package/templates/admin/src/features/system/roles/api/role.views.js +22 -9
- package/templates/admin/src/features/system/roles/components/role-form-dialog.js +11 -7
- package/templates/admin/src/features/system/roles/components/role-table.js +35 -47
- package/templates/admin/src/features/system/roles/pages/role-list-page.js +18 -14
- package/templates/admin/src/shared/state.rows.js +17 -0
- package/templates/admin/src/shell/api/shell.req.js +1 -1
- package/templates/admin/src/shell/api/shell.state.js +23 -55
- package/templates/admin/src/shell/components/admin-shell.js +4 -15
- package/templates/admin/src/shell/components/app-navbar.js +48 -56
- package/templates/admin/src/shell/components/app-sidebar.js +20 -31
- package/templates/admin/src/shell/components/sidebar-menu.js +1 -1
- package/templates/basic/package.json +1 -1
- package/templates/ssr/README.md +1 -1
- package/templates/ssr/package.json +1 -1
- package/templates/ssr/src/server.mjs +3 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { toast, vConfirm, vDialog,
|
|
1
|
+
import { computed, toast, vConfirm, vDialog, vText, vTr } from '@yoyaflow/yoya-ui';
|
|
2
|
+
import { fieldValue } from '../../../../shared/state.rows.js';
|
|
2
3
|
import { RowActionButton } from '../../../../shared/ui.buttons.js';
|
|
3
4
|
import { DictItemFormDialog } from './dict-item-form-dialog.js';
|
|
4
5
|
import { statusOptions, statusText } from '../utils/options.js';
|
|
@@ -12,24 +13,28 @@ const rowStyle = {
|
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
// 字典编辑弹窗:上半部分是字典基本信息表单,下半部分维护该字典的字典值表格。
|
|
16
|
+
// 字典值表格绑 state.items 句柄,增删改写入信号后自动刷新,不需要手动重建行。
|
|
15
17
|
export function DictEditorDialog({ state, onSubmit }) {
|
|
16
18
|
const dialog = vDialog();
|
|
17
19
|
const itemDialog = DictItemFormDialog({ onSubmit: saveItem });
|
|
18
20
|
let editingId = null;
|
|
19
|
-
let itemsBody = null;
|
|
20
|
-
let itemsCountText = null;
|
|
21
|
-
let addItemButton = null;
|
|
22
21
|
let form = null;
|
|
23
22
|
|
|
24
23
|
dialog.style('maxWidth', 'min(92vw, 880px)');
|
|
25
24
|
|
|
26
25
|
function open(type = null) {
|
|
27
26
|
editingId = type?.id ?? null;
|
|
28
|
-
state.
|
|
27
|
+
// 写入 state.items(ref)即刷新弹窗里的字典值表格
|
|
28
|
+
state.selectType(editingId);
|
|
29
|
+
|
|
30
|
+
const name = fieldValue(type?.name) ?? '';
|
|
31
|
+
const code = fieldValue(type?.code) ?? '';
|
|
32
|
+
const status = fieldValue(type?.status) ?? 'active';
|
|
33
|
+
const remark = fieldValue(type?.remark) ?? '';
|
|
29
34
|
|
|
30
35
|
dialog.content((content) => {
|
|
31
36
|
content.vstack({ gap: '16px' }, (stack) => {
|
|
32
|
-
stack.h3(type ? `编辑字典「${
|
|
37
|
+
stack.h3(type ? `编辑字典「${name}」` : '新增字典');
|
|
33
38
|
|
|
34
39
|
stack.vForm((editor) => {
|
|
35
40
|
form = editor;
|
|
@@ -44,30 +49,24 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
44
49
|
editor.vFormItem((item) => {
|
|
45
50
|
item.styles(rowStyle);
|
|
46
51
|
item.label('名称').name('name').required({ message: '请填写名称' });
|
|
47
|
-
item.control((control) => control.vInput({ name: 'name', value:
|
|
52
|
+
item.control((control) => control.vInput({ name: 'name', value: name }));
|
|
48
53
|
});
|
|
49
54
|
editor.vFormItem((item) => {
|
|
50
55
|
item.styles(rowStyle);
|
|
51
56
|
item.label('编码').name('code').required({ message: '请填写编码' });
|
|
52
|
-
item.control((control) => control.vInput({ name: 'code', value:
|
|
57
|
+
item.control((control) => control.vInput({ name: 'code', value: code }));
|
|
53
58
|
});
|
|
54
59
|
editor.vFormItem((item) => {
|
|
55
60
|
item.styles(rowStyle);
|
|
56
61
|
item.label('状态').name('status');
|
|
57
62
|
item.control((control) =>
|
|
58
|
-
control.vSelect({
|
|
59
|
-
name: 'status',
|
|
60
|
-
options: statusOptions,
|
|
61
|
-
value: type?.status ?? 'active'
|
|
62
|
-
})
|
|
63
|
+
control.vSelect({ name: 'status', options: statusOptions, value: status })
|
|
63
64
|
);
|
|
64
65
|
});
|
|
65
66
|
editor.vFormItem((item) => {
|
|
66
67
|
item.styles(rowStyle);
|
|
67
68
|
item.label('备注').name('remark');
|
|
68
|
-
item.control((control) =>
|
|
69
|
-
control.vInput({ name: 'remark', value: type?.remark ?? '' })
|
|
70
|
-
);
|
|
69
|
+
item.control((control) => control.vInput({ name: 'remark', value: remark }));
|
|
71
70
|
});
|
|
72
71
|
});
|
|
73
72
|
|
|
@@ -75,7 +74,6 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
75
74
|
section.vstack({ gap: '10px' }, (items) => {
|
|
76
75
|
items.hstack({ alignItems: 'center', gap: '10px' }, (toolbar) => {
|
|
77
76
|
toolbar.vButton('新增字典值', (btn) => {
|
|
78
|
-
addItemButton = btn;
|
|
79
77
|
btn.variant('primary');
|
|
80
78
|
btn.disabled(editingId === null);
|
|
81
79
|
btn.on('click', () => itemDialog.open(null));
|
|
@@ -83,33 +81,33 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
83
81
|
toolbar.spacer();
|
|
84
82
|
toolbar.span((count) => {
|
|
85
83
|
count.style('color', 'var(--yoya-color-text-muted, #64748b)');
|
|
86
|
-
|
|
87
|
-
syncItemsCount();
|
|
84
|
+
count.child(vText(computed(() => `共 ${state.items.value.length} 条`)));
|
|
88
85
|
});
|
|
89
86
|
});
|
|
90
87
|
items.p((hint) => {
|
|
91
88
|
hint.style('color', 'var(--yoya-color-text-muted, #64748b)');
|
|
92
|
-
hint.
|
|
89
|
+
hint.child(
|
|
93
90
|
editingId === null ? '保存字典类型后即可添加字典值。' : '维护当前字典的字典值。'
|
|
94
91
|
);
|
|
95
92
|
});
|
|
96
|
-
items.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
row.vTh('操作');
|
|
105
|
-
});
|
|
93
|
+
items.vTable((table) => {
|
|
94
|
+
table.vThead((head) => {
|
|
95
|
+
head.vTr((row) => {
|
|
96
|
+
row.vTh('标签');
|
|
97
|
+
row.vTh('值');
|
|
98
|
+
row.vTh('排序');
|
|
99
|
+
row.vTh('状态');
|
|
100
|
+
row.vTh('操作');
|
|
106
101
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
});
|
|
103
|
+
table.vTbody((tbody) => {
|
|
104
|
+
tbody.keyed(
|
|
105
|
+
state.items,
|
|
106
|
+
(item) => item.id,
|
|
107
|
+
(item) => buildItemRow(item)
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
113
111
|
});
|
|
114
112
|
});
|
|
115
113
|
|
|
@@ -128,48 +126,32 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
128
126
|
dialog.open(true);
|
|
129
127
|
}
|
|
130
128
|
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
RowActionButton('删除', (btn) => {
|
|
149
|
-
btn.variant('danger');
|
|
150
|
-
btn.on('click', () => askRemoveItem(item));
|
|
151
|
-
})
|
|
152
|
-
);
|
|
153
|
-
});
|
|
129
|
+
function buildItemRow(item) {
|
|
130
|
+
return vTr((row) => {
|
|
131
|
+
row.vTd(vText(item.label));
|
|
132
|
+
row.vTd(vText(item.value));
|
|
133
|
+
row.vTd(vText(computed(() => String(item.sort.value))));
|
|
134
|
+
row.vTd(vText(computed(() => statusText[item.status.value] ?? item.status.value)));
|
|
135
|
+
row.vTd((cell) => {
|
|
136
|
+
cell.hstack({ gap: '8px' }, (actions) => {
|
|
137
|
+
actions.child(
|
|
138
|
+
RowActionButton('编辑', (btn) => btn.on('click', () => itemDialog.open(item)))
|
|
139
|
+
);
|
|
140
|
+
actions.child(
|
|
141
|
+
RowActionButton('删除', (btn) => {
|
|
142
|
+
btn.variant('danger');
|
|
143
|
+
btn.on('click', () => askRemoveItem(item));
|
|
144
|
+
})
|
|
145
|
+
);
|
|
154
146
|
});
|
|
155
147
|
});
|
|
156
148
|
});
|
|
157
|
-
syncItemsCount();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function syncItemsCount() {
|
|
161
|
-
if (itemsCountText) {
|
|
162
|
-
itemsCountText.textContent(`共 ${state.items().length} 条`);
|
|
163
|
-
}
|
|
164
|
-
if (addItemButton) {
|
|
165
|
-
addItemButton.disabled(editingId === null);
|
|
166
|
-
}
|
|
167
149
|
}
|
|
168
150
|
|
|
169
151
|
async function askRemoveItem(item) {
|
|
170
152
|
const ok = await vConfirm({
|
|
171
153
|
title: '删除字典值',
|
|
172
|
-
content: `确定删除字典值「${item.label}」?`,
|
|
154
|
+
content: `确定删除字典值「${item.label.value}」?`,
|
|
173
155
|
danger: true,
|
|
174
156
|
confirmText: '删除'
|
|
175
157
|
});
|
|
@@ -177,8 +159,7 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
177
159
|
return;
|
|
178
160
|
}
|
|
179
161
|
await state.removeItem(item.id);
|
|
180
|
-
toast.success(
|
|
181
|
-
renderItems();
|
|
162
|
+
toast.success(`已删除字典值 ${item.label.value}`);
|
|
182
163
|
}
|
|
183
164
|
|
|
184
165
|
async function saveItem(itemId, payload) {
|
|
@@ -189,7 +170,6 @@ export function DictEditorDialog({ state, onSubmit }) {
|
|
|
189
170
|
await state.editItem(itemId, payload);
|
|
190
171
|
toast.success('已更新字典值');
|
|
191
172
|
}
|
|
192
|
-
renderItems();
|
|
193
173
|
}
|
|
194
174
|
|
|
195
175
|
function save() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { vDialog } from '@yoyaflow/yoya-ui';
|
|
2
|
+
import { fieldValue } from '../../../../shared/state.rows.js';
|
|
2
3
|
import { statusOptions } from '../utils/options.js';
|
|
3
4
|
|
|
4
5
|
const rowStyle = {
|
|
@@ -15,6 +16,10 @@ export function DictItemFormDialog({ onSubmit }) {
|
|
|
15
16
|
|
|
16
17
|
function open(item = null) {
|
|
17
18
|
editingId = item?.id ?? null;
|
|
19
|
+
const label = fieldValue(item?.label) ?? '';
|
|
20
|
+
const value = fieldValue(item?.value) ?? '';
|
|
21
|
+
const sort = fieldValue(item?.sort) ?? 0;
|
|
22
|
+
const status = fieldValue(item?.status) ?? 'active';
|
|
18
23
|
dialog.content((content) => {
|
|
19
24
|
content.vForm((form) => {
|
|
20
25
|
form.styles({
|
|
@@ -28,19 +33,17 @@ export function DictItemFormDialog({ onSubmit }) {
|
|
|
28
33
|
form.vFormItem((field) => {
|
|
29
34
|
field.styles(rowStyle);
|
|
30
35
|
field.label('标签').name('label').required({ message: '请填写标签' });
|
|
31
|
-
field.control((editor) => editor.vInput({ name: 'label', value:
|
|
36
|
+
field.control((editor) => editor.vInput({ name: 'label', value: label }));
|
|
32
37
|
});
|
|
33
38
|
form.vFormItem((field) => {
|
|
34
39
|
field.styles(rowStyle);
|
|
35
40
|
field.label('值').name('value').required({ message: '请填写值' });
|
|
36
|
-
field.control((editor) => editor.vInput({ name: 'value', value
|
|
41
|
+
field.control((editor) => editor.vInput({ name: 'value', value }));
|
|
37
42
|
});
|
|
38
43
|
form.vFormItem((field) => {
|
|
39
44
|
field.styles(rowStyle);
|
|
40
45
|
field.label('排序').name('sort');
|
|
41
|
-
field.control((editor) =>
|
|
42
|
-
editor.vInput({ name: 'sort', value: String(item?.sort ?? 0) })
|
|
43
|
-
);
|
|
46
|
+
field.control((editor) => editor.vInput({ name: 'sort', value: String(sort) }));
|
|
44
47
|
});
|
|
45
48
|
form.vFormItem((field) => {
|
|
46
49
|
field.styles(rowStyle);
|
|
@@ -49,7 +52,7 @@ export function DictItemFormDialog({ onSubmit }) {
|
|
|
49
52
|
editor.vSelect({
|
|
50
53
|
name: 'status',
|
|
51
54
|
options: statusOptions,
|
|
52
|
-
value:
|
|
55
|
+
value: status
|
|
53
56
|
})
|
|
54
57
|
);
|
|
55
58
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { toast, vConfirm, vPagination,
|
|
1
|
+
import { computed, toast, vConfirm, vPagination, vText, vTr, vstack } from '@yoyaflow/yoya-ui';
|
|
2
2
|
import { RowActionButton } from '../../../../shared/ui.buttons.js';
|
|
3
3
|
import DictsPageState from '../api/dict.state.js';
|
|
4
4
|
import { DictEditorDialog } from '../components/dict-editor-dialog.js';
|
|
@@ -13,58 +13,33 @@ export function DictListPage() {
|
|
|
13
13
|
onChange({ page: nextPage, pageSize: nextSize }) {
|
|
14
14
|
state.setPage(nextPage);
|
|
15
15
|
state.setPageSize(nextSize);
|
|
16
|
-
|
|
16
|
+
load();
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
|
-
let typesBody = null;
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
load();
|
|
21
|
+
|
|
22
|
+
// vPagination 是命令式组件(不是信号感知的):数据到位后由动作同步一次
|
|
23
|
+
async function load() {
|
|
24
|
+
await state.loadTypes();
|
|
23
25
|
syncPagination();
|
|
24
|
-
}
|
|
25
|
-
state.loadTypes();
|
|
26
|
+
}
|
|
26
27
|
|
|
27
28
|
function syncPagination() {
|
|
29
|
+
const total = state.total.value;
|
|
30
|
+
const pageSize = state.pageSize.value;
|
|
28
31
|
pagination.update({
|
|
29
|
-
page: state.page
|
|
30
|
-
pageSize
|
|
31
|
-
total
|
|
32
|
-
totalPages: Math.max(1, Math.ceil(
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function renderTypes() {
|
|
37
|
-
if (!typesBody) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
typesBody.children().forEach((child) => child.destroy());
|
|
41
|
-
state.types().forEach((type) => {
|
|
42
|
-
typesBody.vTr((row) => {
|
|
43
|
-
row.vTd(type.name);
|
|
44
|
-
row.vTd(type.code);
|
|
45
|
-
row.vTd(statusText[type.status] ?? type.status);
|
|
46
|
-
row.vTd(type.remark || '—');
|
|
47
|
-
row.vTd((cell) => {
|
|
48
|
-
cell.hstack({ gap: '8px' }, (actions) => {
|
|
49
|
-
actions.child(
|
|
50
|
-
RowActionButton('编辑', (btn) => btn.on('click', () => editorDialog.open(type)))
|
|
51
|
-
);
|
|
52
|
-
actions.child(
|
|
53
|
-
RowActionButton('删除', (btn) => {
|
|
54
|
-
btn.variant('danger');
|
|
55
|
-
btn.on('click', () => askRemoveType(type));
|
|
56
|
-
})
|
|
57
|
-
);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
|
32
|
+
page: state.page.value,
|
|
33
|
+
pageSize,
|
|
34
|
+
total,
|
|
35
|
+
totalPages: Math.max(1, Math.ceil(total / pageSize))
|
|
61
36
|
});
|
|
62
37
|
}
|
|
63
38
|
|
|
64
39
|
async function askRemoveType(type) {
|
|
65
40
|
const ok = await vConfirm({
|
|
66
41
|
title: '删除字典',
|
|
67
|
-
content: `确定删除字典「${type.name}」及其所有字典值?`,
|
|
42
|
+
content: `确定删除字典「${type.name.value}」及其所有字典值?`,
|
|
68
43
|
danger: true,
|
|
69
44
|
confirmText: '删除'
|
|
70
45
|
});
|
|
@@ -72,7 +47,7 @@ export function DictListPage() {
|
|
|
72
47
|
return;
|
|
73
48
|
}
|
|
74
49
|
await state.removeType(type.id);
|
|
75
|
-
toast.success(`已删除 ${type.name}`);
|
|
50
|
+
toast.success(`已删除 ${type.name.value}`);
|
|
76
51
|
}
|
|
77
52
|
|
|
78
53
|
async function saveType(editingId, payload) {
|
|
@@ -85,6 +60,28 @@ export function DictListPage() {
|
|
|
85
60
|
}
|
|
86
61
|
}
|
|
87
62
|
|
|
63
|
+
function buildTypeRow(type) {
|
|
64
|
+
return vTr((row) => {
|
|
65
|
+
row.vTd(vText(type.name));
|
|
66
|
+
row.vTd(vText(type.code));
|
|
67
|
+
row.vTd(vText(computed(() => statusText[type.status.value] ?? type.status.value)));
|
|
68
|
+
row.vTd(vText(computed(() => type.remark.value || '—')));
|
|
69
|
+
row.vTd((cell) => {
|
|
70
|
+
cell.hstack({ gap: '8px' }, (actions) => {
|
|
71
|
+
actions.child(
|
|
72
|
+
RowActionButton('编辑', (btn) => btn.on('click', () => editorDialog.open(type)))
|
|
73
|
+
);
|
|
74
|
+
actions.child(
|
|
75
|
+
RowActionButton('删除', (btn) => {
|
|
76
|
+
btn.variant('danger');
|
|
77
|
+
btn.on('click', () => askRemoveType(type));
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
88
85
|
return {
|
|
89
86
|
render() {
|
|
90
87
|
return vstack({ gap: '16px' }, (stack) => {
|
|
@@ -99,29 +96,30 @@ export function DictListPage() {
|
|
|
99
96
|
toolbar.spacer();
|
|
100
97
|
toolbar.span((hint) => {
|
|
101
98
|
hint.style('color', 'var(--yoya-color-text-muted, #64748b)');
|
|
102
|
-
hint.
|
|
99
|
+
hint.child('点击「编辑」可在弹窗中维护基本信息与字典值');
|
|
103
100
|
});
|
|
104
101
|
});
|
|
105
102
|
});
|
|
106
103
|
card.vCardBody((body) => {
|
|
107
104
|
body.vstack({ gap: '12px' }, (content) => {
|
|
108
|
-
content.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
row.vTh('操作');
|
|
117
|
-
});
|
|
105
|
+
content.vTable((table) => {
|
|
106
|
+
table.vThead((head) => {
|
|
107
|
+
head.vTr((row) => {
|
|
108
|
+
row.vTh('名称');
|
|
109
|
+
row.vTh('编码');
|
|
110
|
+
row.vTh('状态');
|
|
111
|
+
row.vTh('备注');
|
|
112
|
+
row.vTh('操作');
|
|
118
113
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
});
|
|
115
|
+
table.vTbody((tbody) => {
|
|
116
|
+
tbody.keyed(
|
|
117
|
+
state.types,
|
|
118
|
+
(type) => type.id,
|
|
119
|
+
(type) => buildTypeRow(type)
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
125
123
|
content.child(pagination);
|
|
126
124
|
});
|
|
127
125
|
});
|
|
@@ -130,7 +128,7 @@ export function DictListPage() {
|
|
|
130
128
|
});
|
|
131
129
|
},
|
|
132
130
|
refresh() {
|
|
133
|
-
return
|
|
131
|
+
return load();
|
|
134
132
|
}
|
|
135
133
|
};
|
|
136
134
|
}
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/** 页面数据状态类类型声明(对应 member.state.js)。 */
|
|
2
|
+
import type { SignalHandle } from '@yoyaflow/yoya-ui';
|
|
3
|
+
import type { Result } from '@yoyaflow/yoya-ui/api';
|
|
2
4
|
import Members from './member.views.js';
|
|
3
5
|
|
|
4
6
|
type MemberListItem = InstanceType<typeof Members.ListItem>;
|
|
5
7
|
|
|
6
8
|
export default class MembersPageState {
|
|
7
9
|
constructor();
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
page
|
|
12
|
-
pageSize
|
|
13
|
-
|
|
10
|
+
/** 视图字段:页面与组件直接绑句柄,写入即更新 */
|
|
11
|
+
items: SignalHandle<MemberListItem[]>;
|
|
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
|
-
|
|
19
|
-
load(): Promise<unknown>;
|
|
21
|
+
load(): Promise<Result<MemberListItem[]>>;
|
|
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,51 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 页面数据状态类:要驱动视图的字段用 ref 持有——动作里写句柄即完成通知,
|
|
2
|
+
// 页面不需要订阅回调去手动 refresh。subscribe() 只留给非视图副作用(埋点 / 持久化)。
|
|
3
|
+
import { ref } from '@yoyaflow/yoya-ui';
|
|
4
|
+
import { mergeRowsByKey } from '../../../../shared/state.rows.js';
|
|
2
5
|
import MemberMgr from './member.mgr.js';
|
|
3
6
|
|
|
4
7
|
export default class MembersPageState {
|
|
5
8
|
constructor() {
|
|
6
|
-
this.
|
|
7
|
-
this.
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
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;
|
|
9
|
+
this.items = ref([]);
|
|
10
|
+
this.total = ref(0);
|
|
11
|
+
this.page = ref(1);
|
|
12
|
+
this.pageSize = ref(5);
|
|
13
|
+
this.keyword = ref('');
|
|
14
|
+
this.status = ref('');
|
|
37
15
|
}
|
|
38
16
|
|
|
39
17
|
setKeyword(value) {
|
|
40
|
-
this.
|
|
18
|
+
this.keyword.value = value;
|
|
41
19
|
}
|
|
42
20
|
|
|
43
21
|
setStatus(value) {
|
|
44
|
-
this.
|
|
22
|
+
this.status.value = value;
|
|
45
23
|
}
|
|
46
24
|
|
|
47
25
|
setPage(value) {
|
|
48
|
-
this.
|
|
26
|
+
this.page.value = Math.max(1, Number(value) || 1);
|
|
49
27
|
}
|
|
50
28
|
|
|
51
29
|
setPageSize(value) {
|
|
52
|
-
this.
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
subscribe(listener) {
|
|
56
|
-
this._listeners.add(listener);
|
|
57
|
-
return () => this._listeners.delete(listener);
|
|
30
|
+
this.pageSize.value = Math.max(1, Number(value) || 5);
|
|
58
31
|
}
|
|
59
32
|
|
|
60
33
|
async load() {
|
|
61
34
|
const result = await MemberMgr.Query({
|
|
62
|
-
page: this.
|
|
63
|
-
pageSize: this.
|
|
64
|
-
keyword: this.
|
|
65
|
-
status: this.
|
|
35
|
+
page: this.page.value,
|
|
36
|
+
pageSize: this.pageSize.value,
|
|
37
|
+
keyword: this.keyword.value,
|
|
38
|
+
status: this.status.value
|
|
66
39
|
}).submit();
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
this.
|
|
40
|
+
// 按 key 合并:能复用的行保持实例身份,表格只刷变化的格子
|
|
41
|
+
this.items.value = mergeRowsByKey(this.items.peek(), result.data);
|
|
42
|
+
this.total.value = result.total;
|
|
70
43
|
return result;
|
|
71
44
|
}
|
|
72
45
|
|
|
73
46
|
async add(payload) {
|
|
74
47
|
await MemberMgr.Create(payload).submit();
|
|
75
|
-
this.
|
|
48
|
+
this.page.value = 1;
|
|
76
49
|
await this.load();
|
|
77
50
|
return payload;
|
|
78
51
|
}
|
|
@@ -85,13 +58,9 @@ export default class MembersPageState {
|
|
|
85
58
|
|
|
86
59
|
async remove(id) {
|
|
87
60
|
await MemberMgr.Remove({ id }).submit();
|
|
88
|
-
if (this.
|
|
89
|
-
this.
|
|
61
|
+
if (this.items.peek().length === 1 && this.page.value > 1) {
|
|
62
|
+
this.page.value -= 1;
|
|
90
63
|
}
|
|
91
64
|
await this.load();
|
|
92
65
|
}
|
|
93
|
-
|
|
94
|
-
_emit() {
|
|
95
|
-
this._listeners.forEach((listener) => listener());
|
|
96
|
-
}
|
|
97
66
|
}
|
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
/** 领域结果结构类型声明(对应 member.views.js)。 */
|
|
2
|
+
import type { SignalHandle } from '@yoyaflow/yoya-ui';
|
|
3
|
+
|
|
4
|
+
export type MemberListRow = {
|
|
5
|
+
id: number;
|
|
6
|
+
name?: string;
|
|
7
|
+
email?: string;
|
|
8
|
+
role?: string;
|
|
9
|
+
status?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
2
12
|
export class ListItem {
|
|
13
|
+
/** key:普通值,不能是句柄 */
|
|
3
14
|
id: number;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
/** 行内热字段:句柄写入即刷新对应单元格 */
|
|
16
|
+
name: SignalHandle<string>;
|
|
17
|
+
email: SignalHandle<string>;
|
|
18
|
+
role: SignalHandle<string>;
|
|
19
|
+
status: SignalHandle<string>;
|
|
20
|
+
constructor(row?: MemberListRow);
|
|
21
|
+
/** 合并而非重建:实例身份不变 */
|
|
22
|
+
apply(row: MemberListRow): this;
|
|
9
23
|
}
|
|
10
24
|
|
|
11
25
|
export class Detail {
|