payment-statistics/src/views/components/chartsComponents/OperatingTrendsChart.vue

713 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div ref="chartDom" class="w-full h-full"></div>
</template>
<script setup lang="ts">
import { onMounted, ref, watch, computed } from "vue";
import * as echarts from "echarts";
const chartDom = ref(null);
let myChart: echarts.ECharts | null = null;
const props = defineProps({
chartDataArray: {
type: Array,
default: () => []
},
});
const emits = defineEmits(["dateChange"]);
// 定义数据类型接口
interface ChartDataItem {
date: string;
value: number;
}
interface ChartTypeData {
type: number;
items: ChartDataItem[];
}
interface SeriesDataItem {
name: string;
data: number[];
color: string;
}
interface ChartDataFormat {
dates: string[];
seriesData: SeriesDataItem[];
}
// 处理图表数据
const processChartData = (dataArray: ChartTypeData[], mode: string): ChartDataFormat => {
if (!dataArray || dataArray.length === 0) return {
dates: [],
seriesData: [
{ name: "获客", data: [], color: "#20E6A4" },
{ name: "缴费", data: [], color: "#0783FA" },
{ name: "流失", data: [], color: "#FFD15C" },
],
};
// 收集所有日期并排序
let allDates = new Set<string>();
dataArray.forEach((item: ChartTypeData) => {
if (item.items && item.items.length > 0) {
item.items.forEach((dateItem: ChartDataItem) => {
if (dateItem.date) {
allDates.add(dateItem.date);
}
});
}
});
// 将日期转换为数组并排序
let sortedDates = Array.from(allDates).sort((a: string, b: string) => {
// 假设日期格式为 MM-DD
const [monthA, dayA] = a.split('-').map(Number);
const [monthB, dayB] = b.split('-').map(Number);
if (monthA !== monthB) return monthA - monthB;
return dayA - dayB;
});
// 根据模式选择显示的数据量
if (mode === "week") {
// 周模式显示最近7天
if (sortedDates.length > 7) {
sortedDates = sortedDates.slice(-7);
}
}
// 月模式显示最近30天或全部
else if (mode === "month") {
// 如果数据超过30天显示最近30天
if (sortedDates.length > 30) {
sortedDates = sortedDates.slice(-30);
}
// 否则显示全部数据
}
// 格式化日期显示
const formattedDates = sortedDates.map((date: string) => {
const [month, day] = date.split('-');
return `${month}.${day}`;
});
// 创建每个系列的数据
const seriesData: SeriesDataItem[] = [
{ name: "获客", data: [], color: "#20E6A4" },
{ name: "缴费", data: [], color: "#0783FA" },
{ name: "流失", data: [], color: "#FFD15C" },
];
// 填充数据值
sortedDates.forEach((date: string) => {
// 对每个系列填充当前日期的值
dataArray.forEach((typeData: ChartTypeData, typeIndex: number) => {
if (typeIndex < seriesData.length) {
const dateItem = typeData.items.find(item => item.date === date);
seriesData[typeIndex].data.push(dateItem ? dateItem.value : 0);
}
});
});
return {
dates: formattedDates,
seriesData,
};
};
// 使用computed属性基于props生成图表数据
const chartData = computed((): ChartDataFormat => {
if (props.chartDataArray && props.chartDataArray.length > 0) {
return processChartData(props.chartDataArray as ChartTypeData[], currentMode.value);
}
// 如果没有提供数据,则使用默认数据
return {
dates: ["5.24", "5.25", "5.26", "5.27", "5.28", "5.29", "5.30"],
seriesData: [
{ name: "获客", data: [180, 230, 200, 280, 250, 300, 350], color: "#20E6A4" },
{ name: "缴费", data: [150, 210, 180, 240, 200, 260, 320], color: "#0783FA" },
{ name: "流失", data: [120, 380, 220, 350, 300, 280, 400], color: "#FFD15C" },
],
};
});
const currentMode = ref("week");
const legendIcons = ref<{ [key: string]: string }>({});
// 生成SVG图标
const generateSvgIcon = (color: string) => {
return encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" viewBox="0 0 16 16">
<g><g><path d="M0,2L0,14C0,15.1046,0.89543,16,2,16L14,16C15.1046,16,16,15.1046,16,14L16,2C16,0.89543,15.1046,0,14,0L2,0C0.89543,0,0,0.89543,0,2ZM13.949,6.32574L7.64171,11.99Q7.35295,12.25,6.94914,12.25Q6.54551,12.25,6.25625,11.9902L2.05119,8.2139Q1.75,7.94411,1.75,7.55595Q1.75,7.16798,2.05072,6.89792Q2.33947,6.63794,2.74329,6.63794Q3.14692,6.63794,3.43617,6.89771L6.94886,10.0523L12.5636,5.00998Q12.8523,4.75,13.2562,4.75Q13.6598,4.75,13.9488,5.00955Q14.25,5.27935,14.25,5.66751Q14.25,6.05547,13.949,6.32574Z" fill-rule="evenodd" fill="${color}" fill-opacity="1"/></g></g></svg>`);
};
// 初始化图例图标
const initLegendIcons = () => {
chartData.value.seriesData.forEach((item) => {
legendIcons.value[item.name] = generateSvgIcon(item.color);
});
};
// 获取图例图标
const getLegendIcon = (name: string, isSelected = true) => {
const seriesItem = chartData.value.seriesData.find((item) => item.name === name);
const color = isSelected ? seriesItem?.color || "#555" : "#555";
return `image://data:image/svg+xml;charset=utf-8,${generateSvgIcon(color)}`;
};
// 更新图例图标颜色
const updateLegendIcons = (selected: Record<string, boolean>) => {
if (!myChart) return;
const option = myChart.getOption() as any;
const legendData = option.legend[0].data as any[];
// 更新每个图例项的图标
const updatedLegendData = legendData.map((item: any) => {
const isSelected = selected[item.name] !== false;
return {
...item,
icon: getLegendIcon(item.name, isSelected),
};
});
// 更新图表配置
myChart.setOption({
legend: {
data: updatedLegendData,
},
});
};
// 生成图表系列的函数
const generateSeries = (seriesData: any) => {
return seriesData.map((item: any) => {
const series = {
name: item.name,
type: "line",
data: item.data,
symbol: "circle",
symbolSize: 8,
itemStyle: {
color: "#111", // 中心填充颜色
borderColor: item.color, // 边框颜色
borderWidth: 2,
},
lineStyle: {
width: 3,
color: item.color,
},
};
return series;
});
};
// 切换周/月显示
const switchDisplayMode = (mode: string) => {
currentMode.value = mode;
emits("dateChange", mode);
// 重新渲染图表
initChart();
};
const initChart = () => {
if (!myChart) {
myChart = echarts.init(chartDom.value);
}
// 初始化图例图标
initLegendIcons();
// 按钮的SVG背景 - 使用Base64编码
const buttonSvgBase64 = encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><filter id="master_svg0_104_04023" filterUnits="objectBoundingBox" color-interpolation-filters="sRGB" x="0" y="0" width="1" height="1"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feColorMatrix in="SourceAlpha" type="matrix" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="8" dx="0"/><feGaussianBlur stdDeviation="6"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.08237092196941376 0 0 0 0 0.17847032845020294 0 0 0 1 0"/><feBlend mode="normal" in2="shape" result="effect1_innerShadow"/><feColorMatrix in="SourceAlpha" type="matrix" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="-8" dx="0"/><feGaussianBlur stdDeviation="6"/><feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/><feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0.08235294371843338 0 0 0 0 0.18039216101169586 0 0 0 1 0"/><feBlend mode="normal" in2="effect1_innerShadow" result="effect2_innerShadow"/></filter></defs><g><g filter="url(#master_svg0_104_04023)"><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="#061E3A" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="#2A8EFE" fill-opacity="1"/></g></g></svg>
`);
// 定义选中状态的SVG背景
const activeButtonSvgBase64 = encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" width="72" height="28" viewBox="0 0 72 28"><defs><radialGradient cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" id="master_svg0_85_00187" gradientTransform="translate(36 28) rotate(90) scale(16.125258922576904 182.11454980862436)"><stop offset="0%" stop-color="#FFCE4F" stop-opacity="0.5749016404151917"/><stop offset="99.90439414978027%" stop-color="#E0BF00" stop-opacity="0"/></radialGradient><linearGradient x1="0.5" y1="0" x2="0.5" y2="1" id="master_svg1_85_00188"><stop offset="0%" stop-color="#E0BF00" stop-opacity="0.4000000059604645"/><stop offset="100%" stop-color="#E0BF00" stop-opacity="0"/></linearGradient><linearGradient x1="0.5" y1="1" x2="0.5" y2="1.9133386611938477" id="master_svg2_85_00190"><stop offset="0%" stop-color="#FFF7A6" stop-opacity="1"/><stop offset="100%" stop-color="#046172" stop-opacity="0"/></linearGradient></defs><g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg0_85_00187)" fill-opacity="1"/><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113Z" fill="url(#master_svg1_85_00188)" fill-opacity="1"/></g><g><path d="M0,14.5113C0,8.31958,0,6.07937,0.871948,4.36808C1.63894,2.86278,2.86278,1.63893,4.36808,0.871948C6.07937,0,8.31958,0,14.5113,0L72,0L72,13.4887C72,19.6804,72,21.9206,71.1281,23.6319C70.3611,25.1372,69.1372,26.3611,67.6319,27.1281C65.9206,28,63.6804,28,57.4887,28L0,28L0,14.5113ZM14.5113,1L71,1L71,13.4887Q71,18.6574,70.8935,20.1702Q70.753,22.1653,70.237,23.1779Q69.2045,25.2045,67.1779,26.237Q66.1653,26.753,64.1702,26.8935Q62.6574,27,57.4887,27L1,27L1,14.5113Q1,9.34262,1.10652,7.82977Q1.247,5.83468,1.76295,4.82207Q2.79553,2.79553,4.82207,1.76295Q5.83468,1.247,7.82977,1.10652Q9.34262,1,14.5113,1Z" fill-rule="evenodd" fill="url(#master_svg2_85_00190)" fill-opacity="1"/></g></g></svg>
`);
const option = {
grid: {
top: "14%",
left: "5%",
right: "3%",
bottom: "15%",
},
legend: {
type: "plain",
itemWidth: 16,
itemHeight: 16,
top: 3,
left: "center",
itemGap: 40,
textStyle: {
color: "#C0EEFF",
fontSize: 14,
lineHeight: 16,
},
data: chartData.value.seriesData.map((item) => ({
name: item.name,
icon:
"image://data:image/svg+xml;charset=utf-8," +
encodeURIComponent(`
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" fill="none" version="1.1" viewBox="0 0 16 16">
<g><g><path d="M0,2L0,14C0,15.1046,0.89543,16,2,16L14,16C15.1046,16,16,15.1046,16,14L16,2C16,0.89543,15.1046,0,14,0L2,0C0.89543,0,0,0.89543,0,2ZM13.949,6.32574L7.64171,11.99Q7.35295,12.25,6.94914,12.25Q6.54551,12.25,6.25625,11.9902L2.05119,8.2139Q1.75,7.94411,1.75,7.55595Q1.75,7.16798,2.05072,6.89792Q2.33947,6.63794,2.74329,6.63794Q3.14692,6.63794,3.43617,6.89771L6.94886,10.0523L12.5636,5.00998Q12.8523,4.75,13.2562,4.75Q13.6598,4.75,13.9488,5.00955Q14.25,5.27935,14.25,5.66751Q14.25,6.05547,13.949,6.32574Z" fill-rule="evenodd" fill="${item.color}" fill-opacity="1"/></g></g></svg>`),
textStyle: {
color: "#C0EEFF",
verticalAlign: "middle",
},
})),
selectedMode: true,
inactiveColor: "#555",
itemStyle: {
borderWidth: 0,
},
},
// 使用graphic组件添加自定义按钮
graphic: [
// 按周按钮
{
type: "group",
right: 97,
top: 0,
// z: 100,
children: [
{
type: "image",
// z: 100,
style: {
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "week" ? activeButtonSvgBase64 : buttonSvgBase64}`,
width: 72,
height: 28,
},
cursor: "pointer",
onclick: () => switchDisplayMode("week"),
},
{
type: "text",
// z: 100,
style: {
text: "按周",
x: 36,
y: 14,
textAlign: "center",
textVerticalAlign: "middle",
fill: `${currentMode.value === "week" ? "#F8EA21" : "#DBF7FF"}`,
fontSize: 14,
},
cursor: "pointer",
onclick: () => switchDisplayMode("week"),
},
],
},
// 按月按钮
{
type: "group",
right: 15,
top: 0,
// z: 100,
children: [
{
type: "image",
// z: 100,
style: {
image: `data:image/svg+xml;charset=utf-8,${currentMode.value === "month" ? activeButtonSvgBase64 : buttonSvgBase64}`,
width: 72,
height: 28,
},
cursor: "pointer",
onclick: () => switchDisplayMode("month"),
},
{
type: "text",
// z: 100,
style: {
text: "按月",
x: 36,
y: 14,
textAlign: "center",
textVerticalAlign: "middle",
fill: `${currentMode.value === "month" ? "#F8EA21" : "#DBF7FF"}`,
fontSize: 14,
},
cursor: "pointer",
onclick: () => switchDisplayMode("month"),
},
],
},
],
tooltip: {
trigger: "axis",
renderMode: "html",
padding: 0,
borderWidth: 0,
extraCssText: "background-color: transparent;",
formatter: (params: any) => {
const date = params[0].name;
const seriesData = params
.map(
(item: any) => `<div class="tip-content">
<div class="tip-content-left">
<div class="tip-icon" style="background-color: ${item.borderColor};"></div>
<div class="tip-text">${item.seriesName}</div>
</div>
<div class="tip-content-right">
<div class="tip-text">${item.value}人</div>
</div>
</div>`,
)
.join("");
return `<div style="" class='tool-tip-wrapper'>
<div class="tool-tip-header">
<span class="tip-date">${date}</span>
<div class="tip-graphic">
<div class="qua-group">
<div class="qua-item"></div>
<div class="qua-item"></div>
<div class="qua-item"></div>
<div class="qua-item"></div>
<div class="qua-item"></div>
</div>
<div class="rect-group">
<div class="rect-item"></div>
<div class="rect-item"></div>
<div class="rect-item"></div>
</div>
</div>
</div>
<div class="tip-content-wrapper">
${seriesData}
</div>
</div>`;
},
axisPointer: {
id: "xPointer",
type: "line", // 直线指示器
lineStyle: {
color: "#4760FF",
width: 2,
type: "dashed",
},
},
},
xAxis: {
type: "category",
data: chartData.value.dates,
axisLabel: {
formatter: (value: any) => value,
color: "#C0EEFF",
fontSize: 14,
},
nameTextStyle: {
color: "#243174",
},
axisLine: {
lineStyle: {
color: "#243174",
},
},
},
yAxis: {
name: "人数",
nameLocation: "end",
nameTextStyle: {
color: "#C0EEFF",
fontSize: 14,
padding: [0, 5, 0, 0],
align: "right",
},
type: "value",
min: 0,
max: 500,
interval: 100,
axisLabel: {
color: "#C0EEFF",
fontSize: 14,
},
axisLine: {
show: true,
lineStyle: {
color: "#243174",
},
},
splitLine: {
lineStyle: {
type: "dashed",
color: "#308EFF",
width: 1,
opacity: 0.2,
},
},
},
series: [
...generateSeries(chartData.value.seriesData),
{
type: "bar",
id: "axisOverlayBar",
// zlevel: 1,
silent: false,
barWidth: "100%",
data: chartData.value.dates.map(() => 500), // 使用y轴的最大值
itemStyle: {
color: "transparent",
},
tooltip: {
show: false,
},
},
],
};
myChart.setOption(option, true);
// 跟踪鼠标指示器状态
const hoverBarId = "mouseHoverBar";
myChart.on("mouseover", { seriesId: "axisOverlayBar" }, (params) => {
if (!myChart) return;
// 获取当前数据索引
const dataIndex = params.dataIndex;
// 获取当前option配置
const _option = myChart.getOption() as any;
// 检查是否已存在高亮条
let seriesIndex = -1;
if (_option.series && Array.isArray(_option.series)) {
_option.series.forEach((series: any, index: number) => {
if (series.id === hoverBarId) {
seriesIndex = index;
}
});
}
// 构建高亮条配置
const hoverBarOption = {
id: hoverBarId,
type: "bar",
barWidth: 40,
barGap: "-100%",
// zlevel: 10,
silent: true,
animation: true,
data: chartData.value.dates.map((_: any, index: number) => {
return index === dataIndex ? 500 : "-";
}),
itemStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#fff", // 开始颜色
},
{
offset: 1,
color: "#0783FA", // 结束颜色
},
],
global: false,
},
opacity: 0.3,
},
tooltip: {
show: false,
},
};
if (seriesIndex === -1) {
// 如果不存在则添加新的高亮条
if (_option.series && Array.isArray(_option.series)) {
_option.series.push(hoverBarOption);
}
} else {
// 如果已存在则更新位置
if (_option.series && Array.isArray(_option.series)) {
_option.series[seriesIndex].data = hoverBarOption.data;
}
}
// 更新图表
myChart.setOption(_option, false);
});
// 鼠标移出图表时移除高亮条
myChart.on("mouseout", { seriesId: "axisOverlayBar" }, () => {
if (!myChart) return;
const _option = myChart.getOption() as any;
const hoverBarExists = _option.series.find((series: any) => series.id && series.id === hoverBarId);
if (hoverBarExists) {
try {
// 检查series是否存在
if (_option.series && Array.isArray(_option.series)) {
// 查找并删除高亮条
const newSeries = _option.series.filter((series: any) => {
return series.id !== hoverBarId;
});
// 直接设置新的series数组
_option.series = newSeries;
// 更新图表
myChart.setOption(_option, true);
}
} catch (err) {
console.error("删除高亮条出错:", err);
}
}
});
myChart.on("legendselectchanged", (params: any) => {
// 更新图例图标
updateLegendIcons(params.selected);
});
};
const handleResize = () => {
myChart?.resize();
};
onMounted(() => {
window.addEventListener("resize", handleResize);
});
// 监听图表数据变化,更新图表
watch(() => props.chartDataArray, () => {
initChart();
}, { deep: true });
onUnmounted(() => {
window.removeEventListener("resize", handleResize);
myChart?.dispose();
});
</script>
<style lang="scss" scoped>
:deep(.tool-tip-wrapper) {
background-color: transparent;
position: relative;
width: 184px;
border-radius: 4px;
box-shadow: 0px 4px 10px 0px #0d3472;
overflow: hidden;
padding: 10px;
background-color: rgb(3, 33, 64);
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border: 2px solid transparent;
border-image: linear-gradient(rgb(3, 33, 64), rgba(0, 170, 255, 0.3)) 2;
}
.tool-tip-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.tip-date {
font-size: 14px;
color: #c0eeff;
font-weight: 600;
}
.tip-graphic {
display: flex;
align-items: center;
.qua-group {
display: flex;
align-items: center;
margin-right: 4px;
.qua-item {
width: 2px;
height: 8px;
color: rgba(255, 255, 255, 1);
text-align: center;
transform: skewX(-40deg);
background: linear-gradient(360deg, #0085ff 0%, rgba(0, 133, 255, 0) 93%, rgba(0, 133, 255, 0) 100%);
margin-left: 3px;
}
}
.rect-group {
display: flex;
align-items: center;
.rect-item {
width: 5px;
height: 5px;
background-color: #00c2ff;
transform: rotate(45deg);
margin-left: 5px;
animation: colorChange 1.5s infinite;
&:nth-child(1) {
animation-delay: 0s;
}
&:nth-child(2) {
animation-delay: 0.5s;
}
&:nth-child(3) {
animation-delay: 1s;
}
}
}
}
}
.tip-content {
padding: 6px 7px 6px 10px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: space-between;
background-color: rgb(4, 50, 89);
font-size: 14px;
color: #b9ddfd;
.tip-content-left {
display: flex;
align-items: center;
.tip-icon {
width: 8px;
height: 8px;
border-radius: 50%;
margin-right: 5px;
}
}
}
.tip-content-wrapper {
display: grid;
grid-template-columns: 1fr;
gap: 4px;
}
}
@keyframes colorChange {
0%,
100% {
background-color: #00c2ff;
}
50% {
background-color: #0085ff;
}
}
</style>