chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,144 +1,148 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, shallowRef } from "vue";
|
||||
import { useQuery } from "@tanstack/vue-query";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import {
|
||||
TeamOutlined,
|
||||
UserAddOutlined,
|
||||
FileDoneOutlined,
|
||||
import { kolDashboardApi } from '@/lib/api'
|
||||
import {
|
||||
CloseCircleOutlined,
|
||||
ReloadOutlined
|
||||
} from "@ant-design/icons-vue";
|
||||
import type { TableColumnsType } from "ant-design-vue";
|
||||
import type { KolDashboardPackageStat, KolDashboardTrendPoint } from "@geo/shared-types";
|
||||
import { kolDashboardApi } from "@/lib/api";
|
||||
FileDoneOutlined,
|
||||
ReloadOutlined,
|
||||
TeamOutlined,
|
||||
UserAddOutlined,
|
||||
} from '@ant-design/icons-vue'
|
||||
import type { KolDashboardPackageStat } from '@geo/shared-types'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { useElementSize } from '@vueuse/core'
|
||||
import type { TableColumnsType } from 'ant-design-vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const { t } = useI18n();
|
||||
const { t } = useI18n()
|
||||
|
||||
// Filter for trend chart
|
||||
const periodDays = ref(7);
|
||||
const periodDays = ref(7)
|
||||
|
||||
// Queries
|
||||
const overviewQuery = useQuery({
|
||||
queryKey: ["kol", "dashboard", "overview"],
|
||||
queryKey: ['kol', 'dashboard', 'overview'],
|
||||
queryFn: () => kolDashboardApi.overview(),
|
||||
});
|
||||
})
|
||||
|
||||
const packagesQuery = useQuery({
|
||||
queryKey: ["kol", "dashboard", "packages"],
|
||||
queryKey: ['kol', 'dashboard', 'packages'],
|
||||
queryFn: () => kolDashboardApi.packages(),
|
||||
});
|
||||
})
|
||||
|
||||
const trendQuery = useQuery({
|
||||
queryKey: ["kol", "dashboard", "trend", periodDays],
|
||||
queryKey: ['kol', 'dashboard', 'trend', periodDays],
|
||||
queryFn: () => kolDashboardApi.trend(periodDays.value),
|
||||
});
|
||||
})
|
||||
|
||||
// Stats formatting
|
||||
const stats = computed(() => {
|
||||
const data = overviewQuery.data.value;
|
||||
const data = overviewQuery.data.value
|
||||
return [
|
||||
{
|
||||
title: t("kol.dashboard.totalSubs"),
|
||||
title: t('kol.dashboard.totalSubs'),
|
||||
value: data?.total_subscriber_tenants ?? 0,
|
||||
icon: TeamOutlined,
|
||||
color: "#1890ff",
|
||||
color: '#1890ff',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.monthSubs"),
|
||||
title: t('kol.dashboard.monthSubs'),
|
||||
value: data?.month_new_subscriptions ?? 0,
|
||||
icon: UserAddOutlined,
|
||||
color: "#52c41a",
|
||||
color: '#52c41a',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.totalUsage"),
|
||||
title: t('kol.dashboard.totalUsage'),
|
||||
value: data?.total_usage ?? 0,
|
||||
icon: FileDoneOutlined,
|
||||
color: "#722ed1",
|
||||
color: '#722ed1',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.failRate"),
|
||||
value: data ? `${(data.failure_rate * 100).toFixed(2)}%` : "0%",
|
||||
title: t('kol.dashboard.failRate'),
|
||||
value: data ? `${(data.failure_rate * 100).toFixed(2)}%` : '0%',
|
||||
icon: CloseCircleOutlined,
|
||||
color: "#f5222d",
|
||||
color: '#f5222d',
|
||||
},
|
||||
];
|
||||
});
|
||||
]
|
||||
})
|
||||
|
||||
// Table columns
|
||||
const packageColumns: TableColumnsType<KolDashboardPackageStat> = [
|
||||
{
|
||||
title: t("common.name"),
|
||||
dataIndex: "package_name",
|
||||
key: "package_name",
|
||||
title: t('common.name'),
|
||||
dataIndex: 'package_name',
|
||||
key: 'package_name',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.totalSubs"),
|
||||
dataIndex: "subscriber_tenants",
|
||||
key: "subscriber_tenants",
|
||||
align: "right",
|
||||
title: t('kol.dashboard.totalSubs'),
|
||||
dataIndex: 'subscriber_tenants',
|
||||
key: 'subscriber_tenants',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.totalUsage"),
|
||||
dataIndex: "usage_count",
|
||||
key: "usage_count",
|
||||
align: "right",
|
||||
title: t('kol.dashboard.totalUsage'),
|
||||
dataIndex: 'usage_count',
|
||||
key: 'usage_count',
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
title: t("kol.dashboard.failRate"),
|
||||
dataIndex: "failure_rate",
|
||||
key: "failure_rate",
|
||||
align: "right",
|
||||
title: t('kol.dashboard.failRate'),
|
||||
dataIndex: 'failure_rate',
|
||||
key: 'failure_rate',
|
||||
align: 'right',
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
// SVG Chart logic
|
||||
const chartWrapperRef = ref<HTMLElement | null>(null);
|
||||
const { width: wrapperWidth, height: wrapperHeight } = useElementSize(chartWrapperRef, { width: 1000, height: 300 });
|
||||
const chartWrapperRef = ref<HTMLElement | null>(null)
|
||||
const { width: wrapperWidth, height: wrapperHeight } = useElementSize(chartWrapperRef, {
|
||||
width: 1000,
|
||||
height: 300,
|
||||
})
|
||||
|
||||
const chartPadding = { top: 20, right: 40, bottom: 40, left: 60 };
|
||||
const chartPadding = { top: 20, right: 40, bottom: 40, left: 60 }
|
||||
|
||||
const chartViewBox = computed(() => {
|
||||
const w = Math.max(wrapperWidth.value, 1);
|
||||
const h = Math.max(wrapperHeight.value, 1);
|
||||
return `0 0 ${w} ${h}`;
|
||||
});
|
||||
const w = Math.max(wrapperWidth.value, 1)
|
||||
const h = Math.max(wrapperHeight.value, 1)
|
||||
return `0 0 ${w} ${h}`
|
||||
})
|
||||
|
||||
const chartPoints = computed(() => {
|
||||
const points = trendQuery.data.value || [];
|
||||
if (points.length < 2) return [];
|
||||
const points = trendQuery.data.value || []
|
||||
if (points.length < 2) return []
|
||||
|
||||
const maxVal = Math.max(
|
||||
...points.map(p => Math.max(p.subscriptions, p.usages)),
|
||||
1 // avoid division by zero
|
||||
) * 1.1; // 10% headroom
|
||||
const maxVal =
|
||||
Math.max(
|
||||
...points.map((p) => Math.max(p.subscriptions, p.usages)),
|
||||
1, // avoid division by zero
|
||||
) * 1.1 // 10% headroom
|
||||
|
||||
const w = Math.max(wrapperWidth.value, 1);
|
||||
const h = Math.max(wrapperHeight.value, 1);
|
||||
const cWidth = Math.max(w - chartPadding.left - chartPadding.right, 1);
|
||||
const cHeight = Math.max(h - chartPadding.top - chartPadding.bottom, 1);
|
||||
const w = Math.max(wrapperWidth.value, 1)
|
||||
const h = Math.max(wrapperHeight.value, 1)
|
||||
const cWidth = Math.max(w - chartPadding.left - chartPadding.right, 1)
|
||||
const cHeight = Math.max(h - chartPadding.top - chartPadding.bottom, 1)
|
||||
|
||||
const getX = (index: number) => chartPadding.left + (index / (points.length - 1)) * cWidth;
|
||||
const getY = (val: number) => chartPadding.top + cHeight - (val / maxVal) * cHeight;
|
||||
const getX = (index: number) => chartPadding.left + (index / (points.length - 1)) * cWidth
|
||||
const getY = (val: number) => chartPadding.top + cHeight - (val / maxVal) * cHeight
|
||||
|
||||
return points.map((p, i) => {
|
||||
const x = getX(i);
|
||||
let hoverX = 0;
|
||||
let hoverWidth = 0;
|
||||
|
||||
const x = getX(i)
|
||||
let hoverX = 0
|
||||
let hoverWidth = 0
|
||||
|
||||
if (i === 0) {
|
||||
hoverX = chartPadding.left;
|
||||
hoverWidth = (getX(1) - x) / 2;
|
||||
hoverX = chartPadding.left
|
||||
hoverWidth = (getX(1) - x) / 2
|
||||
} else if (i === points.length - 1) {
|
||||
const prevX = getX(i - 1);
|
||||
hoverX = prevX + (x - prevX) / 2;
|
||||
hoverWidth = (x - prevX) / 2;
|
||||
const prevX = getX(i - 1)
|
||||
hoverX = prevX + (x - prevX) / 2
|
||||
hoverWidth = (x - prevX) / 2
|
||||
} else {
|
||||
const prevX = getX(i - 1);
|
||||
const nextX = getX(i + 1);
|
||||
hoverX = prevX + (x - prevX) / 2;
|
||||
hoverWidth = (x - prevX) / 2 + (nextX - x) / 2;
|
||||
const prevX = getX(i - 1)
|
||||
const nextX = getX(i + 1)
|
||||
hoverX = prevX + (x - prevX) / 2
|
||||
hoverWidth = (x - prevX) / 2 + (nextX - x) / 2
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -148,61 +152,61 @@ const chartPoints = computed(() => {
|
||||
useY: getY(p.usages),
|
||||
hoverX,
|
||||
hoverWidth,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const chartPaths = computed(() => {
|
||||
const points = chartPoints.value;
|
||||
if (points.length < 2) return { subscriptions: "", usages: "" };
|
||||
const points = chartPoints.value
|
||||
if (points.length < 2) return { subscriptions: '', usages: '' }
|
||||
|
||||
let subPath = `M ${points[0].x} ${points[0].subY}`;
|
||||
let usagePath = `M ${points[0].x} ${points[0].useY}`;
|
||||
let subPath = `M ${points[0].x} ${points[0].subY}`
|
||||
let usagePath = `M ${points[0].x} ${points[0].useY}`
|
||||
|
||||
for (let i = 1; i < points.length; i++) {
|
||||
subPath += ` L ${points[i].x} ${points[i].subY}`;
|
||||
usagePath += ` L ${points[i].x} ${points[i].useY}`;
|
||||
subPath += ` L ${points[i].x} ${points[i].subY}`
|
||||
usagePath += ` L ${points[i].x} ${points[i].useY}`
|
||||
}
|
||||
|
||||
return { subscriptions: subPath, usages: usagePath };
|
||||
});
|
||||
return { subscriptions: subPath, usages: usagePath }
|
||||
})
|
||||
|
||||
const chartAxis = computed(() => {
|
||||
const points = chartPoints.value;
|
||||
if (points.length === 0) return [];
|
||||
|
||||
const points = chartPoints.value
|
||||
if (points.length === 0) return []
|
||||
|
||||
// Show at most 7 date labels
|
||||
const step = Math.max(1, Math.floor(points.length / 6));
|
||||
const labels = [];
|
||||
const step = Math.max(1, Math.floor(points.length / 6))
|
||||
const labels = []
|
||||
for (let i = 0; i < points.length; i += step) {
|
||||
labels.push({
|
||||
x: points[i].x,
|
||||
label: points[i].date.split("-").slice(1).join("/"), // MM/DD
|
||||
});
|
||||
label: points[i].date.split('-').slice(1).join('/'), // MM/DD
|
||||
})
|
||||
if (i + step >= points.length && i !== points.length - 1) {
|
||||
labels.push({
|
||||
labels.push({
|
||||
x: points[points.length - 1].x,
|
||||
label: points[points.length - 1].date.split("-").slice(1).join("/"),
|
||||
});
|
||||
label: points[points.length - 1].date.split('-').slice(1).join('/'),
|
||||
})
|
||||
}
|
||||
}
|
||||
return labels;
|
||||
});
|
||||
return labels
|
||||
})
|
||||
|
||||
const activePoint = ref<any>(null);
|
||||
const activePoint = ref<any>(null)
|
||||
|
||||
function showTooltip(point: any) {
|
||||
activePoint.value = point;
|
||||
activePoint.value = point
|
||||
}
|
||||
|
||||
function hideTooltip() {
|
||||
activePoint.value = null;
|
||||
activePoint.value = null
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
void overviewQuery.refetch();
|
||||
void packagesQuery.refetch();
|
||||
void trendQuery.refetch();
|
||||
void overviewQuery.refetch()
|
||||
void packagesQuery.refetch()
|
||||
void trendQuery.refetch()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -211,8 +215,8 @@ function refresh() {
|
||||
<section class="page-title-card">
|
||||
<div class="page-title-card__header">
|
||||
<div class="page-title-card__copy">
|
||||
<h2>{{ t("kol.dashboard.title") }}</h2>
|
||||
<p>{{ t("kol.dashboard.subtitle") }}</p>
|
||||
<h2>{{ t('kol.dashboard.title') }}</h2>
|
||||
<p>{{ t('kol.dashboard.subtitle') }}</p>
|
||||
</div>
|
||||
<div class="page-title-card__actions">
|
||||
<a-button @click="refresh">
|
||||
@@ -248,7 +252,7 @@ function refresh() {
|
||||
<div class="dashboard-grid">
|
||||
<!-- Package Table -->
|
||||
<section class="panel panel-packages">
|
||||
<h3 class="panel-title">{{ t("kol.dashboard.byPackage") }}</h3>
|
||||
<h3 class="panel-title">{{ t('kol.dashboard.byPackage') }}</h3>
|
||||
<a-table
|
||||
:columns="packageColumns"
|
||||
:data-source="packagesQuery.data.value || []"
|
||||
@@ -273,7 +277,7 @@ function refresh() {
|
||||
<!-- Trend Chart -->
|
||||
<section class="panel panel-trend">
|
||||
<div class="panel-header">
|
||||
<h3 class="panel-title">{{ t("kol.dashboard.trend") }}</h3>
|
||||
<h3 class="panel-title">{{ t('kol.dashboard.trend') }}</h3>
|
||||
<a-radio-group v-model:value="periodDays" size="small" button-style="solid">
|
||||
<a-radio-button :value="7">7天</a-radio-button>
|
||||
<a-radio-button :value="30">30天</a-radio-button>
|
||||
@@ -281,33 +285,73 @@ function refresh() {
|
||||
</a-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="chart-container" v-if="!trendQuery.isPending.value">
|
||||
<div v-if="chartPoints.length > 0" class="svg-chart-wrapper" ref="chartWrapperRef" @mouseleave="hideTooltip">
|
||||
<div v-if="!trendQuery.isPending.value" class="chart-container">
|
||||
<div
|
||||
v-if="chartPoints.length > 0"
|
||||
ref="chartWrapperRef"
|
||||
class="svg-chart-wrapper"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<div class="chart-legend">
|
||||
<div class="legend-item"><span class="dot sub"></span> 订阅</div>
|
||||
<div class="legend-item"><span class="dot usage"></span> 使用</div>
|
||||
<div class="legend-item">
|
||||
<span class="dot sub"></span>
|
||||
订阅
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<span class="dot usage"></span>
|
||||
使用
|
||||
</div>
|
||||
</div>
|
||||
<svg :viewBox="chartViewBox" class="svg-chart" preserveAspectRatio="none">
|
||||
<!-- Grid lines -->
|
||||
<line
|
||||
v-for="i in 5" :key="i"
|
||||
:x1="chartPadding.left"
|
||||
:y1="chartPadding.top + (Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) * (i-1) / 4)"
|
||||
:x2="chartPadding.left + Math.max(wrapperWidth - chartPadding.left - chartPadding.right, 1)"
|
||||
:y2="chartPadding.top + (Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) * (i-1) / 4)"
|
||||
<line
|
||||
v-for="i in 5"
|
||||
:key="i"
|
||||
:x1="chartPadding.left"
|
||||
:y1="
|
||||
chartPadding.top +
|
||||
(Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) * (i - 1)) /
|
||||
4
|
||||
"
|
||||
:x2="
|
||||
chartPadding.left +
|
||||
Math.max(wrapperWidth - chartPadding.left - chartPadding.right, 1)
|
||||
"
|
||||
:y2="
|
||||
chartPadding.top +
|
||||
(Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) * (i - 1)) /
|
||||
4
|
||||
"
|
||||
stroke="#f0f0f0"
|
||||
stroke-width="1"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Paths -->
|
||||
<path :d="chartPaths.subscriptions" fill="none" stroke="#1890ff" stroke-width="2" stroke-linejoin="round" />
|
||||
<path :d="chartPaths.usages" fill="none" stroke="#52c41a" stroke-width="2" stroke-linejoin="round" />
|
||||
|
||||
<path
|
||||
:d="chartPaths.subscriptions"
|
||||
fill="none"
|
||||
stroke="#1890ff"
|
||||
stroke-width="2"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
:d="chartPaths.usages"
|
||||
fill="none"
|
||||
stroke="#52c41a"
|
||||
stroke-width="2"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
|
||||
<!-- X Axis Labels -->
|
||||
<text
|
||||
v-for="tick in chartAxis" :key="tick.x"
|
||||
:x="tick.x"
|
||||
:y="chartPadding.top + Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) + 20"
|
||||
<text
|
||||
v-for="tick in chartAxis"
|
||||
:key="tick.x"
|
||||
:x="tick.x"
|
||||
:y="
|
||||
chartPadding.top +
|
||||
Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1) +
|
||||
20
|
||||
"
|
||||
text-anchor="middle"
|
||||
font-size="12"
|
||||
fill="#8c8c8c"
|
||||
@@ -318,37 +362,75 @@ function refresh() {
|
||||
<!-- Hover Interaction Areas -->
|
||||
<rect
|
||||
v-for="(point, i) in chartPoints"
|
||||
:key="'hover-'+i"
|
||||
:key="'hover-' + i"
|
||||
:x="point.hoverX"
|
||||
:y="chartPadding.top"
|
||||
:width="point.hoverWidth"
|
||||
:height="Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1)"
|
||||
fill="transparent"
|
||||
style="cursor: pointer;"
|
||||
style="cursor: pointer"
|
||||
@mouseenter="showTooltip(point)"
|
||||
/>
|
||||
|
||||
<!-- Tooltip Overlay -->
|
||||
<g v-if="activePoint">
|
||||
<!-- Vertical dashed line -->
|
||||
<line
|
||||
:x1="activePoint.x" :y1="chartPadding.top"
|
||||
:x2="activePoint.x" :y2="chartPadding.top + Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1)"
|
||||
stroke="#bfbfbf" stroke-width="1" stroke-dasharray="4 4"
|
||||
<line
|
||||
:x1="activePoint.x"
|
||||
:y1="chartPadding.top"
|
||||
:x2="activePoint.x"
|
||||
:y2="
|
||||
chartPadding.top +
|
||||
Math.max(wrapperHeight - chartPadding.top - chartPadding.bottom, 1)
|
||||
"
|
||||
stroke="#bfbfbf"
|
||||
stroke-width="1"
|
||||
stroke-dasharray="4 4"
|
||||
/>
|
||||
|
||||
|
||||
<!-- Dots on the line -->
|
||||
<circle :cx="activePoint.x" :cy="activePoint.subY" r="4" fill="#1890ff" stroke="#fff" stroke-width="2" />
|
||||
<circle :cx="activePoint.x" :cy="activePoint.useY" r="4" fill="#52c41a" stroke="#fff" stroke-width="2" />
|
||||
|
||||
<circle
|
||||
:cx="activePoint.x"
|
||||
:cy="activePoint.subY"
|
||||
r="4"
|
||||
fill="#1890ff"
|
||||
stroke="#fff"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<circle
|
||||
:cx="activePoint.x"
|
||||
:cy="activePoint.useY"
|
||||
r="4"
|
||||
fill="#52c41a"
|
||||
stroke="#fff"
|
||||
stroke-width="2"
|
||||
/>
|
||||
|
||||
<!-- Tooltip Panel -->
|
||||
<g :transform="`translate(${activePoint.x > (wrapperWidth - 160) ? activePoint.x - 140 : activePoint.x + 15}, ${chartPadding.top + 20})`">
|
||||
<rect x="0" y="0" width="125" height="75" fill="#ffffff" stroke="#e8e8e8" rx="4" style="filter: drop-shadow(0 2px 6px rgba(0,0,0,0.1));" />
|
||||
<text x="12" y="22" font-size="12" font-weight="bold" fill="#8c8c8c">{{ activePoint.date }}</text>
|
||||
<g
|
||||
:transform="`translate(${activePoint.x > wrapperWidth - 160 ? activePoint.x - 140 : activePoint.x + 15}, ${chartPadding.top + 20})`"
|
||||
>
|
||||
<rect
|
||||
x="0"
|
||||
y="0"
|
||||
width="125"
|
||||
height="75"
|
||||
fill="#ffffff"
|
||||
stroke="#e8e8e8"
|
||||
rx="4"
|
||||
style="filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.1))"
|
||||
/>
|
||||
<text x="12" y="22" font-size="12" font-weight="bold" fill="#8c8c8c">
|
||||
{{ activePoint.date }}
|
||||
</text>
|
||||
<circle cx="16" cy="42" r="4" fill="#1890ff" />
|
||||
<text x="28" y="46" font-size="12" fill="#1a1a1a">订阅: {{ activePoint.subscriptions }}</text>
|
||||
<text x="28" y="46" font-size="12" fill="#1a1a1a">
|
||||
订阅: {{ activePoint.subscriptions }}
|
||||
</text>
|
||||
<circle cx="16" cy="62" r="4" fill="#52c41a" />
|
||||
<text x="28" y="66" font-size="12" fill="#1a1a1a">使用: {{ activePoint.usages }}</text>
|
||||
<text x="28" y="66" font-size="12" fill="#1a1a1a">
|
||||
使用: {{ activePoint.usages }}
|
||||
</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
@@ -440,7 +522,7 @@ function refresh() {
|
||||
align-items: center;
|
||||
}
|
||||
.panel-title::before {
|
||||
content: "";
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 4px;
|
||||
height: 14px;
|
||||
@@ -491,8 +573,12 @@ function refresh() {
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.dot.sub { background: #1890ff; }
|
||||
.dot.usage { background: #52c41a; }
|
||||
.dot.sub {
|
||||
background: #1890ff;
|
||||
}
|
||||
.dot.usage {
|
||||
background: #52c41a;
|
||||
}
|
||||
|
||||
.svg-chart {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user