ch3chi-commons-vue 0.1.0 → 0.1.2
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.
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { Dropdown, Tooltip } from 'bootstrap';
|
|
2
|
+
import { CBSModalViewModel } from "../model/CBSModalViewModel";
|
|
3
|
+
/**
|
|
4
|
+
* v-tooltip 指令
|
|
5
|
+
* 初始化 Bootstrap 的 Tooltip
|
|
6
|
+
* 使用方式:<button v-tooltip>Hover me</button>
|
|
7
|
+
*/
|
|
8
|
+
export const vdTooltip = {
|
|
9
|
+
mounted(el) {
|
|
10
|
+
new Tooltip(el);
|
|
11
|
+
},
|
|
12
|
+
unmounted(el) {
|
|
13
|
+
const tooltip = Tooltip.getInstance(el);
|
|
14
|
+
if (tooltip)
|
|
15
|
+
tooltip.dispose();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* v-cbs-modal 指令
|
|
20
|
+
* binding.value 必須是 CBSModalViewModel 的實例
|
|
21
|
+
*/
|
|
22
|
+
export const vdCBSModal = {
|
|
23
|
+
mounted(elt, binding) {
|
|
24
|
+
if (!(binding.value instanceof CBSModalViewModel)) {
|
|
25
|
+
console.error('vdCBSModal directive requires binding.value to be an instance of CBSModalView');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const viewModel = binding.value;
|
|
29
|
+
viewModel.init({ elt });
|
|
30
|
+
},
|
|
31
|
+
unmounted(elt, binding) {
|
|
32
|
+
if (!(binding.value instanceof CBSModalViewModel)) {
|
|
33
|
+
console.error('vdCBSModal directive requires binding.value to be an instance of CBSModalView');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// 清理 Modal 實例
|
|
37
|
+
const viewModel = binding.value;
|
|
38
|
+
viewModel.unmount();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
// 當點擊其他地方時,隱藏 Dropdown
|
|
42
|
+
function hideDropdown(el, event) {
|
|
43
|
+
// 如果點擊的不是 Dropdown 元素或其子元素,則隱藏 Dropdown
|
|
44
|
+
if (!el.contains(event.target)) {
|
|
45
|
+
Dropdown.getInstance(el)?.hide();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* v-cbs-dropdown 指令
|
|
51
|
+
* 初始化 Bootstrap 的 Dropdown
|
|
52
|
+
*/
|
|
53
|
+
export const vdCBSDropdown = {
|
|
54
|
+
mounted(el) {
|
|
55
|
+
// 初始化 Bootstrap 的 Dropdown
|
|
56
|
+
const dropdown = Dropdown.getOrCreateInstance(el);
|
|
57
|
+
// click toggle dropdown
|
|
58
|
+
el.addEventListener('click', (event) => {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
// 顯示 Dropdown
|
|
62
|
+
dropdown.toggle();
|
|
63
|
+
return false;
|
|
64
|
+
});
|
|
65
|
+
const docListener = hideDropdown.bind(null, el);
|
|
66
|
+
// 點擊其他地方關閉 Dropdown
|
|
67
|
+
document.addEventListener('click', docListener);
|
|
68
|
+
},
|
|
69
|
+
unmounted(el) {
|
|
70
|
+
// 清理 Dropdown 實例
|
|
71
|
+
const dropdown = Dropdown.getInstance(el);
|
|
72
|
+
dropdown?.dispose();
|
|
73
|
+
const docListener = hideDropdown.bind(null, el);
|
|
74
|
+
// 移除點擊事件監聽器
|
|
75
|
+
document.removeEventListener('click', docListener);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import { isRef } from 'vue';
|
|
3
|
+
function formatDateToElement(el, value) {
|
|
4
|
+
let date;
|
|
5
|
+
if (isRef(value)) {
|
|
6
|
+
date = value.value;
|
|
7
|
+
}
|
|
8
|
+
else if (value instanceof Date) {
|
|
9
|
+
date = value;
|
|
10
|
+
}
|
|
11
|
+
// 如果 el 有 data-date-format 屬性,則使用該格式化方式
|
|
12
|
+
const dateFormat = el.getAttribute('data-date-format') || 'YYYY-MM-DD';
|
|
13
|
+
const formatted = date ? dayjs(date).format(dateFormat) : '';
|
|
14
|
+
if (el.tagName === 'INPUT' && el.type === 'text') {
|
|
15
|
+
el.value = formatted;
|
|
16
|
+
}
|
|
17
|
+
else if (['SPAN', 'P', 'DIV'].includes(el.tagName)) {
|
|
18
|
+
el.innerHTML = formatted;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* v-date-formatter 指令
|
|
23
|
+
* 解析 ref<Date> 並格式化為文字,根據元素型態輸出
|
|
24
|
+
*/
|
|
25
|
+
export const vdDateFormatter = {
|
|
26
|
+
mounted(el, binding) {
|
|
27
|
+
formatDateToElement(el, binding.value);
|
|
28
|
+
},
|
|
29
|
+
updated(el, binding) {
|
|
30
|
+
formatDateToElement(el, binding.value);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as _ from 'lodash';
|
|
2
|
+
export const CFTurnstileDirective = {
|
|
3
|
+
mounted(el, binding) {
|
|
4
|
+
const fieldContext = binding.value;
|
|
5
|
+
const siteKey = _.get(import.meta.env, 'VITE_CLOUDFLARE_TURNSTILE_SITE_KEY');
|
|
6
|
+
if (!siteKey) {
|
|
7
|
+
console.error('Cloudflare Turnstile site key is not defined.');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const renderTurnstile = () => {
|
|
11
|
+
window.turnstile?.render(el, {
|
|
12
|
+
sitekey: siteKey,
|
|
13
|
+
callback: (token) => {
|
|
14
|
+
fieldContext.value.value = token;
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
if (window.turnstile) {
|
|
19
|
+
renderTurnstile();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const interval = setInterval(() => {
|
|
23
|
+
if (window.turnstile) {
|
|
24
|
+
clearInterval(interval);
|
|
25
|
+
renderTurnstile();
|
|
26
|
+
}
|
|
27
|
+
}, 100);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
/**
|
|
3
|
+
* 此指令用於當 FormField 有錯誤時,顯示錯誤樣式 'is-invalid'。
|
|
4
|
+
*/
|
|
5
|
+
export const CFormFieldErrorStyleDirective = {
|
|
6
|
+
mounted(el, binding) {
|
|
7
|
+
addErrorClassIfNeed(el, binding.value);
|
|
8
|
+
},
|
|
9
|
+
updated(el, binding) {
|
|
10
|
+
addErrorClassIfNeed(el, binding.value);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
// 此函式用於檢查 FieldContext 的錯誤狀態,並根據錯誤狀態添加或移除 'is-invalid' 類別。
|
|
14
|
+
function addErrorClassIfNeed(el, fieldContext) {
|
|
15
|
+
if (_.isNil(fieldContext) || !_.has(fieldContext, 'errors') || !_.isArray(fieldContext.errors.value)) {
|
|
16
|
+
el.classList.remove('is-invalid');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (_.isEmpty(fieldContext.errors.value)) {
|
|
20
|
+
el.classList.remove('is-invalid');
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
el.classList.add('is-invalid');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 此指令用於當 Yup Schema 驗證失敗時,顯示錯誤樣式 'is-invalid'。
|
|
28
|
+
*/
|
|
29
|
+
export const CFormFieldErrorOnYupDirective = {
|
|
30
|
+
mounted(el, binding) {
|
|
31
|
+
addErrorClassIfNeedOnYup(el, binding.value);
|
|
32
|
+
},
|
|
33
|
+
updated(el, binding) {
|
|
34
|
+
addErrorClassIfNeedOnYup(el, binding.value);
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
function addErrorClassIfNeedOnYup(el, schema) {
|
|
38
|
+
if (_.isNil(schema) || !_.isFunction(schema.validateSync)) {
|
|
39
|
+
el.classList.remove('is-invalid');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const val = el.value || '';
|
|
44
|
+
schema.validateSync(val);
|
|
45
|
+
el.classList.remove('is-invalid');
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
el.classList.add('is-invalid');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ref } from 'vue';
|
|
2
|
+
import { Modal } from 'bootstrap';
|
|
3
|
+
import { v4 as uuid } from 'uuid';
|
|
4
|
+
export var ICBSModalViewType;
|
|
5
|
+
(function (ICBSModalViewType) {
|
|
6
|
+
ICBSModalViewType["Info"] = "infoModal";
|
|
7
|
+
ICBSModalViewType["Form"] = "formModal";
|
|
8
|
+
ICBSModalViewType["Table"] = "tableModal";
|
|
9
|
+
})(ICBSModalViewType || (ICBSModalViewType = {}));
|
|
10
|
+
export class CBSModalViewModel {
|
|
11
|
+
modalInstance = ref(null);
|
|
12
|
+
elt = null;
|
|
13
|
+
type;
|
|
14
|
+
modalId;
|
|
15
|
+
labelId;
|
|
16
|
+
title;
|
|
17
|
+
config; // 額外的配置選項與 Modal config 一致
|
|
18
|
+
onOpen;
|
|
19
|
+
onClose;
|
|
20
|
+
constructor(data) {
|
|
21
|
+
this.type = data?.type || ICBSModalViewType.Info;
|
|
22
|
+
this.modalId = `${this.type}-${uuid()}`;
|
|
23
|
+
this.labelId = `${this.modalId}-label`;
|
|
24
|
+
this.title = data?.title || this.defaultTitle();
|
|
25
|
+
this.onOpen = data?.onOpen;
|
|
26
|
+
this.onClose = data?.onClose;
|
|
27
|
+
this.config = Object.assign({
|
|
28
|
+
backdrop: 'static',
|
|
29
|
+
keyboard: false
|
|
30
|
+
}, data?.config);
|
|
31
|
+
}
|
|
32
|
+
defaultTitle() {
|
|
33
|
+
switch (this.type) {
|
|
34
|
+
case ICBSModalViewType.Info:
|
|
35
|
+
return '資訊';
|
|
36
|
+
case ICBSModalViewType.Form:
|
|
37
|
+
return '請填寫表單';
|
|
38
|
+
case ICBSModalViewType.Table:
|
|
39
|
+
return '請選擇';
|
|
40
|
+
default:
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
show() {
|
|
45
|
+
this.modalInstance.value?.show();
|
|
46
|
+
}
|
|
47
|
+
hide() {
|
|
48
|
+
this.modalInstance.value?.hide();
|
|
49
|
+
}
|
|
50
|
+
init({ elt }) {
|
|
51
|
+
if (!elt) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.elt = elt;
|
|
55
|
+
this.modalInstance.value = new Modal(this.elt, {
|
|
56
|
+
...this.config
|
|
57
|
+
});
|
|
58
|
+
this.elt.addEventListener('show.bs.modal', () => {
|
|
59
|
+
this.onOpen?.();
|
|
60
|
+
});
|
|
61
|
+
this.elt.addEventListener('hidden.bs.modal', () => {
|
|
62
|
+
this.onClose?.();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
unmount() {
|
|
66
|
+
if (this.modalInstance.value) {
|
|
67
|
+
this.modalInstance.value.dispose();
|
|
68
|
+
this.modalInstance.value = null;
|
|
69
|
+
}
|
|
70
|
+
this.elt = null;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ch3chi-commons-vue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -12,9 +12,27 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc"
|
|
14
14
|
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"bootstrap": "^5.3.7",
|
|
17
|
+
"dayjs": ">=1.11.13",
|
|
18
|
+
"lodash": ">=4.17.21",
|
|
19
|
+
"uuid": ">=11.1.0",
|
|
20
|
+
"vee-validate": ">=4.15.1",
|
|
21
|
+
"vue": "^3.5.18",
|
|
22
|
+
"yup": ">=1.7.1"
|
|
23
|
+
},
|
|
15
24
|
"devDependencies": {
|
|
25
|
+
"@types/bootstrap": "^5.2.10",
|
|
26
|
+
"@types/lodash": "^4.17.20",
|
|
16
27
|
"@types/node": "^24.10.0",
|
|
17
|
-
"
|
|
28
|
+
"bootstrap": "^5.3.7",
|
|
29
|
+
"dayjs": "^1.11.13",
|
|
30
|
+
"lodash": "^4.17.21",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"uuid": "^11.1.0",
|
|
33
|
+
"vee-validate": "^4.15.1",
|
|
34
|
+
"vue": "^3.5.23",
|
|
35
|
+
"yup": "^1.7.1"
|
|
18
36
|
},
|
|
19
37
|
"prepublishOnly": [
|
|
20
38
|
"npm run build"
|