mirror of
https://github.com/pure-admin/vue-pure-admin.git
synced 2025-06-06 16:37:18 +08:00
feat: 添加文件上传示例
This commit is contained in:
parent
9b5745a9a1
commit
0887dd46d5
@ -64,6 +64,7 @@ menus:
|
||||
hsStatistic: Statistic
|
||||
hsCollapse: Collapse
|
||||
hsProgress: Progress
|
||||
hsUpload: File Upload
|
||||
hsmenus: MultiLevel Menu
|
||||
hsmenu1: Menu1
|
||||
hsmenu1-1: Menu1-1
|
||||
|
@ -64,6 +64,7 @@ menus:
|
||||
hsStatistic: 统计组件
|
||||
hsCollapse: 折叠面板
|
||||
hsProgress: 进度条
|
||||
hsUpload: 文件上传
|
||||
hsmenus: 多级菜单
|
||||
hsmenu1: 菜单1
|
||||
hsmenu1-1: 菜单1-1
|
||||
|
16
package.json
16
package.json
@ -54,8 +54,8 @@
|
||||
"@logicflow/extension": "^1.2.19",
|
||||
"@pureadmin/descriptions": "^1.2.0",
|
||||
"@pureadmin/table": "^3.0.1",
|
||||
"@pureadmin/utils": "^2.4.0",
|
||||
"@vueuse/core": "^10.7.1",
|
||||
"@pureadmin/utils": "^2.4.3",
|
||||
"@vueuse/core": "^10.7.2",
|
||||
"@vueuse/motion": "^2.0.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
@ -77,20 +77,20 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"path": "^0.12.7",
|
||||
"pinia": "^2.1.7",
|
||||
"pinyin-pro": "^3.19.0",
|
||||
"pinyin-pro": "^3.19.3",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"responsive-storage": "^2.2.0",
|
||||
"sortablejs": "^1.15.1",
|
||||
"sortablejs": "^1.15.2",
|
||||
"swiper": "^11.0.5",
|
||||
"typeit": "8.7.1",
|
||||
"v-contextmenu": "3.0.0",
|
||||
"v3-infinite-loading": "^1.3.1",
|
||||
"version-rocket": "^1.7.1",
|
||||
"vue": "^3.4.10",
|
||||
"vue": "3.4.14",
|
||||
"vue-i18n": "^9.9.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-tippy": "^6.4.1",
|
||||
"vue-types": "^5.1.1",
|
||||
@ -153,10 +153,10 @@
|
||||
"svgo": "^3.2.0",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.11",
|
||||
"vite": "^5.0.12",
|
||||
"vite-plugin-cdn-import": "^0.3.5",
|
||||
"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-router-warn": "^1.0.0",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
|
691
pnpm-lock.yaml
generated
691
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -9,3 +9,17 @@ type Result = {
|
||||
export const mapJson = (params?: object) => {
|
||||
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"
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -26,6 +26,15 @@ export default {
|
||||
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",
|
||||
name: "DatePicker",
|
||||
|
@ -7,35 +7,10 @@
|
||||
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 {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.el-range-separator {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.is-dark {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
@ -198,7 +198,7 @@ watch(size, val =>
|
||||
@change="value1 = ''"
|
||||
>
|
||||
<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-group>
|
||||
<br />
|
||||
|
94
src/views/components/upload/form.vue
Normal file
94
src/views/components/upload/form.vue
Normal 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>
|
BIN
src/views/components/upload/imgs/1.jpg
Normal file
BIN
src/views/components/upload/imgs/1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 287 KiB |
BIN
src/views/components/upload/imgs/2.jpg
Normal file
BIN
src/views/components/upload/imgs/2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
BIN
src/views/components/upload/imgs/3.jpg
Normal file
BIN
src/views/components/upload/imgs/3.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 174 KiB |
313
src/views/components/upload/index.vue
Normal file
313
src/views/components/upload/index.vue
Normal 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"
|
||||
>
|
||||
Mocky
|
||||
</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>
|
Loading…
x
Reference in New Issue
Block a user