Sfoglia il codice sorgente

新增图片旋转功能

duy 6 giorni fa
parent
commit
90177f940e
1 ha cambiato i file con 277 aggiunte e 53 eliminazioni
  1. 277 53
      src/components/plugins/table-form/hc-form-upload.vue

+ 277 - 53
src/components/plugins/table-form/hc-form-upload.vue

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