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:
@@ -25,6 +25,12 @@
|
||||
- ❌ `useStudentCrudCommands.ts` → ✅ `useCrudCommands.ts`
|
||||
- ✅ `models/student.ts`, `stores/students.ts` — domain layer, specific names are correct
|
||||
|
||||
## GUIDE.md 寫作規則
|
||||
|
||||
- `src/**/GUIDE.md` 只保留該層/目錄的**約束、慣例與索引**,不要塞入詳細 API 文件。
|
||||
- 當新增 pattern、目錄或慣例影響層邊界時,建立或更新對應的 `src/**/GUIDE.md`,並確保 `docs/llm-development-guide.md` 將其列入索引。
|
||||
- 元件的 Props/Slots/Emits 詳細說明放在各子目錄的 `GUIDE.md`(如 `src/components/base/GUIDE.md`、`src/components/sections/GUIDE.md`),不要放在上層 `src/components/GUIDE.md`。
|
||||
|
||||
## Stack
|
||||
- Framework: Vue 3 + Vite
|
||||
- UI Library: Vuetify
|
||||
|
||||
@@ -443,4 +443,4 @@ views/xxx.vue
|
||||
|
||||
---
|
||||
|
||||
*本文件取代 `docs/frontend-layering.md` 與 `src/components/README.md` 成為新增功能與重構的最高準則。既有檔案可保留作為歷史參考,但後續開發以本文為準。*
|
||||
*本文件取代 `docs/frontend-layering.md` 與 `src/components/GUIDE.md` 成為新增功能與重構的最高準則。既有檔案可保留作為歷史參考,但後續開發以本文為準。*
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
| route view 與薄 view 規則 | `src/views/GUIDE.md` |
|
||||
| maintenance demo view | `src/views/maint/GUIDE.md` |
|
||||
| Vue component 分層 | `src/components/GUIDE.md` |
|
||||
| base 元件 | `src/components/base/GUIDE.md` |
|
||||
| section 元件 | `src/components/sections/GUIDE.md` |
|
||||
| layout 邊界 | `src/components/layouts/GUIDE.md` |
|
||||
| page driver、command、layout composable | `src/composables/GUIDE.md` |
|
||||
| route 與 guard | `src/router/GUIDE.md` |
|
||||
@@ -64,43 +66,6 @@
|
||||
- 錯誤頁:讀 `src/views/GUIDE.md`(ErrorShell 模式)與 `src/router/GUIDE.md`(錯誤頁路由慣例)。
|
||||
- 語系文案:讀 `src/language/GUIDE.md`。
|
||||
|
||||
## 環境變數(.env 檔案)
|
||||
|
||||
Vite 會依 mode 自動載入對應層級的 `.env` 檔案,優先順序由高到低:
|
||||
|
||||
```
|
||||
.env.[mode].local ← 本機覆蓋,不提交(最高優先)
|
||||
.env.[mode] ← mode 專用
|
||||
.env.local ← 本機覆蓋,不提交
|
||||
.env ← 所有 mode 通用
|
||||
```
|
||||
|
||||
專案實際使用與版本控制規則:
|
||||
|
||||
| 檔案 | 用途 | Git |
|
||||
|------|------|-----|
|
||||
| `.env.example` | 範本檔,列出所有可用變數與說明 | 提交 |
|
||||
| `.env` | 所有 Vite 指令皆載入(serve / build) | 忽略 |
|
||||
| `.env.development` | `vite serve`(mode=development)載入 | 忽略 |
|
||||
| `.env.development.local` | 本機開發機密值(帳號密碼等) | 忽略 |
|
||||
| `.env.production` | `vite build`(mode=production)載入 | 忽略 |
|
||||
|
||||
**新增環境變數時應遵守的順序:**
|
||||
|
||||
1. 在 `.env` 或 `.env.development` 中新增變數並給定預設值
|
||||
2. 在 `.env.example` 中加入該變數的說明與格式,供其他開發者參考
|
||||
3. 若變數包含機密資訊(帳號、密碼、token),僅放在 `.env.development.local`,不要寫入 `.env.example` 的預設值
|
||||
|
||||
**現有變數說明:**
|
||||
|
||||
| 變數 | 適用檔案 | 說明 |
|
||||
|------|----------|------|
|
||||
| `VITE_PROXY_TARGET` | `.env`, `.env.development`, `.env.development.local` | dev server proxy 目標後端 URL |
|
||||
| `VITE_API_BASE_URL` | `.env`, `.env.development`, `.env.production` | API 請求 base URL,dev 用相對路徑走 proxy,prod 用完整 URL |
|
||||
| `VITE_SKIP_LOGIN` | `.env.development` | 登入示範開關,`true` 時略過登入流程 |
|
||||
| `VITE_DEV_DEFAULT_USER_ID` | `.env.development.local` | 本機開發示範帳號 |
|
||||
| `VITE_DEV_DEFAULT_PASSWORD` | `.env.development.local` | 本機開發示範密碼 |
|
||||
|
||||
## 修改前檢查
|
||||
|
||||
- 是否碰到 template core。
|
||||
|
||||
+19
-11
@@ -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`。
|
||||
|
||||
@@ -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`。
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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" />
|
||||
```
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user