Merge branch 'feature/linzhijun_扣子智能体_0710' of https://gta.lvfunai.com/ai-team/lingji-work-fe into feature/linzhijun_扣子智能体_0710

# Conflicts:
#	src/views/agent/work-flow/index.vue
This commit is contained in:
林志军
2025-07-31 14:37:45 +08:00
8 changed files with 221 additions and 22 deletions

View File

@ -0,0 +1,26 @@
import { getImageMainColor } from '@/utils/tools';
// 创建图片主色调指令
const imageMainColorDirective = {
mounted(el: HTMLElement, binding: any) {
const imageUrl = binding.value;
if (!imageUrl) return;
getImageMainColor(imageUrl)
.then(color => {
el.style.backgroundColor = color;
})
.catch(error => {
console.error('获取图片主色调失败:', error);
// 设置默认背景色
el.style.backgroundColor = '#E6E6E8';
});
}
};
export default {
install(app: any) {
app.directive('image-main-color', imageMainColorDirective);
}
};

View File

@ -0,0 +1,3 @@
import getImageMainColor from './getImageMainColor';
export { getImageMainColor };

View File

@ -5,9 +5,10 @@
import App from './App.vue';
import router from './router';
import store from './stores';
import * as directives from '@/directives';
import NoData from '@/components/no-data';
import SvgIcon from "@/components/svg-icon";
import SvgIcon from '@/components/svg-icon';
import '@/api/index';
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
@ -15,7 +16,7 @@ import './core';
import 'normalize.css';
import 'uno.css';
import 'virtual:svg-icons-register'
import 'virtual:svg-icons-register';
// import '@/styles/vars.css'; // 优先加载
@ -26,4 +27,7 @@ app.component('SvgIcon', SvgIcon);
app.use(store);
app.use(router);
Object.keys(directives).forEach((k) => app.use(directives[k])); // 注册指令
app.mount('#app');

View File

@ -110,3 +110,173 @@ export function downloadByUrl(url: string, filename?: string) {
export function genRandomId() {
return `id_${Date.now()}_${Math.floor(Math.random() * 10000)}`;
}
export function getImageMainColor(imageUrl: string): Promise<string> {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域图片
img.src = imageUrl;
img.onload = () => {
// 创建画布缩小图片尺寸以提高性能
const canvas = document.createElement('canvas');
const maxDimension = 100; // 最大尺寸为100px
let width = img.width;
let height = img.height;
// 按比例缩小图片
if (width > height) {
if (width > maxDimension) {
height *= maxDimension / width;
width = maxDimension;
}
} else {
if (height > maxDimension) {
width *= maxDimension / height;
height = maxDimension;
}
}
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
if (!ctx) {
reject(new Error('Canvas context not available'));
return;
}
// 绘制缩小后的图片
ctx.drawImage(img, 0, 0, width, height);
// 获取图片数据
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// 使用中位数切分法提取主色调
const colorGroups = medianCut(data, 8); // 分成8组
// 找出最大的颜色组
let maxGroup = colorGroups[0];
for (let i = 1; i < colorGroups.length; i++) {
if (colorGroups[i].count > maxGroup.count) {
maxGroup = colorGroups[i];
}
}
// 计算组内平均颜色
const avgColor = {
r: Math.round(maxGroup.sumR / maxGroup.count),
g: Math.round(maxGroup.sumG / maxGroup.count),
b: Math.round(maxGroup.sumB / maxGroup.count)
};
resolve(`rgb(${avgColor.r},${avgColor.g},${avgColor.b})`);
};
img.onerror = () => {
reject(new Error('Failed to load image'));
};
});
}
/**
* 中位数切分法进行色彩量化
* @param data 图像数据
* @param levels 切分级别
* @returns 颜色组
*/
function medianCut(data: Uint8ClampedArray, levels: number): any[] {
const colors = [];
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
const a = data[i + 3];
// 跳过透明像素
if (a < 128) continue;
colors.push({
r, g, b,
count: 1,
sumR: r, sumG: g, sumB: b
});
}
// 如果没有颜色数据,返回默认白色
if (colors.length === 0) {
return [{
count: 1,
sumR: 255, sumG: 255, sumB: 255
}];
}
// 开始中位数切分
let colorGroups = [{
colors,
count: colors.length,
sumR: colors.reduce((sum, c) => sum + c.r, 0),
sumG: colors.reduce((sum, c) => sum + c.g, 0),
sumB: colors.reduce((sum, c) => sum + c.b, 0)
}];
for (let i = 0; i < levels; i++) {
const newGroups = [];
for (const group of colorGroups) {
if (group.colors.length <= 1) {
newGroups.push(group);
continue;
}
// 找出颜色范围最大的通道
const rMin = Math.min(...group.colors.map(c => c.r));
const rMax = Math.max(...group.colors.map(c => c.r));
const gMin = Math.min(...group.colors.map(c => c.g));
const gMax = Math.max(...group.colors.map(c => c.g));
const bMin = Math.min(...group.colors.map(c => c.b));
const bMax = Math.max(...group.colors.map(c => c.b));
const rRange = rMax - rMin;
const gRange = gMax - gMin;
const bRange = bMax - bMin;
let sortChannel = 'r';
if (gRange > rRange && gRange > bRange) {
sortChannel = 'g';
} else if (bRange > rRange && bRange > gRange) {
sortChannel = 'b';
}
// 按最大范围通道排序
group.colors.sort((a, b) => a[sortChannel] - b[sortChannel]);
// 切分中位数
const mid = Math.floor(group.colors.length / 2);
const group1 = group.colors.slice(0, mid);
const group2 = group.colors.slice(mid);
// 添加新组
newGroups.push({
colors: group1,
count: group1.length,
sumR: group1.reduce((sum, c) => sum + c.r, 0),
sumG: group1.reduce((sum, c) => sum + c.g, 0),
sumB: group1.reduce((sum, c) => sum + c.b, 0)
});
newGroups.push({
colors: group2,
count: group2.length,
sumR: group2.reduce((sum, c) => sum + c.r, 0),
sumG: group2.reduce((sum, c) => sum + c.g, 0),
sumB: group2.reduce((sum, c) => sum + c.b, 0)
});
}
colorGroups = newGroups;
}
return colorGroups;
}

View File

@ -6,7 +6,7 @@
</div>
<div class="workflow-container">
<div class="left-wap mr-24px" v-if="isCollapsed == false">
<div class="w-full w-100% mb-15px bg-#F0EDFF h-160px rounded-8px">
<div class="w-full w-100% mb-15px h-160px rounded-8px bg-#E6E6E8" v-image-main-color="cozeInfo.image_url">
<img v-if="cozeInfo?.image_url" :src="cozeInfo?.image_url" class= "w-full h-full object-contain" />
</div>
<div class="content mb-15px">
@ -43,7 +43,7 @@
</div>
</div>
</div>
<div class="content" id="coze-chat-container"></div>
<div class="coze-content" id="coze-chat-container"></div>
</div>
</div>
</div>

View File

@ -243,21 +243,17 @@
gap: 10px;
display: inline-flex;
.body {
width: 20px;
height: 20px;
position: relative;
overflow: hidden;
}
}
.content {
.coze-content {
align-self: stretch;
justify-content: space-between;
align-items: flex-start;
display: inline-flex;
:deep(.coze-chat-sdk) {
border-radius: none !important;
box-shadow: none !important;
}
.form {
width: 400px;

View File

@ -1,5 +1,5 @@
<template>
<div class="agent-wrap relative">
<div class="agent-wrap relative h-full">
<a-input
v-model="query.name"
@blur="getData()"
@ -23,10 +23,10 @@
:xxl="4"
v-for="(product, k) in item.agent_products">
<div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)">
<img
class="card-image h-120px object-contain w-100% mb-8px bg-#F0EDFF"
:src="product?.image_url"
/>
<div class="card-image h-120px w-100% bg-cover bg-center bg-#E6E6E8" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100% mb-8px" :src="product?.image_url"/>
</div>
<div class="card-content">
<div class="card-title mb-4px">{{ product?.name }}</div>
<TextoverTips :context="product.description" class="card-description mb-8px color-#737478 text-14px lh-22px font-400" :line="2" />

View File

@ -6,7 +6,7 @@
</div>
<div class="workflow-container">
<div class="left-wap mr-24px" v-if="isCollapsed == false">
<div class="w-full w-100% mb-15px bg-#F0EDFF h-160px rounded-8px">
<div class="w-full w-100% mb-15px h-160px rounded-8px bg-#E6E6E8" v-image-main-color="cozeInfo.image_url">
<img v-if="cozeInfo?.image_url" :src="cozeInfo?.image_url" class= "w-full h-full object-contain" />
</div>
<div class="content mb-15px">
@ -104,7 +104,7 @@ import { marked } from 'marked';
import DOMPurify from 'dompurify';
import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { formatNumberShow } from '@/utils/tools';
import { formatNumberShow } from "@/utils/tools"
const formFields = ref({});
const history = ref([]);