@fui-org/fui-cli 1.3.2 → 2.0.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/README.md +19 -3
- package/dist/fui-bmg5pnmq.js +379 -0
- package/dist/fui.js +1 -1
- package/package.json +3 -6
- package/skills/fui/SKILL.md +9 -41
- package/skills/fui-skill/SKILL.md +95 -225
- package/skills/fui-skill/assets/projectdefaultstyle-3.0.css +555 -0
- package/skills/fui-skill/assets/projectdefaultstyle.css +207 -235
- package/skills/fui-skill/references/INDEX.md +105 -137
- package/skills/fui-skill/references/advanced-techniques.md +76 -68
- package/skills/fui-skill/references/coding-standards.md +56 -56
- package/skills/fui-skill/references/component-design.md +166 -173
- package/skills/fui-skill/references/component-quickref.md +61 -60
- package/skills/fui-skill/references/component-table.md +128 -117
- package/skills/fui-skill/references/components-dialog.md +55 -56
- package/skills/fui-skill/references/components-display.md +24 -30
- package/skills/fui-skill/references/components-echart.md +186 -261
- package/skills/fui-skill/references/components-input.md +72 -96
- package/skills/fui-skill/references/controls-patterns.md +196 -342
- package/skills/fui-skill/references/controls-styling-vocabulary.md +130 -97
- package/skills/fui-skill/references/db-table-design.md +24 -28
- package/skills/fui-skill/references/db-workflow.md +191 -390
- package/skills/fui-skill/references/default-function.md +169 -128
- package/skills/fui-skill/references/design-modes.md +35 -63
- package/skills/fui-skill/references/echart-templates.md +204 -196
- package/skills/fui-skill/references/fastproject.md +62 -60
- package/skills/fui-skill/references/fsheet.md +109 -124
- package/skills/fui-skill/references/fullstack-workflow.md +90 -128
- package/skills/fui-skill/references/module-data-patterns.md +31 -40
- package/skills/fui-skill/references/module-json-anatomy.md +47 -52
- package/skills/fui-skill/references/module-structure.md +80 -196
- package/skills/fui-skill/references/new-session.md +49 -51
- package/skills/fui-skill/references/pdfmake.md +17 -17
- package/skills/fui-skill/references/permission-system.md +89 -108
- package/skills/fui-skill/references/platform-architecture.md +128 -153
- package/skills/fui-skill/references/project-config.md +102 -134
- package/skills/fui-skill/references/project-provisioning.md +139 -244
- package/skills/fui-skill/references/script-map.md +208 -242
- package/skills/fui-skill/references/sql-clr-functions.md +98 -97
- package/skills/fui-skill/references/system-design.md +63 -88
- package/skills/fui-skill/references/tapi-file-api.md +46 -52
- package/skills/fui-skill/references/tapi-permission-patterns.md +51 -53
- package/skills/fui-skill/references/tapi-reference.md +132 -207
- package/skills/fui-skill/references/tools-registry.md +84 -460
- package/skills/fui-skill/references/ui-crosswindow-patterns.md +79 -75
- package/skills/fui-skill/references/ui-dialog-patterns.md +98 -72
- package/skills/fui-skill/references/ui-layout-patterns.md +26 -26
- package/skills/fui-skill/references/ui-patterns.md +71 -83
- package/skills/fui-skill/references/ui-screenshot-review.md +63 -62
- package/skills/fui-skill/references/ui-table-cell-patterns.md +61 -59
- package/skills/fui-skill/references/ui-templates.md +16 -23
- package/skills/fui-skill/references/verification.md +236 -246
- package/skills/fui-skill/references/watcher-patterns.md +30 -63
- package/skills/fui-skill/references/websocket-realtime.md +83 -66
- package/skills/fui-skill/scripts/component-3.0.js +298 -131
- package/skills/fui-skill/scripts/component.js +277 -271
- package/skills/fui-skill/scripts/componentTable-3.0.js +182 -53
- package/skills/fui-skill/scripts/componentTable.js +171 -49
- package/skills/fui-skill/scripts/defaultfunction-3.0.js +88 -3
- package/skills/fui-skill/scripts/defaultfunction.js +88 -3
- package/skills/fui-skill/scripts/fsheet.js +38 -0
- package/dist/fui-y8an39cn.js +0 -420
- package/skills/fui-skill/README.md +0 -112
- package/skills/fui-skill/metadata.json +0 -75
- package/skills/fui-skill-cli/SKILL.md +0 -139
- package/skills/fui-skill-cli/references/INDEX.md +0 -110
- package/skills/fui-skill-cli/references/advanced-techniques.md +0 -168
- package/skills/fui-skill-cli/references/coding-standards.md +0 -112
- package/skills/fui-skill-cli/references/component-design.md +0 -448
- package/skills/fui-skill-cli/references/component-quickref.md +0 -78
- package/skills/fui-skill-cli/references/component-table.md +0 -248
- package/skills/fui-skill-cli/references/components-dialog.md +0 -191
- package/skills/fui-skill-cli/references/components-display.md +0 -141
- package/skills/fui-skill-cli/references/components-echart.md +0 -316
- package/skills/fui-skill-cli/references/components-input.md +0 -335
- package/skills/fui-skill-cli/references/controls-patterns.md +0 -701
- package/skills/fui-skill-cli/references/controls-styling-vocabulary.md +0 -137
- package/skills/fui-skill-cli/references/db-table-design.md +0 -73
- package/skills/fui-skill-cli/references/db-workflow.md +0 -288
- package/skills/fui-skill-cli/references/default-function.md +0 -425
- package/skills/fui-skill-cli/references/design-modes.md +0 -57
- package/skills/fui-skill-cli/references/echart-templates.md +0 -489
- package/skills/fui-skill-cli/references/fastproject.md +0 -99
- package/skills/fui-skill-cli/references/fsheet.md +0 -203
- package/skills/fui-skill-cli/references/fullstack-workflow.md +0 -313
- package/skills/fui-skill-cli/references/module-data-patterns.md +0 -117
- package/skills/fui-skill-cli/references/module-json-anatomy.md +0 -132
- package/skills/fui-skill-cli/references/module-structure.md +0 -141
- package/skills/fui-skill-cli/references/new-session.md +0 -85
- package/skills/fui-skill-cli/references/pdfmake.md +0 -60
- package/skills/fui-skill-cli/references/permission-system.md +0 -150
- package/skills/fui-skill-cli/references/platform-architecture.md +0 -269
- package/skills/fui-skill-cli/references/project-config.md +0 -303
- package/skills/fui-skill-cli/references/project-provisioning.md +0 -278
- package/skills/fui-skill-cli/references/script-map.md +0 -262
- package/skills/fui-skill-cli/references/sql-clr-functions.md +0 -225
- package/skills/fui-skill-cli/references/system-design.md +0 -89
- package/skills/fui-skill-cli/references/tapi-file-api.md +0 -185
- package/skills/fui-skill-cli/references/tapi-permission-patterns.md +0 -156
- package/skills/fui-skill-cli/references/tapi-reference.md +0 -474
- package/skills/fui-skill-cli/references/tools-registry.md +0 -84
- package/skills/fui-skill-cli/references/ui-crosswindow-patterns.md +0 -321
- package/skills/fui-skill-cli/references/ui-dialog-patterns.md +0 -255
- package/skills/fui-skill-cli/references/ui-layout-patterns.md +0 -176
- package/skills/fui-skill-cli/references/ui-patterns.md +0 -303
- package/skills/fui-skill-cli/references/ui-screenshot-review.md +0 -95
- package/skills/fui-skill-cli/references/ui-table-cell-patterns.md +0 -318
- package/skills/fui-skill-cli/references/ui-templates.md +0 -22
- package/skills/fui-skill-cli/references/verification.md +0 -236
- package/skills/fui-skill-cli/references/watcher-patterns.md +0 -163
- package/skills/fui-skill-cli/references/websocket-realtime.md +0 -271
|
@@ -1,56 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
# Controls Patterns & Logic
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
|
|
5
|
-
Guide to defining logic and layout in `module.json`.
|
|
3
|
+
> Owns: value resolution (backtick / `{{ }}` / JS eval), action engine (API/IN/OUT/CALLBACK/CONFIRM/MESS/CALL/IF/EXE), key notation, grid wrapper `container > rows > cols`, control object, layout rules. Classes/styles: [controls-styling-vocabulary.md](controls-styling-vocabulary.md); module.json overview: [module-json-anatomy.md](module-json-anatomy.md). _(Tier A — loaded with `fui skill get`.)_
|
|
6
4
|
|
|
7
5
|
---
|
|
8
6
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
FUI đã xây dựng sẵn các `f-*` component với nhiều tính năng mặc định (sort, search, CRUD, inline action, form validation, ...) — dùng `f-*` sẽ ít code hơn và tận dụng được toàn bộ tích hợp runtime. Chỉ dùng `v-*` khi FUI chưa có equivalent phù hợp.
|
|
7
|
+
## Prefer `f-*` over `v-*`
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|---|---|---|
|
|
15
|
-
| `f-table` | `v-data-table` (CRUD lẫn readonly) | sort/search/inline action/CRUD tích hợp sẵn; readonly = không khai `:update-api`; tự build headers khi bỏ `:headers`; hàng tổng qua `sum-format` |
|
|
16
|
-
| `f-search` | `v-autocomplete` | API search, debounce, loading state có sẵn |
|
|
17
|
-
| `f-button` | `v-btn` (khi cần action FUI) | hỗ trợ CALL, hotkey, form validation |
|
|
18
|
-
| `f-date` / `f-time` | `v-text-field` + date parsing | picker + typed mask tích hợp sẵn |
|
|
19
|
-
| `f-menu` | `v-menu + v-btn` dropdown | cấu hình đơn giản hơn, tích hợp icon/action/link |
|
|
20
|
-
| `f-echart` | `<canvas>`, `v-chart`, lib khác | ECharts V5 — hỗ trợ mọi loại chart |
|
|
9
|
+
Use `v-*` only when no `f-*` equivalent exists (check [component-quickref.md](component-quickref.md)); `f-*` has built-in features and runtime integration.
|
|
21
10
|
|
|
22
|
-
|
|
11
|
+
| Use | Instead of | Why |
|
|
12
|
+
| ------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `f-table` | `v-data-table` (CRUD and readonly) | built-in sort/search/inline action/CRUD; readonly = omit `:update-api`; auto headers when `:headers` omitted; totals via `sum-format` |
|
|
14
|
+
| `f-search` | `v-autocomplete` | API search, debounce, loading |
|
|
15
|
+
| `f-button` | `v-btn` (FUI action) | CALL, hotkey, form validation |
|
|
16
|
+
| `f-date` / `f-time` | `v-text-field` + parsing | picker + typed mask |
|
|
17
|
+
| `f-menu` | `v-menu + v-btn` | simpler, icon/action/link |
|
|
18
|
+
| `f-echart` | `<canvas>`, `v-chart`, other libs | ECharts V5, all chart types |
|
|
23
19
|
|
|
24
|
-
|
|
20
|
+
Defaults auto-injected (`defaultControlAttr` in `scripts/component.js`) — declare a prop only to override:
|
|
25
21
|
|
|
26
|
-
| Component
|
|
27
|
-
|
|
28
|
-
| `f-table`
|
|
29
|
-
| `f-button`
|
|
30
|
-
| `f-date` / `f-time` | `:dense true` · `:outlined true` · `:hide-details true`
|
|
31
|
-
| `f-search`
|
|
32
|
-
| `f-menu`
|
|
22
|
+
| Component | Defaults |
|
|
23
|
+
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `f-table` | `:dense true` · `:show-search true` · `:fixed-header true` · `:items-per-page 50` · `:hide-default-footer true` · `mobile-breakpoint 0` |
|
|
25
|
+
| `f-button` | `color primary` · `elevation 0` |
|
|
26
|
+
| `f-date` / `f-time` | `:dense true` · `:outlined true` · `:hide-details true` |
|
|
27
|
+
| `f-search` | `:dense true` · `:outlined true` · `:hide-details true` · `:auto-select-first true` |
|
|
28
|
+
| `f-menu` | `color primary` · `:depressed true` |
|
|
33
29
|
|
|
34
|
-
> **
|
|
30
|
+
> **CRUD:** never `f-button` + `f-dialog` for an add form (unstable in V2). Use `f-table` `ctrl-update` (`:update-form` + `:update-api`). Special dialogs (custom logic, dynamic controls, multi-step) → hand-built `v-dialog` in f-dialog anatomy (`fui design read fui` §4, `ui-dialog-patterns.md`).
|
|
35
31
|
|
|
36
32
|
---
|
|
37
33
|
|
|
38
34
|
## 0. Value Resolution Rules (Fundamental)
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
### Quy tắc xác định kiểu value
|
|
43
|
-
|
|
44
|
-
| Dạng value | Cách xử lý | Kết quả |
|
|
45
|
-
|---|---|---|
|
|
46
|
-
| Primitive (number, boolean, object, array) | Lưu nguyên | Giá trị gốc |
|
|
47
|
-
| String bắt đầu bằng `` ` `` | Lodash template (literal + `{{ }}` interpolation) | String sau khi render |
|
|
48
|
-
| String có `{{ }}` | Lodash template với interpolation | String sau khi render |
|
|
49
|
-
| String có spaces (không có backtick, không có `{{ }}`) | Lodash template → trả về literal | String đó |
|
|
50
|
-
| String không có spaces, không backtick, không `{{ }}` | **Evaluated as JS expression** | Giá trị của expression |
|
|
51
|
-
| `""` (empty string) | Empty string | `""` |
|
|
36
|
+
`getVueData(value, src)` resolves every string in `data[]` and `IN`:
|
|
52
37
|
|
|
53
|
-
|
|
38
|
+
| Value | Result |
|
|
39
|
+
| ------------------------------------------ | ----------------------------------- |
|
|
40
|
+
| Primitive (number, boolean, object, array) | as-is |
|
|
41
|
+
| String starting with `` ` `` | lodash template (literal + `{{ }}`) |
|
|
42
|
+
| String containing `{{ }}` | lodash template |
|
|
43
|
+
| String with spaces, no backtick/`{{ }}` | literal |
|
|
44
|
+
| String without spaces, backtick, `{{ }}` | **evaluated as JS** |
|
|
45
|
+
| `""` | `""` |
|
|
54
46
|
|
|
55
47
|
```json
|
|
56
48
|
"data": [
|
|
@@ -59,10 +51,10 @@ FUI dùng hàm `getVueData(value, src)` để giải thích mọi string value t
|
|
|
59
51
|
"b": 3, // number → 3
|
|
60
52
|
"c": true, // boolean → true
|
|
61
53
|
"d": null, // null → null
|
|
62
|
-
"e": "val1", // no space → JS eval:
|
|
54
|
+
"e": "val1", // no space → JS eval: value of variable val1
|
|
63
55
|
"f": "val4.prop2", // JS eval: vueData.val4.prop2
|
|
64
|
-
"g": "val5[1]", // JS eval:
|
|
65
|
-
"h": "[val1]", // JS eval:
|
|
56
|
+
"g": "val5[1]", // JS eval: 2nd element of array val5
|
|
57
|
+
"h": "[val1]", // JS eval: array [value_of_val1]
|
|
66
58
|
"i": "[1,2,3].map(x=>x*2)", // no space → JS eval → [2,4,6]
|
|
67
59
|
"j": "`Xin chào {{user.UserName}}", // backtick → lodash template → "Xin chào Tuấn"
|
|
68
60
|
"k": "tổng là {{a+b}} item", // {{ }} → lodash template → "tổng là 3 item"
|
|
@@ -71,13 +63,11 @@ FUI dùng hàm `getVueData(value, src)` để giải thích mọi string value t
|
|
|
71
63
|
]
|
|
72
64
|
```
|
|
73
65
|
|
|
74
|
-
|
|
66
|
+
`"val1"` = value of `val1`, not the string; literal needs ``"`val1"``.
|
|
75
67
|
|
|
76
|
-
> ⚠️ **
|
|
68
|
+
> ⚠️ **Not for `:attr`.** Bindings (`:option`, `:config`, any `v-bind:`) go straight to Vue's compiler: one pure expression; no `function(){}`/`var`/`return`/`;`. Violation ⇒ blank page, 0 JS error, 0 `[Vue warn]`. Rules + moving logic to `script.js` + `EXE`: [advanced-techniques.md](advanced-techniques.md) §1.
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
`{{ }}` dùng lodash template — có thể chứa bất kỳ JS expression nào:
|
|
70
|
+
`{{ }}` takes any JS expression:
|
|
81
71
|
|
|
82
72
|
```json
|
|
83
73
|
"label": "Còn {{items.length}} bản ghi",
|
|
@@ -87,61 +77,53 @@ FUI dùng hàm `getVueData(value, src)` để giải thích mọi string value t
|
|
|
87
77
|
|
|
88
78
|
---
|
|
89
79
|
|
|
90
|
-
## 1.
|
|
91
|
-
|
|
92
|
-
### Key notation — Dot, Bracket, và Cross-window
|
|
80
|
+
## 1. `data[]` keys and assignment
|
|
93
81
|
|
|
94
|
-
Key
|
|
82
|
+
### Key notation
|
|
95
83
|
|
|
96
84
|
```json
|
|
97
85
|
"data": [
|
|
98
86
|
{
|
|
99
|
-
"val": "Hang So", //
|
|
87
|
+
"val": "Hang So", // simple variable
|
|
100
88
|
"obj.prop": "val1", // dot notation → vueData.obj.prop
|
|
101
89
|
"obj.nested.deep": true, // multi-level dot
|
|
102
|
-
"arr[0].name": "Tuan", // bracket
|
|
103
|
-
"arr[val2].name": "abc", // bracket
|
|
104
|
-
"arr[0][val1].x": "y", // bracket
|
|
105
|
-
"#PARENT.field": "val1", //
|
|
106
|
-
"#Win1.field": "val1" //
|
|
90
|
+
"arr[0].name": "Tuan", // bracket, literal index
|
|
91
|
+
"arr[val2].name": "abc", // bracket, variable index (val2 = 3 → arr[3])
|
|
92
|
+
"arr[0][val1].x": "y", // bracket, variable key (val1 = "Hang So")
|
|
93
|
+
"#PARENT.field": "val1", // write to parent window
|
|
94
|
+
"#Win1.field": "val1" // write to iframe Win1
|
|
107
95
|
}
|
|
108
96
|
]
|
|
109
97
|
```
|
|
110
98
|
|
|
111
|
-
|
|
99
|
+
Missing intermediate objects/arrays are auto-created (also for `OUT` targets and assignment fragments) — no need to reassign the whole object. `#...` keys use `setWindowData` ([ui-patterns.md](ui-patterns.md) §Cross-window).
|
|
112
100
|
|
|
113
|
-
|
|
101
|
+
### Events/slots in `attr` — full form only
|
|
114
102
|
|
|
115
|
-
|
|
103
|
+
Every event/slot of every component, incl. custom `f-*` and `$emit()` events:
|
|
116
104
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
|
120
|
-
|
|
121
|
-
| Event | `@click`, `@saved`, `@bất-kỳ-event-nào` | `v-on:click`, `v-on:saved`, `v-on:bất-kỳ-event-nào` |
|
|
122
|
-
| Slot | `#default`, `#item.name` | `v-slot:default`, `v-slot:item.name` |
|
|
105
|
+
| Kind | Shorthand (`.vue` only) | `module.json` |
|
|
106
|
+
| ----- | -------------------------------- | -------------------------------------------- |
|
|
107
|
+
| Event | `@click`, `@saved`, `@any-event` | `v-on:click`, `v-on:saved`, `v-on:any-event` |
|
|
108
|
+
| Slot | `#default`, `#item.name` | `v-slot:default`, `v-slot:item.name` |
|
|
123
109
|
|
|
124
110
|
```json
|
|
125
|
-
// ❌
|
|
111
|
+
// ❌ WRONG — shorthand, fails to load
|
|
126
112
|
{ "el": "f-contest-list", "attr": { "@saved": "CALL(apiLoadContests)" } }
|
|
127
113
|
|
|
128
|
-
// ✅
|
|
114
|
+
// ✅ RIGHT
|
|
129
115
|
{ "el": "f-contest-list", "attr": { "v-on:saved": "CALL(apiLoadContests)" } }
|
|
130
116
|
```
|
|
131
117
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
> Không nhầm với `#PARENT.field` / `#Win1.field` — đó là cú pháp `#` riêng của FUI cho cross-window trong **`data[]`** (xem trên), khác hoàn toàn với slot shorthand `#` của Vue trong `attr`.
|
|
118
|
+
`#PARENT.field` / `#Win1.field` in `data[]` is FUI cross-window syntax, unrelated to Vue slot `#`.
|
|
135
119
|
|
|
136
|
-
###
|
|
120
|
+
### Alias and JS init
|
|
137
121
|
|
|
138
122
|
```json
|
|
139
|
-
"GanLienGiaTri": "val1" // GanLienGiaTri =
|
|
123
|
+
"GanLienGiaTri": "val1" // GanLienGiaTri = current value of val1
|
|
140
124
|
```
|
|
141
125
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
### Data initialization với JS expressions
|
|
126
|
+
Snapshot at `data[]` run, not reactive — use `watch` for reactivity.
|
|
145
127
|
|
|
146
128
|
```json
|
|
147
129
|
"data": [
|
|
@@ -156,22 +138,16 @@ Không reactive — chỉ snapshot tại thời điểm `data[]` chạy. Muốn
|
|
|
156
138
|
|
|
157
139
|
---
|
|
158
140
|
|
|
159
|
-
## 2.
|
|
160
|
-
|
|
161
|
-
Actions are the "methods" of your module. They handle APIs, dialogs, and logic flow.
|
|
162
|
-
Khai báo action thành object có tên trong `data`, gọi lại bằng `CALL`.
|
|
141
|
+
## 2. Actions (`data`)
|
|
163
142
|
|
|
164
|
-
|
|
143
|
+
Declare named action objects in `data`; invoke with `CALL`.
|
|
165
144
|
|
|
166
|
-
|
|
145
|
+
### CORE RULE — one `{}` = ONE command; sequence = ARRAY `[]`
|
|
167
146
|
|
|
168
|
-
|
|
169
|
-
> **Nhiều lệnh chạy tuần tự = một MẢNG các object `[{…}, {…}, {…}]`** — mảng là cách DUY NHẤT diễn đạt "trước, rồi sau".
|
|
170
|
-
|
|
171
|
-
Nhét nhiều lệnh vào chung một `{}` **không phải là "viết gọn"** — nó làm rơi lệnh:
|
|
147
|
+
An object may hold parameters and nested sub-actions (`IN`, `OUT`, `CALLBACK`, `THEN`/`ELSE`…) but runs one command. An array is the ONLY way to sequence.
|
|
172
148
|
|
|
173
149
|
```json
|
|
174
|
-
// ❌
|
|
150
|
+
// ❌ WRONG — only CALL runs. EXE and MESS NEVER run.
|
|
175
151
|
"CALLBACK": {
|
|
176
152
|
"EXE": "vueData.previewRows = []; vueData.excelRaw = [];",
|
|
177
153
|
"CALL": "getList",
|
|
@@ -180,7 +156,7 @@ Nhét nhiều lệnh vào chung một `{}` **không phải là "viết gọn"**
|
|
|
180
156
|
```
|
|
181
157
|
|
|
182
158
|
```json
|
|
183
|
-
// ✅
|
|
159
|
+
// ✅ RIGHT — three commands, three objects, run in written order.
|
|
184
160
|
"CALLBACK": [
|
|
185
161
|
{ "EXE": "vueData.previewRows = []; vueData.excelRaw = [];" },
|
|
186
162
|
{ "CALL": "getList" },
|
|
@@ -188,63 +164,52 @@ Nhét nhiều lệnh vào chung một `{}` **không phải là "viết gọn"**
|
|
|
188
164
|
]
|
|
189
165
|
```
|
|
190
166
|
|
|
191
|
-
|
|
167
|
+
`vueAction()` (`fastproject.js:407`, `fastproject-3.0.js:448`, same in V2/V3) checks keys in fixed order, each branch returns:
|
|
192
168
|
|
|
193
169
|
```
|
|
194
170
|
IN (tham số, không return) → API ⏎ → CALL ⏎ → EXE ⏎ → FUN ⏎ → IF ⏎ → MESS ⏎ → MESSBOX ⏎ → (fallthrough) mapData
|
|
195
171
|
```
|
|
196
172
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Ngược lại, `runAction()` nhận mảng thì `_.forEach` từng phần tử qua `vueAction` ⇒ **mảng = tuần tự, giữ đúng thứ tự viết**.
|
|
200
|
-
|
|
201
|
-
**Đây là lỗi hỏng-âm-thầm**: không JS error, không `[Vue warn]`, API vẫn chạy, chỉ là hai lệnh kia biến mất. Triệu chứng thường gặp: "sau khi lưu, danh sách có reload nhưng không thấy toast", hoặc "toast hiện mà form không reset".
|
|
202
|
-
|
|
203
|
-
> Mọi chỗ nhận action đều theo luật này: `CALLBACK`, `ERRORCALLBACK`, `THEN`/`ELSE`, `CANCEL`, `:action`, `:update-api.new/edit/delete`, `watch`, `onclose`, và named action trong `data[]`. **Nghi ngờ thì bọc mảng** — mảng một phần tử `[{…}]` luôn hợp lệ.
|
|
204
|
-
|
|
205
|
-
#### Key nào là LỆNH, key nào là THAM SỐ
|
|
206
|
-
|
|
207
|
-
Chỉ nhóm "lệnh" mới bị luật một-object-một-lệnh. Tham số thì đi kèm lệnh trong **cùng** object là đúng.
|
|
173
|
+
Highest priority wins, not first written; reordering changes nothing. `runAction()` runs arrays in order (`_.forEach`). Silent failure (no error, no `[Vue warn]`); symptoms: "list reloads but no toast", "toast shows but form not reset".
|
|
208
174
|
|
|
209
|
-
|
|
210
|
-
|---|---|---|
|
|
211
|
-
| **Lệnh** (mỗi object chỉ chạy 1) | `API` · `CALL` · `EXE` · `FUN` · `IF` · `MESS` · `MESSBOX` | theo đúng thứ tự ưu tiên ở trên |
|
|
212
|
-
| **Tham số** (đi chung, không tính là lệnh) | `IN` · `OUT` · `HEADER` · `METHOD` · `CALLBACK` · `ERROROUT` · `ERRORCALLBACK` · `THEN` · `ELSE` · `CONFIRM` · `CANCEL` | `CONFIRM`/`CANCEL` được `runAction` xử lý một tầng trên rồi mới gọi `vueAction` |
|
|
175
|
+
Applies to `CALLBACK`, `ERRORCALLBACK`, `THEN`/`ELSE`, `CANCEL`, `:action`, `:update-api.new/edit/delete`, `watch`, `onclose`, named actions. When in doubt wrap: `[{…}]` is always valid.
|
|
213
176
|
|
|
214
|
-
|
|
215
|
-
|
|
177
|
+
| Group | Keys |
|
|
178
|
+
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
179
|
+
| **Commands** (one per object, priority above) | `API` · `CALL` · `EXE` · `FUN` · `IF` · `MESS` · `MESSBOX` |
|
|
180
|
+
| **Parameters** (same object as command) | `IN` · `OUT` · `HEADER` · `METHOD` · `CALLBACK` · `ERROROUT` · `ERRORCALLBACK` · `THEN` · `ELSE` · `CONFIRM` · `CANCEL` (`CONFIRM`/`CANCEL` handled by `runAction` before `vueAction`) |
|
|
216
181
|
|
|
217
|
-
|
|
182
|
+
**Sole exception:** `MESS` beside `API` is a parameter — `callAPI` toasts on success (`fastproject-3.0.js:755`, after `OUT`, before `CALLBACK`). `{ "API": …, "IN": …, "MESS": …, "CALLBACK": … }` is valid. Not `MESSBOX`; not with `FUN` (`runFunction` ignores `MESS`).
|
|
218
183
|
|
|
219
|
-
|
|
184
|
+
Assignment fragments (`{ "dlgOpen": false, "form.Name": "" }`) run only in the fallthrough branch; any command in the same object silently drops them:
|
|
220
185
|
|
|
221
186
|
```json
|
|
222
|
-
// ❌
|
|
187
|
+
// ❌ WRONG — CALL runs, the three variables are NOT assigned
|
|
223
188
|
{ "PageNum": 1, "Description": "", "IsPaper": false, "CALL": "reload" }
|
|
224
189
|
|
|
225
|
-
// ✅
|
|
190
|
+
// ✅ RIGHT
|
|
226
191
|
[
|
|
227
192
|
{ "PageNum": 1, "Description": "", "IsPaper": false },
|
|
228
193
|
{ "CALL": "reload" }
|
|
229
194
|
]
|
|
230
195
|
```
|
|
231
196
|
|
|
232
|
-
###
|
|
197
|
+
### Keys
|
|
233
198
|
|
|
234
|
-
| Key
|
|
235
|
-
|
|
236
|
-
| `API`
|
|
237
|
-
| `IN`
|
|
238
|
-
| `OUT`
|
|
239
|
-
| `CALLBACK`
|
|
240
|
-
| `ERROROUT` / `ERRORCALLBACK` |
|
|
241
|
-
| `CONFIRM` / `CANCEL`
|
|
242
|
-
| `MESS` / `MESSBOX`
|
|
243
|
-
| `CALL`
|
|
244
|
-
| `IF` / `THEN` / `ELSE`
|
|
245
|
-
| `FUN`
|
|
246
|
-
| `ARRAY` + `COL`
|
|
247
|
-
| `EXE`
|
|
199
|
+
| Key | Meaning |
|
|
200
|
+
| ---------------------------- | ------------------------------------------------------------ |
|
|
201
|
+
| `API` | endpoint |
|
|
202
|
+
| `IN` | input params (`vueData.` or `item.`) |
|
|
203
|
+
| `OUT` | store response (e.g. `"myList"`); object form splits SELECTs |
|
|
204
|
+
| `CALLBACK` | on success |
|
|
205
|
+
| `ERROROUT` / `ERRORCALLBACK` | manual error handling |
|
|
206
|
+
| `CONFIRM` / `CANCEL` | confirm first / on Cancel |
|
|
207
|
+
| `MESS` / `MESSBOX` | toast / modal |
|
|
208
|
+
| `CALL` | run named action |
|
|
209
|
+
| `IF` / `THEN` / `ELSE` | branch |
|
|
210
|
+
| `FUN` | FUI function (`openWindow`, `showMessage`, ...) |
|
|
211
|
+
| `ARRAY` + `COL` | column of object array → flat array |
|
|
212
|
+
| `EXE` | raw JS, sparingly |
|
|
248
213
|
|
|
249
214
|
```json
|
|
250
215
|
"fetchUsers": {
|
|
@@ -255,9 +220,6 @@ Fragment gán dữ liệu (`{ "dlgOpen": false, "form.Name": "" }`) chạy ở *
|
|
|
255
220
|
}
|
|
256
221
|
```
|
|
257
222
|
|
|
258
|
-
Chi tiết từng key ở các mục bên dưới.
|
|
259
|
-
|
|
260
|
-
### Standard API Call
|
|
261
223
|
```json
|
|
262
224
|
"apiLoadData": {
|
|
263
225
|
"API": "/api/controller/action",
|
|
@@ -274,13 +236,12 @@ Chi tiết từng key ở các mục bên dưới.
|
|
|
274
236
|
}
|
|
275
237
|
```
|
|
276
238
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
> **Không gán nested object trực tiếp trong action:** `{ "form": { "field": "item.field" } }` sẽ không hoạt động — dùng dot notation cho từng field: `{ "form.field": "item.field" }`. Nếu đang dùng `ctrl-update`, không cần quản lý form thủ công.
|
|
239
|
+
- Two commands ⇒ array; merged, only `CALL` runs.
|
|
240
|
+
- No nested object assignment: `{ "form": { "field": "item.field" } }` fails; use `{ "form.field": "item.field" }`. `ctrl-update` needs no manual form handling.
|
|
280
241
|
|
|
281
|
-
###
|
|
242
|
+
### URL interpolation
|
|
282
243
|
|
|
283
|
-
`{{ }}`
|
|
244
|
+
`{{ }}` in `API` reads vueData:
|
|
284
245
|
|
|
285
246
|
```json
|
|
286
247
|
"getDetail": {
|
|
@@ -293,9 +254,9 @@ Chi tiết từng key ở các mục bên dưới.
|
|
|
293
254
|
}
|
|
294
255
|
```
|
|
295
256
|
|
|
296
|
-
###
|
|
257
|
+
### ERROROUT / ERRORCALLBACK
|
|
297
258
|
|
|
298
|
-
|
|
259
|
+
Default: full-screen error dialog. With `ERROROUT` nothing shows automatically — you handle it.
|
|
299
260
|
|
|
300
261
|
```json
|
|
301
262
|
"saveData": {
|
|
@@ -311,11 +272,11 @@ Mặc định, API lỗi sẽ hiện dialog lỗi toàn màn hình. Dùng `ERROR
|
|
|
311
272
|
}
|
|
312
273
|
```
|
|
313
274
|
|
|
314
|
-
|
|
315
|
-
- `ERRORCALLBACK`: action chạy sau khi map ERROROUT (không hiện dialog mặc định)
|
|
316
|
-
- Khi có `ERROROUT`, lỗi sẽ KHÔNG tự hiện — bạn phải xử lý hoàn toàn
|
|
275
|
+
`ERROROUT` maps the XHR error (`status` = HTTP code, `responseJSON` = parsed body); `ERRORCALLBACK` runs after.
|
|
317
276
|
|
|
318
|
-
###
|
|
277
|
+
### METHOD / HEADER
|
|
278
|
+
|
|
279
|
+
Defaults `METHOD = "POST"`, `HEADER = { authorization: token }`; override for GET or external APIs.
|
|
319
280
|
|
|
320
281
|
```json
|
|
321
282
|
"getPublicData": {
|
|
@@ -326,35 +287,26 @@ Mặc định, API lỗi sẽ hiện dialog lỗi toàn màn hình. Dùng `ERROR
|
|
|
326
287
|
}
|
|
327
288
|
```
|
|
328
289
|
|
|
329
|
-
|
|
330
|
-
- Override khi cần GET hoặc custom header cho external API
|
|
331
|
-
|
|
332
|
-
### Literal String Values (Important)
|
|
290
|
+
### Literal strings in `IN`
|
|
333
291
|
|
|
334
|
-
|
|
335
|
-
If a string has no spaces, FUI can treat it like an expression/variable in `IN`.
|
|
336
|
-
When you need a plain literal string, force it with:
|
|
337
|
-
- Backtick prefix: ``"`myValue"`` (or closed ``"`myValue`"``)
|
|
338
|
-
- Single quotes inside JSON string: `"'myValue'"`
|
|
292
|
+
Only for `IN`: no-space strings may be evaluated. Force literal with backtick ``"`myValue"`` (or ``"`myValue`"``) or inner single quotes `"'myValue'"`. Always do this for `id`, `url`, and any key that must stay text.
|
|
339
293
|
|
|
340
294
|
```json
|
|
341
295
|
{
|
|
342
296
|
"FUN": "openWindow",
|
|
343
297
|
"IN": {
|
|
344
|
-
"id":
|
|
345
|
-
"url":
|
|
298
|
+
"id": "`winUser",
|
|
299
|
+
"url": "'/fp/module?mid=123'",
|
|
346
300
|
"title": "`Tiêu đề cố định"
|
|
347
301
|
}
|
|
348
302
|
}
|
|
349
303
|
```
|
|
350
304
|
|
|
351
|
-
|
|
305
|
+
`attr` props without `:` are already plain strings (`"url": "/fp/module?mid=123"` needs no backtick).
|
|
352
306
|
|
|
353
|
-
|
|
307
|
+
### CONFIRM + CANCEL
|
|
354
308
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
`CONFIRM` hiện dialog xác nhận trước khi thực thi. `CANCEL` (optional) chạy khi user bấm Hủy:
|
|
309
|
+
`CONFIRM` (supports `{{ }}`) asks first; optional `CANCEL` runs on Cancel.
|
|
358
310
|
|
|
359
311
|
```json
|
|
360
312
|
"deleteItem": {
|
|
@@ -366,9 +318,8 @@ For component props in `attr` without `:`, value is already a plain string prop
|
|
|
366
318
|
}
|
|
367
319
|
```
|
|
368
320
|
|
|
369
|
-
|
|
321
|
+
### IF / THEN / ELSE
|
|
370
322
|
|
|
371
|
-
### Conditional Logic
|
|
372
323
|
```json
|
|
373
324
|
"checkStatus": {
|
|
374
325
|
"IF": "status === 1",
|
|
@@ -381,24 +332,20 @@ For component props in `attr` without `:`, value is already a plain string prop
|
|
|
381
332
|
}
|
|
382
333
|
```
|
|
383
334
|
|
|
384
|
-
|
|
385
|
-
> THEN/ELSE có thể là string JS: `"THEN": "vueData.x = 1"`.
|
|
335
|
+
Condition is JS reading vueData directly (no `vueData.` prefix). THEN/ELSE may be a JS string: `"THEN": "vueData.x = 1"`.
|
|
386
336
|
|
|
387
337
|
### MESS vs MESSBOX
|
|
388
338
|
|
|
389
|
-
|
|
390
|
-
|---|---|---|
|
|
391
|
-
| Hiển thị | Toast (góc trên, tự đóng) | Modal dialog (user phải đóng) |
|
|
392
|
-
| Dùng khi | Thông báo thành công/thất bại | Cần user đọc và xác nhận đã thấy |
|
|
339
|
+
`MESS` = auto-closing top toast (success/failure). `MESSBOX` = modal the user must close (must be read/acknowledged). Both support `{{ }}`.
|
|
393
340
|
|
|
394
341
|
```json
|
|
395
342
|
{ "MESS": "Lưu thành công!" }
|
|
396
343
|
{ "MESSBOX": "Phiên của bạn đã hết hạn. Vui lòng đăng nhập lại." }
|
|
397
344
|
```
|
|
398
345
|
|
|
399
|
-
|
|
346
|
+
### FUN: showMessage
|
|
400
347
|
|
|
401
|
-
|
|
348
|
+
`title`; `message` (optional); `onclose` = action after closing (use instead of `MESS` when something must follow).
|
|
402
349
|
|
|
403
350
|
```json
|
|
404
351
|
{
|
|
@@ -413,13 +360,9 @@ Cả hai hỗ trợ `{{ }}` interpolation.
|
|
|
413
360
|
}
|
|
414
361
|
```
|
|
415
362
|
|
|
416
|
-
|
|
417
|
-
- `message`: nội dung chi tiết (optional)
|
|
418
|
-
- `onclose`: action chạy sau khi user đóng dialog (thay thế `MESS` khi cần làm gì sau khi đọc xong)
|
|
419
|
-
|
|
420
|
-
### CALL — Gọi action đã khai báo
|
|
363
|
+
### CALL
|
|
421
364
|
|
|
422
|
-
|
|
365
|
+
Actions stored in `data[]` are callable anywhere (CALLBACK, IF/THEN/ELSE, watch):
|
|
423
366
|
|
|
424
367
|
```json
|
|
425
368
|
"data": [
|
|
@@ -431,29 +374,21 @@ Action được lưu thành biến trong `data[]` → gọi lại bất cứ đ
|
|
|
431
374
|
{ "CALL": "apiGetLogs" }
|
|
432
375
|
```
|
|
433
376
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
### ARRAY — Trích xuất cột từ mảng cấu trúc
|
|
439
|
-
|
|
440
|
-
Tạo mảng phẳng gồm tất cả giá trị của một cột từ array có cấu trúc:
|
|
377
|
+
### ARRAY + COL
|
|
441
378
|
|
|
442
379
|
```json
|
|
443
380
|
{
|
|
444
381
|
"danhSachID": {
|
|
445
382
|
"ARRAY": "dsHoaDon",
|
|
446
|
-
"COL":
|
|
383
|
+
"COL": "DanhMucThuID"
|
|
447
384
|
}
|
|
448
385
|
}
|
|
449
386
|
// danhSachID = [32423, 4, 23, 4, 234, 2]
|
|
450
387
|
```
|
|
451
388
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
### OUT — Gán kết quả API vào nhiều biến
|
|
389
|
+
### OUT object — multiple SELECTs
|
|
455
390
|
|
|
456
|
-
|
|
391
|
+
For SPs returning `data[0]`, `data[1]`...; prefer this over CALLBACK + EXE.
|
|
457
392
|
|
|
458
393
|
```json
|
|
459
394
|
"getReport": {
|
|
@@ -466,19 +401,13 @@ Thay vì gán toàn bộ response vào một biến, dùng `OUT` dạng object
|
|
|
466
401
|
}
|
|
467
402
|
```
|
|
468
403
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
---
|
|
472
|
-
|
|
473
|
-
### MAP — Mapping dữ liệu trong bộ nhớ (không qua API)
|
|
474
|
-
|
|
475
|
-
Khi chỉ cần re-map dữ liệu giữa các biến mà không gọi API, dùng block chỉ có `IN` và `OUT`:
|
|
404
|
+
### MAP — `IN` + `OUT` only, no API
|
|
476
405
|
|
|
477
406
|
```json
|
|
478
407
|
{
|
|
479
408
|
"IN": {
|
|
480
409
|
"HoTen": "formData.FullName",
|
|
481
|
-
"MaSV":
|
|
410
|
+
"MaSV": "formData.StudentID"
|
|
482
411
|
},
|
|
483
412
|
"OUT": "mappedData"
|
|
484
413
|
}
|
|
@@ -491,23 +420,19 @@ Khi chỉ cần re-map dữ liệu giữa các biến mà không gọi API, dùn
|
|
|
491
420
|
}
|
|
492
421
|
```
|
|
493
422
|
|
|
494
|
-
|
|
423
|
+
### EXE
|
|
495
424
|
|
|
496
|
-
|
|
425
|
+
Only for logic action JSON can't express; `vueData` accessible; end with `;`.
|
|
497
426
|
|
|
498
427
|
```json
|
|
499
428
|
{ "EXE": "vueData.items = vueData.items.filter(x => x.active); " }
|
|
500
429
|
```
|
|
501
430
|
|
|
502
|
-
> Dùng khi cần logic phức tạp không thể diễn tả bằng action JSON. Có thể access `vueData` trực tiếp. Kết thúc bằng `;`.
|
|
503
|
-
|
|
504
431
|
---
|
|
505
432
|
|
|
506
433
|
## 3. Watchers (`watch`)
|
|
507
434
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
For production-grade cascading and filter watcher design, see [watcher-patterns.md](watcher-patterns.md).
|
|
435
|
+
Cascading/filter design: [watcher-patterns.md](watcher-patterns.md).
|
|
511
436
|
|
|
512
437
|
```json
|
|
513
438
|
"watch": {
|
|
@@ -516,9 +441,7 @@ For production-grade cascading and filter watcher design, see [watcher-patterns.
|
|
|
516
441
|
}
|
|
517
442
|
```
|
|
518
443
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
Trong CALLBACK hoặc action của watch, `v_old` chứa giá trị CŨ (trước khi thay đổi):
|
|
444
|
+
`v_old` holds the previous value in watch actions/CALLBACK:
|
|
522
445
|
|
|
523
446
|
```json
|
|
524
447
|
"watch": {
|
|
@@ -529,9 +452,7 @@ Trong CALLBACK hoặc action của watch, `v_old` chứa giá trị CŨ (trướ
|
|
|
529
452
|
}
|
|
530
453
|
```
|
|
531
454
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
Mặc định watch là shallow (chỉ detect thay đổi reference). Dùng `deep-watch` để detect thay đổi bên trong:
|
|
455
|
+
Watch is shallow (reference only). `deep-watch` detects inner changes, runs before normal watches, costs more — use only when needed:
|
|
535
456
|
|
|
536
457
|
```json
|
|
537
458
|
"watch": {
|
|
@@ -543,19 +464,11 @@ Mặc định watch là shallow (chỉ detect thay đổi reference). Dùng `dee
|
|
|
543
464
|
}
|
|
544
465
|
```
|
|
545
466
|
|
|
546
|
-
> `deep-watch` được xử lý trước các watch thông thường. Tốn hiệu năng hơn — chỉ dùng khi cần thiết.
|
|
547
|
-
|
|
548
467
|
---
|
|
549
468
|
|
|
550
|
-
## 4. Table
|
|
469
|
+
## 4. Table cell action context
|
|
551
470
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
| Variable | Giá trị |
|
|
555
|
-
|---|---|
|
|
556
|
-
| `item` | Object của row hiện tại |
|
|
557
|
-
| `index` | Index của row trong array |
|
|
558
|
-
| `$row` | Có thể set thủ công: `{ "$row": "item" }` |
|
|
471
|
+
Actions from `f-table` cells (t-button, t-check, t-select, t-menu, v-on:click) get `item` (row), `index` (row index), and `$row` (settable: `{ "$row": "item" }`). Update one row via `danhSach[index].field` — the standard pattern:
|
|
559
472
|
|
|
560
473
|
```json
|
|
561
474
|
{
|
|
@@ -579,13 +492,11 @@ Khi action chạy từ cell trong `f-table` (t-button, t-check, t-select, t-menu
|
|
|
579
492
|
}
|
|
580
493
|
```
|
|
581
494
|
|
|
582
|
-
> `danhSach[index].kohienthi` — update trực tiếp item trong array bằng `index` inject từ context. Đây là pattern chuẩn để update 1 item cụ thể trong table.
|
|
583
|
-
|
|
584
495
|
---
|
|
585
496
|
|
|
586
|
-
## 5. URL
|
|
497
|
+
## 5. URL parameters → vueData
|
|
587
498
|
|
|
588
|
-
|
|
499
|
+
On load: `p_router` (path, no query), `p_routers` (segment array), each query param → same-named string variable.
|
|
589
500
|
|
|
590
501
|
```
|
|
591
502
|
URL: https://fp.example.vn/ts/newhocsinh?id=12345&mod=abcdef
|
|
@@ -595,13 +506,6 @@ URL: https://fp.example.vn/ts/newhocsinh?id=12345&mod=abcdef
|
|
|
595
506
|
→ vueData.mod = "abcdef"
|
|
596
507
|
```
|
|
597
508
|
|
|
598
|
-
| Biến | Kiểu | Mô tả |
|
|
599
|
-
|---|---|---|
|
|
600
|
-
| `p_router` | string | Path sau domain, không có query string |
|
|
601
|
-
| `p_routers` | array | Mảng các segment của path |
|
|
602
|
-
| (query params) | string | Mỗi param trong query string → biến cùng tên |
|
|
603
|
-
|
|
604
|
-
Dùng trong `data[]` để đọc URL params:
|
|
605
509
|
```json
|
|
606
510
|
"data": [
|
|
607
511
|
{
|
|
@@ -613,49 +517,36 @@ Dùng trong `data[]` để đọc URL params:
|
|
|
613
517
|
|
|
614
518
|
---
|
|
615
519
|
|
|
616
|
-
## 6.
|
|
617
|
-
|
|
618
|
-
> **Vuetify version (V2 framework):** FUI V2 dùng **Vuetify 1.5** — grid system dùng `v-container / v-layout / v-flex`. FUI V3 (fastproject-3.0.js) dùng **Vuetify 3** với grid `v-container / v-row / v-col`. Phần lớn module.json trong skill này viết theo cú pháp V2 (đại đa số project đang chạy); project mới dùng V3 thì tra bảng ngưỡng `w` V2/V3 bên dưới (mục `w dạng pixel`). Tham chiếu V2: https://v15.vuetifyjs.com/en/framework/grid/ — **đang tạo project mới và phải chọn V2 hay V3?** Mặc định là V3, xem [project-provisioning.md](project-provisioning.md) §Bước 1.
|
|
520
|
+
## 6. Layout (`controls`)
|
|
619
521
|
|
|
620
|
-
|
|
522
|
+
> V2 = Vuetify 1.5 (`v-container / v-layout / v-flex`); V3 (fastproject-3.0.js) = Vuetify 3 (`v-container / v-row / v-col`). Examples use V2 syntax (most projects); for V3 see the `w` thresholds below. V2 ref: https://v15.vuetifyjs.com/en/framework/grid/. New project default is V3 — [project-provisioning.md](project-provisioning.md) §Step 1.
|
|
621
523
|
|
|
622
|
-
|
|
524
|
+
**MANDATORY**: Container > Row > Col > Element. Nothing at root. No `children` key — nest via array `innerHTML`. `attr` = element attributes; `col` = grid-column attributes. Events `v-on:click`, never `@click`.
|
|
623
525
|
|
|
624
|
-
|
|
625
|
-
|---|---|---|
|
|
626
|
-
| Container `prop` | `"fluid grid-list-md"` | Dùng mặc định này trước — chỉ thêm class khác khi có lý do cụ thể |
|
|
627
|
-
| Row `prop` | `"row wrap"` hoặc `""` | Dùng mặc định này trước; hàng có nhiều loại control/chiều cao khác nhau dùng `"row wrap align-center"` |
|
|
526
|
+
Defaults — use first, deviate only with reason: container `prop` `"fluid grid-list-md"`; row `prop` `"row wrap"` or `""`, mixed control types/heights `"row wrap align-center"`. Put spacing/alignment on `col` or `attr`, not container/row.
|
|
628
527
|
|
|
629
|
-
|
|
528
|
+
### Vertical centering
|
|
630
529
|
|
|
631
|
-
|
|
530
|
+
Verify each row's controls share a vertical center (esp. mixing input/select, button, icon, checkbox, label, differing heights).
|
|
632
531
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
- Mặc định cho hàng hỗn hợp: thêm `align-center` vào `prop` của row, ví dụ
|
|
638
|
-
`"prop": "row wrap align-center"`.
|
|
639
|
-
- Nếu chỉ một control lệch trục và không nên thay alignment của cả hàng, đặt
|
|
640
|
-
`"col": { "class": "align-self-center" }` cho cột đó.
|
|
641
|
-
- Không dùng `mt-*`, `mb-*` hoặc inline `style` để đẩy từng control lên/xuống nhằm giả lập canh giữa.
|
|
642
|
-
- Sau khi dựng layout, tự rà lại row ở desktop và mobile; control xuống dòng phải vẫn có khoảng cách
|
|
643
|
-
tự nhiên, không bị lệch vì margin thủ công.
|
|
532
|
+
- Mixed row: add `align-center` to row `prop`.
|
|
533
|
+
- One stray control: `"col": { "class": "align-self-center" }` on it.
|
|
534
|
+
- Never `mt-*`, `mb-*`, or inline `style` to fake centering.
|
|
535
|
+
- Recheck desktop and mobile; wrapped controls keep natural spacing.
|
|
644
536
|
|
|
645
537
|
```json
|
|
646
538
|
{
|
|
647
539
|
"prop": "row wrap align-center",
|
|
648
540
|
"cols": [
|
|
649
541
|
{ "el": "v-text-field", "w": 6, "attr": { "label": "Từ khóa", "v-model": "vueData.keyword" } },
|
|
650
|
-
{ "el": "f-button", "w": "", "col": { "class": "shrink" },
|
|
651
|
-
"attr": { "label": "Tìm", "icon-text": "mdi-magnify" } }
|
|
542
|
+
{ "el": "f-button", "w": "", "col": { "class": "shrink" }, "attr": { "label": "Tìm", "icon-text": "mdi-magnify" } }
|
|
652
543
|
]
|
|
653
544
|
}
|
|
654
545
|
```
|
|
655
546
|
|
|
656
|
-
###
|
|
547
|
+
### Exception — page is one custom component
|
|
657
548
|
|
|
658
|
-
|
|
549
|
+
If the page renders only one self-laid-out component (custom dashboard, `uc-*` app), container `prop` = `"fluid pa-0"` to avoid outer padding. Any other controls on the page (filter, header, sections) → keep `"fluid grid-list-md"`.
|
|
659
550
|
|
|
660
551
|
```json
|
|
661
552
|
[
|
|
@@ -677,9 +568,8 @@ Khi cả trang chỉ render **một component tự xây dựng duy nhất** (com
|
|
|
677
568
|
]
|
|
678
569
|
```
|
|
679
570
|
|
|
680
|
-
|
|
571
|
+
### Grid and control object
|
|
681
572
|
|
|
682
|
-
### Basic Grid Structure
|
|
683
573
|
```json
|
|
684
574
|
{
|
|
685
575
|
"prop": "fluid grid-list-md",
|
|
@@ -695,25 +585,23 @@ Khi cả trang chỉ render **một component tự xây dựng duy nhất** (com
|
|
|
695
585
|
}
|
|
696
586
|
```
|
|
697
587
|
|
|
698
|
-
### Cấu trúc đầy đủ của một element trong `cols`
|
|
699
|
-
|
|
700
588
|
```json
|
|
701
589
|
{
|
|
702
|
-
"w":
|
|
703
|
-
"el":
|
|
704
|
-
"col":
|
|
705
|
-
"attr":
|
|
590
|
+
"w": "6",
|
|
591
|
+
"el": "v-text-field",
|
|
592
|
+
"col": { "class": "py-0" },
|
|
593
|
+
"attr": { "label": "Tên", "v-model": "tenBien" },
|
|
706
594
|
"innerHTML": "..."
|
|
707
595
|
}
|
|
708
596
|
```
|
|
709
597
|
|
|
710
|
-
| Field
|
|
711
|
-
|
|
712
|
-
| `w`
|
|
713
|
-
| `col`
|
|
714
|
-
| `el`
|
|
715
|
-
| `attr`
|
|
716
|
-
| `innerHTML` | slot
|
|
598
|
+
| Field | To | Meaning |
|
|
599
|
+
| ----------- | ------------ | ------------------------------------------------------------------------------------------------------------- |
|
|
600
|
+
| `w` | `v-flex` | **Required on every `cols` item.** `1`–`12` grid or `>= 25` px (px **requires `col: { "class": "shrink" }`**) |
|
|
601
|
+
| `col` | `v-flex` | class, style, offset, align-self... |
|
|
602
|
+
| `el` | tag | `div`, `span`, Vuetify/FUI (`v-text-field`, `f-button`, `uc-*`...) |
|
|
603
|
+
| `attr` | element | props/attrs/directives (`label`, `v-model`, `:disabled`, `v-on:click`); **no `@`** |
|
|
604
|
+
| `innerHTML` | default slot | HTML string (supports `{{ }}`) or recursive array of Control Objects |
|
|
717
605
|
|
|
718
606
|
```json
|
|
719
607
|
{
|
|
@@ -729,108 +617,74 @@ Khi cả trang chỉ render **một component tự xây dựng duy nhất** (com
|
|
|
729
617
|
}
|
|
730
618
|
```
|
|
731
619
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
620
|
+
```json
|
|
621
|
+
{ "el": "div", "w": "12", "innerHTML": "Xin chào {{formData.fullName}}" }
|
|
622
|
+
```
|
|
735
623
|
|
|
736
|
-
|
|
737
|
-
- `w` lớn → **bề rộng cố định px**, sinh inline `flex-basis: {w}px`
|
|
624
|
+
### Pixel `w` — REQUIRES `col: { "class": "shrink" }`
|
|
738
625
|
|
|
739
|
-
|
|
626
|
+
Small `w` → grid class `sm{w}` (V2) / `v-col-{w}` (V3). Large `w` → inline `flex-basis: {w}px`. But grid columns have `flex-grow: 1` (`.flex`/`.v-col`), which beats `flex-basis`: without `shrink` the px value silently does nothing.
|
|
740
627
|
|
|
741
628
|
```json
|
|
742
|
-
// ❌
|
|
629
|
+
// ❌ WRONG — 300 has no effect, column still stretches full
|
|
743
630
|
{ "el": "v-text-field", "w": 300, "attr": { "v-model": "tuKhoa" } }
|
|
744
631
|
|
|
745
|
-
// ✅
|
|
632
|
+
// ✅ RIGHT — shrink disables flex-grow, flex-basis 300px applies
|
|
746
633
|
{ "el": "v-text-field", "w": 300, "col": { "class": "shrink" },
|
|
747
634
|
"attr": { "v-model": "tuKhoa" } }
|
|
748
635
|
```
|
|
749
636
|
|
|
750
|
-
|
|
637
|
+
Measured (chromium, 1200px, neighbour takes rest):
|
|
751
638
|
|
|
752
|
-
|
|
|
753
|
-
|
|
754
|
-
| `w: 300`
|
|
755
|
-
|
|
|
756
|
-
|
|
|
639
|
+
| Declared | V2 | V3 |
|
|
640
|
+
| ------------------------------ | -------- | -------- |
|
|
641
|
+
| `w: 300` | 687px ❌ | 738px ❌ |
|
|
642
|
+
| + `col: { "shrink": true }` | 300px ✅ | 738px ❌ |
|
|
643
|
+
| + `col: { "class": "shrink" }` | 300px ✅ | 300px ✅ |
|
|
757
644
|
|
|
758
|
-
|
|
645
|
+
Only `col: { "class": "shrink" }` works in both: `shrink: true` is a V2 `v-flex` prop, inert in Vuetify 3; the `shrink` class is defined for V3 in FUI's `projectdefaultstyle.css` (`flex-grow:0; flex-shrink:1; flex-basis:content`). `"flex-grow-0"` also works; use `shrink` for consistency.
|
|
759
646
|
|
|
760
|
-
|
|
647
|
+
**Never `w` 13–24.** V2 (`fastproject.js`) switches to px at `w > 12`; V3 (`fastproject-3.0.js`) at `w > 24`, so V3 `w: 20` emits `v-col-20`, which doesn't exist (only `.v-col-1`…`.v-col-12`) → no width, equal split (measured `flex-basis: 0px`, 600px). Rule: `w` is `1`–`12` or `>= 25` + shrink. `fui module validate` warns on both (including `cols` nested in `innerHTML`) — run it before publishing.
|
|
761
648
|
|
|
762
|
-
|
|
763
|
-
|---|---|---|
|
|
764
|
-
| V2 (`fastproject.js`) | `w > 12` | `flex-basis: 20px` — px, hoạt động |
|
|
765
|
-
| V3 (`fastproject-3.0.js`) | `w > 24` | class `v-col-20` — **Vuetify 3 chỉ định nghĩa `.v-col-1` … `.v-col-12`** |
|
|
649
|
+
### Toolbar row — every column `shrink` except `v-spacer`
|
|
766
650
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
> **Luật:** `w` chỉ dùng `1`–`12` (chia cột) hoặc **`>= 25`** (pixel, kèm `col: { "class": "shrink" }`). Không bao giờ dùng 13–24.
|
|
770
|
-
|
|
771
|
-
`module_validate` kiểm cả hai vế này (warning, quét cả `cols` lồng trong `innerHTML`) — chạy trước khi publish thì không phải phát hiện qua ảnh render.
|
|
772
|
-
|
|
773
|
-
### Hàng nhiều control (toolbar) — CHUẨN MỰC: mọi cột `shrink`, trừ `v-spacer`
|
|
774
|
-
|
|
775
|
-
Đây là hàng phổ biến nhất trong FUI: vài filter + nút thao tác bên trái, một nhóm nút đẩy sang phải. **Khuôn chuẩn — chép nguyên rồi thay control:**
|
|
651
|
+
Canonical template (filters + buttons left, group pushed right):
|
|
776
652
|
|
|
777
653
|
```json
|
|
778
654
|
{
|
|
779
655
|
"prop": "row",
|
|
780
656
|
"cols": [
|
|
781
|
-
{ "el": "v-select",
|
|
782
|
-
{ "el": "v-select",
|
|
783
|
-
{ "el": "f-button",
|
|
784
|
-
{ "el": "v-spacer",
|
|
785
|
-
{ "el": "f-excel-reader",
|
|
786
|
-
{ "el": "f-button",
|
|
657
|
+
{ "el": "v-select", "w": "150", "col": "shrink", "attr": {} },
|
|
658
|
+
{ "el": "v-select", "w": "180", "col": "shrink", "attr": {} },
|
|
659
|
+
{ "el": "f-button", "w": "", "col": "shrink", "attr": {} },
|
|
660
|
+
{ "el": "v-spacer", "w": "" },
|
|
661
|
+
{ "el": "f-excel-reader", "col": "shrink", "attr": {} },
|
|
662
|
+
{ "el": "f-button", "w": "", "col": "shrink", "attr": {} }
|
|
787
663
|
]
|
|
788
664
|
}
|
|
789
665
|
```
|
|
790
666
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
**`w` rỗng KHÔNG có nghĩa là "tự co theo nội dung".** `fastproject*.js` bọc toàn bộ phần xử lý bề rộng trong `if (c.w)`, nên `w: ""` sinh ra **không class, cũng không `flex-basis`** — cột còn trơ là `.v-col` / `.flex` với `flex-grow: 1`, tức **chia đều phần trống** y như mọi cột khác. Nút bị kéo dãn ra, các nút cách nhau những khoảng trống bằng nhau, và nhóm cuối hàng không sát mép phải.
|
|
794
|
-
|
|
795
|
-
Số đo thật (chromium, framework V2 + V3 thật, viewport 1200px; hàng gồm 2 select `w:150`/`w:180` + nút "Tìm" | `v-spacer` | 2 nút cuối):
|
|
796
|
-
|
|
797
|
-
| | select `w:150` | nút "Tìm" | `v-spacer` | Kết quả nhìn thấy |
|
|
798
|
-
|---|---|---|---|---|
|
|
799
|
-
| Không cột nào `shrink` | 271 / 240 ❌ | 145 / 154 | 145 / 90 | filter phình, mọi thứ dàn đều |
|
|
800
|
-
| **Chỉ `v-spacer`, các cột khác không `shrink`** | 150 / 150 | **218 / 198** ❌ | 218 / 134 | **nút phình, hở khoảng giữa các nút, nhóm cuối không sát phải** |
|
|
801
|
-
| `shrink` cả `v-spacer` | 150 / 150 | 88 / 64 | **24 / 0** ❌ | cả hàng dồn về trái, mất hẳn canh phải (Σ 780/662 trên 1200px) |
|
|
802
|
-
| **`shrink` mọi cột trừ `v-spacer`** | 150 / 150 ✅ | 88 / 64 ✅ | **445 / 537** ✅ | filter đúng px, nút vừa chữ, nhóm cuối sát mép phải |
|
|
803
|
-
|
|
804
|
-
*(số bên trái = V3, bên phải = V2)*
|
|
667
|
+
**Rule:** every control column gets `"col": "shrink"`, including empty-`w` ones. Sole exception: `v-spacer` (shrinking it disables it). Empty `w` ≠ fit content: width logic is inside `if (c.w)`, so `w: ""` emits nothing and the column (`flex-grow: 1`) takes an equal share of free space. Any non-`shrink` column gets a share; in a toolbar only `v-spacer` should.
|
|
805
668
|
|
|
806
|
-
|
|
669
|
+
Measured (1200px; 2 selects `w:150`/`w:180` + "Tìm" | `v-spacer` | 2 buttons; V3 / V2):
|
|
807
670
|
|
|
808
|
-
|
|
671
|
+
| | select `w:150` | "Tìm" | `v-spacer` | Result |
|
|
672
|
+
| -------------------------------- | -------------- | ---------------- | ---------------- | ---------------------------------------------- |
|
|
673
|
+
| no `shrink` | 271 / 240 ❌ | 145 / 154 | 145 / 90 | filters bloat, evenly spread |
|
|
674
|
+
| **`v-spacer` only, no `shrink`** | 150 / 150 | **218 / 198** ❌ | 218 / 134 | buttons bloat, gaps, end group not flush right |
|
|
675
|
+
| `shrink` incl. `v-spacer` | 150 / 150 | 88 / 64 | **24 / 0** ❌ | row packs left (Σ 780/662 of 1200px) |
|
|
676
|
+
| **`shrink` all but `v-spacer`** | 150 / 150 ✅ | 88 / 64 ✅ | **445 / 537** ✅ | exact px, buttons fit, end group flush right |
|
|
809
677
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
- `col` viết được **chuỗi ngắn** (`"col": "shrink"`) hoặc **object** (`"col": { "class": "shrink" }`) — tương đương nhau; chuỗi ngắn gọn hơn cho toolbar. Cần thêm thứ khác (`v-if`, `style`…) thì dùng object và giữ `class: "shrink"` bên trong.
|
|
813
|
-
- **Không bao giờ là `"col": { "shrink": true }`** — dạng thuộc tính trần chỉ ăn ở V2 (xem mục trên).
|
|
814
|
-
- Cột **không** có `v-spacer` mà muốn canh phải cả hàng: đặt `prop` của row là `"row justify-end"` thay vì chèn spacer.
|
|
815
|
-
- Cần nhiều hơn một nhóm (trái | giữa | phải): chèn **hai** `v-spacer`, mỗi cái vẫn không `shrink`.
|
|
816
|
-
- Hàng **form nhập liệu** thì ngược lại — ở đó các field *nên* chia cột (`w` 1–12, không `shrink`) để co giãn theo màn hình. `shrink` là chuẩn cho **hàng điều khiển**, không phải cho mọi hàng.
|
|
817
|
-
|
|
818
|
-
### `innerHTML` Supports `{{ }}`
|
|
819
|
-
|
|
820
|
-
`innerHTML` dạng string hỗ trợ Vue template interpolation để bind text reactive:
|
|
821
|
-
|
|
822
|
-
```json
|
|
823
|
-
{ "el": "div", "w": "12", "innerHTML": "Xin chào {{formData.fullName}}" }
|
|
824
|
-
```
|
|
678
|
+
Row 2 is the common bug ("right-align these buttons" → spacer added, `shrink` forgotten).
|
|
825
679
|
|
|
826
|
-
|
|
680
|
+
- `"col": "shrink"` ≡ `"col": { "class": "shrink" }`; use the object when adding `v-if`, `style`…, keeping `class: "shrink"`.
|
|
681
|
+
- Never `"col": { "shrink": true }` (V2 only).
|
|
682
|
+
- Right-align a whole row without spacer: row `prop` `"row justify-end"`.
|
|
683
|
+
- Left | center | right: two `v-spacer`s, neither `shrink`.
|
|
684
|
+
- Data-entry form rows are the opposite: fields use `w` 1–12, no `shrink`. `shrink` is for control rows only.
|
|
827
685
|
|
|
828
|
-
|
|
829
|
-
- **Không có key `children`**: lồng control bằng `innerHTML` dạng mảng.
|
|
830
|
-
- **Attributes**: `attr` cho thuộc tính của element; `col` cho thuộc tính của cột grid.
|
|
831
|
-
- **Events**: dùng `v-on:click` (JSON hợp lệ), không dùng `@click`.
|
|
686
|
+
### Responsive
|
|
832
687
|
|
|
833
|
-
### Responsive Forms
|
|
834
688
|
```json
|
|
835
689
|
"attr": {
|
|
836
690
|
":width": "$vuetify.breakpoint.mdAndUp ? '500px' : '100%'"
|
|
@@ -839,9 +693,9 @@ Vài chi tiết đi kèm:
|
|
|
839
693
|
|
|
840
694
|
---
|
|
841
695
|
|
|
842
|
-
## 7. Common
|
|
696
|
+
## 7. Common element patterns
|
|
843
697
|
|
|
844
|
-
-
|
|
845
|
-
-
|
|
846
|
-
-
|
|
847
|
-
-
|
|
698
|
+
- Text/simple HTML: `innerHTML`.
|
|
699
|
+
- Events: `v-on:click`, `v-on:change`; wrap logic in `CALL()`.
|
|
700
|
+
- Visibility: `v-if` (pre-render) or `v-show` (CSS toggle).
|
|
701
|
+
- Lists: avoid raw `v-for`; use `f-table` or recursive partials.
|