React Experience

42 阅读5分钟

展开表格嵌套拖拽

  • 代码

    import React, { useState, useRef } from 'react';
    import { message, Table } from 'antd';
    import './App.css'
    
    // 定义表格列
    const columns = [
    	{
    		title: 'ID',
    		dataIndex: 'id',
    	},
    	{
    		title: '名称',
    		dataIndex: 'rule_name',
    	},
    	{
    		title: '描述',
    		dataIndex: 'rule_describe',
    	},
    ];
    
    // 组件
    const App = () => {
    	let startNum: number; // 拖动元素索引
    	let endNum: number; // 目标元素索引
    	let startCardId: number; // 拖动元素所在卡片的id
    	let endCardId: number; // 目标元素所在卡片的id
    	let startCardNum: number; // 拖动卡片索引
    	let endCardNum: number; // 目标卡片索引
    	let trStartKey: number; // 拖动元素所在行的key
    	let trEndKey: number; // 目标元素所在行的key
    	const types = useRef("") // 拖动元素判断
    	const [cardDraggable, setCardDraggable] = useState(false) // 控制是否开启卡片拖动
    	// 表格数据
    	const [dataList, setDataList] = useState([
    		{
    			key: 1001,
    			id: 1001,
    			rule_name: '国家&基地&类型',
    			rule_describe: '该规则优先按照国家匹配,然后对具体基地的基地进行筛选...',
    			classifies: [
    				{
    					id: 1,
    					rule_id: 1001,
    					classify_name: '地区',
    					classify_data: [
    						{ id: '001', name: '俄罗斯1' },
    						{ id: '002', name: '美国1' },
    						{ id: '003', name: '日本1' },
    						{ id: '004', name: '朝鲜1' },
    					]
    				}, {
    					id: 2,
    					rule_id: 1001,
    					classify_name: '基地',
    					classify_data: [
    						{ id: '001', name: '陆军基地1' },
    						{ id: '002', name: '海军基地1' },
    						{ id: '003', name: '空军基地1' },
    					]
    				}, {
    					id: 3,
    					rule_id: 1001,
    					classify_name: '检测类别',
    					classify_data: [
    						{ id: '001', name: '远海舰船1' },
    						{ id: '002', name: '近岸舰船1' },
    						{ id: '003', name: '导弹阵地1' },
    						{ id: '004', name: '弱小目标1' },
    					]
    				},
    			],
    		},
    		{
    			key: 1002,
    			id: 1002,
    			rule_name: '国家&基地&类型',
    			rule_describe: '该规则优先按照国家匹配,然后对具体基地的基地进行筛选...',
    			classifies: [
    				{
    					id: 1,
    					rule_id: 1002,
    					classify_name: '地区',
    					classify_data: [
    						{ id: '001', name: '俄罗斯2' },
    						{ id: '002', name: '美国2' },
    						{ id: '003', name: '日本2' },
    						{ id: '004', name: '朝鲜2' },
    					]
    				}, {
    					id: 2,
    					rule_id: 1002,
    					classify_name: '基地',
    					classify_data: [
    						{ id: '001', name: '陆军基地2' },
    						{ id: '002', name: '海军基地2' },
    						{ id: '003', name: '空军基地2' },
    					]
    				}, {
    					id: 3,
    					rule_id: 1002,
    					classify_name: '检测类别',
    					classify_data: [
    						{ id: '001', name: '远海舰船2' },
    						{ id: '002', name: '近岸舰船2' },
    						{ id: '003', name: '导弹阵地2' },
    						{ id: '004', name: '弱小目标2' },
    					]
    				},
    			],
    		},
    
    	]);
    
    	// list拖拽开始
    	const dropTagStart = (e: any, index: number, cardId: number, type: string) => {
    		types.current = type
    		e.stopPropagation()
    		startNum = index
    		startCardId = cardId
    	}
    	// list拖拽中
    	const dropTagOvert = (e: any) => {
    		// 阻止默认事件,否则drop不会触发
    		e.stopPropagation()
    		e.preventDefault()
    	}
    	// list拖拽结束
    	const dropTag = (e: any, index: number, cardId: number, indexs: any, trItem: any) => {
    		if (types.current !== "ch") { // 阻止卡片往list拖动时造成的混乱
    			return;
    		}
    		e.stopPropagation()
    		e.preventDefault()
    		endNum = index
    		endCardId = cardId
    		if (startCardId !== endCardId) { // 跨卡片拖拽
    			message.warning('不可跨类移动')
    			return;
    		}
    		if (startNum === endNum) { // 未移动
    			return;
    		}
    
    		// 表格第几行
    		let trIndex = dataList.findIndex((item: any) => {
    			return item === trItem
    		})
    		// 排序逻辑代码
    		if (startNum > endNum) { // 从下往上拖拽
    			if (endNum === 0) { // 置顶
    				dataList[trIndex].classifies[indexs].classify_data.unshift(dataList[trIndex].classifies[indexs].classify_data[startNum])
    				dataList[trIndex].classifies[index].classify_data.splice(startNum + 1, 1)
    			} else { // 非置顶
    				dataList[trIndex].classifies[indexs].classify_data.splice(endNum, 0, dataList[trIndex].classifies[indexs].classify_data[startNum])
    				dataList[trIndex].classifies[indexs].classify_data.splice(startNum + 1, 1)
    			}
    		} else { // 从上往下拖拽
    			if (endNum === dataList[trIndex].classifies[indexs].classify_data.length - 1) { // 置低
    				dataList[trIndex].classifies[indexs].classify_data.push(dataList[trIndex].classifies[indexs].classify_data[startNum])
    				dataList[trIndex].classifies[indexs].classify_data.splice(startNum, 1)
    			} else { // 非置底
    				dataList[trIndex].classifies[indexs].classify_data.splice(endNum + 1, 0, dataList[trIndex].classifies[indexs].classify_data[startNum])
    				dataList[trIndex].classifies[indexs].classify_data.splice(startNum, 1)
    			}
    		}
    		setDataList((state: any) => {
    			return [...state]
    		})
    
    	}
    
    	// 卡片拖拽开始
    	const dropTagStartCard = (e: any, indexs: number, cardId: number, key: number, type: string) => {
    		types.current = type
    		e.stopPropagation()
    		startCardNum = indexs
    		startCardId = cardId
    		trStartKey = key
    	}
    	// 卡片拖拽中
    	const dropTagOvertCard = (e: any) => {
    		e.stopPropagation()
    		e.preventDefault()
    	}
    	// 卡片拖拽结束
    	const dropTagCard = (e: any, indexs: number, cardId: number, trItem: any, key: number) => {
    		e.stopPropagation()
    		e.preventDefault()
    		endCardNum = indexs
    		endCardId = cardId;;
    		trEndKey = key
    		if (types.current !== "fa") {// 阻止卡片往list拖动时造成的混乱
    			setCardDraggable(false)
    			return;
    		}
    		if (trStartKey !== trEndKey) { // 跨行拖拽
    			setCardDraggable(false)
    			message.warning('不可跨行移动')
    			return;
    		}
    		if (startCardNum === endCardNum) { // 未移动
    			setCardDraggable(false)
    			return;
    		}
    
    		// 表格第几行
    		let trIndex = dataList.findIndex((item: any) => {
    			return item === trItem
    		})
    		// 排序逻辑代码
    		if (startCardNum > endCardNum) { // 从后往前拖
    			if (endCardNum === 0) { // 置顶
    				dataList[trIndex].classifies.unshift(dataList[trIndex].classifies[startCardNum])
    				dataList[trIndex].classifies.splice(startCardNum + 1, 1)
    			} else { // 非置顶
    				dataList[trIndex].classifies.splice(endCardNum, 0, dataList[trIndex].classifies[startCardNum])
    				dataList[trIndex].classifies.splice(startCardNum + 1, 1)
    			}
    		} else { // 从前往后拖
    			if (endCardNum === dataList[trIndex].classifies.length - 1) { // 置底
    				dataList[trIndex].classifies.push(dataList[trIndex].classifies[startCardNum])
    				dataList[trIndex].classifies.splice(startCardNum, 1)
    			} else { // 非置底
    				dataList[trIndex].classifies.splice(endCardNum + 1, 0, dataList[trIndex].classifies[startCardNum])
    				dataList[trIndex].classifies.splice(startCardNum, 1)
    			}
    		}
    		setDataList((state: any) => {
    			return [...state]
    		})
    	}
    
    	// 在类别处按下事件,开启卡片拖拽
    	const mouseDown = () => {
    		setCardDraggable(true)
    	}
    
    	return (
    		<Table
    			rowKey="key"
    			columns={columns}
    			dataSource={dataList}
    			expandable={{
    				expandRowByClick: true,
    				// 展开表格
    				expandedRowRender: (record) => {
    					return (
    						<div style={{ display: 'flex' }}>
    							{
    								record.classifies.map((items: any, indexs: number) => {
    									return (
    
    										// 卡片 
    										<div className='cardBox' key={items.id}
    											draggable={cardDraggable}
    											onDragStart={(e) => dropTagStartCard(e, indexs, items.id, record.id, 'fa')}
    											onDragOver={(e) => dropTagOvertCard(e)}
    											onDrop={(e) => dropTagCard(e, indexs, items.id, record, record.id)}
    										>
    											<div className='title' onMouseDown={() => { mouseDown() }}>{items.classify_name}</div>
    
    
    											{
    												// 小list
    												items.classify_data.map((item: any, index: number) => {
    													return (
    														<div className='list' key={item.id}
    															draggable={true}
    															onDragStart={(e) => dropTagStart(e, index, items.id, 'ch')}
    															onDragOver={(e) => dropTagOvert(e)}
    															onDrop={(e) => dropTag(e, index, items.id, indexs, record)}
    														>
    															<div className='list-text'>{item.name}</div>
    															<div style={{ display: 'flex' }}>
    																<div>👆</div>
    																<div>👇</div>
    															</div>
    
    														</div>
    													)
    												})
    											}
    
    
    										</div>
    									)
    								})
    							}
    						</div>
    					)
    				}
    
    
    			}}
    		/>
    	);
    };
    export default App;
    
  • 图片示例

drop.png

文件下载

    // 下载调用
    const Download = (DataList) => {
        axios.get(`/test/download?name=${DataList.name}&id=${DataList.id}`, {
            headers: {
                token: localStorage.getItem('token')
            },
            responseType: 'blob',
        }).then((res) => {
            if (res) {
                // 文件名:20230520_1314_.pdf
                const fileName = res && res.headers['content-disposition'].split("=")[1]
                // 创建一个Bolb对象
                const blob = new Blob([res.data])
                // 创建一个dom节点
                const elink = document.createElement('a')
                // 下载文件的文件名称
                elink.download = fileName
                // 隐藏节点
                elink.style.display = 'none'
                // 根据入参创建一个指向该参数对象的url
                elink.href = window.URL.createObjectURL(blob)
                // 将dom节点追加进来
                document.body.appendChild(elink)
                // 手动触发a标签的点击事件
                elink.click()
                // 释放URL.createObjectURL创建的对象url
                URL.revokeObjectURL(elink.href)
                // 下载完成移除元素节点
                document.body.removeChild(elink)
            }
        }).catch((err) => {
            console.log('报错', err);
        })
    }
    <button onClick={() => Download(DataList)}>下载</button>

大文件下载

  • 描述

    下载大文件时
    比如下载一个压缩包
    如果调用后端接口后再通过a标签下载
    会存在接口调用还没执行完毕,页面不出现正在下载的进度条
    如果文件过大,则接口加载的时间过长
    用户需要等待接口执行完毕才能看到下载进度条
    这就导致用户再点击‘下载’按钮时看不到下载进度条,用户不知道是否正在下载
    
  • 解决

    避免调用后端接口
    利用a标签直接访问后端接口即可
    
  • 实现

    // 下载调用
    const Download = (DataList) => {
            // 创建一个dom节点
            const elink = document.createElement('a')
            // 隐藏节点
            elink.style.display = 'none'
            // 设置a标签的跳转URL
            elink.href = window.location.origin + `/img_list/download?file_name=${DataList.file_name}`
            // 将dom节点追加进来
            document.body.appendChild(elink)
            // 手动触发a标签的点击事件
            elink.click()
            // 下载完成移除元素节点
            document.body.removeChild(elink)
    }
    

文件上传

  • 描述

    文件上传
    使用antd中的Upload文件上传组件
    
  • 实现

    // 点击上传
    const handleOk = async () => {
        // 创建FormData对象
        const formData = new FormData()
        // 向FromDat对象中组装数据
        formData.append('name',createFrom.getFieldsValue().name)
        // fileList为文件上传列表,此时上传组件值允许上传一个文件
        formData.append('file',fileList[0])
        // 调用上传接口
        const res await postGenerate(formData)
        if(res.code === 0){
            message.success('上传成功')
        }
    }
    
    <Form name='createFrom' ref={createFrom}>
    	<Form.Item>
            <Upload.Dragger
            	progress={{type:'line'}}
                fileList={fileList}
                beforeUpload={beforeUpload}
                onRemove={onRemove}
    		>
                <p className='antd-upload-darg-icon'>
                    <InboxOutlined />
                </p>
                <p className='antd-upload-text'>点击或拖拽上传</p>
            </Upload.Dragger>
    	</Form.Item>
    </Form>
    
    • 上传接口

      export async function postGenerate(data){
          return request('/test/generate',{
              requestType:'form',
              method:'post',
              data,
          })
      }
      

查看文档

  • 描述

    查看文件内容
    此处以pdf文件为例
    
  • 实现

    // 查看pdf文档
    const onViev (DataList){
    	// 此处路径起始就是下载的接口    
        window.open(`/test/download?name=${DataList.name}&id=${DataList,id}`,'_self')
    }