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.
- package/package.json +2 -1
- package/src/api/ApiService.ts +869 -0
- package/src/auth/AuthorizationService.ts +138 -0
- package/src/auth/PermissionDescriptor.ts +99 -0
- package/src/auth/keys.ts +5 -0
- package/src/components/CAlert.vue +188 -0
- package/src/components/CAlertDefine.ts +20 -0
- package/src/components/CBSToast.vue +119 -0
- package/src/components/CGlobalSpinner.vue +84 -0
- package/src/components/CImage.vue +67 -0
- package/src/components/CRowCheckBox.vue +75 -0
- package/src/components/CRowTextInput.vue +27 -0
- package/src/components/CTable.vue +524 -0
- package/src/components/CTableDefine.ts +566 -0
- package/src/components/CTableTD.vue +28 -0
- package/src/components/HasPermission.vue +28 -0
- package/src/components/form/CChangePasswordFormField.vue +146 -0
- package/src/components/form/CCheckBoxFormField.vue +91 -0
- package/src/components/form/CCheckBoxPlatFormField.vue +94 -0
- package/src/components/form/CDateFormField.vue +149 -0
- package/src/components/form/CDateQueryField.vue +111 -0
- package/src/components/form/CDateRangeFormField.vue +138 -0
- package/src/components/form/CFilePickerFormField.vue +471 -0
- package/src/components/form/CRadioFormField.vue +62 -0
- package/src/components/form/CRadioPlatFormField.vue +67 -0
- package/src/components/form/CSelectFormField.vue +175 -0
- package/src/components/form/CTextAreaFormField.vue +84 -0
- package/src/components/form/CTextInputFormField.vue +99 -0
- package/src/components/form/CTinyMCEEditorFormField.vue +99 -0
- package/src/components/form/SCTextInputFormField.vue +129 -0
- package/src/composables/useCheckBoxFormField.ts +126 -0
- package/src/composables/useRadioFormField.ts +106 -0
- package/src/directive/CBootstrapDirective.ts +83 -0
- package/src/directive/CDateFormatterDirective.ts +37 -0
- package/src/directive/CFTurnstileDirective.ts +46 -0
- package/src/directive/CFormDirective.ts +57 -0
- package/src/directive/PermissionDirective.ts +102 -0
- package/src/env.d.ts +19 -0
- package/src/index.ts +83 -0
- package/src/model/BSFieldStyleConfig.ts +349 -0
- package/src/model/BaseDictionary.ts +86 -0
- package/src/model/BaseFormDataModel.ts +623 -0
- package/src/model/BaseListViewModel.ts +392 -0
- package/src/model/CBSModalViewModel.ts +91 -0
- package/src/model/CFileDataModel.ts +181 -0
- package/src/model/CImageViewModel.ts +34 -0
- package/src/model/CMenuItem.ts +199 -0
- package/src/model/EmailReceiverDataModel.ts +149 -0
- package/src/model/EmptyDataModel.ts +25 -0
- package/src/model/FormOptions.ts +112 -0
- package/src/model/LoginDataModel.ts +51 -0
- package/src/model/PasswordDataModel.ts +70 -0
- package/src/model/QueryParameter.ts +310 -0
- package/src/model/SessionUser.ts +110 -0
- package/src/model/ShowMessageDataModel.ts +69 -0
- package/src/model/TokenUser.ts +157 -0
- package/src/stores/FormDataStore.ts +73 -0
- package/src/stores/ViewStore.ts +701 -0
- package/src/stores/VueSessionStoreInstaller.ts +22 -0
- package/src/types/turnstile.d.ts +8 -0
- package/src/utils/CToolUtils.ts +133 -0
- package/dist/api/ApiService.d.ts +0 -233
- package/dist/auth/AuthorizationService.d.ts +0 -56
- package/dist/auth/PermissionDescriptor.d.ts +0 -37
- package/dist/components/CAlert.vue.d.ts +0 -17
- package/dist/components/CAlertDefine.d.ts +0 -14
- package/dist/components/CBSToast.vue.d.ts +0 -6
- package/dist/components/CGlobalSpinner.vue.d.ts +0 -13
- package/dist/components/CRowCheckBox.vue.d.ts +0 -14
- package/dist/components/CRowTextInput.vue.d.ts +0 -10
- package/dist/components/CTable.vue.d.ts +0 -24
- package/dist/components/CTableDefine.d.ts +0 -201
- package/dist/components/CTableTD.vue.d.ts +0 -7
- package/dist/components/form/CChangePasswordFormField.vue.d.ts +0 -14
- package/dist/components/form/CCheckBoxFormField.vue.d.ts +0 -30
- package/dist/components/form/CDateFormField.vue.d.ts +0 -17
- package/dist/components/form/CDateQueryField.vue.d.ts +0 -16
- package/dist/components/form/CDateRangeFormField.vue.d.ts +0 -17
- package/dist/components/form/CFilePickerFormField.vue.d.ts +0 -28
- package/dist/components/form/CRadioFormField.vue.d.ts +0 -30
- package/dist/components/form/CSelectFormField.vue.d.ts +0 -18
- package/dist/components/form/CTextAreaFormField.vue.d.ts +0 -16
- package/dist/components/form/CTextInputFormField.vue.d.ts +0 -22
- package/dist/directive/CBootstrapDirective.d.ts +0 -17
- package/dist/directive/CDateFormatterDirective.d.ts +0 -10
- package/dist/directive/CFTurnstileDirective.d.ts +0 -15
- package/dist/directive/CFormDirective.d.ts +0 -9
- package/dist/directive/PermissionDirective.d.ts +0 -15
- package/dist/index.cjs.js +0 -19103
- package/dist/index.d.ts +0 -45
- package/dist/index.es.js +0 -19086
- package/dist/model/BSFieldStyleConfig.d.ts +0 -121
- package/dist/model/BaseDictionary.d.ts +0 -34
- package/dist/model/BaseFormDataModel.d.ts +0 -199
- package/dist/model/BaseListViewModel.d.ts +0 -165
- package/dist/model/CBSModalViewModel.d.ts +0 -44
- package/dist/model/CFileDataModel.d.ts +0 -74
- package/dist/model/CImageViewModel.d.ts +0 -8
- package/dist/model/CMenuItem.d.ts +0 -86
- package/dist/model/EmailReceiverDataModel.d.ts +0 -57
- package/dist/model/EmptyDataModel.d.ts +0 -7
- package/dist/model/FormOptions.d.ts +0 -60
- package/dist/model/LoginDataModel.d.ts +0 -12
- package/dist/model/PasswordDataModel.d.ts +0 -15
- package/dist/model/QueryParameter.d.ts +0 -92
- package/dist/model/SessionUser.d.ts +0 -45
- package/dist/model/ShowMessageDataModel.d.ts +0 -44
- package/dist/model/TokenUser.d.ts +0 -50
- package/dist/stores/FormDataStore.d.ts +0 -31
- package/dist/stores/ViewStore.d.ts +0 -349
- package/dist/style.css +0 -223
- 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
|
+
|