You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

525 lines
14 KiB
Vue

<script setup>
import {ref, onMounted, onUnmounted, watch, computed, nextTick, markRaw} from 'vue';
import * as echarts from 'echarts';
import centerIcon from '@/assets/img/Frame 253.png'
import pieBg from '@/assets/img/Frame 274.png'
import {TabTitle1,TabTitle2,TabTitle3,TabTitle4} from "@/components/technology/tabList.jsx";
const tabList=ref([
{
title:markRaw(TabTitle1),
value:40
},
{
title:markRaw(TabTitle2),
value:20
},
{
title:markRaw(TabTitle3),
value:10
},
{
title:markRaw(TabTitle4),
value:100
}
])
// 1. 定义容器引用
const chartRef = ref(null);
// 定义 ECharts 实例引用
let myChart = null;
// 响应式变量存储显示区的占比默认20%
const showRatio = ref(tabList.value[0].value);
// 2. 初始化 ECharts 饼图
const initPieChart = () => {
// 获取容器 DOM 元素
const dom = chartRef.value;
if (!dom) return;
// 初始化 ECharts 实例
myChart = echarts.init(dom);
updatePieOption();
}
const updatePieOption=()=>{
if (!myChart) return;
// 计算两个扇区的数值总和设为100方便对应百分比
const showValue = showRatio.value;
const transparentValue = 100 - showValue; // 计算显示区对应的角度(占比 * 3.6因为100%对应360°
const showAreaAngle = showValue * 3.6;
// 核心修改叠加90°偏移确保显示区中间对齐垂直向上90°
const startAngle = 90 + (showAreaAngle / 2);
// 提取显示区的渐变样式(复用,避免冗余代码)
const showAreaGradient = new echarts.graphic.RadialGradient(
0.5,
0.5,
0.8,
[
{ offset: 0, color: '#25C5D0' },
{ offset: 1, color: 'rgba(11, 224, 232, 0)' }
]
);
// 饼图配置项(核心功能配置)
const option = {
// 全局背景透明,避免遮挡背景图
backgroundColor: 'transparent',
// 图形组件:用于渲染背景图和中心图标
graphic: [
// ① 背景图片(层级最低,在饼图下方)
{
type: 'image',
id: 'pie-bg-image',
// 背景图样式:地址、宽高
style: {
// 替换为你的实际背景图 URL本地图片放 public 文件夹,路径如 /bg.jpg网络图片直接填完整URL
image: pieBg,
width: chartRef.value.offsetWidth,
height: chartRef.value.offsetHeight
},
left: 'center', // 水平居中
top: 'center', // 垂直居中
z: -1 // 层级低于饼图(确保饼图在背景图上方)
},
// ② 中心图标(层级最高,在饼图上方)
{
type: 'image',
id: 'pie-center-icon',
style: {
// 替换为你的实际中心图标 URL本地/网络地址均可)
image: centerIcon,
width: 84.82, // 图标宽度,可自定义
height: 84.82 // 图标高度,可自定义
},
left: 'center',
top: 'center',
z: 2 // 层级高于饼图(确保图标在饼图中心显示)
}
],
series: [
{
type: 'pie', // 饼图类型
radius: ['12%', '100%'], // 圆环内外半径内半径40%外半径70%,形成环形)
center: ['50%', '50%'], // 饼图居中显示
// selectedMode: 'single', // 单选选中模式(点击切换选中状态)
selectedOffset: 0, // 选中时不偏移保持原位如需偏移可设为5等数值
hoverAnimation: false, // 核心1关闭鼠标移入动画取消移入视觉变化
silent: true, // 核心修复3禁用扇区的鼠标交互彻底杜绝移入/点击触发样式变化)
startAngle: startAngle,
// 饼图数据(可根据业务需求动态修改)
data: [
{
value: showValue,
name: '显示区',
selected: true // 默认选中显示区
},
{
value: transparentValue,
name: '透明透视区'
}
],
emphasis: {
itemStyle:{
color: function (params) {
console.log(params)
return params.name === '透明透视区'
? 'rgba(f, f, f, 0)' // 透视区移入:仍完全透明
: showAreaGradient; // 显示区移入:和默认样式一致,取消移入高亮
},
// 额外兜底:禁用所有可能的高亮样式
opacity: 1 ,// 保持透明度不变
shadowBlur: 30,
shadowColor: 'rgba(0, 255, 255, 1)',
borderWidth: 4,
borderColor:'#00FEFE',
}
},
// 扇区样式配置(核心:透明渐变色)
itemStyle: {
borderWidth: 4,
borderColor:'#00FEFE',
// 选中状态下的样式(透明径向渐变)
// 正常状态(非选中)的样式(灰色半透明,突出选中区域)
normal: {
// 透明透视区样式:完全透明,穿透显示背景图
color: function (params) {
// 根据扇区名称分配样式
return params.name === '透明透视区'
? 'rgba(0, 0, 0, 0)' // 透视区:完全透明
: showAreaGradient; // 显示区:默认渐变样式
},
shadowBlur: 50,
shadowColor: 'rgba(0, 255, 255, 0.8)',
}
},
// 隐藏饼图文字标签和引导线(如需显示可改为 show: true
label: { show: false },
labelLine: { show: false }
}
]
};
// 设置配置项,渲染饼图
myChart.setOption(option,true);
};
// 4. 按钮点击事件:修改显示区比例
const changePieRatio = (ratio) => {
// 限制比例范围在0-100之间
if (ratio < 0) ratio = 0;
if (ratio > 100) ratio = 100;
showRatio.value = ratio;
// 比例变化后,更新饼图
updatePieOption();
};
// 3. 窗口大小变化时,自适应调整饼图
const resizeChart = () => {
if (myChart) {
myChart.resize();
updatePieOption();
}
};
watch(showRatio,()=>{
updatePieOption();
})
// 4. 生命周期钩子
// 组件挂载后初始化饼图
onMounted(() => {
initPieChart();
// 监听窗口大小变化,实现自适应
window.addEventListener('resize', resizeChart);
});
// 组件卸载前销毁 ECharts 实例,释放资源
onUnmounted(() => {
if (myChart) {
myChart.dispose();
myChart = null;
}
window.removeEventListener('resize', resizeChart);
});
//tab切换
const tabNavRef = ref(null);
const tabItemRefs = ref([]);
// 新增:存储当前激活 Tab 的顶部偏移和实际高度
const activeTabTop = ref(0);
const activeTabHeight = ref(0);
// 新增:动态更新 Tab 滑动背景的 top 和 height
const updateTabBgStyle = () => {
nextTick(() => { // 确保 DOM 渲染完成后再获取
const activeItem = tabItemRefs.value[activeIndex.value];
const navDom = tabNavRef.value;
if (!activeItem || !navDom) return;
// 获取 Tab 项和导航容器的实际几何信息
const itemRect = activeItem.getBoundingClientRect();
const navRect = navDom.getBoundingClientRect();
// 计算相对顶部偏移(避免窗口滚动影响)
activeTabTop.value = itemRect.top - navRect.top;
// 直接获取 Tab 项的真实高度(适配 space-between 布局)
activeTabHeight.value = itemRect.height;
});
};
const activeIndex = ref(0);
let carouselTimer = null;
const carouselInterval = 3000;
// 4. 手动点击切换Tab核心逻辑是修改activeTabValue变量
const handleTabClick = (index) => {
activeIndex.value = index;
// 切换Tab时修改公共变量的值为当前Tab对应的tabValue
showRatio.value = tabList.value[index].value;
updateTabBgStyle(); // 新增:切换 Tab 时更新高度
resetCarouselTimer(); // 手动点击后重置轮播定时器
};
// 5. 自动轮播逻辑自动切换Tab时同步修改activeTabValue变量
const autoCarousel = () => {
if (carouselTimer) clearInterval(carouselTimer);
carouselTimer = setInterval(() => {
activeIndex.value = (activeIndex.value + 1) % tabList.value.length;
// 自动切换时,同步更新公共变量的值
showRatio.value = tabList.value[activeIndex.value].value;
updateTabBgStyle(); // 新增:自动轮播时更新高度
}, carouselInterval);
};
// 6. 重置轮播定时器
const resetCarouselTimer = () => {
clearInterval(carouselTimer);
autoCarousel();
};
onMounted(() => {
autoCarousel();
updateTabBgStyle(); // 新增:组件挂载时初始化高度
// 新增:窗口 resize 时重新计算高度
window.addEventListener('resize', updateTabBgStyle);
});
onUnmounted(() => {
clearInterval(carouselTimer);
window.removeEventListener('resize', updateTabBgStyle); // 新增:移除 resize 监听
});
</script>
<template>
<div>
<p class="title">多指向性可调</p>
<p class="text">
该技术赋予麦克风多种可切换的拾音模式用户可根据会议场景灵活选择例如在单人发言时使用单指向精准拾音在圆桌讨论时切换至全向模式覆盖全场这种硬件与算法结合的灵活性实现了对拾音区域的精确控制从物理层面有效排除了非发言区域的噪声干扰提升了系统的场景适应性与信噪比
</p>
<div class="bg">
<div class="tab-container">
<div class="tab-nav" ref="tabNavRef">
<ul>
<li
class="tab-item"
:class="{ active: activeIndex === index }"
v-for="(item, index) in tabList"
:key="index"
@click="handleTabClick(index)"
ref="tabItemRefs"
>
<component :is="item.title" />
</li>
</ul>
<div
class="tab-active-bg"
:style="{
top: `${activeTabTop}px`,
height: `${activeTabHeight}px`
}"
></div>
</div>
</div>
<div ref="chartRef" class="echarts-pie-container"></div>
</div>
</div>
</template>
<style scoped lang="less">
.title {
text-align: center;
font-weight: 500;
font-size: 48px;
line-height: 100%;
color: #000000;
margin: 90px 0 50px 0;
}
.text {
width: 1480px;
margin: 50px auto;
text-align: center;
font-weight: 400;
font-size: 18px;
line-height: 1.37em;
letter-spacing: 0.17em;
}
.bg {
display: flex;
justify-content: space-around;
align-items: center;
width: 1232px;
height: 668px;
background-image: url("@/assets/img/tecBg7.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center;
margin: auto;
background-color: #DEDEDE;
border-radius: 20px;
.tab-container {
width: 129px;
height: 508px;
background: #31313187;
border: 2px solid #A6FFFFA6;
border-radius: 12px;
padding: 20px 10px;
.tab-nav {
height: 100%;
position: relative;
.tab-active-bg {
position: absolute;
left: 0;
top: 0;
width: 100%;
transition: top 0.3s cubic-bezier(0.4, 0, 0.2, 1), height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 8px;
z-index: 0;
background: #00C9BE30;
color: #1DFFFF;
border: 1px solid #00FFFF;
box-shadow:
0px -2px 7.3px 0px #00FFFF inset,
0px 2px 4px 0px #00FFFF inset,
1px 2px 4px 0px #00FFFF inset,
0px 2px 4px 0px #00FFFF inset;
}
ul {
list-style: none;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
li:nth-child(1){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 25px;
}
}
li:nth-child(2){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 11px;
}
}
li:nth-child(3){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 7px;
}
}
li:nth-child(4){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 14px;
}
.svg-item:nth-child(3){
top: 6px;
}
}
}
.svg{
position: relative;
width: 60.5px;
height: 59.2px;
margin: auto;
.svg-item{
transform: translate(-50%);
position: absolute;
left: 50%;
}
}
}
}
}
.echarts-pie-container {
width: 581px; /* 宽度 */
height: 580px; /* 高度 */
}
</style>
<style lang="less">
.bg{
ul {
list-style: none;
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
.tab-item{
cursor: pointer;
color: #EDEDED;
padding: 5px;
border-radius: 12px;
border: 1px solid transparent;
}
.tab-item.active{
color: #1DFFFF;
svg:not(svg:nth-child(3)){
path{
fill: white;
}
circle{
fill: white;
stroke: white;
}
}
}
li:nth-child(1){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 25px;
}
}
li:nth-child(2){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 11px;
}
}
li:nth-child(3){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 13px;
}
.svg-item:nth-child(3){
top: 7px;
}
}
li:nth-child(4){
.svg-item:nth-child(1){
top: 5px;
}
.svg-item:nth-child(2){
top: 14px;
}
.svg-item:nth-child(3){
top: 6px;
}
}
}
.svg{
position: relative;
width: 60.5px;
height: 59.2px;
margin: auto;
.svg-item{
transform: translate(-50%);
position: absolute;
left: 50%;
}
}
}
</style>