11import { useEffect , useState } from "react" ;
22import { Input , Button , Radio , Form , App , Select , InputNumber } from "antd" ;
3- import { Link , useNavigate } from "react-router" ;
3+ import { Link , useNavigate , useSearchParams } from "react-router" ;
44import { ArrowLeft } from "lucide-react" ;
5- import { createTaskUsingPost , queryDataXTemplatesUsingGet } from "../collection.apis" ;
5+ import { createTaskUsingPost , updateTaskUsingPut , queryDataXTemplatesUsingGet , queryTasksUsingGet } from "../collection.apis" ;
66import SimpleCronScheduler from "@/pages/DataCollection/Create/SimpleCronScheduler" ;
77import { getSyncModeMap } from "../collection.const" ;
88import { SyncMode } from "../collection.model" ;
@@ -39,11 +39,17 @@ type TemplateFieldDef = {
3939
4040export default function CollectionTaskCreate ( ) {
4141 const navigate = useNavigate ( ) ;
42+ const [ searchParams ] = useSearchParams ( ) ;
4243 const [ form ] = Form . useForm ( ) ;
4344 const { message } = App . useApp ( ) ;
4445 const { t } = useTranslation ( ) ;
4546 const syncModeOptions = Object . values ( getSyncModeMap ( t ) ) ;
4647
48+ // 编辑模式
49+ const taskId = searchParams . get ( "taskId" ) ;
50+ const isEditMode = ! ! taskId ;
51+ const [ editLoading , setEditLoading ] = useState ( false ) ;
52+
4753 const [ templates , setTemplates ] = useState < CollectionTemplate [ ] > ( [ ] ) ;
4854 const [ templatesLoading , setTemplatesLoading ] = useState ( false ) ;
4955 const [ selectedTemplateId , setSelectedTemplateId ] = useState < string | undefined > ( undefined ) ;
@@ -65,8 +71,52 @@ export default function CollectionTaskCreate() {
6571 cronExpression : "0 0 * * *" ,
6672 } ) ;
6773
74+ // 解析 cron 表达式
75+ const parseCronExpression = ( cronExpr : string ) => {
76+ const parts = cronExpr . trim ( ) . split ( / \s + / ) ;
77+ if ( parts . length !== 5 ) {
78+ // 无效的 cron 表达式,返回默认值
79+ return {
80+ type : "daily" as const ,
81+ time : "00:00" ,
82+ cronExpression : cronExpr ,
83+ } ;
84+ }
85+
86+ const [ minute , hour , day , month , weekday ] = parts ;
87+ const formattedHour = hour . padStart ( 2 , "0" ) ;
88+ const formattedMinute = minute . padStart ( 2 , "0" ) ;
89+ const time = `${ formattedHour } :${ formattedMinute } ` ;
90+
91+ // 判断类型:monthly (指定日期), weekly (指定星期), daily (都是 *)
92+ if ( day !== "*" && month === "*" ) {
93+ // monthly: 例如 "0 9 1 * *" 表示每月1号9点
94+ return {
95+ type : "monthly" as const ,
96+ time,
97+ monthDay : parseInt ( day , 10 ) ,
98+ cronExpression : cronExpr ,
99+ } ;
100+ } else if ( weekday !== "*" && day === "*" ) {
101+ // weekly: 例如 "0 9 * * 1" 表示每周一9点
102+ return {
103+ type : "weekly" as const ,
104+ time,
105+ weekDay : parseInt ( weekday , 10 ) ,
106+ cronExpression : cronExpr ,
107+ } ;
108+ } else {
109+ // daily: 例如 "0 9 * * *" 表示每天9点
110+ return {
111+ type : "daily" as const ,
112+ time,
113+ cronExpression : cronExpr ,
114+ } ;
115+ }
116+ } ;
117+
68118 useEffect ( ( ) => {
69- const run = async ( ) => {
119+ const loadTemplates = async ( ) => {
70120 setTemplatesLoading ( true ) ;
71121 try {
72122 const resp : any = await queryDataXTemplatesUsingGet ( { page : 1 , size : 1000 } ) ;
@@ -78,8 +128,56 @@ export default function CollectionTaskCreate() {
78128 setTemplatesLoading ( false ) ;
79129 }
80130 } ;
81- run ( )
82- } , [ ] ) ;
131+
132+ const loadTask = async ( ) => {
133+ if ( ! taskId ) return ;
134+ setEditLoading ( true ) ;
135+ try {
136+ const resp : any = await queryTasksUsingGet ( { page : 1 , size : 1 } ) ;
137+ const task = resp ?. data ?. content ?. find ( ( t : any ) => t . id === taskId ) ;
138+ if ( task ) {
139+ // 设置表单值
140+ setSelectedTemplateId ( task . templateId ) ;
141+ form . setFieldsValue ( {
142+ name : task . name ,
143+ description : task . description ,
144+ syncMode : task . syncMode ,
145+ scheduleExpression : task . scheduleExpression || "" ,
146+ timeoutSeconds : task . timeoutSeconds || 3600 ,
147+ templateId : task . templateId ,
148+ config : task . config || { parameter : { } , reader : { } , writer : { } } ,
149+ } ) ;
150+ setNewTask ( {
151+ name : task . name ,
152+ description : task . description ,
153+ syncMode : task . syncMode ,
154+ scheduleExpression : task . scheduleExpression || "" ,
155+ timeoutSeconds : task . timeoutSeconds || 3600 ,
156+ templateId : task . templateId ,
157+ config : task . config || { parameter : { } , reader : { } , writer : { } } ,
158+ } ) ;
159+ // 解析 cron 表达式
160+ if ( task . scheduleExpression ) {
161+ const parsedSchedule = parseCronExpression ( task . scheduleExpression ) ;
162+ setScheduleExpression ( parsedSchedule ) ;
163+ }
164+ } else {
165+ message . error ( t ( "dataCollection.taskManagement.messages.updateFailed" ) ) ;
166+ navigate ( "/data/collection" ) ;
167+ }
168+ } catch ( e ) {
169+ message . error ( t ( "dataCollection.taskManagement.messages.updateFailed" ) ) ;
170+ navigate ( "/data/collection" ) ;
171+ } finally {
172+ setEditLoading ( false ) ;
173+ }
174+ } ;
175+
176+ loadTemplates ( ) ;
177+ if ( isEditMode ) {
178+ loadTask ( ) ;
179+ }
180+ } , [ taskId ] ) ;
83181
84182 const parseJsonObjectInput = ( value : any ) => {
85183 if ( value === undefined || value === null ) return value ;
@@ -195,10 +293,30 @@ export default function CollectionTaskCreate() {
195293 ) ,
196294 } ;
197295 }
198- await createTaskUsingPost ( payload ) ;
199- message . success ( t ( "dataCollection.createTask.messages.createSuccess" ) ) ;
296+
297+ if ( isEditMode ) {
298+ // 编辑模式:只更新允许的字段
299+ const updateData : any = {
300+ description : payload . description ,
301+ timeoutSeconds : payload . timeoutSeconds ,
302+ config : payload . config ,
303+ } ;
304+ if ( payload . syncMode === SyncMode . SCHEDULED && payload . scheduleExpression ) {
305+ updateData . scheduleExpression = payload . scheduleExpression ;
306+ }
307+ await updateTaskUsingPut ( taskId ! , updateData ) ;
308+ message . success ( t ( "dataCollection.taskManagement.messages.updateSuccess" ) ) ;
309+ } else {
310+ // 创建模式
311+ await createTaskUsingPost ( payload ) ;
312+ message . success ( t ( "dataCollection.createTask.messages.createSuccess" ) ) ;
313+ }
200314 navigate ( "/data/collection" ) ;
201315 } catch ( error ) {
316+ if ( error . errorFields ) {
317+ // 表单验证错误,不显示消息
318+ return ;
319+ }
202320 message . error (
203321 t ( "dataCollection.createTask.messages.errorWithDetail" , {
204322 message : error ?. data ?. message ?? "" ,
@@ -412,22 +530,30 @@ export default function CollectionTaskCreate() {
412530
413531 return (
414532 < div className = "h-full flex flex-col" >
415- < div className = "flex items-center justify-between mb-2" >
416- < div className = "flex items-center" >
417- < Link to = "/data/collection" >
418- < Button type = "text" >
419- < ArrowLeft className = "w-4 h-4 mr-1" />
420- </ Button >
421- </ Link >
422- < h1 className = "text-xl font-bold bg-clip-text" >
423- { t ( "dataCollection.createTask.title" ) }
424- </ h1 >
533+ { editLoading ? (
534+ < div className = "flex-1 flex items-center justify-center" >
535+ < div className = "text-gray-500" > { t ( "common.loading" ) } </ div >
425536 </ div >
426- </ div >
537+ ) : (
538+ < >
539+ < div className = "flex items-center justify-between mb-2" >
540+ < div className = "flex items-center" >
541+ < Link to = "/data/collection" >
542+ < Button type = "text" >
543+ < ArrowLeft className = "w-4 h-4 mr-1" />
544+ </ Button >
545+ </ Link >
546+ < h1 className = "text-xl font-bold bg-clip-text" >
547+ { isEditMode
548+ ? t ( "dataCollection.createTask.editTitle" )
549+ : t ( "dataCollection.createTask.title" ) }
550+ </ h1 >
551+ </ div >
552+ </ div >
427553
428- < div className = "flex-overflow-auto border-card" >
429- < div className = "flex-1 overflow-auto p-4" >
430- < Form
554+ < div className = "flex-overflow-auto border-card" >
555+ < div className = "flex-1 overflow-auto p-4" >
556+ < Form
431557 form = { form }
432558 layout = "vertical"
433559 className = "[&_.ant-form-item]:mb-3 [&_.ant-form-item-label]:pb-1"
@@ -447,7 +573,10 @@ export default function CollectionTaskCreate() {
447573 name = "name"
448574 rules = { [ { required : true , message : t ( "dataCollection.createTask.basicInfo.nameRequired" ) } ] }
449575 >
450- < Input placeholder = { t ( "dataCollection.createTask.basicInfo.namePlaceholder" ) } />
576+ < Input
577+ placeholder = { t ( "dataCollection.createTask.basicInfo.namePlaceholder" ) }
578+ disabled = { isEditMode }
579+ />
451580 </ Form . Item >
452581
453582 < Form . Item
@@ -487,6 +616,7 @@ export default function CollectionTaskCreate() {
487616 < Radio . Group
488617 value = { newTask . syncMode }
489618 options = { syncModeOptions }
619+ disabled = { isEditMode }
490620 onChange = { ( e ) => {
491621 const value = e . target . value ;
492622 setNewTask ( {
@@ -532,6 +662,7 @@ export default function CollectionTaskCreate() {
532662 < Select
533663 placeholder = { t ( "dataCollection.createTask.templateConfig.selectTemplatePlaceholder" ) }
534664 loading = { templatesLoading }
665+ disabled = { isEditMode }
535666 onChange = { ( templateId ) => {
536667 setSelectedTemplateId ( templateId ) ;
537668 form . setFieldsValue ( {
@@ -607,17 +738,21 @@ export default function CollectionTaskCreate() {
607738 ) : null }
608739 </ >
609740 ) : null }
610- </ Form >
611- </ div >
612- < div className = "flex gap-2 justify-end border-top p-4" >
613- < Button onClick = { ( ) => navigate ( "/data/collection" ) } >
614- { t ( "dataCollection.createTask.cancel" ) }
615- </ Button >
616- < Button type = "primary" onClick = { handleSubmit } >
617- { t ( "dataCollection.createTask.submit" ) }
618- </ Button >
619- </ div >
620- </ div >
741+ </ Form >
742+ </ div >
743+ < div className = "flex gap-2 justify-end border-top p-4" >
744+ < Button onClick = { ( ) => navigate ( "/data/collection" ) } >
745+ { t ( "dataCollection.createTask.cancel" ) }
746+ </ Button >
747+ < Button type = "primary" onClick = { handleSubmit } >
748+ { isEditMode
749+ ? t ( "dataCollection.createTask.updateButton" )
750+ : t ( "dataCollection.createTask.submit" ) }
751+ </ Button >
752+ </ div >
753+ </ div >
754+ </ >
755+ ) }
621756 </ div >
622757 ) ;
623758}
0 commit comments