@opendesign-plus/components 0.0.1-rc.2 → 0.0.1-rc.21

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 (205) hide show
  1. package/dist/chunk-OElCookieNotice.cjs.js +1 -0
  2. package/dist/chunk-OElCookieNotice.es.js +839 -0
  3. package/dist/components/OBanner.vue.d.ts +10 -1
  4. package/dist/components/OCookieNotice.vue.d.ts +6 -5
  5. package/dist/components/OFooter.vue.d.ts +46 -0
  6. package/dist/components/OHeaderSearch.vue.d.ts +10 -14
  7. package/dist/components/OHeaderUser.vue.d.ts +40 -0
  8. package/dist/components/OSourceCode.vue.d.ts +18 -0
  9. package/dist/components/activity/OActivityApproval.vue.d.ts +273 -0
  10. package/dist/components/activity/OActivityForm.vue.d.ts +138 -0
  11. package/dist/components/activity/OMyActivityCalendar.vue.d.ts +570 -0
  12. package/dist/components/activity/composables/useActivityConfig.d.ts +17 -0
  13. package/dist/components/activity/config.d.ts +1 -0
  14. package/dist/components/activity/index.d.ts +615 -0
  15. package/dist/components/activity/types.d.ts +88 -0
  16. package/dist/components/element-plus/OElCookieNotice.vue.d.ts +34 -0
  17. package/dist/components/element-plus/index.d.ts +2 -0
  18. package/dist/components/events/OEventsApply.vue.d.ts +16 -0
  19. package/dist/components/events/OEventsCalendar.vue.d.ts +5 -0
  20. package/dist/components/events/OEventsList.vue.d.ts +26 -0
  21. package/dist/components/events/config.d.ts +14 -0
  22. package/dist/components/events/index.d.ts +78 -0
  23. package/dist/components/events/types.d.ts +69 -0
  24. package/dist/components/events/utils.d.ts +7 -0
  25. package/dist/components/header/OHeader.vue.d.ts +30 -0
  26. package/dist/components/header/OHeaderMobile.vue.d.ts +178 -0
  27. package/dist/components/header/components/HeaderContent.vue.d.ts +13 -0
  28. package/dist/components/header/components/HeaderNav.vue.d.ts +19 -0
  29. package/dist/components/header/components/HeaderNavMobile.vue.d.ts +36 -0
  30. package/dist/components/header/index.d.ts +152 -0
  31. package/dist/components/header/types.d.ts +84 -0
  32. package/dist/components/meeting/OMeetingCalendar.vue.d.ts +295 -0
  33. package/dist/components/meeting/OMeetingForm.vue.d.ts +143 -0
  34. package/dist/components/meeting/OMeetingPlayback.vue.d.ts +45 -0
  35. package/dist/components/meeting/OMyMeetingCalendar.vue.d.ts +578 -0
  36. package/dist/components/meeting/OSigMeetingCalendar.vue.d.ts +24 -0
  37. package/dist/components/meeting/components/OMeetingCalendarList.vue.d.ts +28 -0
  38. package/dist/components/meeting/components/OMeetingCalendarSelector.vue.d.ts +664 -0
  39. package/dist/components/meeting/components/OMeetingDetail.vue.d.ts +13 -0
  40. package/dist/components/meeting/components/OMeetingPlaybackSubtitles.vue.d.ts +5 -0
  41. package/dist/components/meeting/components/OMeetingPlaybackVideo.vue.d.ts +17 -0
  42. package/dist/components/meeting/components/OSigMeetingAside.vue.d.ts +16 -0
  43. package/dist/components/meeting/composables/useMeetingConfig.d.ts +14 -0
  44. package/dist/components/meeting/config.d.ts +12 -0
  45. package/dist/components/meeting/index.d.ts +786 -0
  46. package/dist/components/meeting/types.d.ts +233 -0
  47. package/dist/components/meeting/utils.d.ts +8 -0
  48. package/dist/components.cjs.js +224 -1
  49. package/dist/components.css +1 -1
  50. package/dist/components.element.cjs.js +1 -0
  51. package/dist/components.element.es.js +4 -0
  52. package/dist/components.es.js +44417 -784
  53. package/dist/index.d.ts +9 -2
  54. package/docs/design.md +27 -27
  55. package/docs/design_banner.md +41 -41
  56. package/docs/design_section.md +27 -27
  57. package/package.json +13 -4
  58. package/scripts/generate-components-index.js +103 -80
  59. package/src/assets/events/svg-icons/icon-checked.svg +3 -0
  60. package/src/assets/events/svg-icons/icon-competition.svg +7 -0
  61. package/src/assets/events/svg-icons/icon-events.svg +4 -0
  62. package/src/assets/events/svg-icons/icon-release.svg +4 -0
  63. package/src/assets/events/svg-icons/icon-summit.svg +4 -0
  64. package/src/assets/meeting/svg-icons/icon-all.svg +3 -0
  65. package/src/assets/meeting/svg-icons/icon-backward.svg +4 -0
  66. package/src/assets/meeting/svg-icons/icon-calendar.svg +3 -0
  67. package/src/assets/meeting/svg-icons/icon-cancel.svg +4 -0
  68. package/src/assets/meeting/svg-icons/icon-captions.svg +4 -0
  69. package/src/assets/meeting/svg-icons/icon-close-captions.svg +6 -0
  70. package/src/assets/meeting/svg-icons/icon-close-fullscreen.svg +6 -0
  71. package/src/assets/meeting/svg-icons/icon-copy.svg +3 -0
  72. package/src/assets/meeting/svg-icons/icon-create.svg +5 -0
  73. package/src/assets/meeting/svg-icons/icon-delete.svg +7 -0
  74. package/src/assets/meeting/svg-icons/icon-empty.svg +31 -0
  75. package/src/assets/meeting/svg-icons/icon-empty_dark.svg +49 -0
  76. package/src/assets/meeting/svg-icons/icon-event.svg +3 -0
  77. package/src/assets/meeting/svg-icons/icon-export.svg +3 -0
  78. package/src/assets/meeting/svg-icons/icon-forward.svg +4 -0
  79. package/src/assets/meeting/svg-icons/icon-fullscreen.svg +6 -0
  80. package/src/assets/meeting/svg-icons/icon-help.svg +3 -0
  81. package/src/assets/meeting/svg-icons/icon-important.svg +4 -0
  82. package/src/assets/meeting/svg-icons/icon-info.svg +3 -0
  83. package/src/assets/meeting/svg-icons/icon-meet.svg +3 -0
  84. package/src/assets/meeting/svg-icons/icon-meeting-message.svg +5 -0
  85. package/src/assets/meeting/svg-icons/icon-meeting.svg +4 -0
  86. package/src/assets/meeting/svg-icons/icon-play.svg +5 -0
  87. package/src/assets/meeting/svg-icons/icon-playing-tip.svg +7 -0
  88. package/src/assets/meeting/svg-icons/icon-playing.svg +5 -0
  89. package/src/assets/meeting/svg-icons/icon-question.svg +4 -0
  90. package/src/assets/meeting/svg-icons/icon-sound.svg +5 -0
  91. package/src/assets/meeting/svg-icons/icon-speaker.svg +3 -0
  92. package/src/assets/meeting/svg-icons/icon-summit.svg +3 -0
  93. package/src/assets/meeting/svg-icons/icon-telligent.svg +3 -0
  94. package/src/assets/meeting/svg-icons/icon-tip.svg +3 -0
  95. package/src/assets/meeting/svg-icons/icon-todo.svg +4 -0
  96. package/src/assets/meeting/transparent.png +0 -0
  97. package/src/assets/svg-icons/icon-arrow-left.svg +3 -0
  98. package/src/assets/svg-icons/icon-avatar-line.svg +3 -0
  99. package/src/assets/svg-icons/icon-caret-left.svg +3 -0
  100. package/src/assets/svg-icons/icon-caret-right.svg +3 -0
  101. package/src/assets/svg-icons/icon-chevron-down.svg +3 -0
  102. package/src/assets/svg-icons/icon-chevron-right.svg +3 -3
  103. package/src/assets/svg-icons/icon-chevron-up.svg +3 -0
  104. package/src/assets/svg-icons/icon-close.svg +3 -3
  105. package/src/assets/svg-icons/icon-delete.svg +3 -3
  106. package/src/assets/svg-icons/icon-filter.svg +3 -0
  107. package/src/assets/svg-icons/icon-header-back.svg +3 -3
  108. package/src/assets/svg-icons/icon-header-delete.svg +3 -3
  109. package/src/assets/svg-icons/icon-header-menu.svg +3 -0
  110. package/src/assets/svg-icons/icon-header-person.svg +3 -0
  111. package/src/assets/svg-icons/icon-header-search.svg +4 -4
  112. package/src/assets/svg-icons/icon-loading.svg +4 -0
  113. package/src/assets/svg-icons/icon-locale.svg +3 -0
  114. package/src/assets/svg-icons/icon-log-off.svg +3 -0
  115. package/src/assets/svg-icons/icon-message.svg +3 -0
  116. package/src/assets/svg-icons/icon-moon.svg +3 -3
  117. package/src/assets/svg-icons/icon-outlink.svg +3 -0
  118. package/src/assets/svg-icons/icon-overview.svg +3 -0
  119. package/src/assets/svg-icons/icon-search.svg +3 -0
  120. package/src/assets/svg-icons/icon-setting.svg +3 -0
  121. package/src/assets/svg-icons/icon-sun.svg +3 -3
  122. package/src/assets/svg-icons/icon-tips.svg +3 -0
  123. package/src/components/OBanner.vue +398 -390
  124. package/src/components/OCookieNotice.vue +575 -424
  125. package/src/components/OFooter.vue +576 -0
  126. package/src/components/OHeaderSearch.vue +601 -601
  127. package/src/components/OHeaderUser.vue +237 -0
  128. package/src/components/OPlusConfigProvider.vue +32 -32
  129. package/src/components/OSection.vue +178 -178
  130. package/src/components/OSourceCode.vue +151 -0
  131. package/src/components/OThemeSwitcher.vue +108 -108
  132. package/src/components/activity/OActivityApproval.vue +864 -0
  133. package/src/components/activity/OActivityForm.vue +542 -0
  134. package/src/components/activity/OMyActivityCalendar.vue +1502 -0
  135. package/src/components/activity/composables/useActivityConfig.ts +141 -0
  136. package/src/components/activity/config.ts +1 -0
  137. package/src/components/activity/index.ts +24 -0
  138. package/src/components/activity/types.ts +95 -0
  139. package/src/components/common/AppAvatar.vue +83 -0
  140. package/src/components/common/ClientOnlyWrapper.ts +21 -21
  141. package/src/components/common/ContentWrapper.vue +85 -85
  142. package/src/components/common/MoreText.vue +124 -0
  143. package/src/components/common/ThFilter.vue +330 -0
  144. package/src/components/element-plus/OElCookieNotice.vue +603 -0
  145. package/src/components/element-plus/index.ts +3 -0
  146. package/src/components/events/OEventsApply.vue +418 -0
  147. package/src/components/events/OEventsCalendar.vue +598 -0
  148. package/src/components/events/OEventsList.vue +389 -0
  149. package/src/components/events/config.ts +35 -0
  150. package/src/components/events/index.ts +24 -0
  151. package/src/components/events/types.ts +83 -0
  152. package/src/components/events/utils.ts +9 -0
  153. package/src/components/header/OHeader.vue +157 -0
  154. package/src/components/header/OHeaderMobile.vue +184 -0
  155. package/src/components/header/components/HeaderContent.vue +1125 -0
  156. package/src/components/header/components/HeaderNav.vue +278 -0
  157. package/src/components/header/components/HeaderNavMobile.vue +380 -0
  158. package/src/components/header/index.ts +16 -0
  159. package/src/components/header/types.ts +95 -0
  160. package/src/components/meeting/OMeetingCalendar.vue +890 -0
  161. package/src/components/meeting/OMeetingForm.vue +1072 -0
  162. package/src/components/meeting/OMeetingPlayback.vue +439 -0
  163. package/src/components/meeting/OMyMeetingCalendar.vue +1508 -0
  164. package/src/components/meeting/OSigMeetingCalendar.vue +413 -0
  165. package/src/components/meeting/components/OMeetingCalendarList.vue +515 -0
  166. package/src/components/meeting/components/OMeetingCalendarSelector.vue +210 -0
  167. package/src/components/meeting/components/OMeetingDetail.vue +244 -0
  168. package/src/components/meeting/components/OMeetingPlaybackSubtitles.vue +611 -0
  169. package/src/components/meeting/components/OMeetingPlaybackVideo.vue +741 -0
  170. package/src/components/meeting/components/OSigMeetingAside.vue +199 -0
  171. package/src/components/meeting/composables/useMeetingConfig.ts +108 -0
  172. package/src/components/meeting/config.ts +48 -0
  173. package/src/components/meeting/index.ts +45 -0
  174. package/src/components/meeting/types.ts +266 -0
  175. package/src/components/meeting/utils.ts +70 -0
  176. package/src/draft/Banner.vue +265 -265
  177. package/src/draft/ButtonCards.vue +105 -105
  178. package/src/draft/Feature.vue +133 -133
  179. package/src/draft/Footer.vue +512 -512
  180. package/src/draft/HorizontalAnchor.vue +165 -165
  181. package/src/draft/ItemSwiper.vue +133 -133
  182. package/src/draft/Logo.vue +141 -141
  183. package/src/draft/LogoCard.vue +74 -74
  184. package/src/draft/LogoV2.vue +19 -19
  185. package/src/draft/MainCard.vue +38 -38
  186. package/src/draft/MultiCard.vue +94 -94
  187. package/src/draft/MultiIconCard.vue +73 -73
  188. package/src/draft/OInfoCard.vue +176 -176
  189. package/src/draft/Process.vue +81 -81
  190. package/src/draft/Section.vue +167 -167
  191. package/src/draft/SingleTabCard.vue +84 -84
  192. package/src/draft/SliderCard.vue +110 -110
  193. package/src/env.d.ts +16 -1
  194. package/src/i18n/en.ts +264 -20
  195. package/src/i18n/index.ts +56 -42
  196. package/src/i18n/zh.ts +253 -9
  197. package/src/index.ts +14 -3
  198. package/src/shared/provide.ts +6 -6
  199. package/src/shims-vue-dompurify-html.d.ts +17 -0
  200. package/src/vue.d.ts +9 -9
  201. package/tsconfig.json +37 -33
  202. package/vite.config.ts +119 -94
  203. package/dist/components/OCookieNoticeEl.vue.d.ts +0 -29
  204. package/dist/components.umd.js +0 -1
  205. package/src/components/OCookieNoticeEl.vue +0 -404
@@ -0,0 +1,1508 @@
1
+ <script setup lang="ts">
2
+ import {
3
+ DialogActionT,
4
+ OCollapse,
5
+ OCollapseItem,
6
+ ODialog,
7
+ ODivider,
8
+ OIcon,
9
+ OIconArrowLeft,
10
+ OIconArrowRight,
11
+ OIconChevronLeft,
12
+ OIconChevronRight,
13
+ OLink,
14
+ ORadio,
15
+ ORadioGroup,
16
+ OScroller,
17
+ OTag,
18
+ useMessage,
19
+ } from '@opensig/opendesign';
20
+ import { ElCalendar } from 'element-plus';
21
+ import { computed, nextTick, onMounted, ref, onUnmounted, watch } from 'vue';
22
+ import OMeetingDetail from './components/OMeetingDetail.vue';
23
+ import { MeetingGroupType, MeetingItemT, PageParamsT } from './types.ts';
24
+ import dayjs from 'dayjs';
25
+ import IconMeeting from '~icons/meeting/icon-meet.svg';
26
+ import { INTERVAL_DAY, INTERVAL_MONTH, INTERVAL_WEEK } from './config.ts';
27
+ import { formatDate, getDateNumber } from './utils.ts';
28
+ import { useMeetingConfig } from './composables/useMeetingConfig';
29
+ import { useDebounceFn } from '@vueuse/core';
30
+ import { useScreen } from '@opendesign-plus/composables';
31
+ import { Locales } from '@/i18n';
32
+ import IconCopy from '~icons/meeting/icon-copy.svg';
33
+
34
+ const message = useMessage(null);
35
+ const tableLoading = ref(false); // 列表数据加载状态
36
+ const currentPage = ref(1); // 分页-当前页
37
+ const pageSize = ref(50); // 分页-每页数量
38
+ const total = ref<number | null>(null); // 分页-总数
39
+ const list = ref<MeetingItemT[]>([]); // 列表数据
40
+ const originList = ref<MeetingItemT[]>([]); // 原始数据
41
+ const canLoadMore = computed(() => total.value === null || originList.value.length < total.value);
42
+ const reloadAll = ref(false); // 是否需要清空数据
43
+ const { t, locale, getPointStr, getWeekFromDate } = useMeetingConfig();
44
+ const isEn = computed(() => locale.value === Locales.EN);
45
+
46
+ const props = withDefaults(defineProps<{
47
+ cancelSubMeetingRequest: any;
48
+ deleteMeetingRequest: any;
49
+ getMeetingListRequest: any;
50
+ groupType: MeetingGroupType
51
+ }>(), {
52
+ groupType: MeetingGroupType.SIG,
53
+ });
54
+
55
+ const dialogLoading = ref(false); // 弹窗按钮状态
56
+ const { isPhone } = useScreen();
57
+ const expanded = ref<(string | number)[]>([]); // 展开的数据, sub_id 或 id
58
+ const emits = defineEmits(['edit']);
59
+ // -------------------- 自动加载下一页 --------------------
60
+ const nextLoading = ref(false);
61
+ const bottomReached = ref(false);
62
+ const headerRef = ref();
63
+ const headerHeight = ref(0);
64
+ const getHeaderHeight = () => {
65
+ headerHeight.value = headerRef.value?.clientHeight || 0;
66
+ };
67
+
68
+ const getList = async () => {
69
+ if (!props.getMeetingListRequest) {
70
+ return;
71
+ }
72
+ if (nextLoading.value) {
73
+ return;
74
+ }
75
+ try {
76
+ if (total.value !== null && total.value > 0 && (currentPage.value - 1) * pageSize.value > total.value) {
77
+ return;
78
+ }
79
+ // 当月数据已加载完,手动切换下一月
80
+ if (!canLoadMore.value && isPhone.value) {
81
+ changeMonth('next-month');
82
+ return;
83
+ }
84
+ tableLoading.value = true;
85
+ nextLoading.value = true;
86
+ const res = await props.getMeetingListRequest({
87
+ page: currentPage.value,
88
+ size: pageSize.value,
89
+ order_by: 'date',
90
+ order_type: 'asc',
91
+ month: dayjs(selectedDate.value).format('YYYY-MM'),
92
+ } as unknown as PageParamsT);
93
+ const tempList = (res.data || [])
94
+ .map((item: MeetingItemT) => {
95
+ const {
96
+ is_cycle,
97
+ date,
98
+ start,
99
+ end,
100
+ cycle_sub,
101
+ cycle_start_date,
102
+ cycle_end_date,
103
+ cycle_start,
104
+ cycle_end,
105
+ cycle_type,
106
+ cycle_interval,
107
+ cycle_point,
108
+ } =
109
+ item;
110
+ if (is_cycle) {
111
+ let timeRange = '';
112
+ let cycleType = '';
113
+ if (cycle_type === INTERVAL_DAY) {
114
+ cycleType = t('meeting.cycleDay');
115
+ }
116
+ if (cycle_type === INTERVAL_WEEK) {
117
+ if (cycle_interval > 1) {
118
+ cycleType = t('meeting.cycleWeek.other', [getPointStr(cycle_type, cycle_point), cycle_interval]);
119
+ } else {
120
+ cycleType = t('meeting.cycleWeek.one', [getPointStr(cycle_type, cycle_point)]);
121
+ }
122
+ }
123
+ if (cycle_type === INTERVAL_MONTH) {
124
+ cycleType = t('meeting.cycleMonth', [getPointStr(cycle_type, cycle_point)]);
125
+ }
126
+ timeRange = t('meeting.cycleMeetingText2', {
127
+ startDate: cycle_start_date,
128
+ endDate: cycle_end_date,
129
+ startTime: cycle_start,
130
+ endTime: cycle_end,
131
+ cycleType,
132
+ });
133
+ const obsData = item.obs_data?.filter((v) => v.text_video_url) || [];
134
+ return cycle_sub
135
+ .filter((v) => {
136
+ return (
137
+ dayjs(v.date).isBefore(dayjs(selectedDate.value).add(1, 'month').format('YYYY-MM-01')) &&
138
+ !dayjs(v.date).isBefore(dayjs(selectedDate.value).format('YYYY-MM-01'))
139
+ );
140
+ })
141
+ .map(({ id: _id, ...sub }) => {
142
+ return {
143
+ ...item,
144
+ ...sub,
145
+ timeRange: timeRange,
146
+ dateRange: `${ sub.start }-${ sub.end }`,
147
+ hasObsData: obsData.find((v) => v.sub_id === sub.sub_id),
148
+ time: `${ sub.start }-${ sub.end }`,
149
+ isExpired: dayjs(`${ sub.date } ${ sub.start }`).isBefore(dayjs()),
150
+ };
151
+ });
152
+ }
153
+ return [
154
+ {
155
+ ...item,
156
+ dateRange: `${ start } - ${ end }`,
157
+ timeRange: `${ start } - ${ end }`,
158
+ hasObsData: !!item.obs_data?.filter((v) => v.text_video_url)?.length,
159
+ time: `${ start }-${ end }`,
160
+ isExpired: dayjs(`${ date } ${ start }`).isBefore(dayjs()),
161
+ },
162
+ ];
163
+ })
164
+ .flat()
165
+ .filter((v: MeetingItemT) => v.date.slice(0, 7) === formatDate(selectedDate.value, 'YYYY-MM-DD').slice(0, 7));
166
+ // 如果需要清空,则完全替换
167
+ // 如果页码为1,表示第一次加载
168
+ // 如果是移动端,则一直往里填充数据
169
+ if (reloadAll.value) {
170
+ originList.value = res.data || [];
171
+ list.value = tempList;
172
+ } else {
173
+ if (currentPage.value === 1 && !isPhone.value) {
174
+ originList.value = res.data || [];
175
+ list.value = tempList;
176
+ } else {
177
+ originList.value = [...originList.value, ...(res.data || [])];
178
+ list.value = [...list.value, ...tempList];
179
+ }
180
+ }
181
+ list.value.sort((a, b) => {
182
+ if (a.date === b.date) {
183
+ return getDateNumber(a.start) > getDateNumber(b.start) ? 1 : -1;
184
+ } else {
185
+ return dayjs(a.date).isAfter(dayjs(b.date)) ? 1 : -1;
186
+ }
187
+ });
188
+ total.value = res?.total || 0;
189
+ nextTick(() => {
190
+ getSelectedDate();
191
+ // 监听滚动事件以自动加载下一页
192
+ if (scrollerRef.value && !isPhone.value) {
193
+ updateScroller();
194
+ }
195
+ });
196
+ } finally {
197
+ tableLoading.value = false;
198
+ nextLoading.value = false;
199
+ bottomReached.value = false;
200
+ reloadAll.value = false;
201
+ }
202
+ };
203
+
204
+ const load = useDebounceFn(() => {
205
+ if (!canLoadMore.value) return;
206
+ if (isPhone.value) return;
207
+ bottomReached.value = true;
208
+ currentPage.value++;
209
+ getList();
210
+ }, 200);
211
+ const handleScroll = useDebounceFn(() => {
212
+ if (!canLoadMore.value) return;
213
+ if (!isPhone.value) return;
214
+ const scrollPosition = window.scrollY || window.pageYOffset;
215
+ // 检测是否接近底部
216
+ const windowHeight = window.innerHeight;
217
+ const docHeight = document.documentElement.scrollHeight;
218
+ const distanceToBottom = docHeight - (scrollPosition + windowHeight);
219
+ // 当距离底部300px时开始加载
220
+ if (distanceToBottom <= 300 && !nextLoading.value) {
221
+ bottomReached.value = true;
222
+ // 加载下一页
223
+ currentPage.value++;
224
+ getList();
225
+ }
226
+ }, 200);
227
+ const calcIfAllDeleted = (date: string) => {
228
+ const meetingsOfDate = list.value.filter((v) => v.date === date);
229
+ return meetingsOfDate.length && meetingsOfDate.every((v) => v.is_delete);
230
+ };
231
+
232
+ const scrollerScroll = (el: any) => {
233
+ const container = el.target;
234
+ if (!container) return;
235
+ const scrollTop = container.scrollTop; // 已经滚动的距离
236
+ const scrollHeight = container.scrollHeight; // 内容总高度
237
+ const clientHeight = container.clientHeight; // 容器可视高度
238
+ if (scrollTop + clientHeight >= scrollHeight) {
239
+ load();
240
+ }
241
+ };
242
+ const updateScroller = () => {
243
+ const scrollerContainerEl = scrollerRef.value.getContainerEl();
244
+ scrollerContainerEl.addEventListener('scroll', scrollerScroll);
245
+ };
246
+
247
+ const detailRefs = ref<Record<string, any>>({}); // 会议详情组件实例
248
+ const getDetailRefs = (insRef: any, id: number) => {
249
+ if (insRef && id) {
250
+ detailRefs.value[id] = insRef;
251
+ }
252
+ };
253
+ const copyInfo = async (idx: number) => {
254
+ const instance = detailRefs.value[idx];
255
+ await instance.copyInfo();
256
+ message.success({
257
+ content: t('common.copySuccess'),
258
+ });
259
+ };
260
+ // -------------------- 取消 --------------------
261
+ const cancelVisible = ref(false); // 取消弹窗
262
+ const currentRow = ref<MeetingItemT | null>(null); // 当前激活行,用于取消事件
263
+ // 打开编辑会议弹窗
264
+ const editMeeting = (row: MeetingItemT) => {
265
+ emits('edit', row, 'whole');
266
+ };
267
+ // 打开取消会议弹窗
268
+ const cancelMeeting = (row: MeetingItemT) => {
269
+ currentRow.value = row;
270
+ cancelVisible.value = true;
271
+ };
272
+ // 确定取消会议
273
+ const confirmCancel = async () => {
274
+ if (!props.deleteMeetingRequest) {
275
+ return;
276
+ }
277
+ if (!currentRow.value) {
278
+ return;
279
+ }
280
+ try {
281
+ dialogLoading.value = true;
282
+ await props.deleteMeetingRequest(currentRow.value.id);
283
+ cancelVisible.value = false;
284
+ message.success({
285
+ content: `${ t('meeting.meetingCancel', [currentRow.value.topic]) }`,
286
+ });
287
+ reloadAll.value = true;
288
+ getList();
289
+ } finally {
290
+ dialogLoading.value = false;
291
+ }
292
+ };
293
+ const handleDialogVisible = ref(false);
294
+ const handleDialogType = ref('');
295
+ const handleDialogRow = ref<MeetingItemT | null>(null);
296
+ const handleOptions = [
297
+ {
298
+ label: t('meeting.meetingSingle'),
299
+ value: 'single',
300
+ },
301
+ {
302
+ label: t('meeting.meetingCycle'),
303
+ value: 'whole',
304
+ },
305
+ ];
306
+
307
+ const handleType = ref('single');
308
+ const handleItem = (row: MeetingItemT, type: 'edit' | 'cancel') => {
309
+ if (row.is_cycle) {
310
+ handleDialogRow.value = row;
311
+ handleDialogType.value = type;
312
+ handleDialogVisible.value = true;
313
+ } else {
314
+ if (type === 'cancel') {
315
+ cancelMeeting(row);
316
+ } else {
317
+ editMeeting(row);
318
+ }
319
+ }
320
+ };
321
+ const cancelHandleItem = () => {
322
+ handleDialogVisible.value = false;
323
+ handleDialogRow.value = null;
324
+ handleDialogType.value = '';
325
+ handleType.value = 'single';
326
+ };
327
+ const confirmHandleItem = async () => {
328
+ const row = handleDialogRow.value;
329
+ if (!row) {
330
+ return;
331
+ }
332
+ if (handleDialogType.value === 'cancel') {
333
+ try {
334
+ dialogLoading.value = true;
335
+ if (handleType.value === 'single' && row.is_cycle) {
336
+ if (!props.cancelSubMeetingRequest) {
337
+ return;
338
+ }
339
+ await props.cancelSubMeetingRequest(row.sub_id);
340
+ message.success({
341
+ content: `${ t('meeting.meetingCancel', [row.topic]) }`,
342
+ });
343
+ } else {
344
+ if (!props.deleteMeetingRequest) {
345
+ return;
346
+ }
347
+ await props.deleteMeetingRequest(row.id);
348
+ message.success({
349
+ content: `${ t('meeting.meetingCancel', [row.topic]) }`,
350
+ });
351
+ }
352
+ cancelHandleItem();
353
+ reloadAll.value = true;
354
+ getList();
355
+ } finally {
356
+ dialogLoading.value = false;
357
+ }
358
+ } else {
359
+ emits('edit', row, handleType.value);
360
+ cancelHandleItem();
361
+ }
362
+ };
363
+
364
+ // -------------------- 日历 --------------------
365
+ const calendarRef = ref();
366
+ const allDateList = computed<string[]>(() => [...new Set(list.value.map((v) => v.date))].sort((a, b) => (dayjs(a).isBefore(dayjs(b)) ? -1 : 1)));
367
+ const dateList = computed<string[]>(() =>
368
+ [...new Set(list.value.filter((v) => !v.isExpired && !v.is_delete).map((v) => v.date))].sort((a, b) => (dayjs(a).isBefore(dayjs(b)) ? -1 : 1)),
369
+ );
370
+ const selectedDate = ref();
371
+
372
+ const getSelectedDate = () => {
373
+ const latest = dateList.value.find((v) => !dayjs(v).isBefore(dayjs(new Date()).format('YYYY-MM-DD')));
374
+ if (latest) {
375
+ selectedDate.value = latest;
376
+ } else if (!selectedDate.value) {
377
+ selectedDate.value = dayjs().format('YYYY-MM-DD');
378
+ }
379
+ calendarRef.value?.pickDay(dayjs(selectedDate.value));
380
+ selectedDate.value = dayjs(selectedDate.value).format('YYYY-MM-DD');
381
+ // 根据天再计算出需要展开的最近的会议
382
+ const needExpand = list.value.find((v) => v.date === selectedDate.value && !v.isExpired && !v.is_delete);
383
+ if (needExpand) {
384
+ expanded.value = [needExpand.sub_id || needExpand.id];
385
+ }
386
+ };
387
+
388
+ const cellClick = (e: PointerEvent & any, clickable: boolean) => {
389
+ if (!clickable || !e.target?.className.includes('date-cell-text')) {
390
+ e.stopPropagation();
391
+ e.preventDefault();
392
+ }
393
+ };
394
+
395
+ const changeMonth = (val: string) => {
396
+ if (!calendarRef.value) return;
397
+ currentPage.value = 1;
398
+ total.value = null;
399
+ window.scrollTo({
400
+ top: 0,
401
+ behavior: 'smooth',
402
+ });
403
+ calendarRef.value.selectDate(val);
404
+ reloadAll.value = true;
405
+ nextTick(() => {
406
+ selectedDate.value = dayjs(calendarRef.value.selectedDay).format('YYYY-MM-DD');
407
+ getList();
408
+ });
409
+ };
410
+ // -------------------- 列表 --------------------
411
+ interface GroupListItemT {
412
+ date: string;
413
+ list: MeetingItemT[];
414
+ }
415
+
416
+ const groupList = computed(() => {
417
+ return list.value.reduce((prev: GroupListItemT[], cur: MeetingItemT) => {
418
+ if (!prev.length) {
419
+ return [
420
+ {
421
+ date: cur.date,
422
+ list: [cur],
423
+ },
424
+ ];
425
+ } else {
426
+ const last = prev.at(-1);
427
+ if (last?.date === cur.date) {
428
+ last.list.push(cur);
429
+ } else {
430
+ prev.push({
431
+ date: cur.date,
432
+ list: [cur],
433
+ });
434
+ }
435
+ return prev;
436
+ }
437
+ }, []);
438
+ });
439
+
440
+ // -------------------- 处理滚动事件 --------------------
441
+ const scrollerRef = ref();
442
+ watch(
443
+ () => selectedDate.value,
444
+ () => {
445
+ scrollToSelectedDate(selectedDate.value);
446
+ },
447
+ );
448
+ const scrollToSelectedDate = (date: string) => {
449
+ const key = dayjs(date).format('YYYY-MM-DD');
450
+ const targetEle = document.querySelector(`#group-title-${ key }`);
451
+ if (targetEle) {
452
+ if (isPhone.value) {
453
+ window.scrollTo({
454
+ top: (targetEle.parentElement?.offsetTop || 0) - 52,
455
+ behavior: 'smooth',
456
+ });
457
+ } else {
458
+ scrollerRef.value?.scrollTo({
459
+ top: targetEle.parentElement?.offsetTop || 0,
460
+ behavior: 'smooth',
461
+ });
462
+ }
463
+ }
464
+ };
465
+ onMounted(() => {
466
+ getList();
467
+ // 添加滚动事件监听
468
+ window.addEventListener('scroll', handleScroll);
469
+ // 添加resize监听器
470
+ window.addEventListener('resize', handleScroll);
471
+ getHeaderHeight();
472
+ window.addEventListener('resize', getHeaderHeight);
473
+ });
474
+
475
+ onUnmounted(() => {
476
+ window.removeEventListener('scroll', handleScroll);
477
+ window.removeEventListener('resize', handleScroll);
478
+ window.removeEventListener('resize', getHeaderHeight);
479
+ const scrollerContainerEl = scrollerRef.value?.getContainerEl();
480
+ scrollerContainerEl?.removeEventListener('scroll', scrollerScroll);
481
+ });
482
+
483
+ const formatYearMonth = (date: string) => {
484
+ const date2 = dayjs(date ? date : undefined);
485
+ if (isEn.value) {
486
+ return date2.format('MMMM YYYY');
487
+ } else {
488
+ return date2.format('YYYY MM月');
489
+ }
490
+ };
491
+ const handleActions = computed<DialogActionT[]>(() => {
492
+ return [
493
+ {
494
+ id: 'confirm',
495
+ color: 'primary',
496
+ label: t('common.confirm'),
497
+ variant: 'solid',
498
+ size: 'large',
499
+ round: 'pill',
500
+ loading: dialogLoading.value,
501
+ onClick: () => {
502
+ confirmHandleItem();
503
+ },
504
+ },
505
+ {
506
+ id: 'cancel',
507
+ color: 'primary',
508
+ label: t('common.cancel'),
509
+ variant: 'outline',
510
+ size: 'large',
511
+ round: 'pill',
512
+ onClick: () => {
513
+ cancelHandleItem();
514
+ },
515
+ },
516
+ ];
517
+ });
518
+ const cancelActions = computed<DialogActionT[]>(() => {
519
+ return [
520
+ {
521
+ id: 'confirm',
522
+ color: 'primary',
523
+ label: t('common.confirm'),
524
+ variant: 'solid',
525
+ size: 'large',
526
+ round: 'pill',
527
+ loading: dialogLoading.value,
528
+ onClick: () => {
529
+ confirmCancel();
530
+ },
531
+ },
532
+ {
533
+ id: 'cancel',
534
+ color: 'primary',
535
+ label: t('common.cancel'),
536
+ variant: 'outline',
537
+ size: 'large',
538
+ round: 'pill',
539
+ onClick: () => {
540
+ cancelVisible.value = false;
541
+ },
542
+ },
543
+ ];
544
+ });
545
+ </script>
546
+
547
+ <template>
548
+ <div class="o-my-meeting-calendar">
549
+ <div class="meeting-list">
550
+ <div class="list-calendar-mb">
551
+ <span>{{ formatYearMonth(selectedDate) }}</span>
552
+ <span>
553
+ <OIcon @click="changeMonth('prev-month')"><OIconChevronLeft /></OIcon>
554
+ <OIcon @click="changeMonth('next-month')"><OIconChevronRight /></OIcon>
555
+ </span>
556
+ </div>
557
+ <div class="list-calendar">
558
+ <ElCalendar ref="calendarRef" v-model="selectedDate">
559
+ <template #header>
560
+ <span>{{ formatYearMonth(selectedDate) }}</span>
561
+ <div>
562
+ <OIcon @click="changeMonth('prev-month')">
563
+ <OIconChevronLeft />
564
+ </OIcon>
565
+ <OIcon @click="changeMonth('next-month')">
566
+ <OIconChevronRight />
567
+ </OIcon>
568
+ </div>
569
+ </template>
570
+ <template #date-cell="{ data }">
571
+ <div
572
+ @click="(e) => cellClick(e, allDateList.includes(data.day))"
573
+ :class="{
574
+ 'date-cell': true,
575
+ 'is-selected': data.isSelected,
576
+ 'is-today': formatDate(data.day) === formatDate(),
577
+ clickable: allDateList.includes(data.day),
578
+ expired: dayjs(formatDate()).isAfter(dayjs(data.day)),
579
+ 'all-deleted': calcIfAllDeleted(data.day),
580
+ }"
581
+ >
582
+ <div class="date-cell-text">
583
+ {{ Number(data.day.split('-')[2]) }}
584
+ </div>
585
+ </div>
586
+ </template>
587
+ </ElCalendar>
588
+ </div>
589
+ <div class="list-wrapper" :class="!list.length ? 'is-empty' : ''">
590
+ <OScroller
591
+ ref="scrollerRef"
592
+ @scrollend="load"
593
+ v-if="list.length"
594
+ :style="{ '--header-height': headerHeight }"
595
+ class="scroller-container"
596
+ show-type="hover"
597
+ >
598
+ <div class="list-body">
599
+ <OCollapse v-model="expanded" :accordion="isPhone">
600
+ <template v-for="(group, idx) in groupList" :key="group.date">
601
+ <div class="list-month-change prev-month" v-if="idx === 0" @click="changeMonth('prev-month')">
602
+ <OIcon>
603
+ <OIconArrowLeft />
604
+ </OIcon>
605
+ <span>{{ t('meeting.preMonth') }}</span>
606
+ </div>
607
+ <div class="group-item" :class="idx === groupList.length - 1 && 'last-item'">
608
+ <div
609
+ :class="{
610
+ 'group-bar': true,
611
+ 'is-active': dayjs(selectedDate).format('YYYY-MM-DD') === group.date,
612
+ 'is-end': group.list.every((row) => row.isExpired),
613
+ }"
614
+ >
615
+ <div class="group-bar-line"></div>
616
+ <div class="group-bar-dot"></div>
617
+ </div>
618
+ <div
619
+ :class="{
620
+ 'group-title': true,
621
+ 'is-end': group.list.every((row) => row.isExpired),
622
+ }"
623
+ :id="`group-title-${dayjs(new Date(group.date)).format('YYYY-MM-DD')}`"
624
+ >
625
+ {{ dayjs(group.date).format('MM/DD') }} {{ getWeekFromDate(group.date) }}
626
+ </div>
627
+ <OCollapseItem
628
+ v-for="(row, rowIdx) in group.list"
629
+ :key="row.sub_id || row.id"
630
+ :value="row.sub_id || row.id"
631
+ :class="{
632
+ 'last-item': idx === groupList.length - 1 && rowIdx === group.list.length - 1,
633
+ }"
634
+ >
635
+ <template #title>
636
+ <div class="item-header-left">
637
+ <div class="meeting-icon">
638
+ <OIcon>
639
+ <IconMeeting />
640
+ </OIcon>
641
+ </div>
642
+ <div class="header-info">
643
+ <div
644
+ :class="{
645
+ 'meeting-title': true,
646
+ 'is-delete': row.is_delete,
647
+ 'is-end': row.isExpired,
648
+ }"
649
+ >
650
+ <div v-if="row.is_delete">{{ t('meeting.meetingCancelled') }}</div>
651
+ <div class="title-wrapper">
652
+ <div class="title-text">{{ row.topic }}</div>
653
+ </div>
654
+ <div class="tag-wrapper" v-if="row.is_cycle">
655
+ <OTag color="primary" variant="outline">{{ t('meeting.cycle') }}</OTag>
656
+ </div>
657
+ </div>
658
+ <div class="meeting-info">
659
+ <span>{{ row.dateRange }}</span>
660
+ <ODivider direction="v" />
661
+ <span>
662
+ {{ groupType === MeetingGroupType.GROUP ? t('meeting.groups') : t('meeting.sigs')
663
+ }}: {{ row.group_name }}
664
+ </span>
665
+ </div>
666
+ </div>
667
+ </div>
668
+ <div class="item-header-right" v-if="!row.is_delete">
669
+ <OLink
670
+ v-if="!row.isExpired"
671
+ target="_blank"
672
+ :href="row.join_url"
673
+ rel="noopener noreferrer"
674
+ >
675
+ {{ t('meeting.joinMeeting') }}
676
+ <template #suffix>
677
+ <OIcon>
678
+ <OIconChevronRight></OIconChevronRight>
679
+ </OIcon>
680
+ </template>
681
+ </OLink>
682
+ <template v-else>
683
+ <OLink target="_blank" v-if="row.etherpad" :href="row.etherpad">
684
+ {{ t('meeting.meetingSummary') }}
685
+ <template #suffix>
686
+ <OIcon>
687
+ <OIconChevronRight></OIconChevronRight>
688
+ </OIcon>
689
+ </template>
690
+ </OLink>
691
+ <OLink
692
+ target="_blank"
693
+ :href="`/${locale}/video/${row.group_name}/${row.mid}/${row.date}`"
694
+ v-if="row.hasObsData">
695
+ {{ t('meeting.viewRecord') }}
696
+ <template #suffix>
697
+ <OIcon>
698
+ <OIconChevronRight></OIconChevronRight>
699
+ </OIcon>
700
+ </template>
701
+ </OLink>
702
+ </template>
703
+ </div>
704
+ <OIcon @click.stop="() => copyInfo(rowIdx)" class="copy-icon">
705
+ <IconCopy />
706
+ </OIcon>
707
+ </template>
708
+ <div class="meeting-detail">
709
+ <OMeetingDetail
710
+ :show="expanded.includes(row.sub_id || row.id)"
711
+ :data="row"
712
+ :ref="(insRef) => getDetailRefs(insRef, row.id)"
713
+ from="my"
714
+ />
715
+ <div class="meeting-btn" v-if="!row.isExpired && !row.is_delete">
716
+ <OLink @click="handleItem(row, 'edit')">{{ t('meeting.editMeeting') }}
717
+ </OLink>
718
+ <OLink @click="handleItem(row, 'cancel')">{{ t('meeting.cancelMeeting') }}
719
+ </OLink>
720
+ </div>
721
+ </div>
722
+ </OCollapseItem>
723
+ <div class="height-placeholder"></div>
724
+ </div>
725
+ <template v-if="idx === groupList.length - 1">
726
+ <div class="load-text" v-if="bottomReached">{{ t('common.loading') }}</div>
727
+ <div class="list-month-change next-month" @click="changeMonth('next-month')">
728
+ <OIcon>
729
+ <OIconArrowRight />
730
+ </OIcon>
731
+ <span>{{ t('meeting.nextMonth') }}</span>
732
+ </div>
733
+ </template>
734
+ </template>
735
+ </OCollapse>
736
+ </div>
737
+ </OScroller>
738
+ <slot name="empty" v-else-if="!tableLoading"></slot>
739
+ </div>
740
+ <ODialog
741
+ v-model:visible="handleDialogVisible"
742
+ main-class="handle-dialog"
743
+ @close="cancelHandleItem"
744
+ :actions="handleActions"
745
+ >
746
+ <template #header>
747
+ {{
748
+ t('meeting.meetingHandleText', [handleDialogType === 'edit' ? t('meeting.edit') : t('meeting.cancel2')])
749
+ }}
750
+ </template>
751
+ <ORadioGroup v-model="handleType">
752
+ <ORadio v-for="t in handleOptions" :value="t.value" :key="t.value">{{ t.label }}</ORadio>
753
+ </ORadioGroup>
754
+ </ODialog>
755
+ <ODialog v-model:visible="cancelVisible" main-class="cancel-dialog" :actions="cancelActions">
756
+ <template #header>{{ t('meeting.confirmCancel') }}</template>
757
+ <div class="dialog-content">{{ t('meeting.confirmCancelDesc', [currentRow?.topic]) }}</div>
758
+ </ODialog>
759
+ </div>
760
+ </div>
761
+ </template>
762
+
763
+ <style lang="scss">
764
+ .o-my-meeting-calendar {
765
+
766
+ .header {
767
+ display: flex;
768
+ align-items: center;
769
+ justify-content: space-between;
770
+
771
+ .title {
772
+ font-weight: 500;
773
+ color: var(--o-color-info1);
774
+ @include h2;
775
+ }
776
+
777
+ .desc {
778
+ margin-top: 12px;
779
+ color: var(--o-color-info2);
780
+ @include tip1;
781
+ }
782
+ }
783
+
784
+ .meeting-list {
785
+ display: flex;
786
+ flex-wrap: nowrap;
787
+ gap: var(--o-gap-4);
788
+ height: 100%;
789
+ --phone-padding-top: 0;
790
+ @include respond-to('pad_v') {
791
+ flex-direction: column;
792
+ gap: var(--o-gap-4);
793
+ }
794
+ @include respond-to('phone') {
795
+ flex-direction: column;
796
+ gap: var(--o-gap-3);
797
+ --phone-padding-top: calc(var(--o-gap-5) + var(--o-gap-3) + var(--o-gap-3));
798
+ }
799
+
800
+ .o-loading {
801
+ .o-layer-mask {
802
+ background-color: transparent;
803
+ }
804
+
805
+ .o-loading-icon {
806
+ color: var(--layer-mask);
807
+ }
808
+ }
809
+
810
+ .list-calendar-mb {
811
+ display: none;
812
+ align-items: center;
813
+ justify-content: space-between;
814
+ padding: 0 var(--grid--layout-padding);
815
+
816
+ span:first-child {
817
+ @include display2;
818
+ }
819
+
820
+ span:last-child {
821
+ display: flex;
822
+ align-items: center;
823
+ }
824
+
825
+ .o-icon {
826
+ font-size: 24px;
827
+ cursor: pointer;
828
+
829
+ &:last-child {
830
+ margin-left: 24px;
831
+ }
832
+ }
833
+
834
+ @include respond-to('phone') {
835
+ padding-top: var(--o-gap-7);
836
+ background-color: var(--o-color-fill1);
837
+ padding-bottom: var(--o-gap-4);
838
+ display: flex;
839
+ position: fixed;
840
+ height: var(--phone-padding-top);
841
+ z-index: 1;
842
+ top: var(--layout-header-height);
843
+ left: 0;
844
+ right: 0;
845
+ }
846
+ }
847
+
848
+ .list-calendar {
849
+ flex-shrink: 0;
850
+ width: 334px;
851
+ background-color: color-mix(in srgb, var(--o-color-control2-light) 40%, transparent);
852
+
853
+ @include respond-to('pad_h') {
854
+ .el-calendar {
855
+ .el-calendar__body {
856
+ padding-left: 12px;
857
+ padding-right: 12px;
858
+
859
+ .el-calendar-table .date-cell {
860
+ height: 40px;
861
+ width: 28px;
862
+
863
+ .date-cell-text {
864
+ line-height: 24px;
865
+ @include tip2;
866
+ }
867
+ }
868
+ }
869
+ }
870
+ }
871
+ @include respond-to('pad_v') {
872
+ width: 100%;
873
+ }
874
+ @include respond-to('phone') {
875
+ display: none;
876
+ }
877
+ .el-calendar {
878
+ min-height: 460px;
879
+ height: calc(100% - 54px);
880
+ border-radius: var(--o-radius-xs);
881
+ background-color: transparent;
882
+
883
+ .el-calendar__header {
884
+ border-bottom: 1px solid var(--o-color-control4);
885
+ color: var(--o-color-info1);
886
+
887
+ & > span {
888
+ font-weight: 500;
889
+ @include h1;
890
+ }
891
+
892
+ & > div {
893
+ display: flex;
894
+ align-items: center;
895
+ gap: var(--o-gap-5);
896
+
897
+ .o-icon {
898
+ font-size: 24px;
899
+ cursor: pointer;
900
+
901
+ &:hover {
902
+ color: var(--o-color-primary1);
903
+ }
904
+
905
+ svg path {
906
+ fill: currentColor;
907
+ }
908
+ }
909
+ }
910
+ }
911
+
912
+ .el-calendar__body {
913
+ .el-calendar-table {
914
+
915
+ th {
916
+ text-align: center;
917
+ border: none;
918
+ --o-color-control3-light: transparent;
919
+ }
920
+
921
+ tr {
922
+ --o-color-fill2: transparent;
923
+ }
924
+
925
+ td {
926
+ background-color: transparent !important;
927
+ border: none;
928
+ text-align: center;
929
+ transition: none;
930
+ padding: 0;
931
+
932
+ .el-calendar-day {
933
+ padding: 0;
934
+ height: fit-content;
935
+ }
936
+
937
+ div {
938
+ cursor: default !important;
939
+ }
940
+
941
+ &:hover {
942
+ .el-calendar-day {
943
+ background-color: transparent;
944
+ }
945
+ }
946
+ }
947
+
948
+ .date-cell {
949
+ height: 56px;
950
+ width: 42px;
951
+ padding: var(--o-gap-1);
952
+ position: relative;
953
+ cursor: default !important;
954
+
955
+ .date-cell-text {
956
+ font-size: 14px;
957
+ line-height: 36px;
958
+ border-radius: var(--o-radius-xs);
959
+ background-color: var(--o-color-control2-light);
960
+ }
961
+
962
+ .date-cell-text {
963
+ cursor: not-allowed !important;
964
+ }
965
+
966
+ &.clickable {
967
+ .date-cell-text {
968
+ cursor: pointer !important;
969
+ }
970
+
971
+ &:not(.is-selected) {
972
+ .date-cell-text:hover {
973
+ background-color: var(--o-color-control3-light);
974
+ }
975
+ }
976
+ }
977
+
978
+ &::after {
979
+ content: '';
980
+ position: absolute;
981
+ bottom: 2px;
982
+ left: 50%;
983
+ transform: translateX(-50%);
984
+ width: 8px;
985
+ height: 8px;
986
+ border-radius: 50%;
987
+ }
988
+
989
+ &.is-today {
990
+ .date-cell-text {
991
+ color: #000;
992
+ background-color: var(--o-color-control3-light);
993
+ }
994
+ }
995
+
996
+ &.is-selected {
997
+ .date-cell-text {
998
+ color: #fff;
999
+ background-color: var(--o-color-primary1);
1000
+ }
1001
+ }
1002
+
1003
+ &.clickable {
1004
+ &::after {
1005
+ background-color: var(--o-color-primary1);
1006
+ }
1007
+
1008
+ &.expired::after {
1009
+ background-color: rgb(var(--o-mixedgray-6));
1010
+ }
1011
+
1012
+ &.all-deleted::after {
1013
+ background-color: rgb(var(--o-mixedgray-6));
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+ .is-today {
1019
+ color: inherit;
1020
+ }
1021
+ }
1022
+ }
1023
+ }
1024
+
1025
+ }
1026
+
1027
+ .list-wrapper {
1028
+ flex-grow: 1;
1029
+ background-color: var(--o-color-fill2);
1030
+
1031
+ @include respond-to('phone') {
1032
+ margin-top: calc(var(--phone-padding-top) - var(--o-gap-4));
1033
+ }
1034
+ &.is-empty {
1035
+ display: flex;
1036
+ align-items: center;
1037
+ justify-content: center;
1038
+ }
1039
+
1040
+
1041
+ .o-scroller {
1042
+ .o-scrollbar-rail {
1043
+ right: -16px;
1044
+ height: 100%;
1045
+ }
1046
+ }
1047
+
1048
+ .scroller-container {
1049
+ height: 100%;
1050
+ max-height: calc(var(--layout-left-height, 900px) - 4 * var(--o-gap-5) - var(--header-height) * 1px);
1051
+
1052
+ @include respond-to('phone') {
1053
+ max-height: fit-content;
1054
+ }
1055
+
1056
+ .group-item {
1057
+ padding-left: 24px;
1058
+ position: relative;
1059
+
1060
+ &.last-item {
1061
+ flex-grow: 1;
1062
+ }
1063
+
1064
+ @include respond-to('phone') {
1065
+ padding-left: var(--o-gap-2);
1066
+ }
1067
+
1068
+ .group-bar {
1069
+ position: absolute;
1070
+ left: 0;
1071
+ width: 16px;
1072
+ top: 0;
1073
+ bottom: 0;
1074
+ overflow: hidden;
1075
+
1076
+ --active-color: var(--o-color-primary1);
1077
+
1078
+ &::before {
1079
+ content: '';
1080
+ width: 2px;
1081
+ position: absolute;
1082
+ top: 0;
1083
+ bottom: 0;
1084
+ left: 50%;
1085
+ transform: translateX(-50%);
1086
+ background-color: var(--o-color-control4);
1087
+ }
1088
+
1089
+ .group-bar-dot {
1090
+ width: 16px;
1091
+ height: 26px;
1092
+ position: relative;
1093
+ @include respond-to('laptop') {
1094
+ height: 24px;
1095
+ }
1096
+ @include respond-to('pad_h') {
1097
+ height: 22px;
1098
+ }
1099
+ @include respond-to('<=pad_v') {
1100
+ height: 22px;
1101
+ }
1102
+
1103
+ &::before,
1104
+ &::after {
1105
+ content: '';
1106
+ border-radius: 50%;
1107
+ position: absolute;
1108
+ top: 50%;
1109
+ left: 50%;
1110
+ transform: translateY(-50%) translateX(-50%);
1111
+ }
1112
+
1113
+ &::before {
1114
+ width: 16px;
1115
+ height: 16px;
1116
+ background-color: transparent;
1117
+ }
1118
+
1119
+ &::after {
1120
+ width: 8px;
1121
+ height: 8px;
1122
+ background-color: var(--active-color);
1123
+ }
1124
+ }
1125
+
1126
+ &.is-active {
1127
+ .group-bar-dot {
1128
+ &::before {
1129
+ background-color: var(--active-color);
1130
+ }
1131
+
1132
+ &::after {
1133
+ background-color: var(--o-color-fill2);
1134
+ }
1135
+ }
1136
+ }
1137
+
1138
+ &.is-end {
1139
+ --active-color: rgb(222, 222, 227);
1140
+ }
1141
+ }
1142
+
1143
+ .group-title {
1144
+ font-weight: 500;
1145
+ margin-bottom: var(--o-gap-2);
1146
+ color: var(--o-color-info1);
1147
+ @include text2;
1148
+ @include respond-to('phone') {
1149
+ padding-left: var(--o-gap-5);
1150
+ }
1151
+
1152
+ &.is-end {
1153
+ color: var(--o-color-info3);
1154
+ }
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ .list-body {
1161
+ height: 100%;
1162
+
1163
+ @include respond-to('phone') {
1164
+ height: fit-content;
1165
+ padding: var(--o-gap-4) !important;
1166
+ }
1167
+
1168
+ .list-month-change {
1169
+ flex-shrink: 0;
1170
+ display: flex;
1171
+ align-items: center;
1172
+ gap: var(--o-gap-5);
1173
+ cursor: pointer;
1174
+ --btn-color: var(--o-color-primary1);
1175
+ @include hover {
1176
+ --btn-color: var(--o-color-primary2);
1177
+ }
1178
+
1179
+ @include respond-to('phone') {
1180
+ display: none;
1181
+ }
1182
+
1183
+ &.prev-month {
1184
+ margin-bottom: var(--o-gap-6);
1185
+ }
1186
+
1187
+ &.next-month {
1188
+ margin-top: var(--o-gap-6);
1189
+ padding-bottom: 32px;
1190
+ }
1191
+
1192
+ .o-icon {
1193
+ font-size: 24px;
1194
+ color: var(--btn-color);
1195
+ }
1196
+
1197
+ span {
1198
+ color: var(--btn-color);
1199
+ @include text1;
1200
+ }
1201
+ }
1202
+
1203
+ .o-collapse {
1204
+ padding: 0;
1205
+ border-radius: 0;
1206
+ height: 100%;
1207
+ display: flex;
1208
+ flex-direction: column;
1209
+
1210
+ .o-collapse-item {
1211
+ &.last-item {
1212
+ .o-collapse-item-header {
1213
+ border-bottom: none;
1214
+ }
1215
+ }
1216
+ }
1217
+
1218
+ .o-collapse-item-expanded + .o-collapse-item-expanded {
1219
+ margin-top: var(--o-gap-4);
1220
+ }
1221
+
1222
+ .height-placeholder {
1223
+ height: 0;
1224
+ transition: margin var(--o-easing-standard) var(--o-duration-s);
1225
+ }
1226
+
1227
+ .o-collapse-item-expanded + .height-placeholder {
1228
+ height: var(--o-gap-4);
1229
+ }
1230
+
1231
+ .o-collapse-item {
1232
+ padding: var(--o-gap-4) var(--o-gap-5);
1233
+ border-top: none;
1234
+ border-radius: var(--o-radius-xs);
1235
+ transition: margin var(--o-easing-standard) var(--o-duration-s);
1236
+ --copy-display: none;
1237
+ --icon-size: 24px;
1238
+ @include respond-to('<=pad_v') {
1239
+ padding: var(--o-gap-3) var(--o-gap-4);
1240
+ }
1241
+
1242
+ &:hover {
1243
+ @include respond-to('>pad_v') {
1244
+ --copy-display: inline-flex;
1245
+ }
1246
+ }
1247
+
1248
+ @include respond-to('phone') {
1249
+ --icon-size: 20px;
1250
+ }
1251
+ &.o-collapse-item-expanded {
1252
+ @include respond-to('<=pad_v') {
1253
+ --copy-display: inline-flex;
1254
+ }
1255
+ }
1256
+
1257
+ &.o-collapse-item-expanded {
1258
+ background-color: color-mix(in srgb, var(--o-color-control2-light) 40%, transparent) !important;
1259
+ }
1260
+ }
1261
+
1262
+ .o-collapse-item-header {
1263
+ border-bottom: 1px solid var(--o-color-control4);
1264
+ padding-top: 0;
1265
+ padding-bottom: var(--o-gap-2);
1266
+ display: flex;
1267
+ align-items: center;
1268
+ gap: var(--o-gap-4);
1269
+ position: relative;
1270
+
1271
+ .o-collapse-item-icon {
1272
+ position: relative;
1273
+ top: 4px;
1274
+ flex-shrink: 0;
1275
+ @include respond-to('phone') {
1276
+ position: absolute;
1277
+ right: 0;
1278
+ width: 20px;
1279
+ height: 20px;
1280
+ font-size: 20px;
1281
+ top: 50%;
1282
+ transform: translateY(-50%);
1283
+ }
1284
+ }
1285
+
1286
+ .o-collapse-item-title {
1287
+ flex: 1;
1288
+ width: 0;
1289
+ display: flex;
1290
+ flex-direction: column;
1291
+ align-items: flex-start;
1292
+ justify-content: space-between;
1293
+ margin-bottom: 0;
1294
+ .item-header-left {
1295
+ display: flex;
1296
+ align-items: flex-start;
1297
+ gap: var(--o-gap-3);
1298
+ width: 100%;
1299
+ margin-bottom: var(--o-gap-2);
1300
+ @include respond-to('phone') {
1301
+ flex-grow: 1;
1302
+ width: 100%;
1303
+ align-self: stretch;
1304
+ flex-shrink: 0;
1305
+ }
1306
+ .meeting-icon {
1307
+ width: var(--icon-size);
1308
+ height: var(--icon-size);
1309
+ border-radius: 50%;
1310
+ background-color: var(--o-color-primary1);
1311
+ color: #fff;
1312
+ display: flex;
1313
+ align-items: center;
1314
+ justify-content: center;
1315
+ flex-shrink: 0;
1316
+
1317
+ .o-icon {
1318
+ svg path {
1319
+ fill: currentColor;
1320
+ }
1321
+ }
1322
+
1323
+ }
1324
+
1325
+ .header-info {
1326
+ width: calc(100% - var(--o-gap-3) - var(--icon-size));
1327
+
1328
+ .meeting-title {
1329
+ font-weight: 500;
1330
+ display: flex;
1331
+ align-items: center;
1332
+ margin-bottom: var(--o-gap-2);
1333
+ @include text2;
1334
+
1335
+ &.is-delete,
1336
+ &.is-end {
1337
+ color: var(--o-color-info3);
1338
+ }
1339
+
1340
+ .tag-wrapper {
1341
+ flex: 10;
1342
+ margin-left: var(--o-gap-2);
1343
+
1344
+ .o-tag {
1345
+ background-color: var(--o-color-control2-light);
1346
+ border: none;
1347
+ }
1348
+ }
1349
+
1350
+ .title-wrapper {
1351
+ flex: 0 1 auto;
1352
+ min-width: 0;
1353
+ max-width: 100%;
1354
+ }
1355
+
1356
+ .title-text {
1357
+ @include text-truncate(1);
1358
+ }
1359
+ }
1360
+
1361
+ .meeting-info {
1362
+ color: var(--o-color-info3);
1363
+ display: flex;
1364
+ align-items: center;
1365
+ font-weight: 400;
1366
+ @include tip1;
1367
+
1368
+ span:last-child {
1369
+ width: 0;
1370
+ flex: 1;
1371
+ @include text-truncate(1);
1372
+ }
1373
+ }
1374
+ }
1375
+ }
1376
+
1377
+ .item-header-right {
1378
+ display: flex;
1379
+ align-items: center;
1380
+ padding-left: calc(var(--o-gap-3) + var(--icon-size));
1381
+ gap: var(--o-gap-4);
1382
+ width: 100%;
1383
+
1384
+ .o-link {
1385
+ font-size: 14px;
1386
+ line-height: 21px;
1387
+ }
1388
+ .o-icon {
1389
+ font-size: 16px;
1390
+ }
1391
+ }
1392
+
1393
+ .copy-icon {
1394
+ position: absolute;
1395
+ top: 50%;
1396
+ transform: translateY(-50%);
1397
+ right: calc(var(--collapse-item-icon-size) + var(--o-gap-4));
1398
+ font-size: 18px;
1399
+ height: 18px;
1400
+ width: 18px;
1401
+ display: var(--copy-display);
1402
+ @include respond-to('phone') {
1403
+ bottom: var(--o-gap-2);
1404
+ right: calc(20px + var(--o-gap-2))
1405
+ }
1406
+
1407
+ &:hover {
1408
+ color: var(--o-color-primary1);
1409
+ }
1410
+
1411
+
1412
+ svg path {
1413
+ fill: currentColor;
1414
+ }
1415
+ }
1416
+ }
1417
+ }
1418
+
1419
+ .o-collapse-item-body {
1420
+ margin-bottom: 0;
1421
+ padding: var(--o-gap-4) 0 0;
1422
+
1423
+ .meeting-detail {
1424
+ padding-left: calc(var(--o-gap-3) + var(--icon-size));
1425
+
1426
+ @include respond-to('phone') {
1427
+ padding-left: 0;
1428
+ }
1429
+
1430
+ .meeting-btn {
1431
+ border-top: 1px solid var(--o-color-control4);
1432
+ margin-top: var(--o-gap-5);
1433
+ padding-top: var(--o-gap-4);
1434
+ display: flex;
1435
+ align-items: center;
1436
+ justify-content: flex-end;
1437
+ gap: var(--o-gap-5);
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ .o-btn.o-btn-text {
1443
+ padding-left: 0 !important;
1444
+ padding-right: 0 !important;
1445
+ min-width: auto;
1446
+ }
1447
+ }
1448
+
1449
+ .load-text {
1450
+ text-align: center;
1451
+ color: var(--o-color-info3);
1452
+ @include tip1;
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ @include in-dark {
1458
+ .group-bar.is-end {
1459
+ --active-color: rgb(65, 65, 68) !important;
1460
+ }
1461
+ .meeting-list {
1462
+
1463
+ .is-today {
1464
+ .date-cell-text {
1465
+ color: #fff !important;
1466
+ }
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ </style>
1472
+
1473
+ <style lang="scss">
1474
+ .handle-dialog {
1475
+ width: 450px;
1476
+
1477
+ .o-dlg-header {
1478
+ margin-bottom: var(--o-gap-5);
1479
+ }
1480
+
1481
+ .o-dlg-body-content {
1482
+ display: flex;
1483
+ justify-content: center;
1484
+ }
1485
+
1486
+ .dialog-footer {
1487
+ display: flex;
1488
+ justify-content: center;
1489
+ margin-top: var(--o-gap-4);
1490
+ column-gap: var(--o-gap-4);
1491
+ }
1492
+ }
1493
+
1494
+ .cancel-dialog {
1495
+ .dialog-content {
1496
+ width: 384px;
1497
+ text-align: center;
1498
+ }
1499
+
1500
+ .dialog-footer {
1501
+ display: flex;
1502
+ justify-content: center;
1503
+ margin-top: var(--o-gap-4);
1504
+ column-gap: var(--o-gap-4);
1505
+ }
1506
+ }
1507
+
1508
+ </style>