jobdone-shared-files 1.0.6 → 1.0.9

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.
@@ -25,6 +25,7 @@
25
25
  </div>
26
26
  <div class="nav-item navbar-line-item btn nav-link border-0 dropdown"
27
27
  :class="{'active': isActive(navLinkObj.moduleLink)}">
28
+ <!-- NOTE 自己的獨立AP 沒有list,目前因為隕石已被GeneralLink取代 -->
28
29
  <a :href="navLinkObj.url + projectId" class="dropdown-link" v-if="navLinkObj?.moduleLink?.selfIsApp">
29
30
  <span class="material-icons icon-18 me-2">{{ navLinkObj?.moduleLink?.icon }}</span>
30
31
  {{ navLinkObj?.moduleLink?.name }}
@@ -43,7 +44,7 @@
43
44
  </ul>
44
45
  </div>
45
46
  <div class="nav-item navbar-line-item btn nav-link border-0 dropdown"
46
- :class="{'active': isActive(item.clientId)}"
47
+ :class="{'active': isActive(item.id)}"
47
48
  v-for="item in navLinkObj.generalLink">
48
49
  <a :href="item.url + '?projectId=' + projectId" class="dropdown-link">
49
50
  <span class="material-icons icon-18 me-2">{{item.icon}}</span>
@@ -167,12 +168,15 @@
167
168
 
168
169
  const isActive = (target) => {
169
170
  if (target === null) {
171
+ // 專案資訊
170
172
  return target === null && !props.activeClientId;
171
173
  }
172
174
  if (typeof target === 'object') {
175
+ // 功能選單
173
176
  return !!props.activeClientId && (target.clientId === props.activeClientId || target.apps.some(x => x.clientId == props.activeClientId))
174
177
  }
175
178
  if (!!props.activeClientId && target == props.activeClientId) {
179
+ // 功能選單
176
180
  return true;
177
181
  }
178
182
  return false;
package/README.md CHANGED
@@ -1,52 +1,175 @@
1
- # 共用清單
2
- 如果要使用 vue 檔,需要直接引用 node_modules 裡的檔案,例如:
3
- `import OOXX from '../../node_modules/jobdone-shared-files/OOXX.vue';`
4
-
5
- ## Vue Components
6
-
7
- ### paginate
8
- 可設定尺寸:`container-class`
9
- - 小:`pagination-sm`
10
- - 中:預設
11
- - 大:`pagination-lg`
12
-
13
- ----
1
+
14
2
 
15
- ### tagEditor
16
- 標籤
17
-
18
- ----
19
-
20
- ### tree
21
- 尺寸:`size`
22
- - 小: `sm`
23
- - 中:預設
3
+ # **Vue Components**
4
+ 如果要使用 vue 檔,需要直接引用 node_modules 裡的檔案,例如:
5
+ ```
6
+ import OOXX from '../../node_modules/jobdone-shared-files/OOXX.vue';
7
+ ```
8
+ ### .
9
+ ## 01.ProjectManagement/projectNavbar - 專案資訊Navbar
24
10
 
25
- 背景色:`bgColor`
26
- - 預設用在灰色背景上
27
- - 用在白背景上:`white`
11
+ ### 需要傳入的值:
12
+ | 數值名稱 | 說明 | 必要 |
13
+ |------------------|--------------------------------------------------------------------------------|------|
14
+ | root-domain | 底層的 domain,傳入的值有無斜線皆可 | 是 |
15
+ | arrow-back-link | 離開專案的連結,一般來說是底層的 ProjectList | 是 |
16
+ | project-info | 可接受api url或物件,api僅支援Get請求,結構應為`{name: "", caseNo: "", caseNo: "",beginDT:"",endDT:"",approvedWorkingPeriod:0, extendToDT:"",extendDays:""}`| 是 |
17
+ | project-apps | 可接受api url或物件,api僅支援Get請求,結構去Copy底層API取得專案所屬Module的App連結(V2 TOP)| 是 |
18
+ | active-client-id | Navbar active的對象,請填入client id,不填預設active專案資訊| N |
19
+ | project-id | project id | 是 |
20
+ | user-id | 傳入 user 的帳號 id | 是 |
21
+ | user-picture | 傳入 user 的大頭貼 | 是 |
22
+ | log-out-link | 登出連結 | 是 |
23
+ ### .
24
+ ## 02.Paginate - 分頁
25
+ ### 範例
26
+ ```
27
+ <template>
28
+ <paginate
29
+ container-class="..."
30
+ :total-pages="..."
31
+ :current-page="..."
32
+ @to-page="...">
33
+ </paginate>
34
+ </template>
28
35
 
29
- ----
36
+ <script setup>
37
+ import paginate from 'jobdone-shared-files/paginate.vue';
38
+ <script>
39
+ ```
40
+ ### 可用參數
41
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
42
+ | --- | ----------------- | --------- | -------------- | ----------------------------------------- | --------------- |
43
+ | 1 | `container-class` | `String` | `''` | `' '` `'pagination-sm'` `'pagination-lg'` | 尺寸設定|
44
+ | 2 | `total-pages` | `Number` | `1` | 任意阿拉伯數字 | 總頁數 |
45
+ | 3 | `current-page` | `Number` | `1` | 任意阿拉伯數字 | 當前頁碼 |
46
+ | 4 | `page-range` | `Number` | `5` | 任意阿拉伯數字 | 最多顯示幾個頁碼 |
47
+ ### 可用方法
48
+ | # | 參數 Attribute | 說明 Description |
49
+ | --- | ----------------- | --------------- |
50
+ | 1 | `to-page` | 點選頁碼執行的動作 |
51
+ ### .
52
+ ## 03.TagEditor - 標籤
53
+ ### 範例
54
+ ```
55
+ <template>
56
+ <tagEditor
57
+ ref="tagEditorComponent"
58
+ :auto-complete-option='autoCompleteTagList'
59
+ @update-auto-complete-option="getTagListByKeyWord">
60
+ </tagEditor>
61
+ </template>
30
62
 
63
+ <script setup>
64
+ import tagEditor from 'jobdone-shared-files/tagEditor.vue';
65
+
66
+ <!-- autoComplete 功能 -->
67
+ const autoCompleteTagList = ref([]);
68
+ async function getTagListByKeyWord(keyword) {
69
+ autoCompleteTagList.value = await axios.get(...).data;
70
+ }
71
+
72
+ <!-- 取得建立的TAG值 -->
73
+ const tagEditorComponent = ref(null);
74
+ const tags = ref([])
75
+ functio getTag(){
76
+ tags.value = tagEditorComponent.value.tagAry
77
+ }
78
+ <script>
79
+ ```
80
+ ### 可用參數
81
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
82
+ | --- | ----------------- | --------- | -------------- | ----------------------------------------- | --------------- |
83
+ | 1 | `placeholder` | `String` | `'請在此處輸入標籤文字'` | 任意字串 | input的提示文字|
84
+ | 2 | `auto-complete` | `Boolean` | `true` | `true` `false` | 是否啟用AutoComplete,啟用的話必需傳入用於AutoComplete的Array |
85
+ | 3 | `auto-complete-option` | `Array` | `[]` | `['...','...','...']` | 僅接受字串陣列。 |
86
+ | 4 | `disabled` | `Boolean` | `false` | `true` `false` | 停用 |
87
+ ### 可用方法
88
+ | # | 參數 Attribute | 說明 Description |
89
+ | --- | ----------------- | --------------- |
90
+ | 1 | `update-auto-complete-option` | 文字輸入時呼叫,value為當前輸入的值,可用於刷新autoCompleteOption。 |
91
+ ### .
92
+ ## 04.Tree - 樹狀結構
93
+ ### 範例
94
+ ```
95
+ <template>
96
+ <tree :items="items" :click-item="clickItem" size="sm"></tree>
97
+ </template>
31
98
 
32
- ### **VueLoadingOverlay**
33
- 指定樣式的vue-loading-overlay
99
+ <script setup>
100
+ import tree from 'jobdone-shared-files/tree.vue';
101
+ const items = [
102
+ {
103
+ id:1,
104
+ name:'分類A',
105
+ children{
106
+ id:11,
107
+ title:'分類A'-1,
108
+ children:[
109
+ ...
110
+ ]
111
+ }
112
+ },{
113
+ id:2,
114
+ name:'分類B',
115
+ children{
116
+ id:12,
117
+ title:'分類B-1',
118
+ children:[
119
+ ...
120
+ ]
121
+ }
122
+ },
123
+ ...
124
+ ]
125
+
126
+ function clickItem(v){
127
+ console.log(v)
128
+ }
129
+ <script>
130
+ ```
131
+ ### 可用參數
132
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
133
+ | --- | ----------------- | --------- | -------------- | ----------------------------------------- | --------------- |
134
+ | 1 | `items` | `Array` | `[]` | 須包含Id,名稱必須為title,子層的Key必須為children | 尺寸設定|
135
+ | 2 | `size` | `String` | `''` | `''` `'sm'` | 內容陣列|
136
+ | 4 | `bgColor` | `String` | `''` | `''` `'white'` | 用於什麼樣子的背景色上,預設用在灰色背景上。用在白背景上可使用white模式|
137
+ ### 可用方法
138
+ | Function | 說明 Description |
139
+ | --------- | ------------------ |
140
+ | clickItem | 點選項目執行的方法 |
141
+ ### .
142
+ ## 05.VueLoadingOverlay - 指定樣式讀取樣式
143
+ ### 範例
144
+ ```
145
+ <template>
146
+ <Loading :is-active="isLoading"></Loading>
147
+ </template>
148
+ <script setup>
149
+ import Loading from "jobdone-shared-files/vueLoadingOverlay.vue";
34
150
 
35
- * #### VueLoadingOverlay 如何使用
36
- 用法說明 `("isLoading" => 自定義變數)` :
37
- | 步驟 | 說明 | 範例 |
38
- | ---- | ------------------- | -------------------------------------------------------------------------------------- |
39
- | 1. | 在js或.vue中import | `import Loading from "../../node_modules/jobdone-shared-files/vueLoadingOverlay.vue";` |
40
- | 2. | 畫面中放上Component | `<Loading :is-active="isLoading"></Loading>` |
41
- | 3. | loading 初始化 | `const isLoading = ref(false);` |
42
- | 4. | 開啟loading | `isLoading.value = true;` |
43
- | 5. | 關閉loading | `isLoading.value = false;` |
44
- * #### VueLoadingOverlay 可用變數:
151
+ <!-- loading 初始化 "isLoading" => 自定義變數 -->
152
+ const isLoading = ref(false);
153
+
154
+ <!-- 開啟loading -->
155
+ function showLodaing(){
156
+ isLoading.value = true;
157
+ }
158
+
159
+ <!-- 關閉loading -->
160
+ function showLodaing(){
161
+ isLoading.value = false;
162
+ }
163
+
164
+ </script>
165
+ ```
166
+
167
+ ### 可用變數:
45
168
  | # | 參數 Attribute | 型別 Type | 預設值 Default | 說明Description |
46
169
  | --- | -------------- | --------- | -------------- | ---------------------------------------------------- |
47
170
  | 1. | full-page | `Boolean` | `true` | 是否填滿整個視窗。<br>為False時,則填滿放置位置的父層 |
48
171
 
49
- * #### VueLoadingOverlay 其他可用變數(統一樣式不推薦修改):
172
+ ### 其他可用變數(統一樣式不推薦修改):
50
173
 
51
174
  | # | 參數 Attribute | 型別 Type | 預設值 Default | 說明Description |
52
175
  | --- | ---------------- |:---------:|:--------------:|:------------------------------------------ |
@@ -54,74 +177,160 @@
54
177
  | 2. | loader | `String` | `bars` | 圖案樣式<br>spinner / dots / bars 三種可選 |
55
178
  | 3. | colors | `String` | `#fff` | 圖案顏色 |
56
179
  | 4. | background-color | `String` | `#000` | 遮罩顏色 |
57
- | 5. | opacity | `Number ` | `0.5` | 遮罩透明度 |
58
-
59
-
60
- ----
61
-
62
- ### ProjectManagement/projectNavbar
63
- 專案資訊 Navbar
64
-
65
- 需要傳入的值:
66
- | 數值名稱 | 說明 | 必要 |
67
- |------------------|--------------------------------------------------------------------------------|------|
68
- | root-domain | 底層的 domain,傳入的值有無斜線皆可 | 是 |
69
- | arrow-back-link | 離開專案的連結,一般來說是底層的 ProjectList | 是 |
70
- | project-info | 可接受api url或物件,api僅支援Get請求,結構應為`{name: "", caseNo: "", caseNo: "",beginDT:"",endDT:"",approvedWorkingPeriod:0, extendToDT:"",extendDays:""}`| 是 |
71
- | project-apps | 可接受api url或物件,api僅支援Get請求,結構去Copy底層API取得專案所屬Module的App連結(V2 TOP)| 是 |
72
- | active-client-id | Navbar active的對象,請填入client id,不填預設active專案資訊| N |
73
- | project-id | project id | 是 |
74
- | user-id | 傳入 user 的帳號 id | 是 |
75
- | user-picture | 傳入 user 的大頭貼 | 是 |
76
- | log-out-link | 登出連結 | 是 |
180
+ | 5. | opacity | `Number ` | `0.5` | 遮罩透明度 |
181
+ ### .
182
+ ## 06.lightboxWithOverview - 有縮圖的圖片光箱
77
183
 
184
+ ```
185
+ <lightbox-with-overview
186
+ :pictures-data="picturesData"
187
+ ref="lightboxWithOverviewRef">
188
+ </lightbox-with-overview>
189
+ ```
78
190
 
79
- ----
80
-
81
- ### lightboxWithOverview
82
- 有縮圖的圖片光箱
83
-
84
- `<lightbox-with-overview :pictures-data="picturesData" ref="lightboxWithOverviewRef"></lightbox-with-overview>`
85
-
86
- 可傳入的值:
191
+ ### 可傳入的值:
87
192
  | 數值名稱 | 說明 | 必要 |
88
193
  |------------------|-----------------------------------------------------------------|----|
89
194
  | pictures-data | 格式為: `[{src: "", title: ""}]` | 是 |
90
195
  | preview | 是否要顯示縮圖,可傳入 `true` or `false` | 否 |
91
196
  | display-index | 預設 index 為 0 | 否 |
92
- | preview-count | preview 的圖片陣列數量,預設為 5,可使用 `lightboxWithOverviewRef?.previewPictures` 與 `lightboxWithOverviewRef?.otherPictureCount` | 否 |
197
+ | preview-count | preview 的圖片陣列數量,預設為 5,可使用 `lightboxWithOverviewRef?.previewPictures` 與 `lightboxWithOverviewRef?.otherPictureCount` | 否 |
198
+ ### .
199
+ ## 07.autocompleteSelect - autocomplete下拉選單
200
+ ### 範例1:簡易型,String Array 的選單
201
+ ```
202
+ <template>
203
+ <autocomplete-select
204
+ :opts="opts"
205
+ :selected-data="selected"
206
+ @select="(v) => selected = v">
207
+ </autocomplete-select>
208
+ </template>
93
209
 
210
+ <script setup>
211
+ import autocompleteSelect from 'jobdone-shared-files/autocompleteSelect.vue';
212
+
213
+ const selected =''
214
+ const opts =['汪汪汪','喵喵喵','啾啾啾']
215
+ <script>
216
+ ```
217
+ ### 範例2:複雜型,Object Array 的選單
218
+ ```
219
+ <template>
220
+ <autocomplete-select
221
+ :selected-data="selected"
222
+ :opts="members"
223
+ :filter-keys="['firstName','lastName','enName']"
224
+ preview-key="enName + ' ( ' + lastName + ' ' + firstName + ' ) '"
225
+ binding-key="id"
226
+ @select="(v) => selected = v.id"
227
+ ></autocomplete-select>
228
+ </template>
229
+ <script setup>
230
+ import autocompleteSelect from 'jobdone-shared-files/autocompleteSelect.vue';
231
+
232
+ const selected = '1'
233
+ const members =[{
234
+ id:'1',
235
+ firstName:'美美',
236
+ lastName:'陳',
237
+ enName:'Amy'
238
+ },{
239
+ id:'2',
240
+ firstName:'大蒙',
241
+ lastName:'王',
242
+ enName:'Jed'
243
+ },{
244
+ ...
245
+ }]
246
+ <script>
247
+ ```
248
+ ### 範例3:自訂選單樣式()
249
+ ```
250
+ <template>
251
+ <autocomplete-select
252
+ :selected-data="selected"
253
+ :opts="members"
254
+ :filter-keys="['firstName','lastName','enName']"
255
+ preview-key="enName + ' ( ' + lastName + ' ' + firstName + ' ) '"
256
+ binding-key="id"
257
+ @select="(v) => selected = v.id"
258
+ :html-option='true'
259
+ >
260
+ <template #option = "{ optData }">
261
+ <div>我全要喵喵喵喵!!</div>
262
+ <div>
263
+ {{optData.phoneNumber}}
264
+ </div>
265
+ <div>
266
+ {{ `${optData.enName} (${ optData.firstName } ${ optData.lastName })` }}
267
+ </div>
268
+ </template>
269
+ </autocomplete-select>
270
+ </template>
271
+ <script setup>
272
+ import autocompleteSelect from 'jobdone-shared-files/autocompleteSelect.vue';
273
+
274
+ const selected = '1'
275
+ const members =[{
276
+ id:'1',
277
+ firstName:'美美',
278
+ lastName:'陳',
279
+ enName:'Amy',
280
+ phoneNumber:'0911111111',
281
+ },{
282
+ id:'2',
283
+ firstName:'大蒙',
284
+ lastName:'王',
285
+ enName:'Jed',
286
+ phoneNumber:'092222222',
287
+ },{
288
+ ...
289
+ }]
290
+ <script>
291
+ ```
292
+ ### 基本參數
293
+ >
294
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
295
+ | --- | ----------------- | --------- | -------------- | ----------------------------------------- | --------------- |
296
+ | 1 | `selected-data` | `String` `Number` `Object` | `none` | | 當前選擇的值(類似平常`vModel`的用法) |
297
+ | 2 | `opts` | `Array` | `[]` | | 選項列表,只接受`StringArray` `NumberArray` `ObjectArray`,且請陣列內容格
298
+ | 3 | `placeholder` | `String` | `'請選擇'` | | 未選擇內容時的提示文字 |
299
+ | 4 | `search-placeholder` | `String` | `''` | | 自定義搜尋框的提示文字。如未設定,有選擇內容時會將選擇的內容當成提示文字,而未選擇時則顯示placeholder(參數3)的值 |
300
+ | 5 | `trigger-class` | `String` | `''` | | 預覽框與輸入框的樣式CLSS,可用於驗證提示 |
301
+ | 6 | `list-put` | `String`| `'body'` | | 列表Dom放置位置 |
302
+
303
+ ### 清除按鈕參數
304
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
305
+ | --- | ----------------- | --------- | -------------- | ----------------------------------------- | --------------- |
306
+ | 7 | `clear-btn` | `Boolean` | `True` | `True` `False` |是否顯示清除按鈕 |
307
+ | 8 | `clear-placeholder` | `String` | `'清除選擇'` | |清除按鈕的文字 |
308
+ | 9 | `reset-value` | `String` `Number` `Object` | `''` | | 清空選擇替換的預設值 |
309
+ ### Object陣列選項清單用參數
310
+ | # | 參數 Attribute | 型別 Type | 預設值 Default | 選項 Option | 說明 Description |
311
+ | --- | ------------------- | --------- | ----------------------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
312
+ | 10 | `preview-key` | `String` | `''` | 顯示指定字符連接。請填入符合規定的字串 | 用+號連接keyName和自定義字串,自定義字串請用''包起來(空白同理)。範例`enName + ' ( ' + lastName + ' ' + firstName + ' ) ' ` |
313
+ | 11 | `filter-key` | `Array` | `['name']` | 字串陣列 | 傳入需要進行篩選比對的keyName,可多個。 |
314
+ | 12 | `binding-key` | `String` | `''` | 本參數僅供提傳入供單一且為唯一值得keyName,一般用於ID。 | 如傳入的選項列表為複雜object陣列,但選中的值是某個key。可傳入Key Name以供Component辨並從傳入的陣列中找出該object。如綁定的是整個object,不需使用此參數。 |
315
+ | 13 | `is-undefined-hint` | `String` | `'⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動` | 字串 | 傳入binding-key但比對失敗,在opts(參數2)中找不到binding-key內容與selected-data值相符的選項,用於提示的預覽文字 |
316
+ | 14 | `html-option` | `Boolean` | `false` | `true` `false` | 是否啟用自訂選項樣式,用法請參考範例3。請注意就算起啟用preview-key還是必須傳入。|
317
+ ### 可用方法
318
+ | Function | 說明 Description |
319
+ | --------- | ------------------ |
320
+ | select | 選到項目要做什麼 |
321
+
322
+ ### .
323
+ ### .
324
+ ### .
94
325
  ----
95
326
 
96
- ## SCSS
97
-
98
- ### Layout
99
- | Layout 名稱 | 說明 | 在 jobdone-shared-files 內? |
100
- |-------------------|-------------------------|-----------------------------|
101
- | LayoutBase | 客製化的 Bootstrap (5.3.0) | 是 |
102
- | LayoutProject | Project 使用的共用樣式,含開合雙欄樣式 | 是 |
103
- | LayoutSinglePage | 單頁版面的樣式,如:登入、錯誤頁、條款等 | 是 |
104
- | LayoutTwoColumn | 雙欄可開合樣式 | 是 |
105
- | LayoutMobile | 針對 (底層) 手機的共用樣式 | 是 |
106
- | LayoutDocs | 針對條文調整的樣式 | 否 |
107
- | LayoutInnerColumn | 針對 Project 雙欄樣式內的雙欄樣式 | 否 |
108
- | LayoutOutside | 針對 (底層) Project 外的共用樣式 | 否 |
109
-
110
- ### Common
111
-
112
- - Animation:目前只有 Fade In keyframe
113
- - SelectableTable:勾選 tr focus style
114
- - thumbnail-group:方形大頭貼與小大頭貼相疊 style
115
- - filepond:客製化 filepond 樣式
116
-
117
- ---
118
-
119
- # Directives
120
-
121
- ## SelectPlaceholder 使用範例:
327
+ # **Directives**
328
+ ## **01.SelectPlaceholder**
122
329
 
330
+ `<select>`未選擇值時加上Placeholder樣式
123
331
  `null` `false` `undefined` `''` 預設會被判斷為Placeholder,如有增減需求請參考option可用變數。
124
332
 
333
+ ### 使用範例:
125
334
  ```
126
335
  <template>
127
336
  <select class="form-select"
@@ -149,18 +358,17 @@
149
358
  </script>
150
359
  ```
151
360
 
152
- #### Option可用變數
361
+ ### 可用變數
153
362
  | # | 參數 Attribute | 型別 Type | 必填 required | 預設值 Default | 說明Description |
154
363
  | --- | ---------------- |:---------:|:-------------:|:----------------:|:---------------- |
155
364
  | 1 | value | -- | 是 | -- | 當前選擇內容的值 |
156
365
  | 2 | placeholderValue | `Array` | 否 | [] | 除了預設會被判斷為Placeholder的值,如有其他需求可增加。|
157
366
  | 3 | excludeValue | `Array` | 否 | [] | 如預設會被判斷為Placeholder的值,需被排除判斷,可在此處排除|
158
367
  | 4 | unselectClass | `String` | 否 | '' | 如在未選擇值時希望有更多特殊樣式,可在此傳入Class。不需要加. |
159
-
160
- ---
161
-
162
- ## textareaAutoHeight 使用範例:
163
-
368
+
369
+ ### .
370
+ ## 02.textareaAutoHeight
371
+ ### 使用範例:
164
372
  ```
165
373
  <template>
166
374
  <textarea v-textarea-auto-height v-model="inputValue"></textarea>
@@ -172,4 +380,33 @@
172
380
  const inputValue = ref(null)
173
381
 
174
382
  </script>
175
- ```
383
+ ```
384
+
385
+
386
+ ---
387
+
388
+ ## SCSS
389
+
390
+ ### Layout
391
+ | Layout 名稱 | 說明 | 在 jobdone-shared-files 內? |
392
+ |-------------------|-------------------------|-----------------------------|
393
+ | LayoutBase | 客製化的 Bootstrap (5.3.0) | 是 |
394
+ | LayoutProject | Project 使用的共用樣式,含開合雙欄樣式 | 是 |
395
+ | LayoutSinglePage | 單頁版面的樣式,如:登入、錯誤頁、條款等 | 是 |
396
+ | LayoutTwoColumn | 雙欄可開合樣式 | 是 |
397
+ | LayoutMobile | 針對 (底層) 手機的共用樣式 | 是 |
398
+ | LayoutDocs | 針對條文調整的樣式 | 否 |
399
+ | LayoutInnerColumn | 針對 Project 雙欄樣式內的雙欄樣式 | 否 |
400
+ | LayoutOutside | 針對 (底層) Project 外的共用樣式 | 否 |
401
+
402
+ ### Common
403
+
404
+ - Animation:目前只有 Fade In keyframe
405
+ - SelectableTable:勾選 tr focus style
406
+ - thumbnail-group:方形大頭貼與小大頭貼相疊 style
407
+ - filepond:客製化 filepond 樣式
408
+
409
+
410
+
411
+
412
+
@@ -0,0 +1,455 @@
1
+ <template>
2
+ <div class="autocomplete-select-component-trigger-content" ref="componentContentInput">
3
+ <!-- preview input -->
4
+ <button type="button" class='form-control autocomplete-select-component-display-selecting' :disabled="disabled"
5
+ :class="[triggerClass, previewInput]">{{
6
+ previewText
7
+ }}</button>
8
+ <!-- search input -->
9
+ <input class='form-control autocomplete-select-component-keyword-filter-input' :class="[triggerClass, searchInput]"
10
+ type="text" ref="keywordFilterInput" v-model="keyword"
11
+ :placeholder="searchPlaceholder == '' ? previewText : searchPlaceholder" maxlength="50"
12
+ @keydown.enter="keyboardSelectConfirm()" @keydown.up="keyboardSwitch(-1)" @keydown.down="keyboardSwitch(1)"
13
+ @change="keyboardSwitchIndexReset()">
14
+ <Teleport :to="listPut">
15
+ <div class="autocomplete-select-component-selector-content" :class="{ 'active': active }"
16
+ ref="componentContentList" :style="positionStyle">
17
+ <ul class="autocomplete-select-component-opt-list" ref="opt_list">
18
+ <li v-if="!listIsOnTop && clearBtn" class="border-bottom" :title="clearPlaceholder">
19
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
20
+ @click.stop="clearSelected()">
21
+ {{ clearPlaceholder }}
22
+ </button>
23
+ </li>
24
+ <li v-if="filterList?.length == 0">
25
+ <div class="autocomplete-select-component-opt-item is-nothing">
26
+ 沒有符合條件的選項
27
+ </div>
28
+ </li>
29
+ <template v-if="active">
30
+ <li v-for="(opt, idx) in filterList" :key="idx" @click.stop="selectConfirm(opt)" :title="opt.name" :id="`autocomplete-select-component-opt-item_${idx}`">
31
+ <button class="autocomplete-select-component-opt-item" type="button"
32
+ :class="idx == keyboardSwitchIndex ? 'active' : ''">
33
+ <template v-if="htmlOption">
34
+ <slot name="option" :optData="opt"></slot>
35
+ </template>
36
+ <template v-else>
37
+ {{ previewAdjust(opt) }}
38
+ </template>
39
+ </button>
40
+ </li>
41
+ </template>
42
+ <li v-if="listIsOnTop && clearBtn" class="border-top" :title="clearPlaceholder">
43
+ <button class="autocomplete-select-component-opt-item is-clear" type="button"
44
+ @click.stop="clearSelected()">
45
+ {{ clearPlaceholder }}
46
+ </button>
47
+ </li>
48
+ </ul>
49
+ </div>
50
+ </Teleport>
51
+ </div>
52
+ </template>
53
+ <script setup>
54
+ // enum Data & Functions
55
+
56
+ // vue & bootstrap
57
+ import { ref, onMounted, onUnmounted, computed, toRaw } from 'vue'
58
+
59
+ // plugins
60
+
61
+ // vue components
62
+
63
+ const props = defineProps({
64
+ selectedData: {
65
+ required: true
66
+ },
67
+ opts: {
68
+ type: Array,
69
+ default: []
70
+ },
71
+ placeholder: {
72
+ type: String,
73
+ default: '請選擇'
74
+ },
75
+ searchPlaceholder: {
76
+ type: String,
77
+ default: ''
78
+ },
79
+ triggerClass: {
80
+ type: String,
81
+ default: ''
82
+ },
83
+ listPut: {
84
+ type: String,
85
+ default: 'body'
86
+ },
87
+
88
+ clearBtn: {
89
+ type: Boolean,
90
+ default: true
91
+ },
92
+ clearPlaceholder: {
93
+ type: String,
94
+ default: '清除選擇'
95
+ },
96
+ resetValue: {
97
+ default: ''
98
+ },
99
+ disabled:{
100
+ type:Boolean,
101
+ default: false
102
+ },
103
+
104
+
105
+ previewKey: {
106
+ type: String,
107
+ default: '',
108
+ },
109
+ bindingKey: {
110
+ type: String,
111
+ default: '',
112
+ },
113
+ filterKeys: {
114
+ type: Array,
115
+ default: ['name'],
116
+ },
117
+ isUndefinedHint: {
118
+ type: String,
119
+ default: '⚠️ 未找到符合原選擇的選項,可能相關資料曾發生異動',
120
+ },
121
+ htmlOption: {
122
+ type: Boolean,
123
+ default: false
124
+ },
125
+
126
+
127
+ });
128
+
129
+ const emit = defineEmits(['select'])
130
+
131
+ // Just DOM
132
+ const componentContentInput = ref(null)
133
+ const componentContentList = ref(null)
134
+ const keywordFilterInput = ref(null)
135
+
136
+
137
+ // 是否開啟下拉選單
138
+ const active = ref(false)
139
+ const domPosition = ref({ bottom: 0, height: 0, left: 0, right: 0, top: 0, width: 0, x: 0, y: 0 })
140
+ function activeSelector() {
141
+ if(props.disabled){
142
+ return
143
+ }
144
+ active.value = true
145
+ keywordFilterInput.value.focus()
146
+ domPosition.value = componentContentInput.value.getBoundingClientRect()
147
+ }
148
+ const listIsOnTop = ref(false)
149
+ const positionStyle = computed(() => {
150
+ let screenHeight = window.innerHeight;
151
+ let info = toRaw(domPosition.value)
152
+ listIsOnTop.value = info.top >= (screenHeight - info.bottom)
153
+ let str = ''
154
+ if (listIsOnTop.value) {
155
+ str = `bottom:${screenHeight - info.top}px;max-height:calc(${info.top}px - 10%);`
156
+ } else {
157
+ str = `top:${info.bottom}px;max-height:calc(${screenHeight - info.bottom}px - 10%);`
158
+ }
159
+ return `${str}left:${info.x}px;max-width:calc(90% - ${info.x}px);min-width:${Math.abs(info.x - info.right)}px;`
160
+ })
161
+
162
+ function leave() {
163
+ listIsOnTop.value = false
164
+ active.value = false
165
+ keyword.value = ''
166
+ // TODO SCROLL CLOSE
167
+ keyboardSwitchIndexReset()
168
+ keywordFilterInput.value.blur()
169
+ }
170
+
171
+
172
+
173
+ // 確認傳入選單列表內的選項Type,只檢查第一個,請內容統一,不要傳奇怪的東西進來
174
+ const allowOptItemType = ['string', 'number', 'object']
175
+ const optItemType = computed(() => {
176
+ if (!Array.isArray(props.opts) || props.opts?.length < 1) {
177
+ return ''
178
+ }
179
+ let sampling = props.opts[0]
180
+ let type = typeof sampling
181
+ if (!allowOptItemType.includes(type)) {
182
+ return ''
183
+ }
184
+ if ((sampling instanceof Date) && (sampling instanceof RegExp) && Array.isArray(sampling)) {
185
+ return ''
186
+ }
187
+ return type?.toLowerCase()
188
+
189
+ })
190
+
191
+ //
192
+ // 篩選
193
+ const keyword = ref("")
194
+ const filterList = computed(() => {
195
+ if (optItemType.value === '') {
196
+ return []
197
+ }
198
+ if (keyword.value == '') {
199
+ return props.opts
200
+ }
201
+ let kwReplace = keyword.value.replace(/[.*+?^${}()|[\]\\]/g, "");
202
+ let regex = new RegExp(kwReplace, "i");
203
+ if (optItemType.value === 'object') {
204
+ let final = props.opts.filter(i => {
205
+ for (var idx = 0; idx < props.filterKeys.length; idx++) {
206
+ if (regex.test(i[props.filterKeys[idx].toString()])) {
207
+ return i
208
+ };
209
+ }
210
+ })
211
+ return final
212
+ }
213
+ return props.opts.filter(i => {
214
+ if (regex.test(i)) {
215
+ return i
216
+ }
217
+ })
218
+ })
219
+
220
+ // 從選項列表中挖出整個選中的item。因選中的值(可能是某個key),挖不到就直接吐props的值出來。
221
+ const selectedItem = computed(() => {
222
+ if (!props.selectedData) {
223
+ return
224
+ }
225
+
226
+ // 如果選中的值是某個Key,需自行指定bindingKey
227
+ if (props.bindingKey !== '') {
228
+ return props.opts.find(i => i[props.bindingKey.toString()] == props.selectedData) ?? props.selectedData
229
+ }
230
+
231
+ return props.selectedData
232
+ })
233
+
234
+
235
+ // 選項的展示 與 選中的值
236
+ function previewAdjust(itemObj) {
237
+ if (props.previewKey !== '') {
238
+ let strAry = props.previewKey.split('+')
239
+ let obj = toRaw(itemObj)
240
+ let isUndefined = false
241
+
242
+ let finalStr = strAry.reduce((acc, cur) => {
243
+ let v = cur.trim()
244
+ if (v.match(/'[^']+'/g)) {
245
+ return acc + v.substring(1, v.length - 1)
246
+ } else {
247
+ if (obj[v] === undefined) {
248
+ isUndefined = true
249
+ }
250
+ return acc + obj[v]
251
+ }
252
+ }, '')
253
+ if (isUndefined) {
254
+ return props.isUndefinedHint
255
+ }
256
+ return finalStr ? finalStr : itemObj
257
+ }
258
+ return itemObj
259
+ }
260
+
261
+ const previewText = computed(() => {
262
+ if (!selectedItem.value || Object.keys(selectedItem.value).length === 0) {
263
+ return props.placeholder
264
+ }
265
+ return previewAdjust(selectedItem.value)
266
+ })
267
+
268
+ // 清除選擇內容
269
+ function clearSelected() {
270
+ emit('select', props.resetValue)
271
+ leave()
272
+ }
273
+
274
+ // 選擇送出
275
+ function selectConfirm(v) {
276
+ if (!v) {
277
+ return
278
+ }
279
+ emit('select', JSON.parse(JSON.stringify(v)))
280
+ leave()
281
+ keyboardSwitchIndexReset()
282
+ }
283
+
284
+ // 鍵盤送出
285
+ function keyboardSelectConfirm() {
286
+ if (filterList.value?.length) {
287
+ selectConfirm(filterList.value[keyboardSwitchIndex.value])
288
+ }
289
+ }
290
+
291
+ // 鍵盤挑選
292
+ const keyboardSwitchIndex = ref(0)
293
+ function keyboardSwitch(v) {
294
+ if (filterList.value?.length < 1) {
295
+ keyboardSwitchIndexReset()
296
+ return
297
+ }
298
+ let idx = Number(keyboardSwitchIndex.value) + v
299
+
300
+ if (idx < 0) {
301
+ keyboardSwitchIndex.value = filterList.value.length - 1
302
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
303
+ return
304
+ }
305
+ if (idx > filterList.value.length - 1) {
306
+ keyboardSwitchIndexReset()
307
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
308
+ return
309
+ }
310
+ keyboardSwitchIndex.value = idx
311
+ document.getElementById(`autocomplete-select-component-opt-item_${keyboardSwitchIndex.value}`)?.scrollIntoView()
312
+ }
313
+
314
+ function keyboardSwitchIndexReset() {
315
+ keyboardSwitchIndex.value = 0
316
+ }
317
+
318
+ // 樣式
319
+ const previewInput = computed(() => {
320
+ if (previewText.value == props.placeholder) {
321
+ return 'autocomplete-select-component-value-null'
322
+ }
323
+ return ''
324
+ })
325
+ const searchInput = computed(() => {
326
+ if (active.value) {
327
+ return 'active'
328
+ }
329
+ return ''
330
+ })
331
+ function initTrigger() {
332
+ if (componentContentInput.value.contains(event.target) || componentContentList.value.contains(event.target)) {
333
+ activeSelector()
334
+ } else {
335
+ leave()
336
+ }
337
+ }
338
+ onMounted(() => {
339
+ window.addEventListener("click", initTrigger);
340
+ })
341
+
342
+ onUnmounted(() => {
343
+ window.removeEventListener("click", initTrigger);
344
+ })
345
+
346
+ </script>
347
+ <style lang="scss" scoped>
348
+ * {
349
+ word-break: break-word;
350
+ overflow-wrap: break-word;
351
+ }
352
+
353
+ .autocomplete-select-component-trigger-content {
354
+ position: relative;
355
+ width: 100%;
356
+
357
+ .form-control {
358
+ background-image: url("data:image/svg+xml,%3csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 16 16%27%3e%3cpath fill=%27none%27 stroke=%27%232D4155%27 stroke-linecap=%27round%27 stroke-linejoin=%27round%27 stroke-width=%272%27 d=%27m2 5 6 6 6-6%27/%3e%3c/svg%3e");
359
+ background-repeat: no-repeat;
360
+ background-position: right 0.75rem center;
361
+ background-size: 16px 12px;
362
+ text-align: start;
363
+ width: 100%;
364
+ }
365
+
366
+ .autocomplete-select-component-value-null {
367
+ color: var(--bs-secondary-color);
368
+ }
369
+
370
+ .autocomplete-select-component-display-selecting {
371
+ position: absolute;
372
+ top: 0;
373
+ left: 0;
374
+ z-index: 1;
375
+ white-space: nowrap;
376
+ overflow: hidden;
377
+ text-overflow: ellipsis;
378
+ }
379
+
380
+ .autocomplete-select-component-keyword-filter-input {
381
+ opacity: 0;
382
+ position: relative;
383
+
384
+ &.active {
385
+ opacity: 1;
386
+ z-index: 2;
387
+ }
388
+ }
389
+ }
390
+
391
+ .autocomplete-select-component-selector-content {
392
+ visibility: hidden;
393
+ display: none;
394
+ position: fixed;
395
+ z-index: 2000;
396
+ margin-top: 0.25rem;
397
+ margin-bottom: 0.25rem;
398
+ max-height: 76vh;
399
+ overflow: auto;
400
+
401
+ ul.autocomplete-select-component-opt-list {
402
+ background: #fff;
403
+ list-style: none;
404
+ border: 1px solid var(--gray-400);
405
+ border-radius: var(--bs-border-radius);
406
+ margin: 0;
407
+ padding: 0;
408
+ overflow: auto;
409
+ padding: .25rem 0;
410
+
411
+ >li {
412
+ width: 100%;
413
+ overflow: auto;
414
+ }
415
+
416
+ .autocomplete-select-component-opt-item {
417
+ background-color: transparent;
418
+ border: none;
419
+ padding: .25rem .5rem;
420
+ word-break: break-word;
421
+ width: 100%;
422
+ overflow: hidden;
423
+ text-align: start;
424
+ text-overflow: ellipsis;
425
+ color: var(--gray-600);
426
+
427
+ &.is-nothing {
428
+ color: var(--bs-danger);
429
+ }
430
+
431
+ &.is-clear {
432
+ color: var(--gray-500);
433
+ }
434
+
435
+ &:hover,
436
+ &.is-clear:hover {
437
+ background: rgba(var(--bs-primary-rgb), 1) !important;
438
+ color: #fff !important;
439
+ }
440
+
441
+ &.active {
442
+ background: rgba(var(--bs-primary-rgb), .1);
443
+ color: var(--bs-primary);
444
+ }
445
+ }
446
+ }
447
+
448
+ &.active {
449
+ display: block;
450
+ box-shadow: var(--bs-box-shadow);
451
+ visibility: visible;
452
+ z-index: 2000;
453
+ }
454
+ }
455
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jobdone-shared-files",
3
- "version": "1.0.6",
3
+ "version": "1.0.9",
4
4
  "description": "Shared JS and SCSS for Jobdone Enterprise.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/tagEditor.vue CHANGED
@@ -26,21 +26,7 @@
26
26
  </div>
27
27
  </template>
28
28
  <script>
29
- // enum Data & Functions
30
- // import { } from '../common/enum.js'
31
-
32
- // vue & bootstrap
33
29
  import { ref, reactive, onMounted, computed } from 'vue'
34
- // import { } from 'bootstrap';
35
-
36
- // plugins
37
- // import { v4 as uuidv4 } from 'uuid';
38
- // import { useVuelidate } from '@vuelidate/core'
39
- // import { required } from '@vuelidate/validators'
40
- // import { SwalSuccess, SwalConfirmDelete } from '../common/sweetalert.js';
41
-
42
- // vue components
43
- // import Loading from "./vueLoadingOverlay.vue";
44
30
 
45
31
  export default {
46
32
  props: {