duqing2026 commited on
Commit
082aeb1
·
0 Parent(s):

Initial commit: Remote Team Clock

Browse files
Files changed (4) hide show
  1. Dockerfile +16 -0
  2. README.md +42 -0
  3. app.py +15 -0
  4. templates/index.html +432 -0
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY . /app
6
+
7
+ RUN pip install --no-cache-dir flask gunicorn
8
+
9
+ # Create a non-root user
10
+ RUN useradd -m -u 1000 user
11
+ USER user
12
+
13
+ ENV HOME=/home/user \
14
+ PATH=/home/user/.local/bin:$PATH
15
+
16
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Remote Team Clock
3
+ emoji: ⏰
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
+ # 远程团队时钟 (Remote Team Clock)
13
+
14
+ 一个为远程团队设计的可视化时区协作工具。
15
+
16
+ ## 功能特点
17
+
18
+ - 🌍 **多时区支持**:轻松添加位于世界各地的团队成员
19
+ - ⏱️ **可视化时间轴**:直观展示每个人当前的当地时间
20
+ - 📅 **会议规划**:拖动滑块,快速找到适合所有人的会议时间
21
+ - 🌓 **昼夜显示**:自动识别工作时间(绿色)、休息时间(蓝色)和深夜(深色)
22
+ - 🔒 **隐私安全**:所有数据仅存储在本地浏览器,不上传服务器
23
+
24
+ ## 技术栈
25
+
26
+ - **Backend**: Flask (Python)
27
+ - **Frontend**: Vue 3 + Tailwind CSS
28
+ - **Library**: Day.js (Timezone handling)
29
+
30
+ ## 部署说明
31
+
32
+ 本项目已配置 Dockerfile,可直接部署至 Hugging Face Spaces。
33
+
34
+ ```bash
35
+ # 本地运行
36
+ pip install flask
37
+ python app.py
38
+ ```
39
+
40
+ ## 关于
41
+
42
+ Designed for remote teams to synchronize effectively.
app.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, send_from_directory
2
+ import os
3
+
4
+ app = Flask(__name__, static_folder='static', template_folder='templates')
5
+
6
+ @app.route('/')
7
+ def index():
8
+ return render_template('index.html')
9
+
10
+ @app.route('/static/<path:path>')
11
+ def serve_static(path):
12
+ return send_from_directory('static', path)
13
+
14
+ if __name__ == '__main__':
15
+ app.run(host='0.0.0.0', port=7860)
templates/index.html ADDED
@@ -0,0 +1,432 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>远程团队时钟 (Remote Team Clock)</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/timezone.js"></script>
12
+ <script src="https://cdn.jsdelivr.net/npm/dayjs@1/locale/zh-cn.js"></script>
13
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
14
+ <script>
15
+ dayjs.extend(window.dayjs_plugin_utc);
16
+ dayjs.extend(window.dayjs_plugin_timezone);
17
+ dayjs.locale('zh-cn');
18
+ tailwind.config = {
19
+ theme: {
20
+ extend: {
21
+ colors: {
22
+ primary: '#3b82f6',
23
+ secondary: '#10b981',
24
+ night: '#1e293b',
25
+ day: '#f0f9ff'
26
+ }
27
+ }
28
+ }
29
+ }
30
+ </script>
31
+ <style>
32
+ .slider-thumb::-webkit-slider-thumb {
33
+ -webkit-appearance: none;
34
+ appearance: none;
35
+ width: 20px;
36
+ height: 20px;
37
+ background: #3b82f6;
38
+ cursor: pointer;
39
+ border-radius: 50%;
40
+ }
41
+ .time-slot {
42
+ transition: all 0.3s ease;
43
+ }
44
+ .time-slot:hover {
45
+ transform: scale(1.05);
46
+ }
47
+ /* Custom scrollbar */
48
+ ::-webkit-scrollbar {
49
+ width: 8px;
50
+ height: 8px;
51
+ }
52
+ ::-webkit-scrollbar-track {
53
+ background: #f1f1f1;
54
+ }
55
+ ::-webkit-scrollbar-thumb {
56
+ background: #cbd5e1;
57
+ border-radius: 4px;
58
+ }
59
+ ::-webkit-scrollbar-thumb:hover {
60
+ background: #94a3b8;
61
+ }
62
+ </style>
63
+ </head>
64
+ <body class="bg-gray-50 text-gray-800 min-h-screen font-sans">
65
+ <div id="app" class="container mx-auto px-4 py-8 max-w-5xl">
66
+ <!-- Header -->
67
+ <header class="mb-8 text-center">
68
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">
69
+ <i class="fas fa-clock text-primary mr-2"></i>远程团队时钟
70
+ </h1>
71
+ <p class="text-gray-500">可视化跨时区协作,轻松找到最佳会议时间</p>
72
+ </header>
73
+
74
+ <!-- Main Content -->
75
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
76
+
77
+ <!-- Left Column: Controls & Members -->
78
+ <div class="lg:col-span-1 space-y-6">
79
+ <!-- Add Member Card -->
80
+ <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-100">
81
+ <h2 class="text-xl font-semibold mb-4 flex items-center">
82
+ <i class="fas fa-user-plus text-secondary mr-2"></i>添加成员
83
+ </h2>
84
+ <div class="space-y-4">
85
+ <div>
86
+ <label class="block text-sm font-medium text-gray-700 mb-1">姓名 / 角色</label>
87
+ <input v-model="newMember.name" type="text" placeholder="例如:产品经理" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none transition">
88
+ </div>
89
+ <div>
90
+ <label class="block text-sm font-medium text-gray-700 mb-1">城市 / 时区</label>
91
+ <select v-model="newMember.timezone" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none bg-white">
92
+ <option value="" disabled>选择时区</option>
93
+ <option v-for="tz in commonTimezones" :key="tz.value" :value="tz.value">
94
+ {{ tz.label }} ({{ formatOffset(tz.value) }})
95
+ </option>
96
+ </select>
97
+ </div>
98
+ <button @click="addMember" :disabled="!newMember.name || !newMember.timezone" class="w-full bg-primary hover:bg-blue-600 text-white font-bold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed">
99
+ <i class="fas fa-plus mr-1"></i> 添加
100
+ </button>
101
+ </div>
102
+ </div>
103
+
104
+ <!-- Current Time Display -->
105
+ <div class="bg-gradient-to-br from-night to-slate-800 text-white rounded-xl shadow-lg p-6">
106
+ <div class="text-center">
107
+ <div class="text-sm opacity-75 mb-1">我的本地时间</div>
108
+ <div class="text-3xl font-mono font-bold">{{ localTimeStr }}</div>
109
+ <div class="text-xs opacity-50 mt-1">{{ localDateStr }}</div>
110
+ </div>
111
+ </div>
112
+
113
+ <!-- Member List (Compact) -->
114
+ <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-100 max-h-[400px] overflow-y-auto">
115
+ <div class="flex justify-between items-center mb-4">
116
+ <h2 class="text-lg font-semibold">团队成员 ({{ members.length }})</h2>
117
+ <button @click="resetMembers" class="text-xs text-red-500 hover:text-red-700 underline">重置</button>
118
+ </div>
119
+ <div v-if="members.length === 0" class="text-center text-gray-400 py-8">
120
+ 暂无成员,请添加
121
+ </div>
122
+ <ul class="space-y-3">
123
+ <li v-for="(member, index) in members" :key="index" class="flex justify-between items-center group p-2 hover:bg-gray-50 rounded-lg transition">
124
+ <div class="flex items-center">
125
+ <div class="w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-gray-600 font-bold mr-3 text-xs">
126
+ {{ getInitials(member.name) }}
127
+ </div>
128
+ <div>
129
+ <div class="font-medium text-sm">{{ member.name }}</div>
130
+ <div class="text-xs text-gray-500">{{ getCityName(member.timezone) }}</div>
131
+ </div>
132
+ </div>
133
+ <button @click="removeMember(index)" class="text-gray-300 hover:text-red-500 opacity-0 group-hover:opacity-100 transition">
134
+ <i class="fas fa-trash-alt"></i>
135
+ </button>
136
+ </li>
137
+ </ul>
138
+ </div>
139
+ </div>
140
+
141
+ <!-- Right Column: Visualization -->
142
+ <div class="lg:col-span-2 space-y-6">
143
+ <!-- Timeline Controller -->
144
+ <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-100">
145
+ <div class="flex justify-between items-end mb-4">
146
+ <div>
147
+ <h2 class="text-xl font-semibold flex items-center">
148
+ <i class="fas fa-sliders-h text-primary mr-2"></i>会议时间规划
149
+ </h2>
150
+ <p class="text-sm text-gray-500 mt-1">拖动滑块查看不同时间的团队状态</p>
151
+ </div>
152
+ <div class="text-right">
153
+ <div class="text-2xl font-bold text-primary">{{ formatTime(selectedHour) }}</div>
154
+ <div class="text-xs text-gray-500">基准时间 (我的时区)</div>
155
+ </div>
156
+ </div>
157
+
158
+ <div class="relative pt-6 pb-2">
159
+ <input type="range" min="0" max="23" step="1" v-model.number="selectedHour"
160
+ class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider-thumb">
161
+ <div class="flex justify-between text-xs text-gray-400 mt-2">
162
+ <span>00:00</span>
163
+ <span>06:00</span>
164
+ <span>12:00</span>
165
+ <span>18:00</span>
166
+ <span>23:00</span>
167
+ </div>
168
+ </div>
169
+
170
+ <div class="mt-4 flex justify-center space-x-4 text-sm">
171
+ <button @click="selectedHour = Math.max(0, selectedHour - 1)" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded text-gray-600">
172
+ <i class="fas fa-minus mr-1"></i> -1小时
173
+ </button>
174
+ <button @click="setNow" class="px-3 py-1 bg-blue-50 text-blue-600 hover:bg-blue-100 rounded font-medium">
175
+ 回到现在
176
+ </button>
177
+ <button @click="selectedHour = Math.min(23, selectedHour + 1)" class="px-3 py-1 bg-gray-100 hover:bg-gray-200 rounded text-gray-600">
178
+ +1小时 <i class="fas fa-plus ml-1"></i>
179
+ </button>
180
+ </div>
181
+ </div>
182
+
183
+ <!-- Team Timeline Grid -->
184
+ <div class="bg-white rounded-xl shadow-lg p-6 border border-gray-100 overflow-x-auto">
185
+ <h3 class="font-semibold text-gray-700 mb-4">团队时间概览</h3>
186
+
187
+ <div class="space-y-4 min-w-[500px]">
188
+ <div v-for="(member, index) in members" :key="index" class="relative">
189
+ <div class="flex items-center mb-2">
190
+ <div class="w-24 font-medium text-sm truncate mr-2" :title="member.name">{{ member.name }}</div>
191
+ <div class="flex-1 h-12 flex rounded-lg overflow-hidden border border-gray-100">
192
+ <!-- Render 24 hour blocks centered around selected time? No, show current selected time explicitly -->
193
+ <!-- Better: Show a single bar with the time at selectedHour -->
194
+ <div class="w-full flex items-center px-4 rounded-lg transition-colors duration-300"
195
+ :class="getSlotClass(member.timezone)">
196
+ <div class="text-2xl mr-3">
197
+ <i :class="getIcon(member.timezone)"></i>
198
+ </div>
199
+ <div>
200
+ <div class="text-xl font-bold">
201
+ {{ getMemberTime(member.timezone) }}
202
+ </div>
203
+ <div class="text-xs opacity-75">
204
+ {{ getMemberDateDiff(member.timezone) }}
205
+ </div>
206
+ </div>
207
+ <div class="ml-auto text-right">
208
+ <div class="text-xs font-bold uppercase tracking-wide opacity-60">
209
+ {{ getPeriod(member.timezone) }}
210
+ </div>
211
+ <div class="text-xs opacity-50">
212
+ {{ getCityName(member.timezone) }}
213
+ </div>
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </div>
220
+
221
+ <!-- Legend -->
222
+ <div class="mt-6 flex flex-wrap gap-4 text-xs text-gray-500 justify-center border-t pt-4">
223
+ <div class="flex items-center"><span class="w-3 h-3 rounded-full bg-green-100 border border-green-300 mr-1"></span> 工作时间 (9-18)</div>
224
+ <div class="flex items-center"><span class="w-3 h-3 rounded-full bg-blue-50 border border-blue-200 mr-1"></span> 休息/早晚</div>
225
+ <div class="flex items-center"><span class="w-3 h-3 rounded-full bg-slate-800 border border-slate-600 mr-1"></span> 深夜 (22-7)</div>
226
+ </div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <footer class="mt-12 text-center text-gray-400 text-sm">
232
+ <p>© 2025 Remote Team Clock. Built with Flask, Vue 3 & Tailwind.</p>
233
+ </footer>
234
+ </div>
235
+
236
+ <script>
237
+ const { createApp, ref, computed, onMounted, watch } = Vue;
238
+
239
+ createApp({
240
+ setup() {
241
+ const now = dayjs();
242
+ const selectedHour = ref(now.hour());
243
+ const currentMinute = ref(now.minute());
244
+
245
+ // Common Timezones
246
+ const commonTimezones = [
247
+ { label: '北京/上海 (UTC+8)', value: 'Asia/Shanghai' },
248
+ { label: '东京 (UTC+9)', value: 'Asia/Tokyo' },
249
+ { label: '悉尼 (UTC+11)', value: 'Australia/Sydney' },
250
+ { label: '伦敦 (UTC+0)', value: 'Europe/London' },
251
+ { label: '巴黎/柏林 (UTC+1)', value: 'Europe/Paris' },
252
+ { label: '纽约 (UTC-5)', value: 'America/New_York' },
253
+ { label: '旧金山/硅谷 (UTC-8)', value: 'America/Los_Angeles' },
254
+ { label: '芝加哥 (UTC-6)', value: 'America/Chicago' },
255
+ { label: '迪拜 (UTC+4)', value: 'Asia/Dubai' },
256
+ { label: '新加坡 (UTC+8)', value: 'Asia/Singapore' },
257
+ { label: '孟买 (UTC+5:30)', value: 'Asia/Kolkata' },
258
+ ];
259
+
260
+ const newMember = ref({ name: '', timezone: '' });
261
+ const members = ref([]);
262
+
263
+ // Load from local storage
264
+ onMounted(() => {
265
+ const saved = localStorage.getItem('rtc_members');
266
+ if (saved) {
267
+ members.value = JSON.parse(saved);
268
+ } else {
269
+ // Default members
270
+ members.value = [
271
+ { name: '我 (北京)', timezone: 'Asia/Shanghai' },
272
+ { name: 'Alice (伦敦)', timezone: 'Europe/London' },
273
+ { name: 'Bob (纽约)', timezone: 'America/New_York' }
274
+ ];
275
+ }
276
+
277
+ // Update current minute every minute to keep display accurate-ish
278
+ setInterval(() => {
279
+ currentMinute.value = dayjs().minute();
280
+ }, 60000);
281
+ });
282
+
283
+ watch(members, (newVal) => {
284
+ localStorage.setItem('rtc_members', JSON.stringify(newVal));
285
+ }, { deep: true });
286
+
287
+ const addMember = () => {
288
+ if (newMember.value.name && newMember.value.timezone) {
289
+ members.value.push({ ...newMember.value });
290
+ newMember.value = { name: '', timezone: '' };
291
+ }
292
+ };
293
+
294
+ const removeMember = (index) => {
295
+ members.value.splice(index, 1);
296
+ };
297
+
298
+ const resetMembers = () => {
299
+ if(confirm('确定要重置所有成员吗?')) {
300
+ members.value = [];
301
+ localStorage.removeItem('rtc_members');
302
+ }
303
+ };
304
+
305
+ const setNow = () => {
306
+ selectedHour.value = dayjs().hour();
307
+ };
308
+
309
+ const formatTime = (hour) => {
310
+ return `${String(hour).padStart(2, '0')}:00`;
311
+ };
312
+
313
+ const getInitials = (name) => {
314
+ return name ? name.substring(0, 2).toUpperCase() : '?';
315
+ };
316
+
317
+ const getCityName = (tz) => {
318
+ return tz.split('/')[1].replace('_', ' ');
319
+ };
320
+
321
+ const formatOffset = (tz) => {
322
+ return dayjs().tz(tz).format('Z');
323
+ };
324
+
325
+ // Core logic to calculate time for a member based on selectedHour (which is in local user's timezone)
326
+ const getMemberDayjs = (tz) => {
327
+ // 1. Get "today" at "selectedHour" in LOCAL time
328
+ const localTarget = dayjs().hour(selectedHour.value).minute(0);
329
+ // 2. Convert this instant to the target timezone
330
+ return localTarget.tz(tz);
331
+ };
332
+
333
+ const getMemberTime = (tz) => {
334
+ return getMemberDayjs(tz).format('HH:mm');
335
+ };
336
+
337
+ const getMemberDateDiff = (tz) => {
338
+ const target = getMemberDayjs(tz);
339
+ const local = dayjs().hour(selectedHour.value).minute(0);
340
+
341
+ const diff = target.date() - local.date();
342
+ if (diff === 0) return '今天';
343
+ if (diff === 1 || diff < -20) return '明天 (+1天)'; // simplistic check
344
+ if (diff === -1 || diff > 20) return '昨天 (-1天)';
345
+ return target.format('MM-DD');
346
+ };
347
+
348
+ const getPeriod = (tz) => {
349
+ const hour = getMemberDayjs(tz).hour();
350
+ if (hour >= 5 && hour < 12) return '上午';
351
+ if (hour >= 12 && hour < 18) return '下午';
352
+ return '晚上';
353
+ };
354
+
355
+ const getIcon = (tz) => {
356
+ const hour = getMemberDayjs(tz).hour();
357
+ if (hour >= 6 && hour < 18) return 'fas fa-sun text-yellow-500';
358
+ return 'fas fa-moon text-blue-300';
359
+ };
360
+
361
+ const getSlotClass = (tz) => {
362
+ const hour = getMemberDayjs(tz).hour();
363
+ // Working hours: 9 - 18 (Green)
364
+ // Sleeping: 22 - 7 (Dark)
365
+ // Others: Light Blue
366
+ if (hour >= 9 && hour < 18) {
367
+ return 'bg-green-100 text-green-900 border-green-200';
368
+ } else if (hour >= 22 || hour < 7) {
369
+ return 'bg-slate-800 text-slate-100 border-slate-700';
370
+ } else {
371
+ return 'bg-blue-50 text-blue-800 border-blue-200';
372
+ }
373
+ };
374
+
375
+ const localTimeStr = computed(() => {
376
+ return dayjs().format('HH:mm:ss');
377
+ });
378
+
379
+ const localDateStr = computed(() => {
380
+ return dayjs().format('YYYY年MM月DD日 dddd');
381
+ });
382
+
383
+ // Update real-time clock
384
+ setInterval(() => {
385
+ // Trigger reactivity for localTimeStr
386
+ // But we are using computed without ref dependency?
387
+ // Need a ref to trigger re-calc.
388
+ // Actually dayjs() is not reactive.
389
+ // Let's use a trigger.
390
+ }, 1000);
391
+
392
+ const nowTrigger = ref(Date.now());
393
+ setInterval(() => {
394
+ nowTrigger.value = Date.now();
395
+ }, 1000);
396
+
397
+ const reactiveLocalTime = computed(() => {
398
+ nowTrigger.value; // dependency
399
+ return dayjs().format('HH:mm:ss');
400
+ });
401
+
402
+ const reactiveLocalDate = computed(() => {
403
+ nowTrigger.value;
404
+ return dayjs().format('YYYY年MM月DD日 dddd');
405
+ });
406
+
407
+ return {
408
+ selectedHour,
409
+ members,
410
+ newMember,
411
+ commonTimezones,
412
+ addMember,
413
+ removeMember,
414
+ resetMembers,
415
+ setNow,
416
+ formatTime,
417
+ getInitials,
418
+ getCityName,
419
+ formatOffset,
420
+ getMemberTime,
421
+ getMemberDateDiff,
422
+ getPeriod,
423
+ getIcon,
424
+ getSlotClass,
425
+ localTimeStr: reactiveLocalTime,
426
+ localDateStr: reactiveLocalDate
427
+ };
428
+ }
429
+ }).mount('#app');
430
+ </script>
431
+ </body>
432
+ </html>