im 管理后台
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { default as BasicUpload } from './src/BasicUpload.vue';
|
||||
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
:mask-closable="false"
|
||||
:show-icon="false"
|
||||
preset="dialog"
|
||||
:on-after-leave="handleRemove"
|
||||
:style="{
|
||||
width: width,
|
||||
}"
|
||||
title="上传大文件"
|
||||
>
|
||||
<n-upload
|
||||
directory-dnd
|
||||
:custom-request="handleUpload"
|
||||
:on-remove="handleRemove"
|
||||
name="file"
|
||||
:disabled="uploadStatus != 0 && uploadStatus != 3"
|
||||
>
|
||||
<n-upload-dragger>
|
||||
<div style="margin-bottom: 12px">
|
||||
<n-icon size="48" :depth="3">
|
||||
<FileAddOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
<template v-if="uploadStatus == 0 || uploadStatus == 3">
|
||||
<n-text style="font-size: 16px">点击或者拖动{{ typeTag }}到该区域来上传</n-text>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">支持大文件分片上传,支持断点续传</n-p>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 1">
|
||||
<span style="font-weight: 600">解析中,请稍候...</span>
|
||||
</template>
|
||||
<template v-else-if="uploadStatus == 2">
|
||||
<span style="font-weight: 600">正在上传({{ progress }}%)...</span>
|
||||
<n-p depth="3" style="margin: 8px 0 0 0">文件大小:{{ sizeFormat }}</n-p>
|
||||
</template>
|
||||
</n-upload-dragger>
|
||||
</n-upload>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { NModal, UploadCustomRequestOptions, useMessage, useDialog } from 'naive-ui';
|
||||
import { FileAddOutlined } from '@vicons/antd';
|
||||
import SparkMD5 from 'spark-md5';
|
||||
import { Attachment, FileType, getFileType } from '@/components/FileChooser/src/model';
|
||||
import { CheckMultipart, UploadPart } from '@/api/base';
|
||||
import type { UploadFileParams } from '@/utils/http/axios/types';
|
||||
|
||||
export interface Props {
|
||||
width?: string;
|
||||
uploadType?: FileType;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
width: '60%',
|
||||
uploadType: 'default',
|
||||
});
|
||||
|
||||
const emit = defineEmits(['onFinish']);
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const showModal = ref(false);
|
||||
const chunkSize = 2 * 1024 * 1024; // 每个分片大小限制,默认2M
|
||||
const uploadStatus = ref(0); // 上传状态 0等待上传 1解析中 2上传中 3已取消
|
||||
const progress = ref(0);
|
||||
const sizeFormat = ref('0B');
|
||||
const typeTag = computed(() => {
|
||||
return getFileType(props.uploadType);
|
||||
});
|
||||
|
||||
// 取消上传
|
||||
function handleRemove() {
|
||||
if (uploadStatus.value == 1 || uploadStatus.value == 2) {
|
||||
uploadStatus.value = 3;
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '已取消大文件上传,已上传的文件不会自动删除,重新操作可进行断点续传',
|
||||
positiveText: '确定',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
function handleUpload(options: UploadCustomRequestOptions) {
|
||||
uploadStatus.value = 1;
|
||||
|
||||
// 初始化上传进度
|
||||
updateProgress(options, 0);
|
||||
|
||||
const file = options.file.file as File;
|
||||
const fileReader = new FileReader();
|
||||
fileReader.readAsArrayBuffer(file);
|
||||
fileReader.onload = async (e) => {
|
||||
const spark = new SparkMD5.ArrayBuffer();
|
||||
spark.append(e.target.result);
|
||||
let md5 = spark.end();
|
||||
let start = 0;
|
||||
let end = 0;
|
||||
let index = 0;
|
||||
let shards: any[] = [];
|
||||
while (end < file.size) {
|
||||
start = index * chunkSize;
|
||||
end = (index + 1) * chunkSize;
|
||||
|
||||
const params: UploadFileParams = {
|
||||
uploadType: props.uploadType,
|
||||
md5: md5,
|
||||
index: index + 1,
|
||||
fileName: file.name,
|
||||
file: file.slice(start, end),
|
||||
};
|
||||
|
||||
const shard = { index: index + 1, params: params };
|
||||
shards.push(shard);
|
||||
index++;
|
||||
}
|
||||
|
||||
uploadStatus.value = 2;
|
||||
|
||||
const params = {
|
||||
uploadType: props.uploadType,
|
||||
fileName: file.name,
|
||||
size: file.size,
|
||||
md5: md5,
|
||||
shardCount: shards.length,
|
||||
};
|
||||
|
||||
CheckMultipart(params)
|
||||
.then(async (res) => {
|
||||
// 已存在
|
||||
if (!res.waitUploadIndex || res.waitUploadIndex.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 断点续传,过滤掉已上传成功的分片文件
|
||||
shards = shards.filter((shard) => res.waitUploadIndex.includes(shard.index));
|
||||
if (shards.length == 0) {
|
||||
onFinish(options, res.attachment);
|
||||
return;
|
||||
}
|
||||
|
||||
// 导入断点续传进度
|
||||
updateProgress(options, res.progress);
|
||||
sizeFormat.value = res.sizeFormat;
|
||||
|
||||
for (const item of shards) {
|
||||
if (uploadStatus.value == 3) {
|
||||
break;
|
||||
}
|
||||
item.params.uploadId = res.uploadId;
|
||||
await handleUploadPart(options, item);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uploadStatus.value = 0;
|
||||
options.onError();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// 上传分片文件
|
||||
async function handleUploadPart(options: UploadCustomRequestOptions, item) {
|
||||
const res = await UploadPart(item.params);
|
||||
updateProgress(options, res.progress);
|
||||
if (res.finish) {
|
||||
onFinish(options, res.attachment);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新上传进度
|
||||
function updateProgress(options: UploadCustomRequestOptions, value: number) {
|
||||
options.onProgress({ percent: value });
|
||||
progress.value = value;
|
||||
}
|
||||
|
||||
// 上传成功后的回调
|
||||
function onFinish(options: UploadCustomRequestOptions, result: Attachment) {
|
||||
options.onFinish();
|
||||
message.success('上传成功');
|
||||
uploadStatus.value = 0;
|
||||
emit('onFinish', result, true);
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
showModal.value = true;
|
||||
uploadStatus.value = 0;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
openModal,
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,412 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="w-full">
|
||||
<div class="upload">
|
||||
<div class="upload-card">
|
||||
<!--图片列表-->
|
||||
<div
|
||||
class="upload-card-item"
|
||||
:style="getCSSProperties"
|
||||
v-for="(item, index) in imgList"
|
||||
:key="`img_${index}`"
|
||||
>
|
||||
<div class="upload-card-item-info">
|
||||
<div class="img-box">
|
||||
<template v-if="fileType === 'image'">
|
||||
<img :src="item" @error="errorImg($event)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-avatar :style="fileAvatarCSS">{{ getFileExt(item) }}</n-avatar>
|
||||
</template>
|
||||
</div>
|
||||
<div class="img-box-actions">
|
||||
<template v-if="fileType === 'image'">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
|
||||
<EyeOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<template v-else>
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="download(item)">
|
||||
<CloudDownloadOutlined />
|
||||
</n-icon>
|
||||
</template>
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="remove(index)">
|
||||
<DeleteOutlined />
|
||||
</n-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--上传图片-->
|
||||
<div
|
||||
class="upload-card-item upload-card-item-select-picture"
|
||||
:style="getCSSProperties"
|
||||
v-if="imgList.length < maxNumber"
|
||||
>
|
||||
<n-upload
|
||||
v-bind="$props"
|
||||
:file-list-style="{ display: 'none' }"
|
||||
@before-upload="beforeUpload"
|
||||
@finish="finish"
|
||||
>
|
||||
<div class="flex flex-col justify-center">
|
||||
<n-icon size="18" class="m-auto">
|
||||
<PlusOutlined />
|
||||
</n-icon>
|
||||
<span class="upload-title">{{ uploadTitle }}</span>
|
||||
</div>
|
||||
</n-upload>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--上传图片-->
|
||||
<n-space>
|
||||
<n-alert title="提示" type="info" v-if="helpText" class="flex w-full">
|
||||
{{ helpText }}
|
||||
</n-alert>
|
||||
</n-space>
|
||||
</div>
|
||||
|
||||
<Preview ref="previewRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, computed, watch, onMounted, ref } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined } from '@vicons/antd';
|
||||
import { basicProps } from './props';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import Preview from '@/components/FileChooser/src/Preview.vue';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { isArray, isJsonString, isNullOrUnDef, isUrl } from '@/utils/is';
|
||||
import { getFileExt } from '@/utils/urlUtils';
|
||||
import { errorImg } from '@/utils/hotgo';
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
|
||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined, CloudDownloadOutlined },
|
||||
props: {
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['uploadChange', 'delete'],
|
||||
setup(props, { emit }) {
|
||||
const getCSSProperties = computed(() => {
|
||||
return {
|
||||
width: `${props.width}px`,
|
||||
height: `${props.height}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const previewRef = ref();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const uploadTitle = ref(props.fileType === 'image' ? '上传图片' : '上传附件');
|
||||
const maxNumber = computed(() => {
|
||||
return props.maxNumber;
|
||||
});
|
||||
const fileAvatarCSS = computed(() => {
|
||||
return {
|
||||
'--n-merged-size': `var(--n-avatar-size-override, ${props.width * 0.8}px)`,
|
||||
'--n-font-size': `18px`,
|
||||
};
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
showModal: false,
|
||||
previewUrl: '',
|
||||
originalImgList: [] as string[],
|
||||
imgList: [] as string[],
|
||||
});
|
||||
|
||||
//赋值默认图片显示
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadValue(props.value);
|
||||
return;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.values,
|
||||
() => {
|
||||
loadValue(props.values);
|
||||
return;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
// 加载默认
|
||||
function loadValue(value: any) {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let data: string[] = [];
|
||||
if (isJsonString(value)) {
|
||||
value = JSON.parse(value);
|
||||
}
|
||||
|
||||
// 单图模式
|
||||
if (typeof value === 'string') {
|
||||
if (value !== '') {
|
||||
data.push(value);
|
||||
}
|
||||
} else {
|
||||
// 多图模式
|
||||
data = value;
|
||||
}
|
||||
|
||||
if (!isArray(data) || data.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.imgList = data.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
state.originalImgList = state.imgList;
|
||||
}
|
||||
|
||||
//预览
|
||||
function preview(url: string) {
|
||||
previewRef.value.openPreview(url);
|
||||
}
|
||||
|
||||
//下载
|
||||
function download(url: string) {
|
||||
window.open(url);
|
||||
}
|
||||
|
||||
//删除
|
||||
function remove(index: number) {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要删除吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
state.imgList.splice(index, 1);
|
||||
state.originalImgList.splice(index, 1);
|
||||
if (props.maxNumber === 1) {
|
||||
emit('uploadChange', '');
|
||||
} else {
|
||||
emit('uploadChange', state.originalImgList);
|
||||
}
|
||||
emit('delete', state.originalImgList);
|
||||
},
|
||||
onNegativeClick: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
//组装完整图片地址
|
||||
function getImgUrl(url: string): string {
|
||||
const { imgUrl } = globSetting;
|
||||
return isUrl(url) ? url : `${imgUrl}${url}`;
|
||||
}
|
||||
|
||||
function checkFileType(map: string[], fileType: string) {
|
||||
if (isNullOrUnDef(map)) {
|
||||
return true;
|
||||
}
|
||||
return map.includes(fileType);
|
||||
}
|
||||
|
||||
//上传之前
|
||||
function beforeUpload({ file }) {
|
||||
const fileInfo = file.file;
|
||||
// 设置最大值,则判断
|
||||
if (props.maxSize && fileInfo.size / 1024 / 1024 >= props.maxSize) {
|
||||
message.error(`上传文件最大值不能超过${props.maxSize}M`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置类型,则判断
|
||||
const fileType =
|
||||
props.fileType === 'image'
|
||||
? componentSetting.upload.imageType
|
||||
: componentSetting.upload.fileType;
|
||||
if (!checkFileType(fileType, fileInfo.type)) {
|
||||
console.log('checkFileType fileInfo.type:' + fileInfo.type);
|
||||
message.error(`只能上传文件类型为${fileType.join(',')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//上传结束
|
||||
function finish({ event: Event }) {
|
||||
const res = JSON.parse(Event.target.response);
|
||||
const infoField = componentSetting.upload.apiSetting.infoField;
|
||||
const imgField = componentSetting.upload.apiSetting.imgField;
|
||||
const { code } = res;
|
||||
const msg = res.msg || res.message || '上传失败';
|
||||
const result = res[infoField];
|
||||
//成功
|
||||
if (code === ResultEnum.SUCCESS) {
|
||||
let imgUrl: string = getImgUrl(result[imgField]);
|
||||
state.imgList.push(imgUrl);
|
||||
state.originalImgList = state.imgList;
|
||||
if (props.maxNumber === 1) {
|
||||
emit('uploadChange', imgUrl);
|
||||
} else {
|
||||
emit('uploadChange', state.originalImgList);
|
||||
}
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
setTimeout(function () {
|
||||
if (props.maxNumber === 1) {
|
||||
loadValue(props.value);
|
||||
} else {
|
||||
loadValue(props.values);
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
return {
|
||||
errorImg,
|
||||
...toRefs(state),
|
||||
finish,
|
||||
preview,
|
||||
download,
|
||||
remove,
|
||||
beforeUpload,
|
||||
getCSSProperties,
|
||||
uploadTitle,
|
||||
fileAvatarCSS,
|
||||
getFileExt,
|
||||
Preview,
|
||||
previewRef,
|
||||
maxNumber,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.n-upload {
|
||||
width: auto; /** 居中 */
|
||||
}
|
||||
|
||||
.upload {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
&-card {
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
&-item {
|
||||
margin: 0 8px 8px 0;
|
||||
position: relative;
|
||||
padding: 8px;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: 0 0;
|
||||
|
||||
.upload-card-item-info::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&-info::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&-info {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover {
|
||||
.img-box-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.img-box {
|
||||
position: relative;
|
||||
//padding: 8px;
|
||||
//border: 1px solid #d9d9d9;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.img-box-actions {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 10;
|
||||
white-space: nowrap;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover {
|
||||
background: 0 0;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-item-select-picture {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
background: #fafafa;
|
||||
color: #666;
|
||||
|
||||
.upload-title {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { NUpload } from 'naive-ui';
|
||||
|
||||
export const basicProps = {
|
||||
...NUpload.props,
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'image',
|
||||
},
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.jpg,.png,.jpeg,.svg,.gif,.webp',
|
||||
},
|
||||
helpText: {
|
||||
type: String as PropType<string>,
|
||||
default: '',
|
||||
},
|
||||
maxSize: {
|
||||
type: Number as PropType<number>,
|
||||
default: 2,
|
||||
},
|
||||
maxNumber: {
|
||||
type: Number as PropType<number>,
|
||||
default: Infinity,
|
||||
},
|
||||
value: {
|
||||
type: String as PropType<string>,
|
||||
default: () => '',
|
||||
},
|
||||
values: {
|
||||
type: (Array as PropType<string[]>) || (Object as PropType<object>),
|
||||
default: () => [],
|
||||
},
|
||||
width: {
|
||||
type: Number as PropType<number>,
|
||||
default: 104,
|
||||
},
|
||||
height: {
|
||||
type: Number as PropType<number>,
|
||||
default: 104, //建议不小于这个尺寸 太小页面可能显示有异常
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface BasicProps {
|
||||
title?: string;
|
||||
dataSource: Function;
|
||||
columns: any[];
|
||||
pagination: object;
|
||||
showPagination: boolean;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div>
|
||||
<BasicUpload
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/file`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
accept="*"
|
||||
name="file"
|
||||
:width="100"
|
||||
:height="100"
|
||||
fileType="file"
|
||||
:maxNumber="maxNumber"
|
||||
:helpText="helpText"
|
||||
@uploadChange="uploadChange"
|
||||
v-model:value="image"
|
||||
v-model:values="images"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, unref, reactive, watch } from 'vue';
|
||||
import { BasicUpload } from '@/components/Upload';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
export interface Props {
|
||||
value: string | string[] | null;
|
||||
maxNumber: number;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const { uploadUrl } = globSetting;
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
uploadType: 'default',
|
||||
});
|
||||
const emit = defineEmits(['update:value']);
|
||||
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1, helpText: '' });
|
||||
const image = ref<string>('');
|
||||
const images = ref<string[] | object>([]);
|
||||
|
||||
function uploadChange(list: string | string[]) {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = unref(list as string);
|
||||
emit('update:value', image.value);
|
||||
} else {
|
||||
images.value = unref(list as string[]);
|
||||
emit('update:value', images.value);
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage() {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = props.value as string;
|
||||
} else {
|
||||
images.value = props.value as string[];
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadImage();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
loadImage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<BasicUpload
|
||||
:action="`${uploadUrl}${urlPrefix}/upload/file`"
|
||||
:headers="uploadHeaders"
|
||||
:data="{ type: 0 }"
|
||||
name="file"
|
||||
:width="100"
|
||||
:height="100"
|
||||
:maxNumber="maxNumber"
|
||||
:helpText="helpText"
|
||||
@uploadChange="uploadChange"
|
||||
v-model:value="image"
|
||||
v-model:values="images"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, reactive, ref, unref, watch } from 'vue';
|
||||
import { BasicUpload } from '@/components/Upload';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||
|
||||
export interface Props {
|
||||
value: string | string[] | null;
|
||||
maxNumber: number;
|
||||
helpText?: string;
|
||||
}
|
||||
|
||||
const emit = defineEmits(['update:value']);
|
||||
const props = withDefaults(defineProps<Props>(), { value: '', maxNumber: 1, helpText: '' });
|
||||
const image = ref<string>('');
|
||||
const images = ref<string[]>([]);
|
||||
const globSetting = useGlobSetting();
|
||||
const urlPrefix = globSetting.urlPrefix || '';
|
||||
const { uploadUrl } = globSetting;
|
||||
const useUserStore = useUserStoreWidthOut();
|
||||
const uploadHeaders = reactive({
|
||||
Authorization: useUserStore.token,
|
||||
uploadType: 'image',
|
||||
});
|
||||
|
||||
function uploadChange(list: string | string[]) {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = unref(list as string);
|
||||
emit('update:value', image.value);
|
||||
} else {
|
||||
images.value = unref(list as string[]);
|
||||
emit('update:value', images.value);
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage() {
|
||||
if (props.maxNumber === 1) {
|
||||
image.value = props.value as string;
|
||||
} else {
|
||||
images.value = props.value as string[];
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
loadImage();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => {
|
||||
loadImage();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less"></style>
|
||||
Reference in New Issue
Block a user