<script lang="jsx">
import {
shallowRef,
computed,
ref,
toRefs,
toRaw,
watch,
onMounted,
onUnmounted,
nextTick
} from "vue";
import * as echarts from 'echarts';
export default {
props: {
// 四象限中心点
center:{
type: Array,
default:() => [50, 50],
validator: (value) => {
return value.length === 2;
},
},
// 四个象限区域颜色:透明度为1时的颜色
color: {
type: Array,
default:() => ['#339933', '#FF9900', '#FF3333', '#007FFF'],
validator: (value) => {
return value.length === 4;
},
},
// 四象限区域categories:注意顺序,第一,第二,第三,第四象限
categories:{
type: Array,
default:() => ['优秀维持区', '次待提升区', '亟待提升区', '优秀次维持区'],
validator: (value) => {
return value.length === 4;
},
},
option:{
type: Object,
required: true,
default:()=>{
return {
xAxis:[{
name: '修复成功率',
}],
yAxis:[{
name: '修复提升率',
}],
series: [
{
data: [{
name: '克孜勒苏柯尔克孜自治州',
value: [8.84, 17.3]
}]
}
]
}
}
},
// 自定义tooltip层formatter回调
tooltipFormatter:{
type: Function,
default: (params, ticket, callback)=>{
console.error(params)
return `<h6><i style="background:${params.data.color}"></i><span>${params.data.name}</span></h6>`
}
}
},
emits: ['dataZoom'],
setup(props, { attrs, emit, slots, expose }) {
const { center, color, categories, option, tooltipFormatter } = toRefs(props)
const chartRef= shallowRef(null);
const chartInstance= shallowRef(null);
const getOption = computed(()=>{
return {
tooltip: {
textStyle: {
color: "#666f8e",
fontSize: 12
},
padding: 0,
backgroundColor: "transparent",
extraCssText: "box-shadow: none; z-index: 101; border-width: 0",
confine: true,
formatter:(params, ticket, callback) =>`<div class="custom-chart-tooltip">${tooltipFormatter.value(params, ticket, callback)}</div>`
},
grid: {
padding: "0",
margin: "0",
left: "0",
right: "0",
bottom: "0",
top: "0",
},
dataZoom: [{
id: "dataZoomX",
type: "inside",
xAxisIndex: [0],
minSpan: 1,
filterMode: "empty"
}, {
id: "dataZoomY",
type: "inside",
yAxisIndex: [0],
minSpan: 1,
filterMode: "empty"
}],
xAxis: getXAxis.value,
yAxis: getYAxis.value,
color: color.value,
series: [{
labelLine: {
label: {
show: false
}
},
name: `${option.value?.xAxis?.[0]?.name || ''}-${option.value?.yAxis?.[0]?.name || ''}`,
type: "scatter",
data: getSeriesData.value,
label: {
show: true,
position: "top",
overflow: "truncate",
width: 60,
fontSize: 14,
fontFamily: 'Source Han Sans CN, Source Han Sans CN-Regular',
color: '#4D4D4D',
formatter: (params) => {
return params.data?.name || '';
},
},
symbolSize: 12,
color: color.value,
markArea: {
silent: true,
data: getMarkAreaData.value
}
}]
};
});
const getSeriesData = computed(()=>{
return getData.value.map(item=>{
const color = item?.color || getItemColor(item.value);
return {
...item,
categories: categories.value[getItemIndex(item.value)],
color,
itemStyle:{
borderColor: color,
borderWidth: 1,
color: getHexToRgba(color, 0.5)
},
emphasis: {
itemStyle: {
color: color,
borderColor: getHexToRgba(color, 0.4),
borderWidth: 4,
},
},
}
})
});
const getTableList = computed(()=>{
const sortCategoriesIndex = [2, 1, 3, 0];
const newCategories = sortCategoriesIndex.map(index=>({index, categories:categories.value[index]}));
return newCategories.map(({categories, index})=>{
const list = getSeriesData.value.filter(child=> child.categories === categories)
return list.length ? list : [{ categories, value: [], color: getItemColor(index) }]
})
})
const getData = computed(() => {
const series = Array.isArray(option.value.series) ? option.value.series[0] : option.value.series;
return series.data;
});
const getXYData = computed(() => {
return (index) => getData.value.reduce((prev, current)=>prev.concat(current.value[index]), []);
});
const getMaxAndMin = computed(() => {
const { axisMin: xAxisMin, axisMax: xAxisMax } = getListMinMax(getXYData.value(0), center.value[0]);
const { axisMin: yAxisMin, axisMax: yAxisMax } = getListMinMax(getXYData.value(1), center.value[1]);
return {
xAxisMax,
yAxisMax,
xAxisMin,
yAxisMin,
};
});
const getXAxis = computed(()=>({
...(option.value?.xAxis?.[0] || {}),
type: 'value',
show: false,
max: getMaxAndMin.value.xAxisMax,
min: getMaxAndMin.value.xAxisMin,
}));
const getYAxis = computed(()=>({
...(option.value?.yAxis?.[0] || {}),
type: 'value',
show: false,
max: getMaxAndMin.value.yAxisMax,
min: getMaxAndMin.value.yAxisMin,
}));
const getMarkAreaData = computed(()=>([
[{
xAxis: center.value[0],
yAxis: getMaxAndMin.value.yAxisMax,
itemStyle: {
color: getItemColor(0, 0.04)
}
}, {
yAxis: center.value[1]
}],
[{
yAxis: getMaxAndMin.value.yAxisMax,
itemStyle: {
color: getItemColor(1, 0.04)
}
}, {
xAxis: center.value[0],
yAxis: center.value[1]
}],
[{
yAxis: center.value[1],
itemStyle: {
color: getItemColor(2, 0.04)
}
}, {
xAxis: center.value[0],
yAxis: getMaxAndMin.value.yAxisMin
}],
[{
xAxis: center.value[0],
yAxis: center.value[1],
itemStyle: {
color: getItemColor(3, 0.04)
}
}, {
yAxis: getMaxAndMin.value.yAxisMin
}]
]));
const getListMinMax = (list, halfValue) => {
const minValue = Math.min(...list);
const maxValue = Math.max(...list);
if (minValue === halfValue && maxValue === halfValue) {
return getMaxToHalfValue(halfValue, halfValue*1.25, 2)
} else if (minValue >= halfValue) {
return getMaxToHalfValue(halfValue, maxValue);
} else if (maxValue <= halfValue) {
return getHalfToMinValue(halfValue, minValue);
} else {
const flag = maxValue - halfValue > halfValue - minValue;
if (flag) {
return getMaxToHalfValue(halfValue, maxValue);
} else {
return getHalfToMinValue(halfValue, minValue);
}
}
};
const getMaxToHalfValue = (halfValue, maxValue, pointNum = 0) => {
const powNum = Math.pow(10,pointNum)
const axisMax = Math.floor(powNum * ((maxValue + (maxValue - halfValue) * 0.25))) / powNum;
const axisMin = Math.floor(powNum * (halfValue - (axisMax - halfValue)) / powNum;
return {
axisMin,
axisMax,
};
};
const getHalfToMinValue = (halfValue, minValue) => {
const axisMin = Math.floor(minValue - (halfValue - minValue) * 0.25);
const axisMax = halfValue + (halfValue - axisMin);
return {
axisMin,
axisMax,
};
};
const getItemColor = (value, opacity) =>{
const index = typeof value === 'number' ? value : getItemIndex(value);
return getHexToRgba(color.value[index], opacity);
}
const getItemIndex = (value) =>{
const [xCenter, yCenter] = center.value;
const [x, y] = value;
if (x >= xCenter && y >= yCenter) {
return 0;
} else if (x <= xCenter && y >= yCenter) {
return 1;
} else if (x < xCenter && y < yCenter) {
return 2;
} else if (x >= xCenter && y <= yCenter) {
return 3;
} else {
}
return 0;
}
const getHexToRgba = (hex, opacity = 1) => {
const newHex = hex.match(/^#[0-9a-f]{3}$/gi) ? hex.replace(/^(#)([0-9a-f])([0-9a-f])([0-9a-f])$/gi,'$1$2$2$3$3$4$4') : hex;
if((!opacity && opacity !== 0) || opacity === 1) {
return newHex;
}
return `rgba(${[1, 3, 5].map((h) => parseInt(newHex.substring(h, h + 2), 16)).join(',')},${opacity})`;
};
const updateChart = async () => {
await nextTick();
if (!chartRef.value) {
return;
}
if (chartInstance.value) {
chartInstance.value.clear();
} else {
chartInstance.value = echarts.init(chartRef.value);
chartInstance.value.on('dataZoom', handleDataZoom);
}
chartInstance.value.showLoading({
text: '',
textColor: '#f90',
fontSize: 14,
color: '#f90',
maskColor: 'rgba(255, 255, 255, .5)',
zlevel: 0,
spinnerRadius: 12,
lineWidth: 2,
});
try {
chartInstance.value.setOption(getOption.value, true);
handleResize();
} catch (error) {
}
chartInstance.value.hideLoading();
};
const handleResize = () => {
if (chartInstance.value) {
chartInstance.value.resize();
}
};
const handleDataZoom = (e) => {
const option = chartInstance.value.getOption();
const { xAxis, dataZoom} = option;
const num = dataZoom[0].endValue - dataZoom[0].startValue;
const percentage = (xAxis[0].max - xAxis[0].min) / num;
emit('dataZoom', Math.ceil(percentage * 100));
};
onMounted(() => {
window.addEventListener("resize", handleResize);
});
onUnmounted(() => {
if (chartInstance.value) {
chartInstance.value.off('dataZoom', handleDataZoom);
chartInstance.value.dispose();
chartInstance.value = null;
}
window.removeEventListener("resize", handleResize);
});
watch(
() => option.value,
(val) => {
updateChart();
},
{ deep: true, immediate: true }
);
return () => (
<div class="base-scatter-chart">
<div class="base-scatter-el" ref={chartRef}></div>
{!!slots?.default && slots.default()}
</div>
);
},
};
</script>
<style lang="less" >
.base-scatter-chart{
width: 100%;
height: 100%;
position: relative;
.base-scatter-el{
width: 100%;
height: 100%;
position: relative;
}
.custom-chart-tooltip{
background: #ffffff;
padding: 16px 12px 12px;
border-radius: 8px;
font-size: 14px;
box-shadow: 0px 2px 8px 0px rgba(0, 0, 0, 0.15);
> h6:first-child{
display: flex;
font-size: 14px;
align-items: center;
font-weight: normal;
i{
width: 8px;
height: 8px;
border-radius: 50%;
}
span{
text-indent: 8px;
}
}
}
}
</style>
// app.vue
/*
<template>
<component :is="mode" ></component>
</template>
<script setup>
import { ref, toRefs, markRaw, unref, watch } from 'vue';
defineOptions({
inheritAttrs: false,
});
const props = defineProps({
url:{
type: String,
default: '/CommonComponentFront/satisfaction_remote_component_table_selection_transfer.0.0.1.umd.js?name=__SATISFACTION_REMOTE_COMPONENT_TABLE_SELECTION_TRANSFER'
}
});
const { url } = toRefs(props);
const mode = ref(null);
// 特殊字符转义
const strReplaceXss = (str) => {
const specailToStr = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
',': ',',
';': ';',
'(': '(',
')': ')',
};
return (str || '').replace(/[&<>"',;()]/g, (val) => specailToStr[val]);
};
// 加载远程js
const loadScript = (url) => {
const str = unref(url);
// url编码后参数name提取
const params = new URLSearchParams(`?${encodeURI(strReplaceXss(str).split('.js?')[1] || '')}`);
const name = params.get('name');
if (!name) {
return;
}
if (window[name]) {
mode.value = markRaw(window[name]);
} else {
fetch(str, {
method: 'GET',
mode: 'cors',
}).then((res) => {
if (res.status === 200) {
res.text().then((code) => {
const script = document.createElement('script');
script.text = code;
document.body.appendChild(script);
mode.value = markRaw(window[name]);
});
}
});
}
return;
};
watch(
() => url,
(val) => {
loadScript(val);
},
{ deep: true, immediate: true }
);
</script>
<style>
*{
padding: 0;
margin: 0;
list-style: none;
}
#app,html,body{
width: 100%;
height: 100%;
}
</style>
*/
// index.js 全量导入
const files = import.meta.globEager('./*.vue');
const modules = {}
for (const key in files) {
if (Object.prototype.hasOwnProperty.call(files, key)) {
modules[files[key].default.type] = files[key].default
}
}
export default modules
console