页面设计包括哪些内容,微信seo是什么意思,越秀区网站建设公司,用html制作简单的购物网站一、html 代码#xff1a;
代码中的表格引入了 vxe-table 插件
Tag / 是自己封装的说明组件
表格列表这块我使用了插槽来增加扩展性#xff0c;可根据自己需求#xff0c;在组件外部做调整
templatediv classdragUploadel-dial…一、html 代码
代码中的表格引入了 vxe-table 插件
Tag / 是自己封装的说明组件
表格列表这块我使用了插槽来增加扩展性可根据自己需求在组件外部做调整
templatediv classdragUploadel-dialog v-modeldata.visiblewidth35%center:draggabledraggable:destroy-on-closetrue:close-on-click-modalfalse:close-on-press-escapefalse:before-closecloseDialogFntemplate #headerh3{{ fileData.step 2 ? 提示文件超出大小限制 : 拖拽上传}}/h3/template!-- 文件上传区域 --div classdrag-boxv-iffileData.step 1dragoverhandleDragOverdragleavehandleDragOverdrophandleDropdiv classdiv-text div classdrag-tip拖拽文件至此区域span classclick-txt clicktoUploadFloder点击上传/span/divdiv classbtn-wrapel-button v-ifsingleFile clicktoUploadFile上传文件/el-buttonel-button clicktoUploadFloder上传文件夹/el-buttoninputv-ifsingleFile:style{ display: none }typefilereffileUploadRefchangehandleFileChangemultiple/input:style{ display: none }typefilereffileUploadFloderRefchangehandleFloderChangewebkitdirectorymultiple//div/div/div!-- 超出限制后展示的列表 --div v-iffileData.step 2!-- 组件内默认展示 --div classmax-h311 v-if!openSlotvxe-table:datafileData.goBeyondTableheight100%:checkbox-config{showHeader: true,trigger: cell}template #emptydiv classno-data-boxi classicon_noData/idiv classm-t6暂无数据/div/div/templatevxe-column min-width180 :title${data.fileName || SPU}文件名template #default{ row }{{ row[fileData.firstFileName] ? row[fileData.firstFileName] : -- }}/template/vxe-columnvxe-column min-width111 title大小template #default{ row }{{ row.size ? row.size : -- }} MB/template/vxe-columnvxe-column width100 title操作 :visiblefileData.goBeyondTable.length 1template #default{ $rowIndex, row }el-tooltipcontent移除placementtop:hide-after0a classicon_delete f-s18hrefjavscript:clickhandleDelete($rowIndex, row)/a/el-tooltip/template/vxe-column/vxe-table/div!-- 插槽可使用外部传入 --template v-elseslot nameerrorTable/slot/template/divTag classm-t12v-ifdata.fileSize:contentfileData.step 1 ? 上传文件总大小不能超过 ${data.fileSize} MB : 上传文件总大小不能超过 ${data.fileSize} MB当前文件总大小 ${fileData.allSize} MB/template #footer v-if!autoUpload || fileData.step 2div classdialog-footerel-button clickcloseDialogFn取消/el-button!-- 默认使用内部提交逻辑 --template v-if!openSlotel-button typeprimary clickhandleUploadToServer(true){{ fileData.step 1 ? 上传 : 提交 }}/el-button/template!-- 开启插槽则使用外部自定义 --template v-elseslot namefooterBtn/slot/template/div/template/el-dialog/div
/template 二、js 代码
目前支持校验上传的文件类型有三种
image图片类型video视频类型excel表格类型
这块主要思路是将文件夹判断后进行递归获取出文件夹中的文件出来最后类似单个文件上传然后将文件流进行遍历 append 进创建的 FormData 对象。具体方法看readFiles() 和 handleUploadToServer()
script langts setup
import { reactive, ref, getCurrentInstance } from vue;const { proxy }: any getCurrentInstance();
const $tool proxy.$tool;const props defineProps({// 组件参数配置data: {type: Object,default: () ({// fileSize: 100, // 文件大小限制/*** type 对象为空或者不传则不限制上传类型 */// type: { // 自定义上传的文件类型 image图片类型video视频类型excel表格类型// image: [png, jpg, jpeg],// video: [mp4, avi, mov],// excel: [xlsx, xls]// },/*** 格式错误自定义提示根据 type 来但格式需要写成 ${type中的key}因组件内部代码采用查找 ${} 进行替换不传则使用组件内默认提示 */// formatMessage: {// image: 只支持上传图片${image} 格式,// image,video: 支持上传图片${image} 格式视频${video} 格式,// image,video,excel: 支持上传图片${image} 格式视频${video} 格式Excel文件${excel} 格式// }// limit: 0, // 允许上传文件的最大数量0或不传默认无限制// fileName: , // 超出后列表展示的文件名不传默认为SPU}),},// 是否支持窗口拖拽默认truedraggable: {type: Boolean,default: true},// 是否自动上传文件默认trueautoUpload: {type: Boolean,default: true},// 是否支持打开 file 单文件上传不传默认falsesingleFile: {type: Boolean,default: false},//是否需要开启列表上传失败 和 提交按钮插槽默认不开启展示组件内的失败列表 和 提交逻辑openSlot: {type: Boolean,default: false}
});/*** param dragUploadAxiosFn 上传参数抛出外部做处理不与组件内部逻辑耦合* param dragUploadErrorTable 超出限制后展示的列表插槽抛出外部做处理不与组件内部逻辑耦合*/
const emit defineEmits([dragUploadAxiosFn, dragUploadErrorTable]);const fileUploadRef ref();
const fileUploadFloderRef ref();const fileData: any reactive({step: 1, // 步骤 1文件拖拽上传2文件超出提示uploadList: [], //上传的文件列表waitUploadList: [], //存储待上传的文件列表fileSizeList: [], //存储遍历出来文件里面所有的图片路径及大小goBeyondTable: [], //超出限制后将遍历项还原成文件夹项展示的列表allSize: 0, //文件总大小 MBfirstFileName: pathName0 //第一列字段key
});/*文件上传input*/
const toUploadFile () {fileUploadRef.value.click();
};/*文件夹上传input*/
const toUploadFloder () {fileUploadFloderRef.value.click();
};/*选择文件改变*/
const handleFileChange (e: any) {if (e.target.files) {let filesList: any Array.from(e.target.files);filesList.forEach((item: any) {let size item.size / 1024 / 1024;fileData.allSize size;let obj: any getPath(item.name);changeFileSizeList(item, obj);});fileData.allSize fileData.allSize.toFixed(2); // 文件总大小 MBfileData.waitUploadList filesList;if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器}
};/*文件夹目录上传*/
const handleFloderChange (e: any) {if (e.target.files) {let filesList: any Array.from(e.target.files);filesList.forEach((item: any) { let size item.size / 1024 / 1024;fileData.allSize size;let obj: any getPath(item.webkitRelativePath); // 通过路径获取名称方法changeFileSizeList(item, obj);});filesList.reverse(); // 反转数组保证最先选择的文件排在最后面fileData.allSize fileData.allSize.toFixed(2); // 文件总大小 MBfileData.waitUploadList filesList;if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器}
};// 拖放进入目标区域
const handleDragOver (event) {event.preventDefault();
};// 拖拽放置
const handleDrop async (event) {event.preventDefault();const files [];const promises: any[] [];for (const item of event.dataTransfer.items) {const entry: any item.webkitGetAsEntry();if (!entry.isDirectory !props.singleFile) {proxy.$message.error(只支持文件夹上传不支持单个文件上传);return;}promises.push(readFiles(entry));}const resultFilesArrays await Promise.all(promises); // 等待所有文件读取完成fileData.waitUploadList resultFilesArrays.flat();if (!fileLimitFn(fileData.fileSizeList)) return; // 校验方法handleUploadToServer(); //上传文件到服务器
};//文件各种限制判断方法封装
const fileLimitFn (fileSizeList: any) {//文件类型判断和格式限制if (props.data.type) {if (!testingFileType(fileSizeList)) return false;}//文件数量超出限制if (props.data.limit props.data.limit ! 0) {if (!fileLimit(fileSizeList)) return false;}//文件大小超出限制if (props.data.fileSize) {if (!fileSizeLimit(fileSizeList)) { fileData.goBeyondTable getGoBeyondTable(fileSizeList);fileData.step 2;return false;}}return true;
};/*** 验证文件类型是否为允许的上传的类型* param fileSizeList 文件列表* param type image图片类型video视频类型excel表格类型* param formatMessage 外部传入的自定义提示信息替换默认提示信息或者新增进入提示信息* returns 如果文件类型不符合要求返回 false否则返回 true*/
const testingFileType (fileSizeList: any) {let type: any props.data.type;if (Object.keys(type).length 0) return true; // type 对象为空或者不传则不限制上传类型// 使用 Object.values 获取对象值的数组然后使用 flatMap 合并所有数组const typeList Object.values(type).flatMap((array: any) array);// 获取所有键的字符串表示例如 image,video,excelconst keysString Object.keys(type).join(,); const messageTemplates {image: 只支持上传图片${image} 格式,video: 只支持上传视频${video} 格式,excel: 只支持上传Excel文件${excel} 格式,image,video: 支持上传图片${image} 格式视频${video} 格式,image,excel: 支持上传图片${image} 格式Excel文件${excel} 格式,video,excel: 支持上传视频${video} 格式Excel文件${excel} 格式,image,video,excel: 支持上传图片${image} 格式视频${video} 格式Excel文件${excel} 格式};// 外部传入自定义提示信息替换成外部传入的if (props.data.formatMessage Object.keys(props.data.formatMessage).length) {let msg: any props.data.formatMessage;for (const key in msg) {if (messageTemplates.hasOwnProperty(key)) {// 如果 messageTemplates 中存在该键则替换其值messageTemplates[key] msg[key];} else {// 不存在则增加进去messageTemplates[key] msg[key];}}}// 正则查找 ${}后进行替换let message: string ;if (messageTemplates[keysString]) {message messageTemplates[keysString].replace(/\$\{(\w)\}/g, (_, match) type[match].join(, ));} else {message 不支持上传该类型的文件${keysString}请重新上传;}//过滤不符合要求的文件类型let filterList: any fileSizeList.filter((item: any) !typeList.includes(item.type));if (filterList.length) {//错误的文件不管里面有没有正确格式的一律清除fileData.fileSizeList [];fileData.allSize 0;proxy.$message.error(message);return false;};return true;
};//文件数量超出限制
const fileLimit (fileSizeList: any) {if (fileSizeList.length props.data.limit) {proxy.$message.error(文件数量不能超过 ${props.data.limit} 个请重新上传);fileData.fileSizeList [];return false;}return true;
};//文件超出限制
const fileSizeLimit (fileSizeList: any) {let allSize fileSizeList.reduce((accumulator, currentValue) {if (currentValue) {return accumulator currentValue.size;}return accumulator;}, 0);let fileSize props.data.fileSize * 1024 * 1024;fileData.allSize (allSize / 1024 / 1024).toFixed(2); //存储文件总大小 MBif (allSize fileSize) {proxy.$message.error(文件总大小不能超过 ${props.data.fileSize} MB请重新上传);emit(dragUploadErrorTable, fileData);return false;}return true;
};// 操作数据文件超出后展示的列表
const getGoBeyondTable (fileSizeList: any) {// 遍历相同第一列为一项size累加let result: any fileSizeList.reduce((accumulator, current) {if (accumulator[current[fileData.firstFileName]]) { //如果已经存在则累加sizeaccumulator[current[fileData.firstFileName]].size current.size;} else {accumulator[current[fileData.firstFileName]] { ...current };}return accumulator;}, {});// 将结果对象转换回数组result Object.values(result);// 处理size为MB单位result.forEach((item: any) {item.size (item.size / 1024 / 1024).toFixed(2);});return result;
};//移除超出文件列表的项
const handleDelete (rowIndex: number, row: any) {fileData.goBeyondTable.splice(rowIndex, 1);fileData.allSize (fileData.allSize - row.size).toFixed(2); //更新总大小MBfileData.fileSizeList fileData.fileSizeList.filter((item: any) item[fileData.firstFileName] ! row[fileData.firstFileName]);fileData.waitUploadList fileData.waitUploadList.filter((item: any) item[fileData.firstFileName] ! row[fileData.firstFileName]);
};/*请求上传到服务器*/
const handleUploadToServer (click?: boolean) {let autoUpload: boolean props.autoUpload;if (click fileData.step 1) autoUpload true; //手动上传if (!autoUpload fileData.step 1) return; //不自动上传则不执行fileData.uploadList fileData.waitUploadList;// 再次提交时验证文件大小是否超出限制if (click fileData.step 2 fileData.allSize props.data.fileSize) {proxy.$message.error(文件总大小不能超过 ${props.data.fileSize} MB);return;}let formData new FormData();fileData.uploadList.forEach((item: any) {formData.append(${item.filePathName}, item);});// // 遍历FormData对象并打印其内容查看FormData对象数据是否正确// for (let [key, value] of formData.entries()) {// console.log(${key}: ${value});// }emit(dragUploadAxiosFn, formData); //上传参数抛出外部做操作不在组件做耦合
};//此方法如果是文件夹则会递归调用自己所以最后都会走 else 的逻辑
const readFiles async (item: any) {if (item.isDirectory) {// 是一个文件夹const directoryReader item.createReader();// readEntries是一个异步方法const entries: any[] await new Promise((resolve, reject) {directoryReader.readEntries(resolve, reject);});let files [];for (const entry of entries) {const resultFiles: any await readFiles(entry);files files.concat(resultFiles);}return files;} else {// file也是一个异步方法const file await new Promise((resolve, reject) {item.file(resolve, reject);});let obj: any getPath(item.fullPath); //通过路径获取名称方法changeFileSizeList(file, obj);return [file];}
};//更改 fileData.fileSizeList 的值公共
const changeFileSizeList (file: any, obj: any) {try {file.filePathName obj.filePathName; //添加路径名称file[fileData.firstFileName] obj.pathObj[fileData.firstFileName]; //添加第一列文件名file.pathObj obj.pathObj;let index file.name.lastIndexOf(.);fileData.fileSizeList.push({ //添加图片路径、大小、名称filePathName: obj.filePathName,size: file.size,type: file.name.substring(index 1),...obj.pathObj});} catch {proxy.$message.error(文件读取路径失败请重新上传文件);}
};//通过路径获取名称方法公共
const getPath (path: string) {try {let filePathName: any path; // 传给后端的全路径if (path.startsWith(/)) { // 如果路径以斜杠开头则删除第一个斜杠filePathName path.slice(1);}let parts filePathName.split(/); // 路径分割成数组let pathObj {}; // 存储每个部分for (let i 0; i parts.length; i) {if (parts[i] ! ) { // 跳过空字符串如果路径以 / 开头或结尾pathObj[pathName (i)] parts[i];}}return {filePathName: filePathName,pathObj: pathObj}} catch {proxy.$message.error(文件读取路径失败请重新上传文件);}
};//关闭事件
const closeDialogFn () {if (fileData.step 1) {props.data.visible false; //关闭弹窗return;}proxy.$messageBox({title: 关闭,message: 关闭后不会保留您所做的更改确定关闭吗,callback: (value: string) {//confirm确认cancel取消if (value confirm) {fileData.step 1;props.data.visible false; //关闭弹窗}}});
};
/script
三、css 代码
style langscss scoped
.drag-box {position: relative;.progress-bar {position: absolute;z-index: 100;width: 100%;top: 0;left: 0px;right: 0px;bottom: -5px;display: flex;justify-content: center;align-items: center;background-color: rgba(255, 255, 255, 0.8);:deep(.el-progress.el-progress--line) {width: 100%;margin-left: 10px;}}.uploaded-list-wrap {max-height: 200px;overflow-y: auto;.uploaded-item {display: flex;justify-content: space-between;align-items: center;cursor: pointer;margin-bottom: 3px;.text-content {width: 80%;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;}.icon {width: 25px;height: 25px;}.success-icon {display: block;}.delete-icon {display: none;}:hover {.success-icon {display: none;}.delete-icon {display: block;}}}}
}.div-text {width: 100%;height: 250px;border: 1px dashed #409effc2;;border-radius: 10px;box-sizing: border-box;display: flex;flex-direction: column;justify-content: center;align-items: center;font-size: 18px;background-color: #cccccc1c;.click-txt {color: #409effc2;;cursor: pointer;}.btn-wrap {margin-top: 20px;}
}.min-h311 {min-height: 311px;
}.max-h311 {max-height: 311px;
}:deep(.el-dialog .el-dialog__header) {padding: 12px 30px;display: flex;justify-content: space-between;
}:deep(.el-dialog .el-dialog__body) {padding: 8px 30px 11px;
}:deep(.el-dialog .el-dialog__footer) {padding: 6px 30px 14px;
}.dialog-footer {display: flex;justify-content: flex-end;
}.color-409 {color: #409effc2;;
}/style
四、vue 页面中使用
!-- 拖拽上传 --
DragUpload v-ifdragUpload.visible:datadragUploaddragUploadAxiosFndragUploadAxiosFn
/
const dragUpload: any reactive({visible: false,fileSize: 100, // 单位字节 MBtype: { //定义上传的文件类型 image图片类型video视频类型excel表格类型image: [png, jpg, jpeg],},formatMessage: {image: 自定义外部传入${image} 格式,},fileName: SPU // 超出后列表展示的文件名
}); 五、上传到后端接口的参数 六、效果图如下 七、额外补充后端接收文件流的方法