import React, { useEffect, useState, useRef, useContext, useMemo } from 'react'; import { CreateTablePost, getTableInfo, updateTablePost, GetDefaultTableFields, reloadTableFields, removeFields, } from '@/services/tablemanager/tablemanager'; import { Form, Modal, Button, Input, Select, Checkbox, Table, notification, InputNumber, Tooltip, Switch, Spin, Empty, } from 'antd'; import { DeleteOutlined, PlusOutlined, MinusOutlined, DeleteFilled, KeyOutlined, } from '@ant-design/icons'; import styles from './TableView.less'; import primaryKey from '../../../../../assets/images/icons/主键.svg'; import index from '../../../../../assets/images/icons/索引.svg'; import clearImg from '@/assets/font/omsfont/clear.svg'; // import { defaultFields } from './defaultFields'; const EditableContext = React.createContext(null); const tableMap = { 事件表: '事件', 事件工单表: '事件', 工单表: '工单', 台账表: '台账', 设备表: '设备', 反馈表: '反馈', }; const EditableRow = ({ index, ...props }) => { const [form] = Form.useForm(); return ( <Form form={form} component={false}> <EditableContext.Provider value={form}> <tr {...props} /> </EditableContext.Provider> </Form> ); }; const EditableCell = ({ index, title, editable, children, dataIndex, record, handleSave, ellipsis, width, dataSource, tableDataCount, defaultData, ...restProps }) => { const [editing, setEditing] = useState(false); const inputRef = useRef(null); const form = useContext(EditableContext); // 复选框回显 useEffect(() => { if (record && dataIndex === 'IsNullable') { form.setFieldsValue({ [dataIndex]: record[dataIndex], }); } if (record && dataIndex === 'IsAddFieldConfig') { form.setFieldsValue({ [dataIndex]: record[dataIndex], }); } }, []); useEffect(() => { if (editing && inputRef.current) { 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, [dataIndex]: values[dataIndex], index, errors: [] }, dataIndex); } catch (errInfo) { toggleEdit(); handleSave({ ...record, index, errors: errInfo.errorFields[0].errors }, dataIndex); console.log('Save failed:', errInfo); } }; const saveCheckBox = async () => { const values = await form.validateFields(); form.setFieldsValue({ [dataIndex]: values.IsNullable, }); handleSave({ ...record, ...values, index }); }; const saveAddCheckBox = async e => { const values = await form.validateFields(); form.setFieldsValue({ [dataIndex]: values.IsAddFieldConfig, }); handleSave({ ...record, ...values, index }); }; const rendeFrom = val => { let lengthMin = 0; let lengthMax = 0; let decimalPlaceMin = 0; let decimalPlaceMax = 0; // 字段类型为精确数值型(decimal),字段长度1-38小数点位数0-38 if (record.FieldType === 8) { lengthMin = 1; lengthMax = 38; decimalPlaceMin = 0; decimalPlaceMax = 38; } // 字段类型为字符串型(nvarchar)、二进制(varbinary),字段长度为0-4000 if (record.FieldType === 2 || record.FieldType === 10) { lengthMin = 1; lengthMax = 4000; } // 字段类型为字符串型(varchar)、,字段长度为0-8000 if (record.FieldType === 0 || record.FieldType === 1) { lengthMin = 1; lengthMax = 8000; } if (val === '字段名称') { return <Input ref={inputRef} onPressEnter={save} onBlur={save} />; } if (val === '字段类型') { return ( <Select ref={inputRef} onPressEnter={save} onBlur={save} showSearch filterOption={(input, option) => option.children.toLowerCase().includes(input.toLowerCase()) } > <Select.Option value={0}>字符串型(varchar)</Select.Option> <Select.Option value={12}>字符串型(varchar(max))</Select.Option> <Select.Option value={1}>字符型(nchar)</Select.Option> <Select.Option value={2}>字符串型(nvarchar)</Select.Option> <Select.Option value={3}>字符串型(nvarchar(max))</Select.Option> <Select.Option value={4}>布尔型(bit)</Select.Option> <Select.Option value={5}>整数型(int)</Select.Option> <Select.Option value={6}>浮点型(float)</Select.Option> <Select.Option value={7}>长整型(bigint)</Select.Option> <Select.Option value={8}>精确数值型(decimal)</Select.Option> <Select.Option value={9}>时间(datetime)</Select.Option> <Select.Option value={10}>二进制(varbinary)</Select.Option> <Select.Option value={11}>二进制(varbinary(max))</Select.Option> </Select> ); } if (val === '字段长度') { return ( <InputNumber ref={inputRef} min={lengthMin} max={lengthMax} onPressEnter={save} onBlur={save} /> ); } if (val === '小数点位') { return ( <InputNumber ref={inputRef} min={decimalPlaceMin} max={decimalPlaceMax} onPressEnter={save} onBlur={save} /> ); } return <Input ref={inputRef} onPressEnter={save} onBlur={save} />; }; let childNode = children; if (editable) { // 字段类型为 字符串型(varchar)、二进制(varbinary)、字符型(nchar)、字符串型(nvarchar)、精确数值型(decimal)让修改 if ( title === '字段长度' && (record.FieldType !== 0 && record.FieldType !== 10 && record.FieldType !== 1 && record.FieldType !== 2 && record.FieldType !== 8) ) { return <td {...restProps}>--</td>; } // 字段类型为 精确数值型(decimal)让修改 if (title === '小数点位' && record.FieldType !== 8) { return <td {...restProps}>--</td>; } // 只读 if (record.ReadOnly && title !== '允许空值' && title !== '是否附加') { return <td {...restProps}>{childNode}</td>; } // 表单规则 let rules = []; if (title === '字段名称') { rules = [ { required: true, message: '字段名称不能为空', }, { validator: (rule, value) => { let list = JSON.parse(JSON.stringify(dataSource)); // 合并内置字段 if ( value && [...defaultData, ...list].some( (item, i) => item.Name === value && item.keyIndex !== record.keyIndex, ) ) { return Promise.reject(new Error('字段名称重复,请重新输入')); } return Promise.resolve(); }, }, { pattern: /^(?!(\d+)$)[\u4e00-\u9fffa-zA-Z0-9_]+$/, message: '不能输入特殊符号或者纯数字', }, ]; } childNode = editing ? ( <Form.Item style={{ width: `${width - 20}px`, height: '32px', margin: 0, marginLeft: '50%', transform: 'translateX(-50%)', }} rules={rules} name={dataIndex} > {rendeFrom(title)} </Form.Item> ) : ( <div className="editable-cell-value-wrap" // title={children[1]} style={{ width: `${width - 20}px`, height: '32px', overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis', margin: 'auto', }} onClick={toggleEdit} > {children} </div> ); if (title === '允许空值') { childNode = ( <Form.Item style={{ margin: 0, }} name={dataIndex} valuePropName="checked" > <Checkbox onChange={saveCheckBox} disabled={record.ReadOnly || tableDataCount} /> </Form.Item> ); } if (title === '是否附加') { childNode = ( <Form.Item style={{ margin: 0, }} name={dataIndex} valuePropName="checked" > <Checkbox onChange={saveAddCheckBox} /> </Form.Item> ); } } return <td {...restProps}>{childNode}</td>; }; const TableView = props => { const { callBackSubmit, onCancel, visible, type, formObj, tableType, defaultFieldsList } = props; const [dataSource, setDataSource] = useState([]); const [count, setCount] = useState(0); const [loading, setLoading] = useState(false); const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [tableMsg, setTableMsg] = useState({}); const [tableDataCount, setTableDataCount] = useState(false); const [defaultData, setDefaultData] = useState([]); const [showDefault, setShowDefault] = useState(true); const [exceptionArr, setExceptionArr] = useState([]); const [form] = Form.useForm(); useEffect(() => { if (visible) { if (type === 'tableEdit') { setLoading(true); form.setFieldsValue({ tableName: formObj.tableName, alias: formObj.tableAlias, }); getTableInfo({ tableName: formObj.tableName, isIncludeField: true }) .then(res => { setLoading(false); if (res.code === 0) { setTableDataCount(res.data.root[0].TableDataCount); setTableMsg({ tableStyle: res.data.root[0].tableStyle ? res.data.root[0].tableStyle : '大', officeTmpl: res.data.root[0].officeTmpl, interfaceName: res.data.root[0].interfaceText, tableID: res.data.root[0].tableID, }); const defaultList = []; const fieldList = res.data.root[0].TableFields.map((item, index) => { const obj = { ...item, keyIndex: index, }; if (item.ReadOnly) { defaultList.push(obj); } return obj; }); setDefaultData(defaultList); setCount(fieldList.length); setShowDefault(false); let list = JSON.parse(JSON.stringify(fieldList)); list = list.filter(item => !item.ReadOnly); setDataSource(list); } else { notification.error({ message: '提示', duration: 3, description: res.msg }); } }) .catch(() => { setLoading(false); notification.error({ message: '提示', duration: 3, description: '网络异常' }); }); } else { let list = defaultFieldsList .find(item => item.value === tableType) .list.map((item, i) => ({ ...item, keyIndex: i })); console.log(list); setDefaultData(list); setCount(list.length); setShowDefault(false); let listitem = JSON.parse(JSON.stringify(list)); listitem = listitem.filter(item => !item.ReadOnly); setDataSource(listitem); } reloadTableFieldsArr(); } else { setShowDefault(false); setDataSource([]); setDefaultData([]); setSelectedRowKeys([]); setExceptionArr([]); form.resetFields(); } }, [visible]); const removeMissingFields = () => { let ids = exceptionArr.map(item => item.ID).join(','); removeFields({ fieldIDs: ids, }).then(res => { if (res.code === 0) { notification.success({ message: '提示', duration: 3, description: '清除成功', }); reloadTableFieldsArr(); } }); }; const reloadTableFieldsArr = () => { reloadTableFields({ tableName: formObj.tableName, }).then(res => { if (res.msg === 'Ok') { setExceptionArr(res.data.root.filter(item => item.group === '(缺少字段)')); } }); }; // 提交表单 const onFinish = () => { // 校验提示 let checkMsg = ''; let TableFields = JSON.parse(JSON.stringify(dataSource)); TableFields.forEach((item, index) => { item.Order = index; if (!item.Name) { item.errors = ['字段名称不能为空']; } if (item.errors?.length > 0) { item.errors.forEach(ele => { checkMsg = `${checkMsg}第${index + 1}行 校验错误:${ele}\n`; }); } }); if (!showDefault) { // 默认字段不显示时要拼接上默认字段 TableFields = [...defaultData, ...TableFields]; } // if (checkMsg) { // notification.error({ // message: '提示', // duration: 3, // description: checkMsg, // style: { whiteSpace: 'pre-wrap' }, // }); // return; // } form.validateFields().then(validate => { if (validate) { if (!validate.tableName) { notification.error({ message: '提示', duration: 3, description: '请填写表名' }); return; } if (type === 'add') { // 新建表 CreateTablePost({ ...validate, tableName: `${tableType.substr(0, tableType.length - 1)}_${validate.tableName}`, TableFields, tableType, tableStyle: '大', }).then(res => { if (res.code === 0) { notification.success({ message: '提示', duration: 3, description: '新增成功', }); callBackSubmit(); } else { notification.error({ message: '提示', duration: 3, description: res.msg }); } }); } else { // 编辑表 updateTablePost({ ...validate, TableFields, ...tableMsg }).then(res => { if (res.code === 0) { notification.success({ message: '提示', duration: 3, description: '编辑成功', }); callBackSubmit(); } else { notification.error({ message: '提示', duration: 3, description: res.msg }); } }); } } }); }; // 添加字段 const handleAdd = () => { const newData = { keyIndex: count, Name: '', FieldType: 0, FieldLength: 255, DecimalPlace: 0, IsNullable: true, IsAddFieldConfig: true, }; setDataSource([...dataSource, newData]); setCount(count + 1); setTimeout(() => { let tableEl = document.querySelector(`.${styles.content} .ant-table-body`); tableEl.scrollTop = tableEl.scrollHeight; }, 10); }; // 批量删除字段 const deleteFilleds = () => { if (selectedRowKeys.length === 0) { notification.error({ message: '提示', duration: 3, description: '请选择字段' }); return; } const list = []; dataSource.forEach(item => { const isDelete = selectedRowKeys.some(value => value === item.keyIndex); if (!isDelete) { list.push(item); } }); setDataSource(list); }; // 删除字段 const handleDelete = (record, keyIndex) => { if (record.ReadOnly) { notification.error({ message: '提示', duration: 3, description: '内置字段不允许删除' }); return; } const newData = dataSource.filter((item, index) => index !== keyIndex); setDataSource(newData); }; const addArr = useMemo(() => { let arr = JSON.parse(JSON.stringify(dataSource)); if (showDefault) { return arr; } return [...defaultData, ...arr]; }, [showDefault, defaultData, dataSource]); // 修改后存值 const handleSave = (row, key) => { if (key === 'FieldType') { if (row.FieldType === 0 || row.FieldType === 2) { row.FieldLength = 255; row.DecimalPlace = 0; } else if (row.FieldType === 10) { row.FieldLength = 50; row.DecimalPlace = 0; } else if (row.FieldType === 1) { row.FieldLength = 10; row.DecimalPlace = 0; } else if (row.FieldType === 8) { row.FieldLength = 18; row.DecimalPlace = 3; } else { row.FieldLength = 0; row.DecimalPlace = 0; } } const { index } = row; const newData = [...dataSource]; const item = newData[index]; newData.splice(index, 1, { ...item, ...row }); // 内置字段同步更改 // if (index < defaultData.length) { // const newDefaultData = [...defaultData]; // newDefaultData.splice(index, 1, { ...item, ...row }); // setDefaultData(newDefaultData); // } setDataSource(newData); }; // 是否显示默认字段 const showDefaultFields = e => { setShowDefault(e); let list = JSON.parse(JSON.stringify(dataSource)); if (e) { // 显示内置字段 list = [...defaultData, ...list]; } else { list = list.filter(item => !item.ReadOnly); } setDataSource(list); }; const deleteAddField = (index, name) => { let arr = JSON.parse(JSON.stringify(dataSource)); arr.forEach(item => { if (item.Name === name) { item.IsAddFieldConfig = false; } }); console.log(arr, name, 'arrarrarrarrarrarr'); setDataSource(arr); }; // 表格设置 const components = { body: { row: EditableRow, cell: EditableCell, }, }; const defaultColumns = [ { title: '序号', align: 'center', width: 50, render: (text, record, index) => <span>{index + 1}</span>, }, { title: '字段名称', dataIndex: 'Name', width: 200, // ellipsis: true, editable: true, align: 'center', render: (text, record) => ( <> <span>{text}</span> {record.IsIndex && ( <img src={index} style={{ height: '25px', marginLeft: '5px' }} alt="" /> )} {record.IsPrimaryKey && ( <img src={primaryKey} style={{ height: '25px', marginLeft: '5px' }} alt="" /> )} </> ), }, { title: '字段类型', dataIndex: 'FieldType', width: 220, ellipsis: true, editable: true, align: 'center', render: text => { switch (text) { case 0: return '字符串型(varchar)'; case 1: return '字符型(nchar)'; case 2: return '字符串型(nvarchar)'; case 3: return '字符串型(nvarchar(max))'; case 4: return '布尔型(bit)'; case 5: return '整数型(int)'; case 6: return '浮点型(float)'; case 7: return '长整型(bigint)'; case 8: return '精确数值型(decimal)'; case 9: return '时间(datetime)'; case 10: return '二进制(varbinary)'; case 11: return '二进制(varbinary(max))'; case 12: return '字符串型(varchar(max))'; default: return null; } }, }, { title: '字段长度', dataIndex: 'FieldLength', width: 100, ellipsis: true, editable: true, align: 'center', }, { title: '小数点位', dataIndex: 'DecimalPlace', width: 100, ellipsis: true, editable: true, align: 'center', }, // { // title: '允许空值', // dataIndex: 'IsNullable', // width: 100, // editable: true, // align: 'center', // }, { title: '是否附加', dataIndex: 'IsAddFieldConfig', width: 100, editable: true, align: 'center', }, { title: '操作', width: 50, align: 'center', render: (_, record, index) => dataSource.length >= 1 ? ( <Tooltip title="删除"> <DeleteOutlined onClick={() => handleDelete(record, index)} style={{ fontSize: '16px', color: `${record.ReadOnly ? '#ccc' : '#e86060'}` }} /> </Tooltip> ) : null, }, ]; const columns = defaultColumns.map(col => { if (!col.editable) { return col; } return { ...col, onCell: (record, index) => ({ index, record, editable: col.editable, dataIndex: col.dataIndex, width: col.width, title: col.title, ellipsis: col.ellipsis, align: col.align, dataSource, tableDataCount, defaultData, handleSave, }), }; }); // 表格复选框 const onSelectChange = newSelectedRowKeys => { setSelectedRowKeys(newSelectedRowKeys); }; const rowSelection = { selectedRowKeys, onChange: onSelectChange, getCheckboxProps: record => ({ disabled: record.ReadOnly, }), }; return ( <Modal title={type === 'add' ? `建表【${tableType}】` : `表编辑`} visible={visible} width="1300px" onOk={onFinish} onCancel={onCancel} maskClosable={false} destroyOnClose centered > <div className={styles.content}> <div> <Form form={form}> <div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex' }}> <Form.Item label="表名" rules={[ { required: true, message: '表名称不能为空', }, { pattern: /^[\u4e00-\u9fffa-zA-Z0-9_]+$/, message: '不能输入特殊符号', }, ]} name="tableName" required style={{ marginBottom: '0' }} > <Input addonBefore={ type === 'add' ? `${tableType.substr(0, tableType.length - 1)}_` : null } placeholder="请填写表名" /> </Form.Item> <Form.Item label="展示名称" name="alias" style={{ marginBottom: '0', marginLeft: '10px' }} > <Input placeholder="请填写展示名称" /> </Form.Item> </div> <div style={{ display: 'flex' }}> <Form.Item label="内置字段" style={{ marginBottom: '0', marginRight: '10px' }}> <Switch checkedChildren="显示" unCheckedChildren="隐藏" onChange={showDefaultFields} /> </Form.Item> <Form.Item style={{ marginBottom: '0', marginRight: '10px' }}> <Button type="primary" onClick={() => handleAdd()}> <div style={{ display: 'flex', alignItems: 'center' }}> <PlusOutlined style={{ marginRight: '5px' }} /> <span> 新增</span> </div> </Button> </Form.Item> <Form.Item style={{ marginBottom: '0' }}> <Button onClick={() => deleteFilleds()}> <div style={{ display: 'flex', alignItems: 'center' }}> <MinusOutlined style={{ marginRight: '5px' }} /> <span> 批量删除</span> </div> </Button> </Form.Item> </div> </div> </Form> <Spin spinning={loading}> <Table rowKey="keyIndex" rowSelection={rowSelection} size="small" style={{ marginTop: '10PX' }} components={components} rowClassName={() => 'editable-row'} bordered dataSource={dataSource} columns={columns} scroll={{ y: '540px' }} pagination={false} id="box" /> <div id="page-bottom" /> </Spin> </div> <div className={styles.subContent}> <div className={styles.subArea} style={{ height: exceptionArr.length ? '70%' : '100%' }}> <div className={styles.subTitle}>已附加字段集</div> <div className={styles.subItems}> {addArr.map((item, index) => item.IsAddFieldConfig ? ( <div className={styles.subItem}> <span>{item.Alias || item.Name}</span>{' '} <span className={styles.deleteItem} onClick={() => deleteAddField(index, item.Name)} > X </span> </div> ) : null, )} {/* {dataSource.some(item => { item.IsAddFieldConfig; })?:<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无数据" style={{ margin: '20px auto 0px auto', paddingTop: '50px' }} />} */} </div> </div> {exceptionArr.length ? ( <div className={styles.exceptionArea}> <div className={styles.subTitle}> 异常字段集{' '} <Button danger style={{ borderRadius: '4px', display: 'flex', alignItems: 'center', justifyContent: 'space-around', }} onClick={() => removeMissingFields()} size="small" > <img src={clearImg} alt="" style={{ width: '14px' }} /> 一键清除 </Button> </div> <div className={styles.subItems}> {exceptionArr.map((item, index) => ( <div className={styles.subItem}> <span style={{ color: 'red' }}>{item.alias || item.name}</span>{' '} </div> ))} </div> </div> ) : null} </div> </div> </Modal> ); }; export default TableView;