@mptw/skylens-ui 1.0.0
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/.editorconfig +12 -0
- package/.eslintrc.cjs +4 -0
- package/.nuxtrc +1 -0
- package/.playground/app.config.ts +5 -0
- package/.playground/nuxt.config.ts +3 -0
- package/README.md +73 -0
- package/app.config.ts +5 -0
- package/app.vue +3 -0
- package/assets/css/fonts/MEShPlus-font-icon.eot +0 -0
- package/assets/css/fonts/MEShPlus-font-icon.svg +186 -0
- package/assets/css/fonts/MEShPlus-font-icon.ttf +0 -0
- package/assets/css/fonts/MEShPlus-font-icon.woff +0 -0
- package/assets/css/icons.css +555 -0
- package/assets/css/layout.styl +98 -0
- package/assets/css/tailwind.css +3 -0
- package/assets/images/commons/app-logo-white.png +0 -0
- package/assets/images/commons/app-logo.png +0 -0
- package/assets/images/commons/empty-box.png +0 -0
- package/assets/images/commons/watermark.svg +15 -0
- package/components/SLAlertModal.vue +99 -0
- package/components/SLBreadcrumbs.vue +42 -0
- package/components/SLButton.vue +301 -0
- package/components/SLCalendarButton.vue +259 -0
- package/components/SLCalendarPopup.vue +832 -0
- package/components/SLCard.vue +118 -0
- package/components/SLChart.vue +181 -0
- package/components/SLCheckbox.vue +179 -0
- package/components/SLCircleButton.vue +95 -0
- package/components/SLCollapsableBlock.vue +116 -0
- package/components/SLCollapsableMulitPillSelect.vue +92 -0
- package/components/SLConfirmModal.vue +122 -0
- package/components/SLDateInput.vue +460 -0
- package/components/SLDatePicker.vue +143 -0
- package/components/SLDistributor.vue +30 -0
- package/components/SLDownloadButton.vue +410 -0
- package/components/SLDraggable.vue +151 -0
- package/components/SLDropdown.vue +277 -0
- package/components/SLDropdownItem.vue +31 -0
- package/components/SLElementWithTitle.vue +181 -0
- package/components/SLEyeCheckbox.vue +162 -0
- package/components/SLFileInput.vue +101 -0
- package/components/SLGenderAgeSelect.vue +279 -0
- package/components/SLHeatMap.vue +151 -0
- package/components/SLHourRangeInput.vue +211 -0
- package/components/SLIOSSwitch.vue +235 -0
- package/components/SLIcon.vue +35 -0
- package/components/SLIconButton.vue +130 -0
- package/components/SLInfoTip.vue +221 -0
- package/components/SLInsightSitePage.vue +27 -0
- package/components/SLLegend.vue +76 -0
- package/components/SLLinearProgressBar.vue +35 -0
- package/components/SLLink.vue +162 -0
- package/components/SLLoading.vue +127 -0
- package/components/SLMapChart.vue +151 -0
- package/components/SLModal.vue +377 -0
- package/components/SLMonthCalendarButton.vue +378 -0
- package/components/SLMonthPicker.vue +151 -0
- package/components/SLMultiSelect.vue +121 -0
- package/components/SLNoData.vue +43 -0
- package/components/SLPageModal.vue +64 -0
- package/components/SLPagination.vue +192 -0
- package/components/SLPillCheckbox.vue +207 -0
- package/components/SLPillLabel.vue +101 -0
- package/components/SLPopupMenuButton.vue +158 -0
- package/components/SLProfileButton.vue +186 -0
- package/components/SLRadioButton.vue +97 -0
- package/components/SLRadioButtonGroup.vue +113 -0
- package/components/SLRadioGroup.vue +201 -0
- package/components/SLRangeInput.vue +209 -0
- package/components/SLRegionsMapChart.vue +151 -0
- package/components/SLRightSider.vue +242 -0
- package/components/SLSearchInput.vue +87 -0
- package/components/SLSelect.vue +545 -0
- package/components/SLSelectAllToggle.vue +118 -0
- package/components/SLSiderbarNav.vue +394 -0
- package/components/SLSiderbarNavItem.vue +204 -0
- package/components/SLSitePage.vue +96 -0
- package/components/SLSkyInsightSitePage.vue +27 -0
- package/components/SLSortByDropdown.vue +189 -0
- package/components/SLSpinIcon.vue +24 -0
- package/components/SLStayRangeInput.vue +306 -0
- package/components/SLTab.vue +159 -0
- package/components/SLTable.vue +409 -0
- package/components/SLTableTooltip.vue +111 -0
- package/components/SLTabs.vue +388 -0
- package/components/SLTextInput.vue +362 -0
- package/components/SLTextarea.vue +134 -0
- package/components/SLToast.vue +71 -0
- package/components/SLToggleButton.vue +78 -0
- package/components/SLTwoLayerMultiSelect.vue +428 -0
- package/components/SLTwoLayerSelect.vue +265 -0
- package/components/SLWarnModal.vue +75 -0
- package/interface/IDateRange.ts +5 -0
- package/interface/IHourRange.ts +4 -0
- package/interface/IInputOption.ts +6 -0
- package/interface/ILegendItem.ts +7 -0
- package/interface/IPopupMenuButton.ts +5 -0
- package/interface/IStayRange.ts +15 -0
- package/interface/ITableField.ts +19 -0
- package/interface/IWeb.ts +5 -0
- package/interface/NavConfigType.ts +14 -0
- package/interface/commons.ts +7 -0
- package/models/DateRange.ts +21 -0
- package/nuxt.config.ts +22 -0
- package/package.json +52 -0
- package/plugins/googlemaps.ts +17 -0
- package/plugins/v-calendar.ts +5 -0
- package/public/assets/files/taiwan-regions.json +7 -0
- package/public/assets/files/taiwan.json +24 -0
- package/public/assets/images/commons/default-favico.png +0 -0
- package/public/assets/images/dashboard/closure-button-icon.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-poi.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-web.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-poi.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-web.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-web.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-poi.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-list-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-list-icon-poi.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-list-icon-web.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-disabled.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-poi.png +0 -0
- package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-web.png +0 -0
- package/public/assets/images/dashboard/drag-hint-icon.svg +3 -0
- package/public/assets/images/dashboard/extend-button-icon.png +0 -0
- package/public/assets/images/dashboard/map-list-chart-icon.svg +17 -0
- package/public/assets/images/dashboard/map-marker-chart-icon.svg +32 -0
- package/public/assets/images/dashboard/pie-chart-icon.svg +4 -0
- package/public/assets/images/dashboard/poi-gender-age-chart-icon.svg +20 -0
- package/public/assets/images/dashboard/poi-nav-icon.png +0 -0
- package/public/assets/images/dashboard/web-gender-age-chart-icon.svg +20 -0
- package/public/assets/images/dashboard/web-nav-icon.png +0 -0
- package/tailwind.config.js +300 -0
- package/tsconfig.json +8 -0
- package/utils/constants.ts +160 -0
- package/utils/generateSheet.ts +36 -0
- package/utils/index.ts +539 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.month-calendar-button(ref='el')
|
|
3
|
+
a.month-calendar-button-toggle(href='javascript:;', ref='toggleEl', @click='onTogglePopup')
|
|
4
|
+
.toggle-wrapper
|
|
5
|
+
SLIcon(icon='calendar')
|
|
6
|
+
.toggle-divide
|
|
7
|
+
.button-title
|
|
8
|
+
.current-month {{ currentMonthString }}
|
|
9
|
+
.compare-month(v-if='compareMonthString !== ""') v.s. {{ compareMonthString }}
|
|
10
|
+
ClientOnly
|
|
11
|
+
Teleport(to="body")
|
|
12
|
+
.month-calendar-popup(ref='popupEl')
|
|
13
|
+
.popup-header
|
|
14
|
+
.compare-switch
|
|
15
|
+
a.compare-switch-button(
|
|
16
|
+
v-if='enableCompare',
|
|
17
|
+
href='javascript:;',
|
|
18
|
+
:class='{ selected: isCompare }',
|
|
19
|
+
@click='onClickedCompare'
|
|
20
|
+
) 比較
|
|
21
|
+
a.compare-switch-button(
|
|
22
|
+
href='javascript:;',
|
|
23
|
+
:class='{ selected: !isCompare }',
|
|
24
|
+
@click='onClickedSingle'
|
|
25
|
+
) 單月
|
|
26
|
+
.popup-body
|
|
27
|
+
SLMonthPicker(
|
|
28
|
+
v-if='isCompare',
|
|
29
|
+
color='primary-hover',
|
|
30
|
+
:min-date='minDate',
|
|
31
|
+
:max-date='maxCompareDate',
|
|
32
|
+
v-model='compareDate',
|
|
33
|
+
)
|
|
34
|
+
.calendar-divide(v-if='isCompare')
|
|
35
|
+
SLMonthPicker(
|
|
36
|
+
color='insight-title',
|
|
37
|
+
:min-date='minDate',
|
|
38
|
+
:max-date="maxDate",
|
|
39
|
+
v-model='currentDate',
|
|
40
|
+
)
|
|
41
|
+
.popup-footer
|
|
42
|
+
a.cancel-button(
|
|
43
|
+
href='javascript:;'
|
|
44
|
+
@click='onClickedCancel'
|
|
45
|
+
) 取消
|
|
46
|
+
SLButton(
|
|
47
|
+
title='套用',
|
|
48
|
+
size='lg',
|
|
49
|
+
color='primary',
|
|
50
|
+
@click='onClickedApply'
|
|
51
|
+
)
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script lang="ts">
|
|
55
|
+
import type { PropType } from "vue";
|
|
56
|
+
import { fromEvent, Subscription } from "rxjs";
|
|
57
|
+
import {
|
|
58
|
+
subMonths,
|
|
59
|
+
differenceInCalendarMonths,
|
|
60
|
+
format as formatDate,
|
|
61
|
+
} from "date-fns";
|
|
62
|
+
import { createPopper } from "@popperjs/core";
|
|
63
|
+
import type { Instance as PopperInstance } from "@popperjs/core";
|
|
64
|
+
import { gaev, ga4ev } from "~ui/utils";
|
|
65
|
+
|
|
66
|
+
export default defineComponent({
|
|
67
|
+
name: "SLMonthCalendarButton",
|
|
68
|
+
components: {},
|
|
69
|
+
props: {
|
|
70
|
+
minDate: {
|
|
71
|
+
type: Date as PropType<Date | null>,
|
|
72
|
+
default: () => null,
|
|
73
|
+
},
|
|
74
|
+
maxDate: {
|
|
75
|
+
type: Date as PropType<Date | null>,
|
|
76
|
+
default: () => new Date(),
|
|
77
|
+
},
|
|
78
|
+
modelValue: {
|
|
79
|
+
type: Array as PropType<(Date | null)[]>,
|
|
80
|
+
default: () => [new Date()],
|
|
81
|
+
},
|
|
82
|
+
enableCompare: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
emits: ["update:modelValue"],
|
|
88
|
+
setup(props, { emit }) {
|
|
89
|
+
const { minDate, maxDate, modelValue, enableCompare } = toRefs(props);
|
|
90
|
+
const el = shallowRef<HTMLDivElement | null>(null);
|
|
91
|
+
const toggleEl = shallowRef<HTMLLinkElement | null>(null);
|
|
92
|
+
const popupEl = shallowRef<HTMLDivElement | null>(null);
|
|
93
|
+
const isShowPopup = ref(false);
|
|
94
|
+
const modelValueProxy = ref<(Date | null)[]>([...modelValue.value]);
|
|
95
|
+
const isCompare = ref(
|
|
96
|
+
modelValue.value[1] && enableCompare.value ? true : false
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
let popperInstance: PopperInstance | null = null;
|
|
100
|
+
let clickSubscription: Subscription | null = null;
|
|
101
|
+
|
|
102
|
+
const currentDate = computed({
|
|
103
|
+
get: () => modelValueProxy.value[0] || null,
|
|
104
|
+
set: (newValue) => {
|
|
105
|
+
modelValueProxy.value = [newValue, modelValueProxy.value[1] || null];
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
const compareDate = computed({
|
|
109
|
+
get: () => modelValueProxy.value[1] || null,
|
|
110
|
+
set: (newValue) => {
|
|
111
|
+
modelValueProxy.value = [modelValueProxy.value[0] || null, newValue];
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const maxCompareDate = computed(() => {
|
|
115
|
+
let maxCompareDate: Date | null = null;
|
|
116
|
+
|
|
117
|
+
if (currentDate.value) {
|
|
118
|
+
maxCompareDate = subMonths(currentDate.value, 1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
maxDate.value &&
|
|
123
|
+
maxCompareDate &&
|
|
124
|
+
differenceInCalendarMonths(maxDate.value, maxCompareDate) < 0
|
|
125
|
+
) {
|
|
126
|
+
maxCompareDate = maxDate.value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return maxCompareDate;
|
|
130
|
+
});
|
|
131
|
+
const currentMonthString = computed(() =>
|
|
132
|
+
modelValue.value[0] ? formatDate(modelValue.value[0], "yyyy/ MM") : ""
|
|
133
|
+
);
|
|
134
|
+
const compareMonthString = computed(() =>
|
|
135
|
+
modelValue.value[1] ? formatDate(modelValue.value[1], "yyyy/ MM") : ""
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
function onClickedCompare() {
|
|
139
|
+
gaev({
|
|
140
|
+
category: "Month picker",
|
|
141
|
+
action: "Month picker 切換比較",
|
|
142
|
+
label: "切換比較",
|
|
143
|
+
});
|
|
144
|
+
ga4ev({
|
|
145
|
+
category: "Month picker",
|
|
146
|
+
action: "Month picker 切換比較",
|
|
147
|
+
label: "切換比較",
|
|
148
|
+
});
|
|
149
|
+
isCompare.value = true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function onClickedSingle() {
|
|
153
|
+
gaev({
|
|
154
|
+
category: "Month picker",
|
|
155
|
+
action: "Month picker 切換單月",
|
|
156
|
+
label: "切換單月",
|
|
157
|
+
});
|
|
158
|
+
ga4ev({
|
|
159
|
+
category: "Month picker",
|
|
160
|
+
action: "Month picker 切換單月",
|
|
161
|
+
label: "切換單月",
|
|
162
|
+
});
|
|
163
|
+
isCompare.value = false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function subscribeClick() {
|
|
167
|
+
clickSubscription = fromEvent(window, "click").subscribe((e) => {
|
|
168
|
+
const { target } = e;
|
|
169
|
+
if (
|
|
170
|
+
el.value &&
|
|
171
|
+
!el.value.contains(target as Node) &&
|
|
172
|
+
popupEl.value &&
|
|
173
|
+
!popupEl.value.contains(target as Node)
|
|
174
|
+
) {
|
|
175
|
+
isShowPopup.value = false;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function unsubscribeClick() {
|
|
181
|
+
if (clickSubscription) {
|
|
182
|
+
clickSubscription.unsubscribe();
|
|
183
|
+
}
|
|
184
|
+
clickSubscription = null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function setupPopper() {
|
|
188
|
+
nextTick(() => {
|
|
189
|
+
if (!toggleEl.value || !popupEl.value || popperInstance) return;
|
|
190
|
+
|
|
191
|
+
popperInstance = createPopper(toggleEl.value, popupEl.value, {
|
|
192
|
+
placement: "bottom-end",
|
|
193
|
+
modifiers: [
|
|
194
|
+
{ name: "offset", options: { offset: [0, 4] } },
|
|
195
|
+
{ name: "eventListeners", enabled: false },
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function destoryPopper() {
|
|
202
|
+
if (popperInstance) {
|
|
203
|
+
popperInstance.destroy();
|
|
204
|
+
}
|
|
205
|
+
popperInstance = null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function showPopup() {
|
|
209
|
+
if (!popperInstance || !popupEl.value) return;
|
|
210
|
+
|
|
211
|
+
subscribeClick();
|
|
212
|
+
popupEl.value.setAttribute("data-show", "");
|
|
213
|
+
popperInstance.setOptions((options) => ({
|
|
214
|
+
...options,
|
|
215
|
+
modifiers: [
|
|
216
|
+
...(options.modifiers || []),
|
|
217
|
+
{ name: "eventListeners", enabled: true },
|
|
218
|
+
],
|
|
219
|
+
}));
|
|
220
|
+
popperInstance.update();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function hidePopup() {
|
|
224
|
+
if (!popperInstance || !popupEl.value) return;
|
|
225
|
+
|
|
226
|
+
unsubscribeClick();
|
|
227
|
+
popupEl.value.removeAttribute("data-show");
|
|
228
|
+
popperInstance.setOptions((options) => ({
|
|
229
|
+
...options,
|
|
230
|
+
modifiers: [
|
|
231
|
+
...(options.modifiers || []),
|
|
232
|
+
{ name: "eventListeners", enabled: false },
|
|
233
|
+
],
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function onTogglePopup() {
|
|
238
|
+
isShowPopup.value = !isShowPopup.value;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function onClickedCancel() {
|
|
242
|
+
modelValueProxy.value = [...modelValue.value];
|
|
243
|
+
isShowPopup.value = false;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function onClickedApply() {
|
|
247
|
+
if (isCompare.value) {
|
|
248
|
+
const compareDate = modelValueProxy.value[1];
|
|
249
|
+
if (minDate.value && compareDate && compareDate < minDate.value) {
|
|
250
|
+
isCompare.value = false;
|
|
251
|
+
emit("update:modelValue", [modelValueProxy.value[0]]);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
emit("update:modelValue", [...modelValueProxy.value]);
|
|
255
|
+
} else {
|
|
256
|
+
emit("update:modelValue", [modelValueProxy.value[0]]);
|
|
257
|
+
}
|
|
258
|
+
isShowPopup.value = false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function setupCompareMonth() {
|
|
262
|
+
if (!currentDate.value) {
|
|
263
|
+
modelValueProxy.value = [];
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!isCompare.value) {
|
|
268
|
+
modelValueProxy.value = [modelValueProxy.value[0]];
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// if (
|
|
273
|
+
// compareMonth.value &&
|
|
274
|
+
// differenceInCalendarMonths(currentMonth.value, compareMonth.value) > 0
|
|
275
|
+
// ) {
|
|
276
|
+
// return;
|
|
277
|
+
// }
|
|
278
|
+
|
|
279
|
+
modelValueProxy.value = [
|
|
280
|
+
modelValueProxy.value[0],
|
|
281
|
+
subMonths(currentDate.value, 1),
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
watch(modelValue, () => {
|
|
286
|
+
modelValueProxy.value = [...modelValue.value];
|
|
287
|
+
isCompare.value =
|
|
288
|
+
modelValue.value[1] && enableCompare.value ? true : false;
|
|
289
|
+
});
|
|
290
|
+
watch(isShowPopup, () => {
|
|
291
|
+
if (isShowPopup.value) {
|
|
292
|
+
showPopup();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
hidePopup();
|
|
297
|
+
});
|
|
298
|
+
watch(currentDate, () => {
|
|
299
|
+
setupCompareMonth();
|
|
300
|
+
});
|
|
301
|
+
watch(isCompare, () => {
|
|
302
|
+
setupCompareMonth();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
onMounted(() => {
|
|
306
|
+
setupPopper();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
onBeforeUnmount(() => {
|
|
310
|
+
destoryPopper();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
el,
|
|
315
|
+
toggleEl,
|
|
316
|
+
popupEl,
|
|
317
|
+
isCompare,
|
|
318
|
+
currentDate,
|
|
319
|
+
compareDate,
|
|
320
|
+
maxCompareDate,
|
|
321
|
+
currentMonthString,
|
|
322
|
+
compareMonthString,
|
|
323
|
+
onTogglePopup,
|
|
324
|
+
onClickedCompare,
|
|
325
|
+
onClickedSingle,
|
|
326
|
+
onClickedCancel,
|
|
327
|
+
onClickedApply,
|
|
328
|
+
};
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
</script>
|
|
332
|
+
|
|
333
|
+
<style lang="stylus" scoped>
|
|
334
|
+
.month-calendar-button
|
|
335
|
+
@apply inline-flex
|
|
336
|
+
|
|
337
|
+
&-toggle
|
|
338
|
+
@apply inline-flex items-center justify-center s('min-w-[163px]') s('min-h-[58px]') py-1 px-4 bg-white border border-primary-light rounded shadow
|
|
339
|
+
.toggle-wrapper
|
|
340
|
+
@apply inline-flex items-center
|
|
341
|
+
.icon
|
|
342
|
+
@apply text-base leading-none text-primary
|
|
343
|
+
.toggle-divide
|
|
344
|
+
@apply self-stretch w-px ml-4 s('mr-3.75') bg-primary rounded-full
|
|
345
|
+
.button-title
|
|
346
|
+
@apply space-y-1 text-end
|
|
347
|
+
.current-month
|
|
348
|
+
@apply text-lg text-gray-2
|
|
349
|
+
.compare-month
|
|
350
|
+
@apply text-xs text-gray-3
|
|
351
|
+
.month-calendar-popup
|
|
352
|
+
@apply z-50 s('p-3.75') space-y-4 bg-white border border-bg rounded-lg shadow-md hidden
|
|
353
|
+
|
|
354
|
+
&[data-show]
|
|
355
|
+
@apply block
|
|
356
|
+
.popup-header
|
|
357
|
+
@apply text-center
|
|
358
|
+
.compare-switch
|
|
359
|
+
@apply inline-flex items-center space-x-px
|
|
360
|
+
.compare-switch-button
|
|
361
|
+
@apply block s('py-0.75') s('px-3.75') bg-white border border-primary-lighter text-sm text-insight-title
|
|
362
|
+
|
|
363
|
+
&:first-child
|
|
364
|
+
@apply rounded-l s('rounded-r-[1px]')
|
|
365
|
+
|
|
366
|
+
&:last-child
|
|
367
|
+
@apply s('rounded-l-[1px]') rounded-r
|
|
368
|
+
&.selected
|
|
369
|
+
@apply bg-insight-title border-insight-title text-white
|
|
370
|
+
.popup-body
|
|
371
|
+
@apply flex
|
|
372
|
+
.calendar-divide
|
|
373
|
+
@apply w-px s('ml-4') s('mr-3.75') bg-gray-4
|
|
374
|
+
.popup-footer
|
|
375
|
+
@apply pt-4 space-x-6 border-t border-gray-4 text-center
|
|
376
|
+
.cancel-button
|
|
377
|
+
@apply text-base text-gray-3
|
|
378
|
+
</style>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.month-picker(:class='{ "month-picker--insight-title": color === "insight-title", "month-picker--primary-hover": color === "primary-hover" }')
|
|
3
|
+
.year-select
|
|
4
|
+
a.prev-button(
|
|
5
|
+
href='javascript:;'
|
|
6
|
+
:class='{ disabled: minYear !== null && yearProxy <= minYear }',
|
|
7
|
+
@click='onClickedPrevYear'
|
|
8
|
+
)
|
|
9
|
+
SLIcon(icon='chevron-left')
|
|
10
|
+
.year {{ yearProxy }}
|
|
11
|
+
a.next-button(
|
|
12
|
+
href='javascript:;'
|
|
13
|
+
:class='{ disabled: maxYear !== null && yearProxy >= maxYear }',
|
|
14
|
+
@click='onClickedNextYear'
|
|
15
|
+
)
|
|
16
|
+
SLIcon(icon='chevron-right')
|
|
17
|
+
.month-select
|
|
18
|
+
.month-select-wrapper
|
|
19
|
+
a.month-button(
|
|
20
|
+
v-for='m in 12'
|
|
21
|
+
href='javascript:;'
|
|
22
|
+
:class='{ selected: yearProxy === year && month === m, disabled: (maxYear !== null && maxYear <= yearProxy && maxMonth !== null && m > maxMonth) || (minYear !== null && minYear >= yearProxy && minMonth !== null && m < minMonth)}'
|
|
23
|
+
@click='onClickedMonth(m)'
|
|
24
|
+
) {{ m }} 月
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script lang="ts">
|
|
28
|
+
import type { PropType } from "vue";
|
|
29
|
+
|
|
30
|
+
export default defineComponent({
|
|
31
|
+
name: "SLMonthPicker",
|
|
32
|
+
components: {},
|
|
33
|
+
props: {
|
|
34
|
+
color: {
|
|
35
|
+
type: String as PropType<"insight-title" | "primary-hover">,
|
|
36
|
+
validate: (value: string) =>
|
|
37
|
+
["insight-title", "primary-hover"].includes(value),
|
|
38
|
+
},
|
|
39
|
+
minDate: {
|
|
40
|
+
type: Date as PropType<Date | null>,
|
|
41
|
+
default: () => null,
|
|
42
|
+
},
|
|
43
|
+
maxDate: {
|
|
44
|
+
type: Date as PropType<Date | null>,
|
|
45
|
+
default: () => null,
|
|
46
|
+
},
|
|
47
|
+
modelValue: {
|
|
48
|
+
type: Date as PropType<Date | null>,
|
|
49
|
+
default: () => null,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
emits: ["update:modelValue"],
|
|
53
|
+
setup(props, { emit }) {
|
|
54
|
+
const { minDate, maxDate, modelValue } = toRefs(props);
|
|
55
|
+
|
|
56
|
+
const yearProxy = ref(
|
|
57
|
+
modelValue.value?.getFullYear() || new Date().getFullYear()
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const minYear = computed(() => minDate.value?.getFullYear() || null);
|
|
61
|
+
const minMonth = computed(() =>
|
|
62
|
+
minDate.value ? minDate.value.getMonth() + 1 : null
|
|
63
|
+
);
|
|
64
|
+
const maxYear = computed(() => maxDate.value?.getFullYear() || null);
|
|
65
|
+
const maxMonth = computed(() =>
|
|
66
|
+
maxDate.value ? maxDate.value.getMonth() + 1 : null
|
|
67
|
+
);
|
|
68
|
+
const year = computed(() => modelValue.value?.getFullYear() || null);
|
|
69
|
+
const month = computed(() =>
|
|
70
|
+
modelValue.value ? modelValue.value.getMonth() + 1 : null
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
function onClickedPrevYear() {
|
|
74
|
+
if (minYear.value !== null && yearProxy.value <= minYear.value) return;
|
|
75
|
+
yearProxy.value -= 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function onClickedNextYear() {
|
|
79
|
+
if (maxYear.value !== null && yearProxy.value >= maxYear.value) return;
|
|
80
|
+
yearProxy.value += 1;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function onClickedMonth(m: number) {
|
|
84
|
+
emit("update:modelValue", new Date(yearProxy.value, m - 1, 1));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
watch(modelValue, () => {
|
|
88
|
+
yearProxy.value =
|
|
89
|
+
modelValue.value?.getFullYear() || new Date().getFullYear();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
yearProxy,
|
|
94
|
+
minYear,
|
|
95
|
+
minMonth,
|
|
96
|
+
maxYear,
|
|
97
|
+
maxMonth,
|
|
98
|
+
year,
|
|
99
|
+
month,
|
|
100
|
+
onClickedPrevYear,
|
|
101
|
+
onClickedNextYear,
|
|
102
|
+
onClickedMonth,
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<style lang="stylus" scoped>
|
|
109
|
+
.month-picker
|
|
110
|
+
@apply s('w-[190px]') space-y-4
|
|
111
|
+
|
|
112
|
+
&--insight-title
|
|
113
|
+
.month-select
|
|
114
|
+
&-wrapper
|
|
115
|
+
.month-button
|
|
116
|
+
&.selected
|
|
117
|
+
@apply bg-insight-title
|
|
118
|
+
|
|
119
|
+
&--primary-hover
|
|
120
|
+
.month-select
|
|
121
|
+
&-wrapper
|
|
122
|
+
.month-button
|
|
123
|
+
&.selected
|
|
124
|
+
@apply bg-primary-hover
|
|
125
|
+
|
|
126
|
+
.year-select
|
|
127
|
+
@apply flex items-center justify-center space-x-10
|
|
128
|
+
|
|
129
|
+
.prev-button, .next-button
|
|
130
|
+
@apply inline-flex items-center text-xs text-gray-3
|
|
131
|
+
|
|
132
|
+
&.disabled
|
|
133
|
+
@apply text-gray-5 cursor-default
|
|
134
|
+
|
|
135
|
+
.year
|
|
136
|
+
@apply text-sm text-gray-2
|
|
137
|
+
|
|
138
|
+
.month-select
|
|
139
|
+
&-wrapper
|
|
140
|
+
@apply flex flex-wrap s('-my-2') s('-mx-4')
|
|
141
|
+
|
|
142
|
+
.month-button
|
|
143
|
+
@apply flex items-center justify-center s('w-[42px]') s('h-[27px]') s('my-2') s('mx-4') bg-white rounded text-sm text-gray-3
|
|
144
|
+
@apply s('hover:bg-primary-lighter') s('hover:text-white')
|
|
145
|
+
|
|
146
|
+
&.selected
|
|
147
|
+
@apply text-white
|
|
148
|
+
|
|
149
|
+
&.disabled
|
|
150
|
+
@apply bg-white text-cancel
|
|
151
|
+
</style>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.custom-multi-select
|
|
3
|
+
.options-wrapper.custom-scrollbar-y(ref='listWrapperEl')
|
|
4
|
+
.options
|
|
5
|
+
a.option-button(
|
|
6
|
+
v-for='option in formatedOptions',
|
|
7
|
+
href='javascript:;',
|
|
8
|
+
:class='{ selected: option.selected }',
|
|
9
|
+
@click='(e) => onClickedOption(e, option.value)'
|
|
10
|
+
)
|
|
11
|
+
span.option-label {{ option.label }}
|
|
12
|
+
span.btn.btn-apply(v-if='!option.selected') {{ applyTitle }}
|
|
13
|
+
span.btn.btn-cancel(v-else) {{ cancelTitle }}
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import type { PropType } from "vue";
|
|
18
|
+
import { isEqual } from "lodash-es";
|
|
19
|
+
import type IInputOption from "~ui/interface/IInputOption";
|
|
20
|
+
|
|
21
|
+
export default defineComponent({
|
|
22
|
+
name: "SLMultiSelect",
|
|
23
|
+
components: {},
|
|
24
|
+
props: {
|
|
25
|
+
modelValue: {
|
|
26
|
+
type: Array,
|
|
27
|
+
default: null,
|
|
28
|
+
},
|
|
29
|
+
options: {
|
|
30
|
+
type: Array as PropType<Array<IInputOption>>,
|
|
31
|
+
default: () => [],
|
|
32
|
+
},
|
|
33
|
+
applyTitle: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: "套用",
|
|
36
|
+
},
|
|
37
|
+
cancelTitle: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: "取消",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
emits: ["update:modelValue"],
|
|
43
|
+
setup(props, { emit }) {
|
|
44
|
+
const { modelValue, options } = toRefs(props);
|
|
45
|
+
|
|
46
|
+
const listWrapperEl = ref(null);
|
|
47
|
+
const listScrollbarEl = ref(null);
|
|
48
|
+
const modelProxy = ref(modelValue.value);
|
|
49
|
+
|
|
50
|
+
const formatedOptions = computed(() => {
|
|
51
|
+
return options.value.map((option) => {
|
|
52
|
+
return {
|
|
53
|
+
...option,
|
|
54
|
+
selected:
|
|
55
|
+
modelValue.value.findIndex((v) => isEqual(v, option.value)) > -1,
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function onClickedOption(_: Event, optionValue: any) {
|
|
61
|
+
const found = modelValue.value.find((v) => isEqual(v, optionValue));
|
|
62
|
+
if (found) {
|
|
63
|
+
emit(
|
|
64
|
+
"update:modelValue",
|
|
65
|
+
modelValue.value.filter((v) => !isEqual(v, optionValue))
|
|
66
|
+
);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
emit("update:modelValue", [...modelValue.value, optionValue]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
watch(modelProxy, () => {
|
|
74
|
+
emit("update:modelValue", modelProxy.value);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
listWrapperEl,
|
|
79
|
+
listScrollbarEl,
|
|
80
|
+
modelProxy,
|
|
81
|
+
formatedOptions,
|
|
82
|
+
onClickedOption,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<style lang="stylus">
|
|
89
|
+
.custom-multi-select
|
|
90
|
+
@apply px-3 py-1 border border-primary-light rounded
|
|
91
|
+
|
|
92
|
+
.options-wrapper
|
|
93
|
+
@apply relative overflow-auto
|
|
94
|
+
|
|
95
|
+
.options
|
|
96
|
+
@apply flex flex-col
|
|
97
|
+
|
|
98
|
+
.option-button
|
|
99
|
+
@apply flex justify-between items-center px-2 py-1 bg-white shadow-inline s("hover:bg-card-primary-light")
|
|
100
|
+
|
|
101
|
+
&:hover
|
|
102
|
+
.btn
|
|
103
|
+
@apply opacity-100
|
|
104
|
+
|
|
105
|
+
&.selected
|
|
106
|
+
.option-label
|
|
107
|
+
@apply text-primary font-bold
|
|
108
|
+
|
|
109
|
+
.btn
|
|
110
|
+
@apply bg-primary-hover opacity-100
|
|
111
|
+
|
|
112
|
+
&:hover, &:active, &:focus
|
|
113
|
+
.btn
|
|
114
|
+
@apply bg-primary
|
|
115
|
+
|
|
116
|
+
.option-label
|
|
117
|
+
@apply flex-grow inline-block text-base leading-normal text-gray-2 truncate
|
|
118
|
+
|
|
119
|
+
.btn
|
|
120
|
+
@apply flex-grow-0 flex-shrink-0 inline-block px-2 py-1 bg-primary rounded text-base leading-normal text-white opacity-0
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template lang="pug">
|
|
2
|
+
.no-data(:class='{ "no-data--sm": size === "sm" }')
|
|
3
|
+
img(src="images/commons/empty-box.png")
|
|
4
|
+
.no-data-title {{ title }}
|
|
5
|
+
.no-data-reason(v-if='reason') 可能的原因:{{ reason }}
|
|
6
|
+
</template>
|
|
7
|
+
|
|
8
|
+
<script lang="ts">
|
|
9
|
+
export default defineComponent({
|
|
10
|
+
name: "SLNoData",
|
|
11
|
+
components: {},
|
|
12
|
+
props: {
|
|
13
|
+
title: {
|
|
14
|
+
type: String,
|
|
15
|
+
default: "無資料",
|
|
16
|
+
},
|
|
17
|
+
reason: {
|
|
18
|
+
type: String,
|
|
19
|
+
default: "數據未匯入,或是未選擇網站",
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
type: String as PropType<"" | "sm">,
|
|
23
|
+
default: "",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
setup() {},
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<style lang="stylus" scoped>
|
|
31
|
+
.no-data
|
|
32
|
+
@apply flex flex-col items-center w-full py-8 px-4 space-y-3 bg-white rounded-lg
|
|
33
|
+
|
|
34
|
+
&--sm
|
|
35
|
+
.no-data-title
|
|
36
|
+
@apply text-sm
|
|
37
|
+
|
|
38
|
+
&-title
|
|
39
|
+
@apply text-lg text-primary-light
|
|
40
|
+
|
|
41
|
+
&-reason
|
|
42
|
+
@apply text-sm text-gray-3
|
|
43
|
+
</style>
|