113113 <div class =" d-flex flex-wrap justify-space-between align-center ga-2 mb-3" >
114114 <div >
115115 <div class =" text-subtitle-1 font-weight-bold" >Neo Skills</div >
116- <div class =" text-caption text-medium-emphasis" >筛选候选与发布记录 </div >
116+ <div class =" text-caption text-medium-emphasis" >{{ tm("skills.neoFilterHint") }} </div >
117117 </div >
118118 <v-btn color =" primary" prepend-icon =" mdi-refresh" variant =" flat" @click =" fetchNeoData" >
119119 {{ tm("skills.refresh") }}
188188 <v-btn size =" x-small" color =" warning" variant =" tonal" @click =" evaluateCandidate(item, false)" >
189189 {{ tm("skills.neoReject") }}
190190 </v-btn >
191- <v-btn size =" x-small" color =" primary" variant =" tonal" @click =" promoteCandidate(item, 'canary')" >
191+ <v-btn
192+ size =" x-small"
193+ color =" primary"
194+ variant =" tonal"
195+ :loading =" isCandidatePromoteLoading(item.id, 'canary')"
196+ :disabled =" isCandidatePromoting(item.id)"
197+ @click =" promoteCandidate(item, 'canary')"
198+ >
192199 Canary
193200 </v-btn >
194- <v-btn size =" x-small" color =" primary" variant =" tonal" @click =" promoteCandidate(item, 'stable')" >
201+ <v-btn
202+ size =" x-small"
203+ color =" primary"
204+ variant =" tonal"
205+ :loading =" isCandidatePromoteLoading(item.id, 'stable')"
206+ :disabled =" isCandidatePromoting(item.id)"
207+ @click =" promoteCandidate(item, 'stable')"
208+ >
195209 Stable
196210 </v-btn >
197211 <v-btn
@@ -347,6 +361,7 @@ export default {
347361 status: " " ,
348362 stage: " " ,
349363 });
364+ const candidatePromoteLoading = reactive ({});
350365 const payloadDialog = reactive ({
351366 show: false ,
352367 content: " " ,
@@ -631,10 +646,21 @@ export default {
631646 }
632647 };
633648
649+ const candidatePromoteLoadingKey = (candidateId , stage ) => ` ${ candidateId} :${ stage} ` ;
650+ const isCandidatePromoteLoading = (candidateId , stage ) =>
651+ !! candidatePromoteLoading[candidatePromoteLoadingKey (candidateId, stage)];
652+ const isCandidatePromoting = (candidateId ) =>
653+ isCandidatePromoteLoading (candidateId, " canary" ) || isCandidatePromoteLoading (candidateId, " stable" );
654+
634655 const promoteCandidate = async (candidate , stage ) => {
656+ const candidateId = candidate? .id ;
657+ if (! candidateId) return ;
658+ const loadingKey = candidatePromoteLoadingKey (candidateId, stage);
659+ if (candidatePromoteLoading[loadingKey]) return ;
660+ candidatePromoteLoading[loadingKey] = true ;
635661 try {
636662 const res = await axios .post (" /api/skills/neo/promote" , {
637- candidate_id: candidate . id ,
663+ candidate_id: candidateId ,
638664 stage,
639665 sync_to_local: true ,
640666 });
@@ -650,6 +676,8 @@ export default {
650676 }
651677 } catch (_err) {
652678 showMessage (tm (" skills.neoPromoteFailed" ), " error" );
679+ } finally {
680+ candidatePromoteLoading[loadingKey] = false ;
653681 }
654682 };
655683
@@ -818,6 +846,8 @@ export default {
818846 deleteSkill,
819847 evaluateCandidate,
820848 promoteCandidate,
849+ isCandidatePromoteLoading,
850+ isCandidatePromoting,
821851 rollbackRelease,
822852 deactivateRelease,
823853 handleReleaseLifecycleAction,
0 commit comments