Skip to content

Commit cf22b29

Browse files
committed
refactor(App): 重构任务列表和时间线显示逻辑
- 修改任务列表显示,增加项目名称和操作按钮 - 优化时间线展示,移除不必要的设置选项 - 添加任务说明列,支持双击编辑任务名称 - 重构导出功能,简化操作流程 - 优化事件编辑对话框布局和功能
1 parent d3549a0 commit cf22b29

File tree

1 file changed

+126
-118
lines changed

1 file changed

+126
-118
lines changed

src/App.vue

+126-118
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,23 @@
1010
<div>
1111
<!-- Task List -->
1212
<el-table :data="tasks" style="width: 100%" @row-click="handleRowClick">
13-
<el-table-column label="任务名称" min-width="150">
13+
<el-table-column label="项目名称" min-width="150">
1414
<template v-slot="scope">
15-
<el-input
16-
v-if="scope.row.isEditing"
17-
v-model="scope.row.editingName"
18-
@blur="finishEdit(scope.row)"
19-
@keyup.enter="finishEdit(scope.row)"
20-
ref="nameInput"
21-
/>
22-
<span v-else @dblclick="startEdit(scope.row)" class="cursor-pointer hover:text-blue-500">
23-
{{ scope.row.name }}
24-
</span>
15+
<el-tooltip :content="taskNameTooltip(scope.row)" placement="top">
16+
<span>
17+
<el-input
18+
v-if="scope.row.isEditing"
19+
v-model="scope.row.editingName"
20+
@blur="finishEdit(scope.row)"
21+
@keyup.enter="finishEdit(scope.row)"
22+
ref="nameInput"
23+
/>
24+
<span v-else @dblclick="startEdit(scope.row)" class="cursor-pointer hover:text-blue-500">
25+
{{ scope.row.name }}
26+
</span>
27+
<el-icon-question class="ml-1 cursor-pointer" @click="showTooltip(scope.row)" />
28+
</span>
29+
</el-tooltip>
2530
</template>
2631
</el-table-column>
2732
<el-table-column label="时长统计(事件计数)" min-width="200">
@@ -41,7 +46,7 @@
4146
</div>
4247
</template>
4348
</el-table-column>
44-
<el-table-column label="任务说明" min-width="200">
49+
<el-table-column label="任务说明" min-width="300">
4550
<template v-slot="scope">
4651
<el-input
4752
v-model="taskDescriptions[scope.row.id]"
@@ -52,132 +57,121 @@
5257
></el-input>
5358
</template>
5459
</el-table-column>
55-
<el-table-column label="计时状态" min-width="150">
60+
<el-table-column label="计时状态" min-width="100">
5661
<template v-slot="scope">
5762
<span v-if="isTaskRunning(scope.row.id)" class="text-blue-500 font-bold">
5863
已计时: {{ getRunningTime(scope.row.id) }}
5964
</span>
6065
<span v-else>未开始计时</span>
6166
</template>
6267
</el-table-column>
63-
<el-table-column label="操作" min-width="500">
68+
<el-table-column label="部分操作" min-width="100">
6469
<template v-slot="scope">
65-
<div class="flex space-x-2">
66-
<el-button style="margin-left: 0px;" @click.stop="startTimer(scope.row.id)" type="primary" :disabled="isTaskRunning(scope.row.id)">开始计时</el-button>
67-
<el-button style="margin-left: 0px;" @click.stop="stopTimer(scope.row.id)" type="danger" :disabled="!isTaskRunning(scope.row.id)">结束计时</el-button>
68-
<el-dropdown trigger="click" @command="command => handleExport(command, scope.row)">
69-
<el-button type="success">导出<el-icon class="el-icon--right"><arrow-down /></el-icon></el-button>
70-
<template #dropdown>
71-
<el-dropdown-menu>
72-
<el-dropdown-item command="json">JSON</el-dropdown-item>
73-
<el-dropdown-item command="csv">CSV</el-dropdown-item>
74-
</el-dropdown-menu>
75-
</template>
76-
</el-dropdown>
77-
<el-button @click="deleteTask(scope.row)" type="danger">删除</el-button>
70+
<div class="flex flex-wrap">
71+
<el-button style="margin-left: 10px;" class="mb-2" @click.stop="startTimer(scope.row.id)" type="primary" :disabled="isTaskRunning(scope.row.id)">开始计时</el-button>
72+
<el-button style="margin-left: 10px;" class="mb-2" @click.stop="stopTimer(scope.row.id)" type="danger" :disabled="!isTaskRunning(scope.row.id)">结束计时</el-button>
7873
</div>
7974
</template>
8075
</el-table-column>
8176
</el-table>
8277
<!-- Add Task -->
83-
<el-input v-model="newTaskName" placeholder="任务名称" style="margin-top: 20px;"></el-input>
84-
<el-button @click="addTask" type="success" style="margin-top: 10px;">创建任务</el-button>
85-
</div>
86-
<!-- Global Actions -->
87-
<div class="flex gap-2 mt-4">
88-
<el-upload class="upload-demo" action="" :auto-upload="false" :show-file-list="false"
89-
accept=".json,.csv" :on-change="handleFileChange">
90-
<el-button type="primary">导入任务</el-button>
91-
</el-upload>
92-
<el-dropdown trigger="click" @command="handleGlobalExport">
93-
<el-button type="success">导出所有数据<el-icon
94-
class="el-icon--right"><arrow-down /></el-icon></el-button>
95-
<template #dropdown>
96-
<el-dropdown-menu>
97-
<el-dropdown-item command="json">JSON</el-dropdown-item>
98-
</el-dropdown-menu>
99-
</template>
100-
</el-dropdown>
101-
<el-button type="danger" @click="clearAllTasks">清除所有数据</el-button>
102-
<el-button type="success" @click="addNewEvent">新增记录</el-button>
78+
<div class="flex mt-5 w-2/4">
79+
<el-input v-model="newTaskName" placeholder="项目名称"></el-input>
80+
<el-button @click="addTask" type="success">添加项目</el-button>
81+
</div>
10382
</div>
83+
10484
<!-- Time Records Display -->
105-
<div v-if="selectedTask" class="mt-5 p-5 border border-gray-200 rounded-lg">
106-
<h3 class="text-lg font-medium mb-4">{{ selectedTask.name }} - 时间记录</h3>
107-
<div class="flex items-center gap-4 px-5 mb-4">
108-
<span>时间单位宽度: {{ (baseUnitWidth * 3600).toFixed(1) }}px/小时</span>
109-
<el-slider v-model="baseUnitWidth" :min="0.0004" :max="1" :step="0.0001"
110-
@change="handleZoomChange" class="flex-1" />
111-
</div>
112-
<!-- 增加一行小字提示:小提示:滚轮缩放时间轴,拖拽滚动时间轴 -->
113-
<div class="text-sm text-gray-500 mb-4">
114-
<span>小提示:鼠标滚轮缩放时间轴,拖拽滚动时间轴</span>
85+
<div class="mt-5 p-5 border border-gray-200 rounded-lg">
86+
<div v-if="!selectedTask">
87+
<h3 class="text-lg font-medium mb-4">时间线</h3>
88+
<div class="text-sm text-gray-500 mt-4">
89+
<span>请先选择一个项目以查看时间线(任务列表)<br>*你知道吗?双击项目名称可以重命名</span>
90+
</div>
11591
</div>
116-
117-
<div class="relative border border-gray-200 rounded-lg overflow-hidden">
118-
<div @wheel.prevent="handleWheel" @mousedown="startDrag" @mousemove="onDrag" @mouseup="stopDrag"
119-
@mouseleave="stopDrag" ref="scrollContainer"
120-
style="scrollbar-width: none; min-height: 200px;"
121-
class="relative w-full overflow-x-auto cursor-grab active:cursor-grabbing select-none">
122-
<div class="timeline-content" :style="{ minWidth: `${24 * 3600 * baseUnitWidth}rem` }">
123-
<div class="sticky top-0 z-10 flex h-8 items-center bg-gray-50 border-b border-gray-200 pl-24">
124-
<div v-for="mark in timeScaleMarks" :key="mark.time"
125-
:style="{ width: `${mark.width}rem` }"
126-
class="flex-shrink-0 text-xs text-gray-600 text-center border-r border-gray-200">
127-
{{ mark.label }}
128-
</div>
129-
</div>
130-
131-
<div class="time-blocks-container">
132-
<div v-for="(blocks, date) in formattedTimeBlocks" :key="date"
133-
class="flex h-10 my-2.5 items-center border-b border-gray-100">
134-
<div
135-
class="sticky left-0 z-20 w-24 px-2.5 py-5 text-sm text-gray-700">
136-
{{ date }}
92+
<div v-if="selectedTask">
93+
<h3 class="text-lg font-medium mb-4">时间线 ( {{ selectedTask.name||"" }} )</h3>
94+
<!-- <div class="flex items-center gap-4 px-5 mb-4">
95+
<span>时间单位宽度: {{ (baseUnitWidth * 3600).toFixed(1) }}px/小时</span>
96+
<el-slider v-model="baseUnitWidth" :min="0.0004" :max="1" :step="0.0001"
97+
@change="handleZoomChange" class="flex-1" />
98+
</div> -->
99+
<!-- 增加一行小字提示:小提示:滚轮缩放时间轴,拖拽滚动时间轴 -->
100+
<div class="mb-4">
101+
102+
<el-button type="success" @click="addNewEvent">新增记录</el-button>
103+
<el-button @click.stop="handleExport(scope.row)" type="success">导出</el-button>
104+
<el-button @click="deleteTask(scope.row)" type="danger">删除</el-button>
105+
</div>
106+
107+
<div class="relative border border-gray-200 rounded-lg overflow-hidden">
108+
<div @wheel.prevent="handleWheel" @mousedown="startDrag" @mousemove="onDrag" @mouseup="stopDrag"
109+
@mouseleave="stopDrag" ref="scrollContainer"
110+
style="scrollbar-width: none; min-height: 200px;"
111+
class="relative w-full overflow-x-auto cursor-grab active:cursor-grabbing select-none">
112+
<div class="timeline-content" :style="{ minWidth: `${24 * 3600 * baseUnitWidth}rem` }">
113+
<div class="sticky top-0 z-10 flex h-8 items-center bg-gray-50 border-b border-gray-200 pl-24">
114+
<div v-for="mark in timeScaleMarks" :key="mark.time"
115+
:style="{ width: `${mark.width}rem` }"
116+
class="flex-shrink-0 text-xs text-gray-600 text-center border-r border-gray-200">
117+
{{ mark.label }}
137118
</div>
138-
<div class="relative flex-grow h-full border-l border-gray-200">
139-
<!-- 原有的时间块显示 -->
140-
<div v-for="block in blocks" :key="block.id"
141-
class="absolute h-8 top-1 rounded text-xs text-white px-1 flex items-center justify-center overflow-hidden whitespace-nowrap opacity-100 hover:opacity-90 transition-opacity cursor-pointer"
142-
:style="{
143-
left: `${calculateLeftPosition(block.start)}rem`,
144-
width: `${calculateDuration(block.start, block.end)}rem`,
145-
backgroundColor: block.color
146-
}" :title="`${block.description}
147-
开始:${formatDetailTime(block.start)}
148-
结束:${formatDetailTime(block.end)}
149-
持续:${formatDuration(block.start, block.end)}
150-
相同颜色累计时间:${formatDurationSimple(calculateColorDurations(blocks)[block.color])}`"
151-
@click="editEvent(block, date)">
152-
{{ block.displayText }}
119+
</div>
120+
121+
<div class="time-blocks-container">
122+
<div v-for="(blocks, date) in formattedTimeBlocks" :key="date"
123+
class="flex h-10 my-2.5 items-center border-b border-gray-100">
124+
<div
125+
class="sticky left-0 z-20 w-24 px-2.5 py-5 text-sm text-gray-700">
126+
{{ date }}
127+
</div>
128+
<div class="relative flex-grow h-full border-l border-gray-200">
129+
<!-- 原有的时间块显示 -->
130+
<div v-for="block in blocks" :key="block.id"
131+
class="absolute h-8 top-1 rounded text-xs text-white px-1 flex items-center justify-center overflow-hidden whitespace-nowrap opacity-100 hover:opacity-90 transition-opacity cursor-pointer"
132+
:style="{
133+
left: `${calculateLeftPosition(block.start)}rem`,
134+
width: `${calculateDuration(block.start, block.end)}rem`,
135+
backgroundColor: block.color
136+
}" :title="`${block.description}
137+
开始:${formatDetailTime(block.start)}
138+
结束:${formatDetailTime(block.end)}
139+
持续:${formatDuration(block.start, block.end)}
140+
相同颜色累计时间:${formatDurationSimple(calculateColorDurations(blocks)[block.color])}`"
141+
@click="editEvent(block, date)">
142+
{{ block.displayText }}
143+
</div>
153144
</div>
154145
</div>
155146
</div>
156147
</div>
157148
</div>
158149
</div>
150+
<div class="text-sm text-gray-500 mt-4">
151+
<span>小提示:鼠标滚轮缩放时间轴,拖拽滚动时间轴</span>
152+
</div>
159153
</div>
160154
</div>
161-
<el-dialog v-model="editDialogVisible" title="编辑时间事件" width="500px">
162-
<el-form :model="editingEvent" label-width="100px">
155+
<el-dialog v-model="editDialogVisible" title="编辑时间片" style="width: 70%;">
156+
<el-form :model="editingEvent">
163157
<el-form-item label="开始时间">
164158
<el-time-picker v-model="editingEvent.start" format="HH:mm:ss" />
165159
</el-form-item>
166160
<el-form-item label="结束时间">
167161
<el-time-picker v-model="editingEvent.end" format="HH:mm:ss" />
168162
</el-form-item>
169163
<el-form-item label="描述">
170-
<el-input v-model="editingEvent.description" type="textarea" />
164+
<el-input v-model="editingEvent.description" type="textarea" :rows="7"/>
171165
</el-form-item>
172166
<el-form-item label="颜色">
173167
<el-color-picker v-model="editingEvent.color" />
174168
</el-form-item>
175169
</el-form>
176170
<template #footer>
177171
<span class="dialog-footer">
172+
<el-button type="danger" @click="deleteEvent">删除</el-button>
178173
<el-button @click="editDialogVisible = false">取消</el-button>
179174
<el-button type="primary" @click="saveEventEdit">保存</el-button>
180-
<el-button type="danger" @click="deleteEvent">删除事件</el-button>
181175
</span>
182176
</template>
183177
</el-dialog>
@@ -191,6 +185,15 @@
191185
inactive-text="手动导出存档"
192186
@change="saveSettings"
193187
/>
188+
<!-- Global Actions -->
189+
<div class="flex gap-2 mt-4">
190+
<el-upload class="upload-demo" action="" :auto-upload="false" :show-file-list="false"
191+
accept=".json" :on-change="handleFileChange">
192+
<el-button type="primary">导入任务</el-button>
193+
</el-upload>
194+
<el-button type="success" @click="handleGlobalExport('json')">导出所有数据</el-button>
195+
<el-button type="danger" @click="clearAllTasks">清除所有数据</el-button>
196+
</div>
194197
</div>
195198
<!-- Footer -->
196199
<div class="mt-8 text-center text-gray-500 text-sm">
@@ -214,11 +217,11 @@
214217
</template>
215218

216219
<script>
220+
import './styles/index.css'
217221
import { ref, onMounted, onUnmounted, nextTick, computed, watch } from 'vue'
218222
import { ElTable, ElTableColumn, ElButton, ElInput, ElContainer, ElMain, ElMessage, ElMessageBox } from 'element-plus'
219223
import { ArrowDown } from '@element-plus/icons-vue'
220224
import 'element-plus/dist/index.css'
221-
import './styles/index.css'
222225
223226
export default {
224227
components: {
@@ -276,6 +279,7 @@ export default {
276279
editingEventOriginal: null,
277280
requireCtrlForZoom: false, // 新增:控制是否需要Ctrl键进行缩放
278281
autoExport: true, // Add this line
282+
taskNameTooltipText: '双击可编辑项目名称',
279283
}
280284
},
281285
mounted() {
@@ -417,7 +421,6 @@ export default {
417421
return;
418422
}
419423
ElMessage.success('计时已结束');
420-
this.taskDescriptions[taskId] = ''; // 清空任务说明
421424
this.saveToStorage(); // 保存到本地存储以保持颜色信息
422425
// Only auto-export if enabled
423426
if (this.autoExport) {
@@ -654,21 +657,13 @@ export default {
654657
}
655658
},
656659
657-
handleExport(format, task) {
658-
if (format === 'json') {
659-
const data = {
660-
...task,
661-
developer: 'createskyblue'
662-
};
663-
const dataStr = JSON.stringify(data, null, 2);
664-
this.downloadFile(dataStr, `task_${task.id}.json`, 'application/json');
665-
} else if (format === 'csv') {
666-
const headers = ['开始时间,结束时间,描述\n'];
667-
const rows = task.timers.map(timer =>
668-
`${timer.start},${timer.end || ''},${timer.description}\n`
669-
);
670-
this.downloadFile(headers.concat(rows).join(''), `task_${task.id}.csv`, 'text/csv');
671-
}
660+
handleExport(task) {
661+
const data = {
662+
...task,
663+
developer: 'createskyblue'
664+
};
665+
const dataStr = JSON.stringify(data, null, 2);
666+
this.downloadFile(dataStr, `task_${task.id}.json`, 'application/json');
672667
},
673668
handleGlobalExport(format) {
674669
if (format === 'json') {
@@ -755,13 +750,13 @@ export default {
755750
},
756751
deleteTask(task) {
757752
ElMessageBox.prompt(
758-
'请输入完整的任务名称以确认删除',
753+
'请输入完整的项目名称以确认删除',
759754
'警告',
760755
{
761756
confirmButtonText: '确认',
762757
cancelButtonText: '取消',
763758
inputPattern: new RegExp(`^${task.name}$`),
764-
inputErrorMessage: '任务名称不正确'
759+
inputErrorMessage: '项目名称不正确'
765760
}
766761
).then(({ value }) => {
767762
this.tasks = this.tasks.filter(t => t.id !== task.id);
@@ -858,16 +853,23 @@ export default {
858853
859854
this.editDialogVisible = true;
860855
},
861-
862856
deleteEvent() {
863-
if (!this.editingEventOriginal || !this.selectedTask) return;
857+
if (!this.editingEventOriginal || !this.selectedTask) return;
864858
859+
this.$confirm('此操作将永久删除该记录, 是否继续?', '提示', {
860+
confirmButtonText: '确定',
861+
cancelButtonText: '取消',
862+
type: 'warning'
863+
}).then(() => {
865864
// 从任务的时间记录中删除
866865
this.selectedTask.timers = this.selectedTask.timers.filter(timer => timer !== this.editingEventOriginal);
867866
this.formattedTimeBlocks = this.formatTimeBlocks(this.selectedTask);
868867
this.saveToStorage();
869868
ElMessage.success('记录已删除');
870869
this.editDialogVisible = false;
870+
}).catch(() => {
871+
ElMessage.info('已取消删除');
872+
});
871873
},
872874
handleRowClick(row, column) {
873875
// 如果点击的是操作列或正在编辑的输入框,不进行跳转
@@ -985,6 +987,12 @@ export default {
985987
986988
return `${hours}小时${minutes}分钟 (${recordCount})`;
987989
},
990+
taskNameTooltip(row) {
991+
return this.taskNameTooltipText;
992+
},
993+
showTooltip(row) {
994+
// 可以在这里添加显示提示的逻辑,如果需要
995+
},
988996
// Add new method for saving settings
989997
saveSettings() {
990998
localStorage.setItem('taskTimeTracker_settings', JSON.stringify({

0 commit comments

Comments
 (0)