4040 class =" todo-item"
4141 >
4242 <span class =" todo-status" :class =" todo.status" >
43- {{ todo.status === 'completed' ? '✓' : todo.status === 'in_progress' ? '⟳' : '○' }}
43+ <svg v-if =" todo.status === 'completed'" viewBox =" 0 0 24 24" fill =" currentColor" class =" icon" >
44+ <path d =" M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
45+ </svg >
46+ <svg v-else-if =" todo.status === 'in_progress'" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" class =" icon spinning" >
47+ <circle cx =" 12" cy =" 12" r =" 10" stroke-dasharray =" 31.416" stroke-dashoffset =" 31.416" stroke-linecap =" round" />
48+ </svg >
49+ <svg v-else viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2" class =" icon" >
50+ <circle cx =" 12" cy =" 12" r =" 10" />
51+ </svg >
4452 </span >
4553 <span class =" todo-text" >{{ todo.content }}</span >
4654 </div >
6068 @click =" showFileContent(fileItem.path, fileItem)"
6169 >
6270 <div class =" file-info" >
63- <div class =" file-name" >{{ getFileName(fileItem) }}</div >
64- <div class =" file-time" v-if =" fileItem.modified_at" >
65- {{ formatDate(fileItem.modified_at) }}
71+ <div class =" file-content-wrapper" >
72+ <div class =" file-name" >{{ getFileName(fileItem) }}</div >
73+ <div class =" file-time" v-if =" fileItem.modified_at" >
74+ {{ formatDate(fileItem.modified_at) }}
75+ </div >
6676 </div >
77+ <button
78+ class =" download-btn"
79+ @click.stop =" downloadFile(fileItem)"
80+ title =" 下载文件"
81+ >
82+ <Download :size =" 18" />
83+ </button >
6784 </div >
6885 </div >
6986 </div >
92109
93110<script setup>
94111import { computed , ref , watch } from ' vue' ;
112+ import { Download } from ' lucide-vue-next' ;
95113
96114const props = defineProps ({
97115 visible: {
@@ -211,6 +229,24 @@ const closeModal = () => {
211229 currentFilePath .value = ' ' ;
212230};
213231
232+ const downloadFile = (fileItem ) => {
233+ try {
234+ const content = formatContent (fileItem .content );
235+ const blob = new Blob ([content], { type: ' text/plain;charset=utf-8' });
236+ const url = URL .createObjectURL (blob);
237+ const link = document .createElement (' a' );
238+
239+ link .href = url;
240+ link .download = getFileName (fileItem);
241+ document .body .appendChild (link);
242+ link .click ();
243+ document .body .removeChild (link);
244+ URL .revokeObjectURL (url);
245+ } catch (error) {
246+ console .error (' 下载文件失败:' , error);
247+ }
248+ };
249+
214250const emitRefresh = () => {
215251 emit (' refresh' );
216252};
@@ -330,19 +366,35 @@ const emitRefresh = () => {
330366
331367.todo - status {
332368 flex- shrink: 0 ;
333- width: 16px ;
334- height: 16px ;
369+ width: 20px ;
370+ height: 20px ;
335371 display: flex;
336372 align- items: center;
337373 justify- content: center;
338- font- size: 10px ;
339374 margin- top: 2px ;
340375 border- radius: 50 % ;
341376 transition: all 0 .15s ease;
342377
378+ .icon {
379+ width: 14px ;
380+ height: 14px ;
381+ transition: all 0 .15s ease;
382+ }
383+
384+ .spinning {
385+ animation: spin 1 .5s linear infinite;
386+ stroke- dasharray: 31.416 ;
387+ stroke- dashoffset: 0 ;
388+ animation: spin 1 .5s linear infinite;
389+ }
390+
343391 & .completed {
344392 background: var (-- color- success- 50 );
345393 color: var (-- color- success- 700 );
394+
395+ .icon {
396+ transform: scale (1.1 );
397+ }
346398 }
347399
348400 & .in_progress {
@@ -356,6 +408,20 @@ const emitRefresh = () => {
356408 }
357409}
358410
411+ @keyframes spin {
412+ 0 % {
413+ transform: rotate (0deg );
414+ stroke- dashoffset: 0 ;
415+ }
416+ 50 % {
417+ stroke- dashoffset: 15.708 ;
418+ }
419+ 100 % {
420+ transform: rotate (360deg );
421+ stroke- dashoffset: 0 ;
422+ }
423+ }
424+
359425.todo - text {
360426 flex: 1 ;
361427 font- size: 14px ;
@@ -377,7 +443,7 @@ const emitRefresh = () => {
377443
378444.file - item {
379445 padding: 12px 14px ;
380- background: var (-- main - 5 );
446+ background: var (-- gray - 0 );
381447 border: 1px solid var (-- gray- 150 );
382448 border- radius: 6px ;
383449 cursor: pointer;
@@ -395,6 +461,15 @@ const emitRefresh = () => {
395461 justify- content: space- between;
396462 align- items: center;
397463 gap: 10px ;
464+ width: 100 % ;
465+ }
466+
467+ .file - content- wrapper {
468+ flex: 1 ;
469+ display: flex;
470+ flex- direction: column;
471+ gap: 4px ;
472+ min- width: 0 ;
398473}
399474
400475.file - name {
@@ -415,6 +490,29 @@ const emitRefresh = () => {
415490 white- space: nowrap;
416491}
417492
493+ .download - btn {
494+ display: flex;
495+ align- items: center;
496+ justify- content: center;
497+ width: 28px ;
498+ height: 28px ;
499+ background: transparent;
500+ border: none;
501+ color: var (-- gray- 600 );
502+ cursor: pointer;
503+ transition: all 0 .15s ease;
504+ padding: 0 ;
505+ flex- shrink: 0 ;
506+
507+ & : hover {
508+ color: var (-- main- 600 );
509+ }
510+
511+ & : active {
512+ color: var (-- main- 400 );
513+ }
514+ }
515+
418516.file - content {
419517 max- height: 60vh ;
420518 overflow- y: auto;
0 commit comments