三方系统密码加密

This commit is contained in:
dengqichen 2025-11-12 09:53:10 +08:00
parent 5cc4958eb0
commit 5a924f7aa5
7 changed files with 96 additions and 51 deletions

View File

@ -183,6 +183,40 @@ const SelectSeparator = React.forwardRef<
)) ))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName SelectSeparator.displayName = SelectPrimitive.Separator.displayName
// ClearableSelect - 支持清空功能的Select包装组件
interface ClearableSelectProps {
value?: string | number | null;
onValueChange?: (value: string | null) => void;
placeholder?: string;
disabled?: boolean;
children: React.ReactNode;
defaultValue?: string;
open?: boolean;
onOpenChange?: (open: boolean) => void;
}
const ClearableSelect = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Root>,
ClearableSelectProps
>(({ value, onValueChange, children, ...props }, ref) => {
const hasValue = value !== undefined && value !== null && value !== '';
const selectProps = hasValue ? { value: value.toString() } : {};
return (
<Select
key={hasValue ? value : 'empty'}
{...selectProps}
onValueChange={(val) => {
onValueChange?.(val || null);
}}
{...props}
>
{children}
</Select>
);
});
ClearableSelect.displayName = "ClearableSelect";
export { export {
Select, Select,
SelectGroup, SelectGroup,
@ -194,4 +228,5 @@ export {
SelectSeparator, SelectSeparator,
SelectScrollUpButton, SelectScrollUpButton,
SelectScrollDownButton, SelectScrollDownButton,
ClearableSelect,
} }

View File

@ -104,7 +104,7 @@ export const EnvironmentTabs: React.FC<EnvironmentTabsProps> = React.memo(({
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (
<div className="grid grid-cols-6 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4">
{env.applications.map((app) => ( {env.applications.map((app) => (
<ApplicationCard <ApplicationCard
key={app.teamApplicationId} key={app.teamApplicationId}

View File

@ -20,8 +20,8 @@ const LoadingState = () => (
{/* 卡片骨架 */} {/* 卡片骨架 */}
<div className="space-y-4"> <div className="space-y-4">
<div className="h-12 w-full bg-slate-100 rounded animate-pulse" /> <div className="h-12 w-full bg-slate-100 rounded animate-pulse" />
<div className="grid grid-cols-6 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 gap-4">
{[1, 2, 3, 4, 5, 6].map((i) => ( {[1, 2, 3, 4].map((i) => (
<div key={i} className="h-48 bg-slate-100 rounded-lg animate-pulse" /> <div key={i} className="h-48 bg-slate-100 rounded-lg animate-pulse" />
))} ))}
</div> </div>

View File

@ -10,6 +10,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogFooter, DialogFooter,
DialogBody,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import {Button} from "@/components/ui/button"; import {Button} from "@/components/ui/button";
import { import {
@ -142,8 +143,8 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
<DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle> <DialogTitle>{isEdit ? '编辑' : '新建'}</DialogTitle>
</DialogHeader> </DialogHeader>
<Form {...form}> <Form {...form}>
<form className="space-y-6"> <form className="flex flex-col flex-1 min-h-0">
<div className="px-6"> <DialogBody>
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-3 gap-4"> <div className="grid grid-cols-3 gap-4">
<FormField <FormField
@ -293,8 +294,8 @@ const ApplicationModal: React.FC<ApplicationModalProps> = ({
/> />
</div> </div>
</div> </DialogBody>
<DialogFooter className="px-6 py-4 border-t mt-0"> <DialogFooter>
<Button type="button" variant="outline" onClick={onCancel}> <Button type="button" variant="outline" onClick={onCancel}>
</Button> </Button>

View File

@ -8,6 +8,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogBody, DialogBody,
DialogFooter,
DialogLoading, DialogLoading,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
@ -266,7 +267,7 @@ const NotificationChannelDialog: React.FC<NotificationChannelDialogProps> = ({
return ( return (
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto"> <DialogContent className="max-w-2xl">
<DialogHeader> <DialogHeader>
<DialogTitle> <DialogTitle>
{mode === 'edit' ? '编辑' : '创建'} {mode === 'edit' ? '编辑' : '创建'}
@ -277,7 +278,7 @@ const NotificationChannelDialog: React.FC<NotificationChannelDialogProps> = ({
{loading ? ( {loading ? (
<DialogLoading /> <DialogLoading />
) : ( ) : (
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4"> <form onSubmit={handleSubmit(onSubmit)} id="notification-channel-form" className="space-y-4">
{/* 渠道名称 */} {/* 渠道名称 */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="name"> <Label htmlFor="name">
@ -622,20 +623,21 @@ const NotificationChannelDialog: React.FC<NotificationChannelDialogProps> = ({
<p className="text-sm text-destructive">{errors.description.message}</p> <p className="text-sm text-destructive">{errors.description.message}</p>
)} )}
</div> </div>
{/* Submit buttons at the bottom of form */}
<div className="flex justify-end gap-2 pt-4 border-t">
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
</Button>
<Button type="submit" disabled={saving}>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{mode === 'edit' ? '保存' : '创建'}
</Button>
</div>
</form> </form>
)} )}
</DialogBody> </DialogBody>
{!loading && (
<DialogFooter>
<Button variant="outline" type="button" onClick={() => onOpenChange(false)}>
</Button>
<Button type="submit" form="notification-channel-form" disabled={saving}>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
{mode === 'edit' ? '保存' : '创建'}
</Button>
</DialogFooter>
)}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@ -22,7 +22,7 @@ import {
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { import {
Select, ClearableSelect,
SelectContent, SelectContent,
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
@ -51,9 +51,12 @@ const formSchema = z
}) })
.refine( .refine(
(data) => { (data) => {
// 如果启用了部署通知或构建通知,则通知渠道必填 // 如果启用了任意通知类型,则通知渠道必填
if ( if (
(data.notificationConfig?.deployNotificationEnabled ||data.notificationConfig?.buildNotificationEnabled) && !data.notificationConfig?.notificationChannelId (data.notificationConfig?.deployNotificationEnabled ||
data.notificationConfig?.buildNotificationEnabled ||
data.notificationConfig?.buildFailureFileEnabled) &&
!data.notificationConfig?.notificationChannelId
) { ) {
return false; return false;
} }
@ -138,6 +141,7 @@ export const NotificationConfigDialog: React.FC<
notificationChannelId: undefined, notificationChannelId: undefined,
deployNotificationEnabled: false, deployNotificationEnabled: false,
buildNotificationEnabled: false, buildNotificationEnabled: false,
buildFailureFileEnabled: false,
}, },
}); });
} }
@ -171,6 +175,9 @@ export const NotificationConfigDialog: React.FC<
}, [open, teamId, environmentId]); }, [open, teamId, environmentId]);
const handleSubmit = async (data: FormData) => { const handleSubmit = async (data: FormData) => {
console.log('表单提交数据:', data);
console.log('当前配置:', currentConfig);
if (!configId || !currentConfig) { if (!configId || !currentConfig) {
toast({ toast({
title: '错误', title: '错误',
@ -182,34 +189,31 @@ export const NotificationConfigDialog: React.FC<
setSubmitting(true); setSubmitting(true);
try { try {
// 提交完整的环境配置,只更新通知部分 // 构建通知配置对象
const payload: any = { let notificationConfig = undefined;
if (data.notificationConfig?.notificationChannelId) {
notificationConfig = {
id: data.notificationConfig.id,
notificationChannelId: data.notificationConfig.notificationChannelId,
deployNotificationEnabled: data.notificationConfig.deployNotificationEnabled ?? false,
buildNotificationEnabled: data.notificationConfig.buildNotificationEnabled ?? false,
buildFailureFileEnabled: data.notificationConfig.buildFailureFileEnabled ?? false,
};
}
// 构建完整的提交数据(保留原有配置,只更新通知部分)
const payload = {
teamId: currentConfig.teamId, teamId: currentConfig.teamId,
environmentId: currentConfig.environmentId, environmentId: currentConfig.environmentId,
approvalRequired: currentConfig.approvalRequired, approvalRequired: currentConfig.approvalRequired,
approverUserIds: currentConfig.approverUserIds || [], approverUserIds: currentConfig.approverUserIds || [],
requireCodeReview: currentConfig.requireCodeReview, requireCodeReview: currentConfig.requireCodeReview,
remark: currentConfig.remark, remark: currentConfig.remark,
// 更新通知配置 notificationConfig,
notificationConfig:
data.notificationConfig &&
(data.notificationConfig.deployNotificationEnabled ||
data.notificationConfig.buildNotificationEnabled ||
data.notificationConfig.buildFailureFileEnabled)
? {
id: data.notificationConfig.id,
notificationChannelId:
data.notificationConfig.notificationChannelId,
deployNotificationEnabled:
data.notificationConfig.deployNotificationEnabled,
buildNotificationEnabled:
data.notificationConfig.buildNotificationEnabled,
buildFailureFileEnabled:
data.notificationConfig.buildFailureFileEnabled,
}
: undefined,
}; };
console.log('最终提交的 payload:', payload);
await updateTeamEnvironmentConfig(configId, payload); await updateTeamEnvironmentConfig(configId, payload);
toast({ toast({
@ -257,10 +261,10 @@ export const NotificationConfigDialog: React.FC<
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel></FormLabel> <FormLabel></FormLabel>
<Select <ClearableSelect
value={field.value?.toString() || ''} value={field.value}
onValueChange={(value) => onValueChange={(value) =>
field.onChange(value ? Number(value) : undefined) field.onChange(value ? Number(value) : null)
} }
disabled={loadingChannels} disabled={loadingChannels}
> >
@ -268,7 +272,9 @@ export const NotificationConfigDialog: React.FC<
<SelectTrigger <SelectTrigger
clearable clearable
hasValue={!!field.value} hasValue={!!field.value}
onClear={() => field.onChange(undefined)} onClear={() => {
field.onChange(null);
}}
> >
<SelectValue <SelectValue
placeholder={ placeholder={
@ -289,7 +295,7 @@ export const NotificationConfigDialog: React.FC<
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>
</Select> </ClearableSelect>
<FormMessage /> <FormMessage />
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
@ -342,7 +348,7 @@ export const NotificationConfigDialog: React.FC<
)} )}
/> />
{/* 构建通知 */} {/* 失败日志 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.buildFailureFileEnabled" name="notificationConfig.buildFailureFileEnabled"

View File

@ -5,6 +5,7 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogFooter, DialogFooter,
DialogBody,
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@ -199,7 +200,7 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
<DialogHeader> <DialogHeader>
<DialogTitle>{isEdit ? '编辑流程' : '新建流程'}</DialogTitle> <DialogTitle>{isEdit ? '编辑流程' : '新建流程'}</DialogTitle>
</DialogHeader> </DialogHeader>
<div className="grid gap-6 px-6 py-4"> <DialogBody className="grid gap-6">
{/* 流程分类 */} {/* 流程分类 */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="categoryId"> <Label htmlFor="categoryId">
@ -325,7 +326,7 @@ const EditModal: React.FC<EditModalProps> = ({ visible, onClose, onSuccess, reco
/> />
</div> </div>
</div> </DialogBody>
<DialogFooter> <DialogFooter>
<Button variant="outline" onClick={handleClose} disabled={submitting}> <Button variant="outline" onClick={handleClose} disabled={submitting}>