@@ -27,6 +27,7 @@ import { isFileSystemObserverSupported } from "../lib/fileObserver";
2727import { useTranslations } from "./LocaleProvider" ;
2828import { motion , AnimatePresence } from "framer-motion" ;
2929import { detectDockerfile } from "../lib/dockerService" ;
30+ import VectorizeModal from "./VectorizeModal" ;
3031
3132export default function ScanControls ( ) {
3233 const { t } = useTranslations ( ) ;
@@ -49,6 +50,7 @@ export default function ScanControls() {
4950 const [ scanProgress , setScanProgress ] = useState ( 0 ) ;
5051 const [ scanCompleted , setScanCompleted ] = useState ( false ) ;
5152 const [ showPulse , setShowPulse ] = useState ( false ) ;
53+ const [ showVectorizeModal , setShowVectorizeModal ] = useState ( false ) ;
5254
5355 // 监控定时器引用
5456 const monitorTimerRef = useRef < NodeJS . Timeout | null > ( null ) ;
@@ -359,6 +361,16 @@ export default function ScanControls() {
359361 }
360362 } ;
361363
364+ // 打开向量化报告模态窗
365+ const handleOpenVectorizeModal = ( ) => {
366+ setShowVectorizeModal ( true ) ;
367+ } ;
368+
369+ // 关闭向量化报告模态窗
370+ const handleCloseVectorizeModal = ( ) => {
371+ setShowVectorizeModal ( false ) ;
372+ } ;
373+
362374 // 如果没有目录句柄,不显示控制器
363375 if ( ! directoryHandle ) return null ;
364376
@@ -647,98 +659,134 @@ export default function ScanControls() {
647659 </ span >
648660 </ motion . button >
649661
662+ < motion . button
663+ onClick = { handleDownloadReport }
664+ disabled = { isDownloading || ! currentScan }
665+ className = { `px-4 py-2 rounded-md text-white transition-colors ${
666+ isDownloading || ! currentScan
667+ ? "bg-gray-400 cursor-not-allowed"
668+ : "bg-indigo-600 hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
669+ } `}
670+ whileHover = { { scale : isDownloading || ! currentScan ? 1 : 1.05 } }
671+ whileTap = { { scale : isDownloading || ! currentScan ? 1 : 0.95 } }
672+ transition = { { type : "spring" , stiffness : 400 , damping : 10 } }
673+ >
674+ < span className = "flex items-center" >
675+ { isDownloading || scanStatus === "preparing" ? (
676+ < >
677+ < motion . svg
678+ className = "-ml-1 mr-2 h-5 w-5 text-white"
679+ xmlns = "http://www.w3.org/2000/svg"
680+ fill = "none"
681+ viewBox = "0 0 24 24"
682+ animate = { {
683+ rotate : 360 ,
684+ scale : [ 1 , 1.1 , 1 ] ,
685+ } }
686+ transition = { {
687+ rotate : {
688+ duration : 1.5 ,
689+ repeat : Infinity ,
690+ ease : "linear" ,
691+ } ,
692+ scale : {
693+ duration : 1 ,
694+ repeat : Infinity ,
695+ ease : "easeInOut" ,
696+ } ,
697+ } }
698+ >
699+ < circle
700+ className = "opacity-25"
701+ cx = "12"
702+ cy = "12"
703+ r = "10"
704+ stroke = "currentColor"
705+ strokeWidth = "4"
706+ > </ circle >
707+ < path
708+ className = "opacity-75"
709+ fill = "currentColor"
710+ d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
711+ > </ path >
712+ </ motion . svg >
713+ < motion . span
714+ animate = { { opacity : [ 0.6 , 1 , 0.6 ] } }
715+ transition = { {
716+ duration : 1.5 ,
717+ repeat : Infinity ,
718+ ease : "easeInOut" ,
719+ } }
720+ >
721+ { scanStatus === "preparing"
722+ ? t ( "scanControls.preparingReport" )
723+ : t ( "scanControls.downloading" ) }
724+ </ motion . span >
725+ </ >
726+ ) : (
727+ < >
728+ < motion . svg
729+ xmlns = "http://www.w3.org/2000/svg"
730+ className = "h-5 w-5 mr-2"
731+ fill = "none"
732+ viewBox = "0 0 24 24"
733+ stroke = "currentColor"
734+ animate = { {
735+ y : [ 0 , - 2 , 0 , 2 , 0 ] ,
736+ } }
737+ transition = { {
738+ duration : 2 ,
739+ repeat : Infinity ,
740+ ease : "easeInOut" ,
741+ } }
742+ >
743+ < path
744+ strokeLinecap = "round"
745+ strokeLinejoin = "round"
746+ strokeWidth = { 2 }
747+ d = "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
748+ />
749+ </ motion . svg >
750+ { t ( "scanControls.download" ) }
751+ </ >
752+ ) }
753+ </ span >
754+ </ motion . button >
755+
756+ { /* 向量化报告按钮 */ }
650757 { currentScan && (
651758 < motion . button
652- onClick = { handleDownloadReport }
653- disabled = { isDownloading || ! changeReport }
654- className = { `px-4 py-2 rounded-md text-white transition-colors ${
655- isDownloading || ! changeReport
656- ? "bg-gray-400 cursor-not-allowed"
657- : "bg-indigo-600 hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
658- } `}
659- whileHover = { { scale : isDownloading || ! changeReport ? 1 : 1.05 } }
660- whileTap = { { scale : isDownloading || ! changeReport ? 1 : 0.95 } }
759+ onClick = { handleOpenVectorizeModal }
760+ className = "px-4 py-2 rounded-md text-white transition-colors bg-purple-600 hover:bg-purple-700 focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
761+ whileHover = { { scale : 1.05 } }
762+ whileTap = { { scale : 0.95 } }
661763 transition = { { type : "spring" , stiffness : 400 , damping : 10 } }
662764 >
663765 < span className = "flex items-center" >
664- { isDownloading || scanStatus === "preparing" ? (
665- < >
666- < motion . svg
667- className = "-ml-1 mr-2 h-5 w-5 text-white"
668- xmlns = "http://www.w3.org/2000/svg"
669- fill = "none"
670- viewBox = "0 0 24 24"
671- animate = { {
672- rotate : 360 ,
673- scale : [ 1 , 1.1 , 1 ] ,
674- } }
675- transition = { {
676- rotate : {
677- duration : 1.5 ,
678- repeat : Infinity ,
679- ease : "linear" ,
680- } ,
681- scale : {
682- duration : 1 ,
683- repeat : Infinity ,
684- ease : "easeInOut" ,
685- } ,
686- } }
687- >
688- < circle
689- className = "opacity-25"
690- cx = "12"
691- cy = "12"
692- r = "10"
693- stroke = "currentColor"
694- strokeWidth = "4"
695- > </ circle >
696- < path
697- className = "opacity-75"
698- fill = "currentColor"
699- d = "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
700- > </ path >
701- </ motion . svg >
702- < motion . span
703- animate = { { opacity : [ 0.6 , 1 , 0.6 ] } }
704- transition = { {
705- duration : 1.5 ,
706- repeat : Infinity ,
707- ease : "easeInOut" ,
708- } }
709- >
710- { scanStatus === "preparing"
711- ? t ( "scanControls.preparingReport" )
712- : t ( "scanControls.downloading" ) }
713- </ motion . span >
714- </ >
715- ) : (
716- < >
717- < motion . svg
718- xmlns = "http://www.w3.org/2000/svg"
719- className = "h-5 w-5 mr-2"
720- fill = "none"
721- viewBox = "0 0 24 24"
722- stroke = "currentColor"
723- animate = { {
724- y : [ 0 , - 2 , 0 , 2 , 0 ] ,
725- } }
726- transition = { {
727- duration : 2 ,
728- repeat : Infinity ,
729- ease : "easeInOut" ,
730- } }
731- >
732- < path
733- strokeLinecap = "round"
734- strokeLinejoin = "round"
735- strokeWidth = { 2 }
736- d = "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
737- />
738- </ motion . svg >
739- { t ( "scanControls.download" ) }
740- </ >
741- ) }
766+ < motion . svg
767+ xmlns = "http://www.w3.org/2000/svg"
768+ className = "h-5 w-5 mr-2"
769+ fill = "none"
770+ viewBox = "0 0 24 24"
771+ stroke = "currentColor"
772+ animate = { {
773+ rotate : [ 0 , 0 , 10 , - 10 , 0 ] ,
774+ scale : [ 1 , 1.1 , 1.1 , 1.1 , 1 ] ,
775+ } }
776+ transition = { {
777+ duration : 3 ,
778+ repeat : Infinity ,
779+ ease : "easeInOut" ,
780+ } }
781+ >
782+ < path
783+ strokeLinecap = "round"
784+ strokeLinejoin = "round"
785+ strokeWidth = { 2 }
786+ d = "M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"
787+ />
788+ </ motion . svg >
789+ { t ( "scanControls.vectorize" ) }
742790 </ span >
743791 </ motion . button >
744792 ) }
@@ -829,6 +877,26 @@ export default function ScanControls() {
829877 </ motion . div >
830878 ) }
831879 </ AnimatePresence >
880+
881+ { /* 向量化报告模态窗 */ }
882+ { showVectorizeModal && (
883+ < div
884+ className = "fixed inset-0 z-[9999]"
885+ style = { {
886+ position : "fixed" ,
887+ top : 0 ,
888+ left : 0 ,
889+ right : 0 ,
890+ bottom : 0 ,
891+ display : "flex" ,
892+ alignItems : "center" ,
893+ justifyContent : "center" ,
894+ backgroundColor : "rgba(0, 0, 0, 0.5)" ,
895+ } }
896+ >
897+ < VectorizeModal onClose = { handleCloseVectorizeModal } />
898+ </ div >
899+ ) }
832900 </ div >
833901 ) ;
834902}
0 commit comments