Browse Source

添加批量上传文件功能,新增文件上传组件,优化相关样式和逻辑

master
parent
commit
1f59958f80
  1. 24
      index.html
  2. 5
      src/api/infra/file/index.ts
  3. BIN
      src/assets/imgs/position.png
  4. 5
      src/components/UploadFile/index.ts
  5. 318
      src/components/UploadFile/src/FileUploader.vue
  6. 501
      src/components/UploadFile/src/InnerImage.vue
  7. 205
      src/components/UploadFile/src/InnerUploadImg.vue
  8. 568
      src/components/UploadFile/src/InnerUploader.vue
  9. 17
      src/components/UploadFile/src/UploadImgs.vue
  10. 2
      src/components/UploadFile/src/useUpload.ts
  11. 3
      src/layout/components/Menu/src/Menu.vue
  12. 2
      src/styles/var.css
  13. 18
      src/styles/variables.scss
  14. 182
      src/views/enterprises/components/getGpsByAmap.vue
  15. 88
      src/views/enterprises/components/getGpsByQq.vue
  16. 169
      src/views/enterprises/update.vue
  17. 202
      src/views/qualification/EnterpriseQualificationForm.vue
  18. 4
      src/views/qualification/index.vue
  19. 141
      src/views/qualification/prove.vue

24
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>

5
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 })
}

BIN
src/assets/imgs/position.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

5
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 }

318
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>

501
src/components/UploadFile/src/InnerImage.vue

@ -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>

205
src/components/UploadFile/src/InnerUploadImg.vue

@ -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>

568
src/components/UploadFile/src/InnerUploader.vue

@ -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>

17
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 {

2
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) => {

3
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)"

2
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;

18
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%;
}

182
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>

88
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>

169
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>

202
src/views/qualification/EnterpriseQualificationForm.vue

@ -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) // 12
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>

4
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";
/** 企业资质 列表 */

141
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) // 12
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…
Cancel
Save