展开表格嵌套拖拽
-
代码
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;
-
图片示例
文件下载
// 下载调用
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') }