evui 3.1.41 → 3.1.45

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.
@@ -4,10 +4,10 @@
4
4
  v-clickoutside="clickOutsideDropbox"
5
5
  class="ev-date-picker"
6
6
  :class="{
7
- disabled,
7
+ disabled : $props.disabled,
8
8
  }"
9
9
  >
10
- <template v-if="mode === 'date' || mode === 'dateTime'">
10
+ <template v-if="$props.mode === 'date' || $props.mode === 'dateTime'">
11
11
  <span class="ev-date-picker-prefix-icon">
12
12
  <i class="ev-icon-calendar" />
13
13
  </span>
@@ -15,15 +15,18 @@
15
15
  v-model.trim="currentValue"
16
16
  type="text"
17
17
  class="ev-input"
18
- :placeholder="placeholder"
19
- :disabled="disabled"
18
+ :placeholder="$props.placeholder"
19
+ :disabled="$props.disabled"
20
20
  @click="clickSelectInput"
21
21
  @keydown.enter.prevent="validateValue(currentValue)"
22
22
  @change="validateValue(currentValue)"
23
23
  />
24
24
  </template>
25
25
  <template v-else>
26
- <div class="ev-date-picker-tag-wrapper">
26
+ <div
27
+ class="ev-date-picker-tag-wrapper"
28
+ @click="clickSelectInput"
29
+ >
27
30
  <span class="ev-date-picker-prefix-icon">
28
31
  <i class="ev-icon-calendar" />
29
32
  </span>
@@ -31,23 +34,22 @@
31
34
  type="text"
32
35
  class="ev-input readonly"
33
36
  readonly
34
- :placeholder="placeholder"
35
- :disabled="disabled"
36
- @click="clickSelectInput"
37
+ :placeholder="$props.placeholder"
38
+ :disabled="$props.disabled"
37
39
  />
38
40
  <template
39
- v-if="mode === 'dateMulti'
40
- && (options.multiType === 'date' || !options.tagShorten)"
41
+ v-if="$props.mode === 'dateMulti'
42
+ && ($props.options.multiType === 'date' || !$props.options.tagShorten)"
41
43
  >
42
44
  <div
43
45
  v-for="(item, idx) in mv"
44
46
  :key="`${item}_${idx}`"
45
47
  class="ev-select-tag"
46
- :class="{ num: options.multiType !== 'date' }"
48
+ :class="{ num: $props.options.multiType !== 'date' }"
47
49
  >
48
50
  <span class="ev-tag-name"> {{ item }} </span>
49
51
  <span
50
- v-if="options.multiType === 'date'"
52
+ v-if="$props.options.multiType === 'date'"
51
53
  class="ev-tag-suffix"
52
54
  @click.stop="[removeMv(item), changeDropboxPosition()]"
53
55
  >
@@ -68,7 +70,7 @@
68
70
  </template>
69
71
  </div>
70
72
  </template>
71
- <template v-if="clearable">
73
+ <template v-if="$props.clearable">
72
74
  <span
73
75
  v-show="isClearableIcon"
74
76
  class="ev-input-suffix"
@@ -82,17 +84,38 @@
82
84
  v-if="isDropbox"
83
85
  ref="dropbox"
84
86
  class="ev-date-picker-dropdown"
85
- :class="mode"
87
+ :class="$props.mode"
86
88
  :style="dropboxPosition"
87
89
  >
88
- <ev-calendar
89
- key="fromCalendar"
90
- v-model="mv"
91
- :mode="mode"
92
- :month-notation="monthNotation"
93
- :day-of-the-week-notation="dayOfTheWeekNotation"
94
- :options="options"
90
+ <div
91
+ v-if="usedShortcuts.length"
92
+ class="ev-date-picker-dropbox__button-layout">
93
+ <ev-button-group>
94
+ <ev-button
95
+ v-for="button in usedShortcuts"
96
+ :key="button.key"
97
+ :type="button.isActive ? 'primary' : 'default'"
98
+ @click="clickShortcut(button.key)"
99
+ >
100
+ {{ button.label }}
101
+ </ev-button>
102
+ </ev-button-group>
103
+ </div>
104
+ <div
105
+ v-if="usedShortcuts.length"
106
+ class="ev-date-picker-dropbox__divider"
95
107
  />
108
+ <div
109
+ :class="{ 'ev-date-picker-dropbox__calendar':usedShortcuts.length }">
110
+ <ev-calendar
111
+ key="fromCalendar"
112
+ v-model="mv"
113
+ :mode="$props.mode"
114
+ :month-notation="$props.monthNotation"
115
+ :day-of-the-week-notation="$props.dayOfTheWeekNotation"
116
+ :options="$props.options"
117
+ />
118
+ </div>
96
119
  </div>
97
120
  </div>
98
121
  </div>
@@ -100,7 +123,7 @@
100
123
 
101
124
  <script>
102
125
  import { datePickerClickoutside as clickoutside } from '@/directives/clickoutside';
103
- import { useModel, useDropdown } from './uses';
126
+ import { useModel, useDropdown, useShortcuts } from './uses';
104
127
 
105
128
  export default {
106
129
  name: 'EvDatePicker',
@@ -116,7 +139,8 @@ export default {
116
139
  const dateTimeReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]/);
117
140
  if (Array.isArray(value)) {
118
141
  return value.every(v => !!(!v
119
- || (v.length === 10 && dateReg.exec(v))));
142
+ || (v.length === 10 && dateReg.exec(v)))
143
+ || (v.length === 19 && dateTimeReg.exec(v)));
120
144
  }
121
145
  return !!(!value
122
146
  || (value.length === 10 && dateReg.exec(value))
@@ -138,7 +162,7 @@ export default {
138
162
  mode: {
139
163
  type: String,
140
164
  default: 'date',
141
- validator: value => ['date', 'dateTime', 'dateMulti', 'dateRange']
165
+ validator: value => ['date', 'dateTime', 'dateMulti', 'dateRange', 'dateTimeRange']
142
166
  .indexOf(value) !== -1,
143
167
  },
144
168
  monthNotation: {
@@ -160,11 +184,33 @@ export default {
160
184
  limit: 1,
161
185
  tagShorten: false,
162
186
  }),
163
- validator: ({ multiType, multiDayLimit, disabledDate, tagShorten }) =>
164
- (multiType ? ['weekday', 'week', 'date'].indexOf(multiType) !== -1 : true)
187
+ validator: ({ multiType, multiDayLimit, disabledDate, tagShorten, timeFormat }) => {
188
+ const timeReg = new RegExp(/(HH|2[0-3]|[01][0-9]):(mm|[0-5][0-9]):(ss|[0-5][0-9])/);
189
+ return (multiType ? ['weekday', 'week', 'date'].indexOf(multiType) !== -1 : true)
165
190
  && (multiDayLimit ? typeof multiDayLimit === 'number' && multiDayLimit > 0 : true)
166
191
  && (disabledDate ? typeof disabledDate === 'function' : true)
167
- && (tagShorten !== undefined ? typeof tagShorten === 'boolean' : true),
192
+ && (tagShorten !== undefined ? typeof tagShorten === 'boolean' : true)
193
+ && Array.isArray(timeFormat)
194
+ ? timeFormat.every(v => !!(!v || timeReg.exec(v)))
195
+ : !!(!timeFormat || (timeReg.exec(timeFormat)));
196
+ },
197
+ },
198
+ shortcuts: {
199
+ type: Array,
200
+ default: () => [],
201
+ validator: (value) => {
202
+ if (!value.length) {
203
+ return true;
204
+ }
205
+ return value.every(({ shortcutDate }) => {
206
+ if (typeof shortcutDate !== 'function') {
207
+ return false;
208
+ }
209
+ const date = shortcutDate();
210
+ return (Array.isArray(date) && date.every(d => d instanceof Date) && date[0] <= date[1])
211
+ || (typeof date === 'object' && date instanceof Date);
212
+ });
213
+ },
168
214
  },
169
215
  },
170
216
  emits: {
@@ -190,10 +236,20 @@ export default {
190
236
  clickSelectInput,
191
237
  clickOutsideDropbox,
192
238
  changeDropboxPosition,
193
- } = useDropdown({
239
+ } = useDropdown();
240
+
241
+ const {
242
+ usedShortcuts,
243
+ clickShortcut,
244
+ setActiveShortcut,
245
+ } = useShortcuts({
246
+ mv,
194
247
  currentValue,
248
+ clickOutsideDropbox,
195
249
  });
196
250
 
251
+ setActiveShortcut();
252
+
197
253
  return {
198
254
  mv,
199
255
  currentValue,
@@ -211,6 +267,9 @@ export default {
211
267
  clickSelectInput,
212
268
  clickOutsideDropbox,
213
269
  changeDropboxPosition,
270
+
271
+ usedShortcuts,
272
+ clickShortcut,
214
273
  };
215
274
  },
216
275
  };
@@ -311,6 +370,7 @@ export default {
311
370
 
312
371
  &.num {
313
372
  padding-right: 8px;
373
+ cursor: pointer;
314
374
  }
315
375
 
316
376
  .ev-tag-suffix {
@@ -329,8 +389,26 @@ export default {
329
389
  }
330
390
  }
331
391
 
332
- .ev-date-picker-dropbox-wrapper {
333
- height: 0;
334
- z-index: 100;
392
+ .ev-date-picker-dropbox {
393
+ &-wrapper {
394
+ height: 0;
395
+ z-index: 100;
396
+ }
397
+
398
+ &__button-layout {
399
+ margin: 5px;
400
+ }
401
+
402
+ &__divider {
403
+ width: 100%;
404
+ height: 2px;
405
+ margin: 8px 0;
406
+ background-color: #E5E5E5;
407
+ }
408
+
409
+ &__calendar {
410
+ height: 100%;
411
+ margin: 5px;
412
+ }
335
413
  }
336
414
  </style>
@@ -2,12 +2,14 @@ import {
2
2
  ref, reactive, computed, watch,
3
3
  nextTick, getCurrentInstance,
4
4
  } from 'vue';
5
+ import { getChangedValueByTimeFormat } from '../calendar/uses';
5
6
 
6
7
  const dateReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/);
7
8
  const dateTimeReg = new RegExp(/[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9]/);
8
9
 
9
10
  export const useModel = () => {
10
11
  const { props, emit } = getCurrentInstance();
12
+ const timeFormat = props.options?.timeFormat;
11
13
 
12
14
  // Select 컴포넌트의 v-model 값
13
15
  const mv = computed({
@@ -15,13 +17,42 @@ export const useModel = () => {
15
17
  if (!props.modelValue) {
16
18
  return (props.mode === 'date' || props.mode === 'dateTime') ? '' : [];
17
19
  }
20
+ if (['dateTime', 'dateTimeRange'].includes(props.mode) && timeFormat) {
21
+ if (props.mode === 'dateTime') {
22
+ return getChangedValueByTimeFormat(timeFormat, props.modelValue);
23
+ } else if (props.modelValue.length) {
24
+ const [fromTimeFormat, toTimeFormat] = timeFormat;
25
+ return [
26
+ getChangedValueByTimeFormat(fromTimeFormat, props.modelValue[0]),
27
+ getChangedValueByTimeFormat(toTimeFormat, props.modelValue[1]),
28
+ ];
29
+ }
30
+ }
18
31
  return props.modelValue;
19
32
  },
20
33
  set: value => emit('update:modelValue', value),
21
34
  });
22
35
 
23
36
  // mode: 'date' or 'dateTime'시 input box의 입력된 텍스트값
24
- const currentValue = ref(props.modelValue);
37
+ let currentValue;
38
+ if (['dateTimeRange', 'dateTime'].includes(props.mode) && timeFormat) {
39
+ if (props.mode === 'dateTimeRange' && props.modelValue.length) {
40
+ const [fromDate, toDate] = props.modelValue;
41
+ const [fromTimeFormat, toTimeFormat] = timeFormat;
42
+
43
+ props.modelValue = [
44
+ getChangedValueByTimeFormat(fromTimeFormat, fromDate),
45
+ getChangedValueByTimeFormat(toTimeFormat, toDate),
46
+ ];
47
+ currentValue = ref(props.modelValue);
48
+ } else if (props.mode === 'dateTime' && props.modelValue) {
49
+ currentValue = ref(getChangedValueByTimeFormat(timeFormat, props.modelValue));
50
+ } else {
51
+ currentValue = ref(props.modelValue);
52
+ }
53
+ } else {
54
+ currentValue = ref(props.modelValue);
55
+ }
25
56
 
26
57
  const validateValue = (curr) => {
27
58
  if (props.mode === 'date'
@@ -89,9 +120,8 @@ export const useModel = () => {
89
120
  };
90
121
  };
91
122
 
92
- export const useDropdown = (param) => {
123
+ export const useDropdown = () => {
93
124
  const { props } = getCurrentInstance();
94
- const { currentValue } = param;
95
125
 
96
126
  const isDropbox = ref(false);
97
127
  const datePicker = ref(null);
@@ -162,24 +192,6 @@ export const useDropdown = (param) => {
162
192
  isDropbox.value = false;
163
193
  };
164
194
 
165
- watch(
166
- () => props.modelValue,
167
- (curr) => {
168
- if (props.mode === 'dateMulti'
169
- && props?.options?.multiType === 'date'
170
- && props?.options?.multiDayLimit > curr.length
171
- ) {
172
- return;
173
- } else if (props.mode === 'dateTime') {
174
- currentValue.value = curr;
175
- return;
176
- } else if (props.mode === 'date') {
177
- currentValue.value = curr;
178
- }
179
- clickOutsideDropbox();
180
- },
181
- );
182
-
183
195
  return {
184
196
  isDropbox,
185
197
  datePicker,
@@ -191,3 +203,189 @@ export const useDropdown = (param) => {
191
203
  changeDropboxPosition,
192
204
  };
193
205
  };
206
+
207
+ export const useShortcuts = (param) => {
208
+ const { props } = getCurrentInstance();
209
+ const { mv, currentValue, clickOutsideDropbox } = param;
210
+
211
+ const usedShortcuts = reactive([]);
212
+ props.shortcuts?.forEach(({ value, label, shortcutDate }) => {
213
+ usedShortcuts.push({
214
+ key: value,
215
+ label,
216
+ shortcutDate,
217
+ isActive: false,
218
+ });
219
+ });
220
+
221
+ /**
222
+ * active 되어있는 shortcut 제거
223
+ */
224
+ const clearShortcuts = () => {
225
+ const targetShortcut = usedShortcuts.find(shortcut => shortcut.isActive);
226
+ if (targetShortcut) {
227
+ targetShortcut.isActive = false;
228
+ }
229
+ };
230
+
231
+ /**
232
+ * targetKey에 해당하는 shortcut을 active
233
+ * @param targetKey
234
+ */
235
+ const activeShortcut = (targetKey) => {
236
+ const targetShortcut = usedShortcuts.find(shortcut => shortcut.key === targetKey);
237
+ if (targetShortcut) {
238
+ targetShortcut.isActive = true;
239
+ }
240
+ };
241
+
242
+ /**
243
+ * 월, 일을 두자리 숫자로 보정
244
+ * @param num
245
+ * @returns {string|*}
246
+ */
247
+ const lpadToTwoDigits = (num) => {
248
+ if (num === null) {
249
+ return '00';
250
+ } else if (+num < 10) {
251
+ return `0${num}`;
252
+ }
253
+ return num;
254
+ };
255
+
256
+ /**
257
+ * 'YYYY-MM-DD' 형식으로 format
258
+ * @param targetDate
259
+ * @returns string
260
+ */
261
+ const formatDate = (targetDate) => {
262
+ const dateValue = targetDate ? new Date(targetDate) : new Date();
263
+ const year = dateValue.getFullYear();
264
+ const month = dateValue.getMonth() + 1;
265
+ const day = dateValue.getDate();
266
+ return `${year}-${lpadToTwoDigits(month)}-${lpadToTwoDigits(day)}`;
267
+ };
268
+
269
+ /**
270
+ * 'YYYY-MM-DD HH:mm:ss' 형식으로 format
271
+ * @param targetDateTime
272
+ * @returns string
273
+ */
274
+ const formatDateTime = (targetDateTime) => {
275
+ const dateTimeValue = targetDateTime ? new Date(targetDateTime) : new Date();
276
+ const hour = dateTimeValue.getHours();
277
+ const min = dateTimeValue.getMinutes();
278
+ const sec = dateTimeValue.getSeconds();
279
+ return `${formatDate(dateTimeValue)} ${lpadToTwoDigits(hour)}:${lpadToTwoDigits(min)}:${lpadToTwoDigits(sec)}`;
280
+ };
281
+
282
+ /**
283
+ * 초기 shortcut 세팅
284
+ * 해당하는 날짜면 active
285
+ */
286
+ const setActiveShortcut = () => {
287
+ clearShortcuts();
288
+
289
+ const isRange = ['dateRange', 'dateTimeRange'].includes(props.mode);
290
+
291
+ if (!usedShortcuts.length
292
+ || (props.mode === 'dateMulti' && props.options?.multiType !== 'date')
293
+ || (isRange && !mv.value.length)
294
+ || (!isRange && !mv.value)
295
+ ) {
296
+ return;
297
+ }
298
+
299
+ let targetKey;
300
+ if (isRange) {
301
+ const [fromDate, toDate] = mv.value;
302
+ const targetShortcut = usedShortcuts.find(({ shortcutDate }) => {
303
+ const [sFromDate, sToDate] = shortcutDate();
304
+ const isCorrectFromDate = formatDate(sFromDate) === formatDate(fromDate);
305
+ const isCorrectToDate = formatDate(sToDate) === formatDate(toDate);
306
+ return isCorrectFromDate && isCorrectToDate;
307
+ });
308
+ targetKey = targetShortcut?.key;
309
+ } else {
310
+ const date = formatDate(mv.value);
311
+ const targetShortcut = usedShortcuts.find(({ shortcutDate }) => {
312
+ const sDate = formatDate(shortcutDate());
313
+ return sDate === formatDate(date);
314
+ });
315
+ targetKey = targetShortcut?.key;
316
+ }
317
+
318
+ if (targetKey) {
319
+ activeShortcut(targetKey);
320
+ }
321
+ };
322
+
323
+ /**
324
+ * shortcut을 클릭했을 때 이벤트
325
+ * @param targetKey
326
+ */
327
+ const clickShortcut = (targetKey) => {
328
+ const isRange = ['dateRange', 'dateTimeRange'].includes(props.mode);
329
+ const targetShortcut = usedShortcuts.find(({ key }) => key === targetKey);
330
+
331
+ if (!targetShortcut) {
332
+ return;
333
+ }
334
+
335
+ const shortcutDate = targetShortcut.shortcutDate;
336
+ const timeFormat = props.options?.timeFormat;
337
+
338
+ if (isRange) {
339
+ const [fromDate, toDate] = shortcutDate();
340
+ if (props.mode === 'dateTimeRange') {
341
+ if (timeFormat?.length) {
342
+ const [fromTimeFormat, toTimeFormat] = timeFormat;
343
+
344
+ mv.value = [
345
+ getChangedValueByTimeFormat(fromTimeFormat, formatDateTime(fromDate)),
346
+ getChangedValueByTimeFormat(toTimeFormat, formatDateTime(toDate)),
347
+ ];
348
+ } else {
349
+ mv.value = [formatDateTime(fromDate), formatDateTime(toDate)];
350
+ }
351
+ } else {
352
+ mv.value = [formatDate(fromDate), formatDate(toDate)];
353
+ }
354
+ } else {
355
+ const sDate = shortcutDate();
356
+ mv.value = props.mode === 'dateTime'
357
+ ? getChangedValueByTimeFormat(
358
+ timeFormat,
359
+ formatDateTime(sDate))
360
+ : formatDate(sDate);
361
+ }
362
+
363
+ clearShortcuts();
364
+ activeShortcut(targetKey);
365
+ };
366
+
367
+ watch(
368
+ () => props.modelValue,
369
+ (curr) => {
370
+ setActiveShortcut();
371
+ if (props.mode === 'dateMulti'
372
+ && props?.options?.multiType === 'date'
373
+ && props?.options?.multiDayLimit > curr.length
374
+ ) {
375
+ return;
376
+ } else if (props.mode === 'dateTime' || props.mode === 'dateTimeRange') {
377
+ currentValue.value = curr;
378
+ return;
379
+ } else if (props.mode === 'date') {
380
+ currentValue.value = curr;
381
+ }
382
+ clickOutsideDropbox();
383
+ },
384
+ );
385
+
386
+ return {
387
+ usedShortcuts,
388
+ clickShortcut,
389
+ setActiveShortcut,
390
+ };
391
+ };
@@ -94,10 +94,10 @@
94
94
  <table>
95
95
  <tbody>
96
96
  <tree-grid-node
97
- v-for="(item, idx) in viewStore"
97
+ v-for="(node, idx) in viewStore"
98
98
  :key="idx"
99
99
  :selected-data="selectedRow"
100
- :node-data="item"
100
+ :node-data="node"
101
101
  :use-checkbox="useCheckbox"
102
102
  :ordered-columns="orderedColumns"
103
103
  :expand-icon="option.expandIcon"
@@ -114,6 +114,26 @@
114
114
  @click-tree-data="onRowClick"
115
115
  @dbl-click-tree-data="onRowDblClick"
116
116
  >
117
+ <!-- cell renderer -->
118
+ <template
119
+ v-for="(column, cellIndex) in orderedColumns"
120
+ :key="cellIndex"
121
+ v-slot:[getSlotName(column.field)] = "{ item }"
122
+ >
123
+ <template v-if="!!$slots[column.field]">
124
+ <slot
125
+ :name="column.field"
126
+ :item="{
127
+ data: item.data,
128
+ fieldName: column.field
129
+ }"
130
+ >
131
+ </slot>
132
+ </template>
133
+ <template v-else>
134
+ <span :title="node[column.field]">{{node[column.field]}}</span>
135
+ </template>
136
+ </template>
117
137
  </tree-grid-node>
118
138
  <tr v-if="!viewStore.length">
119
139
  <td class="is-empty">No records</td>
@@ -412,6 +432,7 @@ export default {
412
432
  'min-width': render ? `${resizeInfo.rendererMinWidth}px;` : `${resizeInfo.minWidth}px`,
413
433
  };
414
434
  };
435
+ const getSlotName = column => `${column}Node`;
415
436
 
416
437
  return {
417
438
  ...toRefs(styleInfo),
@@ -449,6 +470,7 @@ export default {
449
470
  isHeaderCheckbox,
450
471
  getColumnClass,
451
472
  getColumnStyle,
473
+ getSlotName,
452
474
  bodyStyle,
453
475
  };
454
476
  },
@@ -75,7 +75,16 @@
75
75
  <i></i>
76
76
  </span>
77
77
  </span>
78
- <span :title="node[column.field]">{{node[column.field]}}</span>
78
+ <!-- cell renderer -->
79
+ <template v-if="!!$slots[column.field + 'Node']">
80
+ <slot
81
+ :name="column.field + 'Node'"
82
+ :item="{
83
+ data: node.data,
84
+ }"
85
+ >
86
+ </slot>
87
+ </template>
79
88
  </div>
80
89
  </td>
81
90
  </template>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div class="description treeGridToolbar">
2
+ <div class="description tree-grid-toolbar">
3
3
  <slot name="toolbarWrapper"></slot>
4
4
  </div>
5
5
  </template>
@@ -12,15 +12,15 @@ export default {
12
12
 
13
13
  <style lang="scss">
14
14
  @import '../../style/index.scss';
15
- .treeGridToolbar {
15
+ .tree-grid-toolbar {
16
16
  margin-bottom: 10px;
17
17
  overflow: hidden;
18
18
  }
19
- .treeGridToolbar > .search {
19
+ .tree-grid-toolbar > .search {
20
20
  float: right;
21
21
  margin-right: 0;
22
22
  }
23
- .treeGridToolbar > .ev-button {
23
+ .tree-grid-toolbar > .ev-button {
24
24
  margin: 0 2px 0 2px;
25
25
  }
26
26
  </style>