mirror of
http://112.124.100.131/ebiz-ai/ebiz-ai-knowledge-manage.git
synced 2025-12-11 03:46:50 +08:00
feat(components): 添加图片裁剪组件
- 新增 RenderCropper组件,用于图片上传和裁剪功能 - 在 knowledge 页面中集成图片裁剪组件 - 在主文件中注册图片裁剪组件- 优化了 label 样式,注释掉行高属性
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
.el-form-item__label {
|
.el-form-item__label {
|
||||||
color: $--color-primary-label;
|
color: $--color-primary-label;
|
||||||
|
//line-height: 30px;
|
||||||
}
|
}
|
||||||
//.top-label {
|
//.top-label {
|
||||||
// & .el-form-item__label {
|
// & .el-form-item__label {
|
||||||
|
|||||||
201
src/components/RenderCropper/index.vue
Normal file
201
src/components/RenderCropper/index.vue
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<el-upload
|
||||||
|
action="#"
|
||||||
|
list-type="picture-card"
|
||||||
|
:auto-upload="false"
|
||||||
|
:limit="1"
|
||||||
|
:file-list="fileList"
|
||||||
|
:on-change="change"
|
||||||
|
>
|
||||||
|
<i slot="default" class="el-icon-plus"></i>
|
||||||
|
<div slot="file" slot-scope="{ file }">
|
||||||
|
<img class="el-upload-list__item-thumbnail" :src="file.url" alt="" />
|
||||||
|
<span class="el-upload-list__item-actions">
|
||||||
|
<span class="el-upload-list__item-delete" @click="handleRemove(file)">
|
||||||
|
<i class="el-icon-delete"></i>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<r-dialog
|
||||||
|
title="图片裁剪"
|
||||||
|
:visible.sync="visible"
|
||||||
|
@confirm="confirm"
|
||||||
|
width="600px"
|
||||||
|
>
|
||||||
|
<div class="flex">
|
||||||
|
<cropper-canvas style="width: 100%;flex:1" cover image>
|
||||||
|
<cropper-image
|
||||||
|
:src="file.url"
|
||||||
|
alt="Picture"
|
||||||
|
rotatable
|
||||||
|
scalable
|
||||||
|
skewable
|
||||||
|
translatable
|
||||||
|
></cropper-image>
|
||||||
|
<cropper-shade hidden></cropper-shade>
|
||||||
|
<cropper-handle action="select" plain></cropper-handle>
|
||||||
|
<cropper-selection
|
||||||
|
id="cropperSelection"
|
||||||
|
movable
|
||||||
|
resizable
|
||||||
|
x="150"
|
||||||
|
y="100"
|
||||||
|
width="275"
|
||||||
|
height="275"
|
||||||
|
ref="selection"
|
||||||
|
>
|
||||||
|
<cropper-handle
|
||||||
|
action="move"
|
||||||
|
theme-color="rgba(255, 255, 255, 0.35)"
|
||||||
|
></cropper-handle>
|
||||||
|
<cropper-handle action="n-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="e-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="s-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="w-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="ne-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="nw-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="se-resize"></cropper-handle>
|
||||||
|
<cropper-handle action="sw-resize"></cropper-handle>
|
||||||
|
</cropper-selection>
|
||||||
|
</cropper-canvas>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="cropperSelection"
|
||||||
|
style="flex:0;border-radius: 8px; border: 1px solid #ccc;overflow: hidden"
|
||||||
|
>
|
||||||
|
<!-- 修改: 添加 selection 属性并绑定到 file.url -->
|
||||||
|
<cropper-viewer
|
||||||
|
style="width: 100%;height: 100%"
|
||||||
|
selection="#cropperSelection"
|
||||||
|
initial-aspect-ratio="1.5"
|
||||||
|
initial-coverage="0.5"
|
||||||
|
></cropper-viewer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</r-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Cropper from 'cropperjs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'index',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fileList: [],
|
||||||
|
cropper: null,
|
||||||
|
imageUrl: '',
|
||||||
|
visible: false,
|
||||||
|
file: {
|
||||||
|
url: ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {},
|
||||||
|
watch: {},
|
||||||
|
components: {},
|
||||||
|
filters: {},
|
||||||
|
methods: {
|
||||||
|
handleRemove() {
|
||||||
|
this.fileList = []
|
||||||
|
},
|
||||||
|
change(file) {
|
||||||
|
this.file = file
|
||||||
|
this.fileList = []
|
||||||
|
this.visible = true
|
||||||
|
// setTimeout(() => {
|
||||||
|
// this.cropper = new Cropper('#image', {})
|
||||||
|
// }, 300)
|
||||||
|
},
|
||||||
|
confirm() {
|
||||||
|
this.$refs.selection.$toCanvas().then(canvas => {
|
||||||
|
// 根据canvas 生成一个图片 并转换成file
|
||||||
|
const image = canvas.toDataURL()
|
||||||
|
this.dataURLtoFile(image, this.file.name).then(res => {
|
||||||
|
// 文件生成 bolburl
|
||||||
|
const blobUrl = URL.createObjectURL(res)
|
||||||
|
res.url = blobUrl
|
||||||
|
this.fileList.push(res)
|
||||||
|
this.$emit('getFiles', this.fileList)
|
||||||
|
this.visible = false
|
||||||
|
})
|
||||||
|
// 根据canvas 生成一个图片
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// 修改: 优化 base64 转文件的逻辑,增加压缩功能
|
||||||
|
dataURLtoFile(dataurl, filename) {
|
||||||
|
let arr = dataurl.split(','),
|
||||||
|
mime = arr[0].match(/:(.*?);/)[1],
|
||||||
|
bstr = atob(arr[1]),
|
||||||
|
n = bstr.length,
|
||||||
|
u8arr = new Uint8Array(n)
|
||||||
|
|
||||||
|
while (n--) {
|
||||||
|
u8arr[n] = bstr.charCodeAt(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建临时 canvas 元素用于压缩图片
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
const img = new Image()
|
||||||
|
// 返回一个 Promise,确保异步操作完成后再返回文件
|
||||||
|
return new Promise(resolve => {
|
||||||
|
img.src = dataurl
|
||||||
|
|
||||||
|
img.onload = () => {
|
||||||
|
// 限制最大宽度或高度为 800px(可根据需求调整)
|
||||||
|
const maxWidthOrHeight = 800
|
||||||
|
let width = img.width
|
||||||
|
let height = img.height
|
||||||
|
|
||||||
|
if (width > height) {
|
||||||
|
if (width > maxWidthOrHeight) {
|
||||||
|
height *= maxWidthOrHeight / width
|
||||||
|
width = maxWidthOrHeight
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (height > maxWidthOrHeight) {
|
||||||
|
width *= maxWidthOrHeight / height
|
||||||
|
height = maxWidthOrHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.width = width
|
||||||
|
canvas.height = height
|
||||||
|
ctx.drawImage(img, 0, 0, width, height)
|
||||||
|
|
||||||
|
// 将 canvas 转换为 base64,质量设置为 0.8(可根据需求调整)
|
||||||
|
const compressedDataUrl = canvas.toDataURL(mime, 0.8)
|
||||||
|
|
||||||
|
// 将压缩后的 base64 转换为文件
|
||||||
|
const compressedArr = compressedDataUrl.split(',')
|
||||||
|
const compressedBstr = atob(compressedArr[1])
|
||||||
|
const compressedN = compressedBstr.length
|
||||||
|
const compressedU8arr = new Uint8Array(compressedN)
|
||||||
|
|
||||||
|
for (let i = 0; i < compressedN; i++) {
|
||||||
|
compressedU8arr[i] = compressedBstr.charCodeAt(i)
|
||||||
|
}
|
||||||
|
resolve(new File([compressedU8arr], filename, { type: mime }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {},
|
||||||
|
mounted() {},
|
||||||
|
computed: {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
cropper-canvas {
|
||||||
|
height: 50vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cropperSelection {
|
||||||
|
//width: 20vw;
|
||||||
|
max-height: 30vw !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -17,6 +17,7 @@ import utils from '@/assets/js/common'
|
|||||||
import generatedFormat from '@/assets/js/generatedFormat'
|
import generatedFormat from '@/assets/js/generatedFormat'
|
||||||
import generatedComponents from './generatedComponents'
|
import generatedComponents from './generatedComponents'
|
||||||
import RenderDialog from '@/components/RenderDialog/index.vue'
|
import RenderDialog from '@/components/RenderDialog/index.vue'
|
||||||
|
import RenderCropper from '@/components/RenderCropper/index.vue'
|
||||||
Vue.prototype.$generatedFormat = generatedFormat
|
Vue.prototype.$generatedFormat = generatedFormat
|
||||||
Vue.prototype.$utils = utils
|
Vue.prototype.$utils = utils
|
||||||
Vue.prototype.$generatedDictList = generatedFormat.formatList
|
Vue.prototype.$generatedDictList = generatedFormat.formatList
|
||||||
@@ -31,6 +32,7 @@ import '@/assets/js/utils/permission' // permission control
|
|||||||
Vue.use(ElementUI, { locale })
|
Vue.use(ElementUI, { locale })
|
||||||
//二次封装的el-table
|
//二次封装的el-table
|
||||||
Vue.component('RTable', RenderTable)
|
Vue.component('RTable', RenderTable)
|
||||||
|
Vue.component('RCropper', RenderCropper)
|
||||||
Vue.component('RDialog', RenderDialog)
|
Vue.component('RDialog', RenderDialog)
|
||||||
Vue.component('RSwiper', RenderSwiper)
|
Vue.component('RSwiper', RenderSwiper)
|
||||||
Vue.component('RMinerU', RenderMinerU)
|
Vue.component('RMinerU', RenderMinerU)
|
||||||
|
|||||||
Reference in New Issue
Block a user