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,199 @@
1
+ import {ref, Ref} from "vue";
2
+ import _ from 'lodash';
3
+ import {writeVueRefValue} from "../utils/CToolUtils";
4
+ import {PermissionDescriptor} from "../auth/PermissionDescriptor";
5
+
6
+ /**
7
+ * MenuItem 介面用於描述左側選單項目結構
8
+ * name: 顯示名稱
9
+ * icon: 圖示 class 或路徑(可選)
10
+ * path: 路由路徑
11
+ * children: 子選單項目(可選,型別為 CMenuItem 陣列)
12
+ */
13
+ export interface MenuItem {
14
+ id: string
15
+ name: string;
16
+ icon?: string; // icon class name
17
+ fontIcon?: string; // awesome font icon name
18
+ path?: string;
19
+ children?: MenuItem[];
20
+ needRoles?: string[]; // 允許的權限列表
21
+ permission?: string | string[]; // 需要的權限
22
+ activePaths?: string[]; // 當下路徑匹配列表,當前路徑符合其中一個即視為 active
23
+
24
+ // 提供方法告知是否有子選單
25
+ hasChildren?: () => boolean;
26
+ }
27
+
28
+ /**
29
+ * CMenuItem 類別實現 MenuItem 介面
30
+ */
31
+ export class CMenuItem implements MenuItem {
32
+ id: string;
33
+ name: string;
34
+ icon?: string;
35
+ fontIcon?: string;
36
+ path?: string;
37
+ needRoles?: string[];
38
+ permission?: string | string[];
39
+ permissionDescriptors?: PermissionDescriptor[];
40
+ activePaths?: string[];
41
+ children?: CMenuItem[];
42
+ isOpen?: Ref<boolean>; // 是否展開子選單
43
+ isActive?: Ref<boolean>; // 是否為當前選中項目
44
+
45
+ constructor(params: MenuItem) {
46
+ this.id = params.id;
47
+ this.name = params.name;
48
+ this.icon = params.icon;
49
+ this.fontIcon = params.fontIcon;
50
+ this.path = params.path;
51
+ this.needRoles = params.needRoles;
52
+ this.permission = params.permission;
53
+ if(params.permission) {
54
+ if(_.isArray(params.permission)) {
55
+ this.permissionDescriptors = params.permission.map(perm => new PermissionDescriptor(perm));
56
+ } else {
57
+ this.permissionDescriptors = [new PermissionDescriptor(params.permission)];
58
+ }
59
+ }
60
+ this.activePaths = params.activePaths || [];
61
+ if(params.children) {
62
+ this.children = params.children.map(child => CMenuItem.parse(child));
63
+ this.isOpen = ref(false); // 初始化折疊狀態
64
+ }
65
+ this.isActive = ref(false); // 初始化選中狀態
66
+ }
67
+
68
+ /**
69
+ * 判斷是否有子選單項目
70
+ * @returns {boolean} 如果有子選單項目則返回 true,否則返回 false
71
+ */
72
+ hasChildren(): boolean {
73
+ // 使用 lodash
74
+ return _.isArray(this.children) && !_.isEmpty(this.children);
75
+ }
76
+
77
+ /**
78
+ * 使用 collapse 屬性來控制子選單的展開或收起狀態
79
+ * 這個方法可以在 Vue 模板中使用 v-bind 指令來
80
+ * 綁定到 collapse 屬性上,以實現子選單的折
81
+ */
82
+ useCollapseAttribute(): Record<string, any> {
83
+ if (!this.hasChildren()) return {};
84
+ const collapseId = this.useCollapseId();
85
+ return {
86
+ href: `#${collapseId}`,
87
+ role: 'button',
88
+ 'aria-expanded': 'false',
89
+ 'aria-controls': collapseId
90
+ };
91
+ }
92
+
93
+ /**
94
+ * 使用 collapseId 方法來生成唯一的折疊 ID
95
+ */
96
+ useCollapseId(): string {
97
+ return `collapse-sidebar-${this.id}`;
98
+ }
99
+
100
+ /**
101
+ * 使用 useClassName 方法來生成 CSS 類名
102
+ */
103
+ useClassNameForNavLink(): string {
104
+ let className = 'c-nav-link';
105
+ // 根據 isActive 狀態返回不同的 CSS 類名
106
+ if (this.isActive?.value) {
107
+ className += ' c-active';
108
+ }
109
+ return className;
110
+ }
111
+
112
+ /**
113
+ * 使用 useClassName 方法來生成 CSS 類名
114
+ */
115
+ useClassNameForLi(): string {
116
+ let className = '';
117
+ // 根據 isActive 狀態返回不同的 CSS 類名
118
+ if (this.isActive?.value) {
119
+ className += ' c-active';
120
+ }
121
+
122
+ // 如果有子選單項目,則返回展合狀態的 CSS 類名
123
+ if(this.hasChildren()) {
124
+ className += ' has-children';
125
+ }
126
+ return className;
127
+ }
128
+
129
+ /**
130
+ * 計算箭頭圖示的 CSS 類名
131
+ */
132
+ computeArrowClass(): string {
133
+ // 根據 isOpen 狀態返回不同的箭頭圖示類名
134
+ return this.isOpen?.value ? 'caret-down' : 'caret-left';
135
+ }
136
+
137
+ /**
138
+ * 計算折疊顯示的 CSS 類名
139
+ */
140
+ computeCollapseShowClass(): string{
141
+ return this.isOpen?.value ?'show' : '';
142
+ }
143
+
144
+ /**
145
+ * 計算折疊的 CSS 類名
146
+ * @returns {string} 返回折疊的 CSS 類名
147
+ */
148
+ collapseClassName(): string {
149
+ // 根據 isOpen 狀態返回不同的折疊類名
150
+ return this.isOpen?.value ? 'show' : '';
151
+ }
152
+
153
+ /**
154
+ * 檢查當前路徑是否與此選單項目的路徑匹配
155
+ * @param currentPath
156
+ */
157
+ checkCurrentPath(currentPath: string): boolean {
158
+ let matched = false;
159
+ // 檢查當前路徑是否與此選單項目的路徑匹配
160
+ if (this.path === currentPath) {
161
+ writeVueRefValue(this.isActive, true);
162
+ matched = true;
163
+ }
164
+ // 檢查當前路徑是否在 activePaths 列表中,符合規則為前綴匹配
165
+ if (!matched && this.activePaths) {
166
+ writeVueRefValue(this.isActive, _.some(this.activePaths, (p) => currentPath.startsWith(p)))
167
+ matched = this.isActive?.value || false;
168
+ }
169
+ // 如果有子選單,遞迴檢查子選單項目
170
+ if (this.children) {
171
+ for (const child of this.children) {
172
+ if (child.checkCurrentPath(currentPath)) {
173
+ writeVueRefValue(this.isActive, true);
174
+ writeVueRefValue(this.isOpen, true);
175
+ matched = true;
176
+ }
177
+ }
178
+ }
179
+ if(!matched) {
180
+ writeVueRefValue(this.isActive, false);
181
+ }
182
+ return matched;
183
+ }
184
+
185
+ /**
186
+ * 靜態方法用於從 MenuItem 物件解析出 CMenuItem 實例
187
+ * @param item {MenuItem} 要解析的 MenuItem 物件
188
+ * @return {CMenuItem} 返回解析後的 CMenuItem 實例
189
+ */
190
+ static parse(item: MenuItem): CMenuItem {
191
+ return new CMenuItem({
192
+ ...item,
193
+ children: item.children ? item.children.map(CMenuItem.parse) : []
194
+ })
195
+ }
196
+ }
197
+
198
+ // ~ ----------------------------------------------------------
199
+
@@ -0,0 +1,149 @@
1
+ import {BaseFormDataModel} from "./BaseFormDataModel";
2
+ import * as yup from "yup";
3
+ import _ from "lodash";
4
+ import {computed, ComputedRef} from "vue";
5
+
6
+ /**
7
+ * Email 收件人資料
8
+ */
9
+ export class EmailRecipientData extends BaseFormDataModel {
10
+ email: string
11
+ type: 'receiver' | 'cc' | 'bcc'
12
+
13
+ constructor(data?: Partial<EmailRecipientData>) {
14
+ super();
15
+ this.email = data?.email || '';
16
+ this.type = data?.type || 'receiver';
17
+ }
18
+
19
+ // ~ ----------------------------------------------------------
20
+
21
+ dataFieldNameList(): string[] {
22
+ return [
23
+ 'email',
24
+ 'type',
25
+ ];
26
+ }
27
+
28
+ initFormSchema() {
29
+ return yup.object({
30
+ email: yup.string().email('收件人 Email 格式不正確').required('收件人 Email 為必填項'),
31
+ type: yup.string().oneOf(['receiver', 'cc', 'bcc'], '收件人類型不正確').required('收件人類型為必填項'),
32
+ });
33
+ }
34
+
35
+ }
36
+
37
+ /**
38
+ * Email 收件人資料模型
39
+ *
40
+ */
41
+ export class EmailReceiverDataModel extends BaseFormDataModel {
42
+
43
+ recipientList: Array<EmailRecipientData> = []; // 收件人 Email 清單
44
+
45
+ // ~ ----------------------------------------------------------
46
+ // ~ constructor
47
+
48
+ constructor(data?: Partial<EmailReceiverDataModel>) {
49
+ super();
50
+ if (data) {
51
+ Object.assign(this, data);
52
+ }
53
+ if(_.isNil(this.recipientList)) {
54
+ this.recipientList = [];
55
+ }
56
+ }
57
+
58
+ // ~ ----------------------------------------------------------
59
+
60
+ dataFieldNameList(): string[] {
61
+ return [
62
+ 'recipientList',
63
+ ];
64
+ }
65
+
66
+ initFormSchema() {
67
+ return yup.object({
68
+ recipientList: yup.array().of(
69
+ yup.object().shape({
70
+ email: yup.string().email('收件人 Email 格式不正確').required('收件人 Email 為必填項'),
71
+ type: yup.string().oneOf(['receiver', 'cc', 'bcc'], '收件人類型不正確').required('收件人類型為必填項'),
72
+ })
73
+ ).min(1, '請至少提供一個收件人 Email')
74
+ });
75
+ }
76
+
77
+
78
+ toPayload(key?: string): Record<string, any> {
79
+ return {
80
+ recipientEmailList: _.chain(this.recipientList)
81
+ .map(item => _.trim(item.email))
82
+ .filter(email => !_.isEmpty(email))
83
+ .uniq()
84
+ .value(),
85
+ }
86
+ }
87
+
88
+ // ~ ----------------------------------------------------------
89
+
90
+ /**
91
+ * 收件人列表
92
+ * @return {ComputedRef<Array<EmailRecipientData>>}
93
+ */
94
+ dataList() : ComputedRef<Array<EmailRecipientData>> {
95
+ return computed(() => {
96
+ const value = _.get(this.formFieldMap, 'recipientList.value.value', []);
97
+ if(_.isArray(value)) {
98
+ return value;
99
+ }
100
+ return [];
101
+ });
102
+ }
103
+
104
+ /**
105
+ * 新增收件人
106
+ * @param data
107
+ */
108
+ add(data?: Partial<EmailRecipientData>) {
109
+ // field 更新
110
+ const field = this.recipientListField;
111
+ if(!field) {
112
+ return;
113
+ }
114
+ const newItem = new EmailRecipientData(data);
115
+ _.invoke((field.value.value as Array<EmailRecipientData>), 'push', newItem);
116
+ }
117
+
118
+ /**
119
+ * 刪除收件人
120
+ * @param index
121
+ */
122
+ remove(index: number) {
123
+ // field 更新
124
+ const field = this.recipientListField;
125
+ if(!field) {
126
+ return;
127
+ }
128
+ if(_.isArray(field.value.value) && index >= 0 && index < field.value.value.length) {
129
+ field.value.value.splice(index, 1);
130
+ }
131
+ }
132
+
133
+ /**
134
+ * 清空表單
135
+ */
136
+ clean() {
137
+ // field 清空
138
+ const field = this.recipientListField;
139
+ if(field) {
140
+ field.value.value = [];
141
+ }
142
+ }
143
+
144
+ // ~ ----------------------------------------------------------
145
+
146
+ get recipientListField() {
147
+ return _.get(this.formFieldMap, 'recipientList');
148
+ }
149
+ }
@@ -0,0 +1,25 @@
1
+ import * as yup from 'yup'
2
+ import {BaseFormDataModel} from "./BaseFormDataModel";
3
+
4
+ export class EmptyDataModel extends BaseFormDataModel{
5
+
6
+ // 空的數據模型,沒有任何屬性
7
+
8
+ constructor(data?: Partial<EmptyDataModel>) {
9
+ super();
10
+ if (data) {
11
+ Object.assign(this, data);
12
+ }
13
+ }
14
+
15
+ // 初始化表單驗證的 schema
16
+ initFormSchema() {
17
+ return yup.object({});
18
+ }
19
+
20
+ // 取得需要套用 form field 欄位名稱的列表
21
+ dataFieldNameList(): string[] {
22
+ return [];
23
+ }
24
+
25
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 表單選項相關的 TypeScript 接口定義
3
+ */
4
+
5
+ import * as _ from 'lodash';
6
+
7
+ // ~ ----------------------------------------------------------
8
+
9
+ /**
10
+ * COptionItem 接口定義
11
+ */
12
+ export interface COptionItem {
13
+ id: string; // 可選的唯一識別符
14
+ text: string;
15
+ value: string | number | boolean;
16
+ disabled?: boolean;
17
+ selected?: boolean; // 是否被選中
18
+ children?: CCOptionItem[]; // 可選的子選項,用於多層級選單
19
+ meta?: Record<string, any>; // 可選的額外元資料
20
+ }
21
+
22
+ /**
23
+ * COptionItem 類別實現
24
+ */
25
+ export class CCOptionItem implements COptionItem {
26
+ id: string = '';
27
+ text: string = '';
28
+ value: string | number | boolean = '';
29
+ disabled?: boolean = false;
30
+ selected?: boolean = false;
31
+ children?: CCOptionItem[] = []; // 可選的子選項,用於多層級選單
32
+ meta?: Record<string, any>; // 可選的額外元資料
33
+
34
+ constructor(data: Partial<COptionItem> = {}) {
35
+ Object.assign(this, data);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * DataCategory 資料類別
41
+ */
42
+ export class DataCategory {
43
+ slug?: string; // 類別標識符
44
+ name?: string; // 類別名稱
45
+
46
+ constructor(data: Partial<DataCategory> = {}) {
47
+ Object.assign(this, data);
48
+ }
49
+ }
50
+
51
+ // ~ ----------------------------------------------------------
52
+
53
+ /**
54
+ * 基本狀態選項
55
+ */
56
+ export const ToggleStatusOptions: COptionItem[] = [
57
+ { id: 'enable', text: '開放', value: true },
58
+ { id: 'disable', text: '關閉', value: false }
59
+ ];
60
+
61
+ /**
62
+ * 通用選項,適用當 value 為 boolean 時
63
+ */
64
+ export const CommonStatusOptions: COptionItem[] = [
65
+ { id: 'enable', text: '啟用', value: true },
66
+ { id: 'disable', text: '停用', value: false }
67
+ ];
68
+
69
+ /**
70
+ * 通用選項,適用當 value 為字串時
71
+ */
72
+ export const CommonStatusStrOptions: COptionItem[] = [
73
+ { id: 'enable', text: '啟用', value: 'ENABLED' },
74
+ { id: 'disable', text: '停用', value: 'DISABLED' }
75
+ ];
76
+
77
+ /**
78
+ * 是/否 選項
79
+ */
80
+ export const YesNoStatusOptions: COptionItem[] = [
81
+ { id: 'enable', text: '是', value: true },
82
+ { id: 'disable', text: '否', value: false }
83
+ ];
84
+
85
+ // ~ ----------------------------------------------------------
86
+
87
+ // 工具方法,根據 CommonStatusOptions 產生對應的 狀態標籤 HTML
88
+ export const OptionUtils = {
89
+ /**
90
+ * 根據 CommonStatusOptions 產生對應的 狀態標籤 HTML
91
+ * @param value 狀態值
92
+ */
93
+ makeCommonStatusLabelText: (value: string | boolean): string => {
94
+ const matchOption = CommonStatusOptions.find(option => option.value === value);
95
+ if(!matchOption) {
96
+ return '';
97
+ }
98
+ switch(matchOption) {
99
+ case CommonStatusOptions[0]: // 啟用
100
+ return `<span class="badge bg-success fs-6 py-2 px-3">啟用</span>`;
101
+ case CommonStatusOptions[1]: // 停用
102
+ return `<span class="badge bg-danger fs-6 py-2 px-3">停用</span>`;
103
+ default:
104
+ return matchOption.text; // 預設返回原始文本
105
+ }
106
+ },
107
+ }
108
+
109
+ // 防止 Vite/rollup tree-shake 掉 type export
110
+ export const __FormOptionsTypes__ = null as unknown as
111
+ COptionItem &
112
+ DataCategory;
@@ -0,0 +1,51 @@
1
+ import {BaseFormDataModel} from "./BaseFormDataModel";
2
+ import * as yup from "yup";
3
+ import {ObjectSchema} from "yup";
4
+ import _ from "lodash";
5
+
6
+ export class LoginDataModel extends BaseFormDataModel{
7
+
8
+ account?:string; // 帳號
9
+ password?:string; // 密碼
10
+ turnstileToken?:string; // turnstile token
11
+ enableCFTurnstile: boolean = false; // 是否啟用 Cloudflare Turnstile
12
+
13
+ constructor(data?: Partial<LoginDataModel>) {
14
+ super();
15
+ if(data) {
16
+ Object.assign(this, data);
17
+ }
18
+ }
19
+
20
+ dataFieldNameList(): string[] {
21
+ return [
22
+ "account",
23
+ "password",
24
+ "codeChallenge",
25
+ "turnstileToken"
26
+ ];
27
+ }
28
+
29
+ initFormSchema(): ObjectSchema<any> {
30
+ return yup.object({
31
+ account: yup.string()
32
+ .required("帳號為必填欄位"),
33
+ password: yup.string().required("密碼為必填欄位"),
34
+ codeChallenge: yup.string().nullable().optional(),
35
+ enableCFTurnstile: yup.boolean(),
36
+ turnstileToken: yup.string().when('enableCFTurnstile', {
37
+ is: true,
38
+ then: (schema) => schema.required("請完成驗證"),
39
+ otherwise: (schema) => schema.nullable().optional()
40
+ })
41
+ });
42
+ }
43
+
44
+ formToJsonData(): Record<string, any> {
45
+ return {
46
+ account : _.trim(this.account),
47
+ password : this.password,
48
+ ...(this.enableCFTurnstile ? { turnstileToken: this.turnstileToken } : {})
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,70 @@
1
+ import {BaseFormDataModel} from "./BaseFormDataModel";
2
+ import * as yup from "yup";
3
+ import {ObjectSchema, ObjectShape} from "yup";
4
+ import * as _ from "lodash";
5
+
6
+ /**
7
+ * 密碼數據模型
8
+ */
9
+ export class PasswordDataModel extends BaseFormDataModel {
10
+ oldPassword: string | null = null; // 舊密碼
11
+ newPassword: string | null = null; // 新密碼
12
+ confirmNewPassword: string | null = null; // 確認新密碼
13
+ requiredOldPassword: boolean = true; // 是否需要舊密碼
14
+
15
+ constructor(data?: Partial<PasswordDataModel>) {
16
+ super();
17
+ if (data) {
18
+ Object.assign(this, data);
19
+ }
20
+ }
21
+
22
+ dataFieldNameList(): string[] {
23
+ if(!this.requiredOldPassword) {
24
+ return ["newPassword", "confirmNewPassword"];
25
+ }
26
+ return ["oldPassword", "newPassword", "confirmNewPassword"];
27
+ }
28
+
29
+ initFormSchema(): ObjectSchema<any> {
30
+ const shema : ObjectShape = {
31
+ newPassword: yup.string()
32
+ .required('新密碼為必填項')
33
+ .max(100, '新密碼長度不可超過100個字')
34
+ .min(12, '新密碼長度至少為12個字符')
35
+ .matches(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[^\w\s]).+$/, '新密碼必須包含英數及符號'),
36
+ confirmNewPassword: yup.string()
37
+ .required('確認新密碼為必填項')
38
+ .max(100, '確認新密碼長度不可超過100個字')
39
+ .oneOf([yup.ref('newPassword')], '新密碼和確認新密碼必須相同')
40
+ };
41
+ if(this.requiredOldPassword) {
42
+ _.set(shema, 'requiredOldPassword', yup.boolean().nullable().optional());
43
+ _.set(shema, 'oldPassword', yup.string()
44
+ .when('requiredOldPassword', {
45
+ is: true,
46
+ then: s => s.required('舊密碼為必填項')
47
+ .max(100, '舊密碼長度不可超過100個字')
48
+ .min(12, '舊密碼長度至少為12個字符')
49
+ .matches(/^(?=.*[A-Za-z])(?=.*\d)(?=.*[^\w\s]).+$/, '舊密碼必須包含英數及符號'),
50
+ otherwise: s => s.notRequired()
51
+ })
52
+ );
53
+ }
54
+ return yup.object(shema);
55
+ }
56
+
57
+ toPayload(): Record<string, any> {
58
+ if(this.requiredOldPassword) {
59
+ return {
60
+ oldPassword: this.oldPassword,
61
+ newPassword: this.newPassword,
62
+ newPasswordConfirm: this.confirmNewPassword
63
+ }
64
+ }
65
+ return {
66
+ newPassword: this.newPassword,
67
+ newPasswordConfirm: this.confirmNewPassword
68
+ }
69
+ }
70
+ }