docs: reorganize component guide structure and indexes

Update documentation rules for GUIDE.md files to keep higher-level
guides focused on constraints, conventions, and indexes. Add base and
section component guides to the LLM development index, clarify component
layering responsibilities, and fix architecture references from README to
GUIDE.md.docs: reorganize component guide structure and indexes

Update documentation rules for GUIDE.md files to keep higher-level
guides focused on constraints, conventions, and indexes. Add base and
section component guides to the LLM development index, clarify component
layering responsibilities, and fix architecture references from README to
GUIDE.md.
This commit is contained in:
skytek_xinliang
2026-05-20 17:06:09 +08:00
parent 4d66718b05
commit 8af82f5900
10 changed files with 296 additions and 74 deletions
+19 -11
View File
@@ -1,25 +1,29 @@
# Components Guide
`components` 放 Vue UI 元件。元件以 props 接收資料,以 emits 回報事件;不要直接處理 route、HTTP client、token/session 或全域流程
`src/components` 放 Vue UI 元件,包含 layout、page component、feature/domain component 與少量跨頁共用元件
## 分層
- `pages/`:完整頁面組裝,檔名使用 `Page` 前綴。
- `sections/`:頁面區塊容器,例如搜尋區、表格、dialog shell、panel。
- `items/`:單筆資料、欄位群組或原子級呈現。
- `layouts/`app shell layout。詳見 `src/components/layouts/GUIDE.md`
- `base/`:真正跨頁共用且不屬於特定 domain 的基礎元件。
- `login/`:登入頁專用 UI。
- `maint/`maintenance demo 舊有或領域型 UI 元件。
| 目錄 | 說明 | 指南 |
|------|------|------|
| `pages/` | 完整頁面組裝,檔名使用 `Page` 前綴 | — |
| `sections/` | 頁面區塊容器,例如搜尋區、表格、dialog shell、panel | `sections/GUIDE.md` |
| `items/` | 單筆資料、欄位群組或原子級呈現 | `items/GUIDE.md` |
| `layouts/` | App shell layout | `layouts/GUIDE.md` |
| `base/` | 真正跨頁共用且不屬於特定 domain 的基礎元件 | `base/GUIDE.md` |
| `login/` | 登入頁專用 UI | — |
| `maint/` | maintenance demo 舊有或領域型 UI 元件 | — |
## 規則
- 不假設元件全域註冊;使用時明確 import。
- route component 放在 `views`,不要放在 `components`
-假設 `src/components` 會自動全域註冊元件;需要使用元件時,依照目前 Vue SFC 慣例明確 import。
- 直接被 route 載入的檔案放在 `src/views`,不要放在 `src/components`
- 負責完整頁面主畫面組裝的元件使用 `Page` 前綴。
- 只服務單一功能或 domain 的元件,放在對應資料夾,不要放進 `base`
- layout 元件只處理 app shell 與框架 UI,不放頁面專屬業務流程。
- `pages` 可組合 sections/items,但不直接處理 API。
- `sections` 決定布局與區塊互動,不知道 route。
- `items` 不知道自己在表格、grid 或 dialog 中。
- 只服務單一 domain 的元件放在 domain/feature 目錄,不放進 `base`
## 命名
@@ -27,3 +31,7 @@
- Section component`SectionXxx.vue`
- Item component`ItemXxx.vue`
- Layout component:依 shell/區塊命名,例如 `MainLayout.vue`
## 資料流
component 以 props 接收資料,以 emit 回報使用者事件。需要跨頁共享的狀態交給 `src/stores`;可重用流程或較複雜 UI state 放到 `src/composables`
-25
View File
@@ -1,25 +0,0 @@
# Components
`src/components` 放 Vue 元件,包含 layout、page component、feature/domain component 與少量跨頁共用元件。
## 目前結構
- `PageLogin.vue``PageIndex.vue``PageMaint.vue`:頁面型元件,接收 view 組好的資料與事件,負責完整頁面主畫面組裝。
- `layouts/*`app shell 與 layout 子元件。`MainLayout.vue` 負責主框架,`PlainLayout.vue` 負責不套主框架的頁面。
- `layouts/main-layout/*``MainLayout.vue` 拆出的 drawer、app bar、breadcrumb、favorites 等骨架子元件。
- `login/*`:登入頁專用 UI 區塊,服務 `PageLogin.vue`
- `maint/*`maintenance 領域元件,服務 `views/maint/*`
- `maint/master-detail/*`master-detail 維護頁專用子元件。
- `base/*`:真正跨頁重用且不屬於特定 domain 的基礎元件。
## 使用規則
- 不要假設 `src/components` 會自動全域註冊元件;需要使用元件時,依照目前 Vue SFC 慣例明確 import。
- 直接被 route 載入的檔案放在 `src/views`,不要放在 `src/components`
- 負責完整頁面主畫面組裝的元件使用 `Page` 前綴。
- 只服務單一功能或 domain 的元件,放在對應資料夾,不要放進 `base`
- layout 元件只處理 app shell 與框架 UI,不放頁面專屬業務流程。
## 資料流
component 以 props 接收資料,以 emit 回報使用者事件。需要跨頁共享的狀態交給 `src/stores`;可重用流程或較複雜 UI state 放到 `src/composables`
+37
View File
@@ -0,0 +1,37 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
label?: string
items: any[]
labelCharCount?: number
prependMarginEnd?: number
}>()
const modelValue = defineModel<any>({ required: true })
const prependMinWidth = computed(() =>
props.labelCharCount != null ? `${props.labelCharCount * 0.785}rem` : undefined,
)
const marginEndStyle = computed(() => `${props.prependMarginEnd ?? 8}px`)
</script>
<template>
<v-select v-model="modelValue" variant="outlined" density="compact" hide-details :items="items">
<template v-if="label" #prepend>
<span
class="text-title-small"
:style="prependMinWidth ? { minWidth: prependMinWidth } : undefined"
>
{{ label }}
</span>
</template>
</v-select>
</template>
<style scoped>
:deep(.v-input__prepend) {
margin-inline-end: v-bind(marginEndStyle);
}
</style>
+43
View File
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
label?: string
labelCharCount?: number
prependMarginEnd?: number
readonly?: boolean
}>()
const modelValue = defineModel<string>({ required: true })
const prependMinWidth = computed(() =>
props.labelCharCount != null ? `${props.labelCharCount * 0.785}rem` : undefined
)
const marginEndStyle = computed(() => `${props.prependMarginEnd ?? 8}px`)
</script>
<template>
<v-text-field
v-model="modelValue"
variant="outlined"
density="compact"
hide-details
:readonly="readonly"
>
<template v-if="label" #prepend>
<span
class="text-title-small"
:style="prependMinWidth ? { minWidth: prependMinWidth } : undefined"
>
{{ label }}
</span>
</template>
</v-text-field>
</template>
<style scoped>
:deep(.v-input__prepend) {
margin-inline-end: v-bind(marginEndStyle);
}
</style>
+46
View File
@@ -0,0 +1,46 @@
# Base Components Guide
`src/components/base` 放真正跨頁共用且不屬於特定 domain 的基礎元件。
## 規則
- 只服務單一 domain 的元件不要放進 `base`
- 命名不使用 `Page`/`Section`/`Item` 前綴,直接以功能命名。
## BaseFormTextField
前置 label + `v-text-field`,預設 `variant="outlined"``density="compact"``hide-details`
| Prop | 型別 | 預設 | 說明 |
|------|------|------|------|
| `modelValue` | `string` | — | 雙向綁定字串值 |
| `label` | `string` | `undefined` | `#prepend``<span>` 的文字 |
| `labelCharCount` | `number` | `undefined` | 字數,用於計算 `min-width: 字數 × 0.785rem` |
| `prependMarginEnd` | `number` | `8` | `#prepend``margin-inline-end`px |
| `readonly` | `boolean` | `undefined` | 是否唯讀 |
```vue
<BaseFormTextField
v-model="form.cellPhone"
label="手機"
class="ml-2"
:label-char-count="4"
:prepend-margin-end="16"
/>
```
## BaseFormSelect
前置 label + `v-select`,預設 `variant="outlined"``density="compact"``hide-details`
| Prop | 型別 | 預設 | 說明 |
|------|------|------|------|
| `modelValue` | `any` | — | 雙向綁定值 |
| `items` | `any[]` | — | `v-select``items` |
| `label` | `string` | `undefined` | `#prepend``<span>` 的文字 |
| `labelCharCount` | `number` | `undefined` | 字數,用於計算 `min-width` |
| `prependMarginEnd` | `number` | `8` | `#prepend``margin-inline-end`px |
```vue
<BaseFormSelect v-model="form.status" label="狀態" :items="statusOptions" class="ml-2" />
```
+84
View File
@@ -0,0 +1,84 @@
# Section Components Guide
`src/components/sections` 放頁面區塊容器,例如搜尋區、表格、dialog shell、panel。
## 規則
- 決定布局與區塊互動,不知道 route。
- 檔名使用 `Section` 前綴。
## SectionFormPage
表單申請/填寫頁面通用外殼。最外層為 `v-form`,內含標題卡片、表單欄位區、子區段插槽、配合事項與動作按鈕列。
### 使用時機
- 頁面包含**送出/存檔按鈕**`type="submit"`
- 需要**表單驗證**與整體 `v-form` 包覆
- 具有**標題卡片**、**配合事項/注意事項區**、**動作按鈕列**的固定結構
- 例如:申請單、借用單、報名表、維護單等填寫頁面
不適用情境:純粹列表/查詢頁面(無送出按鈕)、結構差異過大的頁面。
### Props
| Prop | 型別 | 預設 | 說明 |
|------|------|------|------|
| `title` | `string` | — | 頁面標題 |
| `loading` | `boolean` | `undefined` | 是否顯示 loading |
| `error` | `string` | `undefined` | 錯誤訊息 |
| `message` | `string` | `undefined` | 成功訊息 |
| `submitLabel` | `string` | `'存檔'` | 送出按鈕文字 |
| `resetLabel` | `string` | `'清除'` | 清除按鈕文字 |
| `backLabel` | `string` | `'返回'` | 返回按鈕文字 |
### Slots
| Slot | 用途 |
|------|------|
| `#fields` | 表單欄位區,用 `v-row`/`v-col` 配置 |
| `#sections` | 額外子區段卡片(明細、表格等) |
| `#notices` | 配合事項/注意事項清單 |
### Emits
| Emit | 說明 |
|------|------|
| `@submit` | 點擊存檔時觸發 |
| `@reset` | 點擊清除時觸發 |
| `@back` | 點擊返回時觸發 |
### 範例
```vue
<SectionFormPage
title="設備借用申請"
:loading="loading"
:error="error"
:message="message"
@submit="save"
@reset="reset"
@back="router.push('/venue/apply-choose')"
>
<template #fields>
<v-row density="compact">
<v-col cols="12" md="4">
<BaseFormTextField v-model="form.cellPhone" label="手機" class="ml-2" />
</v-col>
</v-row>
</template>
<template #sections>
<v-card>
<v-card-title class="text-title-medium font-weight-bold">設備明細</v-card-title>
<!-- 明細表格 -->
</v-card>
</template>
<template #notices>
<v-list class="bg-yellow-lighten-5">
<v-list-item>借用設備時請愛惜公物</v-list-item>
</v-list>
</template>
</SectionFormPage>
```
@@ -0,0 +1,58 @@
<script setup lang="ts">
interface Props {
title: string
backLabel?: string
error?: string
loading?: boolean
message?: string
resetLabel?: string
submitLabel?: string
}
withDefaults(defineProps<Props>(), {
backLabel: '返回',
resetLabel: '清除',
submitLabel: '存檔',
})
const emit = defineEmits<{
back: []
reset: []
submit: []
}>()
</script>
<template>
<v-form @submit.prevent="emit('submit')">
<v-container fluid class="pt-2 px-1">
<v-card>
<v-card-title class="bg-primary text-title-large text-center py-2">
{{ title }}
</v-card-title>
<v-card-text class="pa-4">
<v-alert v-if="error" class="mb-4" type="error" variant="tonal">{{ error }}</v-alert>
<v-alert v-if="message" class="mb-4" type="success" variant="tonal">
{{ message }}
</v-alert>
<slot name="fields" />
</v-card-text>
</v-card>
<slot name="sections" />
<v-card>
<v-card-title class="text-title-medium font-weight-bold">配合事項</v-card-title>
<v-card-text>
<slot name="notices" />
</v-card-text>
<v-row justify="center" class="pa-4 ga-2">
<v-btn type="submit" variant="elevated" color="primary" :loading="loading">
{{ submitLabel }}
</v-btn>
<v-btn type="button" variant="tonal" @click="emit('reset')">{{ resetLabel }}</v-btn>
<v-btn type="button" variant="text" @click="emit('back')">{{ backLabel }}</v-btn>
</v-row>
</v-card>
</v-container>
</v-form>
</template>