|
|
@@ -1,30 +1,86 @@
|
|
|
<template>
|
|
|
- <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"
|
|
|
- >
|
|
|
- <img v-if="isSrc" :src="isSrc" alt="" class="hc-table-form-img">
|
|
|
- <div v-else class="hc-table-form-icon">
|
|
|
- 点此选择或者拖拽文件并上传
|
|
|
- </div>
|
|
|
- <div v-if="isSrc" class="hc-table-form-del">
|
|
|
- <el-button plain type="danger" @click.stop="delTableFormFile">
|
|
|
- 删除当前文件
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- <input :id="isKeyName" v-model="isSrc" class="hc-upload-input-src" @blur="handleBlur" @focus="handleFocus">
|
|
|
- </el-upload>
|
|
|
+ <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"
|
|
|
+ >
|
|
|
+ <img v-if="isSrc" :src="isSrc" alt="" class="hc-table-form-img">
|
|
|
+ <div v-else class="hc-table-form-icon">
|
|
|
+ 点此选择或者拖拽文件并上传
|
|
|
+ </div>
|
|
|
+ <div v-if="isSrc" class="hc-table-form-actions">
|
|
|
+ <el-button plain type="primary" size="small" @click.stop="handlePreview">
|
|
|
+ 预览
|
|
|
+ </el-button>
|
|
|
+ <el-button plain type="danger" size="small" @click.stop="delTableFormFile">
|
|
|
+ 删除
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ <input
|
|
|
+ :id="isKeyName"
|
|
|
+ v-model="isSrc"
|
|
|
+ class="hc-upload-input-src"
|
|
|
+ @blur="handleBlur"
|
|
|
+ @focus="handleFocus"
|
|
|
+ >
|
|
|
+ </el-upload>
|
|
|
+
|
|
|
+ <!-- 预览弹窗 -->
|
|
|
+ <el-dialog
|
|
|
+ v-model="previewVisible"
|
|
|
+ title="图片预览"
|
|
|
+ :width="dialogWidth"
|
|
|
+ :before-close="handleClose"
|
|
|
+ >
|
|
|
+ <div class="preview-container">
|
|
|
+ <div
|
|
|
+ class="preview-image-wrapper"
|
|
|
+ :style="{ transform: `rotate(${rotation}deg)` }"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ :src="previewSrc"
|
|
|
+ alt="预览图片"
|
|
|
+ class="preview-image"
|
|
|
+ :style="{ maxHeight: `calc(100vh - 200px)` }"
|
|
|
+ >
|
|
|
+ </div>
|
|
|
+ <div class="rotation-controls">
|
|
|
+ <el-button type="primary" plain @click="rotate(-90)">向左旋转</el-button>
|
|
|
+ <el-button type="primary" plain @click="rotate(90)">向右旋转</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="previewVisible = false">取消</el-button>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ :loading="confirmLoading"
|
|
|
+ @click="confirmRotation"
|
|
|
+ >
|
|
|
+ 确定
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, watch } from 'vue'
|
|
|
+import { onMounted, ref, watch } from 'vue'
|
|
|
import { getHeader } from 'hc-vue3-ui'
|
|
|
+import { ElMessage } from 'element-plus'
|
|
|
|
|
|
const props = defineProps({
|
|
|
src: {
|
|
|
@@ -39,37 +95,50 @@ const props = defineProps({
|
|
|
type: [Number, String],
|
|
|
default: '相片',
|
|
|
},
|
|
|
+ // 新增pkeyId属性,用于接口参数
|
|
|
+ pkeyId: {
|
|
|
+ type: [Number, String],
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
})
|
|
|
|
|
|
-//事件
|
|
|
-const emit = defineEmits(['success', 'del'])
|
|
|
-//变量
|
|
|
+// 事件
|
|
|
+const emit = defineEmits(['success', 'del', 'rotate-success'])
|
|
|
+
|
|
|
+// 变量
|
|
|
const isLoading = ref(false)
|
|
|
const isSrc = ref(props.src)
|
|
|
const isKeyName = ref(props.keyname)
|
|
|
+const confirmLoading = ref(false)
|
|
|
+
|
|
|
+// 预览相关变量
|
|
|
+const previewVisible = ref(false)
|
|
|
+const previewSrc = ref('')
|
|
|
+const rotation = ref(0)
|
|
|
+const dialogWidth = ref('80%')
|
|
|
+const rotatedBlob = ref(null)
|
|
|
|
|
|
const action = '/api/blade-manager/exceltab/add-buss-imginfo'
|
|
|
const accept = 'image/png,image/jpg,image/jpeg'
|
|
|
|
|
|
-//监听
|
|
|
-watch(() => [
|
|
|
- props.src,
|
|
|
- props.keyname,
|
|
|
-], ([src, keyname]) => {
|
|
|
+// 监听props变化
|
|
|
+watch(() => [props.src, props.keyname, props.pkeyId],
|
|
|
+([src, keyname]) => {
|
|
|
isSrc.value = src
|
|
|
isKeyName.value = keyname
|
|
|
})
|
|
|
|
|
|
-//上传进度
|
|
|
+// 上传进度
|
|
|
const uploadprogress = () => {
|
|
|
isLoading.value = true
|
|
|
}
|
|
|
|
|
|
-//上传完成
|
|
|
+// 上传完成
|
|
|
const formUploadSuccess = (res) => {
|
|
|
isLoading.value = false
|
|
|
if (res.code === 200) {
|
|
|
const link = res.data?.link || ''
|
|
|
+
|
|
|
emit('success', {
|
|
|
res,
|
|
|
src: link,
|
|
|
@@ -78,47 +147,168 @@ const formUploadSuccess = (res) => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-//上传失败
|
|
|
+// 上传失败
|
|
|
const formUploadError = () => {
|
|
|
isLoading.value = false
|
|
|
+ ElMessage.error('上传失败,请重试')
|
|
|
}
|
|
|
|
|
|
-//格式错误
|
|
|
+// 格式错误
|
|
|
const formUploadExceed = () => {
|
|
|
isLoading.value = false
|
|
|
+ ElMessage.error('只能上传一个文件')
|
|
|
}
|
|
|
|
|
|
-//删除上传的文件
|
|
|
+// 删除上传的文件
|
|
|
const delTableFormFile = () => {
|
|
|
emit('del', isKeyName.value)
|
|
|
}
|
|
|
|
|
|
+// 焦点状态管理
|
|
|
const isFocus = ref(false)
|
|
|
-
|
|
|
-//获得焦点
|
|
|
const handleFocus = () => {
|
|
|
- isFocus.value = true
|
|
|
+ isFocus.value = true
|
|
|
}
|
|
|
-
|
|
|
-//失去焦点
|
|
|
const handleBlur = () => {
|
|
|
- isFocus.value = false
|
|
|
+ isFocus.value = false
|
|
|
+}
|
|
|
+
|
|
|
+// 预览图片
|
|
|
+const handlePreview = () => {
|
|
|
+ if (!isSrc.value) return
|
|
|
+ previewSrc.value = isSrc.value
|
|
|
+ rotation.value = 0
|
|
|
+ previewVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭预览弹窗
|
|
|
+const handleClose = () => {
|
|
|
+ previewVisible.value = false
|
|
|
+ rotation.value = 0
|
|
|
+ rotatedBlob.value = null
|
|
|
+}
|
|
|
+
|
|
|
+// 旋转图片
|
|
|
+const rotate = (degrees) => {
|
|
|
+ rotation.value = (rotation.value + degrees) % 360
|
|
|
+}
|
|
|
+
|
|
|
+// 确认旋转并上传
|
|
|
+const confirmRotation = async () => {
|
|
|
+ if (rotation.value % 360 === 0) {
|
|
|
+ previewVisible.value = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ confirmLoading.value = true
|
|
|
+ try {
|
|
|
+ // 将旋转后的图片转换为blob
|
|
|
+ const blob = await rotateImageAndGetBlob(previewSrc.value, rotation.value)
|
|
|
+ if (!blob) {
|
|
|
+ throw new Error('图片处理失败')
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建FormData
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('file', blob, `rotated-${Date.now()}.png`)
|
|
|
+ // 添加新参数
|
|
|
+ formData.append('pkeyId', props.pkeyId)
|
|
|
+ formData.append('keyname', isKeyName.value)
|
|
|
+
|
|
|
+ // 调用上传接口
|
|
|
+ const response = await fetch(action, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: getHeader(),
|
|
|
+ body: formData,
|
|
|
+ })
|
|
|
+
|
|
|
+ const res = await response.json()
|
|
|
+
|
|
|
+ if (res.code === 200) {
|
|
|
+ const link = res.data?.link || ''
|
|
|
+ isSrc.value = link
|
|
|
+ emit('success', {
|
|
|
+ res,
|
|
|
+ src: link,
|
|
|
+ key: isKeyName.value,
|
|
|
+ })
|
|
|
+ emit('rotate-success', {
|
|
|
+ src: link,
|
|
|
+ rotation: rotation.value,
|
|
|
+ key: isKeyName.value,
|
|
|
+ })
|
|
|
+ ElMessage.success(res.msg)
|
|
|
+ previewVisible.value = false
|
|
|
+ } else {
|
|
|
+ throw new Error(res.msg || '上传失败')
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ ElMessage.error(error.message || '处理失败,请重试')
|
|
|
+ } finally {
|
|
|
+ confirmLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 旋转图片并转为Blob
|
|
|
+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) {
|
|
|
+ resolve(blob)
|
|
|
+ } else {
|
|
|
+ reject(new Error('无法转换图片为Blob'))
|
|
|
+ }
|
|
|
+ }, 'image/png')
|
|
|
+ }
|
|
|
+ img.onerror = () => reject(new Error('图片加载失败'))
|
|
|
+ img.src = imageUrl
|
|
|
+ })
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+.upload-container {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
.hc-upload-table-form {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- justify-content: center; /* 纵向居中 */
|
|
|
- // align-items: center; /* 横向居中,如果需要的话 */
|
|
|
- height: 100%; /* 确保容器有高度 */
|
|
|
+ justify-content: center;
|
|
|
+ height: 100%;
|
|
|
border-radius: 3px;
|
|
|
|
|
|
&.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;
|
|
|
@@ -126,36 +316,70 @@ const handleBlur = () => {
|
|
|
width: 10px;
|
|
|
}
|
|
|
|
|
|
+ .hc-table-form-img {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 200px;
|
|
|
+ object-fit: contain;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
.hc-table-form-icon {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
- width: 100%; /* 如果需要的话,确保宽度填满容器 */
|
|
|
- height: 100%; /* 如果需要的话,确保高度填满容器 */
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.hc-table-form-actions {
|
|
|
+ position: absolute;
|
|
|
+ top: 10px;
|
|
|
+ right: 10px;
|
|
|
+ display: flex;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image-wrapper {
|
|
|
+ transition: transform 0.3s ease;
|
|
|
+ margin: 20px 0;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image {
|
|
|
+ max-width: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
+
|
|
|
+.rotation-controls {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ margin-top: 15px;
|
|
|
}
|
|
|
</style>
|
|
|
|
|
|
<style lang="scss">
|
|
|
.hc-upload-table-form{
|
|
|
- border-radius: 3px;
|
|
|
+ border-radius: 3px;
|
|
|
transition: box-shadow 0.3s, background-color 0.3s;
|
|
|
+
|
|
|
&.is-focus, &:hover {
|
|
|
background-color: #eddac4;
|
|
|
box-shadow: 0 0 0 1.5px var(--el-color-primary) inset;
|
|
|
}
|
|
|
+
|
|
|
.el-upload-dragger{
|
|
|
height:100%;
|
|
|
width: 100%;
|
|
|
background-color: transparent;
|
|
|
-
|
|
|
padding: 10px;
|
|
|
text-align: left;
|
|
|
border:none;
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
}
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|