ch3chi-commons-vue
v1.19.0
Published
這是一個 Vue 3 公用元件/函式庫,提供常用的 UI 元件、API 服務、權限管理、表單模型與工具函式,基於 TypeScript 與 Bootstrap 5。
Readme
Commons Vue Library
這是一個 Vue 3 公用元件/函式庫,提供常用的 UI 元件、API 服務、權限管理、表單模型與工具函式,基於 TypeScript 與 Bootstrap 5。
目錄
- 功能概述
- 安裝與相依套件
- 快速開始:應用初始化
- API 與授權
- Model 與工具
- BaseFormDataModel
- BaseListViewModel
- QueryParameter / QueryPage
- BSFieldStyleConfig
- CFileDataModel / FileUploadProgress
- CBSModalViewModel
- SessionUser / TokenUser / AccessToken
- LoginDataModel / PasswordDataModel / EmailReceiverDataModel
- BaseDictionary
- ShowMessageDataModel
- FormOptions(COptionItem 與通用選項)
- CToolUtils 工具函式
- Store(Pinia)
- Vue 指令
- UI 元件
- 表格元件 CTable
- Form 欄位元件
- 進階文件索引
- 開發與建置
- 授權
功能概述
- API 服務:封裝 Axios,提供統一的 API 呼叫介面,支援 Bearer Token 自動刷新、上下載進度、檔案下載等。
- 權限管理:角色權限映射、選單權限過濾、
v-permission指令與<HasPermission>元件。 - UI 元件:表格、表單欄位、Modal Alert、Toast、全域 Spinner、圖片等 Vue 3 元件。
- 指令:權限控制、日期格式化、Bootstrap Tooltip/Dropdown/Modal、Cloudflare Turnstile、表單錯誤樣式等。
- 模型與工具:表單資料模型(含 vee-validate + yup 整合)、查詢參數模型、字典模型、工具函式。
安裝與相依套件
npm install ch3chi-commons-vue此函式庫採用 peerDependency 設計,請於 host 專案安裝以下套件:
vue^3.5pinia^3.0.4(自 1.15.0 起改為 peerDependency,host 需自行 install 並app.use(pinia))bootstrap^5.3axios>= 1.13vee-validate>= 4.15yup>= 1.7dayjs>= 1.11lodash>= 4.17uuid>= 11.1mime-types>= 3.0flatpickr>= 4.6(若使用CDateFormField/CDateRangeFormField)@fortawesome/fontawesome-svg-core、@fortawesome/free-solid-svg-icons、@fortawesome/vue-fontawesome(Font Awesome icons)
其他選用:
pinia-plugin-persistedstate:當啟用 SessionStore / Dictionary Store 的persist設定時使用。- 額外見
package.json的peerDependencies。
樣式檔需另外載入:
import 'ch3chi-commons-vue/style.css';快速開始:應用初始化
下面是建議的應用初始化順序,將 commons-vue 提供的能力一次接齊:
// main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
import {
ApiService,
AuthorizationService,
BSFieldStyleConfig,
CFormFieldErrorOnYupDirective,
CFormFieldErrorStyleDirective,
CTableConfig,
PermissionDirective,
QueryParameter,
VueSessionStoreInstaller,
useUserSessionStore,
vdCBSDropdown,
vdCBSModal,
vdDateFormatter,
vdTooltip,
} from 'ch3chi-commons-vue';
import 'ch3chi-commons-vue/style.css';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(router);
// 1) ApiService:註冊 baseUrl、context path、端點以及顯示載入動畫的勾子
ApiService.configure({
baseUrl: import.meta.env.VITE_API_BASE_URL,
contextPath: '/api',
createEndpointOptions: {
isAuthenticated: true,
security: ['bearerAuth'],
},
endpoints: {
me: { path: '/me', method: 'GET' },
'Resources-Create': { path: '/resources', method: 'POST' },
'Resources-Delete': { path: '/resources/{uid}', method: 'DELETE' },
},
refreshTokenFunc: async () => useUserSessionStore().currentToken!, // 視專案實際刷新邏輯而定
});
// 2) AuthorizationService:定義角色 ↔ 權限 ↔ 選單
AuthorizationService.configure({
rolePermissionMap: {
admin: ['user:READ', 'user:UPDATE'],
},
menuDefineMap: {
default: [{ id: 'm1', name: '使用者管理', path: '/users', permission: 'user:READ' }],
},
});
// 3) UserSessionStore + 全域權限元件/指令
const sessionStore = useUserSessionStore();
app.use(VueSessionStoreInstaller, { sessionStore });
// 4) 註冊全域指令
app.directive('tooltip', vdTooltip);
app.directive('cbs-dropdown', vdCBSDropdown);
app.directive('cbs-modal', vdCBSModal);
app.directive('date-formatter', vdDateFormatter);
app.directive('form-invalid', CFormFieldErrorStyleDirective);
app.directive('form-invalid-yup', CFormFieldErrorOnYupDirective);
app.directive('permission', PermissionDirective);
// 5) 全域樣式預設值(可選)
BSFieldStyleConfig.merge({
requiredLabelText: '*',
errorClass: 'invalid-feedback d-block mt-1',
});
CTableConfig.setPaginationStyle({ activeBackgroundColor: '#E4B445' });
// 6) 全域 toQueryStringParam 版本切換(若後端仍使用 limit/offset)
// QueryParameter.queryStringVersion = 'v1';
app.mount('#app');⚠️ Pinia 必須由 host 端 install 並
app.use(pinia),commons-vue 內部 store(useViewStore、useUserSessionStore、useQueryFormDataStore、backupFormDataStore)會自動跟 host 共用同一份 pinia。
App.vue 中再掛載全域 Modal / Toast / Spinner(與 useViewStore 串接):
<template>
<CAlert ref="mainBSModal" />
<CBSToast ref="toastView" :delay="5000" />
<CGlobalSpinner ref="globalSpinner" />
<RouterView />
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { CAlert, CBSToast, CGlobalSpinner, useViewStore } from 'ch3chi-commons-vue';
const mainBSModal = ref();
const toastView = ref();
const globalSpinner = ref();
const viewStore = useViewStore();
onMounted(() => {
viewStore.mainBSModal = mainBSModal.value;
viewStore.toastView = toastView.value;
viewStore.globalSpinner = globalSpinner.value;
});
</script>之後就能在任何地方使用 viewStore.showModalConfirm({...}) 或 viewStore.addToast({...})。
API 與授權
ApiService
ApiService 是封裝後的 Axios 入口,支援端點註冊、Bearer Token 自動刷新、檔案下載、上下載進度回呼。
主要 API:
| 方法 | 說明 |
| --- | --- |
| ApiService.configure(config) | 初始化 base URL、攔截器、預設端點、token 刷新與認證失敗回呼。 |
| ApiService.addEndpoints(key, endpoint) | 動態新增單一端點(已存在則略過)。 |
| ApiService.call(request) | 呼叫 API,回傳 Promise<ApiResponse>。 |
| ApiService.download(request) | 以瀏覽器原生方式下載檔案。 |
| ApiService.addHeader(key, value) / removeHeader(key) | 操作全域標頭。 |
| ApiService.customHeaderProvider(provider) | 為每次呼叫動態組標頭(例如語系、device id)。 |
| ApiService.accessToken / accessTokenProvider | 設定靜態 token 或非同步取得方式。 |
| ApiService.refreshToken() | 手動觸發 token 刷新。 |
ApiRequest 重要欄位:
| 欄位 | 型別 | 說明 |
| --- | --- | --- |
| endpointKey | string | 端點識別字(configure({ endpoints }) 內定義)。 |
| pathParam | Record<string, any> | 用來替換 path 上的 {xxx} placeholder。 |
| queryParam | Record<string, any> | query string 參數。 |
| postBody | Record<string, any> \| FormData | request body;Date 會自動序列化成 ISO 字串。 |
| headers | Record<string, string> | 額外標頭(不影響全域)。 |
| isDownloadMode / downloadFileName | boolean / string | 觸發下載;檔名可從 Content-Disposition 取得。 |
| onUploadProgress | (e: AxiosProgressEvent) => void | 上傳進度(FormData / 大檔上傳常用)。 |
| onDownloadProgress | (e: AxiosProgressEvent) => void | 下載進度(自 1.15.0 起支援)。 |
| axiosConfig | AxiosRequestConfig | 額外覆寫 Axios 設定。 |
| noSpinner | boolean | 不顯示全域載入動畫。 |
ApiResponse 提供 isOk()、httpStatus、data、paging、blobData、headers、nativeError 等欄位。錯誤時可以包成 ApiRejectError 並 .notFound() 標記類型。
範例:
import { ApiService, ApiRequest } from 'ch3chi-commons-vue';
const req = new ApiRequest({
endpointKey: 'getUser',
pathParam: { id: 123 },
});
const resp = await ApiService.call(req);
if (resp.isOk()) {
console.log('使用者:', resp.data);
}
// 帶下載進度的下載
await ApiService.download(
new ApiRequest({
endpointKey: 'exportUser',
queryParam: { id: 123 },
downloadFileName: 'user_123.xlsx',
onDownloadProgress: (e) => {
if (e.total) console.log(`已下載 ${Math.round((e.loaded / e.total) * 100)}%`);
},
}),
);AuthorizationService
集中管理「角色 → 權限」與「角色 → 選單結構」。
import { AuthorizationService } from 'ch3chi-commons-vue';
AuthorizationService.configure({
rolePermissionMap: {
admin: ['user:READ', 'user:UPDATE', 'menu:READ'],
user: ['user:READ', 'menu:READ'],
},
menuDefineMap: {
default: [
{ id: 'm1', name: '使用者管理', path: '/users', permission: 'user:READ' },
{
id: 'm2',
name: '系統設定',
path: '/settings',
permission: ['settings:READ', 'settings:UPDATE'],
children: [{ id: 'm2-1', name: '基本參數', path: '/settings/base', permission: 'settings:READ' }],
},
],
},
});
const menu = AuthorizationService.provideMenuByPermission({
roleCode: 'user',
permissions: ['user:READ', 'menu:READ'],
});
const hasWrite = AuthorizationService.hasPermissionByRole(['user'], 'user:UPDATE');回傳的 CMenuItem 已具備 hasChildren()、checkCurrentPath()、useCollapseAttribute()、useClassNameForNavLink() 等可直接在 sidebar 模板使用的輔助方法。
PermissionDescriptor / PermissionAction
權限字串使用 module:ACTION 格式(例如 user:READ),由 PermissionDescriptor 解析並提供「跨 action 的繼承關係」(例如有 CREATE、UPDATE 自動視同擁有 READ):
import { PermissionAction, PermissionDescriptor } from 'ch3chi-commons-vue';
const d = new PermissionDescriptor('user:CREATE');
d.module; // 'user'
d.action; // PermissionAction.CREATE
d.supportedActions; // [CREATE, READ]
d.checkPermission(['user:CREATE']); // true
d.checkModule(['user:READ']); // truePermissionAction 提供常用列舉值:SIGN_IN、SIGN_OUT、FORGOT_PASSWORD、RESET_PASSWORD、CHANGE_PASSWORD、CREATE、SEARCH、READ、UPDATE、DELETE、EXPORT。
Model 與工具
BaseFormDataModel
繼承後即整合 vee-validate + yup,提供:
- 由 yup schema 自動產生
formFieldMap,欄位元件透過inject('c-formViewModel', model)取得。 - 自動偵測必填、
maxLength、巢狀欄位(address.city)等屬性。 - 雙向同步:表單欄位值改變會 mirror 回模型屬性。
最常用的方法:
class UserForm extends BaseFormDataModel {
account: string = '';
password: string = '';
dataFieldNameList() {
return ['account', 'password'];
}
initFormSchema() {
return yup.object({
account: yup.string().required('帳號為必填'),
password: yup.string().min(8).required('密碼為必填'),
});
}
}
const model = new UserForm();
model.initForm(); // 建立 formContext / formFieldMap
model.loadFormData({ account: 'a' }); // 載入既有資料
const { valid, errors } = await model.validateForm();
const payload = model.toPayload(); // 給 API 用
model.fieldIsRequired('password'); // true
model.fieldMaxLength('account'); // null / number
model.setFieldValue('account', 'x');CheckableDataModel 在 BaseFormDataModel 之上加了 checked: boolean 與 toggleChecked(),搭配 CTable 的 Checkbox 欄位很方便。
FormDataModelMapper 可把外部 plain object 自動 wrap 成具備表單能力的 model 實例並雙向同步,特別適合「動態欄位 array」的場景。
BaseListViewModel
抽象列表頁 view model,整合:
- 查詢參數(
QueryParameter系列) - 排序、分頁事件
- 自動呼叫
ApiService取資料、塞dataList - 自動計算
__rowNumber(行號)與__isLastAndLastPage(刪掉最後一筆時自動往前翻頁) - 透過
useQueryFormDataStore把查詢條件存進 Pinia / localStorage
class UserListVM extends BaseListViewModel<QueryParameter, UserModel> {
saveOnQueryFormDataStore = true;
useTableColumnArray() {
return [
new CTableColumn({ type: CTableColumnType.RowNumber, text: '#', width: '60px' }),
new CTableColumn({ type: CTableColumnType.Text, dataName: 'account', text: '帳號',
sortConfig: { sortable: true, key: 'account' } }),
new CTableColumn({
type: CTableColumnType.Action, text: '操作', dataAlign: 'right',
actionList: [{ actionType: CTableColumnActionType.Edit }],
}),
];
}
queryListApiEndpointMeta() {
return {
key: 'getUsers',
dataParser: (raw) => new UserModel().load(raw),
};
}
toCreateUrl() { return '/users/new'; }
toEditUrl(row: UserModel) { return `/users/${row.uid}`; }
}提供的常用方法:callOnMounted()、doSearch()、doClear()、onSortChange()、onPageChange(page)、onColumnActionEdit(rowMeta)、storeQueryDefaultValues(values)、resetToDefaultValues()、showConfirm(...)。
CheckableListViewModel 額外提供 findCheckedData()、resetCheckedData()。
QueryParameter / QueryPage
QueryParameter 封裝清單頁的關鍵字、排序、分頁,並提供轉換為 API payload/query string 的工具。
import { QueryParameter, QueryPage, QueryParamTool } from 'ch3chi-commons-vue';
const queryParam = new QueryParameter({
keyword: 'apple',
page: new QueryPage({ pageIndex: 0, pageSize: 20 }),
sort: [QueryParamTool.defSort('createdAt', 'DESC')],
});
queryParam.toQueryStringParam(); // 預設 v2:{ size: 20, page: 0 }
queryParam.toPayload(); // 排除 page / sort 後的 payloadQueryPage 提供完整分頁狀態:pageIndex、pageSize、totalCount(自動算 totalPage、offset)、hasPreviousPage()、hasNextPage()、previous()、next()、pageRange()(產生 CTable 用的頁碼陣列)。
DateRangeParam 是 QueryParameter 的子類,多了 startsAt、endsAt 欄位(clear() 會一併清空)。
分頁參數版本(v1 / v2)
toQueryStringParam() 支援兩種輸出格式,透過全域靜態屬性 QueryParameter.queryStringVersion 切換:
| 版本 | 輸出格式 | 適用情境 |
| --- | --- | --- |
| 'v2'(預設) | { size, page } | 新版 page-based 分頁 API,page index 從 0 開始 |
| 'v1' | { limit, offset } | 舊版 offset-based 分頁 API |
QueryParameter.queryStringVersion = 'v1';BSFieldStyleConfig
集中管理 Form 欄位的 Bootstrap class 名稱與 helper icon 等。所有 form 欄位元件都可透過 styleConfig prop 蓋掉預設值。
優先順序:prop styleConfig > 全域 BSFieldStyleConfig.instance > 元件預設值。
import { BSFieldStyleConfig } from 'ch3chi-commons-vue';
BSFieldStyleConfig.merge({
containerClass: 'form-group mb-3',
requiredLabelText: ' (必填) ',
errorClass: 'text-danger small mt-1',
selectPlaceholder: '— 請選擇 —',
});可調整的 key 涵蓋:containerClass、labelClass、inputClass、wrapperClass、errorClass、requiredLabelClass、requiredLabelText、plainTextClass、selectClass、textareaClass、textareaRows、checkboxClass、radioClass、fileClass、calendarIconClass、calendarClearIconClass、changePasswordButtonClass、changePasswordButtonIcon 等。完整清單見 IBSFieldStyleConfig。
CFileDataModel / FileUploadProgress
CFileDataModel 描述已上傳檔案,並在 init() 中根據副檔名自動套用 icon / badge 樣式(圖片、PDF、Word、Excel、影片各自有對應 Font Awesome icon)。
CPhotoDataModel 是 CFileDataModel 的子類,多了 width、height、loaded。
FileUploadProgress 管理上傳狀態:status: 'ready' | 'uploading' | 'completed' | 'failed'、progress: 0~100。常用方法:start()、complete()、fail(message)、readyToUpload()、isShowProgress()、isDone()。
CFilePickerFormField 內部已整合此模型。
CBSModalViewModel
封裝 Bootstrap 5 Modal 的 view-model,搭配 v-cbs-modal 指令。
import { CBSModalViewModel, ICBSModalViewType } from 'ch3chi-commons-vue';
const modal = new CBSModalViewModel({
type: ICBSModalViewType.Form,
title: '編輯資料',
onOpen: () => console.log('opened'),
onClose: () => model.resetFormData(),
config: { backdrop: 'static', keyboard: false },
});
modal.show();
modal.hide();<div class="modal fade" v-cbs-modal="modal" :id="modal.modalId" :aria-labelledby="modal.labelId">
...
</div>支援的 type:Info、Form、Table(會給定不同預設標題)。
SessionUser / TokenUser / AccessToken
SessionUser:基本使用者欄位(userUid、account、name、email、roleCode、permissions等)+load()/merge()/toJSON()/loadJSON()。TokenUserextendsSessionUser,包含tokens: AccessToken與profile。AccessToken:tokenType、accessToken、accessTokenExpiresAt、refreshToken、refreshTokenExpiresAt、sessionUid;提供isExpired()、isRefreshTokenExpired()與get bearerToken()(產生"Bearer xxx"字串)。
LoginDataModel / PasswordDataModel / EmailReceiverDataModel
開箱即用的 BaseFormDataModel 子類:
LoginDataModel:account、password、turnstileToken、enableCFTurnstile(搭配v-cf-turnstile指令)。PasswordDataModel:oldPassword、newPassword、confirmNewPassword,內建長度 ≥12、必須含英數加符號等規則。requiredOldPassword控制是否需要舊密碼欄位。EmailReceiverDataModel:管理收件人陣列(receiver/cc/bcc),提供add()、remove(index)、clean()、dataList()。
BaseDictionary
集中管理「下拉選項/代碼表」這類字典資料:
class AppDictionary extends BaseDictionary {
init() {
this.data = { roleOptions: [], statusOptions: [] };
this.dataProvider = async (key) => {
const resp = await ApiService.call({ endpointKey: `dict-${key}` });
return resp.data;
};
}
}
const dict = new AppDictionary();
await dict.loadAll(); // 透過 dataProvider 載入所有 key
await dict.loadDictionary('roleOptions');
dict.val('roleOptions');可搭配 createDictionaryStoreOptions 寫入 Pinia store 並啟用 persist 持久化(見 Store 章節)。
ShowMessageDataModel
集中管理「顯示訊息頁」對應的訊息表(忘記密碼信件送出、認證過期等)。
import { ShowMessageDataModel, ShowMessageType } from 'ch3chi-commons-vue';
ShowMessageDataModel.messageMap = {
[ShowMessageType.FORGOT_PASSWORD_SENT]: { title: '已寄出', message: '請至信箱收信', showHomeButton: true },
[ShowMessageType.RESET_PASSWORD_SUCCESS]: { title: '已重置', message: '請重新登入', showLoginButton: true },
[ShowMessageType.AUTH_EXPIRED]: { title: '認證過期', message: '請重新登入', showLoginButton: true },
};
const data = ShowMessageDataModel.getMessageData(ShowMessageType.AUTH_EXPIRED);
const routeParams = ShowMessageDataModel.toRouteParams(ShowMessageType.AUTH_EXPIRED);FormOptions(COptionItem 與通用選項)
import {
CCOptionItem,
CommonStatusOptions, // 啟用 / 停用
CommonStatusStrOptions, // ENABLED / DISABLED
OptionUtils,
ToggleStatusOptions, // 開放 / 關閉
YesNoStatusOptions, // 是 / 否
} from 'ch3chi-commons-vue';
const yesNo: CCOptionItem[] = YesNoStatusOptions.map((o) => new CCOptionItem(o));
// 產生狀態徽章 HTML
const html = OptionUtils.makeCommonStatusLabelText(true); // 啟用:bg-successCOptionItem 欄位:id、text、value、disabled?、selected?、children?、meta?。
CToolUtils 工具函式
| Function | 說明 |
| --- | --- |
| voidFunction | 空函式,常作為 callback 預設值。 |
| delay(ms) | await delay(300),包裝 setTimeout 的 Promise。 |
| pickAndAssign(payload, params) | 把 params 中有值的屬性 assign 到 payload(忽略 null/undefined)。 |
| writeVueRefValue(ref, value) | 安全寫入 Ref 的 value(若不是 ref 則無動作)。 |
| lodashExTools.getVal(obj, path, default) | 包裝 _.get,path 為空時直接回 default。 |
| checkHasSameFile(list, file) | 比對檔名/大小/type/lastModified 判斷重複檔案。 |
| formatDatesInObject(obj) | 把物件樹中的 Date/dayjs 全部轉成 YYYY-MM-DDTHH:mm:ss 字串(ApiService 已自動使用)。 |
| makeDateRangeValidator({ startDateKey, endDateKey, ... }) | 產生 yup .test() 用的日期區間檢查器(起 ≤ 迄)。 |
Store(Pinia)
自 1.15.0 起,
pinia為 peerDependency:host 專案需自行 install 並app.use(pinia)。commons-vue 內部所有 store 都會跟 host 共用同一份 pinia。
useViewStore
維護全域 UI 狀態,並把 <CAlert>、<CBSToast>、<CGlobalSpinner> 三個元件以 ref 形式注入 store,之後就能從任何地方呼叫:
const viewStore = useViewStore();
viewStore.showModalConfirm({
title: '確認刪除',
content: '確定要刪除這筆資料嗎?',
onOk: async () => { /* ... */ },
});
viewStore.addToast({ title: '已儲存', content: '資料更新成功', type: 'success', delay: 3000 });
viewStore.showSpinner();
viewStore.hideSpinner();
viewStore.toggleSidebar();
viewStore.setVersion('1.15.0');提供的 actions:routerNavigationType()、toggleSidebar()、showModal()、showModalError()、showModelAlert()、showModalConfirm()、hideModal()、addToast()、showSpinner()、hideSpinner()、setVersion()。
useUserSessionStore
預設提供 SessionUser 型別的會話 store。需要自訂使用者類別或啟用持久化時,請用工廠:
import { AccessToken, createUserSessionStore, SessionUser } from 'ch3chi-commons-vue';
class AppUser extends SessionUser {
// 自訂屬性
}
export const useAppSessionStore = createUserSessionStore<AppUser>('appSession', {
enabled: true,
key: 'app-session',
userConstructor: AppUser,
onLogin: (user) => console.log('logged in:', user),
onLogout: (user, token) => console.log('logged out:', user, token),
});Store 主要動作:
| Action | 說明 |
| --- | --- |
| saveUser(user) / saveToken(token) | 寫入使用者/token。 |
| setAuthenticated(true) | 設定登入狀態。 |
| checkSessionIsValid() | 呼叫 me 端點驗證 session(會自動 sync user)。 |
| validateSession(helper) | 委派 helper 取得使用者並更新狀態。 |
| refreshToken(helper) | 委派 helper 刷新 token。 |
| startSessionCheck() / stopSessionCheck() | 定時檢查(預設工廠有實作;客製版本可自行覆寫)。 |
| hasPermission(need) | 檢查單一或多個權限字串。 |
| logout() | 清空狀態並派發 user:before-logout / user:after-logout 自訂事件。 |
| triggerManualLogout() / triggerAuthFailedRedirect() | 區分使用者主動登出 vs token 失效;分別把 shouldRedirectToLogin / shouldRedirectToMessage 設為 true。 |
Getters:currentUser、currentToken、isAuthenticated、userUid、account、userName、email、roleCode、permissions、menuItems(自動依權限產生 CMenuItem[])。
Dictionary Store
createDictionaryStoreOptions({ dictionary, persistOptions }) 把 BaseDictionary 子類包裝成 Pinia store。
import { CBaseDictionary, createDictionaryStoreOptions } from 'ch3chi-commons-vue';
import { defineStore } from 'pinia';
const useDictStore = defineStore(
'dictionary',
createDictionaryStoreOptions({
dictionary: new MyDictionary(),
persistOptions: { enabled: true, key: 'dict' },
}),
);
const dict = useDictStore();
await dict.loadAll();
await dict.loadDictionary('roleOptions');
dict.dict?.val('roleOptions');useQueryFormDataStore / backupFormDataStore
兩個 commons-vue 自帶的 store,自 1.15.0 起也對外匯出,供 host 直接呼叫:
useQueryFormDataStore(queryFormDatastore id):儲存清單頁的查詢條件,自動同步到localStorage,可作為「上一次查詢條件還原」用途。BaseListViewModel.saveQueryParamToStore()內部用的就是它。backupFormDataStore(formDatastore id):通用備份 store,提供backupData(name, data)/getBackupData(name)。
import { backupFormDataStore, useQueryFormDataStore } from 'ch3chi-commons-vue';
useQueryFormDataStore().save('userListQuery', { keyword: 'foo', page: { pageIndex: 0 } });
useQueryFormDataStore().getQueryParam('userListQuery');
useQueryFormDataStore().loadAllFromLocalStorage();
useQueryFormDataStore().clearQueryParam('userListQuery');
backupFormDataStore().backupData('draft.user', { name: 'tmp' });
backupFormDataStore().getBackupData('draft.user');VueSessionStoreInstaller
Vue 插件,把 sessionStore 注入 commons-vue 內部的 SESSION_STORE_KEY,同時:
- 全域註冊
<HasPermission>元件。 - 全域註冊
v-permission指令(綁定sessionStore.hasPermission)。
import { VueSessionStoreInstaller, useUserSessionStore } from 'ch3chi-commons-vue';
app.use(VueSessionStoreInstaller, { sessionStore: useUserSessionStore() });Vue 指令
| 指令 | 用途 | 範例 |
| --- | --- | --- |
| v-tooltip | 初始化 Bootstrap Tooltip(讀 title 屬性)。 | <button v-tooltip title="說明">i</button> |
| v-cbs-dropdown | 初始化 Bootstrap Dropdown,點擊外部自動關閉。 | <button v-cbs-dropdown>...</button> |
| v-cbs-modal | 綁定 CBSModalViewModel,自動處理 init/show/hide/dispose。 | <div class="modal fade" v-cbs-modal="modal"> |
| v-date-formatter | 把 Ref<Date> 或 Date 渲染到元素上;可透過 data-date-format 屬性指定格式。 | <span v-date-formatter="dateRef" data-date-format="YYYY/MM/DD" /> |
| v-cf-turnstile | 渲染 Cloudflare Turnstile,token 寫回 vee-validate FieldContext。需先設定 window.__CLOUDFLARE_TURNSTILE_SITE_KEY__。 | <div v-cf-turnstile="fieldContext" /> |
| v-form-invalid(CFormFieldErrorStyleDirective) | 偵測 vee-validate FieldContext.errors,自動切換 is-invalid class。 | <input v-form-invalid="fieldModel" /> |
| v-form-invalid-yup(CFormFieldErrorOnYupDirective) | 直接以 yup Schema 同步驗證 element value。 | <input v-form-invalid-yup="yupSchema" /> |
| v-permission(PermissionDirective) | need + granted 模式:自行傳入授權字串陣列做比對。 | <button v-permission="{ need: 'user:UPDATE', granted: perms }">編輯</button> |
| v-permission(createPermissionDirectives) | 由 VueSessionStoreInstaller 自動註冊的版本:只傳權限字串/陣列,內部用 sessionStore 比對。 | <button v-permission="'user:UPDATE'">編輯</button> |
| v-store-permission(StorePermissionDirective) | 從 binding 帶入 sessionStore:{ sessionStore, need, enableVisible }。 | <button v-store-permission="{ sessionStore, need: ['user:UPDATE'] }">編輯</button> |
enableVisible: true會用d-none隱藏元素,false直接el.remove()。createPermissionDirectives版本則會 replace 成 comment node。
註冊指令的範例已在「快速開始」一章。
UI 元件
CAlert
全域 Bootstrap Modal 元件。透過 defineExpose 提供 show(data)、hide(),配合 useViewStore 使用最方便:
<CAlert ref="mainBSModal" />viewStore.mainBSModal = mainBSModal.value;
viewStore.showModal({ title: '提示', content: '完成', onOk: () => {} });
viewStore.showModalConfirm({ content: '確認刪除?', onOk: doDelete, onCancel: doCancel });
viewStore.showModalError({ content: 'API 錯誤' });
viewStore.showModelAlert({ content: '警告' });CAlertModalData 欄位:
| 欄位 | 說明 |
| --- | --- |
| type | CAlertModalType.Info / Error / Alert / Confirm / Success 控制 header 樣式。 |
| title | 自訂標題;未設定時依 type 取預設(資訊/錯誤/警告/確認)。 |
| content | 內文。 |
| onOk / onCancel | 按鈕 callback。Confirm 一律顯示取消按鈕;其他 type 則有 onCancel 才顯示。 |
| nextStep | (Toast 用)後續動作描述。 |
| delay | (Toast 用)覆寫單筆 toast 的 autohide 毫秒數。 |
| Prop | 預設 | 說明 |
| --- | --- | --- |
| id | 自動 uuid | Modal element id,需要外部控制時可指定。 |
CBSToast
右上角訊息推播。addToast(data) 透過 ref 呼叫;useViewStore().addToast(...) 是同樣的路徑。
<CBSToast ref="toastView" :delay="5000" />viewStore.toastView = toastView.value;
viewStore.addToast({ title: '已儲存', content: '使用者已建立', type: 'success' });
viewStore.addToast({ title: '錯誤', content: '上傳失敗', type: 'error', delay: 8000 });| Prop | 預設 | 說明 |
| --- | --- | --- |
| delay | 5000 | 全域 autohide 毫秒;可被單筆 toast 的 data.delay 覆寫(delay: 0 也合法)。 |
樣式類別 .c-toast-success / .c-toast-error / .c-toast-alert / .c-toast-info 已內建(Bootstrap 5 顏色)。
CGlobalSpinner
全頁載入動畫,可由 useViewStore 或自行 ref 呼叫 show() / hide()。
| Prop | 預設 | 說明 |
| --- | --- | --- |
| show | false | 初始是否顯示。 |
| color | #e4b445 | spinner 顏色,會綁定到 --spinner-color CSS 變數。 |
| isDelay | false | 顯示前是否延遲(避免閃爍)。 |
| delayMS | 300 | 延遲毫秒(isDelay = true 時生效)。 |
<CGlobalSpinner ref="globalSpinner" color="#e4b445" :is-delay="true" :delay-m-s="200" />CImage
包裝 <img> 並提供:載入時 fade-in、載入失敗顯示佔位圖、SVG 預設破圖樣式。
| Prop | 說明 |
| --- | --- |
| src | 圖片 URL;空值時自動使用 1×1 base64 佔位圖。 |
| alt | 替代文字。 |
| loading | 'lazy' / 'eager',預設 lazy。 |
| className | 額外 class。 |
| onLoad | (e: Event) => void,圖片載入完成。 |
| onError | (e: Event \| string) => void,圖片載入失敗。 |
<CImage :src="avatarUrl" alt="頭像" :onError="onAvatarError" />HasPermission
根據 inject('c-sessionStore') 取得 sessionStore,再用 hasPermission(need) 判斷子內容是否渲染。VueSessionStoreInstaller 會自動 provide。
<HasPermission :need="['user:UPDATE']">
<button @click="onEdit">編輯</button>
</HasPermission>| Prop | 型別 | 說明 |
| --- | --- | --- |
| need | string \| string[] | 必須具備的權限(其中一個符合即顯示)。 |
表格元件 CTable
完整功能:欄位定義(文字/日期/日期區間/行號/Checkbox/TextInput/Action)、排序、分頁、自訂 render(HTML 或 VNode)、Action 過濾、樣式變數。
<template>
<CTable
:columns="columns"
:data-list="dataList"
:query-param="queryParam"
:multi-sort="false"
:page-size-options="[10, 20, 50]"
@page-change="handlePageChange"
@sort-change="handleSortChange"
@table-action="handleTableAction"
/>
</template>
<script setup lang="ts">
import {
CTable, CTableColumn, CTableColumnType, CTableColumnActionType,
QueryParameter, QueryPage,
} from 'ch3chi-commons-vue';
const queryParam = reactive(new QueryParameter({ page: new QueryPage({ pageSize: 20 }) }));
const columns: CTableColumn[] = [
new CTableColumn({ type: CTableColumnType.RowNumber, text: '#', width: '60px' }),
new CTableColumn({
type: CTableColumnType.Text,
dataName: 'name',
text: '名稱',
sortConfig: { sortable: true, key: 'name' },
}),
new CTableColumn({ type: CTableColumnType.Date, dataName: 'createdAt', text: '建立日' }),
new CTableColumn({
type: CTableColumnType.DateRange,
dataNameList: ['startAt', 'endAt'],
text: '使用期間',
}),
new CTableColumn({
type: CTableColumnType.Action,
text: '操作',
dataAlign: 'right',
actionList: [
{ actionType: CTableColumnActionType.Edit, title: '編輯', permission: 'user:UPDATE' },
{ actionType: CTableColumnActionType.Delete, title: '刪除' },
],
actionFilter: (action, row) => {
if (action.actionType === CTableColumnActionType.Delete) return !row.isProtected;
return true;
},
}),
];
function handleTableAction(actionType, rowData, event) {
// table-action: 由 actionList 點擊時觸發
}
</script>CTableColumn 重要設定:
| 欄位 | 說明 |
| --- | --- |
| type | RowNumber / Text / TextInput / Date / DateRange / DateTime / Action / Checkbox。 |
| dataName | 對應 dataList 中的屬性名(單欄)。 |
| dataNameList | DateRange 專用(兩個欄位)。 |
| formatPattern | Date / DateTime 的 dayjs 格式字串。 |
| sortConfig | { sortable, key, direction },搭配 queryParam.sort 同步。 |
| width / align / dataAlign | th / td 對齊與寬度。 |
| actionList | Action 欄位的按鈕清單,可指定 actionType、text、title、cls、icon、onClick、permission。 |
| actionFilter | 單一函式或陣列,用來動態過濾 actionList(搭配 BaseListViewModel.tableActionFilter 自動串接權限過濾)。 |
| customRender(rowData) | 完全自訂 render,可回傳 string/HTMLElement/VNode;搭配 useHtml 或 useVNode。 |
全域樣式:CTableConfig.setPaginationStyle(...)、CTableConfig.setTableStyleConfig(...)、CTableConfig.pageSizeOptions = [10, 20, 50],CTableColumn.configure({ iconMap, btnClassMap, actionInnerCls }) 可調整 action 欄位的預設 icon / class。
事件:
@page-change(QueryPage):分頁或每頁筆數變更。@sort-change({ column, sort }):點擊可排序欄位。@table-action(actionType,rowData,event):Action 欄位按鈕點擊。
Form 欄位元件
使用前提:所有 Form 欄位元件都會
inject('c-formViewModel', model)取得BaseFormDataModel子類別。請在父層元件provide('c-formViewModel', model),並先呼叫model.initForm()。
<script setup lang="ts">
import { provide } from 'vue';
const model = new UserForm();
model.initForm();
provide('c-formViewModel', model);
</script>共通的可選 prop(多數元件都有):id、label、name(必填,對應 yup field key)、required / requiredReactive(後者為響應式版本,當 yup schema 用 .when() 動態判斷時建議使用)、placeholder、readMode(轉為純文字顯示模式)、styleConfig(IBSFieldStyleConfig)。
CTextInputFormField
文字/密碼/數字輸入欄位。
| Prop | 說明 |
| --- | --- |
| type | 預設 'text',常見 password / number。 |
| min / max | number type 時生效。 |
| autocomplete | 預設 'off'。 |
| disabledState | ComputedRef<boolean>,動態 disable。 |
| readMode | 純文字顯示。 |
maxlength 會自動從 yup schema 的 .max(n) 推導。
<CTextInputFormField name="account" label="帳號" placeholder="請輸入帳號" />
<CTextInputFormField name="password" label="密碼" type="password" autocomplete="new-password" />CTextAreaFormField
多行文字輸入。rows(預設 3)、maxlength 可自行指定,未填時會嘗試從 schema 推導。
<CTextAreaFormField name="description" label="說明" :rows="5" :maxlength="500" />CSelectFormField
下拉選單。optionList 可為陣列或 Promise<COptionItem[]>;若有 dependentField + fetchOptions(value),會在父欄位變動時自動重新載入並清掉現值(首次載入不清)。
<CSelectFormField
name="cityCode"
label="城市"
:optionList="cityOptions"
dependentField="countryCode"
:fetchOptions="(country) => fetchCityOptions(country)"
/>CCheckBoxFormField / CCheckBoxPlatFormField
複選框群組(綁定到陣列欄位)。Plat 版本把整個 row 變成 label,視覺上更像「卡片式」選項,並支援 helper icon。
<CCheckBoxFormField name="tags" label="興趣" :optionList="tagOptions" />
<CCheckBoxPlatFormField name="permissions" label="權限" :optionList="permOptions" />CRadioFormField / CRadioPlatFormField
單選 radio 群組。Plat 版本同樣是「整個 row 為 label」風格。
<CRadioFormField name="gender" label="性別" :optionList="genderOptions" />
<CRadioPlatFormField name="role" label="角色" :optionList="roleOptions" />CDateFormField
flatpickr 單日選擇器(中文 locale 內建)。
| Prop | 說明 |
| --- | --- |
| timeType | 'startOfDay' 將時間補到 00:00:00.000;'endOfDay' 補到 23:59:59.999。 |
<CDateFormField name="birthDate" label="生日" />
<CDateFormField name="startAt" label="生效日" timeType="startOfDay" />CNDateFormField
HTML5 <input type="date"> 原生版本,外觀依瀏覽器,但零第三方依賴、支援 min / max。
| Prop | 說明 |
| --- | --- |
| min / max | 'YYYY-MM-DD' 格式字串。 |
| timeType | 同上。 |
<CNDateFormField name="effectiveAt" label="生效日期" timeType="startOfDay" min="2026-01-01" max="2026-12-31" />選用建議:
| 元件 | 底層 | 適用情境 |
| --- | --- | --- |
| CDateFormField | flatpickr | 跨瀏覽器外觀一致、中文 locale。 |
| CNDateFormField | HTML5 <input type="date"> | 零第三方依賴、min/max 範圍限制。 |
CDateRangeFormField
flatpickr 日期區間(同時綁兩個 field)。
| Prop | 說明 |
| --- | --- |
| startDateFieldName | yup 中的開始日期 field 名(預設 'startDate')。 |
| endDateFieldName | 結束日期 field 名(預設 'endDate')。 |
<CDateRangeFormField startDateFieldName="startAt" endDateFieldName="endAt" />CDateQueryField
不依賴 BaseFormDataModel 的單日選擇器,直接綁 model[name](適合查詢列表用)。
| Prop | 說明 |
| --- | --- |
| model | 任意物件(會用 _.get/set)。 |
| name | 物件屬性 path。 |
| timeType | 同上。 |
<CDateQueryField :model="queryParam" name="startsAt" label="起始日" timeType="startOfDay" />CFilePickerFormField
整合上傳 API、進度條、已上傳列表。會自動呼叫 Resources-Create / Resources-Delete 端點(請在 ApiService.configure 時註冊)。
| Prop | 預設 | 說明 |
| --- | --- | --- |
| name | — | 對應 dataModel 屬性名(值為 CFileDataModel 或 CFileDataModel[])。 |
| dataModel | — | 父層的資料模型(含 formFieldMap)。 |
| validationDataKey | name | 驗證對應的 field key(單/多重欄位場景可分開)。 |
| accept | * | <input> accept 值。 |
| multiple | false | 多檔上傳。 |
| fileCountLimit | — | 多檔模式下的最多檔案數。 |
| fileSizeLimit | 2MB(或 VITE_MAX_FILE_SIZE) | 單檔上限(bytes)。 |
| tip | — | label 旁的 tooltip 提示文字。 |
defineExpose({ addSavedFiles }):可從父元件主動塞入已存在的檔案清單。
<CFilePickerFormField
ref="picker"
name="attachments"
label="附件"
:data-model="model"
multiple
:file-count-limit="5"
accept="image/*,application/pdf"
tip="最多 5 個檔案,單檔 2MB"
/>CChangePasswordFormField
包含「編輯密碼」按鈕 + Bootstrap Modal 表單,內建 PasswordDataModel 驗證規則(長度、英數加符號、新/確認新密碼一致)。
| Prop | 預設 | 說明 |
| --- | --- | --- |
| userUid | — | 使用者 uid。 |
| requireOldPassword | true | 是否顯示舊密碼欄位。 |
| dataModelConstructor | PasswordDataModel | 自訂 model(需繼承 PasswordDataModel)。 |
| callApi(model) | — | 送出時呼叫的 API 函式,回傳 Promise<ApiResponse>。 |
<CChangePasswordFormField
:user-uid="userUid"
:require-old-password="true"
:call-api="(m) => ApiService.call({ endpointKey: 'changePassword', postBody: m.toPayload() })"
/>CTinyMCEEditorFormField
TinyMCE Editor 整合,需在 .env 設定 VITE_TINYMCE_API_KEY。
<CTinyMCEEditorFormField name="content" label="內容" required />SCTextInputFormField(slot-based 客製版本)
跟 CTextInputFormField 同樣的 props,但把 componentId / fieldRef / fieldModel / isRequired / isReadMode / maxLengthAttr / inputAttrs 透過 slot 暴露出來,方便自訂外觀。提供 default / label / input / error 四個具名 slot。
<SCTextInputFormField name="email" label="信箱" type="email">
<template #input="{ inputAttrs, fieldRef, fieldModel }">
<div class="input-group">
<span class="input-group-text">@</span>
<input class="form-control" v-bind="inputAttrs" v-model="fieldRef" v-form-invalid="fieldModel" />
</div>
</template>
</SCTextInputFormField>進階文件索引
- API Service Bearer Token 機制
- BSFieldStyleConfig 指南
- CTable 使用指南
- CTable 樣式配置
- CTable scrollbar 樣式
- SCTextInputFormField 使用範例
- UserSession Store 泛型用法
- UserSession Logout 事件
- Permission 使用
- 其他文件請參考
doc/目錄
開發與建置
# 安裝依賴
npm install
# Type check
npm run build
# Bundle
npm run vite-build
# 發行
npm run release # 依 Conventional Commits 自動 bump
npm run release:minor # 強制 minor
# 程式碼品質
npm run lint
npm run lint:fix
npm run format程式碼規範:
- ESLint:Flat Config + Google Style(2 space indent、單引號、強制分號)。
- Prettier:與 ESLint 整合,避免格式衝突。
- Commit Message:Conventional Commits(
feat:/fix:/chore:/refactor:/docs:/feat!:等),由standard-version自動產生 CHANGELOG。
授權
此專案採用 MIT 授權。
