@fui-org/fui-cli 0.2.0 → 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 +13 -1
- 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,112 @@
|
|
|
1
|
+
# FUI Coding Standards
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **chuẩn code chung: đặt tên, quy ước `<style scoped>` trong `.vue` (scope thủ công), cấm backtick trong `<template>`, reusable API**. Quy tắc thiết kế component chi tiết xem [component-design.md](component-design.md).
|
|
4
|
+
|
|
5
|
+
## 1. Naming Conventions
|
|
6
|
+
|
|
7
|
+
### Components (`components/`)
|
|
8
|
+
- **Prefix**: Use `uc-` (User Component) for custom module components to distinguish from standard FUI components (`f-`, `t-`).
|
|
9
|
+
- **Format**: Kebab-case (e.g., `uc-trangthai-sukien.vue`, `uc-user-profile.vue`).
|
|
10
|
+
- **Props**: Use camelCase in script, kebab-case in templates (e.g., `userProfile` -> `:user-profile`).
|
|
11
|
+
- **Prefer reusable APIs**: Default to generic props, emits, and slots so the component can be reused across modules or screens.
|
|
12
|
+
- **Avoid overfitting names**: Prefer neutral contracts like `items`, `value`, `label`, `loading`, `readonly`, `disabled`, `options`, `config` unless the business domain truly requires a specific prop name.
|
|
13
|
+
- **Push orchestration upward**: Keep API calls, route changes, and page-specific coordination in `module.json` or the parent when possible. Let the component focus on presentation and local interaction.
|
|
14
|
+
- **Emit upward**: Prefer `$emit(...)` for `input`, `change`, `select`, `submit`, `remove`, or explicit action events rather than mutating parent-owned state.
|
|
15
|
+
|
|
16
|
+
### CSS Classes
|
|
17
|
+
- **`<style scoped>` in `.vue` is allowed** (saved server-side as `ComCSS`), but `scoped` has **no real scoping effect** — FUI only concatenates `ComCSS` from every component in the module into one global stylesheet (`ComponentCSSBuild()`), no AST transform, no `data-v-xxxx` attribute injection. **Every selector must be manually scoped**: give the component's root template element a unique class matching the component name (kebab-case, e.g. `uc-invoice-list`), then write every rule as a descendant selector starting from that class (`.uc-invoice-list__title`, never a bare `.title`). See [component-design.md](component-design.md) for a full example.
|
|
18
|
+
- **Style Placement**: Component-local CSS → `<style scoped>` in the `.vue` file (with manual scoping above). Module-wide CSS → `style.css` (publish via `module_publish_css`). `header.html` is still valid for CSS but no longer the only option.
|
|
19
|
+
- **Vuetify Utilities**: Prioritize Vuetify helper classes (e.g., `ma-2`, `pa-0`, `d-flex`, `primary--text`).
|
|
20
|
+
- **Custom Classes**: Use meaningful prefixed names (e.g., `ep-hero`, `ep-field-grid`). Avoid generic names like `.box` or `.red`.
|
|
21
|
+
- **State Classes**: Use descriptive names for state (e.g., `.is-active`, `.has-error`).
|
|
22
|
+
|
|
23
|
+
### Action Keys (`module.json`)
|
|
24
|
+
- **API Actions**: Predix with `api` (e.g., `apiGetDSSuKien`, `apiUpdateUser`).
|
|
25
|
+
- **Event Handlers**: Prefix with `handle` (e.g., `handleOpenReport`, `handleSubmit`).
|
|
26
|
+
- **Dialog Actions**: Prefix with verb (e.g., `openUploadDialog`, `closeSettings`).
|
|
27
|
+
|
|
28
|
+
## 2. Menu Configuration (`set.menu`)
|
|
29
|
+
|
|
30
|
+
Define the application menu in the `set` object of `module.json`.
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
"menu": [
|
|
34
|
+
{
|
|
35
|
+
"name": "Main Group",
|
|
36
|
+
"icon": "mdi-home", // Material Design Icons
|
|
37
|
+
"url": "/dashboard", // Route
|
|
38
|
+
"right": { // Permission check
|
|
39
|
+
"SystemRight": [1, 2] // Array of allowed Right IDs
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "Management",
|
|
44
|
+
"icon": "mdi-cog",
|
|
45
|
+
"submenu": [ // Nested menu items
|
|
46
|
+
{
|
|
47
|
+
"name": "Users",
|
|
48
|
+
"url": "/users"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "Settings",
|
|
52
|
+
"url": "/settings"
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 3. Project Structure
|
|
60
|
+
|
|
61
|
+
- **Canonical module structure**: Follow [module-structure.md](module-structure.md) as the default module layout in both workspace and chat contexts. In workspace-aware environments, apply it to real local files. In chat contexts, use it to virtualize the same module structure in the response.
|
|
62
|
+
- **Required core files**: Keep `_info.json` and `module.json` at module root.
|
|
63
|
+
- **`components/`**: Only place `.vue` files here. Do not sub-folder unless strictly necessary (FUI auto-scans this root).
|
|
64
|
+
- **`components/_components.json`**: Maintain component registry when using `uc-*` components.
|
|
65
|
+
- **`header.html`**: Still valid for CSS, but component CSS should prefer `<style scoped>` in the `.vue` (manually scoped) and module-wide CSS should prefer `style.css`.
|
|
66
|
+
- **`module.json`**: Keep this file clean. Move large static lists to the database or separate JSON files if supported.
|
|
67
|
+
|
|
68
|
+
## 4. Best Practices
|
|
69
|
+
|
|
70
|
+
- **Data Binding**: Avoid complex logic in JSON attributes. Use computed properties in components or simpler `vueData` structures.
|
|
71
|
+
- **Event Handling**: Use `CALL(vueData.actionName)` for all complex interactions. Avoid inline JS like `vueData.count++` for anything beyond simple toggles.
|
|
72
|
+
- **Mobile Responsiveness**: Always configure `configForm.xs` and `configForm.md` for responsive form widths.
|
|
73
|
+
|
|
74
|
+
## 5. Component Registration (`_components.json`)
|
|
75
|
+
|
|
76
|
+
Components are registered using an **upsert pattern**:
|
|
77
|
+
|
|
78
|
+
- **New component** (before publish): Only `comName` is required. Do NOT manually assign `comID`.
|
|
79
|
+
```json
|
|
80
|
+
[
|
|
81
|
+
{ "comName": "hr-employee-profile" }
|
|
82
|
+
]
|
|
83
|
+
```
|
|
84
|
+
- **After publish & sync**: The server auto-assigns `comID`. The file gets updated on sync:
|
|
85
|
+
```json
|
|
86
|
+
[
|
|
87
|
+
{ "comID": 7813, "comName": "hr-employee-profile" }
|
|
88
|
+
]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Rule**: Never manually create or modify `comID`. It is server-generated.
|
|
92
|
+
|
|
93
|
+
## 6. Vue Component Design
|
|
94
|
+
|
|
95
|
+
Xem toàn bộ quy tắc thiết kế `uc-*.vue` component tại [component-design.md](component-design.md), bao gồm:
|
|
96
|
+
- Không dùng `components: {}` để đăng ký
|
|
97
|
+
- `<style scoped>` trong file `.vue` phải tự scope thủ công (xem mục CSS Classes ở trên)
|
|
98
|
+
- Không dùng backtick trong `<template>`
|
|
99
|
+
- Không dùng `v-dialog` — dùng cấu trúc `v-overlay` thay thế
|
|
100
|
+
- Cấu trúc bắt buộc cho dialog component
|
|
101
|
+
|
|
102
|
+
## 7. Complex Module Architecture
|
|
103
|
+
|
|
104
|
+
When a module's `module.json` controls exceed ~200 lines:
|
|
105
|
+
|
|
106
|
+
- **Extract to Vue component**: Move the UI into a `.vue` component. Keep `module.json` lean (data/API only + a single component call in `controls`).
|
|
107
|
+
- **Props binding**: Pass all data from `module.json` via props. Use kebab-case for prop names in templates (`:nhan-vien="nhanVien"`).
|
|
108
|
+
- **Design for reuse first**: Before naming props or methods, check whether the component can be expressed as a generic list, card, dialog body, filter panel, summary block, or form section reused by other modules.
|
|
109
|
+
- **Configurable states**: Expose loading, empty, disabled, and readonly behavior through props or slots instead of hardcoding one workflow.
|
|
110
|
+
- **Sticky headers**: When combining multiple sticky elements (e.g., hero + tabs), wrap them in **one parent div** with `position: sticky` instead of making each element sticky individually.
|
|
111
|
+
- **Tab navigation vs Accordion**: For 5+ sections of data, prefer horizontal `v-tabs` over `v-expansion-panels`. Tabs show one section at a time, reduce cognitive overload, and support swipe gestures on mobile.
|
|
112
|
+
- **Swipe gestures**: Attach `touchstart`/`touchend` listeners on the **outermost wrapper** (not on content area) so swipe works regardless of content height.
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
# FUI Vue Component Design Rules
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **quy tắc viết `uc-*.vue`: scope module/project, cấm `name:`/`components:{}`, props/emits/slots, cấm `_injectStyle`, quy tắc scope thủ công cho `<style scoped>`, quy trình kiểm chứng component (`component_preview` + luật push component cấp project trước khi render module), và quy ước `{projectId}/COMPONENTS.md` (tra component project-scope đã có + vòng tích luỹ)**. Bộ class/prop được duyệt xem [controls-styling-vocabulary.md](controls-styling-vocabulary.md).
|
|
4
|
+
|
|
5
|
+
Quy tắc bắt buộc khi viết bất kỳ Vue component nào (`uc-*.vue`) trong hệ thống FUI. Áp dụng cho cả component thông thường lẫn dialog component.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Hai cấp component: module-scope vs project-scope
|
|
10
|
+
|
|
11
|
+
| | Module-scope | Project-scope (global) |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| **Vị trí file** | `{projectId}/modules/{moduleId}/components/uc-*.vue` | `{projectId}/components/uc-*.vue` |
|
|
14
|
+
| **Registry** | `modules/{moduleId}/components/_components.json` | `{projectId}/components/_components.json` |
|
|
15
|
+
| **Phạm vi** | Chỉ trong module đó | **Tất cả modules trong project** |
|
|
16
|
+
| **Auto-load** | Trong module đã có component | Tự động — **không cần khai báo gì thêm** |
|
|
17
|
+
| **Khi nào dùng** | Component chỉ dùng ở một module | Component dùng lại ở nhiều module |
|
|
18
|
+
|
|
19
|
+
> **Project-scope component được FUI tự động load ở cấp global** — bất kỳ module nào trong project đều dùng được `<uc-*>` ngay, không cần tạo lại hay khai báo thêm.
|
|
20
|
+
|
|
21
|
+
### Quy tắc chọn scope
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
Component này sẽ dùng ở nhiều module? → project-scope
|
|
25
|
+
Component chỉ dùng trong một module? → module-scope
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Bảng thao tác theo scope
|
|
29
|
+
|
|
30
|
+
| Thao tác | Module-scope | Project-scope (global) |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| **Tạo mới** (chưa có trên server) | `component_new(componentName)` — dùng session.moduleId | `component_new(componentName, projectId=<pid>)` — KHÔNG có moduleId |
|
|
33
|
+
| **Cập nhật** (đã có trên server) | `component_update(componentName)` — dùng session.moduleId | `component_update(componentName, projectId=<pid>)` — KHÔNG có moduleId |
|
|
34
|
+
| **Xóa** | `component_delete(componentName)` — dùng session.moduleId | `component_delete(componentName, projectId=<pid>)` — KHÔNG có moduleId |
|
|
35
|
+
| **Đọc source** | `component_get(projectId, moduleId, componentName)` | `component_get(projectId, componentName)` — bỏ moduleId |
|
|
36
|
+
| **Sync danh sách** | `component_list(projectId, moduleId)` | `component_list(projectId)` — bỏ moduleId |
|
|
37
|
+
| **File trong workspace** | `{moduleDir}/components/uc-foo.vue` | `{projectDir}/components/uc-foo.vue` |
|
|
38
|
+
|
|
39
|
+
**Quy tắc duy nhất:** `moduleId` có → module-scope · `moduleId` vắng, chỉ `projectId` → project-scope
|
|
40
|
+
|
|
41
|
+
**Tạo và cập nhật là HAI tool riêng — không có upsert ngầm.** Cả hai đọc file `.vue` **local** (file phải tồn tại sẵn trên đĩa) và tự suy `ComID` từ `_components.json`, không truyền tay:
|
|
42
|
+
|
|
43
|
+
- `component_new` — chỉ INSERT. Làm mới `_components.json` **từ server trước** rồi mới kiểm tra tên; trùng tên = **lỗi cứng**. (Cache cũ mà cứ INSERT thì server sinh **hai record cùng tên**, không sửa được từ MCP.) Sau khi tạo tự sync lại để lấy `ComID` thật.
|
|
44
|
+
- `component_update` — chỉ UPDATE. Không tra được `ComID` = **lỗi cứng**, không âm thầm tạo mới: hoặc component chưa từng được tạo (→ `component_new`), hoặc cache local cũ (→ `component_list` rồi thử lại).
|
|
45
|
+
|
|
46
|
+
**`component_delete` tự lookup ComID** từ `_components.json` local. Nếu danh sách lệch, chạy `component_list` trước.
|
|
47
|
+
|
|
48
|
+
**`module_publish_html` không bao gồm project-scope component** — nếu có thay đổi component global, phải gọi `component_new`/`component_update` riêng trước hoặc sau.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1a. Tra cứu component project-scope đã có — `{projectId}/COMPONENTS.md`
|
|
53
|
+
|
|
54
|
+
Trước khi thiết kế một component cấp project mới, **phải kiểm tra `{projectId}/COMPONENTS.md`** (nếu có) — file này liệt kê các `uc-*.vue` cấp project đã tồn tại, kèm tóm tắt mục đích và props chính. Dựng lại component đã có chỉ gây trùng lặp và khó bảo trì.
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
{FUI_MCP_WORKDIR}/{projectId}/COMPONENTS.md ← kiểm tra TRƯỚC khi tạo component project-scope mới
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Vòng tích luỹ:** sau khi tạo/cập nhật một component cấp project có ý định dùng lại ở nhiều module, nối thêm một mục vào `{projectId}/COMPONENTS.md`. Chưa có file thì tạo mới theo khuôn:
|
|
61
|
+
|
|
62
|
+
```markdown
|
|
63
|
+
# {projectId} — Danh sách component project-scope
|
|
64
|
+
|
|
65
|
+
## uc-ten-component
|
|
66
|
+
|
|
67
|
+
**Mục đích:** mô tả ngắn gọn component dùng để làm gì
|
|
68
|
+
**Props:** `propA` (kiểu, mặc định) · `propB` (kiểu, mặc định)
|
|
69
|
+
**Events:** `@eventName` — khi nào phát
|
|
70
|
+
**Ghi chú:** điều gì cần biết khi dùng (nếu có)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
> Khuôn này theo đúng `{projectId}/DESIGN.md` (vòng tích luỹ quy tắc thẩm mỹ — xem [design-modes.md](design-modes.md) §2-3). File `COMPONENTS.md` **không bao giờ tự xoá mục** — xoá khi đã dùng `component_delete` bỏ component đó khỏi server.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 2. Đặt tên và cấu trúc file
|
|
78
|
+
|
|
79
|
+
- **Prefix bắt buộc**: `uc-` (User Component) — phân biệt với `f-*` (FUI runtime) và `v-*` (Vuetify)
|
|
80
|
+
- **Format**: kebab-case — ví dụ `uc-trangthai-sukien.vue`, `uc-user-profile.vue`
|
|
81
|
+
- **Vị trí**: chỉ đặt trong `components/` ở root module hoặc root project, không tạo subfolder
|
|
82
|
+
- **Registry**: khai báo trong `components/_components.json` (chỉ cần `comName`, không tự đặt `comID`)
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 3. Đăng ký component — KHÔNG dùng `components: {}`, KHÔNG dùng `name:`
|
|
87
|
+
|
|
88
|
+
FUI tự động scan và đăng ký toàn bộ `uc-*.vue` ở cấp **global**. Tuyệt đối không khai báo `components: { ... }` bên trong một component — nó không hoạt động trong môi trường FUI.
|
|
89
|
+
|
|
90
|
+
Không khai báo thuộc tính `name:` trong `export default` — FUI tự xử lý tên component từ tên file khi tạo script, khai báo thêm là thừa và có thể gây xung đột:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
// ❌ SAI — không cần và có thể gây xung đột
|
|
94
|
+
export default {
|
|
95
|
+
name: 'uc-user-card',
|
|
96
|
+
props: { ... }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ✅ ĐÚNG — bỏ name, FUI tự resolve từ tên file
|
|
100
|
+
export default {
|
|
101
|
+
props: { ... }
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
// ❌ SAI — không dùng trong FUI
|
|
107
|
+
export default {
|
|
108
|
+
components: { 'uc-user-card': UcUserCard },
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ✅ ĐÚNG — dùng trực tiếp tag trong template
|
|
112
|
+
// <uc-user-card :data="data" />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 4. Props
|
|
118
|
+
|
|
119
|
+
- Khai báo trong `script`: camelCase (`userName`, `itemList`)
|
|
120
|
+
- Dùng trong `template`: kebab-case (`:user-name="userName"`)
|
|
121
|
+
- Default của Array/Object phải dùng `function()`, không dùng arrow function:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
// ❌ SAI
|
|
125
|
+
props: { items: { default: () => [] } }
|
|
126
|
+
|
|
127
|
+
// ✅ ĐÚNG
|
|
128
|
+
props: { items: { default: function() { return [] } } }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- Ưu tiên tên props trung lập và tái sử dụng được: `items`, `value`, `label`, `loading`, `readonly`, `disabled`, `options`, `config`
|
|
132
|
+
- Không đặt tên props gắn chặt với một màn hình cụ thể nếu component có thể dùng lại
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## 5. Emits
|
|
137
|
+
|
|
138
|
+
- Luôn emit lên parent thay vì mutate trực tiếp state của parent
|
|
139
|
+
- Các event chuẩn: `input`, `change`, `select`, `submit`, `remove`, `action`
|
|
140
|
+
- Dialog component luôn emit `('input', false)` để đóng — tương thích với `v-model` Vue 2
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 6. Template syntax — các ràng buộc bắt buộc
|
|
145
|
+
|
|
146
|
+
### Không dùng backtick trong `<template>`
|
|
147
|
+
|
|
148
|
+
Template strings (backtick) không hoạt động đúng trong Vue 2 template. Dùng string concatenation:
|
|
149
|
+
|
|
150
|
+
```html
|
|
151
|
+
<!-- ❌ SAI -->
|
|
152
|
+
:label="`Total (${items.length})`"
|
|
153
|
+
|
|
154
|
+
<!-- ✅ ĐÚNG -->
|
|
155
|
+
:label="'Total (' + items.length + ')'"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Không dùng `@` shorthand cho events trong JSON
|
|
159
|
+
|
|
160
|
+
Trong `module.json` luôn dùng `v-on:click`, không dùng `@click`. Trong `.vue` template thì `@click` vẫn hợp lệ.
|
|
161
|
+
|
|
162
|
+
### Vue 2 yêu cầu một root element duy nhất
|
|
163
|
+
|
|
164
|
+
```html
|
|
165
|
+
<!-- ✅ ĐÚNG -->
|
|
166
|
+
<template>
|
|
167
|
+
<div>
|
|
168
|
+
<!-- nội dung -->
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 7. Style & class — ƯU TIÊN class Vuetify có sẵn, KHÔNG tự chế
|
|
176
|
+
|
|
177
|
+
**Quy tắc: dùng class tiện ích Vuetify có sẵn cho MỌI nhu cầu spacing/flex/text/màu/kích thước. Chỉ định nghĩa class mới khi thật đặc biệt** (hiệu ứng/layout Vuetify không có). AI hay lạm dụng class tự chế → lãng phí, phá tính nhất quán, khó bảo trì.
|
|
178
|
+
|
|
179
|
+
### ❌ TUYỆT ĐỐI KHÔNG inject CSS bằng JavaScript
|
|
180
|
+
|
|
181
|
+
Không tạo `<style>` bằng JS rồi chèn vào `document.head` (kiểu `_injectStyle`) — đây là anti-pattern nặng (vừa lách luật no-`<style>`, vừa tạo hàng chục class tự chế):
|
|
182
|
+
|
|
183
|
+
```js
|
|
184
|
+
// ❌ SAI NGHIÊM TRỌNG — không bao giờ làm thế này trong .vue
|
|
185
|
+
methods: {
|
|
186
|
+
_injectStyle: function() {
|
|
187
|
+
var s = document.createElement('style')
|
|
188
|
+
s.textContent = ['.smg-root{...}', '.smg-card{...}', '.smg-badge{...}', ...].join('')
|
|
189
|
+
document.head.appendChild(s) // ❌ bơm CSS + hàng loạt class tự chế
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`<style scoped>` trong `.vue` được phép dùng cho CSS tùy chỉnh (hiếm khi cần) — nhưng `scoped` **không có tác dụng scoping thật**: FUI chỉ nối text `ComCSS` của mọi component trong module thành MỘT stylesheet chung (`ComponentCSSBuild()`), không compile SFC, không gắn `data-v-xxxx`. Vì vậy **bắt buộc tự scope thủ công**:
|
|
195
|
+
|
|
196
|
+
1. Thẻ gốc trong `<template>` mang một class định danh duy nhất theo tên component (kebab-case) — ví dụ component `uc-invoice-list` thì thẻ gốc `class="uc-invoice-list"`.
|
|
197
|
+
2. MỌI selector trong `<style scoped>` phải là descendant bắt đầu từ class đó — **không viết selector trần** (`.title`, `.header`...) vì sẽ leak sang mọi component khác cùng module.
|
|
198
|
+
|
|
199
|
+
```vue
|
|
200
|
+
<template>
|
|
201
|
+
<div class="uc-invoice-list">
|
|
202
|
+
<div class="uc-invoice-list__toolbar">
|
|
203
|
+
<span class="uc-invoice-list__title">{{ title }}</span>
|
|
204
|
+
</div>
|
|
205
|
+
<f-table class="uc-invoice-list__table" :items="rows"></f-table>
|
|
206
|
+
</div>
|
|
207
|
+
</template>
|
|
208
|
+
|
|
209
|
+
<style scoped>
|
|
210
|
+
.uc-invoice-list__toolbar { display: flex; align-items: center; justify-content: space-between; }
|
|
211
|
+
.uc-invoice-list__title { font-weight: 600; font-size: 14px; }
|
|
212
|
+
.uc-invoice-list__table { margin-top: 8px; }
|
|
213
|
+
</style>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
```vue
|
|
217
|
+
<!-- ❌ SAI — selector trần, đụng với component khác cùng module có class .title/.toolbar -->
|
|
218
|
+
<style scoped>
|
|
219
|
+
.title { font-weight: 600; }
|
|
220
|
+
.toolbar { display: flex; }
|
|
221
|
+
</style>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
CSS toàn-module (không gắn riêng component nào) → đặt trong `style.css` (publish qua `module_publish_css`), không phải `header.html`. `header.html` vẫn hợp lệ nhưng không còn là lựa chọn duy nhất.
|
|
225
|
+
|
|
226
|
+
### Trước khi đặt một class mới → tra bộ từ vựng đã duyệt
|
|
227
|
+
|
|
228
|
+
Bảng đầy đủ class/prop Vuetify được phép dùng (spacing, flex, text, màu, bo góc, elevation,
|
|
229
|
+
breakpoint, kích thước) + thứ tự ưu tiên + điều kiện được định nghĩa class riêng:
|
|
230
|
+
→ [controls-styling-vocabulary.md](controls-styling-vocabulary.md) §3.
|
|
231
|
+
|
|
232
|
+
Bộ từ vựng đó áp dụng y nguyên trong `<template>` của `.vue` — **khác biệt duy nhất**: trong `.vue`
|
|
233
|
+
không có `w`/`col` của grid FUI (đó là cú pháp của `module.json`, runtime FUI dịch ra class), nên
|
|
234
|
+
phải viết grid Vuetify trực tiếp.
|
|
235
|
+
|
|
236
|
+
**Cú pháp grid KHÁC NHAU giữa hai framework** — FUI V2 dùng Vuetify 1.5, FUI V3 dùng Vuetify 3
|
|
237
|
+
(canonical: [controls-patterns.md](controls-patterns.md) §Vuetify version):
|
|
238
|
+
|
|
239
|
+
| | V2 (Vuetify 1.5) | V3 (Vuetify 3) |
|
|
240
|
+
|---|---|---|
|
|
241
|
+
| Cách chia cột | `v-layout` > `v-flex xs8` | `v-row` > `v-col cols="8"` |
|
|
242
|
+
| `v-flex` | ✅ | ❌ **không tồn tại** — thẻ rơi xuống thành phần tử HTML trơ: không lỗi, không `[Vue warn]`, chỉ là **layout mất hẳn** |
|
|
243
|
+
| `v-layout` | ✅ hàng của grid | ⚠️ **có tồn tại nhưng nghĩa khác hẳn**: là wrapper bố cục ứng dụng (đi với `v-main`, như `fastproject-3.0.js` dùng), KHÔNG phải hàng của grid. Dùng nó thay `v-row` thì được một flex container không có gutter/breakpoint của grid |
|
|
244
|
+
| prop trần `shrink` | ✅ (prop của `v-flex`) | ❌ không có prop đó → rơi thành attribute DOM trơ. Dùng `class="shrink"` |
|
|
245
|
+
|
|
246
|
+
Template chuẩn ở mục 8 ngay dưới viết theo **V3** (`v-row`/`v-col`). Component viết cho project V2 thì
|
|
247
|
+
đổi sang `v-layout`/`v-flex` tương ứng.
|
|
248
|
+
|
|
249
|
+
Đây là lỗi hỏng-âm-thầm theo cả hai chiều: component dùng `v-flex` đưa sang project V3 sẽ **hiện đủ
|
|
250
|
+
nội dung nhưng sai bố cục**, và không tầng nào báo. Sửa component cũ thì kiểm bằng `component_preview`
|
|
251
|
+
ở **đúng framework của project** trước khi push.
|
|
252
|
+
|
|
253
|
+
CSS tùy chỉnh riêng component (nếu thật sự cần) đặt trong `<style scoped>` của chính `.vue` đó, tự
|
|
254
|
+
scope thủ công theo quy tắc ở mục 7 phía trên — không đặt trong `header.html` trừ khi CSS đó dùng
|
|
255
|
+
chung cho nhiều component/toàn module.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## 8. Dialog chuẩn trong Vue component (`uc-*.vue`) — TEMPLATE CHUẨN HOÁ
|
|
260
|
+
|
|
261
|
+
Dùng `v-dialog` + `v-card` với **quy ước spacing/size cố định** dưới đây cho MỌI dialog form. Chỉ dùng class tiện ích Vuetify — không tự chế class, không `style`.
|
|
262
|
+
|
|
263
|
+
```html
|
|
264
|
+
<v-dialog v-model="editDialog" max-width="600">
|
|
265
|
+
<v-card>
|
|
266
|
+
<!-- Tiêu đề: px-3 pt-2 pb-3, subtitle-1 font-weight-bold, nút X bên phải -->
|
|
267
|
+
<v-card-title class="d-flex align-center px-3 pt-2 pb-3 subtitle-1 font-weight-bold">
|
|
268
|
+
{{ editItem.RoundID ? 'Chỉnh sửa vòng thi' : 'Thêm vòng thi mới' }}
|
|
269
|
+
<v-spacer></v-spacer>
|
|
270
|
+
<v-btn icon @click="editDialog = false"><v-icon>mdi-close</v-icon></v-btn>
|
|
271
|
+
</v-card-title>
|
|
272
|
+
|
|
273
|
+
<!-- Nội dung: px-3 py-1; field dùng outlined dense hide-details, chia cột bằng v-row/v-col -->
|
|
274
|
+
<v-card-text class="px-3 py-1">
|
|
275
|
+
<v-row>
|
|
276
|
+
<v-col cols="8">
|
|
277
|
+
<v-text-field v-model="editItem.RoundName" label="Tên vòng thi"
|
|
278
|
+
hide-details outlined dense></v-text-field>
|
|
279
|
+
</v-col>
|
|
280
|
+
<v-col cols="4">
|
|
281
|
+
<v-text-field v-model.number="editItem.RoundOrder" label="Thứ tự" type="number"
|
|
282
|
+
hide-details outlined dense></v-text-field>
|
|
283
|
+
</v-col>
|
|
284
|
+
</v-row>
|
|
285
|
+
</v-card-text>
|
|
286
|
+
|
|
287
|
+
<!-- Hàng cuối: action căn phải, button rộng tự nhiên; action phụ text, action chính primary -->
|
|
288
|
+
<v-card-actions class="pa-3">
|
|
289
|
+
<v-spacer></v-spacer>
|
|
290
|
+
<v-btn text @click="resetForm">Đặt lại</v-btn>
|
|
291
|
+
<v-btn color="primary" elevation="0" :loading="saving" @click="saveRound">Lưu</v-btn>
|
|
292
|
+
</v-card-actions>
|
|
293
|
+
</v-card>
|
|
294
|
+
</v-dialog>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Quy ước bắt buộc (không đổi tuỳ tiện)
|
|
298
|
+
|
|
299
|
+
| Phần | Class/prop chuẩn | Ghi chú |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `v-dialog` | `max-width="600"` | 600 cho form thường; 400 nhỏ; 800–900 form rộng |
|
|
302
|
+
| `v-card-title` | `d-flex align-center px-3 pt-2 pb-3 subtitle-1 font-weight-bold` | Tiêu đề gọn + nút X (`v-btn icon` → `mdi-close`) bên phải qua `v-spacer` |
|
|
303
|
+
| `v-card-text` | `px-3 py-1` | Padding nội dung nhất quán |
|
|
304
|
+
| Field nhập | `outlined dense hide-details` | Gọn, cùng chiều cao; chia cột bằng `v-row` + `v-col cols="N"` |
|
|
305
|
+
| `v-card-actions` | Là **hàng cuối**; `pa-3` + `v-spacer` trước nhóm nút | Toàn bộ action căn phải |
|
|
306
|
+
| Chiều ngang nút | Không đặt `block`, `width`, class grow hay bọc trong cột giãn | Giữ chiều ngang tự nhiên theo nhãn |
|
|
307
|
+
| Nút chính | Tối đa một nút dùng `color="primary" elevation="0"` + `:loading="saving"` | Có nền để thể hiện ưu tiên; `:loading` khi đang gọi API |
|
|
308
|
+
| Nút phụ | `text`, không `color` nền | Dùng cho action ít quan trọng; giao diện nhã nhặn, tối giản |
|
|
309
|
+
|
|
310
|
+
- Tiêu đề động theo chế độ: `{{ editItem.ID ? 'Chỉnh sửa' : 'Thêm mới' }}`.
|
|
311
|
+
- Số nhập dùng `v-model.number` + `type="number"`.
|
|
312
|
+
- Đóng dialog: `@click="dialogVar = false"`; sau khi lưu thành công cũng set `false` trong callback.
|
|
313
|
+
- Icon giữ kích thước mặc định; không tự thêm `small`/`:small` cho `v-icon` hoặc nút icon.
|
|
314
|
+
|
|
315
|
+
### Biến thể
|
|
316
|
+
|
|
317
|
+
- **Toolbar header** (khi cần màu/icon nổi bật): thay `v-card-title` bằng `v-toolbar dense flat` + `v-toolbar-title`.
|
|
318
|
+
- **Fullscreen** (form lớn/nhiều vùng): `<v-dialog v-model="..." fullscreen>` + `v-toolbar` header, nội dung trong `v-container class="grid-list-md"`.
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
## 9. Thiết kế cho tái sử dụng
|
|
323
|
+
|
|
324
|
+
- Kiểm tra xem component có thể là: list, card, dialog body, filter panel, summary block, form section không
|
|
325
|
+
- Expose loading/empty/disabled/readonly qua props hoặc slots thay vì hardcode một workflow
|
|
326
|
+
- Giữ API calls, routing, permissions trong `module.json` hoặc parent — component chỉ lo presentation
|
|
327
|
+
- Khi module.json `controls` vượt ~200 dòng: extract UI sang Vue component, giữ module.json chỉ có data/API
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 10. Không nhúng `f-dialog` vào template component
|
|
332
|
+
|
|
333
|
+
`f-dialog` chỉ hoạt động khi khai báo trực tiếp trong `controls` JSON của module. Nếu cần dialog trong một `uc-*.vue`, dùng cấu trúc v-dialog ở mục 7 thay thế.
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 11. Giới hạn cú pháp trong `<script>` của component
|
|
338
|
+
|
|
339
|
+
### Không dùng `async / await / finally`
|
|
340
|
+
|
|
341
|
+
FUI parser không hỗ trợ các keyword này bên trong file `.vue`. Dùng callback thay thế:
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
// ❌ SAI
|
|
345
|
+
async mounted() {
|
|
346
|
+
const res = await fetch(url)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ✅ ĐÚNG
|
|
350
|
+
mounted() {
|
|
351
|
+
ajaxCALL(url, {}, function(res) { ... })
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Không dùng template literals (backtick) trong `<script>`
|
|
356
|
+
|
|
357
|
+
Backtick chỉ hoạt động trong `script.js` độc lập, không dùng được trong file `.vue`:
|
|
358
|
+
|
|
359
|
+
```js
|
|
360
|
+
// ❌ SAI — trong .vue <script>
|
|
361
|
+
var url = `/api/Student/${this.studentID}`
|
|
362
|
+
|
|
363
|
+
// ✅ ĐÚNG
|
|
364
|
+
var url = '/api/Student/' + this.studentID
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## 12. Tận dụng hàm có sẵn trong `defaultfunction.js` — KHÔNG viết lại
|
|
370
|
+
|
|
371
|
+
FUI đã cung cấp sẵn nhiều hàm global. **Trước khi tự viết logic (gọi API, xác nhận, thông báo, format, group, copy...) → kiểm tra [default-function.md](default-function.md) xem đã có chưa.** Chỉ tự viết khi hàm có sẵn không đáp ứng được. Viết lại thứ đã có = lãng phí + không nhất quán.
|
|
372
|
+
|
|
373
|
+
### Gọi API — dùng `ajaxCALL` (KHÔNG `fetch`/`axios`/`$.ajax` thủ công)
|
|
374
|
+
|
|
375
|
+
`fetch` bị lỗi CORS/credentials sang domain khác. `ajaxCALL` tự xử lý credentials, token (Bearer), và base domain (`v_Set.apiDomain`) — không cần ghép domain.
|
|
376
|
+
|
|
377
|
+
```js
|
|
378
|
+
// ❌ SAI
|
|
379
|
+
fetch(url, { method: 'POST', body: JSON.stringify(data) })
|
|
380
|
+
.then(r => r.json()).then(res => { ... })
|
|
381
|
+
|
|
382
|
+
// ✅ ĐÚNG — ajaxCALL(URL, DATA, successCb, errorCb, header)
|
|
383
|
+
ajaxCALL('spAPI_RoundList', { ContestID: this.contestID }, function(res) {
|
|
384
|
+
// res đã parse JSON (json_data tự parse)
|
|
385
|
+
}, function(err) {
|
|
386
|
+
// tuỳ chọn — mặc định ajaxCALL đã showMessage lỗi
|
|
387
|
+
})
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Xác nhận — dùng `confirm` (KHÔNG `window.confirm`)
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
confirm({
|
|
394
|
+
title: 'Xác nhận xoá',
|
|
395
|
+
message: 'Bạn có chắc muốn xoá vòng thi này?',
|
|
396
|
+
action: function() { ajaxCALL('spAPI_RoundDelete', { RoundID: id }, function(){ ... }); }
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Thông báo — dùng `showMessage` hoặc `Vue.$toast`
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
showMessage({ title: 'Đã lưu thành công' }); // hộp thoại
|
|
404
|
+
showMessage({ title: 'Lỗi', message: '...', type: 'red' });
|
|
405
|
+
Vue.$toast.success('Đã lưu', { position: 'top' }); // toast góc màn hình
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Các hàm hay dùng khác (đừng viết lại)
|
|
409
|
+
|
|
410
|
+
`groupBy` · `findInArray` · `jsonToExcel` (xuất Excel) · `printPDF` · `copyToClipboard` · `openWindow` · `pushRouter` · `rightTest` (kiểm tra quyền) · `generateID` · `capacityText` · `buildHeader` (tự sinh headers f-table). Chi tiết: [default-function.md](default-function.md).
|
|
411
|
+
|
|
412
|
+
> Thư viện toàn cục cũng có sẵn — dùng thay vì tự cài: `_` (Lodash: `_.groupBy`, `_.sumBy`, `_.cloneDeep`...), `moment` (ngày giờ), `numeral` (format số). Không thêm CDN cho các thư viện này.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 13. Kiểm chứng component — `component_preview` TRƯỚC khi ráp vào module
|
|
417
|
+
|
|
418
|
+
Một `uc-*.vue` là đơn vị đầy đủ (template + script + style) nhưng nếu chỉ nhìn thấy nó sau khi đã ráp vào module thì lỗi của component và lỗi lắp ráp trộn lẫn — mất thời gian khoanh vùng. `component_preview` render **đúng một component** trên một trang riêng, bằng file `.vue` **local** (chưa cần push), với dữ liệu mẫu bạn truyền thẳng trong tham số.
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
component_preview({
|
|
422
|
+
componentName: "uc-invoice-list",
|
|
423
|
+
projectId: "...", // chỉ projectId → project-scope
|
|
424
|
+
// moduleId: "..." // có moduleId → module-scope (suy luận y hệt component_new/component_update)
|
|
425
|
+
cases: [
|
|
426
|
+
{ name: "danh sách rỗng", props: { items: [] } },
|
|
427
|
+
{ name: "có dữ liệu", props: { items: [ {...}, {...} ], title: "Hoá đơn" } },
|
|
428
|
+
{ name: "click chọn dòng", props: { items: [...] },
|
|
429
|
+
scenario: [ { click: "Xem" }, { assert: "vueData.selected != null" } ] }
|
|
430
|
+
],
|
|
431
|
+
devices: ["desktop", "mobile"] // mặc định
|
|
432
|
+
})
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
- **Props**: mảng/object cứ truyền **giá trị thật** — tool tự đẩy vào `data[]` và bind `:prop`. Chuỗi thành attr tĩnh; key đã bắt đầu bằng `:`/`v-` giữ nguyên văn (dùng `v-on:`/`v-slot:`, **không** `@`/`#`).
|
|
436
|
+
- **`cases × devices` ≤ 8**. Mỗi lần render là một phiên browser thật.
|
|
437
|
+
- `slot` (nội dung `innerHTML`), `vueData`, `apiMocks`, `user` (test permission gate), `viewport` (ép chiều cao thấp để lộ component tự đo chiều cao) đều truyền được.
|
|
438
|
+
- `scenario` dùng **đúng grammar renderUI trên DOM thật**: `set`/`click`/`call`/`exe`/`assert`, chọn dòng bằng `nth` (không có `item`/`index`).
|
|
439
|
+
- **`style.css` của module KHÔNG được nạp** — đó chính là ý nghĩa "không gian riêng": component phải tự đứng được bằng `<style>` của chính nó + theme project.
|
|
440
|
+
- Trả về: ảnh chụp (tối đa 4 inline, còn lại ghi đường dẫn) + JS error + `[Vue warn]` + DOM audit. **Đọc ảnh rồi tự sửa** trước khi push.
|
|
441
|
+
|
|
442
|
+
### Component cấp project: BẮT BUỘC push trước khi render module dùng nó
|
|
443
|
+
|
|
444
|
+
`module_simulate({ renderUI: true })` dựng tầng project bằng bundle **tải từ server**. Bản local chưa push sẽ **không xuất hiện** — ảnh render là bản cũ trên server, tức ảnh nói dối. Vì vậy renderUI **chặn cứng** (không có cờ bỏ qua) khi component cấp project ở local lệch với bundle server.
|
|
445
|
+
|
|
446
|
+
```
|
|
447
|
+
sửa uc-*.vue cấp project
|
|
448
|
+
→ component_preview (kiểm chứng bản local, chưa push)
|
|
449
|
+
→ component_new (lần đầu) hoặc component_update (các lần sau) — push lên server
|
|
450
|
+
→ module_simulate({ renderUI: true }) ← chỉ chạy được sau khi đã push
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Nếu bị chặn mà **bản trên server mới hơn** (ai đó vừa sửa trên web IDE): chạy `project_sync` để lấy về, **đừng push đè** — sẽ mất bài của người khác.
|
|
454
|
+
|
|
455
|
+
Component **cấp module** không bị luật này: renderUI dựng tầng module từ file local nên sửa tới đâu render thấy tới đó.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# FUI Component Quick Reference
|
|
2
|
+
|
|
3
|
+
> File này sở hữu: **danh mục component FUI, bảng component cần import JS/CSS, quy trình thêm/bỏ import**. Chi tiết từng component xem file reference tương ứng ở cột phải.
|
|
4
|
+
|
|
5
|
+
Tra cứu nhanh toàn bộ component có sẵn trong FUI runtime. Ưu tiên dùng `f-*` trước — chỉ dùng `v-*` khi không có FUI equivalent.
|
|
6
|
+
|
|
7
|
+
| Component | Dùng khi nào | Reference |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `f-table` | **Hiển thị dữ liệu dạng datatable** — cả CRUD qua dialog form lẫn chỉ xem (readonly = không khai `:update-api`); auto-build headers khi không truyền `:headers`; hàng tổng qua `sum-format` | [component-table.md](component-table.md) |
|
|
10
|
+
| `f-dialog` | Modal dialog với form, watch, button | [components-dialog.md](components-dialog.md) |
|
|
11
|
+
| `f-window` | Dialog mở URL trong iframe | [components-dialog.md](components-dialog.md) |
|
|
12
|
+
| `f-button` | Nút bấm có action, hotkey, form validation | [components-input.md](components-input.md) |
|
|
13
|
+
| `f-date` | Input ngày có date picker + typed mask | [components-input.md](components-input.md) |
|
|
14
|
+
| `f-time` | Input giờ có time picker + typed mask | [components-input.md](components-input.md) |
|
|
15
|
+
| `f-time-counter` | Đếm ngược / đồng hồ | [components-input.md](components-input.md) |
|
|
16
|
+
| `f-search` | Autocomplete tìm kiếm qua API | [components-input.md](components-input.md) |
|
|
17
|
+
| `f-menu` | Dropdown button menu có icon/action/link | [components-input.md](components-input.md) |
|
|
18
|
+
| `f-radiobox` | Radio group từ array items | [components-input.md](components-input.md) |
|
|
19
|
+
| `f-file-upload` | Upload file (Plupload, progress dialog) | [components-input.md](components-input.md) |
|
|
20
|
+
| `f-image-update` | Upload + crop ảnh | [components-input.md](components-input.md) |
|
|
21
|
+
| `f-excel-reader` | Đọc file Excel/CSV → JSON (chọn sheet, bỏ N dòng đầu, tự đặt tên cột) | [components-input.md](components-input.md) |
|
|
22
|
+
| `f-qrcode` | Render mã QR | [components-input.md](components-input.md) |
|
|
23
|
+
| `f-qrcode-reader` | Đọc QR bằng camera | [components-input.md](components-input.md) |
|
|
24
|
+
| `f-editor` | Rich text editor (CKEditor 5) | [components-dialog.md](components-dialog.md) |
|
|
25
|
+
| `f-editor-dialog` | Rich text editor trong modal | [components-dialog.md](components-dialog.md) |
|
|
26
|
+
| `f-echart` | **Biểu đồ** — ECharts V5: bar, line, pie, scatter, gauge, radar, heatmap, sankey, tree, map, graph... | [components-echart.md](components-echart.md) · [echart-templates.md](echart-templates.md) |
|
|
27
|
+
| `f-sheet` | **Nhập liệu trực tiếp trên bảng kiểu Excel** — sửa ô tại chỗ, dán từ Excel, không qua dialog (dựa trên AG Grid) — **cần import** | [fsheet.md](fsheet.md) |
|
|
28
|
+
| `f-pdfmake` | Render PDF trong iframe | [components-display.md](components-display.md) · [pdfmake.md](pdfmake.md) |
|
|
29
|
+
| `f-label` | Chip/badge hiển thị tag, trạng thái | [components-display.md](components-display.md) |
|
|
30
|
+
| `f-box` | Hiển thị/chỉnh sửa key-value rows | [components-display.md](components-display.md) |
|
|
31
|
+
| `f-header` | Tiêu đề lớn (display-1, primary color) | [components-display.md](components-display.md) |
|
|
32
|
+
| `f-title` | Tiêu đề nhỏ (title, primary color) | [components-display.md](components-display.md) |
|
|
33
|
+
| `f-slider` | Carousel ảnh | [components-display.md](components-display.md) |
|
|
34
|
+
| `fp-profile` | Khối thông tin/hồ sơ user | [components-display.md](components-display.md) |
|
|
35
|
+
|
|
36
|
+
### Component trong ô bảng (`t-*`) — dùng trong `headers` của f-table
|
|
37
|
+
|
|
38
|
+
`t-html` · `t-label` · `t-num` · `t-time` · `t-boolean` · `t-check` (toggle) · `t-text` · `t-select` · `t-combobox` · `t-menu` (context menu) · `t-link` · `t-button` — chi tiết & ví dụ: [ui-table-cell-patterns.md](ui-table-cell-patterns.md).
|
|
39
|
+
|
|
40
|
+
> `header-bar` và `f-window` là component nội bộ do runtime tự dựng (menu header, cửa sổ `openWindow`) — không đặt trực tiếp trong `controls`.
|
|
41
|
+
|
|
42
|
+
## Components cần import JS/CSS
|
|
43
|
+
|
|
44
|
+
Một số component yêu cầu import thêm thư viện — **phải gọi `file_import_list` để kiểm tra và thêm/xóa import tương ứng khi thêm hoặc bỏ component**.
|
|
45
|
+
|
|
46
|
+
| Component | Import bắt buộc |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `f-pdfmake` | `/include/pdfmake/pdfmake.min.js` · `/include/pdfmake/print.min.js` · `/include/pdfmake/vfs_fonts.js` |
|
|
49
|
+
| `f-file-upload` | `/include/plupload-2.3.7/plupload.full.min.js` |
|
|
50
|
+
| `f-image-update` | `/include/vue-advanced-cropper/index.umd.js` · `/include/vue-advanced-cropper/style.css` |
|
|
51
|
+
| `f-qrcode` | `/include/qrcode/qrcode.min.js` |
|
|
52
|
+
| `f-qrcode-reader` | `/include/qrcode/qrcode-reader.min.js` |
|
|
53
|
+
| `f-echart` | `/include/chart/echarts.min.js` · `/include/chart/fechart.js` (**theo thứ tự này**) — nền tảng Apache ECharts |
|
|
54
|
+
| `f-sheet` | `/include/ag-grid/ag-grid-community.min.js` · `/include/ag-grid/fsheet.js` (**theo thứ tự này**) — nền tảng AG Grid |
|
|
55
|
+
| `f-editor` | `/include/ckeditor5/build/ckeditor.js` |
|
|
56
|
+
| `f-editor-dialog` | `/include/ckeditor5/build/ckeditor.js` |
|
|
57
|
+
|
|
58
|
+
> **Bundled sẵn, KHÔNG cần import thêm:** `v-*` (Vuetify) · `f-table` · `f-dialog` · `f-button` ·
|
|
59
|
+
> `f-date` · `f-time` · `f-search` · `f-menu` · `f-radiobox` · `f-box` · `f-label` · `f-header` ·
|
|
60
|
+
> `f-title` · `f-slider` · `f-excel-reader` — và mọi component không có tên trong bảng trên.
|
|
61
|
+
|
|
62
|
+
**Quy trình khi thêm component có import:**
|
|
63
|
+
1. `file_import_list` (projectId + moduleId) — kiểm tra import hiện có
|
|
64
|
+
2. Nếu chưa có → `file_import_new` để thêm từng file
|
|
65
|
+
3. Nếu bỏ component → kiểm tra import đó còn dùng ở chỗ nào khác không; nếu không → `file_import_delete`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quy tắc chọn nhanh
|
|
70
|
+
|
|
71
|
+
- `f-button` thay `v-btn` khi cần action FUI (CALL, hotkey, form validation)
|
|
72
|
+
- `f-date` / `f-time` thay `v-text-field` + manual date parsing
|
|
73
|
+
- `f-search` thay `v-autocomplete` khi cần search qua API
|
|
74
|
+
- `f-menu` thay `v-menu + v-btn` khi cần dropdown actions
|
|
75
|
+
- `f-table` thay `v-data-table` cho MỌI bảng — CRUD lẫn chỉ xem (readonly = không khai `:update-api`)
|
|
76
|
+
- `f-echart` cho mọi loại biểu đồ (bar, line, pie, gauge, scatter, radar, sankey, tree, map...)
|
|
77
|
+
- Dialog: `ctrl-update` của `f-table` cho CRUD inline; dialog riêng → dựng `v-dialog` thủ công theo anatomy chuẩn (`design_read("fui")` §4 + `ui-dialog-patterns.md`); `f-dialog` chỉ cho form rất nhỏ
|