feat: 添加文件上传示例

This commit is contained in:
xiaoxian521 2024-01-21 23:14:46 +08:00
parent 9b5745a9a1
commit 0887dd46d5
13 changed files with 807 additions and 359 deletions

View File

@ -64,6 +64,7 @@ menus:
hsStatistic: Statistic hsStatistic: Statistic
hsCollapse: Collapse hsCollapse: Collapse
hsProgress: Progress hsProgress: Progress
hsUpload: File Upload
hsmenus: MultiLevel Menu hsmenus: MultiLevel Menu
hsmenu1: Menu1 hsmenu1: Menu1
hsmenu1-1: Menu1-1 hsmenu1-1: Menu1-1

View File

@ -64,6 +64,7 @@ menus:
hsStatistic: 统计组件 hsStatistic: 统计组件
hsCollapse: 折叠面板 hsCollapse: 折叠面板
hsProgress: 进度条 hsProgress: 进度条
hsUpload: 文件上传
hsmenus: 多级菜单 hsmenus: 多级菜单
hsmenu1: 菜单1 hsmenu1: 菜单1
hsmenu1-1: 菜单1-1 hsmenu1-1: 菜单1-1

View File

@ -54,8 +54,8 @@
"@logicflow/extension": "^1.2.19", "@logicflow/extension": "^1.2.19",
"@pureadmin/descriptions": "^1.2.0", "@pureadmin/descriptions": "^1.2.0",
"@pureadmin/table": "^3.0.1", "@pureadmin/table": "^3.0.1",
"@pureadmin/utils": "^2.4.0", "@pureadmin/utils": "^2.4.3",
"@vueuse/core": "^10.7.1", "@vueuse/core": "^10.7.2",
"@vueuse/motion": "^2.0.0", "@vueuse/motion": "^2.0.0",
"@wangeditor/editor": "^5.1.23", "@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12", "@wangeditor/editor-for-vue": "^5.1.12",
@ -77,20 +77,20 @@
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"path": "^0.12.7", "path": "^0.12.7",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"pinyin-pro": "^3.19.0", "pinyin-pro": "^3.19.3",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qs": "^6.11.2", "qs": "^6.11.2",
"responsive-storage": "^2.2.0", "responsive-storage": "^2.2.0",
"sortablejs": "^1.15.1", "sortablejs": "^1.15.2",
"swiper": "^11.0.5", "swiper": "^11.0.5",
"typeit": "8.7.1", "typeit": "8.7.1",
"v-contextmenu": "3.0.0", "v-contextmenu": "3.0.0",
"v3-infinite-loading": "^1.3.1", "v3-infinite-loading": "^1.3.1",
"version-rocket": "^1.7.1", "version-rocket": "^1.7.1",
"vue": "^3.4.10", "vue": "3.4.14",
"vue-i18n": "^9.9.0", "vue-i18n": "^9.9.0",
"vue-json-pretty": "^2.3.0", "vue-json-pretty": "^2.3.0",
"vue-pdf-embed": "^1.2.1", "vue-pdf-embed": "1.2.1",
"vue-router": "^4.2.5", "vue-router": "^4.2.5",
"vue-tippy": "^6.4.1", "vue-tippy": "^6.4.1",
"vue-types": "^5.1.1", "vue-types": "^5.1.1",
@ -153,10 +153,10 @@
"svgo": "^3.2.0", "svgo": "^3.2.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.11", "vite": "^5.0.12",
"vite-plugin-cdn-import": "^0.3.5", "vite-plugin-cdn-import": "^0.3.5",
"vite-plugin-compression": "^0.5.1", "vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "2.0.0", "vite-plugin-fake-server": "^2.1.1",
"vite-plugin-remove-console": "^2.2.0", "vite-plugin-remove-console": "^2.2.0",
"vite-plugin-router-warn": "^1.0.0", "vite-plugin-router-warn": "^1.0.0",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",

691
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -9,3 +9,17 @@ type Result = {
export const mapJson = (params?: object) => { export const mapJson = (params?: object) => {
return http.request<Result>("get", "/get-map-info", { params }); return http.request<Result>("get", "/get-map-info", { params });
}; };
/** 文件上传 */
export const formUpload = data => {
return http.request<Result>(
"post",
"https://run.mocky.io/v3/3aa761d7-b0b3-4a03-96b3-6168d4f7467b",
{ data },
{
headers: {
"Content-Type": "multipart/form-data"
}
}
);
};

View File

@ -26,6 +26,15 @@ export default {
title: $t("menus.hsmessage") title: $t("menus.hsmessage")
} }
}, },
{
path: "/components/upload",
name: "PureUpload",
component: () => import("@/views/components/upload/index.vue"),
meta: {
title: $t("menus.hsUpload"),
extraIcon: "IF-pure-iconfont-new svg"
}
},
{ {
path: "/components/date-picker", path: "/components/date-picker",
name: "DatePicker", name: "DatePicker",

View File

@ -7,35 +7,10 @@
font-weight: 400 !important; font-weight: 400 !important;
} }
.el-upload {
input[type="file"] {
display: none !important;
}
}
.el-upload__input {
display: none;
}
.upload-container {
.el-upload {
width: 100%;
.el-upload-dragger {
width: 100%;
height: 200px;
}
}
}
.el-dropdown-menu { .el-dropdown-menu {
padding: 0 !important; padding: 0 !important;
} }
.el-range-separator {
box-sizing: content-box;
}
.is-dark { .is-dark {
z-index: 9999 !important; z-index: 9999 !important;
} }

View File

@ -198,7 +198,7 @@ watch(size, val =>
@change="value1 = ''" @change="value1 = ''"
> >
<el-radio label="">Date</el-radio> <el-radio label="">Date</el-radio>
<el-radio label="YYYY-MM-DD h:m:s a">年月日 时分秒</el-radio> <el-radio label="YYYY-MM-DD HH:mm:ss">年月日 时分秒</el-radio>
<el-radio label="x">时间戳</el-radio> <el-radio label="x">时间戳</el-radio>
</el-radio-group> </el-radio-group>
<br /> <br />

View File

@ -0,0 +1,94 @@
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { formUpload } from "@/api/mock";
import { message } from "@/utils/message";
import { createFormData } from "@pureadmin/utils";
import UploadIcon from "@iconify-icons/ri/upload-2-line";
const formRef = ref();
const uploadRef = ref();
const validateForm = reactive({
fileList: [],
date: ""
});
const submitForm = formEl => {
if (!formEl) return;
formEl.validate(valid => {
if (valid) {
// file
const formData = createFormData({
files: validateForm.fileList.map(file => ({ raw: file.raw })), // file
date: validateForm.date //
});
formUpload(formData)
.then(({ success }) => {
if (success) {
message("提交成功", { type: "success" });
} else {
message("提交失败");
}
})
.catch(error => {
message(`提交异常 ${error}`, { type: "error" });
});
} else {
return false;
}
});
};
const resetForm = formEl => {
if (!formEl) return;
formEl.resetFields();
};
</script>
<template>
<el-form ref="formRef" :model="validateForm" label-width="82px">
<el-form-item
label="附件"
prop="fileList"
:rules="[{ required: true, message: '附件不能为空' }]"
>
<el-upload
ref="uploadRef"
v-model:file-list="validateForm.fileList"
drag
multiple
action="#"
class="!w-[200px]"
:auto-upload="false"
>
<div class="el-upload__text">
<IconifyIconOffline
:icon="UploadIcon"
width="26"
class="m-auto mb-2"
/>
可点击或拖拽上传
</div>
</el-upload>
</el-form-item>
<el-form-item
label="日期"
prop="date"
:rules="[{ required: true, message: '日期不能为空' }]"
>
<el-date-picker
v-model="validateForm.date"
type="datetime"
class="!w-[200px]"
placeholder="请选择日期时间"
value-format="YYYY-MM-DD HH:mm:ss"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" text bg @click="submitForm(formRef)">
提交
</el-button>
<el-button text bg @click="resetForm(formRef)">重置</el-button>
</el-form-item>
</el-form>
</template>

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -0,0 +1,313 @@
<script setup lang="ts">
import axios from "axios";
import Sortable from "sortablejs";
import UploadForm from "./form.vue";
import { ref, computed } from "vue";
import { useRouter } from "vue-router";
import { message } from "@/utils/message";
import type { UploadFile } from "element-plus";
import { getKeyList, extractFields, downloadByData } from "@pureadmin/utils";
import Add from "@iconify-icons/ep/plus";
import Eye from "@iconify-icons/ri/eye-line";
import Delete from "@iconify-icons/ri/delete-bin-7-line";
defineOptions({
name: "PureUpload"
});
const fileList = ref([]);
const router = useRouter();
const curOpenImgIndex = ref(0);
const dialogVisible = ref(false);
const urlList = computed(() => getKeyList(fileList.value, "url"));
const imgInfos = computed(() => extractFields(fileList.value, "name", "size"));
const getImgUrl = name => new URL(`./imgs/${name}.jpg`, import.meta.url).href;
const srcList = Array.from({ length: 3 }).map((_, index) => {
return getImgUrl(index + 1);
});
/** 上传文件前校验 */
const onBefore = file => {
if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
message("只能上传图片");
return false;
}
const isExceed = file.size / 1024 / 1024 > 2;
if (isExceed) {
message(`单个图片大小不能超过2MB`);
return false;
}
};
/** 超出最大上传数时触发 */
const onExceed = () => {
message("最多上传3张图片请先删除在上传");
};
/** 移除上传的文件 */
const handleRemove = (file: UploadFile) => {
fileList.value.splice(fileList.value.indexOf(file), 1);
};
/** 大图预览 */
const handlePictureCardPreview = (file: UploadFile) => {
curOpenImgIndex.value = fileList.value.findIndex(img => img.uid === file.uid);
dialogVisible.value = true;
};
const getUploadItem = () => document.querySelectorAll("#pure-upload-item");
/** 缩略图拖拽排序 */
const imgDrop = uid => {
const CLASSNAME = "el-upload-list";
const _curIndex = fileList.value.findIndex(img => img.uid === uid);
getUploadItem()?.[_curIndex]?.classList?.add(`${CLASSNAME}__item-actions`);
const wrapper: HTMLElement = document.querySelector(`.${CLASSNAME}`);
Sortable.create(wrapper, {
handle: `.${CLASSNAME}__item`,
onEnd: ({ newIndex, oldIndex }) => {
const oldFile = fileList.value[oldIndex];
fileList.value.splice(oldIndex, 1);
fileList.value.splice(newIndex, 0, oldFile);
// fix: https://github.com/SortableJS/Sortable/issues/232 (firefox is ok, but chromium is bad. see https://bugs.chromium.org/p/chromium/issues/detail?id=410328)
getUploadItem().forEach(ele => {
ele.classList.remove(`${CLASSNAME}__item-actions`);
});
}
});
};
/** 下载图片 */
const onDownload = () => {
[
{ name: "巴旦木.jpeg", type: "img" },
{ name: "恭喜发财.png", type: "img" },
{ name: "可爱动物.gif", type: "gif" },
{ name: "pure-upload.csv", type: "other" },
{ name: "pure-upload.txt", type: "other" }
].forEach(img => {
axios
.get(`https://xiaoxian521.github.io/hyperlink/${img.type}/${img.name}`, {
responseType: "blob"
})
.then(({ data }) => {
downloadByData(data, img.name);
});
});
};
</script>
<template>
<el-card shadow="never">
<template #header>
<div class="card-header">
<el-link
v-tippy="{
content: '点击查看详细文档'
}"
href="https://element-plus.org/zh-CN/component/upload.html"
target="_blank"
style="font-size: 16px; font-weight: 800"
>
文件上传
</el-link>
<span class="header-right">
<el-button class="ml-1" text bg @click="onDownload">
点击下载安全文件进行上传测试
</el-button>
</span>
</div>
</template>
<p class="mb-4">
综合示例<span class="text-[14px]">
<span class="text-[red]">自动上传</span>
拖拽上传拖拽排序设置请求头上传进度大图预览多选文件最大文件数量文件类型限制文件大小限制删除文件
</span>
</p>
<p v-show="fileList.length > 0" class="mb-4">
{{ imgInfos }}
</p>
<el-upload
v-model:file-list="fileList"
drag
multiple
class="pure-upload"
list-type="picture-card"
accept="image/jpeg,image/png,image/gif"
action="https://run.mocky.io/v3/3aa761d7-b0b3-4a03-96b3-6168d4f7467b"
:limit="3"
:headers="{ Authorization: 'eyJhbGciOiJIUzUxMiJ9.admin' }"
:on-exceed="onExceed"
:before-upload="onBefore"
>
<IconifyIconOffline :icon="Add" class="m-auto mt-4" width="30" />
<template #file="{ file }">
<div
v-if="file.status == 'ready' || file.status == 'uploading'"
class="mt-[35%] m-auto"
>
<p class="font-medium">文件上传中</p>
<el-progress
class="mt-2"
:stroke-width="2"
:text-inside="true"
:show-text="false"
:percentage="file.percentage"
/>
</div>
<div v-else @mouseenter.stop="imgDrop(file.uid)">
<img
class="el-upload-list__item-thumbnail select-none"
:src="file.url"
/>
<span
id="pure-upload-item"
:class="[
'el-upload-list__item-actions',
fileList.length > 1 && '!cursor-move'
]"
>
<span
title="查看"
class="hover:text-primary"
@click="handlePictureCardPreview(file)"
>
<IconifyIconOffline
:icon="Eye"
class="hover:scale-125 duration-100"
/>
</span>
<span
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<span title="移除" class="hover:text-[var(--el-color-danger)]">
<IconifyIconOffline
:icon="Delete"
class="hover:scale-125 duration-100"
/>
</span>
</span>
</span>
</div>
</template>
</el-upload>
<!-- 有时文档没写并不代表没有多看源码好处多多😝 https://github.com/element-plus/element-plus/tree/dev/packages/components/image-viewer/src emm...西🥹giao -->
<el-image-viewer
v-if="dialogVisible"
:initialIndex="curOpenImgIndex"
:url-list="urlList"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
@close="dialogVisible = false"
@switch="index => (curOpenImgIndex = index)"
/>
<!-- 将自定义内容插入到body里有了它在图片预览的时候想插入个分页器或者别的东东在预览区某个位置就很方便咯用户需求可以很灵活开源组件库几乎不可能尽善尽美很多时候寻找别的解决途径或许更好 -->
<teleport to="body">
<div
v-if="fileList[curOpenImgIndex] && dialogVisible"
effect="dark"
round
size="large"
type="info"
class="img-name"
>
<p class="text-[#fff] dark:text-black">
{{ fileList[curOpenImgIndex].name }}
</p>
</div>
</teleport>
<p class="el-upload__tip">
可拖拽上传最多3张单个不超过2MB且格式为jpeg/png/gif的图片
</p>
<el-divider />
<p class="mb-4 mt-4">
结合表单校验进行<span class="text-[red]">手动上传</span>
<span class="text-[14px]">
可先打开浏览器控制台找到Network然后填写表单内容后点击点提交观察请求变化
</span>
</p>
<div class="flex justify-between">
<UploadForm />
<div>
<p class="text-center">上传接口相关截图</p>
<el-image
class="w-[200px] rounded-md"
:src="srcList[0]"
:preview-src-list="srcList"
fit="cover"
/>
</div>
</div>
<el-divider />
<div class="flex flex-wrap">
<p>
裁剪上传头像请参考
<span
class="font-bold text-[18x] cursor-pointer hover:text-[red]"
@click="router.push({ name: 'SystemUser' })"
>
系统管理-用户管理
</span>
表格操作栏中的上传头像功能
</p>
<p class="text-[red] text-[12px] flex flex-auto items-center justify-end">
免责声明上传接口使用免费开源的
<el-link
href="https://designer.mocky.io/"
target="_blank"
style="font-size: 16px; font-weight: 800"
>
&nbsp;Mocky&nbsp;
</el-link>
<span class="font-bold text-[18x]"> 请不要上传重要信息 </span
>如果造成任何损失我们概不负责
</p>
</div>
</el-card>
</template>
<style lang="scss" scoped>
:deep(.card-header) {
display: flex;
.header-right {
display: flex;
flex: auto;
align-items: center;
justify-content: flex-end;
font-size: 14px;
}
}
:deep(.pure-upload) {
.el-upload-dragger {
background-color: transparent;
border: none;
}
}
.img-name {
position: absolute;
bottom: 80px;
left: 50%;
z-index: 4000;
padding: 5px 23px;
background-color: var(--el-text-color-regular);
border-radius: 22px;
transform: translateX(-50%);
/** left: 50%; bottom: 80px; transform: translateX(-50%);
* 解开下面 left: 40px; top: 40px; 注释体验不一样的感觉还是差强人意自己调整位置吧🥹
*/
// left: 40px;
// top: 40px;
}
</style>