import React, { useState } from 'react';
import {
Select,
DatePicker,
TimePicker,
Button,
Table,
Modal,
Form,
Input,
ConfigProvider,
Space,
message,
Spin,
} from 'antd';
import { FormOutlined, PlusCircleOutlined, MinusCircleOutlined } from '@ant-design/icons';
import locale from 'antd/locale/zh_CN';
import dayjs, { Dayjs } from 'dayjs';
import 'dayjs/locale/zh-cn';
import { useSetPublish, useGetPublishHistory } from './request';
import type { PublishParams, PublishFormValues } from '@/types/autoPublish';
import {PROJECT_NAMES, statusMap, envMap, typeMap} from './config';
import { useQueryClient } from '@tanstack/react-query';
dayjs.locale('zh-cn');
const AutoPublish: React.FC = () => {
const queryClient = useQueryClient();
const { data, isLoading } = useGetPublishHistory({
pageSize: '10',
page: '1',
});
const publishHistory = data?.data || [];
const [messageApi, contextHolder] = message.useMessage();
const [chooseDate, setChooseDate] = useState<Dayjs | null>(dayjs());
const [chooseTime, setChooseTime] = useState<Dayjs | null>(dayjs());
const [isModalVisible, setIsModalVisible] = useState(false);
const [form] = Form.useForm();
/**
* 整合时间值
*/
const handlePublishDate = (publishDate: Dayjs, publishTime: Dayjs) => {
if (publishDate && publishTime) {
const fullTime = publishDate
.hour(publishTime.hour())
.minute(publishTime.minute())
.second(0);
const formattedTime = fullTime.format('YYYY-MM-DD HH:mm:ss');
return formattedTime
}
}
const { mutate } = useSetPublish();
/**
* 新建发布
*/
const newPublish = () => {
setIsModalVisible(true);
};
/**
* 修改发布
*/
const revisePublish = (record: PublishParams) => {
const projects = record.product.map((item: any) => ({
productName: item.product_name,
productTag: item.product_tag,
}));
// 解析发布时间
const publishTime = dayjs(record.publish_time);
// 设置表单值
form.setFieldsValue({
projects: projects,
branchVersion: record.branch_version,
productEnv: record.publish_env.toString(),
publishTime: record.publish_time,
});
// 设置日期和时间选择器的值
setChooseDate(publishTime);
setChooseTime(publishTime);
setIsModalVisible(true);
};
/**
* 发布
*/
const handleSubmit = (type: string) => {
form.validateFields().then((values: PublishFormValues) => {
if (!chooseDate || !chooseTime) {
alert('请选择完整的日期和时间');
return;
}
const publishTime = handlePublishDate(chooseDate, chooseTime);
const { projects, branchVersion, productEnv } = values;
const publishType = type === 'schedule' ? 1 : 2;
const publishStatus = 2;
const params = {
product: projects.map((p) => ({
product_name: p.productName,
product_tag: p.productTag,
})),
branch_version: branchVersion,
publish_time: publishTime!,
publish_user: 'admin',
publish_status: publishStatus,
publish_type: publishType,
publish_env: Number(productEnv),
};
console.log(`【${type}】提交数据:`, params);
mutate(params, {
onSuccess: () => {
messageApi.open({
type: 'success',
content: '发布成功!',
});
// 刷新列表
queryClient.invalidateQueries({
queryKey: ['publishHistory'],
});
},
onError: (data) => {
messageApi.open({
type: 'error',
content: data.message || '发布失败~请重试!',
});
}
});
setIsModalVisible(false);
});
};
/**
* 禁用今天之前的日期
*/
const disabledDate = (current: Dayjs) => {
return current < dayjs().startOf('day');
};
/**
* 禁用此刻之前时间点
*/
const disabledDateTime = (current: Dayjs | null) => {
const now = dayjs();
if(!current) return;
if (current.isSame(now, 'day')) {
return {
disabledHours: () => range(0, now.hour()),
disabledMinutes: (selectedHour: number) =>
selectedHour === now.hour() ? range(0, now.minute()) : [],
};
}
return {};
};
const range = (start: number, end: number) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
/**
* 定义表格
*/
const columns1 = [
{
title: '发布时间',
dataIndex: 'publish_time',
key: 'publish_time',
},
{
title: '发布版本',
dataIndex: 'branch_version',
key: 'branch_version',
},
{
title: '发布状态',
dataIndex: 'publish_status',
key: 'publish_status',
render: (status: number) => statusMap[status] || '未知类型',
},
{
title: '发布类型',
dataIndex: 'publish_type',
key: 'publish_type',
render: (type: number) => typeMap[type] || '未知类型',
},
{
title: '发布环境',
dataIndex: 'publish_env',
key: 'publish_env',
render: (text: number) => envMap[text] || '未知环境',
},
{
title: '编辑',
key: 'edit',
render: (_: any, record: PublishParams) => (
<FormOutlined
onClick={() => revisePublish(record)}
style={{ cursor: 'pointer' }}
/>
),
},
];
const columns2 = [
{
title: '项目名称',
dataIndex: 'product_name',
key: 'product_name',
},
{
title: 'Tag号',
dataIndex: 'product_tag',
key: 'product_tag',
},
];
if (isLoading) {
return <Spin tip="Loading">{<a>加载中...</a>}</Spin>;
}
return (
<div style={{ padding: '20px' }}>
{contextHolder}
{/* 控件 */}
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: '16px', marginBottom: '20px' }}>
<Button type="primary" onClick={newPublish} style={{ backgroundColor: '#3662ec' }}>
新建发布
</Button>
</div>
{/* 表格 */}
<Table
columns={columns1}
dataSource={publishHistory}
rowKey={(record) => record.branch_version}
expandable={{
expandedRowRender: (record) => (
<Table
columns={columns2}
dataSource={record.product || []}
pagination={false}
style={{ width: 500 }}
rowKey={(item, index) => index!}
/>
),
}}
/>
{/* 编辑模态框 */}
<Modal
title="项目信息"
open={isModalVisible}
width={700}
onCancel={() => setIsModalVisible(false)}
footer={[
<Button key="back" onClick={() => setIsModalVisible(false)}>
取消
</Button>,
<Button key="schedule" type="primary" onClick={() => handleSubmit('schedule')}>
预约发布
</Button>,
<Button key="publish" type="primary" onClick={() => handleSubmit('rightNow')}>
立即发布
</Button>,
]}
>
<Form
form={form}
layout="horizontal"
labelCol={{ span: 5 }}
wrapperCol={{ span: 10 }}
initialValues={{
projects: [
{
productName: undefined,
productTag: '',
},
],
}}
>
<Form.List name="projects">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 0 }} align="baseline">
<Form.Item
{...restField}
name={[name, 'productName']}
label="项目名称"
labelCol={{ span: 9 }}
wrapperCol={{ span: 16 }}
rules={[{ required: true, message: '请选择项目名称' }]}
>
<Select
showSearch
optionFilterProp="children"
options={PROJECT_NAMES.map((name) => ({
label: name,
value: name,
}))}
style={{ width: 250 }}
/>
</Form.Item>
<Form.Item
{...restField}
name={[name, 'productTag']}
label="Tag"
labelCol={{ span: 10 }}
wrapperCol={{ span: 50 }}
rules={[{ required: true, message: '请输入 Tag' }]}
>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 16 }}>
<Button
type="dashed"
onClick={() => add()}
icon={<PlusCircleOutlined />}
style={{
width: 200,
height: 35,
borderRadius: 6,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
添加项目
</Button>
</div>
</>
)}
</Form.List>
<Form.Item label="选择版本" name="branchVersion" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item label="选择环境" name="productEnv" rules={[{ required: true }]}>
<Select style={{ width: 150 }}>
<Select.Option value="1">测试环境</Select.Option>
<Select.Option value="2">生产环境</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={
<span>
<span style={{ fontSize: '18px', color: 'rgb(255, 141, 142)'}}>*</span> 选择时间
</span>
}
>
<Space>
<ConfigProvider locale={locale}>
<DatePicker
format="YYYY-MM-DD"
value={chooseDate}
disabledDate={disabledDate}
onChange={(date) => setChooseDate(date ? dayjs(date) : null)}
/>
</ConfigProvider>
<TimePicker
format="HH:mm"
value={chooseTime}
disabledTime={() => disabledDateTime(chooseDate)}
onChange={(time) => setChooseTime(time ? dayjs(time) : null)}
/>
</Space>
</Form.Item>
</Form>
</Modal>
</div>
);
};
export default AutoPublish;
其中,TimePicker的disabledTime ts类型报错如下:
Type '() => { disabledHours: () => number[]; disabledMinutes: (selectedHour: number) => number[]; } | { disabledHours?: undefined; disabledMinutes?: undefined; } | undefined' is not assignable to type '(date: Dayjs) => DisabledTimes'.
Type '{ disabledHours: () => number[]; disabledMinutes: (selectedHour: number) => number[]; } | { disabledHours?: undefined; disabledMinutes?: undefined; } | undefined' is not assignable to type 'DisabledTimes'.
Type 'undefined' is not assignable to type 'DisabledTimes'.ts(2322)
interface.d.ts(139, 5): The expected type comes from property 'disabledTime' which is declared here on type 'IntrinsicAttributes & Omit<TimePickerProps, "ref"> & RefAttributes<PickerRef>'
(property) disabledTime?: ((date: dayjs.Dayjs) => DisabledTimes) | undefined
最新发布