@fui-org/fui-cli 1.2.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fui-y8an39cn.js +420 -0
- package/dist/fui.js +1 -429
- package/package.json +7 -3
- package/skills/fui/SKILL.md +3 -3
- package/skills/fui-skill-cli/SKILL.md +139 -0
- package/skills/fui-skill-cli/references/INDEX.md +110 -0
- package/skills/fui-skill-cli/references/advanced-techniques.md +168 -0
- package/skills/fui-skill-cli/references/coding-standards.md +112 -0
- package/skills/fui-skill-cli/references/component-design.md +448 -0
- package/skills/fui-skill-cli/references/component-quickref.md +78 -0
- package/skills/fui-skill-cli/references/component-table.md +248 -0
- package/skills/fui-skill-cli/references/components-dialog.md +191 -0
- package/skills/fui-skill-cli/references/components-display.md +141 -0
- package/skills/fui-skill-cli/references/components-echart.md +316 -0
- package/skills/fui-skill-cli/references/components-input.md +335 -0
- package/skills/fui-skill-cli/references/controls-patterns.md +701 -0
- package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +137 -0
- package/skills/fui-skill-cli/references/db-table-design.md +73 -0
- package/skills/fui-skill-cli/references/db-workflow.md +288 -0
- package/skills/fui-skill-cli/references/default-function.md +425 -0
- package/skills/fui-skill-cli/references/design-modes.md +57 -0
- package/skills/fui-skill-cli/references/echart-templates.md +489 -0
- package/skills/fui-skill-cli/references/fastproject.md +99 -0
- package/skills/fui-skill-cli/references/fsheet.md +203 -0
- package/skills/fui-skill-cli/references/fullstack-workflow.md +313 -0
- package/skills/fui-skill-cli/references/module-data-patterns.md +117 -0
- package/skills/fui-skill-cli/references/module-json-anatomy.md +132 -0
- package/skills/fui-skill-cli/references/module-structure.md +141 -0
- package/skills/fui-skill-cli/references/new-session.md +85 -0
- package/skills/fui-skill-cli/references/pdfmake.md +60 -0
- package/skills/fui-skill-cli/references/permission-system.md +150 -0
- package/skills/fui-skill-cli/references/platform-architecture.md +269 -0
- package/skills/fui-skill-cli/references/project-config.md +303 -0
- package/skills/fui-skill-cli/references/project-provisioning.md +278 -0
- package/skills/fui-skill-cli/references/script-map.md +262 -0
- package/skills/fui-skill-cli/references/sql-clr-functions.md +225 -0
- package/skills/fui-skill-cli/references/system-design.md +89 -0
- package/skills/fui-skill-cli/references/tapi-file-api.md +185 -0
- package/skills/fui-skill-cli/references/tapi-permission-patterns.md +156 -0
- package/skills/fui-skill-cli/references/tapi-reference.md +474 -0
- package/skills/fui-skill-cli/references/tools-registry.md +84 -0
- package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +321 -0
- package/skills/fui-skill-cli/references/ui-dialog-patterns.md +255 -0
- package/skills/fui-skill-cli/references/ui-layout-patterns.md +176 -0
- package/skills/fui-skill-cli/references/ui-patterns.md +303 -0
- package/skills/fui-skill-cli/references/ui-screenshot-review.md +95 -0
- package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +318 -0
- package/skills/fui-skill-cli/references/ui-templates.md +22 -0
- package/skills/fui-skill-cli/references/verification.md +236 -0
- package/skills/fui-skill-cli/references/watcher-patterns.md +163 -0
- package/skills/fui-skill-cli/references/websocket-realtime.md +271 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# FUI Layout Patterns — page-container, master-detail, switch, dynamic label
|
|
2
|
+
|
|
3
|
+
> Owns: **page layout patterns: sticky page-container, 2-column master-detail, v-switch, date init, dynamic label**.
|
|
4
|
+
|
|
5
|
+
Layout and helper-control patterns, drawn from project **security-manager**. See also: [ui-patterns.md](ui-patterns.md), [ui-table-cell-patterns.md](ui-table-cell-patterns.md), [module-structure.md](module-structure.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Date init with moment()
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
"data": [
|
|
13
|
+
{
|
|
14
|
+
"TuNgay": "moment().format('YYYY-MM-DD')",
|
|
15
|
+
"DenNgay": "moment().format('YYYY-MM-DD')"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
> The value is a JS expression string, eval'd at init. `moment()` is available in the FUI runtime.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 2. page-container + page-navigation
|
|
25
|
+
|
|
26
|
+
Use for a fixed filter bar on top with scrolling content below.
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"prop": "page-container grid-list-md fluid",
|
|
31
|
+
"rows": [
|
|
32
|
+
{
|
|
33
|
+
"prop": "page-navigation",
|
|
34
|
+
"cols": [
|
|
35
|
+
{ "el": "f-date", "attr": { "v-model": "TuNgay", "label": "Từ ngày", "date-add": 0 }, "w": "2" },
|
|
36
|
+
{ "el": "f-date", "attr": { "v-model": "DenNgay", "label": "Tới ngày", "date-add": 0 }, "w": "2" },
|
|
37
|
+
{
|
|
38
|
+
"el": "f-button",
|
|
39
|
+
"attr": {
|
|
40
|
+
"color": "primary",
|
|
41
|
+
"icon-text": "mdi-filter",
|
|
42
|
+
"label": "Tìm",
|
|
43
|
+
":action": { "CALL": "getLogData" }
|
|
44
|
+
},
|
|
45
|
+
"w": 3
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"prop": "",
|
|
51
|
+
"cols": [{ "el": "f-table", "attr": { "label": "Log viewer", ":items": "LogData" } }]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- `page-container` + `page-navigation` → sticky top filter bar
|
|
58
|
+
- `f-table` without `:update-api` → readonly; without `:headers` → headers auto-built from data
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. Dynamic label from state
|
|
63
|
+
|
|
64
|
+
Use `:label` with an expression to show context.
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"el": "f-title",
|
|
69
|
+
"attr": {
|
|
70
|
+
":label": "sFullName + ` - ` + sDepartment"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"el": "f-table",
|
|
78
|
+
"attr": {
|
|
79
|
+
":label": "`${fTableTitle==''? 'Computer list' : fTableTitle}`"
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 4. v-switch — toggle with label
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"el": "v-layout",
|
|
91
|
+
"attr": {},
|
|
92
|
+
"innerHTML": [
|
|
93
|
+
{
|
|
94
|
+
"el": "v-switch",
|
|
95
|
+
"attr": {
|
|
96
|
+
"v-model": "switchResetPassword",
|
|
97
|
+
":required": false,
|
|
98
|
+
"label": "Đặt lại mật khẩu"
|
|
99
|
+
},
|
|
100
|
+
"w": "12"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Combine with `v-if` to show/hide extra fields:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"el": "v-layout",
|
|
111
|
+
"attr": { "v-if": "switchResetPassword" },
|
|
112
|
+
"innerHTML": [...]
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## 5. Two-column master-detail
|
|
119
|
+
|
|
120
|
+
Left table row pick → watch → load right table. No new window needed.
|
|
121
|
+
|
|
122
|
+
> Detail button in a table cell: choose `t-button` vs `t-menu` per [ui-table-cell-patterns.md](ui-table-cell-patterns.md) — for master-detail the action is "view/load related data", so use `t-button`.
|
|
123
|
+
|
|
124
|
+
**`data[]` and watch:**
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
"data": [
|
|
128
|
+
{ "dsParent": [], "dsDetail": [], "selectedId": null },
|
|
129
|
+
{
|
|
130
|
+
"getParent": { "API": "/SM_Parent_Select", "OUT": "dsParent" },
|
|
131
|
+
"getDetail": {
|
|
132
|
+
"API": "/SM_Detail_Select",
|
|
133
|
+
"IN": { "ParentID": "selectedId" },
|
|
134
|
+
"OUT": "dsDetail"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{ "CALL": "getParent" }
|
|
138
|
+
],
|
|
139
|
+
"watch": {
|
|
140
|
+
"selectedId": { "CALL": "getDetail" }
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**`t-button` column in the left table:**
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"text": "Chi tiết",
|
|
149
|
+
"value": "ParentID",
|
|
150
|
+
"align": "center",
|
|
151
|
+
"el": "t-button",
|
|
152
|
+
"attr": {
|
|
153
|
+
"icon-text": "mdi-eye",
|
|
154
|
+
"color": "primary",
|
|
155
|
+
":outlined": true,
|
|
156
|
+
":action": { "selectedId": "item.ParentID" }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**2-column layout:**
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"prop": "fluid grid-list-md",
|
|
166
|
+
"rows": [
|
|
167
|
+
{
|
|
168
|
+
"prop": "row wrap",
|
|
169
|
+
"cols": [
|
|
170
|
+
{ "w": 5, "el": "f-table", "attr": { ":items": "dsParent", "item-key": "ParentID", ":headers": [...] } },
|
|
171
|
+
{ "w": 7, "el": "f-table", "attr": { ":items": "dsDetail", "item-key": "DetailID", ":headers": [...] } }
|
|
172
|
+
]
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
```
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
# FUI UI Patterns — Core
|
|
2
|
+
|
|
3
|
+
> Owns: **core UI patterns: toolbar/filter, permission gate, UI design checklist**. Table/dialog/cross-window/layout live in separate `ui-*` files.
|
|
4
|
+
|
|
5
|
+
Patterns taken from project **security-manager** — use as reference for new modules. Load specialised files as needed:
|
|
6
|
+
|
|
7
|
+
| Need | File |
|
|
8
|
+
| -------------------------------------------------------------------------------------- | -------------------------------------------------------- |
|
|
9
|
+
| f-table + in-cell controls (t-check/t-button/t-select/t-menu, ctrl-update, chip-group) | [ui-table-cell-patterns.md](ui-table-cell-patterns.md) |
|
|
10
|
+
| Dialog/popup form (hidden-container, open dialog with row, checkbox-list) | [ui-dialog-patterns.md](ui-dialog-patterns.md) |
|
|
11
|
+
| Sub-window & data exchange (`openWindow`, `#PARENT`/`#Win`) | [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md) |
|
|
12
|
+
| Layout (page-container, master-detail, v-switch, date init, dynamic label) | [ui-layout-patterns.md](ui-layout-patterns.md) |
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 1. `data[]` structure
|
|
17
|
+
|
|
18
|
+
`data` initialises state + actions, processed top to bottom.
|
|
19
|
+
|
|
20
|
+
- Object with **no action key** (no API, CALL, IF…) → state init
|
|
21
|
+
- Object whose **key is a function name** → named (reusable) action
|
|
22
|
+
- Trailing `{ "API": ..., "OUT": ... }` or `{ "CALL": ... }` → runs automatically on load
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
"data": [
|
|
26
|
+
{
|
|
27
|
+
"dsUser": [],
|
|
28
|
+
"dsGroup": [],
|
|
29
|
+
"sDepartment": null,
|
|
30
|
+
"dlUserInfo": false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"getDepartment": { "API": "SM_DepartmentSelectAll", "OUT": "dsDepartment" },
|
|
34
|
+
"getGroup": { "API": "SM_Group_SelectAll", "OUT": "dsGroup" },
|
|
35
|
+
"getUser": {
|
|
36
|
+
"API": "SM_Users_SelectByDepGroup",
|
|
37
|
+
"IN": { "GroupID": "sGroup", "DepartmentID": "sDepartment" },
|
|
38
|
+
"OUT": "dsUser"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{ "CALL": "getDepartment" },
|
|
42
|
+
{ "CALL": "getGroup" }
|
|
43
|
+
]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> **Auto-startup**: trailing CALLs in `data[]` run on module load — no event handler needed.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 2. Cascading API init
|
|
51
|
+
|
|
52
|
+
`CALLBACK` sets a value from the first API result; the next API runs via `watch`.
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
"data": [
|
|
56
|
+
{ "dsModule": [], "sModuleID": null },
|
|
57
|
+
{
|
|
58
|
+
"API": "/SM_Modules_SelectAll",
|
|
59
|
+
"OUT": "dsModule",
|
|
60
|
+
"CALLBACK": { "sModuleID": "dsModule[0].ModuleID" }
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"watch": {
|
|
64
|
+
"sModuleID": { "CALL": "getFunctionRight" }
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> CALLBACK sets `sModuleID` → watch fires `getFunctionRight`. No manual call.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 3. Watch — cascading filter
|
|
73
|
+
|
|
74
|
+
Filter change → reload. Several actions → array of CALL.
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
"watch": {
|
|
78
|
+
"sDepartment": { "CALL": "getUser" },
|
|
79
|
+
"sGroup": { "CALL": "getUser" },
|
|
80
|
+
"sModuleID": [
|
|
81
|
+
{ "CALL": "getUserModule" },
|
|
82
|
+
{ "CALL": "getSysRight" },
|
|
83
|
+
{ "CALL": "getFunctionRight" }
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## 4. Toolbar / filter row
|
|
91
|
+
|
|
92
|
+
Row of filter fields + action buttons; `v-spacer` pushes the button group right.
|
|
93
|
+
|
|
94
|
+
> **Rule: EVERY column is `"col": "shrink"` except `v-spacer`.** A non-`shrink` column takes a share of free space — including an empty-`w` column and buttons. Missing `shrink` → buttons stretch, gaps appear between buttons, and the trailing group doesn't reach the right edge even with `v-spacer`. Standard template + measurements: [controls-patterns.md](controls-patterns.md) §Toolbar row.
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"prop": "",
|
|
99
|
+
"cols": [
|
|
100
|
+
{
|
|
101
|
+
"el": "v-autocomplete",
|
|
102
|
+
"col": { "class": "shrink" },
|
|
103
|
+
"attr": {
|
|
104
|
+
"class": "flex-md-grow-0",
|
|
105
|
+
"style": "width:200px",
|
|
106
|
+
"v-model": "sGroup",
|
|
107
|
+
"label": "Group",
|
|
108
|
+
":items": "dsGroup",
|
|
109
|
+
"item-text": "GroupName",
|
|
110
|
+
"item-value": "GroupID",
|
|
111
|
+
":required": false
|
|
112
|
+
},
|
|
113
|
+
"w": "200"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"el": "v-text-field",
|
|
117
|
+
"col": { "class": "shrink" },
|
|
118
|
+
"attr": {
|
|
119
|
+
"class": "flex-md-grow-0",
|
|
120
|
+
"style": "width:200px",
|
|
121
|
+
"v-model": "UserID",
|
|
122
|
+
"label": "User ID",
|
|
123
|
+
"clearable": ""
|
|
124
|
+
},
|
|
125
|
+
"w": "200"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"el": "f-button",
|
|
129
|
+
"col": { "class": "shrink" },
|
|
130
|
+
"attr": {
|
|
131
|
+
":checkvalid": false,
|
|
132
|
+
"color": "primary",
|
|
133
|
+
"label": "Tìm",
|
|
134
|
+
"icon-text": "mdi-magnify",
|
|
135
|
+
":action": { "CALL": "getUser" }
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{ "el": "v-spacer", "attr": {} },
|
|
139
|
+
{
|
|
140
|
+
"el": "f-button",
|
|
141
|
+
"col": { "v-if": "vueData.user.SystemRight==9", "class": "shrink" },
|
|
142
|
+
"attr": {
|
|
143
|
+
":checkvalid": false,
|
|
144
|
+
":disabled": "!sGroup",
|
|
145
|
+
"color": "primary",
|
|
146
|
+
"label": "Add User",
|
|
147
|
+
"icon-text": "mdi-account-multiple-plus",
|
|
148
|
+
":action": [{ "addNew": 1, "sUserID": "", "sUserName": "" }, { "dlUserInfo": true }]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- Filter fields: `w` in px (`>= 25`) + `col: "shrink"`. The `attr: { class: "flex-md-grow-0", style: "width:Xpx" }` pair above is **redundant** — `w` + `shrink` already gives that width (measured: `w:150` + `shrink` → exactly 150px on V2 and V3). It stays only because the example is copied from production; omit it in new code.
|
|
156
|
+
- Search button right after the fields, also `col: "shrink"` (a non-shrink button stretches).
|
|
157
|
+
- **Always `class: "shrink"` / `"col": "shrink"`, never `shrink: true`** — the bare attribute works only on V2; on V3 it's inert and the column goes full width.
|
|
158
|
+
- `v-spacer` is the **only** non-`shrink` column — it must grow to push the following group right.
|
|
159
|
+
- Admin Add/Delete buttons: after the spacer, permission `v-if` — `col: { "v-if": "…", "class": "shrink" }` (object form when extra keys are needed; keep `shrink`).
|
|
160
|
+
- Right-align the whole row without `v-spacer`: `"prop": "row justify-end"`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 5. Permission gating
|
|
165
|
+
|
|
166
|
+
Show/hide by the user's `SystemRight`.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
"col": { "v-if": "vueData.user.SystemRight>1" }
|
|
170
|
+
"col": { "v-if": "vueData.user.SystemRight==9" }
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
- `>1` — any user with rights (not the lowest level)
|
|
174
|
+
- `==9` — top admin only
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 6. IF/THEN/ELSE
|
|
179
|
+
|
|
180
|
+
Branching inside an action or CALLBACK.
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"IF": "TypeLoad==1",
|
|
185
|
+
"THEN": { "CALL": "getComputer" },
|
|
186
|
+
"ELSE": {
|
|
187
|
+
"IF": "TypeLoad==2",
|
|
188
|
+
"THEN": { "CALL": "getComputerDinied" },
|
|
189
|
+
"ELSE": { "CALL": "getComputerNotVisited" }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- Condition: JS expression using vueData fields directly (no `vueData.` prefix).
|
|
195
|
+
- Nest IF/ELSE for multi-branch switch.
|
|
196
|
+
|
|
197
|
+
Real use: module with several view modes (`TypeLoad`) — after an update, refresh the list currently shown.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
"data": [
|
|
201
|
+
{ "TypeLoad": 1 }
|
|
202
|
+
]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 7. CALLBACK chaining
|
|
208
|
+
|
|
209
|
+
Array CALLBACK → runs in order.
|
|
210
|
+
|
|
211
|
+
> **Several commands MUST be an array, one `{}` per command.** Merging `CALL` + `MESS` + `EXE` into one object runs only **one** (priority `API` → `CALL` → `EXE` → `FUN` → `IF` → `MESS` → `MESSBOX`, each branch `return`s immediately); the rest are dropped silently — no error, no `[Vue warn]`. Full rule + runtime reason: [controls-patterns.md](controls-patterns.md) §CORE RULE.
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
"CALLBACK": [
|
|
215
|
+
{ "MESS": "Cập nhật thành công." },
|
|
216
|
+
{ "dlUserInfo": false },
|
|
217
|
+
{ "CALL": "getUser" }
|
|
218
|
+
]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
"CALLBACK": [
|
|
223
|
+
{ "CALL": "getUserModule" },
|
|
224
|
+
{ "sUserDeptSelected": [], "dlAddUser": false },
|
|
225
|
+
{ "MESS": "Thêm user thành công." }
|
|
226
|
+
]
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Usual order: reload data → reset state → show message.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## 8. `set` — module-level config
|
|
234
|
+
|
|
235
|
+
`set` at module.json root configures the whole module.
|
|
236
|
+
|
|
237
|
+
```json
|
|
238
|
+
"set": {
|
|
239
|
+
"menu": false
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
- `menu: false` — hide navigation menu (sub-windows, log viewers).
|
|
244
|
+
|
|
245
|
+
> Module opened via `openWindow`: declare `"set": { "menu": false }` in the child module's module.json — **don't** pass `?menu=0` in the URL. Full pattern (incl. setting it dynamically from `data[]`): [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md).
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## 9. CALLBACK with JS expression
|
|
250
|
+
|
|
251
|
+
`{{ expression }}` in CALLBACK runs complex code after the API.
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
"CALLBACK": "{{ _.forEach(dsUsers, (x)=> x.FunctionName = JSON.parse(x.FunctionName)) }}"
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
"CALLBACK": "{{ _.forEach(dsUserFunction, (x)=> x.FunctionName = JSON.parse(x.FunctionName)), _.forEach(dsUserFunction, (x)=> x.UserSysItem = JSON.parse(x.UserSysItem)) }}"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
> Use when the API returns a JSON string that must be parsed to object/array before render.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## 10. script.js — helpers
|
|
266
|
+
|
|
267
|
+
`script.js` holds helpers called from `v-on:click` or actions. They access `vueData` directly; use for logic plain action JSON can't express.
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
// script.js
|
|
271
|
+
function setFunction(user, fc) {
|
|
272
|
+
vueData.sFunctionSelected = [];
|
|
273
|
+
vueData.sUserSelected = [];
|
|
274
|
+
vueData.sUserSelected.push(user);
|
|
275
|
+
if (!fc) return;
|
|
276
|
+
vueData.dsFunctionRight.map((item) => {
|
|
277
|
+
if (fc.some((fci) => fci.FunctionID == item.FunctionID)) {
|
|
278
|
+
vueData.sFunctionSelected.push(item.FunctionID.toString());
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Called from `v-on:click` in a header:
|
|
285
|
+
|
|
286
|
+
```json
|
|
287
|
+
"v-on:click": "vueData.dlFunction = true; CALL(setFunction(item, item.FunctionName));"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## UI design checklist
|
|
293
|
+
|
|
294
|
+
> `module.json` structure checklist (`data[]` order, grid wrapper, `w`, `v-on:`, `set`, valid JSON) is in [module-json-anatomy.md](module-json-anatomy.md). Below is UI-specific only.
|
|
295
|
+
|
|
296
|
+
1. **Toolbar row**: shrink filters + v-spacer + permission-gated buttons
|
|
297
|
+
2. **f-table**: fixed-header, item-class for row highlight, correct item-key — [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
|
|
298
|
+
3. **Dialogs**: always in hidden-container, v-card[padding:0] + v-toolbar[dense flat] — [ui-dialog-patterns.md](ui-dialog-patterns.md)
|
|
299
|
+
4. **menuChucNang**: define in data[], reference via t-menu in headers — [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
|
|
300
|
+
5. **ctrl-update**: simple CRUD; manual dialog for complex forms
|
|
301
|
+
6. **CALLBACK**: chaining array — reload → reset state → MESS
|
|
302
|
+
7. **#PARENT**: set at the top of data[] before startup CALLs — [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md)
|
|
303
|
+
8. **Permission**: `v-if: "vueData.user.SystemRight>1"` for admin-only sections
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# UI review from screenshots
|
|
2
|
+
|
|
3
|
+
> Owns: **reading and assessing UI screenshots (user-sent or from `--render`)**.
|
|
4
|
+
|
|
5
|
+
Sources: (1) **user screenshot in chat** ("why is it ugly", "make it nicer", or an image + bug description); (2) **`fui module simulate --render`** — `render.png` (inline), `render-full.png`, `render-dialog.jpg`.
|
|
6
|
+
|
|
7
|
+
Act as design QA: scan with the checklist; map every finding to a **concrete code cause** (controls[i] in module.json, class/style, CSS in header.html) + fix. No vague "doesn't look good".
|
|
8
|
+
|
|
9
|
+
**MANDATORY — FIX findings, don't just report them.** Stuck/overlapping text (too little gap), misaligned controls, abnormal whitespace between controls are bugs **to fix in the same turn**, like hard rendering bugs. "Noted in the report" ≠ done. Ask the user only when design intent is unclear (e.g. should two blocks be on separate rows); otherwise fix and re-render (step 4).
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
1. **Establish context** first:
|
|
16
|
+
- App-mode (CRUD/admin — aesthetic standard `fui design read fui`) or Web-mode (landing — chosen brand DESIGN.md)?
|
|
17
|
+
- Desktop or mobile? (narrow/portrait image → judge by responsive standards)
|
|
18
|
+
- module.json in workspace? Yes → cross-check image ↔ controls to pinpoint the fix; no → comment on the image, ask for module.json if a fix is needed
|
|
19
|
+
2. **Scan with checklist A→D**, record each finding.
|
|
20
|
+
3. **Report as a table**: `Finding | Severity (bug/should fix/suggestion) | Location in image | Likely cause | Proposed fix`.
|
|
21
|
+
4. **Fix → re-render → compare** (required for every A/B finding): with workspace + Playwright, edit module.json, run `fui module simulate --render` (same viewport/device as the original if guessable), compare with the user's image; repeat until clean. `--render` auto-detects stuck/overlapping text (bounding rects, see A) and prints it in DOM AUDIT with "PHẢI sửa, không được bỏ qua" — fix it this turn; don't hand it to the user.
|
|
22
|
+
|
|
23
|
+
Blurry/cropped/insufficient image → ask the user to recapture the specific area; don't guess.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Scan checklist
|
|
28
|
+
|
|
29
|
+
### A. Hard rendering bugs (must fix)
|
|
30
|
+
|
|
31
|
+
- Abnormal empty area: component didn't render (empty app, blank card, white/0-size chart)
|
|
32
|
+
- Leaked error text: `undefined`, `NaN`, `[object Object]`, exception messages, keys instead of values
|
|
33
|
+
- Horizontal overflow: horizontal scrollbar, elements clipped at the right edge
|
|
34
|
+
- Overlapping text/elements, or so close they read as one word; unintended truncation (`...`). `--render` catches most (line `⚠ text dính/chồng lấn` in DOM AUDIT) — only a safety net; still scan by eye, the heuristic misses non-text cases (icon over button, image over card...)
|
|
35
|
+
- Broken image (empty frame/broken icon), missing icon (tofu square — usually mdi font not loaded or wrong icon name)
|
|
36
|
+
- Spinner forever (v_Loading never returns to false — usually a hung API/init error)
|
|
37
|
+
|
|
38
|
+
### B. Layout, alignment, spacing
|
|
39
|
+
|
|
40
|
+
- **Alignment**: fields in a row share baseline and height (consistent `dense`/`outlined`); cards/blocks in a row share height
|
|
41
|
+
- **Semantic alignment**: text left; **numbers/currency in tables right** (`t-num`); column header matches data alignment
|
|
42
|
+
- **Button placement**: page primary action in the toolbar on the right; in a dialog the primary button (Lưu/Đồng ý) sits **right** in `v-card-actions` (pushed by `v-spacer`); no redundant "Đóng" button (D7)
|
|
43
|
+
- **Toolbar row — bloated buttons / equal gaps** = columns missing `shrink`: buttons much wider than their text, **equal** gaps, trailing group **not flush right** despite `v-spacer` ⇒ columns still `flex-grow: 1`, sharing free space like the spacer. Standard: **every column `"col": "shrink"` except `v-spacer`** — including empty-`w` columns ([controls-patterns.md](controls-patterns.md) §Toolbar row). `fui module validate` catches this before any screenshot
|
|
44
|
+
- **Consistent spacing**: Vuetify rhythm (multiples of 4px — pa-2/ma-2...); no abnormally large gaps between blocks, no blocks touching the screen edge
|
|
45
|
+
- **Data tables**: scroll ONLY inside the table (main table declares `":height": -16`), no two nested vertical page scrollbars, table header stays visible on scroll (D9)
|
|
46
|
+
- **Grid balance**: sensible column ratios in a row (Σw ≤ 12), no column squeezed so narrow content wraps badly
|
|
47
|
+
|
|
48
|
+
### C. Color & visual hierarchy (App-mode vs `fui design read fui`)
|
|
49
|
+
|
|
50
|
+
- Validator rules D1–D9: dialog toolbar **same background** as body (no color/dark); delete button **warning orange** (not red/error); dialog width in 400/550/600/650/800
|
|
51
|
+
- 1–2 accent colors per screen; no arbitrary color on cards/secondary buttons
|
|
52
|
+
- Readable contrast (light grey on white, text on colored backgrounds — report if doubtful)
|
|
53
|
+
- Status (chip/badge) semantic colors consistent: success/warning/error mean the same everywhere
|
|
54
|
+
|
|
55
|
+
### D. Density & responsive
|
|
56
|
+
|
|
57
|
+
- Consistent `dense`/compact level across the screen (no dense-here, sparse-there)
|
|
58
|
+
- Mobile image: columns wrap correctly, buttons don't overflow and aren't too small to tap, readable font, tables have a mobile plan (`mobile-breakpoint`)
|
|
59
|
+
- `viewport-fit=cover` (FUI default): content at top/bottom edges on notched screens → needs safe-area padding
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Symptom → common cause
|
|
64
|
+
|
|
65
|
+
| Symptom | Common cause | Fix |
|
|
66
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
67
|
+
| Page has vertical scroll, endless table | f-table/f-sheet without `:height` → grows with row count | Add `":height": -16` (measures `rect.top`, fills to bottom) |
|
|
68
|
+
| Table short/overflowing by a fixed amount | Old hand-computed `:height` `"$( window ).height()-N"` doesn't match the real toolbar | Replace with `":height": -16` |
|
|
69
|
+
| Form fields of uneven height | Mixed with/without `dense`, `hide-details` | Use `outlined dense hide-details` across the form |
|
|
70
|
+
| Numbers left-aligned in table | Column missing `el: "t-num"` / align | See `component-table.md` |
|
|
71
|
+
| Blank chart | Container 0-height at chart init | Give the container a concrete `:height`; `--render` audit "canvas 0-size" confirms |
|
|
72
|
+
| Square icon | Wrong `mdi-*` name or font not loaded | Check icon name; import materialdesignicons |
|
|
73
|
+
| Dialog overflowing / tiny | Non-standard width | Widths 400/550/600/650/800 (D3) |
|
|
74
|
+
| Primary button left in dialog | Missing `v-spacer` in v-card-actions | Add v-spacer before the primary button |
|
|
75
|
+
| Block touching screen edge | Missing page-container / pa-* | See `ui-layout-patterns.md` |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## FUI default meta viewport (affects UI)
|
|
80
|
+
|
|
81
|
+
FUI pages already include in `<head>` (do NOT repeat in header.html):
|
|
82
|
+
|
|
83
|
+
```html
|
|
84
|
+
<meta charset="UTF-8" />
|
|
85
|
+
<meta
|
|
86
|
+
name="viewport"
|
|
87
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Consequences:
|
|
92
|
+
|
|
93
|
+
- `user-scalable=no, maximum-scale=1` — **no pinch-zoom** → fonts/buttons must be large enough on mobile from the start.
|
|
94
|
+
- `width=device-width, initial-scale=1` — layout in true device CSS pixels; Vuetify breakpoints work normally.
|
|
95
|
+
- `viewport-fit=cover` — page extends into iOS notch/home-indicator → content at top/bottom edges needs `env(safe-area-inset-*)` padding for fullscreen/HTMLOnly.
|