import intl from 'react-intl-universal'; import React, { useCallback, useRef, useState, useEffect, useMemo, } from 'react'; import { Button, message, Modal, Table, Tag, Space, Typography, Tooltip, Input, UploadProps, Upload, } from 'antd'; import { EditOutlined, DeleteOutlined, SyncOutlined, CheckCircleOutlined, StopOutlined, UploadOutlined, } from '@ant-design/icons'; import config from '@/utils/config'; import { PageContainer } from '@ant-design/pro-layout'; import { request } from '@/utils/http'; import EnvModal from './modal'; import EditNameModal from './editNameModal'; import { DndProvider, useDrag, useDrop } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import './index.less'; import { exportJson } from '@/utils/index'; import { useOutletContext } from '@umijs/max'; import { SharedContext } from '@/layouts'; import useTableScrollHeight from '@/hooks/useTableScrollHeight'; import Copy from '../../components/copy'; import { useVT } from 'virtualizedtableforantd4'; import dayjs from 'dayjs'; const { Paragraph } = Typography; const { Search } = Input; enum Status { '已启用', '已禁用', } enum StatusColor { 'success', 'error', } enum OperationName { '启用', '禁用', } enum OperationPath { 'enable', 'disable', } const type = 'DragableBodyRow'; const Env = () => { const { headerStyle, isPhone, theme } = useOutletContext(); const columns: any = [ { title: intl.get('序号'), width: 80, render: (text: string, record: any, index: number) => { return {index + 1} ; }, }, { title: intl.get('名称'), dataIndex: 'name', key: 'name', sorter: (a: any, b: any) => a.name.localeCompare(b.name), render: (text: string, record: any) => { return (
{text}
); }, }, { title: intl.get('值'), dataIndex: 'value', key: 'value', width: '35%', render: (text: string, record: any) => { return (
{text}
); }, }, { title: intl.get('备注'), dataIndex: 'remarks', key: 'remarks', render: (text: string, record: any) => { return (
{text}
); }, }, { title: intl.get('更新时间'), dataIndex: 'timestamp', key: 'timestamp', width: 165, ellipsis: { showTitle: false, }, sorter: { compare: (a: any, b: any) => { const updatedAtA = new Date(a.updatedAt || a.timestamp).getTime(); const updatedAtB = new Date(b.updatedAt || b.timestamp).getTime(); return updatedAtA - updatedAtB; }, }, render: (text: string, record: any) => { const date = dayjs(record.updatedAt || record.timestamp).format( 'YYYY-MM-DD HH:mm:ss', ); return ( {date} ); }, }, { title: intl.get('状态'), key: 'status', dataIndex: 'status', width: 100, filters: [ { text: intl.get('已启用'), value: 0, }, { text: intl.get('已禁用'), value: 1, }, ], onFilter: (value: number, record: any) => record.status === value, render: (text: string, record: any, index: number) => { return ( {intl.get(Status[record.status])} ); }, }, { title: intl.get('操作'), key: 'action', width: 120, render: (text: string, record: any, index: number) => { const isPc = !isPhone; return ( editEnv(record, index)}> enabledOrDisabledEnv(record, index)}> {record.status === Status.已禁用 ? ( ) : ( )} deleteEnv(record, index)}> ); }, }, ]; const [value, setValue] = useState([]); const [loading, setLoading] = useState(true); const [isModalVisible, setIsModalVisible] = useState(false); const [isEditNameModalVisible, setIsEditNameModalVisible] = useState(false); const [editedEnv, setEditedEnv] = useState(); const [selectedRowIds, setSelectedRowIds] = useState([]); const [searchText, setSearchText] = useState(''); const [importLoading, setImportLoading] = useState(false); const tableRef = useRef(null); const tableScrollHeight = useTableScrollHeight(tableRef, 59); const getEnvs = () => { setLoading(true); request .get(`${config.apiPrefix}envs?searchValue=${searchText}`) .then(({ code, data }) => { if (code === 200) { setValue(data); } }) .finally(() => setLoading(false)); }; const enabledOrDisabledEnv = (record: any, index: number) => { Modal.confirm({ title: `确认${ record.status === Status.已禁用 ? intl.get('启用') : intl.get('禁用') }`, content: ( <> {intl.get('确认')} {record.status === Status.已禁用 ? intl.get('启用') : intl.get('禁用')} Env{' '} {record.value} {' '} {intl.get('吗')} ), onOk() { request .put( `${config.apiPrefix}envs/${ record.status === Status.已禁用 ? 'enable' : 'disable' }`, [record.id], ) .then(({ code, data }) => { if (code === 200) { message.success( `${ record.status === Status.已禁用 ? intl.get('启用') : intl.get('禁用') }${intl.get('成功')}`, ); const newStatus = record.status === Status.已禁用 ? Status.已启用 : Status.已禁用; const result = [...value]; result.splice(index, 1, { ...record, status: newStatus, }); setValue(result); } }); }, }); }; const addEnv = () => { setEditedEnv(null as any); setIsModalVisible(true); }; const editEnv = (record: any, index: number) => { setEditedEnv(record); setIsModalVisible(true); }; const deleteEnv = (record: any, index: number) => { Modal.confirm({ title: intl.get('确认删除'), content: ( <> {intl.get('确认删除变量')}{' '} {record.name}: {record.value} {' '} {intl.get('吗')} ), onOk() { request .delete(`${config.apiPrefix}envs`, { data: [record.id] }) .then(({ code, data }) => { if (code === 200) { message.success(intl.get('删除成功')); const result = [...value]; result.splice(index, 1); setValue(result); } }); }, }); }; const handleCancel = (env?: any[]) => { setIsModalVisible(false); getEnvs(); }; const handleEditNameCancel = (env?: any[]) => { setIsEditNameModalVisible(false); getEnvs(); }; const [vt, setVT] = useVT( () => ({ scroll: { y: tableScrollHeight } }), [tableScrollHeight], ); const DragableBodyRow = React.forwardRef((props: any, ref) => { const { index, moveRow, className, style, ...restProps } = props; const [{ isOver, dropClassName }, drop] = useDrop({ accept: type, collect: (monitor) => { const { index: dragIndex } = (monitor.getItem() as any) || {}; if (dragIndex === index) { return {}; } return { isOver: monitor.isOver(), dropClassName: dragIndex < index ? ' drop-over-downward' : ' drop-over-upward', }; }, drop: (item: any) => { moveRow(item.index, index); }, }); const [, drag] = useDrag({ type, item: { index }, collect: (monitor) => ({ isDragging: monitor.isDragging(), }), }); useEffect(() => { drop(drag(ref)); }, [ref]); return ( ); }); useEffect( () => setVT({ body: { row: DragableBodyRow, }, }), [], ); const moveRow = useCallback( (dragIndex: number, hoverIndex: number) => { if (dragIndex === hoverIndex) { return; } const dragRow = value[dragIndex]; request .put(`${config.apiPrefix}envs/${dragRow.id}/move`, { fromIndex: dragIndex, toIndex: hoverIndex, }) .then(({ code, data }) => { if (code === 200) { const newData = [...value]; newData.splice(dragIndex, 1); newData.splice(hoverIndex, 0, { ...dragRow, ...data }); setValue([...newData]); } }); }, [value], ); const onSelectChange = (selectedIds: any[]) => { setSelectedRowIds(selectedIds); }; const rowSelection = { selectedRowKeys: selectedRowIds, onChange: onSelectChange, }; const delEnvs = () => { Modal.confirm({ title: intl.get('确认删除'), content: <>{intl.get('确认删除选中的变量吗')}, onOk() { request .delete(`${config.apiPrefix}envs`, { data: selectedRowIds }) .then(({ code, data }) => { if (code === 200) { message.success(intl.get('批量删除成功')); setSelectedRowIds([]); getEnvs(); } }); }, }); }; const operateEnvs = (operationStatus: number) => { Modal.confirm({ title: `确认${OperationName[operationStatus]}`, content: ( <> {intl.get('确认')} {OperationName[operationStatus]} {intl.get('选中的变量吗')} ), onOk() { request .put( `${config.apiPrefix}envs/${OperationPath[operationStatus]}`, selectedRowIds, ) .then(({ code, data }) => { if (code === 200) { getEnvs(); } }); }, }); }; const exportEnvs = () => { const envs = value .filter((x) => selectedRowIds.includes(x.id)) .map((x) => ({ value: x.value, name: x.name, remarks: x.remarks })); exportJson('env.json', JSON.stringify(envs)); }; const modifyName = () => { setIsEditNameModalVisible(true); }; const onSearch = (value: string) => { setSearchText(value.trim()); }; const uploadProps: UploadProps = { accept: 'application/json', beforeUpload: async (file) => { const formData = new FormData(); formData.append('env', file); setImportLoading(true); try { const { code, data } = await request.post( `${config.apiPrefix}envs/upload`, formData, ); if (code === 200) { message.success(`成功上传${data.length}个环境变量`); getEnvs(); } setImportLoading(false); } catch (error: any) { setImportLoading(false); } return false; }, fileList: [], }; useEffect(() => { getEnvs(); }, [searchText]); return ( , , , ]} header={{ style: headerStyle, }} >
{selectedRowIds.length > 0 && (
{intl.get('已选择')} {selectedRowIds?.length} {intl.get('项')}
)} { return { index, moveRow, } as any; }} /> {isModalVisible && ( )} {isEditNameModalVisible && ( )} ); }; export default Env;