|
|
@@ -0,0 +1,309 @@ |
|
|
|
import React, { useState, useEffect, useRef, useContext, MutableRefObject, forwardRef, useImperativeHandle } from 'react'; |
|
|
|
import { Space, Table, Button, Input, Select, Divider, Form, Popconfirm } from 'antd'; |
|
|
|
import type { TableColumnsType } from 'antd'; |
|
|
|
import type { InputRef } from 'antd'; |
|
|
|
import type { FormInstance } from 'antd/es/form'; |
|
|
|
import type { ColumnType, TableProps } from 'antd/es/table'; |
|
|
|
import { t } from '@/utils/i18n'; |
|
|
|
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined, UndoOutlined } from '@ant-design/icons'; |
|
|
|
import type { GoodsClassifyPageReqVO, GoodsClassifyVO } from '@/models' |
|
|
|
import { antdUtils } from '@/utils/antd'; |
|
|
|
import { useRequest } from '@/hooks/use-request'; |
|
|
|
import goodsClassifyService from '@/request/service/goods-classify'; |
|
|
|
import { formatDate } from '@/utils/formatTime'; |
|
|
|
import { useSetState } from 'ahooks'; |
|
|
|
|
|
|
|
const EditableContext = React.createContext<FormInstance<any> | null>(null); |
|
|
|
|
|
|
|
interface EditableRowProps { |
|
|
|
index: number; |
|
|
|
} |
|
|
|
|
|
|
|
type ClassifyProps = { |
|
|
|
ref: MutableRefObject<any> |
|
|
|
} |
|
|
|
|
|
|
|
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => { |
|
|
|
const [form] = Form.useForm(); |
|
|
|
return ( |
|
|
|
<Form form={form} component={false}> |
|
|
|
<EditableContext.Provider value={form}> |
|
|
|
<tr {...props} /> |
|
|
|
</EditableContext.Provider> |
|
|
|
</Form> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
interface EditableCellProps { |
|
|
|
title: React.ReactNode; |
|
|
|
editable: boolean; |
|
|
|
children: React.ReactNode; |
|
|
|
dataIndex: keyof GoodsClassifyVO; |
|
|
|
record: GoodsClassifyVO; |
|
|
|
handleSave: (record: GoodsClassifyVO) => void; |
|
|
|
} |
|
|
|
|
|
|
|
const EditableCell: React.FC<EditableCellProps> = ({ |
|
|
|
title, |
|
|
|
editable, |
|
|
|
children, |
|
|
|
dataIndex, |
|
|
|
record, |
|
|
|
handleSave, |
|
|
|
...restProps |
|
|
|
}) => { |
|
|
|
const [editing, setEditing] = useState(false); |
|
|
|
const inputRef = useRef<InputRef>(null); |
|
|
|
const form = useContext(EditableContext)!; |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
if (editing) { |
|
|
|
inputRef.current!.focus(); |
|
|
|
} |
|
|
|
}, [editing]); |
|
|
|
|
|
|
|
const toggleEdit = () => { |
|
|
|
setEditing(!editing); |
|
|
|
form.setFieldsValue({ [dataIndex]: record[dataIndex] }); |
|
|
|
}; |
|
|
|
|
|
|
|
const save = async () => { |
|
|
|
try { |
|
|
|
const values = await form.validateFields(); |
|
|
|
|
|
|
|
toggleEdit(); |
|
|
|
handleSave({ ...record, ...values }); |
|
|
|
} catch (errInfo) { |
|
|
|
console.log('Save failed:', errInfo); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
let childNode = children; |
|
|
|
|
|
|
|
if (editable) { |
|
|
|
childNode = editing ? ( |
|
|
|
<Form.Item |
|
|
|
style={{ margin: 0 }} |
|
|
|
name={dataIndex} |
|
|
|
rules={[ |
|
|
|
{ |
|
|
|
required: true, |
|
|
|
message: `${title} is required.`, |
|
|
|
}, |
|
|
|
]} |
|
|
|
> |
|
|
|
<Input size="middle" ref={inputRef} onPressEnter={save} onBlur={save} /> |
|
|
|
</Form.Item> |
|
|
|
) : ( |
|
|
|
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}> |
|
|
|
{children} |
|
|
|
</div> |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
return <td {...restProps}>{childNode}</td>; |
|
|
|
}; |
|
|
|
|
|
|
|
type EditableTableProps = Parameters<typeof Table>[0]; |
|
|
|
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>; |
|
|
|
|
|
|
|
export default forwardRef((props, ref) => { |
|
|
|
useImperativeHandle(ref, () => { |
|
|
|
add: () => { |
|
|
|
console.log("add") |
|
|
|
} |
|
|
|
}) |
|
|
|
const [dataSource, setDataSource] = useState<GoodsClassifyVO[]>([]); |
|
|
|
const [searchFrom] = Form.useForm(); |
|
|
|
|
|
|
|
const [searchState, setSearchState] = useSetState<GoodsClassifyPageReqVO>({ |
|
|
|
pageNo: 1, |
|
|
|
pageSize: 10 |
|
|
|
}); |
|
|
|
const [total, setTotal] = useState(0) |
|
|
|
const searchInput = useRef<InputRef>(null); |
|
|
|
const [onSearching, setOnSearching] = useState(false); |
|
|
|
|
|
|
|
const { runAsync: getPageApi } = useRequest(goodsClassifyService.getGoodsClassifyPageApi, { manual: true }); |
|
|
|
const { runAsync: updateApi } = useRequest(goodsClassifyService.updateGoodsClassifyApi, { manual: true }); |
|
|
|
const { runAsync: verifyApi } = useRequest(goodsClassifyService.classifyNameVerifyUnique, { manual: true }); |
|
|
|
const { runAsync: deleteApi } = useRequest(goodsClassifyService.deleteGoodsClassifyApi, { manual: true }); |
|
|
|
|
|
|
|
const load = async () => { |
|
|
|
const [error, { data }] = await getPageApi(searchFrom.getFieldsValue()); |
|
|
|
if (!error) { |
|
|
|
setDataSource(data.list); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const showDeleteConfirm = (data: GoodsClassifyVO) => { |
|
|
|
antdUtils.modal?.confirm({ |
|
|
|
title: '确认要将该分类删除吗?', |
|
|
|
icon: <ExclamationCircleFilled />, |
|
|
|
content: '请注意删除以后不可恢复!', |
|
|
|
okText: '删除', |
|
|
|
okType: 'danger', |
|
|
|
cancelText: '取消', |
|
|
|
onOk() { |
|
|
|
return new Promise(async (resolve) => { |
|
|
|
const [error, { code, msg }] = await deleteApi(data.id); |
|
|
|
if (error || code !== 0) { |
|
|
|
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' }) |
|
|
|
} else { |
|
|
|
antdUtils.message?.open({ type: 'success', content: '删除成功' }) |
|
|
|
} |
|
|
|
await load(); |
|
|
|
resolve('') |
|
|
|
}).catch(() => antdUtils.message?.open({ |
|
|
|
type: 'error', |
|
|
|
content: '操作失败', |
|
|
|
})); |
|
|
|
}, |
|
|
|
onCancel() { |
|
|
|
}, |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
const getColumnSearchProps = (placeholder: string): ColumnType<GoodsClassifyVO> => ({ |
|
|
|
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => ( |
|
|
|
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}> |
|
|
|
<Input.Search |
|
|
|
ref={searchInput} |
|
|
|
placeholder={placeholder} |
|
|
|
value={selectedKeys[0]} |
|
|
|
onChange={(e) => { |
|
|
|
setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : []) |
|
|
|
}} |
|
|
|
onSearch={(value) => { |
|
|
|
if (value === '' && clearFilters) { |
|
|
|
clearFilters!!() |
|
|
|
} |
|
|
|
confirm(); |
|
|
|
}} |
|
|
|
onPressEnter={() => confirm()} |
|
|
|
allowClear |
|
|
|
style={{ marginBottom: 8, display: 'block' }} |
|
|
|
enterButton="搜索" |
|
|
|
size="middle" |
|
|
|
loading={onSearching} |
|
|
|
/> |
|
|
|
</div> |
|
|
|
), |
|
|
|
filterIcon: (filtered: boolean) => ( |
|
|
|
<SearchOutlined style={{ color: filtered ? 'primaryColor' : undefined }} /> |
|
|
|
), |
|
|
|
onFilterDropdownOpenChange: (visible) => { |
|
|
|
if (visible) { |
|
|
|
setTimeout(() => searchInput.current?.select(), 100); |
|
|
|
} |
|
|
|
}, |
|
|
|
}); |
|
|
|
|
|
|
|
const defaultColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [ |
|
|
|
{ |
|
|
|
title: '类目名称', |
|
|
|
dataIndex: 'classifyName', |
|
|
|
key: 'classifyName', |
|
|
|
align: 'left', |
|
|
|
width: '30%', |
|
|
|
editable: true, |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: '创建时间', |
|
|
|
dataIndex: 'createTime', |
|
|
|
key: 'createTime', |
|
|
|
align: 'center', |
|
|
|
render: (value: number) => { |
|
|
|
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS") |
|
|
|
} |
|
|
|
}, |
|
|
|
{ |
|
|
|
title: t("QkOmYwne" /* 操作 */), |
|
|
|
dataIndex: 'operation', |
|
|
|
key: 'action', |
|
|
|
render: (value: GoodsClassifyVO) => |
|
|
|
dataSource.length >= 1 ? ( |
|
|
|
<Popconfirm title="确认要将该分类删除吗?" onConfirm={() => showDeleteConfirm(value)}> |
|
|
|
<a>删除</a> |
|
|
|
</Popconfirm> |
|
|
|
) : null, |
|
|
|
}, |
|
|
|
]; |
|
|
|
|
|
|
|
const columns = defaultColumns.map((col) => { |
|
|
|
if (!col.editable) { |
|
|
|
return col; |
|
|
|
} |
|
|
|
return { |
|
|
|
...col, |
|
|
|
...getColumnSearchProps("请输入类目名称"), |
|
|
|
onCell: (record: GoodsClassifyVO) => ({ |
|
|
|
record, |
|
|
|
editable: col.editable, |
|
|
|
dataIndex: col.dataIndex, |
|
|
|
title: col.title, |
|
|
|
handleSave, |
|
|
|
}), |
|
|
|
}; |
|
|
|
}); |
|
|
|
|
|
|
|
const handleSave = async (row: GoodsClassifyVO) => { |
|
|
|
const [error, { code, msg }] = await updateApi(row); |
|
|
|
if (!error && code === 0) { |
|
|
|
const newData = [...dataSource]; |
|
|
|
const index = newData.findIndex((item) => row.id === item.id); |
|
|
|
const item = newData[index]; |
|
|
|
newData.splice(index, 1, { |
|
|
|
...item, |
|
|
|
...row, |
|
|
|
}); |
|
|
|
setDataSource(newData); |
|
|
|
} else { |
|
|
|
antdUtils.message?.open({ type: 'error', content: msg ?? '更新失败' }) |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
const components = { |
|
|
|
body: { |
|
|
|
row: EditableRow, |
|
|
|
cell: EditableCell, |
|
|
|
}, |
|
|
|
}; |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
load(); |
|
|
|
}, []); |
|
|
|
|
|
|
|
const onReset = () => { |
|
|
|
searchFrom.resetFields() |
|
|
|
load() |
|
|
|
} |
|
|
|
|
|
|
|
return ( |
|
|
|
<> |
|
|
|
<div> |
|
|
|
{/* <div className='flex justify-end content-center'> |
|
|
|
<div className="mb-[16px]"> |
|
|
|
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={() => { |
|
|
|
// seEditData(undefined); |
|
|
|
// seEditorVisable(true); |
|
|
|
}}> 新增分类 </Button> |
|
|
|
</div> |
|
|
|
</div> */} |
|
|
|
<Table rowKey="id" |
|
|
|
scroll={{ x: true }} |
|
|
|
columns={columns as ColumnTypes} |
|
|
|
dataSource={dataSource} |
|
|
|
components={components} |
|
|
|
rowClassName={() => 'editable-row'} |
|
|
|
pagination={{ |
|
|
|
position: ['bottomRight'], |
|
|
|
current: searchState.pageNo, |
|
|
|
pageSize: searchState.pageSize, |
|
|
|
total |
|
|
|
}} /> |
|
|
|
</div> |
|
|
|
</> |
|
|
|
); |
|
|
|
}); |
|
|
|
|