|
|
@@ -1,22 +1,11 @@
|
|
|
<template>
|
|
|
<div class="upload-container">
|
|
|
<el-upload
|
|
|
- v-loading="isLoading"
|
|
|
- drag
|
|
|
- :accept="accept"
|
|
|
- :action="action"
|
|
|
- :class="isFocus ? 'is-focus' : ''"
|
|
|
- :disabled="isLoading"
|
|
|
- :headers="getHeader()"
|
|
|
- :keyname="isKeyName"
|
|
|
- :on-error="formUploadError"
|
|
|
- :on-progress="uploadprogress"
|
|
|
- :placeholder="placeholder"
|
|
|
- :show-file-list="false"
|
|
|
- class="hc-upload-table-form"
|
|
|
- element-loading-text="上传中..."
|
|
|
- @exceed="formUploadExceed"
|
|
|
- @success="formUploadSuccess"
|
|
|
+ v-loading="isLoading" drag :accept="IMAGE_MIME.join(',')" :action="action"
|
|
|
+ :class="isFocus ? 'is-focus' : ''" :disabled="isLoading" :headers="getHeader()" :keyname="isKeyName"
|
|
|
+ :on-error="formUploadError" :on-progress="uploadProgress" :on-exceed="formUploadExceed"
|
|
|
+ :placeholder="placeholder" :show-file-list="false" class="hc-upload-table-form"
|
|
|
+ element-loading-text="上传中..." :before-upload="beforeUpload" @success="formUploadSuccess"
|
|
|
>
|
|
|
<img v-if="isSrc" :src="isSrc" alt="" class="hc-table-form-img">
|
|
|
<div v-else class="hc-table-form-icon">
|
|
|
@@ -26,39 +15,19 @@
|
|
|
<el-link type="warning" size="small" @click.stop="handlePreview">
|
|
|
预览
|
|
|
</el-link>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
<el-link type="danger" size="small" @click.stop="delTableFormFile">
|
|
|
删除
|
|
|
</el-link>
|
|
|
</div>
|
|
|
- <input
|
|
|
- :id="isKeyName"
|
|
|
- v-model="isSrc"
|
|
|
- class="hc-upload-input-src"
|
|
|
- @blur="handleBlur"
|
|
|
- @focus="handleFocus"
|
|
|
- >
|
|
|
+ <input :id="isKeyName" v-model="isSrc" class="hc-upload-input-src" @blur="handleBlur" @focus="handleFocus">
|
|
|
</el-upload>
|
|
|
|
|
|
<!-- 预览弹窗 -->
|
|
|
- <el-dialog
|
|
|
- v-model="previewVisible"
|
|
|
- title="图片预览"
|
|
|
-
|
|
|
- :before-close="handleClose"
|
|
|
- class="hc-modal-border"
|
|
|
- >
|
|
|
+ <el-dialog v-model="previewVisible" title="图片预览" :before-close="handleClose" class="hc-modal-border">
|
|
|
<div class="preview-container">
|
|
|
- <div
|
|
|
- class="preview-image-wrapper"
|
|
|
- :style="{ transform: `rotate(${rotation}deg)` }"
|
|
|
- >
|
|
|
- <img
|
|
|
- :src="previewSrc"
|
|
|
- alt="预览图片"
|
|
|
- class="preview-image"
|
|
|
+ <div class="preview-image-wrapper" :style="{ transform: `rotate(${rotation}deg)` }">
|
|
|
+ <img
|
|
|
+ :src="previewSrc" alt="预览图片" class="preview-image"
|
|
|
:style="{ maxHeight: `calc(100vh - 200px)` }"
|
|
|
>
|
|
|
</div>
|
|
|
@@ -70,13 +39,7 @@
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer text-center">
|
|
|
<el-button hc-btn size="large" type="info" @click="previewVisible = false">取消</el-button>
|
|
|
- <el-button
|
|
|
- size="large"
|
|
|
- hc-btn
|
|
|
- type="primary"
|
|
|
- :loading="confirmLoading"
|
|
|
- @click="confirmRotation"
|
|
|
- >
|
|
|
+ <el-button size="large" hc-btn type="primary" :loading="confirmLoading" @click="confirmRotation">
|
|
|
确定
|
|
|
</el-button>
|
|
|
</div>
|
|
|
@@ -86,7 +49,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { onMounted, ref, watch } from 'vue'
|
|
|
+import { ref, watch } from 'vue'
|
|
|
import { getHeader } from 'hc-vue3-ui'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
|
@@ -107,7 +70,7 @@ const props = defineProps({
|
|
|
})
|
|
|
|
|
|
// 事件
|
|
|
-const emit = defineEmits(['success', 'del', 'rotate-success'])
|
|
|
+const emit = defineEmits(['success', 'del', 'rotateSuccess'])
|
|
|
|
|
|
// 变量
|
|
|
const isLoading = ref(false)
|
|
|
@@ -119,28 +82,49 @@ const confirmLoading = ref(false)
|
|
|
const previewVisible = ref(false)
|
|
|
const previewSrc = ref('')
|
|
|
const rotation = ref(0)
|
|
|
-const dialogWidth = ref('65%')
|
|
|
+// const dialogWidth = ref('65%')
|
|
|
const rotatedBlob = ref(null)
|
|
|
|
|
|
const action = '/api/blade-manager/exceltab/add-buss-imginfo'
|
|
|
-const accept = 'image/png,image/jpg,image/jpeg'
|
|
|
+const IMAGE_WHITE_EXT = ['png', 'jpg', 'jpeg'] // 图片白名单扩展名(不区分大小写)
|
|
|
+const IMAGE_MIME = ['image/png', 'image/jpeg', 'image/jpg'] // MIME 也同步
|
|
|
+// const accept = 'image/png,image/jpg,image/jpeg'
|
|
|
const oldUrl = ref('')
|
|
|
// 监听props变化
|
|
|
-watch(() => [props.src, props.keyname],
|
|
|
-([src, keyname]) => {
|
|
|
- isSrc.value = src
|
|
|
-
|
|
|
+watch(() => [props.src, props.keyname],
|
|
|
+ ([src, keyname]) => {
|
|
|
+ isSrc.value = src
|
|
|
+
|
|
|
// 只在initialSrc未设置时才赋值(保留第一次的src)
|
|
|
- if (oldUrl.value === '' && src) {
|
|
|
- oldUrl.value = src
|
|
|
- console.log( oldUrl.value, ' oldUrl.value')
|
|
|
-
|
|
|
+ if (oldUrl.value === '' && src) {
|
|
|
+ oldUrl.value = src
|
|
|
+ console.log(oldUrl.value, ' oldUrl.value')
|
|
|
+
|
|
|
+ }
|
|
|
+ isKeyName.value = keyname
|
|
|
+ }, { immediate: true })
|
|
|
+
|
|
|
+function isImageAllow(file) {
|
|
|
+ // 1. 后缀名校验(不区分大小写)
|
|
|
+ const ext = file.name.split('.').pop()?.toLowerCase() || ''
|
|
|
+ if (!IMAGE_WHITE_EXT.includes(ext)) return false
|
|
|
+
|
|
|
+ // 2. MIME 校验(可选,双保险)
|
|
|
+ if (!IMAGE_MIME.includes(file.type)) return false
|
|
|
+
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+const beforeUpload = (rawFile) => {
|
|
|
+ if (!isImageAllow(rawFile)) {
|
|
|
+ ElMessage.error('仅支持 png、jpg、jpeg 格式')
|
|
|
+ return false // 阻断上传
|
|
|
}
|
|
|
- isKeyName.value = keyname
|
|
|
-}, { immediate: true })
|
|
|
+ return true
|
|
|
+}
|
|
|
|
|
|
// 上传进度
|
|
|
-const uploadprogress = () => {
|
|
|
+const uploadProgress = () => {
|
|
|
isLoading.value = true
|
|
|
}
|
|
|
|
|
|
@@ -149,7 +133,7 @@ const formUploadSuccess = (res) => {
|
|
|
isLoading.value = false
|
|
|
if (res.code === 200) {
|
|
|
const link = res.data?.link || ''
|
|
|
-
|
|
|
+
|
|
|
emit('success', {
|
|
|
res,
|
|
|
src: link,
|
|
|
@@ -178,10 +162,10 @@ const delTableFormFile = () => {
|
|
|
// 焦点状态管理
|
|
|
const isFocus = ref(false)
|
|
|
const handleFocus = () => {
|
|
|
- isFocus.value = true
|
|
|
+ isFocus.value = true
|
|
|
}
|
|
|
const handleBlur = () => {
|
|
|
- isFocus.value = false
|
|
|
+ isFocus.value = false
|
|
|
}
|
|
|
|
|
|
// 预览图片
|
|
|
@@ -223,10 +207,7 @@ const confirmRotation = async () => {
|
|
|
const formData = new FormData()
|
|
|
formData.append('file', blob, `rotated-${Date.now()}.png`)
|
|
|
// 添加新参数
|
|
|
-
|
|
|
-
|
|
|
formData.append('oldUrl', oldUrl.value)
|
|
|
-
|
|
|
|
|
|
// 调用上传接口
|
|
|
const response = await fetch(action, {
|
|
|
@@ -236,7 +217,6 @@ const confirmRotation = async () => {
|
|
|
})
|
|
|
|
|
|
const res = await response.json()
|
|
|
-
|
|
|
if (res.code === 200) {
|
|
|
const link = res.data?.link || ''
|
|
|
isSrc.value = link
|
|
|
@@ -245,7 +225,7 @@ const confirmRotation = async () => {
|
|
|
src: link,
|
|
|
key: isKeyName.value,
|
|
|
})
|
|
|
- emit('rotate-success', {
|
|
|
+ emit('rotateSuccess', {
|
|
|
src: link,
|
|
|
rotation: rotation.value,
|
|
|
key: isKeyName.value,
|
|
|
@@ -266,30 +246,30 @@ const confirmRotation = async () => {
|
|
|
const rotateImageAndGetBlob = (imageUrl, degrees) => {
|
|
|
return new Promise((resolve, reject) => {
|
|
|
const img = new Image()
|
|
|
-
|
|
|
-
|
|
|
+
|
|
|
+
|
|
|
img.crossOrigin = 'anonymous'
|
|
|
img.onload = function () {
|
|
|
const canvas = document.createElement('canvas')
|
|
|
const ctx = canvas.getContext('2d')
|
|
|
-
|
|
|
+
|
|
|
// 根据旋转角度设置画布尺寸
|
|
|
const radians = (degrees * Math.PI) / 180
|
|
|
let width = img.width
|
|
|
let height = img.height
|
|
|
-
|
|
|
+
|
|
|
if (degrees % 180 !== 0) {
|
|
|
[width, height] = [height, width]
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
canvas.width = width
|
|
|
canvas.height = height
|
|
|
-
|
|
|
+
|
|
|
// 旋转画布
|
|
|
ctx.translate(width / 2, height / 2)
|
|
|
ctx.rotate(radians)
|
|
|
ctx.drawImage(img, -img.width / 2, -img.height / 2)
|
|
|
-
|
|
|
+
|
|
|
// 转换为Blob
|
|
|
canvas.toBlob(blob => {
|
|
|
if (blob) {
|
|
|
@@ -307,7 +287,7 @@ const rotateImageAndGetBlob = (imageUrl, degrees) => {
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.upload-container {
|
|
|
-
|
|
|
+
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
}
|
|
|
@@ -319,11 +299,12 @@ const rotateImageAndGetBlob = (imageUrl, degrees) => {
|
|
|
height: 100%;
|
|
|
border-radius: 3px;
|
|
|
|
|
|
- &.is-focus, &:hover {
|
|
|
+ &.is-focus,
|
|
|
+ &:hover {
|
|
|
background-color: #eddac4;
|
|
|
box-shadow: 0 0 0 1.5px var(--el-color-primary) inset;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
.hc-upload-input-src {
|
|
|
position: absolute;
|
|
|
z-index: -1;
|
|
|
@@ -349,29 +330,29 @@ const rotateImageAndGetBlob = (imageUrl, degrees) => {
|
|
|
}
|
|
|
|
|
|
.hc-table-form-actions {
|
|
|
- position: absolute;
|
|
|
+ position: absolute;
|
|
|
top: 10px;
|
|
|
- display: flex;
|
|
|
- gap: 4px;
|
|
|
+ display: flex;
|
|
|
+ gap: 4px;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
}
|
|
|
|
|
|
.preview-container {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
- overflow-y:auto ;
|
|
|
+ overflow-y: auto;
|
|
|
max-height: 600px;
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
.preview-image-wrapper {
|
|
|
transition: transform 0.3s ease;
|
|
|
-
|
|
|
+
|
|
|
}
|
|
|
|
|
|
.preview-image {
|
|
|
@@ -389,22 +370,23 @@ const rotateImageAndGetBlob = (imageUrl, degrees) => {
|
|
|
</style>
|
|
|
|
|
|
<style lang="scss">
|
|
|
-.hc-upload-table-form{
|
|
|
+.hc-upload-table-form {
|
|
|
border-radius: 3px;
|
|
|
transition: box-shadow 0.3s, background-color 0.3s;
|
|
|
-
|
|
|
- &.is-focus, &:hover {
|
|
|
+
|
|
|
+ &.is-focus,
|
|
|
+ &:hover {
|
|
|
background-color: #eddac4;
|
|
|
box-shadow: 0 0 0 1.5px var(--el-color-primary) inset;
|
|
|
}
|
|
|
-
|
|
|
- .el-upload-dragger{
|
|
|
- height:100%;
|
|
|
+
|
|
|
+ .el-upload-dragger {
|
|
|
+ height: 100%;
|
|
|
width: 100%;
|
|
|
background-color: transparent;
|
|
|
padding: 10px;
|
|
|
text-align: left;
|
|
|
- border:none;
|
|
|
+ border: none;
|
|
|
}
|
|
|
}
|
|
|
</style>
|