19 changed files with 914 additions and 1540 deletions
After Width: | Height: | Size: 5.8 KiB |
@ -0,0 +1,318 @@ |
|||||||
|
<template> |
||||||
|
<div class="upload-box"> |
||||||
|
<el-upload |
||||||
|
v-model:file-list="fileList" |
||||||
|
:accept="fileType.join(',')" |
||||||
|
:action="uploadUrl" |
||||||
|
:before-upload="beforeUpload" |
||||||
|
:class="['upload', drag ? 'no-border' : '']" |
||||||
|
:disabled="showPlus" |
||||||
|
:drag="drag" |
||||||
|
:http-request="httpRequest" |
||||||
|
:limit="limit" |
||||||
|
:auto-upload="false" |
||||||
|
:multiple="limit > 1" |
||||||
|
:on-error="uploadError" |
||||||
|
:on-exceed="handleExceed" |
||||||
|
:on-change="handleChange" |
||||||
|
list-type="picture-card" |
||||||
|
ref="uploadRef" |
||||||
|
> |
||||||
|
<div class="upload-empty" v-if="!showPlus"> |
||||||
|
<slot name="empty"> |
||||||
|
<Icon icon="ep:plus" :size="30" color="#ccc" /> |
||||||
|
</slot> |
||||||
|
</div> |
||||||
|
<template #file="{ file }"> |
||||||
|
<img :src="file.url" class="upload-image" /> |
||||||
|
<div class="upload-handle" @click.stop> |
||||||
|
<div class="handle-icon" @click="imagePreview(file.url!)"> |
||||||
|
<Icon icon="ep:zoom-in" :size="30" /> |
||||||
|
<span>查看</span> |
||||||
|
</div> |
||||||
|
<div class="handle-icon" @click="handleRemove(file)"> |
||||||
|
<Icon icon="ep:delete" :size="30" /> |
||||||
|
<span>删除</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</el-upload> |
||||||
|
<div class="el-upload__tip"> |
||||||
|
<slot name="tip"></slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
<script lang="ts" setup> |
||||||
|
import { ElNotification } from 'element-plus' |
||||||
|
import { createImageViewer } from '@/components/ImageViewer' |
||||||
|
import { batchUploadFile } from '@/api/infra/file' |
||||||
|
import { propTypes } from '@/utils/propTypes' |
||||||
|
import { useUpload } from '@/components/UploadFile/src/useUpload' |
||||||
|
|
||||||
|
defineOptions({ name: 'UploadImgs' }) |
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗 |
||||||
|
// 查看图片 |
||||||
|
const imagePreview = (imgUrl: string) => { |
||||||
|
createImageViewer({ |
||||||
|
zIndex: 9999999, |
||||||
|
urlList: [imgUrl] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const uploadRef = ref() |
||||||
|
|
||||||
|
type FileTypes = |
||||||
|
| 'image/apng' |
||||||
|
| 'image/bmp' |
||||||
|
| 'image/gif' |
||||||
|
| 'image/jpeg' |
||||||
|
| 'image/pjpeg' |
||||||
|
| 'image/png' |
||||||
|
| 'image/svg+xml' |
||||||
|
| 'image/tiff' |
||||||
|
| 'image/webp' |
||||||
|
| 'image/x-icon' |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired, |
||||||
|
drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传(默认为 true) |
||||||
|
limit: propTypes.number.def(1), // 最大图片上传数 ==> 非必传(默认为 5张) |
||||||
|
fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传(默认为 5M) |
||||||
|
fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"]) |
||||||
|
height: propTypes.string.def('150px'), // 组件高度 ==> 非必传(默认为 150px) |
||||||
|
width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传(默认为 150px) |
||||||
|
borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px) |
||||||
|
}) |
||||||
|
const showPlus = ref(false) |
||||||
|
const { uploadUrl, httpRequest } = useUpload() |
||||||
|
|
||||||
|
const fileList = ref<any>([]) |
||||||
|
const uploadNumber = ref<number>(0) |
||||||
|
const uploadList = ref<any>([]) |
||||||
|
/** |
||||||
|
* @description 文件上传之前判断 |
||||||
|
* @param rawFile 上传的文件 |
||||||
|
* */ |
||||||
|
const beforeUpload = (rawFile: any) => { |
||||||
|
const imgSize = rawFile.size / 1024 / 1024 < props.fileSize |
||||||
|
const imgType = props.fileType |
||||||
|
if (!imgType.includes(rawFile.type as FileTypes)) |
||||||
|
ElNotification({ |
||||||
|
title: '温馨提示', |
||||||
|
message: '上传图片不符合所需的格式!', |
||||||
|
type: 'warning' |
||||||
|
}) |
||||||
|
if (!imgSize) |
||||||
|
ElNotification({ |
||||||
|
title: '温馨提示', |
||||||
|
message: `上传图片大小不能超过 ${props.fileSize}M!`, |
||||||
|
type: 'warning' |
||||||
|
}) |
||||||
|
uploadNumber.value++ |
||||||
|
return imgType.includes(rawFile.type as FileTypes) && imgSize |
||||||
|
} |
||||||
|
|
||||||
|
// 图片上传成功 |
||||||
|
interface UploadEmits { |
||||||
|
(e: 'update:modelValue', value: string[]): void |
||||||
|
} |
||||||
|
|
||||||
|
const emit = defineEmits<UploadEmits>() |
||||||
|
const handleChange = (file, fileList) => { |
||||||
|
showPlus.value = fileList.length >= props.limit |
||||||
|
emitUpdateModelValue(fileList) |
||||||
|
} |
||||||
|
|
||||||
|
// 监听模型绑定值变动 |
||||||
|
watch( |
||||||
|
() => props.modelValue, |
||||||
|
(val: any) => { |
||||||
|
if (!val) { |
||||||
|
fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置 |
||||||
|
return |
||||||
|
} |
||||||
|
fileList.value = [] // 保障数据为空 |
||||||
|
fileList.value.push(...val) |
||||||
|
}, |
||||||
|
{ immediate: true, deep: true } |
||||||
|
) |
||||||
|
|
||||||
|
const emitUpdateModelValue = (fileList: any) => { |
||||||
|
emit('update:modelValue', fileList) |
||||||
|
} |
||||||
|
// 删除图片 |
||||||
|
const handleRemove = (uploadFile: any) => { |
||||||
|
fileList.value = fileList.value.filter((item: any) => { |
||||||
|
if (uploadFile.id) { |
||||||
|
return item.id !== uploadFile.id |
||||||
|
} else { |
||||||
|
return item.uid !== uploadFile.uid |
||||||
|
} |
||||||
|
}) |
||||||
|
emitUpdateModelValue(fileList.value) |
||||||
|
} |
||||||
|
|
||||||
|
// 图片上传错误提示 |
||||||
|
const uploadError = () => { |
||||||
|
ElNotification({ |
||||||
|
title: '温馨提示', |
||||||
|
message: '图片上传失败,请您重新上传!', |
||||||
|
type: 'error' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
// 文件数超出提示 |
||||||
|
const handleExceed = () => { |
||||||
|
ElNotification({ |
||||||
|
title: '温馨提示', |
||||||
|
message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`, |
||||||
|
type: 'warning' |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const handlerUpload = async () => { |
||||||
|
const formData = new FormData() |
||||||
|
fileList.value.forEach((file: any) => { |
||||||
|
formData.append('files', file.raw) |
||||||
|
}) |
||||||
|
let res = await batchUploadFile(formData) |
||||||
|
fileList.value = res.data |
||||||
|
emitUpdateModelValue(fileList.value) |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
handlerUpload |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<style lang="scss" scoped> |
||||||
|
.is-error { |
||||||
|
.upload { |
||||||
|
:deep(.el-upload--picture-card), |
||||||
|
:deep(.el-upload-dragger) { |
||||||
|
border: 1px dashed var(--el-color-danger) !important; |
||||||
|
|
||||||
|
&:hover { |
||||||
|
border-color: var(--el-color-primary) !important; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
:deep(.is-disabled) { |
||||||
|
.el-upload--picture-card, |
||||||
|
.el-upload-dragger { |
||||||
|
display: none !important; |
||||||
|
cursor: not-allowed; |
||||||
|
background: var(--el-disabled-bg-color); |
||||||
|
border: 1px dashed var(--el-border-color-darker); |
||||||
|
|
||||||
|
&:hover { |
||||||
|
border-color: var(--el-border-color-darker) !important; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.upload-box { |
||||||
|
.no-border { |
||||||
|
:deep(.el-upload-list--picture-card) { |
||||||
|
gap: 20px; |
||||||
|
|
||||||
|
.el-upload-list__item { |
||||||
|
margin: 0 !important; |
||||||
|
} |
||||||
|
} |
||||||
|
:deep(.el-upload--picture-card) { |
||||||
|
border: none !important; |
||||||
|
} |
||||||
|
} |
||||||
|
:deep(.upload) { |
||||||
|
.el-upload-dragger { |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
padding: 0; |
||||||
|
gap: 20px; |
||||||
|
overflow: hidden; |
||||||
|
border: 1px dashed var(--el-border-color-darker); |
||||||
|
border-radius: v-bind(borderRadius); |
||||||
|
|
||||||
|
&:hover { |
||||||
|
border: 1px dashed var(--el-color-primary); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.el-upload-dragger.is-dragover { |
||||||
|
background-color: var(--el-color-primary-light-9); |
||||||
|
border: 2px dashed var(--el-color-primary) !important; |
||||||
|
} |
||||||
|
|
||||||
|
.el-upload-list__item, |
||||||
|
.el-upload--picture-card { |
||||||
|
width: v-bind(width); |
||||||
|
height: v-bind(height); |
||||||
|
background-color: transparent; |
||||||
|
border-radius: v-bind(borderRadius); |
||||||
|
} |
||||||
|
|
||||||
|
.upload-image { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
object-fit: contain; |
||||||
|
} |
||||||
|
|
||||||
|
.upload-handle { |
||||||
|
position: absolute; |
||||||
|
inset: 0; |
||||||
|
display: flex; |
||||||
|
cursor: pointer; |
||||||
|
background: rgb(0 0 0 / 60%); |
||||||
|
opacity: 0; |
||||||
|
box-sizing: border-box; |
||||||
|
transition: var(--el-transition-duration-fast); |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
margin: 0; |
||||||
|
gap: 15%; |
||||||
|
.handle-icon { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
color: aliceblue; |
||||||
|
&:hover { |
||||||
|
color: var(--el-color-primary); |
||||||
|
transition: var(--el-transition-duration-fast); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.el-upload-list__item { |
||||||
|
&:hover { |
||||||
|
.upload-handle { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
.upload-empty { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
font-size: 12px; |
||||||
|
line-height: 30px; |
||||||
|
color: var(--el-color-info); |
||||||
|
.el-icon { |
||||||
|
font-size: 28px; |
||||||
|
color: var(--el-text-color-secondary); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.el-upload__tip { |
||||||
|
line-height: 15px; |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
@ -1,501 +0,0 @@ |
|||||||
<template> |
|
||||||
<main class="upload-container"> |
|
||||||
<section class="file-list"> |
|
||||||
<section |
|
||||||
class="file" |
|
||||||
v-for="(file, index) in fileList" |
|
||||||
:key="file.uid" |
|
||||||
v-if="fileList.length > 0" |
|
||||||
> |
|
||||||
<el-image :src="getfileUrl(file)" class="img" /> |
|
||||||
<Icon |
|
||||||
icon="ep:close" |
|
||||||
class="icon close" |
|
||||||
:size="13" |
|
||||||
@click="remove(file, index)" |
|
||||||
color="#fff" |
|
||||||
/> |
|
||||||
</section> |
|
||||||
<section class="emty" v-else> |
|
||||||
<Icon icon="svg-icon:customs-empty" :size="40" /> |
|
||||||
<span>暂无图片</span> |
|
||||||
</section> |
|
||||||
</section> |
|
||||||
<el-upload |
|
||||||
:file-list="fileList" |
|
||||||
:action="uploadUrl" |
|
||||||
:data="uploadExtendParams" |
|
||||||
:http-request="httpRequest" |
|
||||||
:auto-upload="false" |
|
||||||
:on-change="onChange" |
|
||||||
:on-success="onSuccess" |
|
||||||
:on-error="onError" |
|
||||||
multiple |
|
||||||
:limit="limit > 0 ? limit : ''" |
|
||||||
:show-file-list="false" |
|
||||||
:accept="accept" |
|
||||||
ref="uploadRef" |
|
||||||
:on-exceed="exceed" |
|
||||||
class="flex justify-center items-center" |
|
||||||
> |
|
||||||
<section class="button"> |
|
||||||
<Icon icon="svg-icon:customs-upload" :size="20" color="#00a3ff" /> |
|
||||||
选取图片 |
|
||||||
</section> |
|
||||||
</el-upload> |
|
||||||
|
|
||||||
<section class="upload-loading" v-show="uploadLoading"> |
|
||||||
<div class="loading"> <span></span><span></span><span></span><span></span> </div> |
|
||||||
</section> |
|
||||||
</main> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script lang="ts" setup> |
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload' |
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus' |
|
||||||
defineOptions({ |
|
||||||
name: 'InnerUploader' |
|
||||||
}) |
|
||||||
|
|
||||||
const uploadLoading = ref(false) |
|
||||||
|
|
||||||
const emit = defineEmits(['handlerRemove', 'handlerSuccess', 'hanlderWriteState']) |
|
||||||
|
|
||||||
// 上传额外参数 |
|
||||||
const uploadExtendParams = (file: any) => { |
|
||||||
return { writeAble: extendParams.value[file.uid] } |
|
||||||
} |
|
||||||
|
|
||||||
const props = defineProps({ |
|
||||||
/** |
|
||||||
* 文件列表 |
|
||||||
* @default Array |
|
||||||
*/ |
|
||||||
uploadList: { |
|
||||||
type: Array, |
|
||||||
default: () => [] |
|
||||||
}, |
|
||||||
limit: { |
|
||||||
type: Number, |
|
||||||
default: '' |
|
||||||
}, |
|
||||||
/** |
|
||||||
* 是否可已进行文件权限编辑 |
|
||||||
* @default true |
|
||||||
*/ |
|
||||||
iswrite: { |
|
||||||
type: Boolean, |
|
||||||
default: true |
|
||||||
}, |
|
||||||
accept: { |
|
||||||
type: String, |
|
||||||
default: '*' |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
watch( |
|
||||||
() => props.uploadList, |
|
||||||
(newVal) => { |
|
||||||
fileList.value = newVal |
|
||||||
}, |
|
||||||
{ deep: true } |
|
||||||
) |
|
||||||
|
|
||||||
const fileList: any = ref([]) |
|
||||||
|
|
||||||
// 额外参数 |
|
||||||
const extendParams = ref({}) |
|
||||||
|
|
||||||
// el-upload组件 |
|
||||||
const uploadRef = ref() |
|
||||||
|
|
||||||
// 自定义上传地址与方式 |
|
||||||
const { uploadUrl, httpRequest } = useUpload() |
|
||||||
|
|
||||||
// 文件状态改变 |
|
||||||
const onChange = (file: any) => { |
|
||||||
if (props.accept !== '*') { |
|
||||||
if (props.accept.split(',').findIndex((i) => file.name.includes(i)) == -1) { |
|
||||||
unref(uploadRef).handleRemove(file) |
|
||||||
ElMessage({ message: `${file.name}文件格式不正确`, type: 'error' }) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
if (file.status === 'ready') { |
|
||||||
fileList.value.push(file) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const getfileUrl = (file: any) => { |
|
||||||
return file.attachmentPath || URL.createObjectURL(file.raw) |
|
||||||
} |
|
||||||
|
|
||||||
// 上传错误的处理 |
|
||||||
const onError = (e: any, file) => { |
|
||||||
ElMessage({ message: `${file.name}上传失败,${e}`, type: 'error' }) |
|
||||||
// unref(uploadRef).handleRemove(file) |
|
||||||
} |
|
||||||
|
|
||||||
// 移除文件 |
|
||||||
const remove = (file: any, index: any) => { |
|
||||||
if (file.status === 'success') { |
|
||||||
ElMessageBox.alert(`确认移除${file.name}吗?`, '删除', { |
|
||||||
confirmButtonText: '确认', |
|
||||||
showCancelButton: true, |
|
||||||
cancelButtonText: '取消', |
|
||||||
distinguishCancelAndClose: true, |
|
||||||
showClose: false |
|
||||||
}) |
|
||||||
.then(() => { |
|
||||||
fileList.value.splice(index, 1) |
|
||||||
emit('handlerRemove', file) |
|
||||||
}) |
|
||||||
.catch(() => {}) |
|
||||||
return |
|
||||||
} |
|
||||||
fileList.value.splice(index, 1) |
|
||||||
unref(uploadRef).handleRemove(file) |
|
||||||
} |
|
||||||
|
|
||||||
// 触发组件el的上传事件 |
|
||||||
const upload = () => { |
|
||||||
if (fileList.value.filter((file) => file.status == 'ready').length > 0) { |
|
||||||
uploadLoading.value = true |
|
||||||
unref(uploadRef).submit() |
|
||||||
return |
|
||||||
} |
|
||||||
ElMessage('暂无可上传的文件') |
|
||||||
} |
|
||||||
|
|
||||||
// 成功回调暂存 |
|
||||||
const successMap: any = ref([]) |
|
||||||
|
|
||||||
// 上传成功的回调 |
|
||||||
const onSuccess = (response: any) => { |
|
||||||
successMap.value.push(response) |
|
||||||
if (fileList.value.filter((file) => file.status == 'ready').length == 0) { |
|
||||||
uploadLoading.value = false |
|
||||||
emit('handlerSuccess', successMap.value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function exceed() { |
|
||||||
ElMessage({ |
|
||||||
message: `只允许上传${props.limit}张图片` |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
onMounted(() => { |
|
||||||
fileList.value = props.uploadList |
|
||||||
}) |
|
||||||
|
|
||||||
onBeforeUnmount(() => { |
|
||||||
fileList.value = [] |
|
||||||
unref(uploadRef).clearFiles() |
|
||||||
}) |
|
||||||
|
|
||||||
onActivated(() => { |
|
||||||
// console.log('onActivated') |
|
||||||
}) |
|
||||||
|
|
||||||
// 在组件撒上暴露方法 |
|
||||||
defineExpose({ |
|
||||||
upload |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<style lang="scss" scoped> |
|
||||||
:deep(.el-upload-list__item:hover) { |
|
||||||
background-color: transparent !important; |
|
||||||
} |
|
||||||
:deep(.el-upload) { |
|
||||||
flex-flow: column; |
|
||||||
gap: 10px; |
|
||||||
align-items: flex-start; |
|
||||||
} |
|
||||||
.file-list { |
|
||||||
width: 220px; |
|
||||||
margin-bottom: 10px; |
|
||||||
display: block; |
|
||||||
border-radius: 6px; |
|
||||||
padding: 8px 6px; |
|
||||||
} |
|
||||||
.file { |
|
||||||
// width: fit-content; |
|
||||||
margin-bottom: 5px; |
|
||||||
// padding: 4px 10px; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
cursor: pointer; |
|
||||||
border-radius: 6px; |
|
||||||
position: relative; |
|
||||||
justify-content: center; |
|
||||||
transition: 0.2s; |
|
||||||
.img { |
|
||||||
border-radius: 6px; |
|
||||||
} |
|
||||||
.info { |
|
||||||
flex: 1; |
|
||||||
overflow: hidden; |
|
||||||
display: flex; |
|
||||||
flex-flow: column nowrap; |
|
||||||
.name { |
|
||||||
font-weight: bold; |
|
||||||
font-size: small; |
|
||||||
letter-spacing: 1px; |
|
||||||
white-space: nowrap; |
|
||||||
overflow: hidden; |
|
||||||
text-overflow: ellipsis; |
|
||||||
} |
|
||||||
.size { |
|
||||||
font-size: 12px; |
|
||||||
} |
|
||||||
.time { |
|
||||||
font-size: 12px; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.state { |
|
||||||
font-size: small; |
|
||||||
} |
|
||||||
.icon { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
right: 0; |
|
||||||
transform: translateX(20%) translateY(-20%); |
|
||||||
border-radius: 50%; |
|
||||||
transition: 0.2s; |
|
||||||
padding: 2px; |
|
||||||
background-image: radial-gradient(circle at 60% 60%, #fcfcfb 2%, red 54%); |
|
||||||
} |
|
||||||
.close:hover { |
|
||||||
// background-color: #f83f2a; |
|
||||||
// box-shadow: 0 0 2px 0 #640404; |
|
||||||
// filter: drop-shadow(0 0 1px #000000ab); |
|
||||||
opacity: 0.9; |
|
||||||
} |
|
||||||
&:hover { |
|
||||||
background-color: #fff; |
|
||||||
box-shadow: 0 0 4px 0px #ccc; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
.program { |
|
||||||
position: absolute; |
|
||||||
bottom: 0%; |
|
||||||
// border-radius: 999px; |
|
||||||
background-color: #89ffc2a0; |
|
||||||
left: 0; |
|
||||||
width: var(--p); |
|
||||||
// width: 100%; |
|
||||||
transition: 0.2s; |
|
||||||
height: 100%; |
|
||||||
} |
|
||||||
} |
|
||||||
.emty { |
|
||||||
width: 100%; |
|
||||||
display: flex; |
|
||||||
flex-flow: column nowrap; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
color: #9ea3b4; |
|
||||||
} |
|
||||||
.button { |
|
||||||
display: flex; |
|
||||||
padding: 5px 16px; |
|
||||||
color: #00a3ff; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
gap: 4px; |
|
||||||
border-radius: 6px; |
|
||||||
background: #f1faff; |
|
||||||
} |
|
||||||
.upload-container { |
|
||||||
width: fit-content; |
|
||||||
position: relative; |
|
||||||
.upload-loading { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
background-color: transparent; |
|
||||||
z-index: 100; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
background-color: #ffffff84; |
|
||||||
.loading { |
|
||||||
--w: 13ch; |
|
||||||
// font-weight: bold; |
|
||||||
// font-family: monospace; |
|
||||||
font-size: medium; |
|
||||||
letter-spacing: var(--w); |
|
||||||
width: var(--w); |
|
||||||
overflow: hidden; |
|
||||||
white-space: nowrap; |
|
||||||
color: #0000; |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
animation: c10 2s infinite linear; |
|
||||||
&:before { |
|
||||||
content: '文件上传中...'; |
|
||||||
} |
|
||||||
|
|
||||||
@keyframes c10 { |
|
||||||
9.09% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) -10px #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
18.18% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) -10px #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
27.27% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) -10px #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
36.36% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) -10px #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
45.45% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) -10px #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
54.54% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) -10px #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
63.63% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) -10px #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
72.72% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) -10px #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
81.81% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) -10px #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
90.90% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) -10px #000; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
@ -1,205 +0,0 @@ |
|||||||
<template> |
|
||||||
<main class="upload-container"> |
|
||||||
<el-upload |
|
||||||
:file-list="fileList" |
|
||||||
:action="uploadUrl" |
|
||||||
:http-request="httpRequest" |
|
||||||
:auto-upload="false" |
|
||||||
:on-success="onSuccess" |
|
||||||
:on-error="onError" |
|
||||||
multiple |
|
||||||
:limit="limit" |
|
||||||
:on-preview="handlerPerview" |
|
||||||
:on-remove="remove" |
|
||||||
:before-remove="beforeRemove" |
|
||||||
list-type="picture-card" |
|
||||||
accept=".jpg,.png,.jpeg,.gif" |
|
||||||
:on-exceed="onExceed" |
|
||||||
ref="uploadRef" |
|
||||||
> |
|
||||||
<Icon icon="ep:plus" /> |
|
||||||
</el-upload> |
|
||||||
<el-dialog v-model="perview.show" style="width: fit-content"> |
|
||||||
<img w-full :src="perview.url" alt="Preview Image" style="max-height: 60vh" /> |
|
||||||
</el-dialog> |
|
||||||
</main> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script lang="ts" setup> |
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload' |
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus' |
|
||||||
defineOptions({ |
|
||||||
name: 'InnerUploadImg' |
|
||||||
}) |
|
||||||
|
|
||||||
const emit = defineEmits(['handlerRemove', 'handlerSuccess', 'hanlderWriteState']) |
|
||||||
|
|
||||||
const props = defineProps({ |
|
||||||
uploadList: { |
|
||||||
type: Array, |
|
||||||
default: () => [] |
|
||||||
}, |
|
||||||
limit: { |
|
||||||
type: Number, |
|
||||||
default: 3 |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
watch( |
|
||||||
() => props.uploadList, |
|
||||||
(newVal) => { |
|
||||||
fileList.value = newVal |
|
||||||
}, |
|
||||||
{ deep: true } |
|
||||||
) |
|
||||||
|
|
||||||
const fileList: any = ref([]) |
|
||||||
|
|
||||||
// el-upload组件 |
|
||||||
const uploadRef = ref() |
|
||||||
|
|
||||||
// 自定义上传地址与方式 |
|
||||||
const { uploadUrl, httpRequest } = useUpload() |
|
||||||
|
|
||||||
// 上传错误的处理 |
|
||||||
const onError = (e: any, file) => { |
|
||||||
ElMessage({ message: `${file.name}上传失败,${e}`, type: 'error' }) |
|
||||||
// unref(uploadRef).handleRemove(file) |
|
||||||
} |
|
||||||
|
|
||||||
// 移除文件 |
|
||||||
const remove = (file: any) => { |
|
||||||
emit('handlerRemove', file) |
|
||||||
} |
|
||||||
|
|
||||||
// el upload 删除之前的处理 |
|
||||||
const beforeRemove = (file) => { |
|
||||||
return ElMessageBox.alert(`确认移除${file.name}吗?`, '删除', { |
|
||||||
confirmButtonText: '确认', |
|
||||||
showCancelButton: true, |
|
||||||
cancelButtonText: '取消', |
|
||||||
distinguishCancelAndClose: true, |
|
||||||
showClose: false |
|
||||||
}) |
|
||||||
.then(() => { |
|
||||||
return true |
|
||||||
}) |
|
||||||
.catch(() => { |
|
||||||
return false |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
// 触发组件el的上传事件 |
|
||||||
const upload = () => { |
|
||||||
unref(uploadRef).submit() |
|
||||||
} |
|
||||||
|
|
||||||
// 上传成功的回调 |
|
||||||
const onSuccess = (response: any) => { |
|
||||||
emit('handlerSuccess', response) |
|
||||||
} |
|
||||||
|
|
||||||
// 预览控制 |
|
||||||
const perview = reactive({ |
|
||||||
show: false, |
|
||||||
url: '' |
|
||||||
}) |
|
||||||
|
|
||||||
// 预览事件 |
|
||||||
const handlerPerview = (file) => { |
|
||||||
perview.url = file.url |
|
||||||
perview.show = true |
|
||||||
} |
|
||||||
|
|
||||||
// 超出上传数量的回调 |
|
||||||
const onExceed = () => { |
|
||||||
ElMessage.warning(`只能上传${props.limit}张图片`) |
|
||||||
} |
|
||||||
|
|
||||||
onMounted(() => { |
|
||||||
fileList.value = props.uploadList |
|
||||||
}) |
|
||||||
|
|
||||||
// 在组件撒上暴露方法 |
|
||||||
defineExpose({ |
|
||||||
upload |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<style lang="scss" scoped> |
|
||||||
:deep(.el-upload-list__item:hover) { |
|
||||||
background-color: transparent !important; |
|
||||||
} |
|
||||||
|
|
||||||
.file { |
|
||||||
width: fit-content; |
|
||||||
margin-bottom: 5px; |
|
||||||
padding: 0px 10px; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
gap: 10px; |
|
||||||
cursor: pointer; |
|
||||||
border-radius: 4px; |
|
||||||
position: relative; |
|
||||||
.name { |
|
||||||
color: blue; |
|
||||||
font-size: small; |
|
||||||
letter-spacing: 1px; |
|
||||||
width: 200px; |
|
||||||
white-space: nowrap; |
|
||||||
overflow: hidden; |
|
||||||
text-overflow: ellipsis; |
|
||||||
} |
|
||||||
.state { |
|
||||||
font-size: small; |
|
||||||
} |
|
||||||
.icon { |
|
||||||
padding: 2px; |
|
||||||
border-radius: 50%; |
|
||||||
} |
|
||||||
.edit:hover { |
|
||||||
background-color: #46c9f7; |
|
||||||
box-shadow: 0 0 2px 0 #144d61; |
|
||||||
} |
|
||||||
.close:hover { |
|
||||||
background-color: #f83f2a; |
|
||||||
box-shadow: 0 0 2px 0 #640404; |
|
||||||
} |
|
||||||
&:hover { |
|
||||||
background-color: #fff; |
|
||||||
box-shadow: 0 0 4px 0px #ccc; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
.program { |
|
||||||
position: absolute; |
|
||||||
bottom: 0%; |
|
||||||
// border-radius: 999px; |
|
||||||
background-color: #89ffc2a0; |
|
||||||
left: 0; |
|
||||||
width: var(--p); |
|
||||||
// width: 100%; |
|
||||||
transition: 0.2s; |
|
||||||
height: 100%; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.upload-container { |
|
||||||
width: fit-content; |
|
||||||
position: relative; |
|
||||||
.upload-loading { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
background-color: transparent; |
|
||||||
z-index: 100; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
background-color: #ffffff84; |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
@ -1,568 +0,0 @@ |
|||||||
<template> |
|
||||||
<main class="upload-container"> |
|
||||||
<section class="file-list"> |
|
||||||
<section |
|
||||||
class="file" |
|
||||||
v-for="(file, index) in fileList" |
|
||||||
:key="file.uid" |
|
||||||
:style="setFileState(file)" |
|
||||||
v-if="fileList.length > 0" |
|
||||||
> |
|
||||||
<Icon :icon="getFileIcon(file.name)" :size="40" /> |
|
||||||
<el-tooltip class="box-item" effect="dark" :content="file.name" placement="top"> |
|
||||||
<section class="info"> |
|
||||||
<span class="name">{{ file.name }}</span> |
|
||||||
<span class="time" v-if="file.id"> |
|
||||||
{{ formatDate(file.createTime, 'YYYY年M月D日') }} |
|
||||||
</span> |
|
||||||
<span class="size" v-else>{{ niceBytes(file.size) }}</span> |
|
||||||
</section> |
|
||||||
</el-tooltip> |
|
||||||
<!-- <span |
|
||||||
class="state" |
|
||||||
v-show="file.status !== 'fail' && iswrite" |
|
||||||
> |
|
||||||
{{ extendParams[file.uid] ? '可编辑' : '只读' }} |
|
||||||
<Icon :icon="extendParams[file.uid] ? 'ep:edit-pen' : 'ep:view'" /> |
|
||||||
</span> --> |
|
||||||
<Icon |
|
||||||
:icon="extendParams[file.uid] ? 'ep:edit-pen' : 'ep:view'" |
|
||||||
class="icon edit" |
|
||||||
:size="15" |
|
||||||
@click="changeFileState(file)" |
|
||||||
v-show="file.status !== 'fail' && iswrite" |
|
||||||
/> |
|
||||||
<Icon icon="ep:delete" class="icon close" :size="15" @click="remove(file, index)" /> |
|
||||||
<section class="program" :style="{ '--p': `${processData[file.uid]}%` }"> </section> |
|
||||||
</section> |
|
||||||
<section class="emty" v-else> |
|
||||||
<Icon icon="svg-icon:customs-empty" :size="40" /> |
|
||||||
<span>暂无文件</span> |
|
||||||
</section> |
|
||||||
</section> |
|
||||||
<el-upload |
|
||||||
:file-list="fileList" |
|
||||||
:action="uploadUrl" |
|
||||||
:data="uploadExtendParams" |
|
||||||
:http-request="httpRequest" |
|
||||||
:auto-upload="false" |
|
||||||
:on-change="onChange" |
|
||||||
:on-success="onSuccess" |
|
||||||
:on-error="onError" |
|
||||||
:on-progress="onProgress" |
|
||||||
multiple |
|
||||||
:limit="limit > 0 ? limit : ''" |
|
||||||
:show-file-list="false" |
|
||||||
:accept="accept" |
|
||||||
ref="uploadRef" |
|
||||||
:on-exceed="exceed" |
|
||||||
> |
|
||||||
<section class="upload-button-group"> |
|
||||||
<section class="upload-button mr10px"> |
|
||||||
<Icon icon="svg-icon:customs-upload" :size="18" class="mr-5px" /> |
|
||||||
选取文件 |
|
||||||
</section> |
|
||||||
<section class="button" v-if="showUploadButton" @click.stop="upload"> |
|
||||||
<Icon icon="ep:upload-filled" :size="18" class="mr-5px" /> |
|
||||||
上传 |
|
||||||
</section> |
|
||||||
</section> |
|
||||||
</el-upload> |
|
||||||
|
|
||||||
<section class="upload-loading" v-show="uploadLoading"> |
|
||||||
<div class="loading"> <span></span><span></span><span></span><span></span> </div> |
|
||||||
</section> |
|
||||||
</main> |
|
||||||
</template> |
|
||||||
|
|
||||||
<script lang="ts" setup> |
|
||||||
import { useUpload } from '@/components/UploadFile/src/useUpload' |
|
||||||
import { getFileIcon, niceBytes } from '@/utils/filestate' |
|
||||||
import { formatDate } from '@/utils/formatTime' |
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus' |
|
||||||
defineOptions({ |
|
||||||
name: 'InnerUploader' |
|
||||||
}) |
|
||||||
|
|
||||||
const uploadLoading = ref(false) |
|
||||||
|
|
||||||
const emit = defineEmits(['handlerRemove', 'handlerSuccess', 'hanlderWriteState']) |
|
||||||
|
|
||||||
// 上传额外参数 |
|
||||||
const uploadExtendParams = (file: any) => { |
|
||||||
return { writeAble: extendParams.value[file.uid] } |
|
||||||
} |
|
||||||
|
|
||||||
const props = defineProps({ |
|
||||||
/** |
|
||||||
* 文件列表 |
|
||||||
* @default Array |
|
||||||
*/ |
|
||||||
uploadList: { |
|
||||||
type: Array, |
|
||||||
default: () => [] |
|
||||||
}, |
|
||||||
limit: { |
|
||||||
type: Number, |
|
||||||
default: '' |
|
||||||
}, |
|
||||||
/** |
|
||||||
* 是否可已进行文件权限编辑 |
|
||||||
* @default true |
|
||||||
*/ |
|
||||||
iswrite: { |
|
||||||
type: Boolean, |
|
||||||
default: true |
|
||||||
}, |
|
||||||
accept: { |
|
||||||
type: String, |
|
||||||
default: '*' |
|
||||||
}, |
|
||||||
showUploadButton: { |
|
||||||
type: Boolean, |
|
||||||
default: false |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
watch( |
|
||||||
() => props.uploadList, |
|
||||||
(newVal) => { |
|
||||||
fileList.value = newVal |
|
||||||
}, |
|
||||||
{ deep: true } |
|
||||||
) |
|
||||||
|
|
||||||
const fileList: any = ref([]) |
|
||||||
|
|
||||||
// 额外参数 |
|
||||||
const extendParams = ref({}) |
|
||||||
|
|
||||||
// el-upload组件 |
|
||||||
const uploadRef = ref() |
|
||||||
|
|
||||||
// 自定义上传地址与方式 |
|
||||||
const { uploadUrl, httpRequest } = useUpload() |
|
||||||
|
|
||||||
// 文件状态改变 |
|
||||||
const onChange = (file: any) => { |
|
||||||
// console.log(Math.round(file.raw.size / 1024 / 1024) + 'MB') |
|
||||||
if (props.accept !== '*') { |
|
||||||
if (props.accept.split(',').findIndex((i) => file.name.includes(i)) == -1) { |
|
||||||
unref(uploadRef).handleRemove(file) |
|
||||||
ElMessage({ message: `${file.name}文件格式不正确`, type: 'error' }) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
if (file.status === 'ready') { |
|
||||||
extendParams.value[file.uid] = false |
|
||||||
fileList.value.push(file) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 更改文件属性 |
|
||||||
const changeFileState = (file: any) => { |
|
||||||
extendParams.value[file.uid] = !extendParams.value[file.uid] |
|
||||||
if (file.status === 'success') { |
|
||||||
emit('hanlderWriteState', file) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 上传错误的处理 |
|
||||||
const onError = (e: any, file) => { |
|
||||||
ElMessage({ message: `${file.name}上传失败,${e}`, type: 'error' }) |
|
||||||
// unref(uploadRef).handleRemove(file) |
|
||||||
} |
|
||||||
|
|
||||||
// 移除文件 |
|
||||||
const remove = (file: any, index: any) => { |
|
||||||
if (file.status === 'success') { |
|
||||||
ElMessageBox.alert(`确认移除${file.name}吗?`, '删除', { |
|
||||||
confirmButtonText: '确认', |
|
||||||
showCancelButton: true, |
|
||||||
cancelButtonText: '取消', |
|
||||||
distinguishCancelAndClose: true, |
|
||||||
showClose: false |
|
||||||
}) |
|
||||||
.then(() => { |
|
||||||
fileList.value.splice(index, 1) |
|
||||||
emit('handlerRemove', file) |
|
||||||
}) |
|
||||||
.catch(() => {}) |
|
||||||
return |
|
||||||
} |
|
||||||
fileList.value.splice(index, 1) |
|
||||||
unref(uploadRef).handleRemove(file) |
|
||||||
} |
|
||||||
|
|
||||||
// 触发组件el的上传事件 |
|
||||||
const upload = () => { |
|
||||||
if (fileList.value.filter((file) => file.status == 'ready').length > 0) { |
|
||||||
uploadLoading.value = true |
|
||||||
unref(uploadRef).submit() |
|
||||||
return |
|
||||||
} |
|
||||||
ElMessage('暂无可上传的文件') |
|
||||||
} |
|
||||||
|
|
||||||
// 成功回调暂存 |
|
||||||
const successMap: any = ref([]) |
|
||||||
|
|
||||||
// 上传成功的回调 |
|
||||||
const onSuccess = (response: any) => { |
|
||||||
delete processData.value[response.uid] |
|
||||||
successMap.value.push(response) |
|
||||||
if (fileList.value.filter((file) => file.status == 'ready').length == 0) { |
|
||||||
uploadLoading.value = false |
|
||||||
emit('handlerSuccess', successMap.value) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// 设置不同状态文件的背景 |
|
||||||
const setFileState = (file: any) => { |
|
||||||
const colorlist = { |
|
||||||
success: '#89ffc3', |
|
||||||
fail: '#ffd2d2' |
|
||||||
} |
|
||||||
return { backgroundColor: colorlist[file.status] } |
|
||||||
} |
|
||||||
|
|
||||||
// 进度条对象 |
|
||||||
const processData = ref({}) |
|
||||||
// 上传进度 |
|
||||||
const onProgress = (UploadProgressEvent, uploadFile) => { |
|
||||||
// console.log('UploadProgressEvent', UploadProgressEvent) |
|
||||||
processData.value[uploadFile.uid] = UploadProgressEvent.percent |
|
||||||
} |
|
||||||
|
|
||||||
function exceed() { |
|
||||||
ElMessage({ |
|
||||||
message: `只允许上传${props.limit}个文件` |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
onMounted(() => { |
|
||||||
fileList.value = props.uploadList |
|
||||||
}) |
|
||||||
|
|
||||||
onBeforeUnmount(() => { |
|
||||||
fileList.value = [] |
|
||||||
unref(uploadRef).clearFiles() |
|
||||||
}) |
|
||||||
|
|
||||||
onActivated(() => { |
|
||||||
console.log('onActivated') |
|
||||||
}) |
|
||||||
|
|
||||||
// 在组件撒上暴露方法 |
|
||||||
defineExpose({ |
|
||||||
upload |
|
||||||
}) |
|
||||||
</script> |
|
||||||
|
|
||||||
<style lang="scss" scoped> |
|
||||||
* { |
|
||||||
box-sizing: border-box; |
|
||||||
} |
|
||||||
:deep(.el-upload-list__item:hover) { |
|
||||||
background-color: transparent !important; |
|
||||||
} |
|
||||||
:deep(.el-upload) { |
|
||||||
flex-flow: column; |
|
||||||
gap: 10px; |
|
||||||
align-items: flex-start; |
|
||||||
} |
|
||||||
.file-list { |
|
||||||
width: 220px; |
|
||||||
max-height: 60vh; |
|
||||||
overflow-y: scroll; |
|
||||||
// margin: 10px 0; |
|
||||||
margin-bottom: 10px; |
|
||||||
display: block; |
|
||||||
border-radius: 6px; |
|
||||||
border: 1px solid #eff2f5; |
|
||||||
background: #fcfdfd; |
|
||||||
padding: 8px 6px; |
|
||||||
} |
|
||||||
.file { |
|
||||||
// width: fit-content; |
|
||||||
margin-bottom: 2px; |
|
||||||
padding: 4px 10px; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
cursor: pointer; |
|
||||||
border-radius: 6px; |
|
||||||
position: relative; |
|
||||||
justify-content: center; |
|
||||||
transition: 0.2s; |
|
||||||
.info { |
|
||||||
flex: 1; |
|
||||||
overflow: hidden; |
|
||||||
display: flex; |
|
||||||
flex-flow: column nowrap; |
|
||||||
.name { |
|
||||||
font-weight: bold; |
|
||||||
font-size: small; |
|
||||||
letter-spacing: 1px; |
|
||||||
white-space: nowrap; |
|
||||||
overflow: hidden; |
|
||||||
text-overflow: ellipsis; |
|
||||||
} |
|
||||||
.size { |
|
||||||
font-size: 12px; |
|
||||||
} |
|
||||||
.time { |
|
||||||
font-size: 12px; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.state { |
|
||||||
font-size: small; |
|
||||||
} |
|
||||||
.icon { |
|
||||||
padding: 2px; |
|
||||||
border-radius: 50%; |
|
||||||
transition: 0.2s; |
|
||||||
} |
|
||||||
// .edit:hover { |
|
||||||
// background-color: #46c9f7; |
|
||||||
// box-shadow: 0 0 2px 0 #144d61; |
|
||||||
// } |
|
||||||
.close:hover, |
|
||||||
.edit:hover { |
|
||||||
// background-color: #f83f2a; |
|
||||||
// box-shadow: 0 0 2px 0 #640404; |
|
||||||
filter: drop-shadow(0 0 1px #000000ab); |
|
||||||
} |
|
||||||
&:hover { |
|
||||||
background-color: #fff; |
|
||||||
box-shadow: 0 0 4px 0px #ccc; |
|
||||||
border-radius: 4px; |
|
||||||
} |
|
||||||
.program { |
|
||||||
position: absolute; |
|
||||||
bottom: 0%; |
|
||||||
// border-radius: 999px; |
|
||||||
background-color: #89ffc2a0; |
|
||||||
left: 0; |
|
||||||
width: var(--p); |
|
||||||
// width: 100%; |
|
||||||
transition: 0.2s; |
|
||||||
height: 100%; |
|
||||||
} |
|
||||||
} |
|
||||||
.emty { |
|
||||||
width: 100%; |
|
||||||
display: flex; |
|
||||||
flex-flow: column nowrap; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
gap: 5px; |
|
||||||
color: #9ea3b4; |
|
||||||
} |
|
||||||
.upload-button-group { |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
} |
|
||||||
.upload-button { |
|
||||||
display: flex; |
|
||||||
padding: 5px 10px; |
|
||||||
color: #00a3ff; |
|
||||||
justify-content: center; |
|
||||||
align-items: center; |
|
||||||
gap: 4px; |
|
||||||
border-radius: 6px; |
|
||||||
background: #f1faff; |
|
||||||
border: 1px dashed #00a3ff; |
|
||||||
&:hover { |
|
||||||
border: 1px solid #00a3ff; |
|
||||||
} |
|
||||||
} |
|
||||||
.upload-container { |
|
||||||
width: fit-content; |
|
||||||
position: relative; |
|
||||||
.upload-loading { |
|
||||||
position: absolute; |
|
||||||
top: 0; |
|
||||||
left: 0; |
|
||||||
width: 100%; |
|
||||||
height: 100%; |
|
||||||
background-color: transparent; |
|
||||||
z-index: 100; |
|
||||||
display: flex; |
|
||||||
flex-flow: row nowrap; |
|
||||||
align-items: center; |
|
||||||
justify-content: center; |
|
||||||
background-color: #ffffff84; |
|
||||||
.loading { |
|
||||||
--w: 13ch; |
|
||||||
// font-weight: bold; |
|
||||||
// font-family: monospace; |
|
||||||
font-size: medium; |
|
||||||
letter-spacing: var(--w); |
|
||||||
width: var(--w); |
|
||||||
overflow: hidden; |
|
||||||
white-space: nowrap; |
|
||||||
color: #0000; |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
animation: c10 2s infinite linear; |
|
||||||
&:before { |
|
||||||
content: '文件上传中...'; |
|
||||||
} |
|
||||||
|
|
||||||
@keyframes c10 { |
|
||||||
9.09% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) -10px #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
18.18% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) -10px #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
27.27% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) -10px #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
36.36% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) -10px #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
45.45% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) -10px #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
54.54% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) -10px #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
63.63% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) -10px #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
72.72% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) -10px #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
81.81% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) -10px #000, |
|
||||||
calc(-9 * var(--w)) 0 #000; |
|
||||||
} |
|
||||||
|
|
||||||
90.90% { |
|
||||||
text-shadow: |
|
||||||
calc(0 * var(--w)) 0 #000, |
|
||||||
calc(-1 * var(--w)) 0 #000, |
|
||||||
calc(-2 * var(--w)) 0 #000, |
|
||||||
calc(-3 * var(--w)) 0 #000, |
|
||||||
calc(-4 * var(--w)) 0 #000, |
|
||||||
calc(-5 * var(--w)) 0 #000, |
|
||||||
calc(-6 * var(--w)) 0 #000, |
|
||||||
calc(-7 * var(--w)) 0 #000, |
|
||||||
calc(-8 * var(--w)) 0 #000, |
|
||||||
calc(-9 * var(--w)) -10px #000; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
</style> |
|
@ -0,0 +1,182 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import position from '@/assets/imgs/position.png' |
||||||
|
|
||||||
|
const show = ref(false) |
||||||
|
const AMap = (window as any).AMap |
||||||
|
let map: any = null |
||||||
|
const center = ref() |
||||||
|
const loading = ref(false) |
||||||
|
let mapSearch: any = null |
||||||
|
const address = ref() |
||||||
|
const addressInfo = ref() |
||||||
|
const message = useMessage() |
||||||
|
|
||||||
|
const open = async (param) => { |
||||||
|
show.value = true |
||||||
|
await nextTick(() => { |
||||||
|
init(param) |
||||||
|
}) |
||||||
|
} |
||||||
|
const emits = defineEmits(['success']) |
||||||
|
|
||||||
|
const init = (param) => { |
||||||
|
loading.value = true |
||||||
|
navigator.geolocation.getCurrentPosition((position) => { |
||||||
|
center.value = [position.coords.longitude, position.coords.latitude] |
||||||
|
map = new AMap.Map('map-container', { |
||||||
|
center: center.value, // 设置地图中心点坐标 |
||||||
|
zoom: 14, // 设置地图缩放级别 |
||||||
|
viewMode: '2D' |
||||||
|
}) |
||||||
|
map.on('complete', () => { |
||||||
|
const logo = document.getElementsByClassName('amap-logo')[0] |
||||||
|
const copyRight = document.getElementsByClassName('amap-copyright')[0] |
||||||
|
logo.setAttribute('style', 'display:none !important') |
||||||
|
copyRight.setAttribute('style', 'display:none !important') |
||||||
|
}) |
||||||
|
AMap.plugin('AMap.Autocomplete', () => { |
||||||
|
mapSearch = new AMap.Autocomplete({ |
||||||
|
city: '锦州' |
||||||
|
}) |
||||||
|
}) |
||||||
|
if (param) { |
||||||
|
address.value = param.name |
||||||
|
addressInfo.value = param |
||||||
|
addMarker() |
||||||
|
} |
||||||
|
}) |
||||||
|
loading.value = false |
||||||
|
} |
||||||
|
|
||||||
|
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => { |
||||||
|
mapSearch.search(queryString, (status, result) => { |
||||||
|
if (status === 'complete' && result.info === 'OK') { |
||||||
|
cb(result.tips) |
||||||
|
} |
||||||
|
}) |
||||||
|
cb([]) |
||||||
|
} |
||||||
|
|
||||||
|
const handleSelect = (item: Record<string, any>) => { |
||||||
|
address.value = item.name |
||||||
|
addressInfo.value = item |
||||||
|
addMarker() |
||||||
|
} |
||||||
|
|
||||||
|
const addMarker = () => { |
||||||
|
clearMarker() |
||||||
|
const locate = [addressInfo.value.location.lng, addressInfo.value.location.lat] |
||||||
|
const icon = new AMap.Icon({ |
||||||
|
image: position, |
||||||
|
size: new AMap.Size(50, 50), |
||||||
|
imageSize: new AMap.Size(50, 50) |
||||||
|
}) |
||||||
|
const marker = new AMap.Marker({ |
||||||
|
icon, |
||||||
|
position: locate, |
||||||
|
offset: new AMap.Pixel(-25, -45) |
||||||
|
}) |
||||||
|
const infoWindow = new AMap.InfoWindow({ |
||||||
|
anchor: 'top-center', |
||||||
|
content: `<section class="flex flex-col gap-4px"><span class="font-bold">${addressInfo.value.name ?? ''}</span><span>${addressInfo.value.district || ''}${addressInfo.value.address}</span></section>` |
||||||
|
}) |
||||||
|
marker.on('click', () => { |
||||||
|
infoWindow.open(map, locate) |
||||||
|
}) |
||||||
|
infoWindow.open(map, locate) |
||||||
|
map.setCenter(locate) |
||||||
|
map.add(marker) |
||||||
|
} |
||||||
|
|
||||||
|
const clearMarker = () => { |
||||||
|
map.clearMap() |
||||||
|
} |
||||||
|
|
||||||
|
const confirm = () => { |
||||||
|
emits('success', addressInfo.value) |
||||||
|
reset() |
||||||
|
message.success('操作成功') |
||||||
|
show.value = false |
||||||
|
} |
||||||
|
|
||||||
|
const reset = () => { |
||||||
|
address.value = '' |
||||||
|
addressInfo.value = null |
||||||
|
clearMarker() |
||||||
|
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
init() |
||||||
|
}) |
||||||
|
|
||||||
|
defineExpose({ |
||||||
|
open |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<Dialog v-model="show" title="选择地址" top="5vh" width="80vw" height="60vh"> |
||||||
|
<section id="map-container" v-loading="loading"> |
||||||
|
<section class="search-wrapper"> |
||||||
|
<el-autocomplete |
||||||
|
v-model="address" |
||||||
|
:fetch-suggestions="querySearchAsync" |
||||||
|
placeholder="请输入地址" |
||||||
|
@select="handleSelect" |
||||||
|
:debounce="500" |
||||||
|
popper-class="address-suggestion" |
||||||
|
> |
||||||
|
<template #default="{ item }"> |
||||||
|
<section class="flex gap-5px items-center suggestion"> |
||||||
|
<Icon icon="ep:location-filled" :size="20" /> |
||||||
|
<section class="flex flex-col gap-4px"> |
||||||
|
<section class="font-bold">{{ item.name }}</section> |
||||||
|
<section class="address">{{ `${item.district}${item.address}` }}</section> |
||||||
|
</section> |
||||||
|
</section> |
||||||
|
</template> |
||||||
|
</el-autocomplete> |
||||||
|
<el-button type="primary" @click="confirm"> |
||||||
|
<Icon icon="ep:select" class="mr-4px" /> |
||||||
|
确认 |
||||||
|
</el-button> |
||||||
|
</section> |
||||||
|
</section> |
||||||
|
</Dialog> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.search-wrapper { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
z-index: 9999; |
||||||
|
padding: 10px; |
||||||
|
display: flex; |
||||||
|
flex-flow: row nowrap; |
||||||
|
gap: 8px; |
||||||
|
} |
||||||
|
.map-wrapper { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
#map-container { |
||||||
|
width: 100%; |
||||||
|
height: 80vh; |
||||||
|
} |
||||||
|
.address-suggestion { |
||||||
|
.suggestion { |
||||||
|
.address { |
||||||
|
width: 200px; |
||||||
|
white-space: wrap; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
:deep(.amap-logo) { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
:deep(.amap-copyright) { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
</style> |
@ -1,43 +1,101 @@ |
|||||||
<script setup lang="ts"> |
<script setup lang="ts"> |
||||||
const show = ref(false) |
const show = ref(true) |
||||||
const TMap = (window as any).qq.maps |
const TMap = (window as any).TMap |
||||||
let map: any = null |
let map: any = null |
||||||
|
const center = ref() |
||||||
|
const loading = ref(false) |
||||||
|
let mapSearch: any = null |
||||||
//当前位置经纬度 |
//当前位置经纬度 |
||||||
const coordinate = reactive({ |
const coordinate = reactive({ |
||||||
latitude: undefined, |
latitude: undefined, |
||||||
longitude: undefined, |
longitude: undefined, |
||||||
address: '' |
address: '' |
||||||
} as any) |
} as any) |
||||||
|
const address = ref('') |
||||||
|
|
||||||
const open = () => { |
const open = async () => { |
||||||
show.value = true |
show.value = true |
||||||
|
} |
||||||
|
|
||||||
|
const init = () => { |
||||||
|
loading.value = true |
||||||
navigator.geolocation.getCurrentPosition((position) => { |
navigator.geolocation.getCurrentPosition((position) => { |
||||||
coordinate.latitude = position.coords.latitude |
coordinate.latitude = position.coords.latitude |
||||||
coordinate.longitude = position.coords.longitude |
coordinate.longitude = position.coords.longitude |
||||||
|
center.value = new TMap.LatLng(coordinate.latitude, coordinate.longitude) |
||||||
|
map = new TMap.Map(document.getElementById('tencent-container'), { |
||||||
|
center: center.value, // 设置地图中心点坐标 |
||||||
|
zoom: 14, // 设置地图缩放级别 |
||||||
|
viewMode: '2D', |
||||||
|
disableDefaultUI: false |
||||||
|
}) |
||||||
|
const a = document.querySelector( |
||||||
|
'canvas+div:last-child div:last-child div:first-child [style="margin: 1px; display: flex; align-items: center; user-select: none;"]' |
||||||
|
) |
||||||
|
mapSearch = new TMap.service.Search() |
||||||
|
a?.setAttribute('style', 'display:none') |
||||||
}) |
}) |
||||||
nextTick(() => { |
loading.value = false |
||||||
init() |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
const init = () => { |
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => { |
||||||
const center = new TMap.LatLng(coordinate.latitude, coordinate.longitude) |
mapSearch.searchRegion({ |
||||||
map = new TMap.Map(document.getElementById('tencent-container'), { |
keyword: queryString, |
||||||
center: center, // 设置地图中心点坐标 |
center: center.value, |
||||||
zoom: 11, // 设置地图缩放级别 |
autoExtend: true, |
||||||
viewMode: '2D' |
servicesk: 'Lut0q8OhqGkXr6t7dOvxuIhUUf0M3ZzD', |
||||||
|
success: (res: any) => { |
||||||
|
console.log(res) |
||||||
|
} |
||||||
}) |
}) |
||||||
|
cb([]) |
||||||
|
} |
||||||
|
|
||||||
|
const handleSelect = (item: Record<string, any>) => { |
||||||
|
console.log(item) |
||||||
} |
} |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
init() |
||||||
|
}) |
||||||
|
|
||||||
defineExpose({ |
defineExpose({ |
||||||
open |
open |
||||||
}) |
}) |
||||||
</script> |
</script> |
||||||
|
|
||||||
<template> |
<template> |
||||||
<Dialog v-model="show" title="选择地址"> |
<Dialog v-model="show" title="选择地址" top="5vh" width="80vw" height="60vh"> |
||||||
<section id="tencent-container"></section> |
<section id="tencent-container" v-loading="loading"> |
||||||
|
<section class="search-wrapper"> |
||||||
|
<el-autocomplete |
||||||
|
v-model="address" |
||||||
|
:fetch-suggestions="querySearchAsync" |
||||||
|
placeholder="请输入地址" |
||||||
|
@select="handleSelect" |
||||||
|
:debounce="500" |
||||||
|
/> |
||||||
|
</section> |
||||||
|
</section> |
||||||
</Dialog> |
</Dialog> |
||||||
</template> |
</template> |
||||||
|
|
||||||
<style scoped lang="scss"></style> |
<style scoped lang="scss"> |
||||||
|
.search-wrapper { |
||||||
|
position: absolute; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
z-index: 9999; |
||||||
|
padding: 10px; |
||||||
|
} |
||||||
|
.map-wrapper { |
||||||
|
width: 100%; |
||||||
|
height: 100%; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
#tencent-container { |
||||||
|
img { |
||||||
|
display: none; |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
|
@ -1,202 +0,0 @@ |
|||||||
<template> |
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
|
||||||
<el-form |
|
||||||
ref="formRef" |
|
||||||
:model="formData" |
|
||||||
:rules="formRules" |
|
||||||
label-width="100px" |
|
||||||
v-loading="formLoading" |
|
||||||
> |
|
||||||
<el-form-item label="选择企业" prop="enterpriseId"> |
|
||||||
<el-select |
|
||||||
v-model="formData.enterpriseId" |
|
||||||
filterable |
|
||||||
remote |
|
||||||
reserve-keyword |
|
||||||
placeholder="请输入企业名称搜索" |
|
||||||
:remote-method="remoteSearchEnterprise" |
|
||||||
:loading="enterpriseLoading" |
|
||||||
> |
|
||||||
<el-option |
|
||||||
v-for="item in enterpriseOptions" |
|
||||||
:key="item.id" |
|
||||||
:label="item.enterprisesName" |
|
||||||
:value="item.id" |
|
||||||
/> |
|
||||||
</el-select> |
|
||||||
</el-form-item> |
|
||||||
<el-form-item label="资质名称" prop="qualificationName"> |
|
||||||
<el-select v-model="formData.qualificationName" placeholder="请选择资质名称"> |
|
||||||
<el-option |
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.ENTERPRISES_QUA)" |
|
||||||
:key="dict.value" |
|
||||||
:label="dict.label" |
|
||||||
:value="dict.value" |
|
||||||
/> |
|
||||||
</el-select> |
|
||||||
|
|
||||||
</el-form-item> |
|
||||||
<el-form-item label="资质到期日期" prop="expiryDate"> |
|
||||||
<el-date-picker |
|
||||||
v-model="formData.expiryDate" |
|
||||||
type="date" |
|
||||||
value-format="x" |
|
||||||
placeholder="选择资质到期日期" |
|
||||||
/> |
|
||||||
</el-form-item> |
|
||||||
<!-- <el-form-item label="资质描述" prop="qualificationDescription"> |
|
||||||
<Editor v-model="formData.qualificationDescription" height="150px" /> |
|
||||||
</el-form-item> --> |
|
||||||
|
|
||||||
<!-- <el-form-item label="办理日期" prop="handleDate"> |
|
||||||
<el-date-picker |
|
||||||
v-model="formData.handleDate" |
|
||||||
type="date" |
|
||||||
value-format="x" |
|
||||||
placeholder="选择办理日期" |
|
||||||
/> |
|
||||||
</el-form-item> --> |
|
||||||
<el-form-item label="资质编号" prop="enterpriseAuth"> |
|
||||||
<el-input v-model="formData.enterpriseAuth" placeholder="请输入资质编号" /> |
|
||||||
</el-form-item> |
|
||||||
</el-form> |
|
||||||
<el-form-item label="资质图片" prop="files"> |
|
||||||
<UploadImgs v-model="fileIds" :limit="1" /> |
|
||||||
</el-form-item> |
|
||||||
<template #footer> |
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button> |
|
||||||
</template> |
|
||||||
</Dialog> |
|
||||||
</template> |
|
||||||
<script setup lang="ts"> |
|
||||||
import { EnterpriseQualificationApi, EnterpriseQualificationVO } from '@/api/qualification' |
|
||||||
import { EnterprisesApi } from '@/api/enterprises' |
|
||||||
/** 企业资质 表单 */ |
|
||||||
defineOptions({ name: 'EnterpriseQualificationForm' }) |
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' |
|
||||||
const { t } = useI18n() // 国际化 |
|
||||||
const message = useMessage() // 消息弹窗 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示 |
|
||||||
const dialogTitle = ref('') // 弹窗的标题 |
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改 |
|
||||||
const formData = ref({ |
|
||||||
id: undefined, |
|
||||||
enterpriseId: undefined, |
|
||||||
qualificationName: undefined, |
|
||||||
expiryDate: undefined, |
|
||||||
qualificationDescription: undefined, |
|
||||||
updateBy: undefined, |
|
||||||
createBy: undefined, |
|
||||||
handleDate: undefined, |
|
||||||
enterpriseAuth: undefined, |
|
||||||
files: [] as any[], |
|
||||||
}) |
|
||||||
const formRules = reactive({ |
|
||||||
qualificationName: [{ required: true, message: '资质名称,例如:排污许可证、环保合格证不能为空', trigger: 'blur' }], |
|
||||||
}) |
|
||||||
const formRef = ref() // 表单 Ref |
|
||||||
const fileIds=ref([]) |
|
||||||
|
|
||||||
// 添加企业搜索相关的响应式变量 |
|
||||||
const enterpriseLoading = ref(false) |
|
||||||
const enterpriseOptions = ref([]) |
|
||||||
|
|
||||||
// 远程搜索企业的方法 |
|
||||||
const remoteSearchEnterprise = async (query: string) => { |
|
||||||
if (query === '') { |
|
||||||
enterpriseOptions.value = [] |
|
||||||
return |
|
||||||
} |
|
||||||
enterpriseLoading.value = true |
|
||||||
try { |
|
||||||
const res = await EnterprisesApi.getEnterprisesPage({ |
|
||||||
pageNo: 1, |
|
||||||
pageSize: 5, |
|
||||||
enterprisesName: query |
|
||||||
}) |
|
||||||
enterpriseOptions.value = res.list |
|
||||||
} finally { |
|
||||||
enterpriseLoading.value = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** 打开弹窗 */ |
|
||||||
const open = async (type: string, id?: number,enterpriseId?:number) => { |
|
||||||
dialogVisible.value = true |
|
||||||
dialogTitle.value = t('action.' + type) |
|
||||||
formType.value = type |
|
||||||
resetForm() |
|
||||||
fileIds.value=[] |
|
||||||
// 修改时,设置数据 |
|
||||||
if (id) { |
|
||||||
formLoading.value = true |
|
||||||
try { |
|
||||||
let res1 = await EnterpriseQualificationApi.getEnterpriseQualification(id) |
|
||||||
formData.value = res1; |
|
||||||
// 确保资质名称是数字类型 |
|
||||||
formData.value.qualificationName = parseInt(formData.value.qualificationName) |
|
||||||
fileIds.value = res1.files |
|
||||||
// 根据企业ID获取企业信息并设置下拉框选项 |
|
||||||
// 获取企业详情 |
|
||||||
|
|
||||||
} finally { |
|
||||||
formLoading.value = false |
|
||||||
} |
|
||||||
} |
|
||||||
if (formData.value.enterpriseId || enterpriseId) { |
|
||||||
formData.value.enterpriseId = formData.value.enterpriseId || enterpriseId |
|
||||||
const res = await EnterprisesApi.getEnterprises(formData.value.enterpriseId) |
|
||||||
enterpriseOptions.value = [res] |
|
||||||
} |
|
||||||
} |
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
|
||||||
|
|
||||||
/** 提交表单 */ |
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 |
|
||||||
const submitForm = async () => { |
|
||||||
// 校验表单 |
|
||||||
await formRef.value.validate() |
|
||||||
// 提交请求 |
|
||||||
formLoading.value = true |
|
||||||
try { |
|
||||||
formData.value.files = fileIds.value.map(f => ( f.id )) |
|
||||||
const data = formData.value as unknown as EnterpriseQualificationVO |
|
||||||
|
|
||||||
if (formType.value === 'create') { |
|
||||||
await EnterpriseQualificationApi.createEnterpriseQualification(data) |
|
||||||
message.success(t('common.createSuccess')) |
|
||||||
} else { |
|
||||||
await EnterpriseQualificationApi.updateEnterpriseQualification(data) |
|
||||||
message.success(t('common.updateSuccess')) |
|
||||||
} |
|
||||||
dialogVisible.value = false |
|
||||||
// 发送操作成功的事件 |
|
||||||
emit('success') |
|
||||||
} finally { |
|
||||||
formLoading.value = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** 重置表单 */ |
|
||||||
const resetForm = () => { |
|
||||||
formData.value = { |
|
||||||
id: undefined, |
|
||||||
enterpriseId: undefined, |
|
||||||
qualificationName: undefined, |
|
||||||
expiryDate: undefined, |
|
||||||
qualificationDescription: undefined, |
|
||||||
updateBy: undefined, |
|
||||||
createBy: undefined, |
|
||||||
handleDate: undefined, |
|
||||||
enterpriseAuth: undefined, |
|
||||||
} |
|
||||||
formRef.value?.resetFields() |
|
||||||
} |
|
||||||
</script> |
|
@ -0,0 +1,141 @@ |
|||||||
|
<template> |
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible"> |
||||||
|
<el-form |
||||||
|
ref="formRef" |
||||||
|
:model="formData" |
||||||
|
:rules="formRules" |
||||||
|
v-loading="formLoading" |
||||||
|
label-width="auto" |
||||||
|
> |
||||||
|
<el-form-item label="选择企业" prop="enterpriseId" v-if="formData.id"> |
||||||
|
<span></span> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="资质名称" prop="qualificationName"> |
||||||
|
<el-select v-model="formData.qualificationName" placeholder="请选择资质名称"> |
||||||
|
<el-option |
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.ENTERPRISES_QUA)" |
||||||
|
:key="dict.value" |
||||||
|
:label="dict.label" |
||||||
|
:value="dict.value" |
||||||
|
/> |
||||||
|
</el-select> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="资质编号" prop="enterpriseAuth"> |
||||||
|
<el-input v-model="formData.enterpriseAuth" placeholder="请输入资质编号" /> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="到期时间" prop="expiryDate"> |
||||||
|
<el-date-picker |
||||||
|
v-model="formData.expiryDate" |
||||||
|
type="date" |
||||||
|
value-format="x" |
||||||
|
placeholder="请选择到期时间" |
||||||
|
class="!w-100%" |
||||||
|
/> |
||||||
|
</el-form-item> |
||||||
|
<el-form-item label="资质照片" prop="photo"> |
||||||
|
<FileUploader v-model="formData.photo" ref="fileUploaderRef" /> |
||||||
|
</el-form-item> |
||||||
|
</el-form> |
||||||
|
<template #footer> |
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button> |
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button> |
||||||
|
</template> |
||||||
|
</Dialog> |
||||||
|
</template> |
||||||
|
<script setup lang="ts"> |
||||||
|
import { EnterpriseQualificationApi } from '@/api/qualification' |
||||||
|
/** 企业资质 表单 */ |
||||||
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict' |
||||||
|
|
||||||
|
defineOptions({ name: 'Prove' }) |
||||||
|
const message = useMessage() // 消息弹窗 |
||||||
|
const fileUploaderRef = ref() |
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示 |
||||||
|
const dialogTitle = ref('新增资质') // 弹窗的标题 |
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用 |
||||||
|
const formData = ref({ |
||||||
|
id: undefined, |
||||||
|
enterpriseId: undefined, |
||||||
|
qualificationName: undefined, |
||||||
|
expiryDate: undefined, |
||||||
|
qualificationDescription: undefined, |
||||||
|
updateBy: undefined, |
||||||
|
createBy: undefined, |
||||||
|
handleDate: undefined, |
||||||
|
enterpriseAuth: undefined, |
||||||
|
photo: [] as any[] |
||||||
|
}) |
||||||
|
const formRules = reactive({ |
||||||
|
qualificationName: [{ required: true, message: '请选择资质名称', trigger: 'change' }], |
||||||
|
photo: [ |
||||||
|
{ |
||||||
|
required: true, |
||||||
|
validator: (rule, value, callback) => { |
||||||
|
if (value.length > 0) { |
||||||
|
callback() |
||||||
|
} else { |
||||||
|
callback(new Error('资质照片不能为空')) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
], |
||||||
|
enterpriseAuth: [{ required: true, message: '请输入资质编号', trigger: 'blur' }], |
||||||
|
expiryDate: [{ required: true, message: '请选择到期时间', trigger: 'change' }] |
||||||
|
}) |
||||||
|
const formRef = ref() // 表单 Ref |
||||||
|
|
||||||
|
/** 打开弹窗 */ |
||||||
|
const open = async (params) => { |
||||||
|
dialogVisible.value = true |
||||||
|
resetForm() |
||||||
|
if (params) { |
||||||
|
dialogTitle.value = '编辑资质' |
||||||
|
formData.value.enterpriseId = params.enterpriseId || undefined |
||||||
|
formData.value.id = params.id || undefined |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗 |
||||||
|
|
||||||
|
/** 提交表单 */ |
||||||
|
const emit = defineEmits(['success', 'addProve']) // 定义 success 事件,用于操作成功后的回调 |
||||||
|
const submitForm = async () => { |
||||||
|
// 校验表单 |
||||||
|
const validate = await formRef.value.validate() |
||||||
|
if (!validate) return |
||||||
|
formLoading.value = true |
||||||
|
await unref(fileUploaderRef).handlerUpload() |
||||||
|
const data: any = formData.value |
||||||
|
if (data.id) { |
||||||
|
data.files = data.photo.map((i) => i.id) |
||||||
|
await EnterpriseQualificationApi.updateEnterpriseQualification(data) |
||||||
|
emit('success') |
||||||
|
} else if (data.enterpriseId) { |
||||||
|
data.files = data.photo.map((i) => i.id) |
||||||
|
await EnterpriseQualificationApi.createEnterpriseQualification(data) |
||||||
|
emit('success') |
||||||
|
} else { |
||||||
|
data.files = data.photo |
||||||
|
emit('addProve', data) |
||||||
|
} |
||||||
|
message.success('操作成功') |
||||||
|
dialogVisible.value = false |
||||||
|
} |
||||||
|
|
||||||
|
/** 重置表单 */ |
||||||
|
const resetForm = () => { |
||||||
|
formData.value = { |
||||||
|
id: undefined, |
||||||
|
enterpriseId: undefined, |
||||||
|
qualificationName: undefined, |
||||||
|
expiryDate: undefined, |
||||||
|
qualificationDescription: undefined, |
||||||
|
updateBy: undefined, |
||||||
|
createBy: undefined, |
||||||
|
handleDate: undefined, |
||||||
|
enterpriseAuth: undefined, |
||||||
|
photo: [] as any[] |
||||||
|
} |
||||||
|
formRef.value?.resetFields() |
||||||
|
} |
||||||
|
</script> |
Loading…
Reference in new issue