mirror of
https://github.com/bufanyun/hotgo.git
synced 2025-10-12 21:13:45 +08:00
v2.0
This commit is contained in:
1
web/src/components/Upload/index.ts
Normal file
1
web/src/components/Upload/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as BasicUpload } from './src/BasicUpload.vue';
|
330
web/src/components/Upload/src/BasicUpload.vue
Normal file
330
web/src/components/Upload/src/BasicUpload.vue
Normal file
@@ -0,0 +1,330 @@
|
||||
<template>
|
||||
<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">
|
||||
<img :src="item" />
|
||||
</div>
|
||||
<div class="img-box-actions">
|
||||
<n-icon size="18" class="mx-2 action-icon" @click="preview(item)">
|
||||
<EyeOutlined />
|
||||
</n-icon>
|
||||
<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">上传图片</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>
|
||||
|
||||
<!--预览图片-->
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
preset="card"
|
||||
title="预览"
|
||||
:bordered="false"
|
||||
:style="{ width: '520px' }"
|
||||
>
|
||||
<img :src="previewUrl" />
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, toRefs, reactive, computed, watch } from 'vue';
|
||||
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||
import { basicProps } from './props';
|
||||
import { useMessage, useDialog } from 'naive-ui';
|
||||
import { ResultEnum } from '@/enums/httpEnum';
|
||||
import componentSetting from '@/settings/componentSetting';
|
||||
import { useGlobSetting } from '@/hooks/setting';
|
||||
import { isString } from '@/utils/is';
|
||||
|
||||
const globSetting = useGlobSetting();
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BasicUpload',
|
||||
|
||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
||||
props: {
|
||||
...basicProps,
|
||||
},
|
||||
emits: ['uploadChange', 'delete'],
|
||||
setup(props, { emit }) {
|
||||
const getCSSProperties = computed(() => {
|
||||
return {
|
||||
width: `${props.width}px`,
|
||||
height: `${props.height}px`,
|
||||
};
|
||||
});
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
|
||||
const state = reactive({
|
||||
showModal: false,
|
||||
previewUrl: '',
|
||||
originalImgList: [] as string[],
|
||||
imgList: [] as string[],
|
||||
});
|
||||
|
||||
//赋值默认图片显示
|
||||
watch(
|
||||
() => props.value,
|
||||
() => {
|
||||
// console.log('props.value:' + props.value);
|
||||
// 单图模式
|
||||
if (typeof props.value === 'string') {
|
||||
let data: string[] = [];
|
||||
if (props.value !== '') {
|
||||
data.push(props.value);
|
||||
}
|
||||
|
||||
state.imgList = data.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 多图模式
|
||||
state.imgList = props.value.map((item) => {
|
||||
return getImgUrl(item);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
//预览
|
||||
function preview(url: string) {
|
||||
state.showModal = true;
|
||||
state.previewUrl = url;
|
||||
}
|
||||
|
||||
//删除
|
||||
function remove(index: number) {
|
||||
dialog.info({
|
||||
title: '提示',
|
||||
content: '你确定要删除吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
state.imgList.splice(index, 1);
|
||||
state.originalImgList.splice(index, 1);
|
||||
emit('uploadChange', state.originalImgList);
|
||||
emit('delete', state.originalImgList);
|
||||
},
|
||||
onNegativeClick: () => {},
|
||||
});
|
||||
}
|
||||
|
||||
//组装完整图片地址
|
||||
function getImgUrl(url: string): string {
|
||||
const { imgUrl } = globSetting;
|
||||
return /(^http|https:\/\/)/g.test(url) ? url : `${imgUrl}${url}`;
|
||||
}
|
||||
|
||||
function checkFileType(fileType: string) {
|
||||
return componentSetting.upload.fileType.includes(fileType);
|
||||
}
|
||||
|
||||
//上传之前
|
||||
function beforeUpload({ file }) {
|
||||
const fileInfo = file.file;
|
||||
const { maxSize, accept } = props;
|
||||
const acceptRef = (isString(accept) && accept.split(',')) || [];
|
||||
|
||||
// 设置最大值,则判断
|
||||
if (maxSize && fileInfo.size / 1024 / 1024 >= maxSize) {
|
||||
message.error(`上传文件最大值不能超过${maxSize}M`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置类型,则判断
|
||||
const fileType = componentSetting.upload.fileType;
|
||||
if (acceptRef.length > 0 && !checkFileType(fileInfo.type)) {
|
||||
message.error(`只能上传文件类型为${fileType.join(',')}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//上传结束
|
||||
function finish({ event: Event }) {
|
||||
const res = eval('(' + 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.push(result[imgField]);
|
||||
emit('uploadChange', state.originalImgList);
|
||||
} else {
|
||||
message.error(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
finish,
|
||||
preview,
|
||||
remove,
|
||||
beforeUpload,
|
||||
getCSSProperties,
|
||||
};
|
||||
},
|
||||
});
|
||||
</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>
|
38
web/src/components/Upload/src/props.ts
Normal file
38
web/src/components/Upload/src/props.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { PropType } from 'vue';
|
||||
import { NUpload } from 'naive-ui';
|
||||
|
||||
export const basicProps = {
|
||||
...NUpload.props,
|
||||
accept: {
|
||||
type: String,
|
||||
default: '.jpg,.png,.jpeg,.svg,.gif',
|
||||
},
|
||||
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[]>) || (String as PropType<string>),
|
||||
default: () => [],
|
||||
},
|
||||
width: {
|
||||
type: Number as PropType<number>,
|
||||
default: 104,
|
||||
},
|
||||
height: {
|
||||
type: Number as PropType<number>,
|
||||
default: 104, //建议不小于这个尺寸 太小页面可能显示有异常
|
||||
},
|
||||
};
|
7
web/src/components/Upload/src/type/index.ts
Normal file
7
web/src/components/Upload/src/type/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface BasicProps {
|
||||
title?: string;
|
||||
dataSource: Function;
|
||||
columns: any[];
|
||||
pagination: object;
|
||||
showPagination: boolean;
|
||||
}
|
Reference in New Issue
Block a user