更新 upload 插件

This commit is contained in:
神仙都没用 2024-02-05 18:18:56 +08:00
parent 52e345712e
commit d9b2405184
7 changed files with 226 additions and 215 deletions

View File

@ -111,7 +111,7 @@ import { ZoomIn, Delete, VideoPause, VideoPlay } from "@element-plus/icons-vue";
import { ContextMenu } from "@cool-vue/crud"; import { ContextMenu } from "@cool-vue/crud";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { extname } from "/@/cool/utils"; import { extname } from "/@/cool/utils";
import { download, fileName, getRule } from "../../utils"; import { fileName, getRule } from "../../utils";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
import Viewer from "./viewer.vue"; import Viewer from "./viewer.vue";
@ -172,16 +172,6 @@ function onContextMenu(e: any) {
done(); done();
} }
}, },
{
label: "下载",
callback(done) {
if (props.item.url) {
download(props.item.url);
}
done();
}
},
{ {
label: "复制链接", label: "复制链接",
callback(done) { callback(done) {

View File

@ -11,34 +11,18 @@
</div> </div>
<!-- 文档 --> <!-- 文档 -->
<cl-dialog <cl-dialog v-model="doc.visible" title="文档预览" height="70vh" width="80%" :scrollbar="false">
v-model="doc.visible"
title="文档预览"
height="70vh"
width="80%"
:scrollbar="false"
:controls="['slot-download', 'fullscreen', 'close']"
>
<template #slot-download>
<button type="button" class="cl-dialog__controls-icon" @click="download(doc.url)">
<el-icon>
<icon-download />
</el-icon>
</button>
</template>
<div class="viewer-doc" v-loading="doc.loading"> <div class="viewer-doc" v-loading="doc.loading">
<iframe :src="doc.previewUrl" :ref="setRefs('docIframe')" /> <iframe :src="doc.url" :ref="setRefs('docIframe')" />
</div> </div>
</cl-dialog> </cl-dialog>
</template> </template>
<script lang="ts" setup name="file-viewer"> <script lang="ts" setup name="file-viewer">
import { reactive, nextTick } from "vue"; import { reactive, nextTick } from "vue";
import { getType, download } from "../../utils"; import { getType } from "../../utils";
import type { Upload } from "../../types"; import type { Upload } from "../../types";
import { useCool } from "/@/cool"; import { useCool } from "/@/cool";
import { Download as IconDownload } from "@element-plus/icons-vue";
const { refs, setRefs } = useCool(); const { refs, setRefs } = useCool();
@ -52,8 +36,7 @@ const img = reactive({
const doc = reactive({ const doc = reactive({
visible: false, visible: false,
loading: false, loading: false,
url: "", url: ""
previewUrl: ""
}); });
// //
@ -77,8 +60,7 @@ function open(item: Upload.Item) {
if (["word", "excel", "ppt", "pdf"].includes(type)) { if (["word", "excel", "ppt", "pdf"].includes(type)) {
doc.visible = true; doc.visible = true;
doc.loading = true; doc.loading = true;
doc.previewUrl = `https://view.officeapps.live.com/op/view.aspx?src=${decodeURIComponent(url)}`; doc.url = `https://view.officeapps.live.com/op/view.aspx?src=${decodeURIComponent(url)}`;
doc.url = url;
nextTick(() => { nextTick(() => {
refs.docIframe.onload = () => { refs.docIframe.onload = () => {

View File

@ -22,7 +22,7 @@
:http-request="httpRequest" :http-request="httpRequest"
:headers="headers" :headers="headers"
:multiple="multiple" :multiple="multiple"
:disabled="uploadDisabled" :disabled="disabled"
> >
<slot> <slot>
<el-button type="success">{{ text }}</el-button> <el-button type="success">{{ text }}</el-button>
@ -61,7 +61,7 @@
:http-request="httpRequest" :http-request="httpRequest"
:headers="headers" :headers="headers"
:multiple="multiple" :multiple="multiple"
:disabled="uploadDisabled" :disabled="disabled"
> >
<slot> <slot>
<div class="cl-upload__demo is-dragger" v-if="drag"> <div class="cl-upload__demo is-dragger" v-if="drag">
@ -103,7 +103,7 @@
} }
" "
:headers="headers" :headers="headers"
:disabled="uploadDisabled" :disabled="disabled"
v-if="showFileList" v-if="showFileList"
> >
<slot name="item" :item="item" :index="index"> <slot name="item" :item="item" :index="index">
@ -127,15 +127,15 @@
<script lang="ts" setup name="cl-upload"> <script lang="ts" setup name="cl-upload">
import { computed, ref, watch, type PropType, nextTick } from "vue"; import { computed, ref, watch, type PropType, nextTick } from "vue";
import { isArray, isNumber } from "lodash-es"; import { isArray, isNumber } from "lodash-es";
import { type AxiosProgressEvent } from "axios";
import Draggable from "vuedraggable"; import Draggable from "vuedraggable";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { PictureFilled, UploadFilled } from "@element-plus/icons-vue"; import { PictureFilled, UploadFilled } from "@element-plus/icons-vue";
import { useForm } from "@cool-vue/crud"; import { useForm } from "@cool-vue/crud";
import { useCool, module } from "/@/cool"; import { useCool } from "/@/cool";
import { useBase } from "/$/base"; import { useBase } from "/$/base";
import { uuid, isPromise } from "/@/cool/utils"; import { uuid, isPromise } from "/@/cool/utils";
import { getUrls, getType, pathJoin } from "../utils"; import { getUrls, getType } from "../utils";
import { useUpload } from "../hooks";
import UploadItem from "./upload-item/index.vue"; import UploadItem from "./upload-item/index.vue";
import type { Upload } from "../types"; import type { Upload } from "../types";
@ -187,15 +187,7 @@ const props = defineProps({
// //
beforeUpload: Function, beforeUpload: Function,
// //
prefixPath: { prefixPath: String,
type: String,
default: "app"
},
//
menu: {
type: String,
default: "base"
},
// CRUD穿 // CRUD穿
isEdit: Boolean, isEdit: Boolean,
@ -206,12 +198,10 @@ const props = defineProps({
const emit = defineEmits(["update:modelValue", "upload", "success", "error", "progress"]); const emit = defineEmits(["update:modelValue", "upload", "success", "error", "progress"]);
const { service, refs, setRefs } = useCool(); const { refs, setRefs } = useCool();
const { user } = useBase(); const { user } = useBase();
const Form = useForm(); const Form = useForm();
const { options, toUpload } = useUpload();
//
const { options } = module.get("upload");
// //
const size = computed(() => { const size = computed(() => {
@ -224,11 +214,6 @@ const disabled = computed(() => {
return props.isDisabled || props.disabled; return props.isDisabled || props.disabled;
}); });
//
const uploadDisabled = computed(() => {
return disabled.value;
});
// //
const limit = props.limit || options.limit.upload; const limit = props.limit || options.limit.upload;
@ -357,7 +342,7 @@ function clear() {
} }
// //
async function httpRequest(req: any, item?: any) { async function httpRequest(req: any, item?: Upload.Item) {
if (!item) { if (!item) {
item = list.value.find((e) => e.uid == req.file.uid); item = list.value.find((e) => e.uid == req.file.uid);
} }
@ -366,149 +351,23 @@ async function httpRequest(req: any, item?: any) {
return false; return false;
} }
// id //
const fileId = uuid(""); toUpload(req.file, {
prefixPath: props.prefixPath,
try { onProgress(progress) {
// item.progress = progress;
const { mode, type } = await service.base.comm.uploadMode();
//
const isLocal = mode == "local";
//
const fileName = fileId + "_" + req.file.name;
// Key
const key = isLocal ? fileName : pathJoin(props.prefixPath, props.menu, fileName);
//
return new Promise((resolve, reject) => {
//
async function next({
host,
preview,
data
}: {
host: string;
preview?: string;
data?: any;
}) {
const fd = new FormData();
// key
fd.append("key", key);
//
for (const i in data) {
if (!fd.has(i)) {
fd.append(i, data[i]);
}
}
//
fd.append("file", req.file);
//
await service
.request({
url: host,
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
Authorization: isLocal ? user.token : null
},
timeout: 600000,
data: fd,
onUploadProgress(e: AxiosProgressEvent) {
item.progress = e.total ? Math.floor((e.loaded / e.total) * 100) : 0;
emit("progress", item); emit("progress", item);
}, }
proxy: isLocal,
NProgress: false
}) })
.then((res) => { .then((res) => {
item.key = encodeURIComponent(key); Object.assign(item, res);
if (isLocal) {
item.url = res;
} else {
item.url = pathJoin(preview || host, item.key);
}
item.fileId = fileId;
emit("success", item); emit("success", item);
resolve(item.url);
update(); update();
}) })
.catch((err) => { .catch((err) => {
ElMessage.error(err.message);
item.error = err.message; item.error = err.message;
emit("error", item); emit("error", item);
reject(err);
}); });
}
if (isLocal) {
next({
host: "/admin/base/comm/upload"
});
} else {
service.base.comm
.upload(
type == "aws"
? {
key
}
: {}
)
.then((res) => {
switch (type) {
//
case "cos":
next({
host: res.url,
data: res.credentials
});
break;
//
case "oss":
next({
host: res.host,
preview: res.publicDomain,
data: {
OSSAccessKeyId: res.OSSAccessKeyId,
policy: res.policy,
signature: res.signature
}
});
break;
//
case "qiniu":
next({
host: res.uploadUrl,
preview: res.publicDomain,
data: {
token: res.token
}
});
break;
// aws
case "aws":
next({
host: res.url,
data: res.fields
});
break;
}
})
.catch(reject);
}
});
} catch (err) {
ElMessage.error("上传配置错误");
}
} }
// //

View File

@ -3,8 +3,8 @@ export default () => {
label: "文件上传", label: "文件上传",
description: "基于 el-upload 封装的文件上传组件", description: "基于 el-upload 封装的文件上传组件",
author: "COOL", author: "COOL",
version: "1.0.0", version: "1.1.0",
updateTime: "2024-02-01", updateTime: "2024-02-05",
demo: [ demo: [
{ {
name: "基础用法", name: "基础用法",
@ -47,6 +47,11 @@ export default () => {
// 上传大小限制 // 上传大小限制
size: 100 size: 100
}, },
// 云端上传路径前缀
prefixPath: {
type: String,
default: "app/base"
},
// 规则 // 规则
rules: [ rules: [
{ {

View File

@ -0,0 +1,180 @@
import { ElMessage } from "element-plus";
import { module, service } from "/@/cool";
import { uuid } from "/@/cool/utils";
import { pathJoin } from "../utils";
import { useBase } from "/$/base";
import { type AxiosProgressEvent } from "axios";
import type { Upload } from "../types";
import { merge } from "lodash-es";
export function useUpload() {
const { options } = module.get("upload");
const { user } = useBase();
// 上传
async function toUpload(
file: File,
opts: Upload.Options = {}
): Promise<{
key: string;
url: string;
fileId: string;
}> {
return new Promise(async (resolve, reject) => {
// 合并配置
const { prefixPath, onProgress } = merge(opts, options);
// 文件id
const fileId = uuid("");
try {
// 上传模式、类型
const { mode, type } = await service.base.comm.uploadMode();
// 本地上传
const isLocal = mode == "local";
// 文件名
const fileName = fileId + "_" + file.name;
// Key
let key = isLocal ? fileName : pathJoin(prefixPath!, fileName);
// 多种上传请求
// 上传到云端
async function next({
host,
preview,
data
}: {
host: string;
preview?: string;
data?: any;
}) {
const fd = new FormData();
// key
fd.append("key", key);
// 签名数据
for (const i in data) {
if (!fd.has(i)) {
fd.append(i, data[i]);
}
}
// 文件
fd.append("file", file);
// 上传
await service
.request({
url: host,
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
Authorization: isLocal ? user.token : null
},
timeout: 600000,
data: fd,
onUploadProgress(e: AxiosProgressEvent) {
const progress = e.total
? Math.floor((e.loaded / e.total) * 100)
: 0;
onProgress?.(progress);
},
proxy: isLocal,
NProgress: false
})
.then((res) => {
key = encodeURIComponent(key);
let url = "";
if (isLocal) {
url = res;
} else {
url = pathJoin(preview || host, key);
}
resolve({
key,
url,
fileId
});
})
.catch((err) => {
ElMessage.error(err.message);
reject(err);
});
}
if (isLocal) {
next({
host: "/admin/base/comm/upload"
});
} else {
service.base.comm
.upload(
type == "aws"
? {
key
}
: {}
)
.then((res) => {
switch (type) {
// 腾讯
case "cos":
next({
host: res.url,
data: res.credentials
});
break;
// 阿里
case "oss":
next({
host: res.host,
preview: res.publicDomain,
data: {
OSSAccessKeyId: res.OSSAccessKeyId,
policy: res.policy,
signature: res.signature
}
});
break;
// 七牛
case "qiniu":
next({
host: res.uploadUrl,
preview: res.publicDomain,
data: {
token: res.token
}
});
break;
// aws
case "aws":
next({
host: res.url,
data: res.fields
});
break;
}
})
.catch(reject);
}
} catch (err) {
ElMessage.error("文件上传失败");
console.error("[upload]", err);
reject(err);
}
});
}
return {
options,
toUpload
};
}

View File

@ -15,4 +15,10 @@ export declare namespace Upload {
isPlay?: boolean; isPlay?: boolean;
[key: string]: any; [key: string]: any;
} }
interface Options {
prefixPath?: string;
onProgress?(progress: number): void;
[key: string]: any;
}
} }

View File

@ -1,7 +1,7 @@
import { last } from "lodash-es"; import { last } from "lodash-es";
import { filename, extname } from "/@/cool/utils"; import { filename, extname } from "/@/cool/utils";
import { module } from "/@/cool"; import { module } from "/@/cool";
import type { Upload } from "../types"; import { Upload } from "../types";
// 模块参数 // 模块参数
const { options } = module.get("upload"); const { options } = module.get("upload");
@ -74,14 +74,3 @@ export function pathJoin(...parts: string[]): string {
return normalizedParts.join("/"); return normalizedParts.join("/");
} }
} }
// 下载
export function download(url: string) {
const a = document.createElement("a");
a.href = url;
a.download = url;
a.target = "_blank";
a.style.display = "none";
document.body.appendChild(a);
a.click();
}