作者:mobiledu2502931957 | 来源:互联网 | 2023-08-19 14:59
首先接到的任务是这样的:
 
 
 
 那么打开参考对象看一眼:
 
 总结一下组件的内容和功能点:
1.一个输入框,两个按钮(确定,取消)
2.点击文本,弹出气泡,进行编辑,提交/取消,关闭气泡,更新数据(数据不变则不更新)
而原本的组件,则是直接点击编辑按钮,变为编辑模式:
 
 
因此,我选择了antd提供的Popover组件,稍微封装一下功能,做成一个独立的小小组件,代码是这样的:
import React, { useState, useEffect, useRef, useImperativeHandle } from 'react'; import { Input, Button, Popover } from 'antd'; import { CloseCircleOutlined } from '@ant-design/icons'; // 工具函数 import { trimAllBlank } from '@/utils/tools'; // 样式文件 import styles from './style.less'; // 属性定义文件 import { Props } from './index.type'; /** * Single line edit bubble component【单行编辑气泡组件】 * author: wun */ const TheEditCellBubble: React.FC = (props) => { const { inputType, initValue, record, dataIndex, placeholder, verify, className, request, update, cRef, } = props; // 输入框ref const inputRef = useRef(null); // 输入框的值 const [inputValue, setInputValue] = useState(''); // 单行展示的值 const [showValue, setShowValue] = useState(''); // 错误提示文案 const [errorText, setErrorText] = useState(''); // 错误提示文案展示状态控制 const [errorVisible, setErrorVisible] = useState(false); // 确认按钮loading状态控制 const [submitLoading, setSubmitLoading] = useState(false); // 气泡展示状态控制 const [visible, setVisible] = useState(false); // 校验函数 const verifyInput = (val: any) => { if (verify && verify.rules && verify.rules.length > 0) { const error = verify.rules.find((el: any) => { // 空验证 if (el.required) { return !val; } // 正则验证 if (el.pattern) { return !el.pattern.test(val); } // 自定义验证 if (el.validator) { return !el.validator(val); } return false; }); if (error) { setErrorVisible(true); setErrorText(error.message); return false; } } return true; }; // 监听输入框实时内容 const handleChange = (e: { target: { value: string } }) => { const val = e.target.value; setInputValue(trimAllBlank(val)); // 重置错误提示 if (errorVisible && verifyInput(val)) { setErrorVisible(false); setErrorText(''); } }; // 确定-回调 const handleOk = async (e: React.MouseEvent | React.KeyboardEvent) => { e.stopPropagation(); // 如输入框内容未修改,直接return if (inputValue === showValue) { return; } // 验证输入内容 if (!verifyInput(inputValue)) return; // 创建参数对象 const params = dataIndex ? { [dataIndex]: inputValue } : {}; // 如需发送请求 if (request) { try { // 确认按钮loading状态开启 setSubmitLoading(true); // 发起请求 const res: any = await request({ ...record, ...params }); if (res && res.code === 0 ) { setShowValue(inputValue); if (update) update(params, res); setVisible(false); } // 默认值不存在时一般是做为新建功能使用此组件, 默认会在成功后清空输入项 if (!initValue) setInputValue(''); setSubmitLoading(false); } catch (error) { setSubmitLoading(false); } finally { // } } else if (update) { // 无需发送请求,则直接修改数据并返回 setShowValue(inputValue); update(params, {}); setVisible(false); setSubmitLoading(false); } } // 取消-回调 const handleCancel =(e: React.MouseEvent)=>{ e.stopPropagation(); setVisible(false); } // 点击打开气泡 const handleVisibleChange = () => { setVisible(true) }; // 暴露给父级的方法 useImperativeHandle(cRef, () => ({ // 获取当前输入框值 value: inputValue, // 可编辑状态时手动插入值 insert: (value: string) => { // 在当前光标位置插入内容 if (typeof inputValue === 'string') { const { input } = inputRef.current; const { selectionStart, selectionEnd } = input; // 优先插入当前光标所在位置, 如无法确定当前光标所在位置则插入当前值末尾 setInputValue( inputValue.substring(0, selectionStart) + value + inputValue.substring(selectionEnd, inputValue.length), ); // 重置光标位置 input.focus(); } // 重置错误提示 if (errorVisible && verifyInput(value)) { setErrorVisible(false); setErrorText(''); } }, })); // 气泡展示时输入框自动聚焦 useEffect(() => { let timer: any = null; if (visible) { timer = setTimeout(() => { inputRef.current.focus(); }, 0); } return function cleanUp() { if (timer) clearTimeout(timer); }; }, [visible]); // 内容初始化赋值 useEffect(() => { if (initValue) { setShowValue(initValue); setInputValue(initValue); } }, []); return ( placement="bottom" cOntent={ /> 确定 取消
{errorVisible &&
{errorText}
}
} trigger="click" visible={visible} OnVisibleChange={handleVisibleChange} getPopupCOntainer={(triggerNode) => triggerNode} // 改变浮层渲染父节点 > {showValue} ); }; export default TheEditCellBubble;
属性定义的文件是这样的:
export interface Props { inputType?: string; // input类型 initValue?: string; // 单元格初使值 record?: any; // 行数据 dataIndex?: string; // 单元格数据在行数据中对应的路径 cRef?: any; placeholder?: string; verify?: { rules?: any; // 规则 maxLength?: number; // 最大程度 }; // 单元格输入相关规则 className?: string; // 自定义文本状态 class request?: (params?: any) => Promise; // 更新单元格数据接口 update?: (params?: object, result?: any) => void; // 更新回调, 回传请求参数和后台返回数据 }
css样式是这样的:
.c-edit_cell-bubble { .c-edit_cell-bubble-content{ width: 500px; display: flex; min-height: 32px; align-items: center; padding: 4px; box-sizing: border-box; white-space: nowrap; transition: linear 2s; input{ width: 70%; } button { margin-left: 8px; } .c-edit_cell-bubble-input-error{ border-color: red; } } .c-edit_cell-bubble-error-tips{ min-height: 20px; line-height: 1.5; color: red; .c-edit_cell-bubble-error-icon{ color: red; margin: 0 4px; } } }
使用方式是这样的:
# Single line edit bubble component【单行编辑气泡组件】 ## 引用 import { BasisTheEditCellBubble } from '@/components/index'; ## 调用 `` `` ## 属性参考 index.type.ts文件 ########### 示例参考 [可替换掉项目管理的BasisEditTableCell组件用以体验] `` initValue={text} record={record} dataIndex="appName" verify={{ ...rulesData.appName, rules: [ {pattern: /\S+/,message: `请输入${ tableHeaderList.filter((el: any) => el.dataIndex === 'appName')[0].title}`, }, ], }} request={modifyProject} update={() => initTableList()} /> ``
我觉得很ok,于是提交了代码,跟大佬表示做完了!
然而大佬看过之后,却表示:代码跟之前那个组件冗余了,要不考虑放到一起吧,减少代码的重复。
我:好的!
于是第二个版本,我的思路是,在原本行内编辑的组件里实现2种模式,在index文件增加一个isBubble(是否气泡模式)的属性,传给这个单行编辑组件进行区分。思路有了,快速进行开发。
开发完成之后,再给大佬看,大佬沉默了。
大佬表示,她想要的不是在最低层去封装,最底层最好不动。
ok!于是第三个版本,我的思路就是在组件的index进行封装,方法都提取出来,底层的组件不再需要进行请求之类的操作,直接在index管理,类似这样:
import React, { useState, useEffect } from &#39;react&#39;; import { Button } from &#39;antd&#39;; import { FormOutlined } from &#39;@ant-design/icons&#39;; import { trimAllBlank } from &#39;@/utils/tools&#39;; // 业务组件 import EditableCellForm from &#39;./EditableCellForm&#39;; import TheEditCellBubble from &#39;./TheEditCellBubble&#39;; // css import styles from &#39;./style.less&#39;; // 类型定义 import { Props } from &#39;./index.type&#39;; /** * @description 可编辑单元格 * @param {object} props - 父级数据 * @returns {component} */ const TheEditTableCell: React.FC = (props) => { const { initValue, record, dataIndex, placeholder, verify, ellipsis, disabled, textClassName, inputType, request, update, onEdit, onCancel, onTextClick, isBubble, } = props; // 文本状态时显示的值 const [textValue, setTextValue] = useState(initValue); // 可编辑状态 const [editable, setEditable] = useState(false); // 输入框的值 const [inputValue, setInputValue] = useState(&#39;&#39;); // 错误提示文案 const [errorText, setErrorText] = useState(&#39;&#39;); // 错误提示文案展示状态控制 const [errorVisible, setErrorVisible] = useState(false); // 确认按钮loading状态控制 const [loading, setLoading] = useState(false); // 气泡展示状态控制 const [visible, setVisible] = useState(false); const handleOk = async (value?: string) => { if (value) { // 输入内容校验不通过,直接return if (!verifyInput(value)) return; // 内容不变,直接return if (inputValue === textValue) return; // 保存展示内容 setTextValue(value); // 如果是编辑状态,则关闭 if (editable) { setEditable(false); } // 创建参数对象 const dataParams = dataIndex ? { [dataIndex]: inputValue } : {}; // 如需发送请求 if (request) { try { // 确认按钮loading状态开启 setLoading(true); // 发起请求 const res: any = await request({ ...record, ...dataParams }); if (res && res.code === 0 ) { // 保存展示内容 setTextValue(value); setInputValue(value); // 如需更新 if (update) update({ ...record, ...dataParams }, res.result); // 关闭编辑框 if(visible) setVisible(false); } // 默认值不存在时一般是做为新建功能使用此组件, 默认会在成功后清空输入项 if (!initValue) setInputValue(&#39;&#39;); setLoading(false); } catch (error) { setLoading(false); } finally { // } } else if (update) { // 无需发送请求,则直接修改数据并返回 setTextValue(inputValue); setInputValue(inputValue); setVisible(false); setLoading(false); } } } // 文本点击回调 const handleTextClick = () => { if (onTextClick) onTextClick(); } // 校验函数 const verifyInput = (val: any) => { if (verify && verify.rules && verify.rules.length > 0) { const error = verify.rules.find((el: any) => { // 空验证 if (el.required) { return !val; } // 正则验证 if (el.pattern) { return !el.pattern.test(val); } // 自定义验证 if (el.validator) { return !el.validator(val); } return false; }); if (error) { setErrorVisible(true); setErrorText(error.message); return false; } } return true; }; // 监听输入框实时内容 const handleChange = (e: { target: { value: string } }) => { const val = e.target.value; setInputValue(trimAllBlank(val)); verifyInput(val); // 重置错误提示 if (errorVisible && verifyInput(val)) { setErrorVisible(false); setErrorText(&#39;&#39;); } }; // 取消-回调 const handleCancel =(e: React.MouseEvent)=>{ e.stopPropagation(); if(visible) setVisible(false); if(editable) setEditable(false); setInputValue(textValue); } // 点击打开气泡 const handleVisibleChange = () => { setVisible(true); }; // 监听初使值的变化 useEffect(() => { if (initValue) { setTextValue(initValue); setInputValue(initValue); } }, [initValue]); // 监听编辑状态的变化 useEffect(() => { // 激活编辑回调 if (editable && onEdit) { onEdit(); } // 取消编辑回调 else if (onCancel) { onCancel(); } }, [editable]); return ( <> {!isBubble && !editable && {ellipsis ? (
title={textValue} className="ads-single-ellipsis" | &#39;-&#39;}
) : (
)}
{!disabled && (
setEditable(true)} /> )} } {!isBubble && editable && !disabled && defaultValue={textValue} inputValue={inputValue} placeholder={placeholder} verify={verify} errorText={errorText} errorVisible={errorVisible} loading={loading} isFocus={editable} inputType={inputType} handleOk={handleOk} handleCancel={handleCancel} handleChange={handleChange} /> } { isBubble && inputValue={inputValue} showValue={textValue} errorText={errorText} errorVisible={errorVisible} loading={loading} visible={visible} verify={verify} handleChange={handleChange} handleVisibleChange={handleVisibleChange} handleOk={handleOk} handleCancel={handleCancel} /> } > ); }; TheEditTableCell.defaultProps = { ellipsis: false, inputType: &#39;text&#39;, }; export default TheEditTableCell;
ok实现!
于是再次提交代码,给大佬过目,然而大佬又一次沉默了。
这次沉默的原因是:大可以和index做成并列关系的组件,只是内部的输入框之类,可以直接调用之前已有的,用气泡包裹起来就好了。
我:……
我:好的,我相信这次一定没问题。
这次的思路就是,单独,与index并列,引用已有的底层组件,包一层popover。于是第四个版本诞生了:
&#160;
import React, { useState, useEffect } from &#39;react&#39;; import { Popover, Button } from &#39;antd&#39;; // 业务组件 import EditableCellForm from &#39;./EditableCellForm&#39;; // 编辑icon import { FormOutlined } from &#39;@ant-design/icons&#39;; // 样式文件 import styles from &#39;./style.less&#39;; // 类型定义 import { Props } from &#39;./index.type&#39;; /** * Single line edit bubble component【单行编辑气泡组件】 * author: wun */ const EditCellBubble: React.FC = (props) => { const { initValue, record, dataIndex, placeholder, verify, ellipsis, disabled, textClassName, inputType, request, update, onEdit, onCancel, cRef, } = props; // 文本状态时显示的值 const [textValue, setTextValue] = useState(initValue); // 编辑状态 const [editable, setEditable] = useState(false); // 确定-回调 const handleOk = async (value?: string, params?: object, result?: any) => { if (value) { setTextValue(value); // 更新父级数据 if (update) { update(params, result); } } if (editable) { setEditable(false); } } const handleVisibleChange = () => { setEditable(!editable); }; // 监听初使值的变化 useEffect(() => { if (initValue) setTextValue(initValue); }, [initValue]); // 监听编辑状态的变化 useEffect(() => { // 激活编辑回调 if (editable && onEdit) { onEdit(); } // 取消编辑回调 else if (onCancel) { onCancel(); } }, [editable]); return ( }trigger="click"visible={editable}OnVisibleChange={handleVisibleChange}> {textValue || &#39;&#39;}{!disabled && ( setEditable(true)} /> )}
} {disabled && {textValue || &#39;&#39;}
}