冲突合并

This commit is contained in:
2090205686@qq.com 2025-05-20 13:31:23 +08:00
commit 71abc7497e
202 changed files with 36674 additions and 12548 deletions

4
.gitignore vendored
View File

@ -23,3 +23,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
sub-operation-service/package-lock.json
main/package-lock.json
sub-operation-service/vite.config.js
main/.eslintrc.cjs

View File

@ -39,7 +39,12 @@ module.exports = {
},
// 这里时配置规则的,自己看情况配置
rules: {
'prettier/prettier': 'error',
'prettier/prettier': [
'error',
{
endOfLine: 'auto', // 允许自动检测换行符
},
],
'no-debugger': 'off',
'no-unused-vars': 'off',
'vue/multi-word-component-names': 'off',

View File

@ -43,6 +43,7 @@ declare module 'vue' {
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
NewPie: typeof import('./src/components/custom-echart-hyaline-cake/new-pie.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SubTop: typeof import('./src/components/subTop.vue')['default']

View File

@ -9,16 +9,13 @@ import { useEcharts } from '@/hooks/useEcharts';
defineOptions({ name: 'NewHyalineCake' });
//
let selectedIndex = null;
let hoveredIndex = null;
// props
const props = defineProps({
chartData: {
type: Array,
default: () => [
//
{ name: '项目一', value: 60 },
{ name: '项目二', value: 44 },
{ name: '项目三', value: 32 },
@ -27,8 +24,8 @@ const props = defineProps({
option: {
type: Object,
default: () => ({
k: 1, // 1
itemGap: 0.1, // ,
k: 1, // 1
itemGap: 0.1, //
itemHeight: 120, // z
autoItemHeight: 0, // 使>0 itemHeight 使 autoItemHeight * value
opacity: 0.6, //
@ -46,17 +43,79 @@ const props = defineProps({
},
});
//
const emit = defineEmits(['click']);
// DOM
const chartRef = ref(null);
// 使 useEcharts ECharts
const { setOptions, getInstance } = useEcharts(chartRef);
// ECharts
const chartOption = ref({});
// pie2d
const defaultPie2dOption = {
center: ['50%', '50%'],
label: {
show: false,
position: 'inside',
formatter: '{c}',
color: '#fff',
distance: 0,
},
labelLine: {
show: false,
smooth: false,
length: props.option.itemGap * 500,
length2: props.option.itemGap * 800,
lineStyle: { color: '#fff', width: 1 },
},
};
// pie2dConfig
const pie2dOption = {
...defaultPie2dOption,
...(props.option.pie2dConfig || {}),
};
//
function initChart() {
// 3D()
const baseOption = getPie3D(props.chartData, props.option.k);
//
const finalOption = merge({}, baseOption, cloneDeep(props.option || {}));
chartOption.value = finalOption;
//
setOptions(chartOption.value);
// DOM + ECharts
nextTick(() => {
const chart = getInstance();
if (!chart) {
return;
}
//
// chart.off('click');
chart.off('mouseover');
chart.off('globalout');
// chart.on('click', handleClick);
chart.on('mouseover', handleMouseover);
chart.on('globalout', handleGlobalout);
});
}
//
onMounted(() => {
initChart();
});
//
watch(
[() => props.chartData, () => props.option],
() => {
if (props.chartData && props.chartData.length) {
initChart();
}
},
{ immediate: true, deep: true }
);
/**
* 获取 parametric 曲面方程
* @param {Number} startRatio 起点弧度
@ -69,8 +128,7 @@ const chartOption = ref({});
* @return {Object} parametric 曲面方程
*/
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h, offsetZ = 0) {
console.log('getParametricEquation params :>> ', startRatio, endRatio, isSelected, isHovered, k, h, offsetZ);
//
// console.log('getParametricEquation params :>> ', startRatio, endRatio, isSelected, isHovered, k, h, offsetZ);
const midRatio = (startRatio + endRatio) / 2;
const startRadian = startRatio * Math.PI * 2;
const endRadian = endRatio * Math.PI * 2;
@ -89,7 +147,6 @@ function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h
// 1
const hoverRate = isHovered ? 1.05 : 1;
// parametric
return {
u: {
// u -π 3π
@ -155,10 +212,6 @@ function getPie3D(pieData, internalDiameterRatio) {
const k = typeof internalDiameterRatio !== 'undefined' ? (1 - internalDiameterRatio) / (1 + internalDiameterRatio) : 1 / 3;
//
// pieData.forEach((item) => {
// sumValue += item.value;
// });
// series
for (let i = 0; i < pieData.length; i += 1) {
sumValue += pieData[i].value;
@ -169,13 +222,8 @@ function getPie3D(pieData, internalDiameterRatio) {
parametric: true,
wireframe: { show: false },
pieData: pieData[i],
// itemStyle: {
// opacity: props.option.opacity,
// borderRadius: 300,
// borderColor: '#fff',
// }
pieStatus: {
selected: true,
selected: false,
hovered: false,
k,
},
@ -189,9 +237,10 @@ function getPie3D(pieData, internalDiameterRatio) {
}
series.push(seriesItem);
}
// 使 sumValue getParametricEquation
// series-surface series-surface.parametricEquation
console.log(series);
// console.log(series);
for (let i = 0; i < series.length; i += 1) {
endValue = startValue + series[i].pieData.value;
@ -212,6 +261,34 @@ function getPie3D(pieData, internalDiameterRatio) {
legendData.push(series[i].name);
}
// 3. 2D pie legend/label
const outerPercent = 50;
const innerPercent =
internalDiameterRatio != null ? Math.round(internalDiameterRatio * outerPercent) : Math.round(((1 - k) / (1 + k)) * outerPercent);
const pie2dData = [];
for (let i = 0; i < pieData.length; i++) {
pie2dData.push({
name: pieData[i].name,
value: pieData[i].value,
// itemStyle: { opacity: 1 },
});
}
series.push({
name: 'pie2d',
type: 'pie',
data: pie2dData,
radius: [`${innerPercent}%`, `${outerPercent}%`],
center: pie2dOption.center,
startAngle: props.option.startAngle || 90,
clockwise: false,
itemStyle: {
color: 'transparent',
},
avoidLabelOverlap: false,
label: pie2dOption.label,
labelLine: pie2dOption.labelLine,
});
//
// legendDataseries
const option = {
@ -240,12 +317,12 @@ function getPie3D(pieData, internalDiameterRatio) {
// animation: false,
tooltip: {
formatter: (params) => {
if (params.seriesName !== 'mouseoutSeries') {
return `${
params.seriesName
}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
params.color
};"></span>${option.series[params.seriesIndex].pieData.value}`;
// 3D surface
if (params.seriesType === 'surface') {
// params.seriesIndex
const serie = chartOption.value.series[params.seriesIndex];
const realValue = serie.pieData.value;
return `${params.seriesName}${realValue}`;
}
return '';
},
@ -276,23 +353,6 @@ function getPie3D(pieData, internalDiameterRatio) {
autoRotate: true,
distance: 150,
},
// SSAO
postEffect: {
// 齿
enable: false,
bloom: {
enable: true,
bloomIntensity: 0.1,
},
SSAO: {
enable: true,
quality: 'medium',
radius: 2,
},
// temporalSuperSampling: {
// enable: true,
// },
},
},
series,
};
@ -301,7 +361,7 @@ function getPie3D(pieData, internalDiameterRatio) {
// mouseover
function handleMouseover(params) {
console.log('mouseover');
// console.log('mouseover');
const idx = params.seriesIndex;
const series = chartOption.value.series;
@ -345,7 +405,7 @@ function updateSeriesHover(index, toHover) {
const k = typeof status.k === 'number' ? status.k : 1 / 3;
// newHeight hover 5 height
const baseHeight = props.option.autoItemHeight > 0 ? props.option.autoItemHeight : props.option.itemHeight;
const newHeight = toHover ? baseHeight + 5 : baseHeight;
const newHeight = toHover ? baseHeight + 80 : baseHeight;
// parametricEquation
item.parametricEquation = getParametricEquation(start, end, isSelected, toHover, k, newHeight);
//
@ -355,34 +415,6 @@ function updateSeriesHover(index, toHover) {
};
}
//
function initChart() {
// 3D()
const baseOption = getPie3D(props.chartData, props.option.k);
//
// const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {}));
const finalOption = merge({}, baseOption, cloneDeep(props.option || {}));
chartOption.value = finalOption;
//
setOptions(chartOption.value);
// DOM + ECharts
nextTick(() => {
const chart = getInstance();
if (!chart) {
console.warn('ECharts 实例未初始化,事件绑定失败');
return;
}
//
// chart.off('click');
chart.off('mouseover');
chart.off('globalout');
// chart.on('click', handleClick);
chart.on('mouseover', handleMouseover);
chart.on('globalout', handleGlobalout);
});
}
function handleClick(params) {
//
if (params.seriesName === 'mouseoutSeries') return;
@ -418,35 +450,10 @@ function handleClick(params) {
series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h);
series[idx].pieStatus.selected = isSelected;
//
selectedIndex = isSelected ? idx : null;
//
setOptions(optionVal);
// click 使
emit('click', params);
}
//
window.debugZValues = {
current: null,
history: [],
};
//
onMounted(() => {
initChart();
});
//
watch(
[() => props.chartData, () => props.option],
() => {
if (props.chartData && props.chartData.length) {
initChart();
}
},
{ immediate: true, deep: true }
);
</script>
<style scoped>

View File

@ -0,0 +1,114 @@
<template>
<div ref="chartRef" class="chart-container" />
</template>
<script setup>
import { ref, onMounted, watch } from 'vue';
import { useEcharts } from '@/hooks/useEcharts';
const props = defineProps({
chartData: {
type: Array,
default: () => [],
},
// ['30%', '50%']
radius: {
type: Array,
default: () => ['30%', '50%'],
},
//
alpha: {
type: Number,
default: 30,
},
//
beta: {
type: Number,
default: 0,
},
//
autoRotate: {
type: Boolean,
default: false,
},
height: {
type: String,
default: '300px',
},
});
const chartRef = ref(null);
const { setOptions } = useEcharts(chartRef);
function buildOption(data) {
return {
tooltip: {
trigger: 'item',
formatter: '{b}{c} ({d}%)',
},
legend: {
orient: 'vertical',
right: 20,
top: 'center',
textStyle: { color: '#fff' },
},
series: [
{
type: 'pie3D',
name: '3D 饼图',
data,
radius: props.radius,
center: ['50%', '50%'],
label: {
show: true,
formatter: '{b}\n{c}',
},
shading: 'realistic', //
itemStyle: {
opacity: 1,
borderWidth: 1,
borderColor: '#333',
},
emphasis: {
label: { fontSize: 16, color: '#fff' },
itemStyle: { opacity: 1 },
},
viewControl: {
alpha: props.alpha, //
beta: props.beta, //
autoRotate: props.autoRotate,
distance: 120,
zoomSensitivity: 0,
panSensitivity: 0,
},
},
],
};
}
let option = buildOption(props.chartData);
onMounted(() => {
setOptions(option);
});
watch(
() => props.chartData,
(v) => {
option = buildOption(v);
setOptions(option);
},
{ deep: true }
);
</script>
<style scoped>
.chart-container {
width: 100%;
height: var(--chart-height);
}
/* 传递 height 到局部变量 */
:root {
--chart-height: 300px;
}
</style>

View File

@ -42,20 +42,49 @@ const state = reactive({
icon: 'rect',
left: 'center',
textStyle: {
color: '#fff', //
fontSize: 14,
},
itemStyle: {
display: 'float',
top: '120px',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
color: '#fff',
fontSize: 16,
},
formatter: (name) => name,
},
pie2dConfig: {
center: ['50%', '40%'],
label: {
show: true,
position: 'outside',
formatter: (params) => {
const total = 221.8 + 70.01; //
const percent = ((params.value / total) * 100).toFixed(0);
return `{name|${params.name}}\n{value|${params.value} 万吨\n${percent}%}`;
},
rich: {
name: {
fontSize: 16,
fontWeight: 'bold',
color: '#ffffff',
lineHeight: 20,
align: 'center',
},
value: {
fontSize: 16,
color: '#79F5AF',
lineHeight: 18,
align: 'center',
},
},
color: '#fff',
},
labelLine: {
show: true,
length: 40,
length2: 40,
lineStyle: { color: '#fff', width: 2 },
},
},
},
data: [
{ value: 76, name: '产业运营平台' },
{ value: 24, name: '其它', floatZ: 1 },
{ value: 221.8, name: '产业运营平台' },
{ value: 70.01, name: '其它', floatZ: 1 },
],
});
</script>

View File

@ -43,6 +43,7 @@ const chartConfig = ref({
['圆茄种苗', '0.3元/棵', '0.4元/棵'],
['高氮复合肥', '1850元/吨', '1980元/吨'],
['硫酸钾', '1250元/吨', '1380元/吨'],
['高氮复合肥', '1850元/吨', '1980元/吨'],
['西红柿种苗', '0.3元/棵', '0.4元/棵'],
],
//

View File

@ -4,7 +4,7 @@
<section class="_circle">{{ _circleNum }}</section>
<section class="_text">{{ allNum }}万亩</section>
</section>
<section class="_right _scroll">
<section ref="scrollRef" class="_right _scroll">
<section
v-for="(item, i) in list"
:key="`right_${i}`"
@ -23,23 +23,26 @@
</template>
<script setup>
import { ref, watch, computed } from 'vue';
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
data: {
type: Array,
default: () => {
return [];
},
default: () => [],
},
});
const list = ref([]);
const ac = ref(0);
const allNum = ref(0);
const currNum = ref(0);
const scrollRef = ref(null);
let timer = null;
watch(
() => props.data,
() => {
allNum.value = 0;
list.value = props.data.map((v, i) => {
allNum.value += v.value;
return {
@ -49,29 +52,74 @@ watch(
};
});
list.value.sort((a, b) => b.value - a.value);
if (list.value.length) {
ac.value = list.value[0].id;
currNum.value = list.value[0].value;
scrollToActive();
startAutoScroll();
}
},
{
immediate: true,
}
{ immediate: true }
);
function randomColor() {
let obj = {
r: 0,
g: 0,
b: 0,
};
Object.keys(obj).forEach((key) => {
obj[key] = Math.floor(Math.random() * 256);
});
return `rgb(${obj.r},${obj.g},${obj.b})`;
return `rgb(${rand255()},${rand255()},${rand255()})`;
}
function rand255() {
return Math.floor(Math.random() * 256);
}
function handleAc(val) {
ac.value = val;
currNum.value = list.value.find((v) => v.id == val).value ?? 0;
const target = list.value.find((v) => v.id == val);
currNum.value = target?.value ?? 0;
scrollToActive();
}
function scrollToActive() {
nextTick(() => {
const container = scrollRef.value;
const activeEl = container?.querySelector('.list_item.active');
if (container && activeEl) {
const containerTop = container.scrollTop;
const containerHeight = container.clientHeight;
const elTop = activeEl.offsetTop;
const elHeight = activeEl.offsetHeight;
if (elTop < containerTop || elTop + elHeight > containerTop + containerHeight) {
container.scrollTop = elTop - containerHeight / 2 + elHeight / 2;
}
}
});
}
const _circleNum = computed(() => {
let s = ((currNum.value / allNum.value) * 100).toFixed(1);
return s + '%';
if (!allNum.value) return '0%';
return ((currNum.value / allNum.value) * 100).toFixed(1) + '%';
});
function startAutoScroll() {
stopAutoScroll();
let index = list.value.findIndex((item) => item.id === ac.value);
timer = setInterval(() => {
index = (index + 1) % list.value.length;
handleAc(list.value[index].id);
}, 2000);
}
function stopAutoScroll() {
if (timer) {
clearInterval(timer);
timer = null;
}
}
onMounted(() => {
startAutoScroll();
});
onBeforeUnmount(() => {
stopAutoScroll();
});
</script>
@ -156,5 +204,8 @@ const _circleNum = computed(() => {
}
}
}
._right::-webkit-scrollbar {
display: none;
}
}
</style>

View File

@ -17,7 +17,7 @@ const state = reactive({
option: {
k: 0,
opacity: 1,
itemGap: 0.2,
itemGap: 0.1,
legendSuffix: '万亩',
itemHeight: 200,
grid3D: {

View File

@ -1,344 +0,0 @@
let selectedIndex = '';
let hoveredIndex = '';
option = getPie3D(
[
{
name: 'cc',
value: 47,
itemStyle: {
color: '#f77b66',
},
},
{
name: 'aa',
value: 44,
itemStyle: {
color: '#3edce0',
},
},
{
name: 'bb',
value: 32,
itemStyle: {
color: '#f94e76',
},
},
{
name: 'ee',
value: 16,
itemStyle: {
color: '#018ef1',
},
},
{
name: 'dd',
value: 23,
itemStyle: {
color: '#9e60f9',
},
},
],
0.59
);
// 生成扇形的曲面参数方程
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
// 计算
const midRatio = (startRatio + endRatio) / 2;
const startRadian = startRatio * Math.PI * 2;
const endRadian = endRatio * Math.PI * 2;
const midRadian = midRatio * Math.PI * 2;
// 如果只有一个扇形,则不实现选中效果。
if (startRatio === 0 && endRatio === 1) {
// eslint-disable-next-line no-param-reassign
isSelected = false;
}
// 通过扇形内径/外径的值,换算出辅助参数 k默认值 1/3
// eslint-disable-next-line no-param-reassign
k = typeof k !== 'undefined' ? k : 1 / 3;
// 计算选中效果分别在 x 轴、y 轴方向上的位移(未选中,则位移均为 0
const offsetX = isSelected ? Math.cos(midRadian) * 0.1 : 0;
const offsetY = isSelected ? Math.sin(midRadian) * 0.1 : 0;
// 计算高亮效果的放大比例(未高亮,则比例为 1
const hoverRate = isHovered ? 1.05 : 1;
// 返回曲面参数方程
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32,
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20,
},
x(u, v) {
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y(u, v) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z(u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
// 当前图形的高度是Z根据h每个value的值决定的
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
},
};
}
// 生成模拟 3D 饼图的配置项
function getPie3D(pieData, internalDiameterRatio) {
const series = [];
// 总和
let sumValue = 0;
let startValue = 0;
let endValue = 0;
const legendData = [];
const k =
typeof internalDiameterRatio !== 'undefined'
? (1 - internalDiameterRatio) / (1 + internalDiameterRatio)
: 1 / 3;
// 为每一个饼图数据,生成一个 series-surface 配置
for (let i = 0; i < pieData.length; i += 1) {
sumValue += pieData[i].value;
const seriesItem = {
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
parametric: true,
wireframe: {
show: false,
},
pieData: pieData[i],
pieStatus: {
selected: false,
hovered: false,
k,
},
};
if (typeof pieData[i].itemStyle !== 'undefined') {
const { itemStyle } = pieData[i];
// eslint-disable-next-line no-unused-expressions
typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
// eslint-disable-next-line no-unused-expressions
typeof pieData[i].itemStyle.opacity !== 'undefined'
? (itemStyle.opacity = pieData[i].itemStyle.opacity)
: null;
seriesItem.itemStyle = itemStyle;
}
series.push(seriesItem);
}
// 使用上一次遍历时,计算出的数据和 sumValue调用 getParametricEquation 函数,
// 向每个 series-surface 传入不同的参数方程 series-surface.parametricEquation也就是实现每一个扇形。
console.log(series);
for (let i = 0; i < series.length; i += 1) {
endValue = startValue + series[i].pieData.value;
series[i].pieData.startRatio = startValue / sumValue;
series[i].pieData.endRatio = endValue / sumValue;
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
// 我这里做了一个处理使除了第一个之外的值都是10
series[i].pieData.value === series[0].pieData.value ? 35 : 10
);
startValue = endValue;
legendData.push(series[i].name);
}
// 准备待返回的配置项,把准备好的 legendData、series 传入。
const option = {
// animation: false,
tooltip: {
formatter: (params) => {
if (params.seriesName !== 'mouseoutSeries') {
return `${
params.seriesName
}<br/><span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:${
params.color
};"></span>${option.series[params.seriesIndex].pieData.value}`;
}
return '';
},
},
xAxis3D: {
min: -1,
max: 1,
},
yAxis3D: {
min: -1,
max: 1,
},
zAxis3D: {
min: -1,
max: 1,
},
grid3D: {
show: false,
boxHeight: 5,
top: '-20%',
viewControl: {
// 3d效果可以放大、旋转等请自己去查看官方配置
alpha: 35,
// beta: 30,
rotateSensitivity: 1,
zoomSensitivity: 0,
panSensitivity: 0,
autoRotate: true,
distance: 150,
},
// 后处理特效可以为画面添加高光、景深、环境光遮蔽SSAO、调色等效果。可以让整个画面更富有质感。
postEffect: {
// 配置这项会出现锯齿,请自己去查看官方配置有办法解决
enable: false,
bloom: {
enable: true,
bloomIntensity: 0.1,
},
SSAO: {
enable: true,
quality: 'medium',
radius: 2,
},
// temporalSuperSampling: {
// enable: true,
// },
},
},
series,
};
return option;
}
// 修正取消高亮失败的 bug
// 监听 mouseover近似实现高亮放大效果
myChart.on('mouseover', function (params) {
// 准备重新渲染扇形所需的参数
let isSelected;
let isHovered;
let startRatio;
let endRatio;
let k;
let i;
// 如果触发 mouseover 的扇形当前已高亮,则不做操作
if (hoveredIndex === params.seriesIndex) {
return;
// 否则进行高亮及必要的取消高亮操作
} else {
// 如果当前有高亮的扇形,取消其高亮状态(对 option 更新)
if (hoveredIndex !== '') {
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 false。
isSelected = option.series[hoveredIndex].pieStatus.selected;
isHovered = false;
startRatio = option.series[hoveredIndex].pieData.startRatio;
endRatio = option.series[hoveredIndex].pieData.endRatio;
k = option.series[hoveredIndex].pieStatus.k;
i = option.series[hoveredIndex].pieData.value === option.series[0].pieData.value ? 35 : 10;
// 对当前点击的扇形,执行取消高亮操作(对 option 更新)
option.series[hoveredIndex].parametricEquation = getParametricEquation(
startRatio,
endRatio,
isSelected,
isHovered,
k,
i
);
option.series[hoveredIndex].pieStatus.hovered = isHovered;
// 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
hoveredIndex = '';
}
// 如果触发 mouseover 的扇形不是透明圆环,将其高亮(对 option 更新)
if (params.seriesName !== 'mouseoutSeries') {
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
isSelected = option.series[params.seriesIndex].pieStatus.selected;
isHovered = true;
startRatio = option.series[params.seriesIndex].pieData.startRatio;
endRatio = option.series[params.seriesIndex].pieData.endRatio;
k = option.series[params.seriesIndex].pieStatus.k;
// 对当前点击的扇形,执行高亮操作(对 option 更新)
option.series[params.seriesIndex].parametricEquation = getParametricEquation(
startRatio,
endRatio,
isSelected,
isHovered,
k,
option.series[params.seriesIndex].pieData.value + 5
);
option.series[params.seriesIndex].pieStatus.hovered = isHovered;
// 记录上次高亮的扇形对应的系列号 seriesIndex
hoveredIndex = params.seriesIndex;
}
// 使用更新后的 option渲染图表
myChart.setOption(option);
}
});
// 修正取消高亮失败的 bug
myChart.on('globalout', function () {
if (hoveredIndex !== '') {
// 从 option.series 中读取重新渲染扇形所需的参数,将是否高亮设置为 true。
isSelected = option.series[hoveredIndex].pieStatus.selected;
isHovered = false;
k = option.series[hoveredIndex].pieStatus.k;
startRatio = option.series[hoveredIndex].pieData.startRatio;
endRatio = option.series[hoveredIndex].pieData.endRatio;
// 对当前点击的扇形,执行取消高亮操作(对 option 更新)
i = option.series[hoveredIndex].pieData.value === option.series[0].pieData.value ? 35 : 10;
option.series[hoveredIndex].parametricEquation = getParametricEquation(
startRatio,
endRatio,
isSelected,
isHovered,
k,
i
);
option.series[hoveredIndex].pieStatus.hovered = isHovered;
// 将此前记录的上次选中的扇形对应的系列号 seriesIndex 清空
hoveredIndex = '';
}
// 使用更新后的 option渲染图表
myChart.setOption(option);
});

File diff suppressed because one or more lines are too long

1074
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ VITE_PORT = 9526
VITE_APP_MIAN = 'daimp-front-main'
VITE_APP_MIAN_URL = 'http://localhost:9000'
VITE_APP_NAME = 'sub-operation-service'
VITE_APP_BASE_API = '/platform'
VITE_APP_BASE_API = '/apis'
VITE_APP_BASE_URL = 'http://192.168.18.99:88'
VITE_APP_UPLOAD_API = '/uploadApis'
VITE_APP_UPLOAD_URL = 'http://192.168.18.99:9300'

75
sub-operation-service/auto-imports.d.ts vendored Normal file
View File

@ -0,0 +1,75 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
export {}
declare global {
const EffectScope: typeof import('vue')['EffectScope']
const computed: typeof import('vue')['computed']
const createApp: typeof import('vue')['createApp']
const customRef: typeof import('vue')['customRef']
const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']
const defineComponent: typeof import('vue')['defineComponent']
const effectScope: typeof import('vue')['effectScope']
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
const getCurrentScope: typeof import('vue')['getCurrentScope']
const h: typeof import('vue')['h']
const inject: typeof import('vue')['inject']
const isProxy: typeof import('vue')['isProxy']
const isReactive: typeof import('vue')['isReactive']
const isReadonly: typeof import('vue')['isReadonly']
const isRef: typeof import('vue')['isRef']
const markRaw: typeof import('vue')['markRaw']
const nextTick: typeof import('vue')['nextTick']
const onActivated: typeof import('vue')['onActivated']
const onBeforeMount: typeof import('vue')['onBeforeMount']
const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']
const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']
const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']
const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']
const onDeactivated: typeof import('vue')['onDeactivated']
const onErrorCaptured: typeof import('vue')['onErrorCaptured']
const onMounted: typeof import('vue')['onMounted']
const onRenderTracked: typeof import('vue')['onRenderTracked']
const onRenderTriggered: typeof import('vue')['onRenderTriggered']
const onScopeDispose: typeof import('vue')['onScopeDispose']
const onServerPrefetch: typeof import('vue')['onServerPrefetch']
const onUnmounted: typeof import('vue')['onUnmounted']
const onUpdated: typeof import('vue')['onUpdated']
const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']
const provide: typeof import('vue')['provide']
const reactive: typeof import('vue')['reactive']
const readonly: typeof import('vue')['readonly']
const ref: typeof import('vue')['ref']
const resolveComponent: typeof import('vue')['resolveComponent']
const shallowReactive: typeof import('vue')['shallowReactive']
const shallowReadonly: typeof import('vue')['shallowReadonly']
const shallowRef: typeof import('vue')['shallowRef']
const toRaw: typeof import('vue')['toRaw']
const toRef: typeof import('vue')['toRef']
const toRefs: typeof import('vue')['toRefs']
const toValue: typeof import('vue')['toValue']
const triggerRef: typeof import('vue')['triggerRef']
const unref: typeof import('vue')['unref']
const useAttrs: typeof import('vue')['useAttrs']
const useCssModule: typeof import('vue')['useCssModule']
const useCssVars: typeof import('vue')['useCssVars']
const useId: typeof import('vue')['useId']
const useLink: typeof import('vue-router')['useLink']
const useModel: typeof import('vue')['useModel']
const useRoute: typeof import('vue-router')['useRoute']
const useRouter: typeof import('vue-router')['useRouter']
const useSlots: typeof import('vue')['useSlots']
const useTemplateRef: typeof import('vue')['useTemplateRef']
const watch: typeof import('vue')['watch']
const watchEffect: typeof import('vue')['watchEffect']
const watchPostEffect: typeof import('vue')['watchPostEffect']
const watchSyncEffect: typeof import('vue')['watchSyncEffect']
}
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}

54
sub-operation-service/components.d.ts vendored Normal file
View File

@ -0,0 +1,54 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
CenterMap: typeof import('./src/components/centerMap.vue')['default']
CodeDialog: typeof import('./src/components/code-dialog/index.vue')['default']
copy: typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
CostomImg: typeof import('./src/components/costomImg.vue')['default']
CustomBack: typeof import('./src/components/customBack.vue')['default']
CustomCarouselPicture: typeof import('./src/components/custom-carousel-picture/index.vue')['default']
CustomEchartBar: typeof import('./src/components/custom-echart-bar/index.vue')['default']
CustomEchartBubble: typeof import('./src/components/custom-echart-bubble/index.vue')['default']
CustomEchartColumnLine: typeof import('./src/components/custom-echart-column-line/index.vue')['default']
CustomEchartHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/index.vue')['default']
CustomEchartLine: typeof import('./src/components/custom-echart-line/index.vue')['default']
CustomEchartLineLine: typeof import('./src/components/custom-echart-line-line/index.vue')['default']
CustomEchartMaps: typeof import('./src/components/custom-echart-maps/index.vue')['default']
CustomEchartMixin: typeof import('./src/components/custom-echart-mixin/index.vue')['default']
CustomEchartPictorialBar: typeof import('./src/components/custom-echart-pictorial-bar/index.vue')['default']
CustomEchartPie: typeof import('./src/components/custom-echart-pie/index.vue')['default']
CustomEchartPie3d: typeof import('./src/components/custom-echart-pie-3d/index.vue')['default']
CustomEchartPieGauge: typeof import('./src/components/custom-echart-pie-gauge/index.vue')['default']
CustomEchartRadar: typeof import('./src/components/custom-echart-radar/index.vue')['default']
CustomEchartScatterBlister: typeof import('./src/components/custom-echart-scatter-blister/index.vue')['default']
CustomEchartTriangle: typeof import('./src/components/custom-echart-triangle/index.vue')['default']
CustomEchartWaterDroplet: typeof import('./src/components/custom-echart-water-droplet/index.vue')['default']
CustomEchartWordCloud: typeof import('./src/components/custom-echart-word-cloud/index.vue')['default']
CustomIframe: typeof import('./src/components/custom-iframe/index.vue')['default']
CustomImportExcel: typeof import('./src/components/custom-import-excel/index.vue')['default']
CustomProgress: typeof import('./src/components/customProgress.vue')['default']
CustomRankList: typeof import('./src/components/custom-rank-list/index.vue')['default']
CustomRichEditor: typeof import('./src/components/custom-rich-editor/index.vue')['default']
CustomScrollBoard: typeof import('./src/components/custom-scroll-board/index.vue')['default']
CustomScrollTitle: typeof import('./src/components/custom-scroll-title/index.vue')['default']
'CustomScrollTitle copy': typeof import('./src/components/custom-scroll-title copy/index.vue')['default']
CustomTableOperate: typeof import('./src/components/custom-table-operate/index.vue')['default']
CustomTableTree: typeof import('./src/components/custom-table-tree/index.vue')['default']
IndexBak: typeof import('./src/components/page-menu/index-bak.vue')['default']
NewHyalineCake: typeof import('./src/components/custom-echart-hyaline-cake/new-hyaline-cake.vue')['default']
PageLayout: typeof import('./src/components/page-layout/index.vue')['default']
PageMenu: typeof import('./src/components/page-menu/index.vue')['default']
PagePagination: typeof import('./src/components/page-pagination/index.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SubTop: typeof import('./src/components/subTop.vue')['default']
UpFile: typeof import('./src/components/custom-rich-editor/upFile.js')['default']
UpImg: typeof import('./src/components/upImg.vue')['default']
}
}

View File

@ -1,5 +1,5 @@
{
"name": "sub-operation-service",
"name": "digital-agriculture-screen",
"private": true,
"version": "0.0.1",
"type": "module",
@ -7,7 +7,6 @@
"dev": "vite --mode development",
"build": "vite build --mode production",
"test": "vite build --mode test",
"pre": "vite build --mode pre",
"preview": "vite preview",
"format": "prettier --write 'src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}'",
"eslint": "npx eslint --init",
@ -18,10 +17,16 @@
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@smallwei/avue": "^3.6.2",
"@vuemap/vue-amap": "^2.0",
"@vuemap/vue-amap-loca": "^2.0",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"animate.css": "^4.1.1",
"axios": "^1.6.5",
"echarts": "^5.6.0",
"echarts-gl": "^2.0.9",
"echarts-liquidfill": "^3.1.0",
"echarts-wordcloud": "^2.1.0",
"element-plus": "^2.7.2",
"hls.js": "^1.6.2",
"js-base64": "^3.7.6",
@ -32,9 +37,11 @@
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"screenfull": "^6.0.2",
"splitpanes": "^4.0.3",
"vue": "^3.3.11",
"vue-router": "^4.2.5"
"vue-cesium": "^3.2.9",
"vue-echarts": "^7.0.3",
"vue-router": "^4.2.5",
"vue3-scroll-seamless": "^1.0.6"
},
"devDependencies": {
"@babel/core": "^7.23.7",

View File

@ -0,0 +1,19 @@
import request from '@/utils/axios';
//农资
//获取农资分类查询数据
export function transaction(params = {}) {
return request('goods/business/category/transactionType?type=1', {
method: 'GET',
params,
});
}
//获取农资列表数据
export function agriculturalList(params) {
return request('goods/business/category/transactionGoodInfo', {
method: 'GET',
params,
});
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 388 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 812 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,279 @@
<template>
<div class="map-center-warp">
<!-- <img :src="getAssetsFile('images/vsualized/gmmap.png')" class="map-img" /> -->
<div class="map-pos">
<custom-echart-maps height="100%" width="100%" :option="chartsData.option" :geo="geoData" :name="mapName" @click="mapClick" />
</div>
<el-dialog
v-model="isShow"
:title="currentMap.name + dialogTitle"
:width="dialogWidth"
:style="{ 'background-image': 'url(' + getAssetsFile(bgImageVal) + ')' }"
:show-close="false"
:before-close="handleClose"
custom-class="map-info-dialog"
>
<template #header="{ close, titleId, titleClass }">
<slot name="header"></slot>
</template>
<slot name="dialogContent"></slot>
</el-dialog>
</div>
</template>
<script setup>
import { isEmpty, getAssetsFile } from '@/utils';
import { ref, reactive, onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import geoJsonData from '../components/530926geo.json'; //
const route = useRoute();
const props = defineProps({
dialogTitle: {
type: String,
default: '首页',
},
markerData: {
type: Array,
default: () => {
return [];
},
},
bgImage: {
type: String,
default: 'images/vsualized/mapopup.png',
},
dialogWidth: {
type: Number,
default: 360,
},
});
var iconUrl = getAssetsFile('images/vsualized/gmmap2.png').href;
let bgImageVal = computed(() => {
return props.bgImage;
});
const isShow = ref(false);
let geoData = geoJsonData;
let mapName = ref('ZJ' + route.name);
let mapConfig = reactive({
map: mapName.value,
zoom: 1,
viewControl: {
distance: 115,
alpha: 60,
beta: 0,
minBeta: -360,
maxBeta: 720,
// 使
// rotateSensitivity: 0,
// zoomSensitivity: 0,
// panSensitivity: 0,
},
// itemStyle: {
// //
// color: 'rgba(75,255,180,0.2)', //
// opacity: 1, // [ default: 1 ]
// borderWidth: 1.5, // (线) [ default: 0 ]
// borderColor: '#4bffb4', // [ default: #333 ]
// },
itemStyle: {
normal: {
borderColor: '#4bffb4', //
borderWidth: 1, //
areaColor: 'rgba(75,255,180,0.2)', //
},
emphasis: {
borderColor: '#4bffb4',
borderWidth: 2,
areaColor: 'rgba(75,255,180,0.5)', //
label: {
color: '#fff',
},
},
},
select: {
// :ml-citation{ref="2,7" data="citationList"}
itemStyle: {
areaColor: 'rgba(75,255,180,0.6)',
borderColor: '#4bffb4',
borderWidth: 2,
},
label: {
color: '#fff',
},
},
label: {
show: true,
distance: 0,
color: '#fff',
padding: [6, 4, 2, 4],
borderRadius: 4,
textStyle: {
fontSize: 12,
color: '#fff', //
borderWidth: 0,
borderColor: '#000',
},
},
// emphasis: {
// //
// label: {
// show: true,
// color: '#fff',
// },
// itemStyle: {
// color: 'rgba(75,255,180,0.3)', //
// },
// },
});
const chartsData = reactive({
option: {
title: {
text: '',
left: 'center',
},
tooltip: {
trigger: 'item',
formatter: function (params) {
if (params.seriesType === 'effectScatter') {
return `${params.name}: (${params.value[0]}, ${params.value[1]})`;
}
return params.name;
},
},
toolbox: {
show: false,
orient: 'vertical',
left: 'right',
top: 'center',
feature: {
// dataView: { readOnly: false },
// restore: {},
// saveAsImage: {},
},
},
geo: {
roam: true,
left: '30px',
show: false,
zlevel: -1, //
...mapConfig,
},
series: [
{
roam: true,
type: 'map',
left: '30px',
...mapConfig,
},
{
name: '闪烁散点',
type: 'effectScatter', // 使 effectScatter
coordinateSystem: 'geo',
data: props.markerData,
symbolSize: function (val) {
return val[2] ? val[2] / 10 : 10; //
},
label: {
formatter: '{b}',
position: 'right',
show: false,
},
rippleEffect: {
period: 4, //
scale: 3, //
brushType: 'stroke', // 'stroke' 'fill'
},
hoverAnimation: true,
},
],
},
});
let currentMap = reactive({});
const mapClick = (data) => {
if (props.markerData.length && props.markerData.length > 0) {
if (data.seriesType == 'effectScatter') {
isShow.value = true;
currentMap = data;
emit('mapclick', currentMap);
}
} else {
isShow.value = true;
currentMap = data;
emit('mapclick', currentMap);
}
};
const handleClose = () => {
isShow.value = false;
};
const emit = defineEmits(['mapclick']);
</script>
<style lang="scss" scoped>
div {
box-sizing: border-box;
}
::v-deep() {
.el-dialog {
background: url(iconUrl) no-repeat left top;
background-repeat: no-repeat;
background-size: cover;
border-radius: 8px !important;
min-height: 200px;
max-height: 500px;
overflow-y: auto;
background-size: 100% 100%;
padding: 16px;
margin-top: 15%;
}
.el-dialog__header {
margin-top: 10px;
text-align: left;
padding-left: 48px;
.el-dialog__title,
i {
color: #fff !important;
}
.el-dialog__headerbtn {
top: 8px !important;
}
}
.map-dialog-my-header {
margin-top: 4px;
display: inline-flex;
justify-content: space-between;
h4 {
font-weight: normal !important;
}
}
}
.map-center-warp {
width: 100%;
text-align: center;
position: relative;
height: 90%;
.map-img {
width: 80%;
height: 80%;
position: absolute;
bottom: 0;
left: 50%;
object-fit: contain;
transform: translateX(-50%);
max-width: 1000px;
max-height: 1000px;
}
.map-pos {
width: 100%;
height: 100%;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<el-dialog
v-model="state.visible"
draggable
title="溯源码"
width="40%"
:close-on-click-modal="false"
:close-on-press-escape="false"
@close="onClose"
>
<div class="code-panel">
<div class="code-panel-picture">
<el-image style="width: 200px; height: 200px" :src="row.orCodeUrl" fit="cover" lazy />
</div>
<el-button type="primary" @click="downloadFile(row.orCodeUrl, `${row.productName}-溯源码.png`, 'image')"> 下载溯源码</el-button>
</div>
</el-dialog>
</template>
<script setup name="code-dialog">
import { reactive } from 'vue';
import { downloadFile } from '@/utils';
const props = defineProps({
row: {
type: Object,
default: () => {},
},
});
const emit = defineEmits(['on-close']);
const state = reactive({
visible: false,
});
const onClose = () => {
state.visible = false;
};
defineExpose({
show: () => {
state.visible = true;
},
hide: () => {
onClose();
},
});
</script>
<style lang="scss" scoped>
.code {
&-panel {
padding-bottom: 40px;
text-align: center;
}
}
</style>

View File

@ -79,24 +79,23 @@ const toPreview = () => {
}
.viewer-btn-warp {
position: absolute;
top: 50%;
left: 0;
display: none;
width: 100%;
text-align: center;
top: 50%;
left: 0;
transform: translateY(-50%);
}
.viewer-btn {
background: $color-balck-mask;
display: inline-block;
padding: 4px 8px;
font-size: 14px;
border-radius: 16px;
color: $color-fff;
font-size: 14px;
background: $color-balck-mask;
cursor: pointer;
}
}
.c-custom-img-warp:hover {
.viewer-btn-warp {
display: inline-block !important;

View File

@ -0,0 +1,220 @@
<template>
<div class="carousel" style="width: 500px">
<el-carousel
v-if="type === 'image'"
ref="carouselRef"
:interval="option.interval"
:arrow="option.arrow"
:indicator-position="option.indicatorPosition"
@change="handleChange"
>
<el-carousel-item v-for="(item, index) in data" :key="index">
<img :src="item.image" style="width: 100%; height: auto" />
</el-carousel-item>
</el-carousel>
<div v-if="type === 'video'" class="carousel-video">
<!-- <img :src="state.videoPicture" class="carousel-video-picture" /> -->
<video ref="videoRef" controls class="carousel-video-video" width="100%" height="100%" @ended="pauseVideo">
<source :src="state.videoUrl" type="video/mp4" />
Your browser does not support the video tag.
</video>
<!-- <span class="carousel-video-btn" @click="handlePlay">
<el-icon><VideoPlay /></el-icon>
</span> -->
</div>
<div class="carousel-container">
<span class="carousel-arrow carousel-arrow-left" @click="handleLeftClick">
<el-icon><ArrowLeftBold /></el-icon>
</span>
<el-scrollbar ref="scrollbarRef" class="carousel-list">
<div
v-for="(item, index) in data"
:key="index"
:class="`carousel-list-item ${state.current === index ? 'active' : ''}`"
@click="handleClick(index)"
>
<el-image style="width: 100px; height: 100px" :src="item.image ?? item" fit="cover" />
</div>
</el-scrollbar>
<span class="carousel-arrow carousel-arrow-right" @click="handleRightClick">
<el-icon><ArrowRightBold /></el-icon>
</span>
</div>
</div>
</template>
<script setup name="custom-carousel-picture">
import { reactive, ref } from 'vue';
const props = defineProps({
data: { type: Array, default: () => [] },
type: { type: String, default: 'image' },
option: {
type: Object,
default: () => {
return {
height: '',
initialIndex: 0,
autoplay: true,
loop: true,
interval: 3000,
arrow: 'never',
indicatorPosition: 'none',
};
},
},
});
const emit = defineEmits(['']);
const carouselRef = ref(null);
const scrollbarRef = ref(null);
const videoRef = ref(null);
const state = reactive({
current: 0,
isReady: false,
videoPicture: '',
videoUrl: '',
});
const handleChange = (cur, last) => {
state.current = cur;
};
const handleLeftClick = () => {
// const index = carouselRef.value.activeIndex;
// carouselRef.value.setActiveItem(index + 1);
scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft - 120);
};
const handleRightClick = () => {
// const index = carouselRef.value.activeIndex;
// carouselRef.value.setActiveItem(index - 1);
scrollbarRef.value.setScrollLeft(scrollbarRef.value.wrapRef.scrollLeft + 120);
};
const playVideo = () => {
if (videoRef.value) {
videoRef.value.play();
}
};
const pauseVideo = () => {
if (videoRef.value) {
videoRef.value.pause();
}
};
const handleClick = (index) => {
const { type, data } = props;
switch (type) {
case 'image': {
carouselRef.value.setActiveItem(index);
break;
}
case 'video': {
const url = data[index];
state.videoUrl = url;
playVideo();
break;
}
}
};
const handlePlay = () => {
playVideo();
};
</script>
<style lang="scss" scoped>
.carousel {
display: flex;
width: 100%;
flex-direction: column;
&-container {
display: flex;
flex-direction: row;
align-items: center;
margin-top: 30px;
}
&-list {
flex: 3;
:deep(.el-scrollbar__bar) {
display: none !important;
}
:deep(.el-scrollbar__view) {
display: flex;
flex-direction: row;
align-items: center;
}
&-item {
display: flex;
justify-content: center;
align-items: center;
margin: 10px;
width: 100px;
height: 100px;
border: 2px solid #ffffff;
border-radius: 4px;
text-align: center;
color: var(--el-color-danger);
background: var(--el-color-danger-light-9);
flex-shrink: 0;
cursor: pointer;
&.active {
border-color: var(--el-color-primary);
}
}
}
&-arrow {
width: 50px;
text-align: center;
.el-icon {
display: inline-block;
margin: 0 auto;
width: 40px;
height: 40px;
font-size: 32px;
line-height: 40px;
cursor: pointer;
}
}
&-video {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
min-height: 300px;
&-picture {
position: absolute;
z-index: 10;
}
&-video {
position: absolute;
z-index: 11;
}
&-btn {
position: absolute;
top: 50%;
left: 50%;
z-index: 12;
display: flex;
justify-content: center;
align-items: center;
margin-top: -25px;
margin-left: -25px;
width: 50px;
height: 50px;
border-radius: 25px;
background: rgb(0 0 0 / 20%);
flex-direction: row;
cursor: pointer;
.el-icon {
font-size: 32px;
color: #ffffff;
}
}
}
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
export default {
name: 'CustomEchartBar',
props: {
chartData: {
type: Array,
default: () => [],
required: true,
},
option: {
type: Object,
default: () => ({}),
},
type: {
type: String,
default: 'bar',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
isSeries: {
type: Boolean,
default: false,
},
},
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
const option = reactive({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
backgroundColor: '#333',
},
},
},
legend: {
top: 30,
},
grid: {
top: 60,
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value',
},
series: [],
});
watchEffect(() => {
props.chartData && initCharts();
});
function initCharts() {
if (props.option) {
Object.assign(option, cloneDeep(props.option));
}
let typeArr = Array.from(new Set(props.chartData.map((item) => item.type)));
let xAxisData = Array.from(new Set(props.chartData.map((item) => item.name)));
let seriesData = [];
typeArr.forEach((type, index) => {
const barStyle = props.option?.barStyle ?? {};
let obj = { name: type, type: props.type, ...barStyle };
let data = [];
xAxisData.forEach((x) => {
let dataArr = props.chartData.filter((item) => type === item.type && item.name == x);
if (dataArr && dataArr.length > 0) {
data.push(dataArr[0].value);
} else {
data.push(null);
}
});
obj['data'] = data;
if (props.option?.color) {
obj.color = props.option?.color[index];
}
seriesData.push(obj);
});
option.series = props.isSeries && option.series.length > 0 ? option.series : seriesData;
option.xAxis.data = xAxisData;
setOptions(option);
startAutoPlay({
interval: 2000,
seriesIndex: 0,
showTooltip: true,
});
getInstance()?.off('click', onClick);
getInstance()?.on('click', onClick);
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};
</script>

View File

@ -0,0 +1,103 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watch, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
export default {
name: 'customEchartBubble',
props: {
chartData: {
type: Array,
default: () => [],
},
size: {
type: Object,
default: () => {},
},
option: {
type: Object,
default: () => ({}),
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
},
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions, getInstance, resize } = useEcharts(chartRef);
const option = reactive({
tooltip: {
formatter: function (params) {
console.log(params);
var str = params.marker + '' + params.data.name + '</br>' + '交易额:' + params.data.value + '万元</br>';
return str;
},
},
animationDurationUpdate: function (idx) {
//
return idx * 100;
},
animationEasingUpdate: 'bounceIn',
color: ['#fff', '#fff', '#fff'],
series: [
{
type: 'graph',
layout: 'force',
force: {
repulsion: 80,
edgeLength: 20,
},
roam: true,
label: {
normal: {
show: true,
},
},
data: props.chartData,
},
],
});
watchEffect(() => {
props.chartData && initCharts();
});
watch(
() => props.size,
() => {
console.info(' option.series[0]', props.chartData);
resize();
},
{
immediate: true,
}
);
function initCharts() {
if (props.option) {
Object.assign(option, cloneDeep(props.option));
}
option.series[0].data = props.chartData;
setOptions(option);
resize();
getInstance()?.off('click', onClick);
getInstance()?.on('click', onClick);
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};
</script>

View File

@ -0,0 +1,147 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script>
import { ref, reactive, watchEffect, watch } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
export default {
name: 'CustomEchartLine',
props: {
chartData: {
type: Array,
default: () => [],
required: true,
},
option: {
type: Object,
default: () => ({}),
},
type: {
type: String,
default: 'line',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
},
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
const option = reactive({
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
label: {
show: true,
backgroundColor: '#333',
},
},
},
legend: {
top: 30,
},
grid: {
top: 60,
},
xAxis: {
type: 'category',
data: [],
},
yAxis: {
type: 'value',
},
series: [],
});
const xData = ref([]);
const yDataColumn = ref([]);
const yDataLine = ref([]);
watchEffect(() => {
props.chartData && initCharts();
});
watch(
() => props.chartData,
() => {
console.info('props.chartData变化', props.chartData);
props.chartData && initCharts();
},
{
deep: true,
immediate: true,
}
);
function hexToRGBA(hex, alpha = 1) {
let hexCode = hex.replace('#', '');
if (hexCode.length === 3) {
hexCode = hexCode
.split('')
.map((char) => char + char)
.join('');
}
const r = parseInt(hexCode.slice(0, 2), 16);
const g = parseInt(hexCode.slice(2, 4), 16);
const b = parseInt(hexCode.slice(4, 6), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function setAreaStyle(color) {
return {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: hexToRGBA(color, 1),
},
{
offset: 1,
color: hexToRGBA(color, 0.2),
},
],
},
};
}
function initCharts() {
xData.value = props.chartData.map((item) => item.name);
yDataColumn.value = props.chartData.map((item) => item.value1);
yDataLine.value = props.chartData.map((item) => item.value);
if (props.option) {
Object.assign(option, cloneDeep(props.option));
}
option.series[0].data = yDataColumn.value;
option.series[1].data = yDataLine.value;
option.xAxis.data = xData.value;
setOptions(option);
startAutoPlay({
interval: 2000,
seriesIndex: 0,
showTooltip: true,
});
getInstance()?.off('click', onClick);
getInstance()?.on('click', onClick);
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};
</script>

View File

@ -0,0 +1,294 @@
<template>
<div ref="chartRef" style="width: 100%; height: 170px"></div>
</template>
<script>
import { ref, reactive, watchEffect } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
export default {
name: 'CustomEchartHyalineCake',
props: {
chartData: {
type: Array,
default: () => [
{
name: '项目一',
value: 60,
},
{
name: '项目二',
value: 44,
},
{
name: '项目三',
value: 32,
},
],
},
option: {
type: Object,
default: () => ({
k: 1,
opacity: '0,6',
itemGap: 0.2,
itemHeight: 120,
autoItemHeight: 0,
legendSuffix: '',
}),
},
type: {
type: String,
default: 'bar',
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: 'calc(100vh - 78px)',
},
isSeries: {
type: Boolean,
default: false,
},
},
emits: ['click'],
setup(props, { emit }) {
const chartRef = ref(null);
const { setOptions, getInstance, startAutoPlay } = useEcharts(chartRef);
const option = ref({});
watchEffect(() => {
props.chartData && initCharts();
});
function initCharts() {
if (props.option) {
option.value = Object.assign(option, cloneDeep(props.option));
}
option.value = getPie3D(props.chartData, props.option.opacity);
setOptions(option.value);
}
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
const midRatio = (startRatio + endRatio) / 2;
const startRadian = startRatio * Math.PI * 2;
const endRadian = endRatio * Math.PI * 2;
const midRadian = midRatio * Math.PI * 2;
if (startRatio === 0 && endRatio === 1) {
isSelected = false;
}
k = typeof k !== 'undefined' ? k : 1 / 3;
const offsetX = Math.cos(midRadian) * props.option.itemGap;
const offsetY = Math.sin(midRadian) * props.option.itemGap;
const hoverRate = 1;
return {
u: {
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32,
},
v: {
min: 0,
max: Math.PI * 2,
step: Math.PI / 20,
},
x(u, v) {
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y(u, v) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z(u, v) {
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
},
};
}
// 3D
function getPie3D(pieData) {
const series = [];
//
let sumValue = 0;
let startValue = 0;
let endValue = 0;
const legendData = [];
const k = props.option.k ?? 1;
for (let i = 0; i < pieData.length; i += 1) {
sumValue += pieData[i].value;
const seriesItem = {
name: typeof pieData[i].name === 'undefined' ? `series${i}` : pieData[i].name,
type: 'surface',
parametric: true,
wireframe: {
show: false,
},
pieData: pieData[i],
itemStyle: {
// color: colors[i], //
opacity: props.option.opacity,
borderRadius: 300,
borderColor: '#fff',
borderWidth: 0,
},
pieStatus: {
selected: false,
hovered: false,
k,
},
};
if (typeof pieData[i].itemStyle !== 'undefined') {
const { itemStyle } = pieData[i];
typeof pieData[i].itemStyle.color !== 'undefined' ? (itemStyle.color = pieData[i].itemStyle.color) : null;
typeof pieData[i].itemStyle.opacity !== 'undefined' ? (itemStyle.opacity = pieData[i].itemStyle.opacity) : null;
seriesItem.itemStyle = itemStyle;
}
series.push(seriesItem);
}
for (let i = 0; i < series.length; i += 1) {
endValue = startValue + series[i].pieData.value;
series[i].pieData.startRatio = startValue / sumValue;
series[i].pieData.endRatio = endValue / sumValue;
series[i].parametricEquation = getParametricEquation(
series[i].pieData.startRatio,
series[i].pieData.endRatio,
false,
false,
k,
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * series[i].pieData.value : props.option.itemHeight
);
startValue = endValue;
legendData.push(series[i].name);
}
const option = Object.assign(
{
tooltip: {
// tooltip
className: 'custom-tooltip-container', //
backgroundColor: 'rgba(0,0,0,0.5)',
borderColor: '#35d0c0',
color: '#fff',
position: function (point, params, dom, rect, size) {
var x = point[0];
var y = point[1];
var viewWidth = size.viewSize[0];
var viewHeight = size.viewSize[1];
var boxWidth = size.contentSize[0];
var boxHeight = size.contentSize[1];
// tooltip 使
if (x + boxWidth > viewWidth) {
x = x - boxWidth;
}
if (y + boxHeight > viewHeight) {
y = y - boxHeight;
}
// tooltip
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
return [x, y];
},
formatter: (params) => {
if (params.seriesName !== 'mouseoutSeries') {
return `
<span style="color:#FFF">
${params.seriesName}<br/>
<span style="display:inline-block;
margin-right:5px;
border-radius:10px;
width:10px;
height:10px;
background-color:${params.color};"></span>
${option.series[params.seriesIndex].pieData.value}
</span>
`;
}
return '';
},
},
xAxis3D: {
min: -1,
max: 1,
},
yAxis3D: {
min: -1,
max: 1,
},
zAxis3D: {
min: -1,
max: 1,
},
grid3D: {
show: false,
boxHeight: 5,
top: '0',
left: '-20%',
viewControl: {
//3d
alpha: 60, //( )
distance: 240, //zoom()
rotateSensitivity: 10, //0
zoomSensitivity: 10, //0
panSensitivity: 10, //0
autoRotate: true, //
autoRotateAfterStill: 2, //, autoRotate
},
},
legend: {
show: true,
right: '25%',
top: '25%',
orient: 'vertical',
icon: 'circle',
itemHeight: 12,
itemWidth: 12,
itemGap: 10,
textStyle: {
color: '#fff',
fontSize: 14,
fontWeight: '400',
},
formatter: (name) => {
if (props.chartData.length) {
const item = props.chartData.filter((item) => item.name === name)[0];
return `${name}`;
}
},
},
series,
},
props.option
);
return option;
}
function onClick(params) {
emit('click', params);
}
return { chartRef };
},
};
</script>

View File

@ -0,0 +1,492 @@
<template>
<div ref="chartRef" :style="{ width: width, height: height }"></div>
</template>
<script setup>
import { ref, watch, onMounted, nextTick } from 'vue';
import { cloneDeep } from 'lodash';
import { useEcharts } from '@/hooks/useEcharts';
defineOptions({ name: 'NewHyalineCake' });
// props
const props = defineProps({
chartData: {
type: Array,
default: () => [
//
{ name: '项目一', value: 60 },
{ name: '项目二', value: 44 },
{ name: '项目三', value: 32 },
],
},
option: {
type: Object,
default: () => ({
// 1
k: 1,
//
itemGap: 0.2,
// z
itemHeight: 120,
// 使>0 itemHeight 使 autoItemHeight * value
autoItemHeight: 0,
//
opacity: 0.6,
//
legendSuffix: '',
}),
},
width: {
type: String,
default: '100%',
},
height: {
type: String,
default: '100%',
},
});
//
const emit = defineEmits(['click']);
// DOM
const chartRef = ref(null);
// 使 useEcharts ECharts
const { setOptions, getInstance } = useEcharts(chartRef);
// ECharts
const chartOption = ref({});
// series-surface.parametricEquation
function getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h) {
//
const midRatio = (startRatio + endRatio) / 2;
const startRadian = startRatio * Math.PI * 2;
const endRadian = endRatio * Math.PI * 2;
const midRadian = midRatio * Math.PI * 2;
//
if (startRatio === 0 && endRatio === 1) {
isSelected = false;
}
// k 1/3 k 使
k = typeof k !== 'undefined' ? k : 1 / 3;
//
const offsetX = isSelected ? Math.cos(midRadian) * props.option.itemGap : 0;
const offsetY = isSelected ? Math.sin(midRadian) * props.option.itemGap : 0;
// 1
const hoverRate = isHovered ? 1.05 : 1;
// parametric
return {
u: {
// u -π 3π
min: -Math.PI,
max: Math.PI * 3,
step: Math.PI / 32,
},
v: {
// v 0 - 2π
min: 0,
max: Math.PI * 2,
step: Math.PI / 20,
},
x(u, v) {
//
if (u < startRadian) {
return offsetX + Math.cos(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
//
if (u > endRadian) {
return offsetX + Math.cos(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
//
return offsetX + Math.cos(u) * (1 + Math.cos(v) * k) * hoverRate;
},
y(u, v) {
if (u < startRadian) {
return offsetY + Math.sin(startRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
if (u > endRadian) {
return offsetY + Math.sin(endRadian) * (1 + Math.cos(v) * k) * hoverRate;
}
return offsetY + Math.sin(u) * (1 + Math.cos(v) * k) * hoverRate;
},
z(u, v) {
// u < -π/2 使
if (u < -Math.PI * 0.5) {
return Math.sin(u);
}
// u > 2.5π
if (u > Math.PI * 2.5) {
return Math.sin(u) * h * 0.1;
}
// z v
// Zhvalue
return Math.sin(v) > 0 ? 1 * h * 0.1 : -1;
},
};
}
// 3D
function getPie3D(pieData) {
const series = [];
let sumValue = 0;
//
pieData.forEach((item) => {
sumValue += item.value;
});
// k
const k = props.option.k ?? 1;
// series
pieData.forEach((dataItem, idx) => {
const seriesItem = {
name: dataItem.name ?? `series${idx}`,
type: 'surface',
parametric: true,
wireframe: { show: false },
pieData: dataItem,
itemStyle: {
opacity: props.option.opacity,
borderRadius: 300,
borderColor: '#fff',
borderWidth: 0,
},
pieStatus: {
selected: false,
hovered: false,
k,
},
};
//
if (dataItem.itemStyle) {
const customStyle = {};
if (dataItem.itemStyle.color !== undefined) {
customStyle.color = dataItem.itemStyle.color;
}
if (dataItem.itemStyle.opacity !== undefined) {
customStyle.opacity = dataItem.itemStyle.opacity;
}
seriesItem.itemStyle = { ...seriesItem.itemStyle, ...customStyle };
}
series.push(seriesItem);
});
// startRatio/endRatio
let startValue = 0;
series.forEach((serie) => {
const endValue = startValue + serie.pieData.value;
const startRatio = startValue / sumValue;
const endRatio = endValue / sumValue;
serie.pieData.startRatio = startRatio;
serie.pieData.endRatio = endRatio;
//
serie.parametricEquation = getParametricEquation(
startRatio,
endRatio,
false,
true,
k,
// 使
props.option.autoItemHeight > 0 ? props.option.autoItemHeight * serie.pieData.value : props.option.itemHeight
);
startValue = endValue;
});
// hover
series.push({
name: 'mouseoutSeries',
type: 'surface',
parametric: true,
wireframe: { show: false },
itemStyle: { opacity: 0 },
parametricEquation: {
u: { min: 0, max: Math.PI * 2, step: Math.PI / 20 },
v: { min: 0, max: Math.PI, step: Math.PI / 20 },
x(u, v) {
return Math.sin(v) * Math.sin(u) + Math.sin(u);
},
y(u, v) {
return Math.sin(v) * Math.cos(u) + Math.cos(u);
},
z(u, v) {
return Math.cos(v) > 0 ? 0.1 : -0.1;
},
},
});
//
const option = Object.assign(
{
tooltip: {
backgroundColor: 'rgba(18, 55, 85, 0.8)',
borderColor: '#35d0c0',
color: '#fff',
position: (point, params, dom, rect, size) => {
// tooltip
let x = point[0],
y = point[1];
const [viewW, viewH] = size.viewSize;
const [boxW, boxH] = size.contentSize;
if (x + boxW > viewW) x -= boxW;
if (y + boxH > viewH) y -= boxH;
if (x < 0) x = 0;
if (y < 0) y = 0;
return [x, y];
},
formatter: (params) => {
//
if (params.seriesName !== 'mouseoutSeries') {
return `
<span style="color:#FFF">
${params.seriesName}<br/>
<span style="
display:inline-block;
margin-right:5px;
border-radius:10px;
width:10px;
height:10px;
background-color:${params.color};"></span>
${chartOption.value.series[params.seriesIndex].pieData.value}
</span>`;
}
return '';
},
},
xAxis3D: { min: -1, max: 1 },
yAxis3D: { min: -1, max: 1 },
zAxis3D: { min: -1, max: 1 },
grid3D: {
show: false,
boxHeight: 5,
top: '0',
left: '-20%',
viewControl: {
// 3D
alpha: 60, //
distance: 240, //
rotateSensitivity: 10,
zoomSensitivity: 10,
panSensitivity: 10,
autoRotate: true,
autoRotateAfterStill: 2,
},
},
legend: {
show: true,
selectedMode: false,
right: '5%',
top: '25%',
orient: 'vertical',
icon: 'circle',
itemHeight: 12,
itemWidth: 12,
itemGap: 10,
textStyle: {
color: '#fff',
fontSize: 14,
fontWeight: '400',
},
//
formatter: (name) => {
const item = props.chartData.find((d) => d.name === name);
return item ? ` ${name} ${item.value}${props.option.legendSuffix || ''}` : name;
},
},
series,
},
//
props.option
);
return option;
}
//
function initChart() {
// 3D
const baseOption = getPie3D(props.chartData);
//
const finalOption = Object.assign({}, baseOption, cloneDeep(props.option || {}));
chartOption.value = finalOption;
//
setOptions(chartOption.value);
// DOM + ECharts
nextTick(() => {
const chart = getInstance();
if (!chart) {
console.warn('ECharts 实例未初始化,事件绑定失败');
return;
}
//
chart.off('click');
chart.off('mouseover');
chart.off('globalout');
chart.on('click', handleClick);
chart.on('mouseover', handleMouseover);
chart.on('globalout', handleGlobalout);
});
}
function handleClick(params) {
//
if (params.seriesName === 'mouseoutSeries') return;
const optionVal = chartOption.value;
const series = optionVal.series;
const idx = params.seriesIndex;
//
const isSelected = !series[idx].pieStatus.selected;
const isHovered = series[idx].pieStatus.hovered;
const k = series[idx].pieStatus.k;
const startRatio = series[idx].pieData.startRatio;
const endRatio = series[idx].pieData.endRatio;
const h = series[idx].pieData.value;
//
if (selectedIndex !== null && selectedIndex !== idx) {
const prev = series[selectedIndex];
prev.parametricEquation = getParametricEquation(
prev.pieData.startRatio,
prev.pieData.endRatio,
false,
false,
prev.pieStatus.k,
prev.pieData.value
);
prev.pieStatus.selected = false;
selectedIndex = null;
}
// /
series[idx].parametricEquation = getParametricEquation(startRatio, endRatio, isSelected, isHovered, k, h);
series[idx].pieStatus.selected = isSelected;
//
selectedIndex = isSelected ? idx : null;
//
setOptions(optionVal);
// click 使
emit('click', params);
}
//
window.debugZValues = {
current: null,
history: [],
};
function handleMouseover(params) {
if (params.seriesName === 'mouseoutSeries') return;
const chart = getInstance();
const optionVal = chart.getOption(); //
const series = optionVal.series;
const idx = params.seriesIndex;
const hh = series[idx].parametricEquation.z();
window.debugZValues.current = hh;
window.debugZValues.history.push({
event: 'mouseover',
series: series[idx].name,
zValue: hh,
time: new Date().toISOString(),
});
console.log('当前Z值:', hh, '历史记录:', window.debugZValues.history);
//
console.log(
'[移入] 当前所有扇形状态',
series.map((s) => ({
name: s.name,
hovered: s.pieStatus?.hovered,
selected: s.pieStatus?.selected,
height: s.parametricEquation?.z(Math.PI, Math.PI),
}))
);
//
if (hoveredIndex !== null && hoveredIndex !== idx) {
const prev = series[hoveredIndex];
prev.pieStatus.hovered = false; //
prev.parametricEquation = getParametricEquation(
//
prev.pieData.startRatio,
prev.pieData.endRatio,
prev.pieStatus.selected,
false, // isHovered=false
prev.pieStatus.k,
prev.pieData.value
);
}
//
const current = series[idx];
current.pieStatus.hovered = true;
current.parametricEquation = getParametricEquation(
current.pieData.startRatio,
current.pieData.endRatio,
current.pieStatus.selected,
true, // isHovered=true
current.pieStatus.k,
current.pieData.value
);
hoveredIndex = idx;
chart.setOption({ series }); // series
}
function handleGlobalout() {
if (hoveredIndex !== null) {
const chart = getInstance();
const optionVal = chart.getOption();
const series = optionVal.series;
const prev = series[hoveredIndex];
//
console.warn('[修复前] 异常状态', {
name: prev.name,
z: prev.parametricEquation.z(Math.PI, Math.PI),
equation: prev.parametricEquation,
});
//
prev.pieStatus.hovered = false;
prev.parametricEquation = getParametricEquation(
prev.pieData.startRatio,
prev.pieData.endRatio,
prev.pieStatus.selected,
false, // isHovered=false
prev.pieStatus.k,
prev.pieData.value
);
hoveredIndex = null;
chart.setOption({ series }, { replaceMerge: 'series' }); // series
}
}
//
let selectedIndex = null;
let hoveredIndex = null;
//
onMounted(() => {
initChart();
});
//
watch(
[() => props.chartData, () => props.option],
() => {
if (props.chartData && props.chartData.length) {
initChart();
}
},
{ immediate: true, deep: true }
);
</script>
<style scoped>
/* 可根据需要自定义图表容器样式 */
</style>

Some files were not shown because too many files have changed in this diff Show More