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"> |
||||
const show = ref(false) |
||||
const TMap = (window as any).qq.maps |
||||
const show = ref(true) |
||||
const TMap = (window as any).TMap |
||||
let map: any = null |
||||
const center = ref() |
||||
const loading = ref(false) |
||||
let mapSearch: any = null |
||||
//当前位置经纬度 |
||||
const coordinate = reactive({ |
||||
latitude: undefined, |
||||
longitude: undefined, |
||||
address: '' |
||||
} as any) |
||||
const address = ref('') |
||||
|
||||
const open = () => { |
||||
const open = async () => { |
||||
show.value = true |
||||
} |
||||
|
||||
const init = () => { |
||||
loading.value = true |
||||
navigator.geolocation.getCurrentPosition((position) => { |
||||
coordinate.latitude = position.coords.latitude |
||||
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(() => { |
||||
init() |
||||
}) |
||||
loading.value = false |
||||
} |
||||
|
||||
const init = () => { |
||||
const center = new TMap.LatLng(coordinate.latitude, coordinate.longitude) |
||||
map = new TMap.Map(document.getElementById('tencent-container'), { |
||||
center: center, // 设置地图中心点坐标 |
||||
zoom: 11, // 设置地图缩放级别 |
||||
viewMode: '2D' |
||||
const querySearchAsync = (queryString: string, cb: (arg: any) => void) => { |
||||
mapSearch.searchRegion({ |
||||
keyword: queryString, |
||||
center: center.value, |
||||
autoExtend: true, |
||||
servicesk: 'Lut0q8OhqGkXr6t7dOvxuIhUUf0M3ZzD', |
||||
success: (res: any) => { |
||||
console.log(res) |
||||
} |
||||
}) |
||||
cb([]) |
||||
} |
||||
|
||||
const handleSelect = (item: Record<string, any>) => { |
||||
console.log(item) |
||||
} |
||||
|
||||
onMounted(() => { |
||||
init() |
||||
}) |
||||
|
||||
defineExpose({ |
||||
open |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<Dialog v-model="show" title="选择地址"> |
||||
<section id="tencent-container"></section> |
||||
<Dialog v-model="show" title="选择地址" top="5vh" width="80vw" height="60vh"> |
||||
<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> |
||||
</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