ch3chi-commons-vue 1.2.0 → 1.8.1

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.
Files changed (112) hide show
  1. package/package.json +2 -1
  2. package/src/api/ApiService.ts +869 -0
  3. package/src/auth/AuthorizationService.ts +138 -0
  4. package/src/auth/PermissionDescriptor.ts +99 -0
  5. package/src/auth/keys.ts +5 -0
  6. package/src/components/CAlert.vue +188 -0
  7. package/src/components/CAlertDefine.ts +20 -0
  8. package/src/components/CBSToast.vue +119 -0
  9. package/src/components/CGlobalSpinner.vue +84 -0
  10. package/src/components/CImage.vue +67 -0
  11. package/src/components/CRowCheckBox.vue +75 -0
  12. package/src/components/CRowTextInput.vue +27 -0
  13. package/src/components/CTable.vue +524 -0
  14. package/src/components/CTableDefine.ts +566 -0
  15. package/src/components/CTableTD.vue +28 -0
  16. package/src/components/HasPermission.vue +28 -0
  17. package/src/components/form/CChangePasswordFormField.vue +146 -0
  18. package/src/components/form/CCheckBoxFormField.vue +91 -0
  19. package/src/components/form/CCheckBoxPlatFormField.vue +94 -0
  20. package/src/components/form/CDateFormField.vue +149 -0
  21. package/src/components/form/CDateQueryField.vue +111 -0
  22. package/src/components/form/CDateRangeFormField.vue +138 -0
  23. package/src/components/form/CFilePickerFormField.vue +471 -0
  24. package/src/components/form/CRadioFormField.vue +62 -0
  25. package/src/components/form/CRadioPlatFormField.vue +67 -0
  26. package/src/components/form/CSelectFormField.vue +175 -0
  27. package/src/components/form/CTextAreaFormField.vue +84 -0
  28. package/src/components/form/CTextInputFormField.vue +99 -0
  29. package/src/components/form/CTinyMCEEditorFormField.vue +99 -0
  30. package/src/components/form/SCTextInputFormField.vue +129 -0
  31. package/src/composables/useCheckBoxFormField.ts +126 -0
  32. package/src/composables/useRadioFormField.ts +106 -0
  33. package/src/directive/CBootstrapDirective.ts +83 -0
  34. package/src/directive/CDateFormatterDirective.ts +37 -0
  35. package/src/directive/CFTurnstileDirective.ts +46 -0
  36. package/src/directive/CFormDirective.ts +57 -0
  37. package/src/directive/PermissionDirective.ts +102 -0
  38. package/src/env.d.ts +19 -0
  39. package/src/index.ts +83 -0
  40. package/src/model/BSFieldStyleConfig.ts +349 -0
  41. package/src/model/BaseDictionary.ts +86 -0
  42. package/src/model/BaseFormDataModel.ts +623 -0
  43. package/src/model/BaseListViewModel.ts +392 -0
  44. package/src/model/CBSModalViewModel.ts +91 -0
  45. package/src/model/CFileDataModel.ts +181 -0
  46. package/src/model/CImageViewModel.ts +34 -0
  47. package/src/model/CMenuItem.ts +199 -0
  48. package/src/model/EmailReceiverDataModel.ts +149 -0
  49. package/src/model/EmptyDataModel.ts +25 -0
  50. package/src/model/FormOptions.ts +112 -0
  51. package/src/model/LoginDataModel.ts +51 -0
  52. package/src/model/PasswordDataModel.ts +70 -0
  53. package/src/model/QueryParameter.ts +310 -0
  54. package/src/model/SessionUser.ts +110 -0
  55. package/src/model/ShowMessageDataModel.ts +69 -0
  56. package/src/model/TokenUser.ts +157 -0
  57. package/src/stores/FormDataStore.ts +73 -0
  58. package/src/stores/ViewStore.ts +701 -0
  59. package/src/stores/VueSessionStoreInstaller.ts +22 -0
  60. package/src/types/turnstile.d.ts +8 -0
  61. package/src/utils/CToolUtils.ts +133 -0
  62. package/dist/api/ApiService.d.ts +0 -233
  63. package/dist/auth/AuthorizationService.d.ts +0 -56
  64. package/dist/auth/PermissionDescriptor.d.ts +0 -37
  65. package/dist/components/CAlert.vue.d.ts +0 -17
  66. package/dist/components/CAlertDefine.d.ts +0 -14
  67. package/dist/components/CBSToast.vue.d.ts +0 -6
  68. package/dist/components/CGlobalSpinner.vue.d.ts +0 -13
  69. package/dist/components/CRowCheckBox.vue.d.ts +0 -14
  70. package/dist/components/CRowTextInput.vue.d.ts +0 -10
  71. package/dist/components/CTable.vue.d.ts +0 -24
  72. package/dist/components/CTableDefine.d.ts +0 -201
  73. package/dist/components/CTableTD.vue.d.ts +0 -7
  74. package/dist/components/form/CChangePasswordFormField.vue.d.ts +0 -14
  75. package/dist/components/form/CCheckBoxFormField.vue.d.ts +0 -30
  76. package/dist/components/form/CDateFormField.vue.d.ts +0 -17
  77. package/dist/components/form/CDateQueryField.vue.d.ts +0 -16
  78. package/dist/components/form/CDateRangeFormField.vue.d.ts +0 -17
  79. package/dist/components/form/CFilePickerFormField.vue.d.ts +0 -28
  80. package/dist/components/form/CRadioFormField.vue.d.ts +0 -30
  81. package/dist/components/form/CSelectFormField.vue.d.ts +0 -18
  82. package/dist/components/form/CTextAreaFormField.vue.d.ts +0 -16
  83. package/dist/components/form/CTextInputFormField.vue.d.ts +0 -22
  84. package/dist/directive/CBootstrapDirective.d.ts +0 -17
  85. package/dist/directive/CDateFormatterDirective.d.ts +0 -10
  86. package/dist/directive/CFTurnstileDirective.d.ts +0 -15
  87. package/dist/directive/CFormDirective.d.ts +0 -9
  88. package/dist/directive/PermissionDirective.d.ts +0 -15
  89. package/dist/index.cjs.js +0 -19103
  90. package/dist/index.d.ts +0 -45
  91. package/dist/index.es.js +0 -19086
  92. package/dist/model/BSFieldStyleConfig.d.ts +0 -121
  93. package/dist/model/BaseDictionary.d.ts +0 -34
  94. package/dist/model/BaseFormDataModel.d.ts +0 -199
  95. package/dist/model/BaseListViewModel.d.ts +0 -165
  96. package/dist/model/CBSModalViewModel.d.ts +0 -44
  97. package/dist/model/CFileDataModel.d.ts +0 -74
  98. package/dist/model/CImageViewModel.d.ts +0 -8
  99. package/dist/model/CMenuItem.d.ts +0 -86
  100. package/dist/model/EmailReceiverDataModel.d.ts +0 -57
  101. package/dist/model/EmptyDataModel.d.ts +0 -7
  102. package/dist/model/FormOptions.d.ts +0 -60
  103. package/dist/model/LoginDataModel.d.ts +0 -12
  104. package/dist/model/PasswordDataModel.d.ts +0 -15
  105. package/dist/model/QueryParameter.d.ts +0 -92
  106. package/dist/model/SessionUser.d.ts +0 -45
  107. package/dist/model/ShowMessageDataModel.d.ts +0 -44
  108. package/dist/model/TokenUser.d.ts +0 -50
  109. package/dist/stores/FormDataStore.d.ts +0 -31
  110. package/dist/stores/ViewStore.d.ts +0 -349
  111. package/dist/style.css +0 -223
  112. package/dist/utils/CToolUtils.d.ts +0 -53
@@ -0,0 +1,126 @@
1
+ import {inject, Ref, watch, ref, computed, toValue, onMounted} from 'vue'
2
+ import {v4 as uuidv4} from 'uuid'
3
+ import {cloneDeep, get, isArray, isObject} from 'lodash'
4
+ import {BaseFormDataModel, hasFormFieldError} from '../model/BaseFormDataModel'
5
+ import {EmptyDataModel} from '../model/EmptyDataModel'
6
+ import {BSFieldStyleConfig, IBSFieldStyleConfig} from '../model/BSFieldStyleConfig'
7
+ import {COptionItem} from '../model/FormOptions'
8
+
9
+ /**
10
+ * useCheckBoxFormField
11
+ * 將 CCheckBoxFormField 的共用邏輯抽離為 composable,
12
+ * 提供 checkbox 欄位的選項、狀態、樣式、驗證等功能。
13
+ */
14
+ interface UseCheckBoxFormFieldProps {
15
+ /** Checkbox 欄位的標籤文字 */
16
+ label?: string
17
+ /** 是否必填 */
18
+ required?: boolean
19
+ requiredReactive?: boolean // 以 reactive 方式控制是否必填
20
+ /** 欄位名稱 (form field name) */
21
+ name: string
22
+ /** Checkbox 選項集,可為靜態陣列或 reactive 物件 */
23
+ optionList: COptionItem[] | Ref<COptionItem[]>
24
+ valueType?: string; // 欄位值的類型 (例如: 'array', 'object', 'string' 等)
25
+ /** 是否為閱讀模式 */
26
+ readMode?: boolean
27
+ /** 樣式設定 */
28
+ styleConfig?: IBSFieldStyleConfig
29
+ }
30
+
31
+ export function useCheckBoxFormField(props: UseCheckBoxFormFieldProps) {
32
+ // 1. 建立 Checkbox 選項集合,支援 reactive optionList
33
+ const useOptionList = computed(() => {
34
+ const rawList = toValue(props.optionList) as COptionItem[]
35
+ const clonedList = cloneDeep(rawList)
36
+ clonedList.forEach((optionItem: COptionItem) => {
37
+ if (!optionItem.id) {
38
+ const uniqueId = uuidv4()
39
+ optionItem.id = `checkbox-${props.name}-${uniqueId}`
40
+ }
41
+ })
42
+ return clonedList
43
+ })
44
+
45
+ // 2. 注入 viewModel,取得表單資料與驗證狀態
46
+ const viewModel = inject('c-formViewModel', new EmptyDataModel()) as BaseFormDataModel
47
+ if (!viewModel) {
48
+ console.error('CheckBoxFormField: viewModel not found, please provide "c-formViewModel" in parent component')
49
+ }
50
+
51
+ // 3. 合併樣式設定,支援自訂與預設樣式
52
+ const bStyleConfig = BSFieldStyleConfig.mix({
53
+ plainTextClass: ''
54
+ }, props.styleConfig)
55
+
56
+ // 4. 取得欄位對應的 fieldModel 與 value 參照 (v-model 綁定)
57
+ const formFieldMap = viewModel.formFieldMap
58
+ const fieldModel = get(formFieldMap, props.name)
59
+ if (!fieldModel) {
60
+ console.error('CheckBoxFormField: field model not found for name:', props.name)
61
+ }
62
+ // 動態推斷型別的 fieldRef
63
+ let fieldRef: Ref<unknown> | undefined = undefined
64
+ if (fieldModel && fieldModel.value !== undefined) {
65
+ fieldRef = fieldModel.value as Ref<unknown>
66
+ } else {
67
+ console.error('CheckBoxFormField.ref: field model not found for name:', props.name)
68
+ }
69
+
70
+ // 5. 控制項屬性:是否必填、是否閱讀模式、選中選項文字
71
+ // isRequired 改為 computed,根據 props.required(優先)及 viewModel.fieldIsRequired(props.name) 響應式判斷
72
+ const isRequired = computed(() => {
73
+ if (typeof props.requiredReactive === 'boolean') {
74
+ return props.requiredReactive
75
+ }
76
+ return props.required || viewModel.fieldIsRequired(props.name)
77
+ })
78
+ const isReadMode = props.readMode === true
79
+ const selectedText = ref('')
80
+
81
+ // 6. 閱讀模式下,動態取得已選擇選項的顯示文字
82
+ if (isReadMode && fieldRef) {
83
+ watch(fieldRef, (newValue) => {
84
+ // 多選情況
85
+ if (isArray(newValue) || isObject(newValue)) {
86
+ const selectedOptions = useOptionList.value.filter(option => {
87
+ if (isArray(newValue)) {
88
+ return newValue.includes(option.value)
89
+ } else {
90
+ return Object.values(newValue).includes(option.value)
91
+ }
92
+ })
93
+ selectedText.value = selectedOptions.map(option => option.text).join('\n')
94
+ return
95
+ }
96
+ // 單選情況
97
+ const selectedOption = useOptionList.value.find(option => option.value === newValue)
98
+ selectedText.value = selectedOption ? selectedOption.text : ''
99
+ }, {immediate: true})
100
+ }
101
+
102
+ onMounted(() => {
103
+ });
104
+
105
+ // 8. 回傳所有組件所需的狀態與參照
106
+ return {
107
+ /** 處理後的選項集 (含唯一 id) */
108
+ useOptionList,
109
+ /** 注入的表單 viewModel */
110
+ viewModel,
111
+ /** 合併後的樣式設定 */
112
+ bStyleConfig,
113
+ /** 欄位的 fieldModel (含驗證狀態) */
114
+ fieldModel,
115
+ /** 欄位的 value 參照 (v-model 綁定) */
116
+ fieldRef,
117
+ /** 是否必填 */
118
+ isRequired,
119
+ /** 是否為閱讀模式 */
120
+ isReadMode,
121
+ /** 已選擇選項的顯示文字 (閱讀模式用) */
122
+ selectedText,
123
+ /** 檢查欄位是否有錯誤的函數 */
124
+ hasFormFieldError
125
+ }
126
+ }
@@ -0,0 +1,106 @@
1
+ import { inject, Ref, watch, ref, computed, toValue } from 'vue'
2
+ import { v4 as uuidv4 } from 'uuid'
3
+ import { cloneDeep, get } from 'lodash'
4
+ import {BaseFormDataModel, hasFormFieldError} from '../model/BaseFormDataModel'
5
+ import { EmptyDataModel } from '../model/EmptyDataModel'
6
+ import { BSFieldStyleConfig, IBSFieldStyleConfig } from '../model/BSFieldStyleConfig'
7
+ import { COptionItem } from '../model/FormOptions'
8
+
9
+ /**
10
+ * useRadioFormField
11
+ * 將 CRadioFormField/CRadioPlatFormField 的共用邏輯抽離為 composable,
12
+ * 提供 radio 欄位的選項、狀態、樣式、驗證等功能。
13
+ */
14
+ interface UseRadioFormFieldProps {
15
+ /** Radio 欄位的標籤文字 */
16
+ label?: string
17
+ /** 是否必填 */
18
+ required?: boolean
19
+ requiredReactive?: boolean // 以 reactive 方式控制是否必填
20
+ /** 欄位名稱 (form field name) */
21
+ name: string
22
+ /** Radio 選項集,可為靜態陣列或 reactive 物件 */
23
+ optionList: COptionItem[] | Ref<COptionItem[]>
24
+ /** 是否為閱讀模式 */
25
+ readMode?: boolean
26
+ /** 樣式設定 */
27
+ styleConfig?: IBSFieldStyleConfig
28
+ }
29
+
30
+ export function useRadioFormField(props: UseRadioFormFieldProps) {
31
+ // 1. 建立 Radio 選項集合,支援 reactive optionList
32
+ const useOptionList = computed(() => {
33
+ const rawList = toValue(props.optionList) as COptionItem[]
34
+ const clonedList = cloneDeep(rawList)
35
+ clonedList.forEach((optionItem: COptionItem) => {
36
+ if (!optionItem.id) {
37
+ const uniqueId = uuidv4()
38
+ optionItem.id = `radio-${props.name}-${uniqueId}`
39
+ }
40
+ })
41
+ return clonedList
42
+ });
43
+
44
+ // 2. 注入 viewModel,取得表單資料與驗證狀態
45
+ const viewModel = inject('c-formViewModel', new EmptyDataModel()) as BaseFormDataModel
46
+ if (!viewModel) {
47
+ console.error('RadioFormField: viewModel not found, please provide "c-formViewModel" in parent component')
48
+ }
49
+
50
+ // 3. 合併樣式設定,支援自訂與預設樣式
51
+ const bStyleConfig = BSFieldStyleConfig.mix({
52
+ plainTextClass: 'form-control-plaintext bg-light border rounded px-3 d-flex align-items-center'
53
+ }, props.styleConfig)
54
+
55
+ // 4. 取得欄位對應的 fieldModel 與 value 參照 (v-model 綁定)
56
+ const formFieldMap = viewModel.formFieldMap
57
+ const fieldModel = get(formFieldMap, props.name)
58
+ if (!fieldModel) {
59
+ console.error('RadioFormField: field model not found for name:', props.name)
60
+ }
61
+ const fieldRef = fieldModel?.value as Ref<boolean>
62
+ if (!fieldRef) {
63
+ console.error('RadioFormField.ref: field model not found for name:', props.name)
64
+ }
65
+
66
+ // 5. 控制項屬性:是否必填、是否閱讀模式、選中選項文字
67
+ // isRequired 改為 computed,根據 props.required(優先)及 viewModel.fieldIsRequired(props.name) 響應式判斷
68
+ const isRequired = computed(() => {
69
+ if (typeof props.requiredReactive === 'boolean') {
70
+ return props.requiredReactive
71
+ }
72
+ return props.required || viewModel.fieldIsRequired(props.name)
73
+ })
74
+ const isReadMode = props.readMode === true
75
+ const selectedText = ref('')
76
+
77
+ // 6. 閱讀模式下,動態取得已選擇選項的顯示文字
78
+ if (isReadMode) {
79
+ watch(fieldRef, (newValue) => {
80
+ const selectedOption = useOptionList.value.find(option => option.value === newValue)
81
+ selectedText.value = selectedOption ? selectedOption.text : ''
82
+ }, { immediate: true })
83
+ }
84
+
85
+ // 7. 回傳所有組件所需的狀態與參照
86
+ return {
87
+ /** 處理後的選項集 (含唯一 id) */
88
+ useOptionList,
89
+ /** 注入的表單 viewModel */
90
+ viewModel,
91
+ /** 合併後的樣式設定 */
92
+ bStyleConfig,
93
+ /** 欄位的 fieldModel (含驗證狀態) */
94
+ fieldModel,
95
+ /** 欄位的 value 參照 (v-model 綁定) */
96
+ fieldRef,
97
+ /** 是否必填 */
98
+ isRequired,
99
+ /** 是否為閱讀模式 */
100
+ isReadMode,
101
+ /** 已選擇選項的顯示文字 (閱讀模式用) */
102
+ selectedText,
103
+ /** 檢查欄位是否有錯誤的函數 */
104
+ hasFormFieldError
105
+ }
106
+ }
@@ -0,0 +1,83 @@
1
+ import {Directive, DirectiveBinding} from 'vue';
2
+ import {Dropdown, Tooltip} from 'bootstrap';
3
+ import {CBSModalViewModel} from "../model/CBSModalViewModel";
4
+
5
+
6
+ /**
7
+ * v-tooltip 指令
8
+ * 初始化 Bootstrap 的 Tooltip
9
+ * 使用方式:<button v-tooltip>Hover me</button>
10
+ */
11
+ export const vdTooltip: Directive = {
12
+ mounted(el) {
13
+ new Tooltip(el);
14
+ },
15
+ unmounted(el) {
16
+ const tooltip = Tooltip.getInstance(el);
17
+ if (tooltip) tooltip.dispose();
18
+ }
19
+ };
20
+
21
+ /**
22
+ * v-cbs-modal 指令
23
+ * binding.value 必須是 CBSModalViewModel 的實例
24
+ */
25
+ export const vdCBSModal: Directive = {
26
+ mounted(elt, binding: DirectiveBinding) {
27
+ if(!(binding.value instanceof CBSModalViewModel)) {
28
+ console.error('vdCBSModal directive requires binding.value to be an instance of CBSModalView');
29
+ return;
30
+ }
31
+ const viewModel = binding.value as CBSModalViewModel;
32
+ viewModel.init({elt});
33
+ },
34
+ unmounted(elt, binding: DirectiveBinding) {
35
+ if(!(binding.value instanceof CBSModalViewModel)) {
36
+ console.error('vdCBSModal directive requires binding.value to be an instance of CBSModalView');
37
+ return;
38
+ }
39
+ // 清理 Modal 實例
40
+ const viewModel = binding.value as CBSModalViewModel;
41
+ viewModel.unmount();
42
+ }
43
+ }
44
+
45
+ // 當點擊其他地方時,隱藏 Dropdown
46
+ function hideDropdown(el: HTMLElement, event: MouseEvent) {
47
+ // 如果點擊的不是 Dropdown 元素或其子元素,則隱藏 Dropdown
48
+ if (!el.contains(event.target as Node)) {
49
+ Dropdown.getInstance(el)?.hide();
50
+ return;
51
+ }
52
+ }
53
+
54
+ /**
55
+ * v-cbs-dropdown 指令
56
+ * 初始化 Bootstrap 的 Dropdown
57
+ */
58
+ export const vdCBSDropdown: Directive = {
59
+ mounted(el: HTMLElement) {
60
+ // 初始化 Bootstrap 的 Dropdown
61
+ const dropdown = Dropdown.getOrCreateInstance(el);
62
+ // click toggle dropdown
63
+ el.addEventListener('click', (event: Event) => {
64
+ event.preventDefault();
65
+ event.stopPropagation();
66
+ // 顯示 Dropdown
67
+ dropdown.toggle();
68
+ return false;
69
+ });
70
+ const docListener = hideDropdown.bind(null, el);
71
+ // 點擊其他地方關閉 Dropdown
72
+ document.addEventListener('click', docListener);
73
+
74
+ },
75
+ unmounted(el: HTMLElement) {
76
+ // 清理 Dropdown 實例
77
+ const dropdown = Dropdown.getInstance(el);
78
+ dropdown?.dispose();
79
+ const docListener = hideDropdown.bind(null, el);
80
+ // 移除點擊事件監聽器
81
+ document.removeEventListener('click', docListener);
82
+ }
83
+ }
@@ -0,0 +1,37 @@
1
+ import dayjs from 'dayjs';
2
+ import type {Directive} from 'vue';
3
+ import {isRef} from 'vue';
4
+
5
+ function formatDateToElement(el: HTMLElement, value: unknown) {
6
+ let date: Date | undefined;
7
+ if (isRef(value)) {
8
+ date = value.value as Date;
9
+ } else if (value instanceof Date) {
10
+ date = value;
11
+ }
12
+ // 如果 el 有 data-date-format 屬性,則使用該格式化方式
13
+ const dateFormat = el.getAttribute('data-date-format') || 'YYYY-MM-DD';
14
+ const formatted = date ? dayjs(date).format(dateFormat) : '';
15
+ if (el.tagName === 'INPUT' && (el as HTMLInputElement).type === 'text') {
16
+ (el as HTMLInputElement).value = formatted;
17
+ } else if (['SPAN', 'P', 'DIV'].includes(el.tagName)) {
18
+ el.innerHTML = formatted;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * v-date-formatter 指令
24
+ * 解析 ref<Date> 並格式化為文字,根據元素型態輸出
25
+ * 可以透過 attribute data-date-format 指定日期格式,預設為 'YYYY-MM-DD'
26
+ * 使用方式:
27
+ * <input type="text" v-date-formatter="dateRef" data-date-format="YYYY/MM/DD" />
28
+ * <span v-date-formatter="dateRef" data-date-format="DD-MM-YYYY"></span>
29
+ */
30
+ export const vdDateFormatter: Directive = {
31
+ mounted(el, binding) {
32
+ formatDateToElement(el, binding.value);
33
+ },
34
+ updated(el, binding) {
35
+ formatDateToElement(el, binding.value);
36
+ }
37
+ };
@@ -0,0 +1,46 @@
1
+ import {type Directive, DirectiveBinding} from 'vue';
2
+ import {FieldContext} from "vee-validate";
3
+
4
+
5
+ /**
6
+ * v-cf-turnstile 指令
7
+ * 用於在表單中嵌入 Cloudflare Turnstile 驗證碼
8
+ * 使用方式:
9
+ * <pre>
10
+ * <div v-cf-turnstile="fieldContext"></div>
11
+ * 其中 fieldContext 為 vee-validate 的 FieldContext,用於接收驗證碼 token
12
+ * </pre>
13
+ * 需要在全域變數中定義 Cloudflare Turnstile 的 site key:
14
+ * <pre>
15
+ * (window as any).__CLOUDFLARE_TURNSTILE_SITE_KEY__ = 'your-site-key-here';
16
+ * </pre>
17
+ */
18
+ export const CFTurnstileDirective: Directive = {
19
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
20
+ const fieldContext = binding.value as FieldContext;
21
+ const siteKey = (window as any).__CLOUDFLARE_TURNSTILE_SITE_KEY__;
22
+ if (!siteKey) {
23
+ console.error('Cloudflare Turnstile site key is not defined.');
24
+ return
25
+ }
26
+ const renderTurnstile = () => {
27
+ window.turnstile?.render(el, {
28
+ sitekey: siteKey,
29
+ callback: (token: string) => {
30
+ fieldContext.value.value = token;
31
+ }
32
+ })
33
+ }
34
+ if (window.turnstile) {
35
+ renderTurnstile()
36
+ return
37
+ }
38
+ const interval = setInterval(() => {
39
+ if (window.turnstile) {
40
+ clearInterval(interval)
41
+ renderTurnstile()
42
+ }
43
+ }, 100)
44
+
45
+ }
46
+ }
@@ -0,0 +1,57 @@
1
+ import {type Directive, DirectiveBinding} from "vue";
2
+ import {FieldContext} from "vee-validate";
3
+ import _ from "lodash";
4
+ import {Schema} from "yup";
5
+
6
+ /**
7
+ * 此指令用於當 FormField 有錯誤時,顯示錯誤樣式 'is-invalid'。
8
+ */
9
+ export const CFormFieldErrorStyleDirective: Directive = {
10
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
11
+ addErrorClassIfNeed(el, binding.value as FieldContext);
12
+ },
13
+ updated(el: HTMLElement, binding: DirectiveBinding) {
14
+ addErrorClassIfNeed(el, binding.value as FieldContext);
15
+ },
16
+ }
17
+
18
+ // 此函式用於檢查 FieldContext 的錯誤狀態,並根據錯誤狀態添加或移除 'is-invalid' 類別。
19
+ function addErrorClassIfNeed(el: HTMLElement, fieldContext: FieldContext) {
20
+ if(_.isNil(fieldContext) || !_.has(fieldContext, 'errors') || !_.isArray(fieldContext.errors.value)) {
21
+ el.classList.remove('is-invalid');
22
+ return;
23
+ }
24
+
25
+ if(_.isEmpty(fieldContext.errors.value)) {
26
+ el.classList.remove('is-invalid');
27
+ } else {
28
+ el.classList.add('is-invalid');
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 此指令用於當 Yup Schema 驗證失敗時,顯示錯誤樣式 'is-invalid'。
34
+ */
35
+ export const CFormFieldErrorOnYupDirective: Directive = {
36
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
37
+ addErrorClassIfNeedOnYup(el, binding.value as Schema);
38
+ },
39
+ updated(el: HTMLElement, binding: DirectiveBinding) {
40
+ addErrorClassIfNeedOnYup(el, binding.value as Schema);
41
+ },
42
+ }
43
+
44
+ function addErrorClassIfNeedOnYup(el: HTMLElement, schema: Schema) {
45
+ if(_.isNil(schema) || !_.isFunction(schema.validateSync)) {
46
+ el.classList.remove('is-invalid');
47
+ return;
48
+ }
49
+
50
+ try {
51
+ const val = (el as HTMLInputElement).value || '';
52
+ schema.validateSync(val);
53
+ el.classList.remove('is-invalid');
54
+ } catch (err) {
55
+ el.classList.add('is-invalid');
56
+ }
57
+ }
@@ -0,0 +1,102 @@
1
+ import {Directive, DirectiveBinding} from "vue";
2
+ import _ from "lodash";
3
+ import {UserSessionActions} from "../stores/ViewStore";
4
+
5
+ /**
6
+ * 此指令用於根據使用者權限來控制元素的顯示與否。
7
+ * 使用方式:v-permission="{need: ['PERMISSION_1', 'PERMISSION_2'], granted: userPermissionsArray, enableVisible: true}"
8
+ * 當使用者擁有其中一個權限時,元素將會顯示,否則隱藏。
9
+ * 根據 enableVisible 參數決定是隱藏元素還是移除元素,true 為隱藏 (使用 d-none),false 為移除元素。
10
+ */
11
+ export const PermissionDirective: Directive = {
12
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
13
+ const {
14
+ need,
15
+ granted,
16
+ enableVisible = false
17
+ } = binding.value as { need: string[] | string, granted: string[], enableVisible?: boolean};
18
+ const executeShow = (needToShow: boolean) => {
19
+ if(enableVisible) {
20
+ el.classList.toggle('d-none', !needToShow);
21
+ return;
22
+ }
23
+ if(!needToShow) {
24
+ el.remove();
25
+ }
26
+ }
27
+ const neededPermissions = Array.isArray(need) ? need : [need];
28
+ if(_.isEmpty(neededPermissions)) {
29
+ executeShow(false);
30
+ return;
31
+ }
32
+ if(_.isEmpty(granted)) {
33
+ executeShow(false);
34
+ return;
35
+ }
36
+ const hasPermission = _.some(neededPermissions, perm => _.includes(granted, perm));
37
+ if(!hasPermission) {
38
+ executeShow(false);
39
+ } else {
40
+ executeShow(true);
41
+ }
42
+ },
43
+ updated(el: HTMLElement, binding: DirectiveBinding) {},
44
+ }
45
+
46
+
47
+ /**
48
+ * 此指令用於根據使用者權限來控制元素的顯示與否,使用 UserSessionActions 來檢查權限。
49
+ * 使用方式:v-store-permission="{sessionStore: userSessionStore, need: ['PERMISSION_1', 'PERMISSION_2'], enableVisible: true}"
50
+ * 當使用者擁有其中一個權限時,元素將會顯示,否則隱藏。
51
+ * 根據 enableVisible 參數決定是隱藏元素還是移除元素,true 為隱藏 (使用 d-none),false 為移除元素。
52
+ */
53
+ export const StorePermissionDirective: Directive = {
54
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
55
+ const {
56
+ sessionStore,
57
+ need,
58
+ enableVisible = false
59
+ } = binding.value as { sessionStore: UserSessionActions, need: string[] | string, enableVisible?: boolean};
60
+ const executeShow = (needToShow: boolean) => {
61
+ if(enableVisible) {
62
+ el.classList.toggle('d-none', !needToShow);
63
+ return;
64
+ }
65
+ if(!needToShow) {
66
+ el.remove();
67
+ }
68
+ }
69
+ const neededPermissions = Array.isArray(need) ? need : [need];
70
+ executeShow(sessionStore.hasPermission(neededPermissions));
71
+ },
72
+ updated(el: HTMLElement, binding: DirectiveBinding) {},
73
+ }
74
+
75
+ // 在外部定義一個 Map
76
+ const nodeMap = new WeakMap<HTMLElement, Node>();
77
+
78
+ /**
79
+ * 建立一個權限指令,使用提供的 UserSessionActions 來檢查權限。
80
+ * @param options
81
+ */
82
+ export const createPermissionDirectives = (options: { sessionStore?: UserSessionActions }) => {
83
+ return {
84
+ mounted(el: HTMLElement, binding: DirectiveBinding) {
85
+ const need = binding.value as string[] | string;
86
+ const sessionStore = options.sessionStore;
87
+ if (!sessionStore) {
88
+ return;
89
+ }
90
+ const hasPermission = sessionStore.hasPermission(need);
91
+ if (!hasPermission) {
92
+ // 建立一個空的註釋節點來取代原有的 DOM
93
+ const comment = document.createComment('v-permission-hidden');
94
+ // 儲存在 Map 中,如果未來需要還原,可以從 nodeMap 取回
95
+ nodeMap.set(el, comment);
96
+ el.parentNode?.replaceChild(comment, el);
97
+ }
98
+ },
99
+ updated(el: HTMLElement, binding: DirectiveBinding) {
100
+ },
101
+ };
102
+ }
package/src/env.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ interface ImportMetaEnv {
2
+ readonly VITE_CLOUDFLARE_TURNSTILE_SITE_KEY: string
3
+ readonly VITE_API_PROXY_ENABLE: string;
4
+ readonly VITE_API_BASE_URL: string;
5
+ readonly VITE_API_PROXY_BASE_URL: string;
6
+ readonly VITE_API_CONTEXT_PATH: string;
7
+ readonly VITE_TINYMCE_API_KEY: string;
8
+ readonly VITE_MAX_FILE_SIZE: number;
9
+ }
10
+
11
+ interface ImportMeta {
12
+ readonly env: ImportMetaEnv
13
+ }
14
+
15
+ declare module "*.vue" {
16
+ import { DefineComponent } from "vue";
17
+ const component: DefineComponent<{}, {}, any>;
18
+ export default component;
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,83 @@
1
+ // ~ ----------------------------------------------------------
2
+ // ~ VUE Directive Exports
3
+ export {vdTooltip, vdCBSDropdown, vdCBSModal} from './directive/CBootstrapDirective';
4
+ export {vdDateFormatter} from './directive/CDateFormatterDirective';
5
+ export {CFTurnstileDirective} from './directive/CFTurnstileDirective';
6
+ export {CFormFieldErrorOnYupDirective, CFormFieldErrorStyleDirective} from './directive/CFormDirective';
7
+ export {PermissionDirective, StorePermissionDirective, createPermissionDirectives} from './directive/PermissionDirective';
8
+
9
+ // ~ ----------------------------------------------------------
10
+ // ~ Model Exports
11
+ export {CBSModalViewModel, ICBSModalViewType} from './model/CBSModalViewModel';
12
+ export {BaseFormDataModel, hasFormFieldError} from './model/BaseFormDataModel';
13
+ export * from './model/BaseListViewModel';
14
+ export * from './model/BSFieldStyleConfig';
15
+ export {CFileDataModel} from './model/CFileDataModel';
16
+ export {CImageViewModel} from './model/CImageViewModel';
17
+ export {EmailReceiverDataModel} from './model/EmailReceiverDataModel';
18
+ export {EmptyDataModel} from './model/EmptyDataModel';
19
+ export {LoginDataModel} from './model/LoginDataModel';
20
+ export {PasswordDataModel} from './model/PasswordDataModel';
21
+ export {Role, SessionUser} from './model/SessionUser';
22
+ export * from './model/TokenUser';
23
+ export * from './model/QueryParameter';
24
+ export * from './model/FormOptions';
25
+ export * from './model/ShowMessageDataModel';
26
+
27
+ // ~ ----------------------------------------------------------
28
+ // ~ Utils Exports
29
+ export * from './utils/CToolUtils';
30
+
31
+ // ~ ----------------------------------------------------------
32
+ // ~ Auth Exports
33
+ export {CMenuItem} from './model/CMenuItem';
34
+ export {AuthorizationService} from './auth/AuthorizationService';
35
+ export * from './auth/PermissionDescriptor';
36
+
37
+ // ~ ----------------------------------------------------------
38
+ // ~ API Exports
39
+ export * from './api/ApiService';
40
+
41
+ // ~ ----------------------------------------------------------
42
+ // ~ Store Exports
43
+ export * from './model/BaseDictionary';
44
+ export * from './stores/ViewStore';
45
+ import HasPermission from "./components/HasPermission.vue";
46
+ import VueSessionStoreInstaller from './stores/VueSessionStoreInstaller';
47
+ export {HasPermission, VueSessionStoreInstaller};
48
+
49
+ // ~ ----------------------------------------------------------
50
+ // ~ VUE component Exports
51
+ export * from './components/CAlertDefine';
52
+ import CAlert from './components/CAlert.vue';
53
+ import CBSToast from './components/CBSToast.vue';
54
+ import CGlobalSpinner from "./components/CGlobalSpinner.vue";
55
+ import CChangePasswordFormField from "./components/form/CChangePasswordFormField.vue";
56
+ import CCheckboxFormField from "./components/form/CCheckBoxFormField.vue";
57
+ import CDateFormField from "./components/form/CDateFormField.vue";
58
+ import CDateQueryField from "./components/form/CDateQueryField.vue";
59
+ import CDateRangeFormField from "./components/form/CDateRangeFormField.vue";
60
+ import CFilePickerFormField from "./components/form/CFilePickerFormField.vue";
61
+ import CRadioFormField from "./components/form/CRadioFormField.vue";
62
+ import CSelectFormField from "./components/form/CSelectFormField.vue";
63
+ import CTextAreaFormField from "./components/form/CTextAreaFormField.vue";
64
+ import CTextInputFormField from "./components/form/CTextInputFormField.vue";
65
+ import CRadioPlatFormField from "./components/form/CRadioPlatFormField.vue";
66
+ import CCheckBoxPlatFormField from "./components/form/CCheckBoxPlatFormField.vue";
67
+
68
+ // ~ ----------------------------------------------------------
69
+ // ~ Table component Exports
70
+ import CTable from "./components/CTable.vue";
71
+ import CTableTD from "./components/CTableTD.vue";
72
+ export * from "./components/CTableDefine";
73
+
74
+ export {
75
+ CAlert, CBSToast, CGlobalSpinner,
76
+ CChangePasswordFormField, CCheckboxFormField, CDateFormField,
77
+ CDateQueryField, CDateRangeFormField, CFilePickerFormField,
78
+ CRadioFormField, CSelectFormField, CTextAreaFormField,
79
+ CRadioPlatFormField, CCheckBoxPlatFormField, CTextInputFormField,
80
+ CTable, CTableTD
81
+ };
82
+
83
+