npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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。

目錄

  1. 功能概述
  2. 安裝與相依套件
  3. 快速開始:應用初始化
  4. API 與授權
  5. Model 與工具
  6. Store(Pinia)
  7. Vue 指令
  8. UI 元件
  9. 表格元件 CTable
  10. Form 欄位元件
  11. 進階文件索引
  12. 開發與建置
  13. 授權

功能概述

  • 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.5
  • pinia ^3.0.4(自 1.15.0 起改為 peerDependency,host 需自行 install 並 app.use(pinia)
  • bootstrap ^5.3
  • axios >= 1.13
  • vee-validate >= 4.15
  • yup >= 1.7
  • dayjs >= 1.11
  • lodash >= 4.17
  • uuid >= 11.1
  • mime-types >= 3.0
  • flatpickr >= 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.jsonpeerDependencies

樣式檔需另外載入:

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(useViewStoreuseUserSessionStoreuseQueryFormDataStorebackupFormDataStore)會自動跟 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()httpStatusdatapagingblobDataheadersnativeError 等欄位。錯誤時可以包成 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 的繼承關係」(例如有 CREATEUPDATE 自動視同擁有 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']);       // true

PermissionAction 提供常用列舉值:SIGN_INSIGN_OUTFORGOT_PASSWORDRESET_PASSWORDCHANGE_PASSWORDCREATESEARCHREADUPDATEDELETEEXPORT


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');

CheckableDataModelBaseFormDataModel 之上加了 checked: booleantoggleChecked(),搭配 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 後的 payload

QueryPage 提供完整分頁狀態:pageIndexpageSizetotalCount(自動算 totalPageoffset)、hasPreviousPage()hasNextPage()previous()next()pageRange()(產生 CTable 用的頁碼陣列)。

DateRangeParamQueryParameter 的子類,多了 startsAtendsAt 欄位(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 涵蓋:containerClasslabelClassinputClasswrapperClasserrorClassrequiredLabelClassrequiredLabelTextplainTextClassselectClasstextareaClasstextareaRowscheckboxClassradioClassfileClasscalendarIconClasscalendarClearIconClasschangePasswordButtonClasschangePasswordButtonIcon 等。完整清單見 IBSFieldStyleConfig

CFileDataModel / FileUploadProgress

CFileDataModel 描述已上傳檔案,並在 init() 中根據副檔名自動套用 icon / badge 樣式(圖片、PDF、Word、Excel、影片各自有對應 Font Awesome icon)。

CPhotoDataModelCFileDataModel 的子類,多了 widthheightloaded

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>

支援的 typeInfoFormTable(會給定不同預設標題)。

SessionUser / TokenUser / AccessToken

  • SessionUser:基本使用者欄位(userUidaccountnameemailroleCodepermissions 等)+ load() / merge() / toJSON() / loadJSON()
  • TokenUser extends SessionUser,包含 tokens: AccessTokenprofile
  • AccessTokentokenTypeaccessTokenaccessTokenExpiresAtrefreshTokenrefreshTokenExpiresAtsessionUid;提供 isExpired()isRefreshTokenExpired()get bearerToken()(產生 "Bearer xxx" 字串)。

LoginDataModel / PasswordDataModel / EmailReceiverDataModel

開箱即用的 BaseFormDataModel 子類:

  • LoginDataModelaccountpasswordturnstileTokenenableCFTurnstile(搭配 v-cf-turnstile 指令)。
  • PasswordDataModeloldPasswordnewPasswordconfirmNewPassword,內建長度 ≥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-success

COptionItem 欄位:idtextvaluedisabled?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) | 安全寫入 Refvalue(若不是 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:currentUsercurrentTokenisAuthenticateduserUidaccountuserNameemailroleCodepermissionsmenuItems(自動依權限產生 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 (queryFormData store id):儲存清單頁的查詢條件,自動同步到 localStorage,可作為「上一次查詢條件還原」用途。BaseListViewModel.saveQueryParamToStore() 內部用的就是它。
  • backupFormDataStore (formData store 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-invalidCFormFieldErrorStyleDirective) | 偵測 vee-validate FieldContext.errors,自動切換 is-invalid class。 | <input v-form-invalid="fieldModel" /> | | v-form-invalid-yupCFormFieldErrorOnYupDirective) | 直接以 yup Schema 同步驗證 element value。 | <input v-form-invalid-yup="yupSchema" /> | | v-permissionPermissionDirective) | need + granted 模式:自行傳入授權字串陣列做比對。 | <button v-permission="{ need: 'user:UPDATE', granted: perms }">編輯</button> | | v-permissioncreatePermissionDirectives) | 由 VueSessionStoreInstaller 自動註冊的版本:只傳權限字串/陣列,內部用 sessionStore 比對。 | <button v-permission="'user:UPDATE'">編輯</button> | | v-store-permissionStorePermissionDirective) | 從 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 欄位的按鈕清單,可指定 actionTypetexttitleclsicononClickpermission。 | | actionFilter | 單一函式或陣列,用來動態過濾 actionList(搭配 BaseListViewModel.tableActionFilter 自動串接權限過濾)。 | | customRender(rowData) | 完全自訂 render,可回傳 string/HTMLElement/VNode;搭配 useHtmluseVNode。 |

全域樣式: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(多數元件都有):idlabelname必填,對應 yup field key)、required / requiredReactive(後者為響應式版本,當 yup schema 用 .when() 動態判斷時建議使用)、placeholderreadMode(轉為純文字顯示模式)、styleConfigIBSFieldStyleConfig)。

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"> | 零第三方依賴、minmax 範圍限制。 |

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 屬性名(值為 CFileDataModelCFileDataModel[])。 | | 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>

進階文件索引

開發與建置

# 安裝依賴
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 授權。