Skip to content

Commit 2bd07d3

Browse files
committed
新增总览
1.显示屏幕使用时间 2.显示所有设备使用总览
1 parent cf24593 commit 2bd07d3

11 files changed

Lines changed: 629 additions & 172 deletions

File tree

src/App.vue

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ const toast = ref({
4040
type: 'error'
4141
});
4242
43+
const OVERVIEW_DEVICE = {
44+
device: 'summary',
45+
currentApp: '不告诉你'
46+
};
47+
4348
// 获取本地日期字符串
4449
const getLocalDateString = (date = new Date()) => {
4550
const offset = date.getTimezoneOffset() * 60000;
@@ -79,7 +84,9 @@ const fetchDevices = async () => {
7984
if (!response.ok) {
8085
throw new Error(`请求失败: ${response.status}`);
8186
}
82-
devices.value = await response.json();
87+
const realDevices = await response.json();
88+
devices.value = [OVERVIEW_DEVICE, ...realDevices];
89+
8390
if (devices.value.length > 0 && !selectedDevice.value) {
8491
selectedDevice.value = devices.value[0].device;
8592
}
@@ -251,12 +258,15 @@ onUnmounted(() => {
251258
/>
252259
253260
<!-- 日期筛选组件 -->
254-
<DateSelector
255-
v-model="statsType"
256-
v-model:offset="timeOffset"
257-
v-model:selected-date="selectedDate"
258-
:date-range-text="getDateRangeText()"
259-
/>
261+
<Transition name="slide-fade">
262+
<DateSelector
263+
v-show="selectedDevice !== null"
264+
v-model="statsType"
265+
v-model:offset="timeOffset"
266+
v-model:selected-date="selectedDate"
267+
:date-range-text="getDateRangeText()"
268+
/>
269+
</Transition>
260270
</div>
261271
</div>
262272
@@ -268,8 +278,8 @@ onUnmounted(() => {
268278
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
269279
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
270280
</svg>
271-
<span v-if="selectedDevice">{{ selectedDevice }} 使用统计</span>
272-
<span v-else>请选择设备查看统计</span>
281+
<span v-if="selectedDevice!=='summary'">{{ selectedDevice }} 使用统计</span>
282+
<span v-else>总览</span>
273283
</h2>
274284
275285
<button v-if="selectedDevice" @click="refreshStats" class="px-3 py-1 text-sm bg-gray-100 rounded-md hover:bg-gray-200 transition-colors dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700">
@@ -296,7 +306,7 @@ onUnmounted(() => {
296306
</transition>
297307
298308
<DeviceStats
299-
v-if="selectedDevice"
309+
v-if="devices.length !== 1 && selectedDevice"
300310
:key="statsKey"
301311
:device-id="selectedDevice"
302312
:device-info="getSelectedDevice()"
@@ -308,7 +318,7 @@ onUnmounted(() => {
308318
/>
309319
310320
<!-- 空状态提示 - 仅当没有设备时显示 -->
311-
<div v-else-if="devices.length === 0" class="text-center py-8 text-gray-500">
321+
<div v-else class="text-center py-8 text-gray-500">
312322
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-3 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
313323
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
314324
</svg>
@@ -370,4 +380,39 @@ onUnmounted(() => {
370380
.fade-leave-from {
371381
opacity: 1;
372382
}
383+
384+
/* DateSelector 动画 - 方案四:使用 max-height 和 overflow */
385+
.slide-fade-enter-active {
386+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
387+
overflow: hidden;
388+
}
389+
390+
.slide-fade-leave-active {
391+
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
392+
overflow: hidden;
393+
}
394+
395+
.slide-fade-enter-from {
396+
opacity: 0;
397+
max-height: 0;
398+
transform: translateY(-10px);
399+
}
400+
401+
.slide-fade-enter-to {
402+
opacity: 1;
403+
max-height: 220px;
404+
transform: translateY(0);
405+
}
406+
407+
.slide-fade-leave-from {
408+
opacity: 1;
409+
max-height: 300px;
410+
transform: translateY(0);
411+
}
412+
413+
.slide-fade-leave-to {
414+
opacity: 0;
415+
max-height: 0;
416+
transform: translateY(-10px);
417+
}
373418
</style>

src/components/AISummary.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ watch(() => props.isExpanded, (expanded) => {
7474
7575
// 监听设备ID变化,重新加载数据
7676
watch(() => props.deviceId, () => {
77+
if (props.deviceId === "summary") return;
78+
7779
// 重置数据但保持展开状态
7880
summary.value = null;
7981
timestamp.value = null;

src/components/DateSelector.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ const getTimeRangeText = () => {
174174
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
175175
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
176176
</svg>
177-
模式
177+
时间
178178
</h2>
179179
<Transition name="fade" mode="out-in">
180180
<span v-if="statsType !== 'daily'" key="date-range-text" class="text-sm font-medium">{{ dateRangeText }}</span>

src/components/DeviceList.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,11 @@ const getBatteryTextClass = (level, isCharging) => {
8080
>
8181
<div class="flex justify-between items-start">
8282
<div class="flex-1">
83-
<h3 class="font-bold text-lg">{{ device.device }}</h3>
83+
<h3 class="font-bold text-lg">
84+
{{ device.device === 'summary' ? '总览' : device.device }}
85+
</h3>
8486
<p class="not-dark:text-gray-600 text-sm mt-1">
85-
<span class="font-medium">当前应用:</span> {{ device.currentApp || '无' }}
87+
<span class="font-medium" v-show="device.device!=='summary'">当前应用:</span>{{ device.device === 'summary' ? '点击查看总览' : device.currentApp }}
8688
</p>
8789

8890
<!-- 动态电量显示 -->
@@ -154,6 +156,7 @@ const getBatteryTextClass = (level, isCharging) => {
154156
<span
155157
class="inline-block px-2 py-1 text-xs rounded-full"
156158
:class="device.running ? 'bg-green-100 not-dark:text-green-800 dark:bg-green-950' : 'bg-red-100 not-dark:text-red-800 dark:bg-red-950'"
159+
v-show="device.device!=='summary'"
157160
>
158161
{{ device.running ? '运行中' : '已停止' }}
159162
</span>

src/components/DeviceStats.vue

Lines changed: 84 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
<script setup>
22
import {ref, onMounted, watch, computed} from 'vue';
33
import { useStats } from '../composables/useStats.js';
4-
4+
import { formatTime } from "../composables/formatTime.js"
55
import RecentApps from "./RecentApps.vue";
66
import UsageDetails from "./UsageDetails.vue";
77
import AppUsageChart from "./charts/AppUsageChart.vue";
88
import TimeUsageChart from "./charts/TimeUsageChart.vue";
99
import AISummary from "./AISummary.vue";
10+
import EyeTimeStats from "./EyeTimeStats.vue";
1011
1112
const props = defineProps({
1213
deviceId: {
@@ -63,18 +64,6 @@ const calculateRunningTime = () => {
6364
return Math.floor((now - startTime) / 60000);
6465
};
6566
66-
// 格式化时间
67-
const formatTime = (minutes) => {
68-
const totalMinutes = parseFloat(minutes);
69-
if (totalMinutes < 60) {
70-
return `${totalMinutes.toFixed(2)}`;
71-
} else {
72-
const hours = Math.floor(totalMinutes / 60);
73-
const remainingMinutes = Math.round(totalMinutes % 60);
74-
return `${hours}${remainingMinutes}`;
75-
}
76-
};
77-
7867
// 获取设备统计信息
7968
const getDeviceStats = () => {
8069
const defaultStats = {
@@ -153,7 +142,7 @@ watch(stats, (newStats) => {
153142
<p class="text-xl md:text-2xl font-bold">{{ getDeviceStats().appCount }}</p>
154143
</div>
155144
<div class="bg-green-50 hover:bg-green-100 transition-colors duration-200 p-4 rounded-lg shadow-md dark:bg-green-950 dark:hover:bg-green-900">
156-
<p class="text-sm text-green-700">总时间</p>
145+
<p class="text-sm text-green-700">应用总时间</p>
157146
<p class="text-xl md:text-2xl font-bold whitespace-nowrap">{{ formatTime(getDeviceStats().totalUsageMinutes) }}</p>
158147
</div>
159148
<div class="bg-yellow-50 hover:bg-yellow-100 transition-colors duration-200 p-4 rounded-lg shadow-md dark:bg-yellow-950 dark:hover:bg-yellow-900">
@@ -168,32 +157,49 @@ watch(stats, (newStats) => {
168157
</div>
169158
</div>
170159

171-
<!-- 当前使用情况 -->
172-
<div class="mb-6">
173-
<div class="bg-blue-50 hover:bg-blue-100 transition-colors duration-200 p-4 rounded-lg shadow-md dark:bg-[#1d1f20] dark:hover:bg-blue-900/30">
174-
<div class="flex items-center justify-between">
175-
<div>
176-
<p class="text-sm text-blue-800">{{ deviceInfo?.running ? '当前应用' : '上次应用' }}</p>
177-
<p class="text-xl font-bold">{{ deviceInfo?.currentApp }}</p>
178-
</div>
179-
<div>
180-
<p class="text-sm text-blue-800">状态</p>
181-
<p class="text-xl font-bold">{{ deviceInfo?.running ? '运行中' : '已停止' }}</p>
182-
</div>
183-
<div>
184-
<p class="text-sm text-blue-800">已运行时间</p>
185-
<p class="text-xl font-bold">{{ calculateRunningTime() }} 分钟</p>
160+
<!-- 当前使用情况和用眼时间的切换容器 -->
161+
<div class="relative mb-6">
162+
<Transition name="slide-fade" mode="out-in">
163+
<!-- 当前使用情况 -->
164+
<div v-if="deviceInfo?.device !== 'summary'" key="current-app">
165+
<div class="bg-blue-50 hover:bg-blue-100 transition-colors duration-200 p-4 rounded-lg shadow-md dark:bg-[#1d1f20] dark:hover:bg-blue-900/30">
166+
<div class="flex items-center justify-between">
167+
<div>
168+
<p class="text-sm text-blue-800">{{ deviceInfo?.running ? '当前应用' : '上次应用' }}</p>
169+
<p class="text-xl font-bold">{{ deviceInfo?.currentApp }}</p>
170+
</div>
171+
<div>
172+
<p class="text-sm text-blue-800">状态</p>
173+
<p class="text-xl font-bold">{{ deviceInfo?.running ? '运行中' : '已停止' }}</p>
174+
</div>
175+
<div>
176+
<p class="text-sm text-blue-800">已运行时间</p>
177+
<p class="text-xl font-bold">{{ calculateRunningTime() }} 分钟</p>
178+
</div>
179+
</div>
186180
</div>
187181
</div>
188-
</div>
182+
183+
<!-- 用眼时间 -->
184+
<div v-else key="eye-time">
185+
<EyeTimeStats
186+
:statsType="props.statsType"
187+
:timeOffset="props.timeOffset"
188+
:date="props.date"
189+
/>
190+
</div>
191+
</Transition>
189192
</div>
190193

191194
<!-- AI总结组件 - 支持双向绑定展开状态 -->
192-
<AISummary
193-
v-if="showAiSummary"
194-
:device-id="deviceId"
195-
v-model:is-expanded="isAISummaryExpanded"
196-
/>
195+
<Transition name="slide-fade" mode="out-in">
196+
<AISummary
197+
v-show="showAiSummary && deviceInfo?.device !== 'summary'"
198+
:device-id="deviceId"
199+
v-model:is-expanded="isAISummaryExpanded"
200+
/>
201+
</Transition>
202+
197203

198204
<!-- 图表组件 -->
199205
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
@@ -212,6 +218,47 @@ watch(stats, (newStats) => {
212218
<UsageDetails :stats="stats || {}" :show-limit="10" />
213219

214220
<!-- 最近使用的APP组件 -->
215-
<RecentApps v-show="props.statsType === 'daily'" :deviceId="deviceId" />
221+
<RecentApps v-show="props.statsType === 'daily' && deviceInfo?.device !== 'summary'" :deviceId="deviceId"/>
216222
</div>
217-
</template>
223+
</template>
224+
225+
<style scoped>
226+
/* Transition动画:同时进行淡入淡出,并保持布局流动 */
227+
.slide-fade-enter-active {
228+
transition: opacity 0.3s ease-out, transform 0.3s ease-out, max-height 0.3s ease-out;
229+
}
230+
231+
.slide-fade-leave-active {
232+
transition: opacity 0.3s ease-out, transform 0.3s ease-out, max-height 0.3s ease-out;
233+
overflow: hidden;
234+
}
235+
236+
/* 为左侧整体容器添加平滑过渡 */
237+
.space-y-6 > * {
238+
transition: transform 0.3s ease, opacity 0.3s ease;
239+
}
240+
241+
.slide-fade-enter-from {
242+
opacity: 0;
243+
transform: translateY(-20px);
244+
max-height: 0;
245+
}
246+
247+
.slide-fade-leave-to {
248+
opacity: 0;
249+
transform: translateY(-20px);
250+
max-height: 0;
251+
}
252+
253+
.slide-fade-enter-to,
254+
.slide-fade-leave-from {
255+
opacity: 1;
256+
transform: translateY(0);
257+
max-height: 500px; /* 根据实际内容高度调整 */
258+
}
259+
260+
/* 让容器在动画期间也有过渡效果 */
261+
.relative.mb-6 {
262+
transition: height 0.3s ease-out;
263+
}
264+
</style>

0 commit comments

Comments
 (0)