重构消息通知弹窗

This commit is contained in:
dengqichen 2025-11-27 15:40:58 +08:00
parent 1af85b2940
commit 1a5786502a
7 changed files with 100 additions and 250 deletions

View File

@ -75,12 +75,11 @@ const DeploymentFormModal: React.FC<DeploymentFormModalProps> = ({
// 通知信息(使用环境配置的通知设置) // 通知信息(使用环境配置的通知设置)
notification: { notification: {
notificationChannelId: environment.notificationConfig?.notificationChannelId, notificationChannelId: environment.notificationConfig?.notificationChannelId,
deployNotificationEnabled: environment.notificationConfig?.deployNotificationEnabled, preApprovalNotificationEnabled: environment.notificationConfig?.preApprovalNotificationEnabled,
deployNotificationTemplateId: environment.notificationConfig?.deployNotificationTemplateId, preApprovalNotificationTemplateId: environment.notificationConfig?.preApprovalNotificationTemplateId,
buildNotificationEnabled: environment.notificationConfig?.buildNotificationEnabled, buildNotificationEnabled: environment.notificationConfig?.buildNotificationEnabled,
buildNotificationTemplateId: environment.notificationConfig?.buildNotificationTemplateId, buildNotificationTemplateId: environment.notificationConfig?.buildNotificationTemplateId,
buildFailureFileEnabled: environment.notificationConfig?.buildFailureFileEnabled, buildFailureFileEnabled: environment.notificationConfig?.buildFailureFileEnabled,
buildFailureNotificationTemplateId: environment.notificationConfig?.buildFailureNotificationTemplateId,
}, },
}; };
}, [app, environment, teamId, currentUser]); // 只在这些依赖变化时重新生成 }, [app, environment, teamId, currentUser]); // 只在这些依赖变化时重新生成

View File

@ -60,12 +60,11 @@ export interface ApplicationConfig {
export interface NotificationConfig { export interface NotificationConfig {
notificationChannelId: number; notificationChannelId: number;
deployNotificationEnabled: boolean; preApprovalNotificationEnabled: boolean;
deployNotificationTemplateId: number; preApprovalNotificationTemplateId: number;
buildNotificationEnabled: boolean; buildNotificationEnabled: boolean;
buildNotificationTemplateId: number; buildNotificationTemplateId: number;
buildFailureFileEnabled: boolean; buildFailureFileEnabled: boolean;
buildFailureNotificationTemplateId: number;
} }
export interface DeployEnvironment { export interface DeployEnvironment {

View File

@ -292,15 +292,15 @@ const ApplicationManageDialog: React.FC<ApplicationManageDialogProps> = ({
{/* 表格 */} {/* 表格 */}
<div className="border rounded-lg overflow-hidden"> <div className="border rounded-lg overflow-hidden">
<div className="max-h-[50vh] overflow-y-auto"> <div className="max-h-[50vh] overflow-auto">
<Table minWidth="690px"> <Table minWidth="750px">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead width="100px">ID</TableHead> <TableHead width="80px" className="whitespace-nowrap">ID</TableHead>
<TableHead width="150px"></TableHead> <TableHead width="160px" className="whitespace-nowrap"></TableHead>
<TableHead width="200px"></TableHead> <TableHead width="200px" className="whitespace-nowrap"></TableHead>
<TableHead width="160px"></TableHead> <TableHead width="180px" className="whitespace-nowrap"></TableHead>
<TableHead width="80px" sticky></TableHead> <TableHead width="80px" sticky className="whitespace-nowrap"></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -316,20 +316,20 @@ const ApplicationManageDialog: React.FC<ApplicationManageDialogProps> = ({
) : data?.content && data.content.length > 0 ? ( ) : data?.content && data.content.length > 0 ? (
data.content.map((record) => ( data.content.map((record) => (
<TableRow key={record.id}> <TableRow key={record.id}>
<TableCell width="100px" className="font-medium"> <TableCell width="80px" className="font-medium whitespace-nowrap">
{record.applicationId} {record.applicationId}
</TableCell> </TableCell>
<TableCell width="150px"> <TableCell width="160px" className="whitespace-nowrap">
{record.applicationCode || '-'} {record.applicationCode || '-'}
</TableCell> </TableCell>
<TableCell width="200px"> <TableCell width="200px" className="whitespace-nowrap">
{record.applicationName || '-'} {record.applicationName || '-'}
</TableCell> </TableCell>
<TableCell width="160px"> <TableCell width="180px" className="whitespace-nowrap">
{record.createTime || '-'} {record.createTime || '-'}
</TableCell> </TableCell>
<TableCell width="80px" sticky> <TableCell width="80px" sticky>
<div className="flex items-center justify-end gap-1"> <div className="flex items-center justify-end">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"

View File

@ -37,10 +37,10 @@ const getChannelIcon = (channelType?: string) => {
// 通知类型配置 // 通知类型配置
const notificationTypes = [ const notificationTypes = [
{ {
key: 'deployNotificationEnabled', key: 'preApprovalNotificationEnabled',
label: '部署', label: '审批前',
variant: 'success' as const, variant: 'success' as const,
description: '部署状态变更时发送通知' description: '提交部署申请进入审批时发送通知'
}, },
{ {
key: 'buildNotificationEnabled', key: 'buildNotificationEnabled',
@ -50,9 +50,9 @@ const notificationTypes = [
}, },
{ {
key: 'buildFailureFileEnabled', key: 'buildFailureFileEnabled',
label: '失败文件', label: '失败日志',
variant: 'destructive' as const, variant: 'destructive' as const,
description: '构建失败文件时发送通知' description: '构建失败时发送日志文件'
} }
]; ];
@ -71,11 +71,9 @@ export const NotificationChannelCell: React.FC<NotificationChannelCellProps> = (
const { const {
notificationChannelName, notificationChannelName,
deployNotificationEnabled, preApprovalNotificationEnabled,
buildNotificationEnabled, buildNotificationEnabled,
buildFailureFileEnabled, buildFailureFileEnabled,
updateTime,
updateBy
} = notificationConfig; } = notificationConfig;
// 计算启用的通知类型 // 计算启用的通知类型

View File

@ -44,12 +44,11 @@ const formSchema = z
.object({ .object({
id: z.number().optional(), id: z.number().optional(),
notificationChannelId: z.number().nullish(), notificationChannelId: z.number().nullish(),
deployNotificationEnabled: z.boolean().default(false), preApprovalNotificationEnabled: z.boolean().default(false),
deployNotificationTemplateId: z.number().nullish(), preApprovalNotificationTemplateId: z.number().nullish(),
buildNotificationEnabled: z.boolean().default(false), buildNotificationEnabled: z.boolean().default(false),
buildNotificationTemplateId: z.number().nullish(), buildNotificationTemplateId: z.number().nullish(),
buildFailureFileEnabled: z.boolean().default(false), buildFailureFileEnabled: z.boolean().default(false),
buildFailureNotificationTemplateId: z.number().nullish(),
}) })
.optional(), .optional(),
}) })
@ -57,7 +56,7 @@ const formSchema = z
(data) => { (data) => {
// 如果启用了任意通知类型,则通知渠道必填 // 如果启用了任意通知类型,则通知渠道必填
if ( if (
(data.notificationConfig?.deployNotificationEnabled || (data.notificationConfig?.preApprovalNotificationEnabled ||
data.notificationConfig?.buildNotificationEnabled || data.notificationConfig?.buildNotificationEnabled ||
data.notificationConfig?.buildFailureFileEnabled) && data.notificationConfig?.buildFailureFileEnabled) &&
!data.notificationConfig?.notificationChannelId !data.notificationConfig?.notificationChannelId
@ -104,7 +103,7 @@ export const NotificationConfigDialog: React.FC<
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [configId, setConfigId] = useState<number | null>(null); const [configId, setConfigId] = useState<number | null>(null);
const [currentConfig, setCurrentConfig] = useState<any>(null); // 存储完整的环境配置 const [currentConfig, setCurrentConfig] = useState<any>(null);
const [notificationChannels, setNotificationChannels] = useState< const [notificationChannels, setNotificationChannels] = useState<
NotificationChannel[] NotificationChannel[]
>([]); >([]);
@ -119,12 +118,11 @@ export const NotificationConfigDialog: React.FC<
defaultValues: { defaultValues: {
notificationConfig: { notificationConfig: {
notificationChannelId: undefined, notificationChannelId: undefined,
deployNotificationEnabled: false, preApprovalNotificationEnabled: false,
deployNotificationTemplateId: undefined, preApprovalNotificationTemplateId: undefined,
buildNotificationEnabled: false, buildNotificationEnabled: false,
buildNotificationTemplateId: undefined, buildNotificationTemplateId: undefined,
buildFailureFileEnabled: false, buildFailureFileEnabled: false,
buildFailureNotificationTemplateId: undefined,
}, },
}, },
}); });
@ -171,22 +169,20 @@ export const NotificationConfigDialog: React.FC<
if (config) { if (config) {
setConfigId(config.id); setConfigId(config.id);
setCurrentConfig(config); // 保存完整配置 setCurrentConfig(config);
form.reset({ form.reset({
notificationConfig: config.notificationConfig || { notificationConfig: config.notificationConfig || {
notificationChannelId: undefined, notificationChannelId: undefined,
deployNotificationEnabled: false, preApprovalNotificationEnabled: false,
deployNotificationTemplateId: undefined, preApprovalNotificationTemplateId: undefined,
buildNotificationEnabled: false, buildNotificationEnabled: false,
buildNotificationTemplateId: undefined, buildNotificationTemplateId: undefined,
buildFailureFileEnabled: false, buildFailureFileEnabled: false,
buildFailureNotificationTemplateId: undefined,
}, },
}); });
} }
} catch (error: any) { } catch (error: any) {
if (error.response?.status === 404) { if (error.response?.status === 404) {
// 配置不存在
toast({ toast({
title: '提示', title: '提示',
description: '请先配置该环境的基本信息', description: '请先配置该环境的基本信息',
@ -205,7 +201,6 @@ export const NotificationConfigDialog: React.FC<
} }
}; };
// 对话框打开时初始化
useEffect(() => { useEffect(() => {
if (open) { if (open) {
loadNotificationChannels(); loadNotificationChannels();
@ -213,7 +208,6 @@ export const NotificationConfigDialog: React.FC<
} }
}, [open, teamId, environmentId]); }, [open, teamId, environmentId]);
// 监听通知渠道变化,重新加载模板列表
const selectedChannelId = form.watch('notificationConfig.notificationChannelId'); const selectedChannelId = form.watch('notificationConfig.notificationChannelId');
useEffect(() => { useEffect(() => {
@ -226,9 +220,6 @@ export const NotificationConfigDialog: React.FC<
}, [selectedChannelId, notificationChannels, open]); }, [selectedChannelId, notificationChannels, open]);
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: '错误',
@ -240,22 +231,19 @@ export const NotificationConfigDialog: React.FC<
setSubmitting(true); setSubmitting(true);
try { try {
// 构建通知配置对象
let notificationConfig = undefined; let notificationConfig = undefined;
if (data.notificationConfig?.notificationChannelId) { if (data.notificationConfig?.notificationChannelId) {
notificationConfig = { notificationConfig = {
id: data.notificationConfig.id, id: data.notificationConfig.id,
notificationChannelId: data.notificationConfig.notificationChannelId, notificationChannelId: data.notificationConfig.notificationChannelId,
deployNotificationEnabled: data.notificationConfig.deployNotificationEnabled ?? false, preApprovalNotificationEnabled: data.notificationConfig.preApprovalNotificationEnabled ?? false,
deployNotificationTemplateId: data.notificationConfig.deployNotificationTemplateId ?? undefined, preApprovalNotificationTemplateId: data.notificationConfig.preApprovalNotificationTemplateId ?? undefined,
buildNotificationEnabled: data.notificationConfig.buildNotificationEnabled ?? false, buildNotificationEnabled: data.notificationConfig.buildNotificationEnabled ?? false,
buildNotificationTemplateId: data.notificationConfig.buildNotificationTemplateId ?? undefined, buildNotificationTemplateId: data.notificationConfig.buildNotificationTemplateId ?? undefined,
buildFailureFileEnabled: data.notificationConfig.buildFailureFileEnabled ?? false, buildFailureFileEnabled: data.notificationConfig.buildFailureFileEnabled ?? false,
buildFailureNotificationTemplateId: data.notificationConfig.buildFailureNotificationTemplateId ?? undefined,
}; };
} }
// 构建完整的提交数据(保留原有配置,只更新通知部分)
const payload = { const payload = {
teamId: currentConfig.teamId, teamId: currentConfig.teamId,
environmentId: currentConfig.environmentId, environmentId: currentConfig.environmentId,
@ -266,8 +254,6 @@ export const NotificationConfigDialog: React.FC<
notificationConfig, notificationConfig,
}; };
console.log('最终提交的 payload:', payload);
await updateTeamEnvironmentConfig(configId, payload); await updateTeamEnvironmentConfig(configId, payload);
toast({ toast({
@ -308,7 +294,7 @@ export const NotificationConfigDialog: React.FC<
<Form {...form}> <Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)}> <form onSubmit={form.handleSubmit(handleSubmit)}>
<DialogBody className="space-y-4"> <DialogBody className="space-y-4">
{/* 通知渠道选择 - 始终显示 */} {/* 通知渠道选择 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.notificationChannelId" name="notificationConfig.notificationChannelId"
@ -326,25 +312,16 @@ export const NotificationConfigDialog: React.FC<
<SelectTrigger <SelectTrigger
clearable clearable
hasValue={!!field.value} hasValue={!!field.value}
onClear={() => { onClear={() => field.onChange(null)}
field.onChange(null);
}}
> >
<SelectValue <SelectValue
placeholder={ placeholder={loadingChannels ? '加载中...' : '请选择通知渠道'}
loadingChannels
? '加载中...'
: '请选择通知渠道'
}
/> />
</SelectTrigger> </SelectTrigger>
</FormControl> </FormControl>
<SelectContent> <SelectContent>
{notificationChannels.map((channel) => ( {notificationChannels.map((channel) => (
<SelectItem <SelectItem key={channel.id} value={channel.id.toString()}>
key={channel.id}
value={channel.id.toString()}
>
{channel.name} ({channel.channelType}) {channel.name} ({channel.channelType})
</SelectItem> </SelectItem>
))} ))}
@ -358,65 +335,40 @@ export const NotificationConfigDialog: React.FC<
)} )}
/> />
{/* 部署通知 */} {/* 审批前提醒 */}
<div className="rounded-lg border p-4 space-y-2"> <div className="rounded-lg border p-4 space-y-2">
<div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center"> <div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center">
{/* 左侧:通知类型标题 */}
<div> <div>
<FormLabel className="text-base"></FormLabel> <FormLabel className="text-base"></FormLabel>
</div> </div>
{/* 中间:开关 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.deployNotificationEnabled" name="notificationConfig.preApprovalNotificationEnabled"
render={({ field }) => ( render={({ field }) => (
<FormItem className="mb-0"> <FormItem className="mb-0">
<FormControl> <FormControl>
<Switch <Switch checked={field.value} onCheckedChange={field.onChange} />
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
/> />
{/* 右侧:模板选择 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.deployNotificationTemplateId" name="notificationConfig.preApprovalNotificationTemplateId"
render={({ field }) => ( render={({ field }) => (
<FormItem className="mb-0"> <FormItem className="mb-0">
<FormControl> <FormControl>
<ClearableSelect <ClearableSelect
value={field.value?.toString()} value={field.value?.toString()}
onValueChange={(value) => onValueChange={(value) => field.onChange(value ? Number(value) : null)}
field.onChange(value ? Number(value) : null)
}
disabled={loadingTemplates} disabled={loadingTemplates}
> >
<SelectTrigger <SelectTrigger clearable hasValue={!!field.value} onClear={() => field.onChange(null)}>
clearable <SelectValue placeholder={loadingTemplates ? '加载中...' : '请选择通知模板(可选)'} />
hasValue={!!field.value}
onClear={() => {
field.onChange(null);
}}
>
<SelectValue
placeholder={
loadingTemplates
? '加载中...'
: '请选择通知模板(可选)'
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{notificationTemplates.map((template) => ( {notificationTemplates.map((template) => (
<SelectItem <SelectItem key={template.id} value={template.id.toString()}>
key={template.id}
value={template.id.toString()}
>
{template.name} {template.name}
</SelectItem> </SelectItem>
))} ))}
@ -428,43 +380,30 @@ export const NotificationConfigDialog: React.FC<
)} )}
/> />
</div> </div>
{/* 描述文字行 */}
<div className="grid grid-cols-[1fr_auto_1fr] gap-4"> <div className="grid grid-cols-[1fr_auto_1fr] gap-4">
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground"></div>
</div>
<div></div> <div></div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground"></div>
</div>
</div> </div>
</div> </div>
{/* 构建通知 */} {/* 构建通知 */}
<div className="rounded-lg border p-4 space-y-2"> <div className="rounded-lg border p-4 space-y-2">
<div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center"> <div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center">
{/* 左侧:通知类型标题 */}
<div> <div>
<FormLabel className="text-base"></FormLabel> <FormLabel className="text-base"></FormLabel>
</div> </div>
{/* 中间:开关 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.buildNotificationEnabled" name="notificationConfig.buildNotificationEnabled"
render={({ field }) => ( render={({ field }) => (
<FormItem className="mb-0"> <FormItem className="mb-0">
<FormControl> <FormControl>
<Switch <Switch checked={field.value} onCheckedChange={field.onChange} />
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
/> />
{/* 右侧:模板选择 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.buildNotificationTemplateId" name="notificationConfig.buildNotificationTemplateId"
@ -473,32 +412,15 @@ export const NotificationConfigDialog: React.FC<
<FormControl> <FormControl>
<ClearableSelect <ClearableSelect
value={field.value?.toString()} value={field.value?.toString()}
onValueChange={(value) => onValueChange={(value) => field.onChange(value ? Number(value) : null)}
field.onChange(value ? Number(value) : null)
}
disabled={loadingTemplates} disabled={loadingTemplates}
> >
<SelectTrigger <SelectTrigger clearable hasValue={!!field.value} onClear={() => field.onChange(null)}>
clearable <SelectValue placeholder={loadingTemplates ? '加载中...' : '请选择通知模板(可选)'} />
hasValue={!!field.value}
onClear={() => {
field.onChange(null);
}}
>
<SelectValue
placeholder={
loadingTemplates
? '加载中...'
: '请选择通知模板(可选)'
}
/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{notificationTemplates.map((template) => ( {notificationTemplates.map((template) => (
<SelectItem <SelectItem key={template.id} value={template.id.toString()}>
key={template.id}
value={template.id.toString()}
>
{template.name} {template.name}
</SelectItem> </SelectItem>
))} ))}
@ -510,114 +432,46 @@ export const NotificationConfigDialog: React.FC<
)} )}
/> />
</div> </div>
{/* 描述文字行 */}
<div className="grid grid-cols-[1fr_auto_1fr] gap-4"> <div className="grid grid-cols-[1fr_auto_1fr] gap-4">
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground"></div>
</div>
<div></div> <div></div>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground"></div>
</div>
</div> </div>
</div> </div>
{/* 失败日志 */} {/* 失败日志 */}
<div className="rounded-lg border p-4 space-y-2"> <div className="rounded-lg border p-4 space-y-2">
<div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center"> <div className="grid grid-cols-[1fr_auto_1fr] gap-4 items-center">
{/* 左侧:通知类型标题 */}
<div> <div>
<FormLabel className="text-base"></FormLabel> <FormLabel className="text-base"></FormLabel>
</div> </div>
{/* 中间:开关 */}
<FormField <FormField
control={form.control} control={form.control}
name="notificationConfig.buildFailureFileEnabled" name="notificationConfig.buildFailureFileEnabled"
render={({ field }) => ( render={({ field }) => (
<FormItem className="mb-0"> <FormItem className="mb-0">
<FormControl> <FormControl>
<Switch <Switch checked={field.value} onCheckedChange={field.onChange} />
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl> </FormControl>
</FormItem> </FormItem>
)} )}
/> />
{/* 右侧:模板选择 */}
<FormField
control={form.control}
name="notificationConfig.buildFailureNotificationTemplateId"
render={({ field }) => (
<FormItem className="mb-0">
<FormControl>
<ClearableSelect
value={field.value?.toString()}
onValueChange={(value) =>
field.onChange(value ? Number(value) : null)
}
disabled={loadingTemplates}
>
<SelectTrigger
clearable
hasValue={!!field.value}
onClear={() => {
field.onChange(null);
}}
>
<SelectValue
placeholder={
loadingTemplates
? '加载中...'
: '请选择通知模板(可选)'
}
/>
</SelectTrigger>
<SelectContent>
{notificationTemplates.map((template) => (
<SelectItem
key={template.id}
value={template.id.toString()}
>
{template.name}
</SelectItem>
))}
</SelectContent>
</ClearableSelect>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* 描述文字行 */}
<div className="grid grid-cols-[1fr_auto_1fr] gap-4">
<div className="text-sm text-muted-foreground">
</div>
<div></div> <div></div>
<div className="text-sm text-muted-foreground"> </div>
<div className="grid grid-cols-[1fr_auto_1fr] gap-4">
</div> <div className="text-sm text-muted-foreground"></div>
<div></div>
<div></div>
</div> </div>
</div> </div>
</DialogBody> </DialogBody>
<DialogFooter> <DialogFooter>
<Button <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={submitting}>
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
disabled={submitting}
>
</Button> </Button>
<Button type="submit" disabled={submitting}> <Button type="submit" disabled={submitting}>
{submitting && ( {submitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
)}
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@ -53,12 +53,11 @@ export interface NotificationConfig {
teamId?: number; teamId?: number;
environmentId?: number; environmentId?: number;
notificationChannelId?: number; notificationChannelId?: number;
deployNotificationEnabled?: boolean; preApprovalNotificationEnabled?: boolean;
deployNotificationTemplateId?: number; preApprovalNotificationTemplateId?: number;
buildNotificationEnabled?: boolean; buildNotificationEnabled?: boolean;
buildNotificationTemplateId?: number; buildNotificationTemplateId?: number;
buildFailureFileEnabled?: boolean; buildFailureFileEnabled?: boolean;
buildFailureNotificationTemplateId?: number;
notificationChannelName?: string; // 关联数据 notificationChannelName?: string; // 关联数据
channelType?: string; // 渠道类型WEWORK, EMAIL, DINGTALK等 channelType?: string; // 渠道类型WEWORK, EMAIL, DINGTALK等
updateTime?: string; // 更新时间 updateTime?: string; // 更新时间
@ -96,12 +95,11 @@ export interface TeamEnvironmentConfigRequest {
notificationConfig?: { notificationConfig?: {
id?: number; id?: number;
notificationChannelId?: number; notificationChannelId?: number;
deployNotificationEnabled?: boolean; preApprovalNotificationEnabled?: boolean;
deployNotificationTemplateId?: number; preApprovalNotificationTemplateId?: number;
buildNotificationEnabled?: boolean; buildNotificationEnabled?: boolean;
buildNotificationTemplateId?: number; buildNotificationTemplateId?: number;
buildFailureFileEnabled?: boolean; buildFailureFileEnabled?: boolean;
buildFailureNotificationTemplateId?: number;
}; };
} }

View File

@ -210,19 +210,19 @@ const OnlineUserList: React.FC = () => {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="rounded-md border"> <div className="rounded-md border">
<Table> <Table minWidth="1400px">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
<TableHead className="w-[120px]"></TableHead> <TableHead width="100px"></TableHead>
<TableHead className="w-[120px]"></TableHead> <TableHead width="100px"></TableHead>
<TableHead className="w-[150px]"></TableHead> <TableHead width="120px"></TableHead>
<TableHead className="w-[180px]"></TableHead> <TableHead width="160px"></TableHead>
<TableHead className="w-[120px]">线</TableHead> <TableHead width="100px">线</TableHead>
<TableHead className="w-[180px]"></TableHead> <TableHead width="160px"></TableHead>
<TableHead className="w-[140px]">IP地址</TableHead> <TableHead width="130px">IP地址</TableHead>
<TableHead className="w-[120px]"></TableHead> <TableHead width="110px"></TableHead>
<TableHead className="w-[120px]"></TableHead> <TableHead width="120px"></TableHead>
<TableHead className="w-[100px] text-right"></TableHead> <TableHead width="120px" sticky></TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -235,26 +235,28 @@ const OnlineUserList: React.FC = () => {
) : data?.content && data.content.length > 0 ? ( ) : data?.content && data.content.length > 0 ? (
data.content.map((user) => ( data.content.map((user) => (
<TableRow key={user.userId}> <TableRow key={user.userId}>
<TableCell className="font-medium">{user.username}</TableCell> <TableCell width="100px" className="font-medium">{user.username}</TableCell>
<TableCell>{user.nickname}</TableCell> <TableCell width="100px">{user.nickname}</TableCell>
<TableCell>{user.departmentName || '-'}</TableCell> <TableCell width="120px">{user.departmentName || '-'}</TableCell>
<TableCell>{formatTime(user.loginTime)}</TableCell> <TableCell width="160px">{formatTime(user.loginTime)}</TableCell>
<TableCell>{formatDuration(user.onlineDuration)}</TableCell> <TableCell width="100px">{formatDuration(user.onlineDuration)}</TableCell>
<TableCell>{formatTime(user.lastActiveTime)}</TableCell> <TableCell width="160px">{formatTime(user.lastActiveTime)}</TableCell>
<TableCell>{user.ipAddress || '-'}</TableCell> <TableCell width="130px">{user.ipAddress || '-'}</TableCell>
<TableCell>{user.browser || '-'}</TableCell> <TableCell width="110px">{user.browser || '-'}</TableCell>
<TableCell>{user.os || '-'}</TableCell> <TableCell width="120px">{user.os || '-'}</TableCell>
<TableCell className="text-right"> <TableCell width="120px" sticky>
<Button <div className="flex justify-end">
type="button" <Button
variant="ghost" type="button"
size="sm" variant="ghost"
onClick={() => handleKickClick(user)} size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50" onClick={() => handleKickClick(user)}
> className="text-red-600 hover:text-red-700 hover:bg-red-50"
<LogOut className="h-4 w-4 mr-1" /> >
线 <LogOut className="h-4 w-4 mr-1" />
</Button> 线
</Button>
</div>
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))