@fui-org/fui-cli 0.1.1 → 0.3.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 +18 -3
- package/dist/fui.js +160 -43
- package/package.json +4 -2
- package/skills/fui/SKILL.md +131 -66
- package/skills/fui-skill/README.md +112 -0
- package/skills/fui-skill/SKILL.md +269 -0
- package/skills/fui-skill/assets/projectdefaultstyle.css +518 -0
- package/skills/fui-skill/design-md/airbnb/DESIGN.md +545 -0
- package/skills/fui-skill/design-md/airbnb/README.md +5 -0
- package/skills/fui-skill/design-md/airtable/DESIGN.md +554 -0
- package/skills/fui-skill/design-md/airtable/README.md +5 -0
- package/skills/fui-skill/design-md/apple/DESIGN.md +562 -0
- package/skills/fui-skill/design-md/apple/README.md +5 -0
- package/skills/fui-skill/design-md/asu/DESIGN.md +179 -0
- package/skills/fui-skill/design-md/asu/README.md +107 -0
- package/skills/fui-skill/design-md/binance/DESIGN.md +634 -0
- package/skills/fui-skill/design-md/binance/README.md +5 -0
- package/skills/fui-skill/design-md/bmw/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/bmw/README.md +5 -0
- package/skills/fui-skill/design-md/bmw-m/DESIGN.md +503 -0
- package/skills/fui-skill/design-md/bmw-m/README.md +5 -0
- package/skills/fui-skill/design-md/bugatti/DESIGN.md +454 -0
- package/skills/fui-skill/design-md/bugatti/README.md +5 -0
- package/skills/fui-skill/design-md/cal/DESIGN.md +542 -0
- package/skills/fui-skill/design-md/cal/README.md +5 -0
- package/skills/fui-skill/design-md/claude/DESIGN.md +589 -0
- package/skills/fui-skill/design-md/claude/README.md +5 -0
- package/skills/fui-skill/design-md/clay/DESIGN.md +541 -0
- package/skills/fui-skill/design-md/clay/README.md +5 -0
- package/skills/fui-skill/design-md/clickhouse/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/clickhouse/README.md +5 -0
- package/skills/fui-skill/design-md/cohere/DESIGN.md +451 -0
- package/skills/fui-skill/design-md/cohere/README.md +5 -0
- package/skills/fui-skill/design-md/coinbase/DESIGN.md +570 -0
- package/skills/fui-skill/design-md/coinbase/README.md +5 -0
- package/skills/fui-skill/design-md/composio/DESIGN.md +506 -0
- package/skills/fui-skill/design-md/composio/README.md +5 -0
- package/skills/fui-skill/design-md/cursor/DESIGN.md +537 -0
- package/skills/fui-skill/design-md/cursor/README.md +5 -0
- package/skills/fui-skill/design-md/elevenlabs/DESIGN.md +504 -0
- package/skills/fui-skill/design-md/elevenlabs/README.md +5 -0
- package/skills/fui-skill/design-md/expo/DESIGN.md +526 -0
- package/skills/fui-skill/design-md/expo/README.md +5 -0
- package/skills/fui-skill/design-md/ferrari/DESIGN.md +531 -0
- package/skills/fui-skill/design-md/ferrari/README.md +5 -0
- package/skills/fui-skill/design-md/figma/DESIGN.md +578 -0
- package/skills/fui-skill/design-md/figma/README.md +5 -0
- package/skills/fui-skill/design-md/framer/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/framer/README.md +5 -0
- package/skills/fui-skill/design-md/fui/DESIGN.md +532 -0
- package/skills/fui-skill/design-md/hashicorp/DESIGN.md +575 -0
- package/skills/fui-skill/design-md/hashicorp/README.md +5 -0
- package/skills/fui-skill/design-md/ibm/DESIGN.md +550 -0
- package/skills/fui-skill/design-md/ibm/README.md +5 -0
- package/skills/fui-skill/design-md/intercom/DESIGN.md +546 -0
- package/skills/fui-skill/design-md/intercom/README.md +5 -0
- package/skills/fui-skill/design-md/kraken/DESIGN.md +125 -0
- package/skills/fui-skill/design-md/kraken/README.md +5 -0
- package/skills/fui-skill/design-md/lamborghini/DESIGN.md +288 -0
- package/skills/fui-skill/design-md/lamborghini/README.md +5 -0
- package/skills/fui-skill/design-md/linear.app/DESIGN.md +548 -0
- package/skills/fui-skill/design-md/linear.app/README.md +5 -0
- package/skills/fui-skill/design-md/lovable/DESIGN.md +298 -0
- package/skills/fui-skill/design-md/lovable/README.md +5 -0
- package/skills/fui-skill/design-md/mastercard/DESIGN.md +365 -0
- package/skills/fui-skill/design-md/mastercard/README.md +5 -0
- package/skills/fui-skill/design-md/meta/DESIGN.md +683 -0
- package/skills/fui-skill/design-md/meta/README.md +5 -0
- package/skills/fui-skill/design-md/minimax/DESIGN.md +746 -0
- package/skills/fui-skill/design-md/minimax/README.md +5 -0
- package/skills/fui-skill/design-md/mintlify/DESIGN.md +852 -0
- package/skills/fui-skill/design-md/mintlify/README.md +5 -0
- package/skills/fui-skill/design-md/miro/DESIGN.md +825 -0
- package/skills/fui-skill/design-md/miro/README.md +5 -0
- package/skills/fui-skill/design-md/mistral.ai/DESIGN.md +773 -0
- package/skills/fui-skill/design-md/mistral.ai/README.md +5 -0
- package/skills/fui-skill/design-md/mongodb/DESIGN.md +767 -0
- package/skills/fui-skill/design-md/mongodb/README.md +5 -0
- package/skills/fui-skill/design-md/nike/DESIGN.md +575 -0
- package/skills/fui-skill/design-md/nike/README.md +5 -0
- package/skills/fui-skill/design-md/notion/DESIGN.md +821 -0
- package/skills/fui-skill/design-md/notion/README.md +5 -0
- package/skills/fui-skill/design-md/nvidia/DESIGN.md +640 -0
- package/skills/fui-skill/design-md/nvidia/README.md +5 -0
- package/skills/fui-skill/design-md/ollama/DESIGN.md +539 -0
- package/skills/fui-skill/design-md/ollama/README.md +5 -0
- package/skills/fui-skill/design-md/opencode.ai/DESIGN.md +521 -0
- package/skills/fui-skill/design-md/opencode.ai/README.md +5 -0
- package/skills/fui-skill/design-md/pinterest/DESIGN.md +597 -0
- package/skills/fui-skill/design-md/pinterest/README.md +5 -0
- package/skills/fui-skill/design-md/playstation/DESIGN.md +661 -0
- package/skills/fui-skill/design-md/playstation/README.md +5 -0
- package/skills/fui-skill/design-md/posthog/DESIGN.md +690 -0
- package/skills/fui-skill/design-md/posthog/README.md +5 -0
- package/skills/fui-skill/design-md/raycast/DESIGN.md +669 -0
- package/skills/fui-skill/design-md/raycast/README.md +5 -0
- package/skills/fui-skill/design-md/renault/DESIGN.md +589 -0
- package/skills/fui-skill/design-md/renault/README.md +5 -0
- package/skills/fui-skill/design-md/replicate/DESIGN.md +616 -0
- package/skills/fui-skill/design-md/replicate/README.md +5 -0
- package/skills/fui-skill/design-md/resend/DESIGN.md +585 -0
- package/skills/fui-skill/design-md/resend/README.md +5 -0
- package/skills/fui-skill/design-md/revolut/DESIGN.md +636 -0
- package/skills/fui-skill/design-md/revolut/README.md +5 -0
- package/skills/fui-skill/design-md/runwayml/DESIGN.md +244 -0
- package/skills/fui-skill/design-md/runwayml/README.md +5 -0
- package/skills/fui-skill/design-md/sanity/DESIGN.md +357 -0
- package/skills/fui-skill/design-md/sanity/README.md +5 -0
- package/skills/fui-skill/design-md/sentry/DESIGN.md +551 -0
- package/skills/fui-skill/design-md/sentry/README.md +5 -0
- package/skills/fui-skill/design-md/shopify/DESIGN.md +516 -0
- package/skills/fui-skill/design-md/shopify/README.md +5 -0
- package/skills/fui-skill/design-md/slack/DESIGN.md +482 -0
- package/skills/fui-skill/design-md/spacex/DESIGN.md +363 -0
- package/skills/fui-skill/design-md/spacex/README.md +5 -0
- package/skills/fui-skill/design-md/spotify/DESIGN.md +246 -0
- package/skills/fui-skill/design-md/spotify/README.md +5 -0
- package/skills/fui-skill/design-md/starbucks/DESIGN.md +580 -0
- package/skills/fui-skill/design-md/starbucks/README.md +5 -0
- package/skills/fui-skill/design-md/stripe/DESIGN.md +487 -0
- package/skills/fui-skill/design-md/stripe/README.md +5 -0
- package/skills/fui-skill/design-md/supabase/DESIGN.md +462 -0
- package/skills/fui-skill/design-md/supabase/README.md +5 -0
- package/skills/fui-skill/design-md/superhuman/DESIGN.md +448 -0
- package/skills/fui-skill/design-md/superhuman/README.md +5 -0
- package/skills/fui-skill/design-md/tesla/DESIGN.md +286 -0
- package/skills/fui-skill/design-md/tesla/README.md +5 -0
- package/skills/fui-skill/design-md/theverge/DESIGN.md +339 -0
- package/skills/fui-skill/design-md/theverge/README.md +5 -0
- package/skills/fui-skill/design-md/together.ai/DESIGN.md +633 -0
- package/skills/fui-skill/design-md/together.ai/README.md +5 -0
- package/skills/fui-skill/design-md/uber/DESIGN.md +636 -0
- package/skills/fui-skill/design-md/uber/README.md +5 -0
- package/skills/fui-skill/design-md/vercel/DESIGN.md +736 -0
- package/skills/fui-skill/design-md/vercel/README.md +5 -0
- package/skills/fui-skill/design-md/vodafone/DESIGN.md +538 -0
- package/skills/fui-skill/design-md/vodafone/README.md +5 -0
- package/skills/fui-skill/design-md/voltagent/DESIGN.md +521 -0
- package/skills/fui-skill/design-md/voltagent/README.md +5 -0
- package/skills/fui-skill/design-md/warp/DESIGN.md +526 -0
- package/skills/fui-skill/design-md/warp/README.md +5 -0
- package/skills/fui-skill/design-md/webflow/DESIGN.md +588 -0
- package/skills/fui-skill/design-md/webflow/README.md +5 -0
- package/skills/fui-skill/design-md/wired/DESIGN.md +497 -0
- package/skills/fui-skill/design-md/wired/README.md +5 -0
- package/skills/fui-skill/design-md/wise/DESIGN.md +544 -0
- package/skills/fui-skill/design-md/wise/README.md +5 -0
- package/skills/fui-skill/design-md/x.ai/DESIGN.md +465 -0
- package/skills/fui-skill/design-md/x.ai/README.md +5 -0
- package/skills/fui-skill/design-md/zapier/DESIGN.md +537 -0
- package/skills/fui-skill/design-md/zapier/README.md +5 -0
- package/skills/fui-skill/examples/component.vue +162 -0
- package/skills/fui-skill/examples/f-table-patterns.json +331 -0
- package/skills/fui-skill/examples/module-patterns.json +973 -0
- package/skills/fui-skill/examples/project-patterns.json +222 -0
- package/skills/fui-skill/metadata.json +75 -0
- package/skills/fui-skill/references/INDEX.md +144 -0
- package/skills/fui-skill/references/advanced-techniques.md +160 -0
- package/skills/fui-skill/references/coding-standards.md +112 -0
- package/skills/fui-skill/references/component-design.md +455 -0
- package/skills/fui-skill/references/component-quickref.md +77 -0
- package/skills/fui-skill/references/component-table.md +276 -0
- package/skills/fui-skill/references/components-dialog.md +192 -0
- package/skills/fui-skill/references/components-display.md +147 -0
- package/skills/fui-skill/references/components-echart.md +391 -0
- package/skills/fui-skill/references/components-input.md +359 -0
- package/skills/fui-skill/references/controls-patterns.md +847 -0
- package/skills/fui-skill/references/controls-styling-vocabulary.md +140 -0
- package/skills/fui-skill/references/db-table-design.md +77 -0
- package/skills/fui-skill/references/db-workflow.md +504 -0
- package/skills/fui-skill/references/default-function.md +415 -0
- package/skills/fui-skill/references/design-modes.md +85 -0
- package/skills/fui-skill/references/echart-templates.md +481 -0
- package/skills/fui-skill/references/fastproject.md +97 -0
- package/skills/fui-skill/references/fsheet.md +218 -0
- package/skills/fui-skill/references/fullstack-workflow.md +351 -0
- package/skills/fui-skill/references/module-data-patterns.md +126 -0
- package/skills/fui-skill/references/module-json-anatomy.md +137 -0
- package/skills/fui-skill/references/module-structure.md +260 -0
- package/skills/fui-skill/references/new-session.md +108 -0
- package/skills/fui-skill/references/pdfmake.md +60 -0
- package/skills/fui-skill/references/permission-system.md +169 -0
- package/skills/fui-skill/references/platform-architecture.md +294 -0
- package/skills/fui-skill/references/project-config.md +335 -0
- package/skills/fui-skill/references/project-provisioning.md +383 -0
- package/skills/fui-skill/references/script-map.md +296 -0
- package/skills/fui-skill/references/sql-clr-functions.md +224 -0
- package/skills/fui-skill/references/system-design.md +116 -0
- package/skills/fui-skill/references/tapi-file-api.md +191 -0
- package/skills/fui-skill/references/tapi-permission-patterns.md +158 -0
- package/skills/fui-skill/references/tapi-reference.md +549 -0
- package/skills/fui-skill/references/tools-registry.md +460 -0
- package/skills/fui-skill/references/ui-crosswindow-patterns.md +317 -0
- package/skills/fui-skill/references/ui-dialog-patterns.md +229 -0
- package/skills/fui-skill/references/ui-layout-patterns.md +176 -0
- package/skills/fui-skill/references/ui-patterns.md +315 -0
- package/skills/fui-skill/references/ui-screenshot-review.md +94 -0
- package/skills/fui-skill/references/ui-table-cell-patterns.md +316 -0
- package/skills/fui-skill/references/ui-templates.md +29 -0
- package/skills/fui-skill/references/verification.md +246 -0
- package/skills/fui-skill/references/watcher-patterns.md +196 -0
- package/skills/fui-skill/references/websocket-realtime.md +254 -0
- package/skills/fui-skill/scripts/component-3.0.js +2549 -0
- package/skills/fui-skill/scripts/component.js +3142 -0
- package/skills/fui-skill/scripts/componentTable-3.0.js +909 -0
- package/skills/fui-skill/scripts/componentTable.js +769 -0
- package/skills/fui-skill/scripts/defaultfunction-3.0.js +781 -0
- package/skills/fui-skill/scripts/defaultfunction.js +966 -0
- package/skills/fui-skill/scripts/fastproject-3.0.js +870 -0
- package/skills/fui-skill/scripts/fastproject.js +828 -0
- package/skills/fui-skill/scripts/fechart.js +890 -0
- package/skills/fui-skill/scripts/fsheet.js +1330 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# FUI Layout Patterns — page-container, master-detail, switch, dynamic label
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **pattern layout trang: page-container sticky, master-detail 2 cột, v-switch, date init, dynamic label**.
|
|
4
|
+
|
|
5
|
+
Các pattern layout và điều khiển bổ trợ. Rút ra từ project **security-manager**.
|
|
6
|
+
|
|
7
|
+
> Xem thêm: [ui-patterns.md](ui-patterns.md), [ui-table-cell-patterns.md](ui-table-cell-patterns.md), [module-structure.md](module-structure.md).
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Khởi tạo Date với moment()
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
"data": [
|
|
15
|
+
{
|
|
16
|
+
"TuNgay": "moment().format('YYYY-MM-DD')",
|
|
17
|
+
"DenNgay": "moment().format('YYYY-MM-DD')"
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
> Giá trị là JS expression string — FUI sẽ eval nó khi khởi tạo. `moment()` có sẵn trong FUI runtime.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 2. page-container + page-navigation Layout
|
|
27
|
+
|
|
28
|
+
Dùng khi cần filter bar cố định phía trên, nội dung bên dưới scroll.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"prop": "page-container grid-list-md fluid",
|
|
33
|
+
"rows": [
|
|
34
|
+
{
|
|
35
|
+
"prop": "page-navigation",
|
|
36
|
+
"cols": [
|
|
37
|
+
{ "el": "f-date", "attr": { "v-model": "TuNgay", "label": "Từ ngày", "date-add": 0 }, "w": "2" },
|
|
38
|
+
{ "el": "f-date", "attr": { "v-model": "DenNgay", "label": "Tới ngày", "date-add": 0 }, "w": "2" },
|
|
39
|
+
{
|
|
40
|
+
"el": "f-button",
|
|
41
|
+
"attr": { "color": "primary", "icon-text": "mdi-filter", "label": "Tìm", ":action": { "CALL": "getLogData" } },
|
|
42
|
+
"w": 3
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"prop": "",
|
|
48
|
+
"cols": [
|
|
49
|
+
{ "el": "f-table", "attr": { "label": "Log viewer", ":items": "LogData" } }
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `page-container` + `page-navigation` → filter bar sticky top
|
|
57
|
+
- `f-table` không khai `:update-api` → readonly view; không truyền `:headers` → tự build header từ data
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 3. Dynamic Label từ State
|
|
62
|
+
|
|
63
|
+
Dùng `:label` với expression để hiển thị thông tin context.
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"el": "f-title",
|
|
68
|
+
"attr": {
|
|
69
|
+
":label": "sFullName + ` - ` + sDepartment"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"el": "f-table",
|
|
77
|
+
"attr": {
|
|
78
|
+
":label": "`${fTableTitle==''? 'Computer list' : fTableTitle}`"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 4. v-switch — Toggle với Label
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"el": "v-layout",
|
|
90
|
+
"attr": {},
|
|
91
|
+
"innerHTML": [
|
|
92
|
+
{
|
|
93
|
+
"el": "v-switch",
|
|
94
|
+
"attr": {
|
|
95
|
+
"v-model": "switchResetPassword",
|
|
96
|
+
":required": false,
|
|
97
|
+
"label": "Đặt lại mật khẩu"
|
|
98
|
+
},
|
|
99
|
+
"w": "12"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Dùng với `v-if` để hiện/ẩn field bổ sung:
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"el": "v-layout",
|
|
109
|
+
"attr": { "v-if": "switchResetPassword" },
|
|
110
|
+
"innerHTML": [...]
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 5. Master-Detail 2 Cột
|
|
117
|
+
|
|
118
|
+
Pattern layout cha-con: bảng trái chọn row → watch → load bảng phải. Không cần mở window mới.
|
|
119
|
+
|
|
120
|
+
> Nút mở detail đặt trong ô bảng: chọn `t-button` hay `t-menu` theo quy tắc ở
|
|
121
|
+
> [ui-table-cell-patterns.md](ui-table-cell-patterns.md#khi-nào-dùng-t-button-vs-t-menu) — với
|
|
122
|
+
> master-detail thì hành động là "xem/load dữ liệu liên quan" nên dùng `t-button`.
|
|
123
|
+
|
|
124
|
+
**`data[]` và 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
|
+
**Cột `t-button` trong bảng trái:**
|
|
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
|
+
**Layout 2 cột:**
|
|
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,315 @@
|
|
|
1
|
+
# FUI UI Patterns — Real-World Design Reference (Core)
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **pattern UI cốt lõi: toolbar/filter, permission gate, checklist thiết kế giao diện**. Bảng/dialog/cross-window/layout đã tách sang các file `ui-*` riêng.
|
|
4
|
+
|
|
5
|
+
Các pattern được rút ra từ project **security-manager** — dùng làm tham chiếu khi thiết kế module mới.
|
|
6
|
+
|
|
7
|
+
File này chứa **core patterns** (data/watch/toolbar/action). Các nhóm pattern chuyên sâu đã tách ra — load thêm khi cần:
|
|
8
|
+
|
|
9
|
+
| Cần làm | File |
|
|
10
|
+
|---|---|
|
|
11
|
+
| f-table + điều khiển trong ô (t-check/t-button/t-select/t-menu, ctrl-update, chip-group) | [ui-table-cell-patterns.md](ui-table-cell-patterns.md) |
|
|
12
|
+
| Dialog/form popup (hidden-container, mở dialog với row, checkbox-list) | [ui-dialog-patterns.md](ui-dialog-patterns.md) |
|
|
13
|
+
| Mở sub-window & trao đổi dữ liệu (`openWindow`, `#PARENT`/`#Win`) | [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md) |
|
|
14
|
+
| Layout (page-container, master-detail, v-switch, date init, dynamic label) | [ui-layout-patterns.md](ui-layout-patterns.md) |
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Cấu trúc `data[]` trong module.json
|
|
19
|
+
|
|
20
|
+
`data` là mảng khởi tạo state + action. FUI xử lý tuần tự từ trên xuống dưới.
|
|
21
|
+
|
|
22
|
+
**Quy ước tổ chức:**
|
|
23
|
+
- Object **không có key action** (không có API, CALL, IF…) → khởi tạo state
|
|
24
|
+
- Object **có key là tên hàm** → định nghĩa named action (tái sử dụng)
|
|
25
|
+
- Object dạng `{ "API": ..., "OUT": ... }` hoặc `{ "CALL": ... }` ở cuối → tự chạy khi load xong
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
"data": [
|
|
29
|
+
{
|
|
30
|
+
"dsUser": [],
|
|
31
|
+
"dsGroup": [],
|
|
32
|
+
"sDepartment": null,
|
|
33
|
+
"dlUserInfo": false
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"getDepartment": { "API": "SM_DepartmentSelectAll", "OUT": "dsDepartment" },
|
|
37
|
+
"getGroup": { "API": "SM_Group_SelectAll", "OUT": "dsGroup" },
|
|
38
|
+
"getUser": {
|
|
39
|
+
"API": "SM_Users_SelectByDepGroup",
|
|
40
|
+
"IN": { "GroupID": "sGroup", "DepartmentID": "sDepartment" },
|
|
41
|
+
"OUT": "dsUser"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{ "CALL": "getDepartment" },
|
|
45
|
+
{ "CALL": "getGroup" }
|
|
46
|
+
]
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Auto-startup**: CALL ở cuối `data[]` sẽ tự chạy khi module load — không cần event handler riêng.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Cascading API Init
|
|
54
|
+
|
|
55
|
+
Dùng `CALLBACK` để set giá trị từ kết quả API đầu tiên, rồi API tiếp theo tự chạy qua `watch`.
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
"data": [
|
|
59
|
+
{ "dsModule": [], "sModuleID": null },
|
|
60
|
+
{
|
|
61
|
+
"API": "/SM_Modules_SelectAll",
|
|
62
|
+
"OUT": "dsModule",
|
|
63
|
+
"CALLBACK": { "sModuleID": "dsModule[0].ModuleID" }
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"watch": {
|
|
67
|
+
"sModuleID": { "CALL": "getFunctionRight" }
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> `sModuleID` được set từ CALLBACK → watch tự kích hoạt `getFunctionRight`. Không cần gọi thủ công.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. Watch — Cascading Filter
|
|
76
|
+
|
|
77
|
+
Khi filter thay đổi → tự reload data. Nhiều watch → mảng CALL.
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
"watch": {
|
|
81
|
+
"sDepartment": { "CALL": "getUser" },
|
|
82
|
+
"sGroup": { "CALL": "getUser" },
|
|
83
|
+
"sModuleID": [
|
|
84
|
+
{ "CALL": "getUserModule" },
|
|
85
|
+
{ "CALL": "getSysRight" },
|
|
86
|
+
{ "CALL": "getFunctionRight" }
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 4. Toolbar / Filter Row Pattern
|
|
94
|
+
|
|
95
|
+
Row chứa filter fields và action buttons, `v-spacer` đẩy nhóm nút về phải.
|
|
96
|
+
|
|
97
|
+
> **Chuẩn mực bố cục của hàng này: MỌI cột đều `"col": "shrink"`, trừ `v-spacer`.** Cột không `shrink` là cột được chia phần trống — kể cả cột `w` rỗng, kể cả nút. Thiếu `shrink` thì nút phình ra, hở khoảng giữa các nút, và nhóm cuối hàng không sát mép phải dù đã có `v-spacer`. Khuôn chuẩn + số đo trên framework thật: [controls-patterns.md](controls-patterns.md) §Hàng nhiều control (toolbar).
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"prop": "",
|
|
102
|
+
"cols": [
|
|
103
|
+
{
|
|
104
|
+
"el": "v-autocomplete",
|
|
105
|
+
"col": { "class": "shrink" },
|
|
106
|
+
"attr": {
|
|
107
|
+
"class": "flex-md-grow-0",
|
|
108
|
+
"style": "width:200px",
|
|
109
|
+
"v-model": "sGroup",
|
|
110
|
+
"label": "Group",
|
|
111
|
+
":items": "dsGroup",
|
|
112
|
+
"item-text": "GroupName",
|
|
113
|
+
"item-value": "GroupID",
|
|
114
|
+
":required": false
|
|
115
|
+
},
|
|
116
|
+
"w": "200"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"el": "v-text-field",
|
|
120
|
+
"col": { "class": "shrink" },
|
|
121
|
+
"attr": {
|
|
122
|
+
"class": "flex-md-grow-0",
|
|
123
|
+
"style": "width:200px",
|
|
124
|
+
"v-model": "UserID",
|
|
125
|
+
"label": "User ID",
|
|
126
|
+
"clearable": ""
|
|
127
|
+
},
|
|
128
|
+
"w": "200"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"el": "f-button",
|
|
132
|
+
"col": { "class": "shrink" },
|
|
133
|
+
"attr": {
|
|
134
|
+
":checkvalid": false,
|
|
135
|
+
"color": "primary",
|
|
136
|
+
"label": "Tìm",
|
|
137
|
+
"icon-text": "mdi-magnify",
|
|
138
|
+
":action": { "CALL": "getUser" }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{ "el": "v-spacer", "attr": {} },
|
|
142
|
+
{
|
|
143
|
+
"el": "f-button",
|
|
144
|
+
"col": { "v-if": "vueData.user.SystemRight==9", "class": "shrink" },
|
|
145
|
+
"attr": {
|
|
146
|
+
":checkvalid": false,
|
|
147
|
+
":disabled": "!sGroup",
|
|
148
|
+
"color": "primary",
|
|
149
|
+
"label": "Add User",
|
|
150
|
+
"icon-text": "mdi-account-multiple-plus",
|
|
151
|
+
":action": [
|
|
152
|
+
{ "addNew": 1, "sUserID": "", "sUserName": "" },
|
|
153
|
+
{ "dlUserInfo": true }
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Quy tắc:**
|
|
162
|
+
- Filter fields: `w` là px (`>= 25`) + `col: "shrink"`. Cặp `attr: { class: "flex-md-grow-0", style: "width:Xpx" }` ở ví dụ trên là **thừa** — `w` + `shrink` đã cho đúng bề rộng đó (đo được: `w:150` + `shrink` → đúng 150px ở cả V2 lẫn V3); giữ lại trong ví dụ vì đây là module production chép nguyên, viết mới thì bỏ đi
|
|
163
|
+
- Nút tìm: ngay sau fields, cũng `col: "shrink"` (nút **không** shrink sẽ bị kéo dãn ra)
|
|
164
|
+
- **Luôn là `class: "shrink"` / `"col": "shrink"`, không phải `shrink: true`** — dạng thuộc tính trần chỉ ăn ở V2, ở V3 nó trơ và cột giãn full
|
|
165
|
+
- `v-spacer`: cột **duy nhất** không `shrink` — nó phải giãn được thì mới đẩy được nhóm sau sang phải
|
|
166
|
+
- Nút Add/Delete admin: sau spacer, thêm `v-if` permission — `col: { "v-if": "…", "class": "shrink" }` (object khi cần thêm key, vẫn giữ `shrink`)
|
|
167
|
+
- Không có `v-spacer` mà muốn canh phải cả hàng: `"prop": "row justify-end"`
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## 5. Permission Gating
|
|
172
|
+
|
|
173
|
+
Ẩn/hiện phần tử theo `SystemRight` của user.
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
"col": { "v-if": "vueData.user.SystemRight>1" }
|
|
177
|
+
"col": { "v-if": "vueData.user.SystemRight==9" }
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- `>1` — visible với mọi user có quyền (không phải level thấp nhất)
|
|
181
|
+
- `==9` — chỉ admin cao nhất mới thấy
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6. IF/THEN/ELSE — Conditional Action
|
|
186
|
+
|
|
187
|
+
Rẽ nhánh logic trong action hoặc CALLBACK.
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"IF": "TypeLoad==1",
|
|
192
|
+
"THEN": { "CALL": "getComputer" },
|
|
193
|
+
"ELSE": {
|
|
194
|
+
"IF": "TypeLoad==2",
|
|
195
|
+
"THEN": { "CALL": "getComputerDinied" },
|
|
196
|
+
"ELSE": { "CALL": "getComputerNotVisited" }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- Condition: JavaScript expression dùng vueData fields trực tiếp (không cần `vueData.`)
|
|
202
|
+
- Dùng nested IF/ELSE cho switch-case nhiều nhánh
|
|
203
|
+
|
|
204
|
+
**Ứng dụng thực tế:** Module có nhiều chế độ xem (`TypeLoad`) — sau khi update, refresh đúng danh sách đang hiển thị.
|
|
205
|
+
|
|
206
|
+
```json
|
|
207
|
+
"data": [
|
|
208
|
+
{ "TypeLoad": 1 }
|
|
209
|
+
]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## 7. CALLBACK Chaining
|
|
215
|
+
|
|
216
|
+
CALLBACK là array → thực thi tuần tự.
|
|
217
|
+
|
|
218
|
+
> **Nhiều lệnh thì BẮT BUỘC là mảng, mỗi lệnh một `{}` riêng.** Gộp `CALL` + `MESS` + `EXE` vào chung một object thì chỉ **một** lệnh chạy (ưu tiên `API` → `CALL` → `EXE` → `FUN` → `IF` → `MESS` → `MESSBOX`, mỗi nhánh `return` ngay), phần còn lại rơi im lặng — không error, không `[Vue warn]`. Luật đầy đủ + lý do trong runtime: [controls-patterns.md](controls-patterns.md) §QUY TẮC NỀN.
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
"CALLBACK": [
|
|
222
|
+
{ "MESS": "Cập nhật thành công." },
|
|
223
|
+
{ "dlUserInfo": false },
|
|
224
|
+
{ "CALL": "getUser" }
|
|
225
|
+
]
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
"CALLBACK": [
|
|
230
|
+
{ "CALL": "getUserModule" },
|
|
231
|
+
{ "sUserDeptSelected": [], "dlAddUser": false },
|
|
232
|
+
{ "MESS": "Thêm user thành công." }
|
|
233
|
+
]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Thứ tự thông dụng:** reload data → reset state → hiển thị message.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 8. set — Cấu hình Module Level
|
|
241
|
+
|
|
242
|
+
`set` ở root module.json — cấu hình behavior của toàn module.
|
|
243
|
+
|
|
244
|
+
```json
|
|
245
|
+
"set": {
|
|
246
|
+
"menu": false
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
- `menu: false` — ẩn menu navigation (dùng cho sub-window, log viewer)
|
|
251
|
+
|
|
252
|
+
> Module mở qua `openWindow`: khai báo `"set": { "menu": false }` trong module.json của module con —
|
|
253
|
+
> **không** truyền `?menu=0` trong URL. Pattern đầy đủ (kể cả cách set động từ `data[]`):
|
|
254
|
+
> [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md).
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 9. CALLBACK với JavaScript Expression
|
|
259
|
+
|
|
260
|
+
Dùng `{{ expression }}` trong CALLBACK để chạy code phức tạp sau API.
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
"CALLBACK": "{{ _.forEach(dsUsers, (x)=> x.FunctionName = JSON.parse(x.FunctionName)) }}"
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
"CALLBACK": "{{ _.forEach(dsUserFunction, (x)=> x.FunctionName = JSON.parse(x.FunctionName)), _.forEach(dsUserFunction, (x)=> x.UserSysItem = JSON.parse(x.UserSysItem)) }}"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
> Dùng khi API trả về JSON string cần parse thành object/array trước khi render.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## 10. script.js — Helper Functions
|
|
275
|
+
|
|
276
|
+
`script.js` chứa helper functions gọi từ `v-on:click` hoặc action.
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
// script.js
|
|
280
|
+
function setFunction(user, fc) {
|
|
281
|
+
vueData.sFunctionSelected = [];
|
|
282
|
+
vueData.sUserSelected = [];
|
|
283
|
+
vueData.sUserSelected.push(user);
|
|
284
|
+
if (!fc) return;
|
|
285
|
+
vueData.dsFunctionRight.map((item) => {
|
|
286
|
+
if (fc.some(fci => fci.FunctionID == item.FunctionID)) {
|
|
287
|
+
vueData.sFunctionSelected.push(item.FunctionID.toString());
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Gọi từ `v-on:click` trong header:
|
|
294
|
+
```json
|
|
295
|
+
"v-on:click": "vueData.dlFunction = true; CALL(setFunction(item, item.FunctionName));"
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
> Functions trong `script.js` có thể access `vueData` trực tiếp. Dùng để xử lý logic phức tạp không thể làm bằng action JSON thuần.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Checklist Thiết Kế UI
|
|
303
|
+
|
|
304
|
+
> Checklist cấu trúc `module.json` (thứ tự `data[]`, grid wrapper, `w`, `v-on:`, `set`, JSON hợp lệ)
|
|
305
|
+
> nằm ở [module-json-anatomy.md](module-json-anatomy.md#checklist-khi-viết-modulejson) — dưới đây chỉ
|
|
306
|
+
> là phần riêng của thiết kế giao diện.
|
|
307
|
+
|
|
308
|
+
1. **Toolbar row**: shrink filters + v-spacer + permission-gated buttons
|
|
309
|
+
2. **f-table**: fixed-header, item-class cho row highlight, item-key đúng — xem [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
|
|
310
|
+
3. **Dialogs**: luôn trong hidden-container, v-card[padding:0] + v-toolbar[dense flat] — xem [ui-dialog-patterns.md](ui-dialog-patterns.md)
|
|
311
|
+
4. **menuChucNang**: định nghĩa trong data[], ref bằng t-menu trong headers — xem [ui-table-cell-patterns.md](ui-table-cell-patterns.md)
|
|
312
|
+
5. **ctrl-update**: cho CRUD đơn giản, dialog thủ công cho form phức tạp
|
|
313
|
+
6. **CALLBACK**: chaining array — reload → reset state → MESS
|
|
314
|
+
7. **#PARENT**: set ngay đầu data[] trước các CALL startup — xem [ui-crosswindow-patterns.md](ui-crosswindow-patterns.md)
|
|
315
|
+
8. **Permission**: `v-if: "vueData.user.SystemRight>1"` cho admin-only sections
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Review giao diện qua ảnh chụp màn hình
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **cách đọc và đánh giá ảnh chụp giao diện (user gửi hoặc từ `renderUI`)**.
|
|
4
|
+
|
|
5
|
+
Áp dụng khi có **ảnh giao diện** để đánh giá, từ một trong hai nguồn:
|
|
6
|
+
|
|
7
|
+
1. **User gửi ảnh chụp màn hình vào chat** — "xem giao diện này", "sao nhìn xấu vậy", "sửa lại cho đẹp", hoặc chỉ gửi ảnh kèm mô tả lỗi.
|
|
8
|
+
2. **Screenshot từ `module_simulate({ renderUI: true })`** — `render.png` (inline), `render-full.png`, `render-dialog.jpg`.
|
|
9
|
+
|
|
10
|
+
Nguyên tắc cốt lõi: **nhìn ảnh như một designer QA** — quét có hệ thống theo checklist bên dưới, và mỗi phát hiện phải map về **nguyên nhân cụ thể trong code** (controls[i] nào trong module.json, class/style nào, CSS nào trong header.html) kèm fix đề xuất. Đừng chỉ mô tả cảm tính "nhìn chưa đẹp".
|
|
11
|
+
|
|
12
|
+
**BẮT BUỘC — phát hiện xong là phải SỬA, không phải chỉ ghi nhận.** Checklist A–D bên dưới không phải danh sách tham khảo: nhìn thấy chữ dính/chồng lấn (khoảng hở quá ít), control lệch hàng/không thẳng, khoảng trắng dư thừa bất thường giữa các control — đây là lỗi thẩm mỹ **phải xử lý trong cùng lượt**, y hệt một lỗi hiển thị cứng. "Đã ghi nhận vào báo cáo" không phải là "đã xong việc". Chỉ dừng lại hỏi user khi bản thân không chắc ý đồ thiết kế (vd không rõ hai khối có nên tách hàng hay không) — còn lại thì tự sửa rồi render lại đối chiếu (bước 4 dưới đây).
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quy trình
|
|
17
|
+
|
|
18
|
+
1. **Xác định bối cảnh** trước khi phán:
|
|
19
|
+
- App-mode (CRUD/quản lý — chuẩn thẩm mỹ `design_read("fui")`) hay Web-mode (landing — theo brand DESIGN.md đã chọn)?
|
|
20
|
+
- Desktop hay mobile? (ảnh hẹp/dọc → đánh giá theo chuẩn responsive)
|
|
21
|
+
- Có module.json tương ứng trong workspace không? (có → đối chiếu ảnh ↔ controls để chỉ đúng vị trí sửa; không → nhận xét trên ảnh + xin module.json nếu cần sửa)
|
|
22
|
+
2. **Quét ảnh theo checklist A→D** (dưới) — ghi nhận từng phát hiện.
|
|
23
|
+
3. **Báo cáo dạng bảng**: `Phát hiện | Mức độ (lỗi/cần sửa/gợi ý) | Vị trí trên ảnh | Nguyên nhân khả dĩ | Fix đề xuất`.
|
|
24
|
+
4. **Tự sửa → render lại đối chiếu** (bắt buộc cho mọi phát hiện ở mục A/B, không chỉ lỗi cứng): nếu có workspace + Playwright, sửa module.json rồi `module_simulate({ renderUI: true })` (cùng viewport/device với ảnh gốc nếu đoán được) và so sánh screenshot mới với ảnh user gửi — lặp cho tới khi sạch. `renderUI` tự phát hiện text dính/chồng lấn (đối chiếu bounding-rect, xem mục A) và in vào DOM AUDIT kèm dòng "PHẢI sửa, không được bỏ qua" — thấy dòng đó thì sửa ngay trong lượt này, đừng để nguyên rồi báo cáo lại cho user quyết.
|
|
25
|
+
|
|
26
|
+
Ảnh mờ/cắt/không đủ thông tin → hỏi user chụp lại vùng cụ thể, đừng đoán bừa.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Checklist quét ảnh
|
|
31
|
+
|
|
32
|
+
### A. Lỗi hiển thị cứng (bug — phải sửa)
|
|
33
|
+
|
|
34
|
+
- Vùng trống bất thường: component không render (app rỗng, card trống, chart trắng/0-size)
|
|
35
|
+
- Text lỗi lộ ra ngoài: `undefined`, `NaN`, `[object Object]`, message exception, key thay vì value
|
|
36
|
+
- Tràn ngang: thanh cuộn ngang xuất hiện, phần tử bị cắt mép phải
|
|
37
|
+
- Chữ/phần tử đè lên nhau, hoặc dính sát nhau tới mức đọc như liền một chữ (khoảng hở quá ít); label bị cắt cụt không chủ ý (truncate `...` ở chỗ không nên). `renderUI` dò được phần lớn ca này tự động (đối chiếu bounding-rect của các phần tử mang text, dòng `⚠ text dính/chồng lấn` trong DOM AUDIT) — nhưng đó chỉ là lưới an toàn, mắt vẫn phải tự quét thêm vì heuristic bỏ sót các ca không phải text thuần (icon chồng nút, ảnh chồng card...)
|
|
38
|
+
- Ảnh vỡ (khung ảnh trống/icon broken), icon thiếu (ô vuông tofu — thường do font mdi chưa load hoặc sai tên icon)
|
|
39
|
+
- Loading spinner đứng mãi (v_Loading không về false — thường API treo/lỗi init)
|
|
40
|
+
|
|
41
|
+
### B. Bố cục, căn chỉnh, khoảng cách
|
|
42
|
+
|
|
43
|
+
- **Thẳng hàng**: field cùng row thẳng baseline, cùng chiều cao (`dense`/`outlined` đồng bộ); các card/khối cùng hàng cùng chiều cao
|
|
44
|
+
- **Căn trái/phải đúng ngữ nghĩa**: text căn trái; **số/tiền tệ trong bảng căn phải** (`t-num`); tiêu đề cột khớp hướng căn của data
|
|
45
|
+
- **Vị trí nút chuẩn**: nút hành động chính của trang ở toolbar bên phải; trong dialog nút chính (Lưu/Đồng ý) nằm **phải** của `v-card-actions` (có `v-spacer` đẩy); không có nút "Đóng" thừa (D7)
|
|
46
|
+
- **Hàng toolbar — nút phình / hở khoảng đều nhau**: triệu chứng đặc trưng của cột thiếu `shrink`. Nút rộng hơn hẳn chữ bên trong, các nút cách nhau những khoảng **bằng nhau**, và nhóm nút cuối **không sát mép phải** dù đã có `v-spacer` ⇒ các cột đó vẫn `flex-grow: 1` nên được chia phần trống y như spacer. Chuẩn: **mọi cột `"col": "shrink"`, trừ đúng `v-spacer`** — kể cả cột `w` rỗng ([controls-patterns.md](controls-patterns.md) §Hàng nhiều control). `module_validate` bắt được ca này trước khi phải nhìn ảnh
|
|
47
|
+
- **Khoảng cách nhất quán**: padding/margin theo nhịp Vuetify (bội 4px — pa-2/ma-2...); không có khoảng hở to bất thường giữa các khối, không có khối dính sát mép màn hình
|
|
48
|
+
- **Bảng data**: cuộn CHỈ trong bảng (bảng chính khai `":height": -16`), trang không có 2 thanh cuộn dọc lồng nhau, header bảng không trôi mất khi cuộn (D9)
|
|
49
|
+
- **Cân đối grid**: các cột trong row có tỷ lệ hợp lý (Σw ≤ 12), không có cột bị bóp quá hẹp làm nội dung xuống dòng xấu
|
|
50
|
+
|
|
51
|
+
### C. Màu sắc & phân cấp thị giác (App-mode đối chiếu `design_read("fui")`)
|
|
52
|
+
|
|
53
|
+
- Đối chiếu bộ quy tắc D1–D9 của validator: toolbar dialog **cùng màu nền** thân (không color/dark); nút xóa màu **warning cam** (không đỏ/error); width dialog thuộc bộ 400/550/600/650/800
|
|
54
|
+
- Một màn hình chỉ 1–2 màu nhấn; không tô màu tùy hứng vào card/nút phụ
|
|
55
|
+
- Contrast đủ đọc (chữ xám nhạt trên nền trắng, chữ trên nền màu — nghi ngờ thì báo)
|
|
56
|
+
- Trạng thái (chip/badge) dùng màu ngữ nghĩa nhất quán: success/warning/error cùng nghĩa ở mọi nơi
|
|
57
|
+
|
|
58
|
+
### D. Mật độ & responsive
|
|
59
|
+
|
|
60
|
+
- Mức `dense`/gọn đồng bộ toàn màn (không chỗ dày chỗ thưa)
|
|
61
|
+
- Ảnh mobile: cột tự xuống dòng đúng, nút không tràn/không quá bé để bấm, font đọc được, bảng có phương án mobile (`mobile-breakpoint`)
|
|
62
|
+
- `viewport-fit=cover` (mặc định của FUI): nội dung sát mép trên/dưới ở màn có notch → cần safe-area padding
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Map triệu chứng → nguyên nhân thường gặp
|
|
67
|
+
|
|
68
|
+
| Triệu chứng trên ảnh | Nguyên nhân hay gặp | Fix |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Trang có thanh cuộn dọc, bảng dài vô tận | f-table/f-sheet không khai `:height` → tự giãn theo số dòng | Thêm `":height": -16` (tự đo `rect.top`, fill tới đáy) |
|
|
71
|
+
| Bảng hụt/tràn một khoảng cố định | `:height` cũ tính tay `"$( window ).height()-N"` không khớp toolbar thật | Thay bằng `":height": -16` |
|
|
72
|
+
| Field trong form so le chiều cao | Trộn field có/không `dense`, có/không `hide-details` | Đồng bộ `outlined dense hide-details` cả form |
|
|
73
|
+
| Số trong bảng căn trái | Cột thiếu `el: "t-num"` / align | Xem `component-table.md` |
|
|
74
|
+
| Chart trắng | Container 0-height lúc chart init | Cho container `:height` cụ thể; renderUI audit "canvas 0-size" xác nhận |
|
|
75
|
+
| Icon ô vuông | Sai tên `mdi-*` hoặc font chưa load | Kiểm tra tên icon; import materialdesignicons |
|
|
76
|
+
| Dialog tràn màn hình / bé tẹo | width không thuộc bộ chuẩn | Bộ width 400/550/600/650/800 (D3) |
|
|
77
|
+
| Nút chính lệch trái trong dialog | Thiếu `v-spacer` trong v-card-actions | Thêm v-spacer trước nút chính |
|
|
78
|
+
| Khối dính mép màn hình | Thiếu page-container / pa-* | Xem `ui-layout-patterns.md` |
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Meta viewport mặc định của FUI (ảnh hưởng UI)
|
|
83
|
+
|
|
84
|
+
Trang FUI mặc định đã có sẵn trong `<head>` (KHÔNG lặp lại trong header.html):
|
|
85
|
+
|
|
86
|
+
```html
|
|
87
|
+
<meta charset="UTF-8"/>
|
|
88
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Hệ quả khi đánh giá/thiết kế:
|
|
92
|
+
- `user-scalable=no, maximum-scale=1` — user **không pinch-zoom được** → font/nút phải đủ lớn ngay từ đầu trên mobile, không trông chờ zoom.
|
|
93
|
+
- `width=device-width, initial-scale=1` — layout theo đúng CSS pixel của thiết bị; breakpoint Vuetify hoạt động chuẩn.
|
|
94
|
+
- `viewport-fit=cover` — trang tràn vào vùng notch/home-indicator trên iOS → nội dung sát mép trên/dưới cần `env(safe-area-inset-*)` padding khi làm fullscreen/HTMLOnly.
|