mirror of
http://112.124.100.131/ebiz-ai/ebiz-ai-knowledge-manage.git
synced 2025-12-15 22:06:50 +08:00
feat(intelligent-agent): 添加智能体头像上传功能
- 新增 cropper 组件用于图片裁剪 - 在智能体信息组件中集成 cropper 组件 - 添加图片上传和裁剪的相关逻辑和接口 - 优化智能体列表和详情页面的头像显示
This commit is contained in:
220
src/components/RenderCropper/components/cropper.vue
Normal file
220
src/components/RenderCropper/components/cropper.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div style="width: 100%;height: 100%">
|
||||
<el-upload
|
||||
class="upload-picture-action"
|
||||
v-show="fileList.length <= 0"
|
||||
action="#"
|
||||
drag
|
||||
list-type="picture-card"
|
||||
:show-file-list="false"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:file-list="fileList"
|
||||
:on-change="change"
|
||||
>
|
||||
<p>点击上传图片或者将图片拖拽到此处</p>
|
||||
</el-upload>
|
||||
|
||||
<div
|
||||
v-if="fileList.length > 0"
|
||||
class="flex"
|
||||
style="width: 100%;height: 100%"
|
||||
>
|
||||
<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>
|
||||
</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 = [file]
|
||||
// this.fileList = []
|
||||
// this.visible = true
|
||||
// setTimeout(() => {
|
||||
// this.cropper = new Cropper('#image', {})
|
||||
// }, 300)
|
||||
},
|
||||
reset() {
|
||||
this.fileList = []
|
||||
this.file = {}
|
||||
},
|
||||
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.file = {}
|
||||
this.fileList = []
|
||||
// this.fileList.push(res)
|
||||
this.$emit('getFiles', [res])
|
||||
// this.fileList = []
|
||||
})
|
||||
// 根据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: 100%;
|
||||
}
|
||||
|
||||
.cropperSelection {
|
||||
//width: 20vw;
|
||||
//max-height: 30vw !important;
|
||||
}
|
||||
|
||||
.upload-picture-action {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
& .el-upload--picture-card {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
& .el-upload-dragger {
|
||||
border: unset;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
& el-upload-dragger {
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,27 +1,37 @@
|
||||
<script>
|
||||
import { agentEdit, agentAdd } from '@/api/intelligent-agent/list'
|
||||
import { VEmojiPicker } from 'v-emoji-picker'
|
||||
import cropper from '@/components/RenderCropper/components/cropper.vue'
|
||||
export default {
|
||||
name: 'info',
|
||||
inject: ['dialog', 'fetchAgentList'],
|
||||
inject: ['dialog', 'fetchAgentList', 'resetList'],
|
||||
components: {
|
||||
VEmojiPicker
|
||||
VEmojiPicker,
|
||||
cropper
|
||||
},
|
||||
computed() {
|
||||
return {
|
||||
copyAgent: item => {
|
||||
return this.dialog.agent
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isImage: '0',
|
||||
popover: false,
|
||||
background: '',
|
||||
chooseBack: [
|
||||
'rgb(255, 255, 255)',
|
||||
'rgb(228, 251, 204)',
|
||||
'rgb(239, 241, 245)',
|
||||
'rgb(224, 234, 255)',
|
||||
'rgb(254, 247, 195)',
|
||||
'rgb(213, 245, 246)',
|
||||
'rgb(209, 233, 255)',
|
||||
'rgb(209, 224, 255)',
|
||||
'rgb(213, 217, 235)',
|
||||
'rgb(255, 228, 232)'
|
||||
'#ffffff',
|
||||
'#e4fbcc',
|
||||
'#eff1f5',
|
||||
'#e0eaf0',
|
||||
'#fef7c3',
|
||||
'#d5f5f6',
|
||||
'#d1e9ff',
|
||||
'#d1e0ff',
|
||||
'#d5d9eb',
|
||||
'#ffe4e8'
|
||||
],
|
||||
// 应用类型
|
||||
agentType: [
|
||||
@@ -57,22 +67,38 @@ export default {
|
||||
}
|
||||
],
|
||||
rules: {
|
||||
name: [
|
||||
appName: [
|
||||
{ required: true, message: '请输入智能体名称', trigger: 'blur' }
|
||||
],
|
||||
description: [
|
||||
{ required: false, message: '请输入智能体描述', trigger: 'blur' }
|
||||
],
|
||||
appType: [
|
||||
{ required: true, message: '请选择应用类型', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getFiles(file) {
|
||||
this.dialog.agent.image = file[0].url
|
||||
this.dialog.agent.imageType = 'image'
|
||||
this.dialog.agent.backgroundColor = null
|
||||
},
|
||||
resetImage() {
|
||||
this.$refs.cropperImage.reset()
|
||||
// this.popover = false
|
||||
},
|
||||
confirmImage() {
|
||||
this.$refs.cropperImage.confirm()
|
||||
this.popover = false
|
||||
},
|
||||
chooseAgentType(item) {
|
||||
this.$set(this.dialog.agent, 'appType', item.value)
|
||||
},
|
||||
|
||||
chooseGround(colors) {
|
||||
this.$set(this.dialog.agent, 'background', colors)
|
||||
this.$set(this.dialog.agent, 'backgroundColor', colors)
|
||||
},
|
||||
/**
|
||||
* 提交表单
|
||||
@@ -83,20 +109,31 @@ export default {
|
||||
// 更新表单之后重新获取列表,然后退出 dialog
|
||||
api(this.dialog.agent).then(res => {
|
||||
if (res) {
|
||||
this.resetList()
|
||||
this.fetchAgentList()
|
||||
this.dialog.visible = false
|
||||
}
|
||||
})
|
||||
},
|
||||
// 调用保存或者编辑
|
||||
validateForm() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (valid) this.handleSubmit()
|
||||
})
|
||||
},
|
||||
|
||||
// 选择emoji
|
||||
selectEmoji(e) {
|
||||
this.dialog.agent.image = e.data
|
||||
this.dialog.agent.imageType = 'emoji'
|
||||
},
|
||||
// 选择是否是上传图片
|
||||
changeUploadImage(e) {
|
||||
// if (e === '0') {
|
||||
// this.dialog.agent.imageType = 'emoji'
|
||||
// this.dialog.agent.imageType = 'emoji'
|
||||
// } else {
|
||||
// this.dialog.agent.imageType = 'image'
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,25 +153,29 @@ export default {
|
||||
<el-input v-model="dialog.agent.appName" size="medium" />
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="imageId" class="mt30 ml20">
|
||||
<el-popover trigger="click">
|
||||
<el-popover trigger="click" v-model="popover">
|
||||
<div style="width: 28vw">
|
||||
<el-radio-group
|
||||
v-model="isImage"
|
||||
style="width: 100%;"
|
||||
class="flex render-group"
|
||||
@change="changeUploadImage"
|
||||
>
|
||||
<el-radio-button label="0" style="flex:1">表情</el-radio-button>
|
||||
<el-radio-button label="1" style="flex:1" disabled
|
||||
>图片</el-radio-button
|
||||
>
|
||||
<el-radio-button label="1" style="flex:1">图片</el-radio-button>
|
||||
</el-radio-group>
|
||||
|
||||
<!-- emoji-->
|
||||
<div class="mt10" v-if="isImage === '0'">
|
||||
<VEmojiPicker @select="selectEmoji" class="emoji " />
|
||||
<div
|
||||
v-if="dialog.agent.image"
|
||||
class="flex mt10 back-content justify-content-b"
|
||||
>
|
||||
<template v-for="item in chooseBack">
|
||||
<template
|
||||
v-for="item in chooseBack"
|
||||
v-if="dialog.agent.imageType === 'emoji'"
|
||||
>
|
||||
<div
|
||||
class="emoji-background"
|
||||
:style="`background:${item}`"
|
||||
@@ -145,15 +186,44 @@ export default {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片切割-->
|
||||
<div v-else class="cropper-image">
|
||||
<cropper
|
||||
ref="cropperImage"
|
||||
style="height: calc(100% - 40px);"
|
||||
@getFiles="getFiles"
|
||||
></cropper>
|
||||
<div class="mt10 flex align-items-c justify-content-b">
|
||||
<el-button
|
||||
class="render-button"
|
||||
style="flex:1"
|
||||
@click="resetImage"
|
||||
>取消</el-button
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="medium"
|
||||
style="flex:1"
|
||||
@click="confirmImage"
|
||||
>确认</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="emoji-content"
|
||||
slot="reference"
|
||||
:style="`background:${dialog.agent.background}`"
|
||||
:style="`background:${dialog.agent.backgroundColor}`"
|
||||
>
|
||||
<div v-if="dialog.agent.imageType === 'emoji'">
|
||||
{{ dialog.agent.image }}
|
||||
</div>
|
||||
<div v-else style="width: 100%">
|
||||
<img :src="dialog.agent.image" alt="" style="width: 100%;" />
|
||||
</div>
|
||||
</div>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</div>
|
||||
@@ -223,7 +293,7 @@ export default {
|
||||
line-height: 50px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
background: rgb(209, 233, 255);
|
||||
background: #d1e9f;
|
||||
}
|
||||
|
||||
.back-content {
|
||||
@@ -292,4 +362,9 @@ export default {
|
||||
max-width: 100%; // 设置最大宽度以限制文本长度
|
||||
}
|
||||
}
|
||||
|
||||
.cropper-image {
|
||||
width: 100%;
|
||||
height: 45vh;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -53,10 +53,15 @@ export default {
|
||||
provide() {
|
||||
return {
|
||||
dialog: this.dialog,
|
||||
fetchAgentList: this.fetchAgentList
|
||||
fetchAgentList: this.fetchAgentList,
|
||||
resetList: this.resetList
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetList() {
|
||||
this.list = []
|
||||
this.page = 0
|
||||
},
|
||||
load() {
|
||||
if (this.total === this.list.length) {
|
||||
return false
|
||||
@@ -85,7 +90,10 @@ export default {
|
||||
*/
|
||||
async handleEditAgent(id) {
|
||||
const { content } = await agentDetail(id)
|
||||
this.dialog.agent = content.content
|
||||
if (!content.content.backgroundColor) {
|
||||
content.content.backgroundColor = '#d1e9ff'
|
||||
}
|
||||
this.$set(this.dialog, 'agent', content.content)
|
||||
this.dialog.type = 'edit'
|
||||
this.dialog.title = '编辑智能体'
|
||||
// 获取数据之后打开内容详情
|
||||
@@ -120,7 +128,8 @@ export default {
|
||||
appType: '',
|
||||
description: '',
|
||||
imageType: '',
|
||||
image: ''
|
||||
image: '',
|
||||
backgroundColor: '#d1e9ff'
|
||||
}
|
||||
this.dialog.title = '创建智能体'
|
||||
this.dialog.visible = true
|
||||
@@ -195,7 +204,16 @@ export default {
|
||||
>
|
||||
<div class="dataset-header">
|
||||
<div class="folder-content">
|
||||
<div class="folder">
|
||||
<div
|
||||
class="folder"
|
||||
:style="
|
||||
`background:${
|
||||
listItem.imageType === 'emoji'
|
||||
? listItem.backgroundColor
|
||||
: ''
|
||||
}`
|
||||
"
|
||||
>
|
||||
<!--listItem.name.获取字符串第一个-->
|
||||
{{ listItem.image ? listItem.image : listItem.appName[0] }}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user