From 1f59958f80d0a851d29b890ccf52d3db44295d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=7E=E6=B5=B7=7E=E7=88=B1=E6=B5=B7=E7=88=B1?= =?UTF-8?q?=E6=B5=B7=7E=E5=8F=B3?= <1828712314@qq.com> Date: Wed, 26 Mar 2025 17:28:15 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=EF=BC=8C=E4=BC=98=E5=8C=96=E7=9B=B8=E5=85=B3=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E5=92=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 24 +- src/api/infra/file/index.ts | 5 + src/assets/imgs/position.png | Bin 0 -> 5892 bytes src/components/UploadFile/index.ts | 5 +- .../UploadFile/src/FileUploader.vue | 318 ++++++++++ src/components/UploadFile/src/InnerImage.vue | 501 --------------- .../UploadFile/src/InnerUploadImg.vue | 205 ------- .../UploadFile/src/InnerUploader.vue | 568 ------------------ src/components/UploadFile/src/UploadImgs.vue | 17 +- src/components/UploadFile/src/useUpload.ts | 2 +- src/layout/components/Menu/src/Menu.vue | 3 - src/styles/var.css | 2 +- src/styles/variables.scss | 18 +- .../enterprises/components/getGpsByAmap.vue | 182 ++++++ .../enterprises/components/getGpsByQq.vue | 88 ++- src/views/enterprises/update.vue | 169 +++++- .../EnterpriseQualificationForm.vue | 202 ------- src/views/qualification/index.vue | 4 +- src/views/qualification/prove.vue | 141 +++++ 19 files changed, 914 insertions(+), 1540 deletions(-) create mode 100644 src/assets/imgs/position.png create mode 100644 src/components/UploadFile/src/FileUploader.vue delete mode 100644 src/components/UploadFile/src/InnerImage.vue delete mode 100644 src/components/UploadFile/src/InnerUploadImg.vue delete mode 100644 src/components/UploadFile/src/InnerUploader.vue create mode 100644 src/views/enterprises/components/getGpsByAmap.vue delete mode 100644 src/views/qualification/EnterpriseQualificationForm.vue create mode 100644 src/views/qualification/prove.vue diff --git a/index.html b/index.html index e8d1042..6e13ea1 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,20 @@ -<!DOCTYPE html> +<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <link rel="icon" href="/favicon.png" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <!-- <script--> + <!-- charset="utf-8"--> + <!-- src="https://map.qq.com/api/gljs?v=1.exp&key=ZAPBZ-WDIW3-E4V3O-ODLEX-VBQ6Z-WLF7Z&libraries=service"--> + <!-- async--> + <!-- ></script>--> + <script + type="text/javascript" + src="https://webapi.amap.com/maps?v=1.4.15&key=1b5bc51917732f5b6df663e16c7caa25" + async + ></script> <title>%VITE_APP_TITLE%</title> </head> <body> @@ -18,7 +28,6 @@ align-items: center; flex-direction: column; background: url(/logingbk.png) no-repeat; - } .app-loading .app-loading-wrap { @@ -71,7 +80,7 @@ left: calc(50% - 20px); width: 40px; height: 40px; - border: 4px solid #89E1A8; + border: 4px solid #89e1a8; border-right: 0; border-top-color: transparent; border-radius: 50%; @@ -140,9 +149,10 @@ </div> </div> <script type="module" src="/src/main.ts"></script> - - <script charset="utf-8" src="https://map.qq.com/api/js?v=2.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script> - - + <script type="text/javascript"> + window._AMapSecurityConfig = { + securityJsCode: "c7b027f9ca5ca4b2eaadb5ac9046fd19", + } + </script> </body> </html> diff --git a/src/api/infra/file/index.ts b/src/api/infra/file/index.ts index 0e1b2e7..a7eb09c 100644 --- a/src/api/infra/file/index.ts +++ b/src/api/infra/file/index.ts @@ -43,3 +43,8 @@ export const createFile = (data: any) => { export const updateFile = (data: any) => { return request.upload({ url: '/infra/file/upload', data }) } + +// 批量上传 +export const batchUploadFile = (data: any) => { + return request.upload({ url: '/infra/file/uploads', data }) +} diff --git a/src/assets/imgs/position.png b/src/assets/imgs/position.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b8ab6c768c44d924b7a44777ac8f8d3e24f2e7 GIT binary patch literal 5892 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 literal 0 HcmV?d00001 diff --git a/src/components/UploadFile/index.ts b/src/components/UploadFile/index.ts index 1a4e10d..0ab28fb 100644 --- a/src/components/UploadFile/index.ts +++ b/src/components/UploadFile/index.ts @@ -2,7 +2,6 @@ import UploadImg from './src/UploadImg.vue' import UploadImgs from './src/UploadImgs.vue' import UploadFile from './src/UploadFile.vue' import UploadExcel from './src/UploadExcel.vue' +import FileUploader from './src/FileUploader.vue' -export { UploadImg, UploadImgs, UploadFile, UploadExcel} - - +export { UploadImg, UploadImgs, UploadFile, UploadExcel, FileUploader } diff --git a/src/components/UploadFile/src/FileUploader.vue b/src/components/UploadFile/src/FileUploader.vue new file mode 100644 index 0000000..7e7ccc7 --- /dev/null +++ b/src/components/UploadFile/src/FileUploader.vue @@ -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> diff --git a/src/components/UploadFile/src/InnerImage.vue b/src/components/UploadFile/src/InnerImage.vue deleted file mode 100644 index c6ae50f..0000000 --- a/src/components/UploadFile/src/InnerImage.vue +++ /dev/null @@ -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> diff --git a/src/components/UploadFile/src/InnerUploadImg.vue b/src/components/UploadFile/src/InnerUploadImg.vue deleted file mode 100644 index 3c2a418..0000000 --- a/src/components/UploadFile/src/InnerUploadImg.vue +++ /dev/null @@ -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> diff --git a/src/components/UploadFile/src/InnerUploader.vue b/src/components/UploadFile/src/InnerUploader.vue deleted file mode 100644 index fecfa50..0000000 --- a/src/components/UploadFile/src/InnerUploader.vue +++ /dev/null @@ -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> diff --git a/src/components/UploadFile/src/UploadImgs.vue b/src/components/UploadFile/src/UploadImgs.vue index 19aff5e..c811301 100644 --- a/src/components/UploadFile/src/UploadImgs.vue +++ b/src/components/UploadFile/src/UploadImgs.vue @@ -10,7 +10,7 @@ :drag="drag" :http-request="httpRequest" :limit="limit" - :multiple="false" + :multiple="limit > 1" :on-error="uploadError" :on-exceed="handleExceed" :on-success="uploadSuccess" @@ -18,7 +18,7 @@ > <div class="upload-empty" v-if="!disabled"> <slot name="empty"> - <Icon icon="ep:plus" /> + <Icon icon="ep:plus" :size="30" color="#ccc" /> <!-- <span>请上传图片</span> --> </slot> </div> @@ -27,11 +27,11 @@ <div class="upload-handle" @click.stop> <div class="handle-icon" @click="imagePreview(file.url!)"> - <Icon icon="ep:zoom-in" /> + <Icon icon="ep:zoom-in" :size="30" /> <span>查看</span> </div> <div v-if="!disabled" class="handle-icon" @click="handleRemove(file)"> - <Icon icon="ep:delete" /> + <Icon icon="ep:delete" :size="30" /> <span>删除</span> </div> </div> @@ -43,7 +43,7 @@ </div> </template> <script lang="ts" setup> -import type { UploadFile, UploadProps, UploadUserFile } from 'element-plus' +import type { UploadProps } from 'element-plus' import { ElNotification } from 'element-plus' import { createImageViewer } from '@/components/ImageViewer' @@ -82,7 +82,7 @@ const props = defineProps({ 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) + borderRadius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传(默认为 8px) }) const { uploadUrl, httpRequest } = useUpload() @@ -146,6 +146,7 @@ watch( }, { immediate: true, deep: true } ) + // 发送图片链接列表更新 const emitUpdateModelValue = () => { let result = fileList.value @@ -221,7 +222,7 @@ const handleExceed = () => { padding: 0; overflow: hidden; border: 1px dashed var(--el-border-color-darker); - border-radius: v-bind(borderradius); + border-radius: v-bind(borderRadius); &:hover { border: 1px dashed var(--el-color-primary); @@ -238,7 +239,7 @@ const handleExceed = () => { width: v-bind(width); height: v-bind(height); background-color: transparent; - border-radius: v-bind(borderradius); + border-radius: v-bind(borderRadius); } .upload-image { diff --git a/src/components/UploadFile/src/useUpload.ts b/src/components/UploadFile/src/useUpload.ts index 356da3a..5950bb7 100644 --- a/src/components/UploadFile/src/useUpload.ts +++ b/src/components/UploadFile/src/useUpload.ts @@ -43,7 +43,7 @@ export const useUpload = () => { }) } else { // 模式二:后端上传 - // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子 + // 重写 el-upload httpRequest 文件上传成功会走成功的钩子,失败走失败的钩子 return new Promise((resolve, reject) => { FileApi.updateFile({ file: options.file }) .then((res) => { diff --git a/src/layout/components/Menu/src/Menu.vue b/src/layout/components/Menu/src/Menu.vue index 94a1da4..095bf64 100644 --- a/src/layout/components/Menu/src/Menu.vue +++ b/src/layout/components/Menu/src/Menu.vue @@ -83,9 +83,6 @@ export default defineComponent({ <ElMenu defaultActive={unref(activeMenu)} mode={unref(menuMode)} - collapse={ - unref(layout) === 'top' || unref(layout) === 'cutMenu' ? false : unref(collapse) - } uniqueOpened={unref(layout) === 'top' ? false : unref(uniqueOpened)} backgroundColor="var(--left-menu-bg-color)" textColor="var(--left-menu-text-color)" diff --git a/src/styles/var.css b/src/styles/var.css index 54874e3..71ef275 100644 --- a/src/styles/var.css +++ b/src/styles/var.css @@ -3,7 +3,7 @@ --left-menu-max-width: 200px; - --left-menu-min-width: 64px; + --left-menu-min-width: 200px; --left-menu-bg-color: #001529; diff --git a/src/styles/variables.scss b/src/styles/variables.scss index 2cd8d73..808980d 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -42,9 +42,17 @@ $elNamespace: el; padding: 10px; } -.el-input__suffix{ - margin-right: 15px; +.el-overlay-dialog { + overflow: hidden; +} +.el-autocomplete-suggestion { + .el-autocomplete-suggestion__list { + display: flex; + flex-flow: column nowrap; + gap: 8px; + } + li { + line-height: normal; + padding: 8px 10px; + } } -.el-input-number.is-controls-right[class*=large] [class*=decrease], .el-input-number.is-controls-right[class*=large] [class*=increase]{ - --el-input-number-controls-height:50%; -} \ No newline at end of file diff --git a/src/views/enterprises/components/getGpsByAmap.vue b/src/views/enterprises/components/getGpsByAmap.vue new file mode 100644 index 0000000..5d37d4b --- /dev/null +++ b/src/views/enterprises/components/getGpsByAmap.vue @@ -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> diff --git a/src/views/enterprises/components/getGpsByQq.vue b/src/views/enterprises/components/getGpsByQq.vue index d0c8b33..d58d455 100644 --- a/src/views/enterprises/components/getGpsByQq.vue +++ b/src/views/enterprises/components/getGpsByQq.vue @@ -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> diff --git a/src/views/enterprises/update.vue b/src/views/enterprises/update.vue index b5092bf..9bce595 100644 --- a/src/views/enterprises/update.vue +++ b/src/views/enterprises/update.vue @@ -1,10 +1,10 @@ <template> <content-wrap> <section class="view-wrapper"> - <section class="w-70%"> + <section class="flex-1"> <section class="mb-40px"> <section class="title-wrapper"> 基本信息</section> - <el-form :rules="formRules" :model="formData" label-width="100px"> + <el-form :rules="formRules" :model="formData" ref="formRef" label-width="100px"> <section class="base-form"> <el-form-item label="企业名称" prop="enterprisesName"> <el-input v-model="formData.enterprisesName" placeholder="请输入企业名称" /> @@ -20,9 +20,14 @@ </el-select> </el-form-item> <el-form-item label="企业地址" prop="address"> - <el-input v-model="formData.address" placeholder="请选择企业地址" readonly @click="getGaps"> + <el-input + v-model="formData.address" + placeholder="请选择企业地址" + readonly + @click="getGaps" + > <template #append> - <Icon icon="ep:position" @click.stop="getGaps"/> + <Icon icon="ep:position" @click.stop="getGaps" /> </template> </el-input> </el-form-item> @@ -63,7 +68,13 @@ /> </el-form-item> <el-form-item label="企业照片" prop="photo" class="form-photo"> - <UploadImgs v-model="formData.photo" /> + <FileUploader + v-model="formData.photo" + :limit="6" + height="148px" + width="148px" + ref="fileUploadRef" + /> </el-form-item> <section class="form-sub-title"> 执法配置</section> <el-form-item label="执法人员" prop="userId"> @@ -102,30 +113,57 @@ </el-form> </section> </section> - <section class="flex-1"> + <section> <section class="title-wrapper"> 相关资质 </section> + <section class="prove-wrapper"> + <section class="prove" v-for="(prove, index) in proveList" :key="index"> + <el-image + :src="prove.files[0].url" + :preview-src-list="prove.files[0]" + :initial-index="0" + class="border-rounded-6px h-130px mb-12px" + /> + <section class="flex flex-col gap-4px items-center"> + <span class="font-bold"> + {{ getDictLabel(DICT_TYPE.ENTERPRISES_QUA, prove.qualificationName) }} + </span> + <span class="text-14px">{{ prove.enterpriseAuth }}</span> + <span class="text-14px color-#606266" v-if="prove.qualificationName != 99"> + {{ formatDate(prove.expiryDate, 'YYYY年M月D日') }}到期 + </span> + <span v-else>永久</span> + </section> + </section> + <section class="add-prove" @click="addProve"> + <Icon icon="ep:plus" :size="30" /> + </section> + </section> </section> </section> <section> - <el-button>保存</el-button> + <el-button @click="submit">保存</el-button> </section> </content-wrap> <GpsDialog ref="GpsDialogRef" @success="setGps" /> + <ProveForm ref="proveFormRef" @add-prove="setProve" /> </template> <script setup lang="ts"> -import { getStrDictOptions, DICT_TYPE } from '@/utils/dict' +import { getStrDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict' import { TagLibraryApi } from '@/api/system/taglibrary' import { getEnterpriseManager } from '@/api/system/user' -import { UploadImgs } from '@/components/UploadFile' -import GpsDialog from './components/getGpsByQq.vue' - +import GpsDialog from './components/getGpsByAmap.vue' +import ProveForm from '@/views/qualification/prove.vue' +import { formatDate } from '@/utils/formatTime' +import { EnterprisesApi } from '@/api/enterprises' /** 企业 表单 */ defineOptions({ name: 'UpdateEnterprises' }) //行业类别 const typeList = ref([] as any) const userList = ref([] as any) -const GpsDialogRef=ref() +const GpsDialogRef = ref() +const proveFormRef = ref() +const formRef = ref() const formData = ref({ id: undefined, departmentId: undefined, @@ -147,9 +185,9 @@ const formData = ref({ startUserSelectAssignees: undefined, enterpriseUserId: undefined, tags: undefined, - photo: undefined, + photo: [] as any, signRadius: 30 -}) +} as any) const formRules = reactive({ type: [{ required: true, message: '企业类型不能为空', trigger: 'change' }], enterprisesName: [{ required: true, message: '企业名称不能为空', trigger: 'blur' }], @@ -174,7 +212,13 @@ const formRules = reactive({ photo: [ { required: true, - message: '请上传企业照片', + validator: (rule, value, callback) => { + if (value.length > 0) { + callback() + } else { + callback(new Error('企业照片不能为空')) + } + }, trigger: 'change' } ], @@ -207,6 +251,8 @@ const formRules = reactive({ } ] }) +const fileUploadRef = ref() +const proveList: any = ref([]) /** 查询搜索项列表 */ const getSelectOption = async () => { userList.value = await getEnterpriseManager() @@ -219,12 +265,55 @@ const getSelectOption = async () => { }) } -const setGps = (gps) => { - formData.value.gpsLocation = gps +const setGps = (gps: any) => { + formData.value.gpsLocation = [gps.location.lat, gps.location.lng] + formData.value.address = `${gps.district}${gps.address}` +} + +const getGaps = () => { + if (formData.value.gpsLocation) { + unref(GpsDialogRef).open({ + name: formData.value.enterprisesName, + address: formData.value.address, + location: { + lat: formData.value.gpsLocation[0], + lng: formData.value.gpsLocation[1] + } + }) + } else { + unref(GpsDialogRef).open() + } } -const getGaps=()=>{ - unref(GpsDialogRef).open() +const addProve = () => { + unref(proveFormRef).open() +} + +const submit = async () => { + const validate = await unref(formRef).validate() + if (!validate) return + await unref(fileUploadRef).handlerUpload() + console.log(formData.value, proveList.value) + if (formData.value.id) { + await EnterprisesApi.updateEnterprises(formData.value) + } else { + const data = { + ...formData.value, + fileIds: formData.value.photo.map((i) => i.id), + gpsLocation:formData.value.gpsLocation.join(','), + qualis: proveList.value.map((p) => { + return { + ...p, + files: p.files.map((f) => f.id) + } + }) + } + await EnterprisesApi.createEnterprises(data) + } +} + +const setProve = (prove) => { + proveList.value.push(prove) } getSelectOption() @@ -249,6 +338,9 @@ onMounted(() => {}) display: grid; grid-template-columns: repeat(2, 1fr); gap: 40px; + .el-form-item--large { + margin-bottom: 0; + } .form-textarea { grid-row: span 2; ::v-deep(.el-textarea) { @@ -279,6 +371,45 @@ onMounted(() => {}) font-weight: 700; grid-column: span 2; } + + :deep( + .el-input-number.is-controls-right[class*='large'] [class*='decrease'], + .el-input-number.is-controls-right[class*='large'] [class*='increase'] + ) { + --el-input-number-controls-height: 50%; + } + :deep(.el-input__suffix-inner > :first-child) { + margin-right: 8px; + } + } + .prove-wrapper { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 20px; + .prove { + width: 200px; + height: 238px; + border: 1px solid #ccc; + border-radius: 8px; + overflow: hidden; + padding: 12px; + } + .add-prove { + width: 200px; + height: 238px; + border: 1px dashed #ccc; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; + color: #ccc; + transition: all 0.2s; + &:hover { + color: var(--el-color-primary); + border-color: var(--el-color-primary); + cursor: pointer; + } + } } } </style> diff --git a/src/views/qualification/EnterpriseQualificationForm.vue b/src/views/qualification/EnterpriseQualificationForm.vue deleted file mode 100644 index f30b009..0000000 --- a/src/views/qualification/EnterpriseQualificationForm.vue +++ /dev/null @@ -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> diff --git a/src/views/qualification/index.vue b/src/views/qualification/index.vue index bcae6d1..57a7f0d 100644 --- a/src/views/qualification/index.vue +++ b/src/views/qualification/index.vue @@ -112,14 +112,14 @@ </ContentWrap> <!-- 表单弹窗:添加/修改 --> - <EnterpriseQualificationForm ref="formRef" @success="getList" /> + <Prove ref="formRef" @success="getList" /> </template> <script setup lang="ts"> import { dateFormatter,dateFormatter2 } from '@/utils/formatTime' import download from '@/utils/download' import { EnterpriseQualificationApi, EnterpriseQualificationVO } from '@/api/qualification' -import EnterpriseQualificationForm from './EnterpriseQualificationForm.vue' +import Prove from './prove.vue' import {DICT_TYPE, getStrDictOptions} from "@/utils/dict"; /** 企业资质 列表 */ diff --git a/src/views/qualification/prove.vue b/src/views/qualification/prove.vue new file mode 100644 index 0000000..5196f02 --- /dev/null +++ b/src/views/qualification/prove.vue @@ -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> From 76eb41e75d291ca7db02da6795c29dfdead16261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=7E=E6=B5=B7=7E=E7=88=B1=E6=B5=B7=E7=88=B1?= =?UTF-8?q?=E6=B5=B7=7E=E5=8F=B3?= <1828712314@qq.com> Date: Thu, 27 Mar 2025 10:20:26 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E8=A1=A8=E5=8D=95=EF=BC=8C=E8=B0=83=E6=95=B4?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=90=8D=E7=A7=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BC=81=E4=B8=9A=E8=AF=A6=E6=83=85=E6=9F=A5=E8=AF=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=EF=BC=8C=E6=94=B9=E8=BF=9B=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/enterprises/index.ts | 13 ++- src/api/qualification/index.ts | 12 +-- .../UploadFile/src/FileUploader.vue | 2 + src/views/enterprises/update.vue | 97 +++++++++++++++---- src/views/qualification/prove.vue | 35 +++++-- 5 files changed, 122 insertions(+), 37 deletions(-) diff --git a/src/api/enterprises/index.ts b/src/api/enterprises/index.ts index 604e631..0fa2a8b 100644 --- a/src/api/enterprises/index.ts +++ b/src/api/enterprises/index.ts @@ -9,15 +9,15 @@ export interface EnterprisesVO { region: string // 企业所属区域 enterprisesName: string // 企业名称 address: string // 企业地址 - enterprisesStatus//企业状态 + enterprisesStatus //企业状态 contactName: string // 环保负责人姓名 environmentalContactPhone: string // 企业环保负责人联系电话 registrationNumber: string // 企业注册号 introduction: string // 企业图文介绍 establishmentDate: Date // 企业成立时间 gpsLocation: string // 企业经纬度 - signRadius:string//签到半径 - managerDeptId: number, // 管理部门 + signRadius: string //签到半径 + managerDeptId: number // 管理部门 tagList: any } @@ -66,4 +66,11 @@ export const EnterprisesApi = { return await request.get({ url: `"/system/system/tag-library/list`, params }) }, + /** + * 查询企业详情 + * @param id 企业ID + */ + enterpriseDetail: async (id: any) => { + return await request.get({ url: `/system/enterprise/pcget?id=` + id }) + } } diff --git a/src/api/qualification/index.ts b/src/api/qualification/index.ts index 62f162e..a93b432 100644 --- a/src/api/qualification/index.ts +++ b/src/api/qualification/index.ts @@ -20,13 +20,13 @@ export const EnterpriseQualificationApi = { return await request.get({ url: `/system/enterprise-qualification/page`, params }) }, - // 查询企业资质分页 - getEnterpriseQualificationPageEnterprise: async (params: any) => { - return await request.get({ url: `/system/enterprise-qualification/pageEnterprise`, params }) - }, + // 查询企业资质分页 + getEnterpriseQualificationPageEnterprise: async (params: any) => { + return await request.get({ url: `/system/enterprise-qualification/pageEnterprise`, params }) + }, // 查询企业资质详情 - getEnterpriseQualification: async (id: number) => { + getEnterpriseQualification: async (id: any) => { return await request.get({ url: `/system/enterprise-qualification/get?id=` + id }) }, @@ -48,5 +48,5 @@ export const EnterpriseQualificationApi = { // 导出企业资质 Excel exportEnterpriseQualification: async (params) => { return await request.download({ url: `/system/enterprise-qualification/export-excel`, params }) - }, + } } diff --git a/src/components/UploadFile/src/FileUploader.vue b/src/components/UploadFile/src/FileUploader.vue index 7e7ccc7..6418993 100644 --- a/src/components/UploadFile/src/FileUploader.vue +++ b/src/components/UploadFile/src/FileUploader.vue @@ -172,8 +172,10 @@ const handleExceed = () => { } const handlerUpload = async () => { + if (fileList.value.filter((i) => i.status == 'ready').length == 0) return const formData = new FormData() fileList.value.forEach((file: any) => { + if (file.status == 'success') return formData.append('files', file.raw) }) let res = await batchUploadFile(formData) diff --git a/src/views/enterprises/update.vue b/src/views/enterprises/update.vue index 9bce595..cd35c41 100644 --- a/src/views/enterprises/update.vue +++ b/src/views/enterprises/update.vue @@ -1,6 +1,6 @@ <template> <content-wrap> - <section class="view-wrapper"> + <section class="view-wrapper" v-loading="loading"> <section class="flex-1"> <section class="mb-40px"> <section class="title-wrapper"> 基本信息</section> @@ -31,9 +31,9 @@ </template> </el-input> </el-form-item> - <el-form-item label="行业类别" prop="tags"> + <el-form-item label="行业类别" prop="tagIds"> <el-select - v-model="formData.tags" + v-model="formData.tagIds" placeholder="请选择行业类型" clearable multiple @@ -116,7 +116,12 @@ <section> <section class="title-wrapper"> 相关资质 </section> <section class="prove-wrapper"> - <section class="prove" v-for="(prove, index) in proveList" :key="index"> + <section + class="prove" + v-for="(prove, index) in proveList" + :key="index" + @click="editProve(prove.id)" + > <el-image :src="prove.files[0].url" :preview-src-list="prove.files[0]" @@ -140,14 +145,15 @@ </section> </section> </section> - <section> - <el-button @click="submit">保存</el-button> + <section class="flex items-center justify-center gap-20px"> + <el-button @click="submit" type="primary">保存信息</el-button> + <el-button @click="router.go(-1)">返回列表</el-button> </section> </content-wrap> - <GpsDialog ref="GpsDialogRef" @success="setGps" /> - <ProveForm ref="proveFormRef" @add-prove="setProve" /> + <ProveForm ref="proveFormRef" @add-prove="setProve" @success="getProveList" /> </template> + <script setup lang="ts"> import { getStrDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict' import { TagLibraryApi } from '@/api/system/taglibrary' @@ -156,13 +162,19 @@ import GpsDialog from './components/getGpsByAmap.vue' import ProveForm from '@/views/qualification/prove.vue' import { formatDate } from '@/utils/formatTime' import { EnterprisesApi } from '@/api/enterprises' +import { EnterpriseQualificationApi } from '@/api/qualification' + /** 企业 表单 */ defineOptions({ name: 'UpdateEnterprises' }) //行业类别 const typeList = ref([] as any) const userList = ref([] as any) +const message = useMessage() const GpsDialogRef = ref() +const router = useRouter() const proveFormRef = ref() +const route = useRoute() +const loading = ref(false) const formRef = ref() const formData = ref({ id: undefined, @@ -184,7 +196,7 @@ const formData = ref({ ides: undefined, startUserSelectAssignees: undefined, enterpriseUserId: undefined, - tags: undefined, + tagIds: undefined, photo: [] as any, signRadius: 30 } as any) @@ -236,7 +248,7 @@ const formRules = reactive({ trigger: 'change' } ], - tags: [ + tagIds: [ { required: true, message: '请选择行业类别', @@ -250,7 +262,7 @@ const formRules = reactive({ trigger: 'change' } ] -}) +} as any) const fileUploadRef = ref() const proveList: any = ref([]) /** 查询搜索项列表 */ @@ -286,21 +298,30 @@ const getGaps = () => { } const addProve = () => { - unref(proveFormRef).open() + unref(proveFormRef).open({ enterpriseId: formData.value.id }) } const submit = async () => { const validate = await unref(formRef).validate() if (!validate) return + loading.value = true await unref(fileUploadRef).handlerUpload() - console.log(formData.value, proveList.value) if (formData.value.id) { - await EnterprisesApi.updateEnterprises(formData.value) + const data = { + ...formData.value, + fileIds: formData.value.photo.map((i) => i.id), + gpsLocation: formData.value.gpsLocation.join(',') + } + EnterprisesApi.updateEnterprises(data).then(() => { + message.success('保存成功') + loading.value = false + getDetail() + }) } else { const data = { ...formData.value, - fileIds: formData.value.photo.map((i) => i.id), - gpsLocation:formData.value.gpsLocation.join(','), + fileIds: formData.value.photo.map((i) => i.id), + gpsLocation: formData.value.gpsLocation.join(','), qualis: proveList.value.map((p) => { return { ...p, @@ -308,7 +329,11 @@ const submit = async () => { } }) } - await EnterprisesApi.createEnterprises(data) + EnterprisesApi.createEnterprises(data).then((res) => { + formData.value.id = res + message.success('保存成功') + loading.value = false + }) } } @@ -316,8 +341,41 @@ const setProve = (prove) => { proveList.value.push(prove) } +const editProve = (id) => { + unref(proveFormRef).open({ id }) +} + +const getDetail = () => { + EnterprisesApi.enterpriseDetail(formData.value.id).then((res) => { + formData.value = res + formData.value.photo = formData.value.files + formData.value.gpsLocation = formData.value.gpsLocation.split(',') + formData.value.tagIds = res.tagObjList.map((i) => i.id) + }) +} + +const getProveList = () => { + EnterpriseQualificationApi.getEnterpriseQualificationPage({ + pageSize: -1, + enterpriseId: formData.value.id + }).then((res) => { + proveList.value = res.list.map((i) => { + return { + ...i, + files: i.qualificationFiles + } + }) + }) +} + getSelectOption() -onMounted(() => {}) +onMounted(() => { + if (route.query.id) { + formData.value.id = route.query.id + getDetail() + getProveList() + } +}) </script> <style lang="scss" scoped> @@ -381,6 +439,9 @@ onMounted(() => {}) :deep(.el-input__suffix-inner > :first-child) { margin-right: 8px; } + :deep(.el-input-number .el-input__inner) { + text-align: left; + } } .prove-wrapper { display: grid; diff --git a/src/views/qualification/prove.vue b/src/views/qualification/prove.vue index 5196f02..9d06c0a 100644 --- a/src/views/qualification/prove.vue +++ b/src/views/qualification/prove.vue @@ -1,5 +1,5 @@ <template> - <Dialog :title="dialogTitle" v-model="dialogVisible"> + <Dialog :title="dialogTitle" v-model="dialogVisible" width="30vw"> <el-form ref="formRef" :model="formData" @@ -7,8 +7,8 @@ v-loading="formLoading" label-width="auto" > - <el-form-item label="选择企业" prop="enterpriseId" v-if="formData.id"> - <span></span> + <el-form-item label="企业名称" v-if="formData.id"> + <el-input v-model="formData.enterpriseName" readonly /> </el-form-item> <el-form-item label="资质名称" prop="qualificationName"> <el-select v-model="formData.qualificationName" placeholder="请选择资质名称"> @@ -56,7 +56,8 @@ const formLoading = ref(false) // 表单的加载中:1)修改时的数据加 const formData = ref({ id: undefined, enterpriseId: undefined, - qualificationName: undefined, + qualificationName: undefined as any, + enterpriseName: undefined, expiryDate: undefined, qualificationDescription: undefined, updateBy: undefined, @@ -86,17 +87,28 @@ const formRef = ref() // 表单 Ref /** 打开弹窗 */ const open = async (params) => { - dialogVisible.value = true resetForm() - if (params) { + dialogTitle.value = '新增资质' + if (params.enterpriseId) { + formData.value.enterpriseId = params.enterpriseId + } + if (params.id) { dialogTitle.value = '编辑资质' - formData.value.enterpriseId = params.enterpriseId || undefined - formData.value.id = params.id || undefined + formData.value.id = params.id + getDetail() } + dialogVisible.value = true } -defineExpose({ open }) // 提供 open 方法,用于打开弹窗 - +const getDetail = () => { + formLoading.value = true + EnterpriseQualificationApi.getEnterpriseQualification(formData.value.id).then((res) => { + formData.value = res + formData.value.photo = res.files + formData.value.qualificationName = Number(res.qualificationName) + formLoading.value = false + }) +} /** 提交表单 */ const emit = defineEmits(['success', 'addProve']) // 定义 success 事件,用于操作成功后的回调 const submitForm = async () => { @@ -118,6 +130,7 @@ const submitForm = async () => { data.files = data.photo emit('addProve', data) } + formLoading.value = false message.success('操作成功') dialogVisible.value = false } @@ -138,4 +151,6 @@ const resetForm = () => { } formRef.value?.resetFields() } + +defineExpose({ open }) // 提供 open 方法,用于打开弹窗 </script>