update chart

This commit is contained in:
Timi
2026-08-12 00:24:12 +08:00
parent 9c30891f2c
commit 171aee1602
17 changed files with 1554 additions and 697 deletions
+119 -298
View File
@@ -38,24 +38,28 @@
<strong v-text="item.value" />
</div>
</section>
<event-record-calendar
<customer-event-record-calendar-section
v-if="selectedCustomer"
v-model="monthCursor"
v-model:selected-event-id-list="selectedEventIdList"
class="calendar-section"
:event-list="eventList"
:customer-name="selectedCustomer.name"
:event-list="eventList"
:loading="calendarLoading"
:records="monthRecords"
/>
<section
v-for="item in chartList"
:key="item.id"
class="chart-section"
>
<h2 v-text="item.title" />
<div :ref="element => setChartRef(item.id, element)" class="chart" />
</section>
<customer-record-trend-chart
v-if="selectedCustomer"
:date-text-list="chartDateTextList"
:selected-event-id-list="selectedEventIdList"
:stat-map="recordDailyStatMap"
/>
<customer-event-structure-chart
v-if="selectedCustomer"
:date-text-list="monthDateTextList"
:event-list="eventList"
:selected-event-id-list="selectedEventIdList"
:stat-map="recordDailyStatMap"
/>
<customer-selector
v-model="selectedCustomerId"
v-model:visible="selectorVisible"
@@ -65,53 +69,34 @@
</template>
<script setup lang="ts">
import type {ECharts, EChartsOption} from "echarts";
import {BarChart, LineChart} from "echarts/charts";
import {GridComponent, LegendComponent, TooltipComponent} from "echarts/components";
import {init, use} from "echarts/core";
import {CanvasRenderer} from "echarts/renderers";
import {Time} from "timi-web";
import { Time } from "timi-web";
import CustomerAPI from "@/api/CustomerAPI";
import CustomerChartAPI from "@/api/CustomerChartAPI";
import EventAPI from "@/api/EventAPI";
import EventRecordAPI from "@/api/EventRecordAPI";
import type {Customer} from "@/types/Customer";
import type {EventItem} from "@/types/EventItem";
import type {EventRecord} from "@/types/EventRecord";
import CodeScanner from "@/components/CodeScanner.vue";
import CustomerSelector from "@/components/CustomerSelector.vue";
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
import {useSettingStore} from "@/store/settingStore";
import {permissionCodes} from "@/utils/permission";
import CustomerEventRecordCalendarSection from "@/pages/customer/component/CustomerEventRecordCalendarSection.vue";
import CustomerEventStructureChart from "@/pages/customer/component/CustomerEventStructureChart.vue";
import CustomerRecordTrendChart from "@/pages/customer/component/CustomerRecordTrendChart.vue";
import { useSettingStore } from "@/store/settingStore";
import type { Customer } from "@/types/Customer";
import type { EventItem } from "@/types/EventItem";
import type { EventRecord } from "@/types/EventRecord";
import type { StoreEventRecordCalendarView, StoreEventRecordDailyStatView } from "@/types/Store";
import { permissionCodes } from "@/utils/permission";
interface SummaryItem {
label: string;
value: string;
}
interface ChartProfile {
id: string;
title: string;
option: EChartsOption;
}
defineOptions({
name: "CustomerDataCharts"
});
use([
BarChart,
LineChart,
GridComponent,
LegendComponent,
TooltipComponent,
CanvasRenderer
]);
const settingStore = useSettingStore();
const router = useRouter();
const route = useRoute();
const chartRefs = new Map<string, HTMLElement>();
const chartInstances = new Map<string, ECharts>();
const routeCustomerId = computed(() => String(route.params.id || ""));
const routeCustomerCode = computed(() => String(route.query.customerCode || ""));
const customerCode = ref(routeCustomerCode.value);
@@ -125,7 +110,12 @@ const selectedEventIdList = ref<string[]>([]);
const monthCursor = ref(getMonthStart(new Date()));
const monthRecords = ref<EventRecord[]>([]);
const calendarLoading = ref(false);
const recordDailyStatMap = ref<Record<string, StoreEventRecordDailyStatView[]>>({});
const chartBeginDate = getDateStart(addMonths(new Date(), -1));
const chartEndDate = getDateEnd(new Date());
const chartDateTextList = buildDateTextList(chartBeginDate, chartEndDate);
let latestDecodedText = "";
let monthDataRequestId = 0;
const eventNameMap = computed(() => {
const map = new Map<string, string>();
@@ -165,33 +155,6 @@ const summaryList = computed<SummaryItem[]>(() => [
value: getRecordEventName(filteredMonthRecords.value[0]) || "暂无"
}
]);
const chartList = computed<ChartProfile[]>(() => {
if (!selectedCustomer.value) {
return [];
}
return [
{
id: "customer-record-trend",
title: "客户登记趋势",
option: buildRecordTrendOption()
},
{
id: "customer-event-structure",
title: "登记事件结构",
option: buildBehaviorStructureOption()
},
{
id: "customer-relation",
title: "客户关系数据",
option: buildRelationOption()
}
];
});
watch(chartList, async () => {
await nextTick();
renderCharts();
});
watch(routeCustomerCode, (value) => {
if (value) {
@@ -210,81 +173,35 @@ watch(customerCode, async (value) => {
});
watch([selectedCustomer, monthCursor], async () => {
await loadMonthRecords();
await loadMonthData();
});
onMounted(async () => {
await loadActions();
await loadEvents();
if (routeCustomerId.value) {
await loadCustomerById(routeCustomerId.value);
} else if (customerCode.value) {
await loadCustomer(customerCode.value);
}
await nextTick();
renderCharts();
window.addEventListener("resize", resizeCharts);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", resizeCharts);
chartInstances.forEach(item => item.dispose());
chartInstances.clear();
});
function setChartRef(id: string, element: Element | ComponentPublicInstance | null): void {
if (element instanceof HTMLElement) {
chartRefs.set(id, element);
}
}
function renderCharts(): void {
for (const item of chartList.value) {
const element = chartRefs.get(item.id);
if (!element) {
continue;
}
const chart = getChartInstance(item.id, element);
chart.setOption(item.option, true);
}
}
function getChartInstance(id: string, element: HTMLElement): ECharts {
const instance = chartInstances.get(id);
if (instance) {
return instance;
}
const chart = init(element);
chartInstances.set(id, chart);
return chart;
}
function resizeCharts(): void {
chartInstances.forEach(item => item.resize());
}
// ---------- 数据 ----------
async function loadActions(): Promise<void> {
async function loadEvents(): Promise<void> {
eventList.value = await EventAPI.listValid();
selectedEventIdList.value = eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
selectedEventIdList.value = getEventIdList();
}
async function loadCustomer(code: string): Promise<void> {
const normalizedCode = code.trim();
if (!normalizedCode) {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
monthRecords.value = [];
clearLoadedCustomer();
return;
}
const customer = await CustomerAPI.findByCode(normalizedCode);
if (!customer) {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
monthRecords.value = [];
clearLoadedCustomer();
return;
}
@@ -295,9 +212,7 @@ async function loadCustomer(code: string): Promise<void> {
async function loadCustomerById(id: string): Promise<void> {
const normalizedId = id.trim();
if (!normalizedId) {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
monthRecords.value = [];
clearLoadedCustomer();
return;
}
@@ -305,9 +220,7 @@ async function loadCustomerById(id: string): Promise<void> {
selectedCustomer.value = await CustomerAPI.detail(normalizedId);
selectedCustomerId.value = selectedCustomer.value.id || "";
} catch (err) {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
monthRecords.value = [];
clearLoadedCustomer();
console.warn("客户详情加载失败", err);
}
}
@@ -317,36 +230,63 @@ async function handleCustomerSelected(id: string): Promise<void> {
}
async function clearCustomerSelection(): Promise<void> {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
customerCode.value = "";
monthRecords.value = [];
clearLoadedCustomer();
if (route.name !== "CustomerDataCharts" || Object.keys(route.query).length) {
await router.replace({name: "CustomerDataCharts"});
await router.replace({ name: "CustomerDataCharts" });
}
}
async function loadMonthRecords(): Promise<void> {
function clearLoadedCustomer(): void {
selectedCustomer.value = undefined;
selectedCustomerId.value = "";
monthRecords.value = [];
recordDailyStatMap.value = {};
}
async function loadMonthData(): Promise<void> {
const customerId = selectedCustomer.value?.id;
if (!customerId) {
monthRecords.value = [];
recordDailyStatMap.value = {};
return;
}
const beginDate = getCalendarBeginDate(monthCursor.value);
const endDate = getDateEnd(addDays(beginDate, 41));
const eventIdList = getEventIdList();
if (!eventIdList.length) {
monthRecords.value = [];
recordDailyStatMap.value = {};
return;
}
const requestId = ++monthDataRequestId;
const calendarBeginDate = getCalendarBeginDate(monthCursor.value);
const calendarEndDate = getDateEnd(addDays(calendarBeginDate, 41));
calendarLoading.value = true;
try {
monthRecords.value = (await EventRecordAPI.list({
index: 0,
size: Number.MAX_SAFE_INTEGER,
customerId,
beginAt: Time.toTimestamp(beginDate),
endAt: Time.toTimestamp(endDate)
})).list;
const [dailyStatList, calendarRecordList] = await Promise.all([
CustomerChartAPI.eventRecordDailyStat({
customerId,
beginAt: Time.toTimestamp(chartBeginDate),
endAt: Time.toTimestamp(chartEndDate),
eventIdList
}),
CustomerChartAPI.eventRecordCalendar({
customerId,
beginAt: Time.toTimestamp(calendarBeginDate),
endAt: Time.toTimestamp(calendarEndDate),
eventIdList
})
]);
if (requestId === monthDataRequestId) {
recordDailyStatMap.value = groupRecordDailyStatByEventId(dailyStatList);
monthRecords.value = calendarRecordList.map(buildCalendarRecord);
}
} finally {
calendarLoading.value = false;
if (requestId === monthDataRequestId) {
calendarLoading.value = false;
}
}
}
@@ -377,9 +317,20 @@ function handleScanStatusChange(value: string): void {
scanStatus.value = value;
}
function getRecordDateText(record: EventRecord): string {
const date = Time.toDateObject(record.registeredAt);
return date ? Time.formatDate(date) : "";
// ---------- 统计 ----------
function groupRecordDailyStatByEventId(list: StoreEventRecordDailyStatView[]): Record<string, StoreEventRecordDailyStatView[]> {
const map: Record<string, StoreEventRecordDailyStatView[]> = {};
for (const item of list) {
if (!item.eventId) {
continue;
}
if (!map[item.eventId]) {
map[item.eventId] = [];
}
map[item.eventId].push(item);
}
return map;
}
function getRecordEventName(record?: EventRecord): string {
@@ -390,120 +341,21 @@ function getRecordEventName(record?: EventRecord): string {
return record.event?.name || (record.eventId ? eventNameMap.value.get(record.eventId) : "") || "未知事件";
}
// ---------- 图表 ----------
function buildBaseOption(): EChartsOption {
return {
color: ["#2563eb", "#0f766e", "#d97706", "#db2777", "#7c3aed"],
grid: {
top: 48,
left: 12,
right: 12,
bottom: 12,
containLabel: true
},
legend: {
top: 0,
icon: "roundRect",
textStyle: {
color: "#52607a"
}
},
tooltip: {
trigger: "axis"
},
xAxis: {
type: "category",
data: monthDateTextList.value.map(item => item.slice(5)),
axisTick: {
show: false
},
axisLabel: {
color: "#52607a"
}
},
yAxis: {
type: "value",
axisLabel: {
color: "#52607a"
},
splitLine: {
lineStyle: {
color: "rgba(148, 163, 184, .2)"
}
}
}
};
function getEventIdList(): string[] {
return eventList.value.map(item => item.id).filter((item): item is string => Boolean(item));
}
function buildRecordTrendOption(): EChartsOption {
function buildCalendarRecord(record: StoreEventRecordCalendarView): EventRecord {
return {
...buildBaseOption(),
series: [
{
name: "登记次数",
type: "line",
smooth: true,
data: monthDateTextList.value.map(date => filteredMonthRecords.value.filter(item => getRecordDateText(item) === date).length)
}
]
};
}
function buildBehaviorStructureOption(): EChartsOption {
return {
...buildBaseOption(),
xAxis: {
type: "category",
data: eventList.value.filter(item => item.id && selectedEventIdSet.value.has(item.id)).map(item => item.name || "未命名事件"),
axisTick: {
show: false
},
axisLabel: {
color: "#52607a"
}
},
series: [
{
name: "登记次数",
type: "bar",
data: eventList.value
.filter(item => item.id && selectedEventIdSet.value.has(item.id))
.map(item => ({
value: filteredMonthRecords.value.filter(record => record.eventId === item.id).length,
itemStyle: getEventColor(item) ? {
color: getEventColor(item)
} : undefined
}))
}
]
};
}
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
function buildRelationOption(): EChartsOption {
return {
...buildBaseOption(),
xAxis: {
type: "category",
data: ["介绍人", "转介绍客户"],
axisTick: {
show: false
},
axisLabel: {
color: "#52607a"
}
},
series: [
{
name: "客户数",
type: "bar",
data: [selectedCustomer.value?.introducerCustomerId ? 1 : 0, selectedCustomer.value?.invitedCount || 0]
}
]
id: record.id,
customerId: record.customerId,
eventId: record.eventId,
registeredAt: record.registeredAt,
customer: record.customerId || record.customerCode || record.customerName ? {
id: record.customerId,
code: record.customerCode,
name: record.customerName
} : undefined
};
}
@@ -513,6 +365,10 @@ function getMonthStart(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), 1, 0, 0, 0, 0);
}
function getDateStart(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0);
}
function getCalendarBeginDate(date: Date): Date {
const monthStart = getMonthStart(date);
return addDays(monthStart, -monthStart.getDay());
@@ -528,6 +384,14 @@ function buildMonthDateTextList(date: Date): string[] {
return list;
}
function buildDateTextList(begin: Date, end: Date): string[] {
const list: string[] = [];
for (let item = getDateStart(begin); item <= end; item = addDays(item, 1)) {
list.push(Time.formatDate(item));
}
return list;
}
function addDays(date: Date, days: number): Date {
const value = new Date(date);
value.setDate(value.getDate() + days);
@@ -541,7 +405,6 @@ function addMonths(date: Date, months: number): Date {
function getDateEnd(date: Date): Date {
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59, 999);
}
</script>
<style scoped lang="less">
@@ -621,37 +484,6 @@ function getDateEnd(date: Date): Date {
}
}
}
.chart-section {
margin: 0 1rem;
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
border-radius: .75rem;
h2 {
margin: 0;
color: #0f172a;
font-size: 1rem;
font-weight: 700;
}
}
.calendar-section {
margin: 0 1rem;
}
.chart-section {
h2 {
margin-bottom: .875rem;
}
.chart {
width: 100%;
height: 16rem;
}
}
}
:global(html.theme-dark) {
@@ -674,17 +506,6 @@ function getDateEnd(date: Date): Date {
}
}
}
.calendar-section,
.chart-section {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
h2 {
color: #e2e8f0;
}
}
}
}
</style>
@@ -0,0 +1,58 @@
<template>
<event-record-calendar
v-model="monthCursorProxy"
v-model:selected-event-id-list="selectedEventIdListProxy"
class="calendar-section"
:event-list="eventList"
:customer-name="customerName"
:loading="loading"
:records="records"
/>
</template>
<script setup lang="ts">
import EventRecordCalendar from "@/components/EventRecordCalendar.vue";
import type { EventItem } from "@/types/EventItem";
import type { EventRecord } from "@/types/EventRecord";
const props = defineProps<{
customerName?: string;
eventList: EventItem[];
loading?: boolean;
modelValue: Date;
records: EventRecord[];
selectedEventIdList: string[];
}>();
const emit = defineEmits<{
"update:modelValue": [value: Date];
"update:selectedEventIdList": [value: string[]];
}>();
const monthCursorProxy = computed({
get: () => props.modelValue,
set: value => emit("update:modelValue", value)
});
const selectedEventIdListProxy = computed({
get: () => props.selectedEventIdList,
set: value => emit("update:selectedEventIdList", value)
});
</script>
<style scoped lang="less">
.calendar-section {
margin: 0 1rem;
}
:global(html.theme-dark) {
.calendar-section {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
h2 {
color: #e2e8f0;
}
}
}
</style>
@@ -0,0 +1,91 @@
<template>
<section class="chart-section">
<h2>登记事件结构</h2>
<div ref="chartElement" class="chart" />
</section>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import type { EventItem } from "@/types/EventItem";
import type { StoreEventRecordDailyStatView } from "@/types/Store";
import { buildCustomerBaseChartOption, useCustomerChart } from "@/pages/customer/component/useCustomerChart";
const props = defineProps<{
dateTextList: string[];
eventList: EventItem[];
selectedEventIdList: string[];
statMap: Record<string, StoreEventRecordDailyStatView[]>;
}>();
const selectedEventIdSet = computed(() => new Set(props.selectedEventIdList));
const visibleEventList = computed(() => props.eventList.filter(item => item.id && selectedEventIdSet.value.has(item.id)));
const chartOption = computed<EChartsOption>(() => ({
...buildCustomerBaseChartOption(props.dateTextList),
xAxis: {
type: "category",
data: visibleEventList.value.map(item => item.name || "未命名事件"),
axisTick: {
show: false
},
axisLabel: {
color: "#52607a"
}
},
series: [
{
name: "登记次数",
type: "bar",
data: visibleEventList.value.map(item => ({
value: countRecordByEvent(item.id || ""),
itemStyle: getEventColor(item) ? {
color: getEventColor(item)
} : undefined
}))
}
]
}));
const { chartElement } = useCustomerChart(chartOption);
function countRecordByEvent(eventId: string): number {
return (props.statMap[eventId] || []).reduce((sum, item) => sum + (item.dailyCount || 0), 0);
}
function getEventColor(event: EventItem): string | undefined {
return event.color || undefined;
}
</script>
<style scoped lang="less">
.chart-section {
margin: 0 1rem;
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
border-radius: .75rem;
h2 {
color: #0f172a;
margin: 0 0 .875rem 0;
font-size: 1rem;
font-weight: 700;
}
.chart {
width: 100%;
height: 16rem;
}
}
:global(html.theme-dark) {
.chart-section {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
h2 {
color: #e2e8f0;
}
}
}
</style>
@@ -0,0 +1,88 @@
<template>
<section class="chart-section">
<h2>客户登记趋势</h2>
<div ref="chartElement" class="chart" />
<chart-range-slider
v-if="showSlider"
:model-value="sliderValue"
:max="maxSliderValue"
:min-range="5"
:label="rangeText"
@change="handleSliderChange"
/>
</section>
</template>
<script setup lang="ts">
import type { EChartsOption } from "echarts";
import ChartRangeSlider from "@/components/ChartRangeSlider.vue";
import type { StoreEventRecordDailyStatView } from "@/types/Store";
import { buildCustomerBaseChartOption, useCustomerChart, useCustomerChartWindow } from "@/pages/customer/component/useCustomerChart";
const props = defineProps<{
dateTextList: string[];
selectedEventIdList: string[];
statMap: Record<string, StoreEventRecordDailyStatView[]>;
}>();
const dateTextList = computed(() => props.dateTextList);
const {
rangeText,
showSlider,
sliderValue,
maxSliderValue,
visibleDateTextList,
handleSliderChange
} = useCustomerChartWindow(dateTextList);
const chartOption = computed<EChartsOption>(() => ({
...buildCustomerBaseChartOption(visibleDateTextList.value),
series: [
{
name: "登记次数",
type: "line",
smooth: true,
data: visibleDateTextList.value.map(dateText => countRecordByDate(dateText))
}
]
}));
const { chartElement } = useCustomerChart(chartOption);
function countRecordByDate(dateText: string): number {
const dateValue = Number(dateText.replaceAll("-", ""));
return props.selectedEventIdList.reduce((sum, eventId) => sum + (props.statMap[eventId]?.find(item => item.dateValue === dateValue)?.dailyCount || 0), 0);
}
</script>
<style scoped lang="less">
.chart-section {
margin: 0 1rem;
padding: 1rem;
background: rgba(255, 255, 255, .92);
box-shadow: var(--td-shadow-1);
border-radius: .75rem;
h2 {
color: #0f172a;
margin: 0 0 .875rem 0;
font-size: 1rem;
font-weight: 700;
}
.chart {
width: 100%;
height: 16rem;
}
}
:global(html.theme-dark) {
.chart-section {
background: rgba(15, 23, 42, .88);
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
h2 {
color: #e2e8f0;
}
}
}
</style>
@@ -0,0 +1,164 @@
import type { ECharts, EChartsOption } from "echarts";
import { BarChart, LineChart } from "echarts/charts";
import { GridComponent, LegendComponent, TooltipComponent } from "echarts/components";
import { init, use } from "echarts/core";
import { CanvasRenderer } from "echarts/renderers";
import type { ComputedRef, Ref } from "vue";
use([
BarChart,
LineChart,
GridComponent,
LegendComponent,
TooltipComponent,
CanvasRenderer
]);
export function buildCustomerBaseChartOption(dateTextList: string[]): EChartsOption {
return {
color: ["#2563eb", "#0f766e", "#d97706", "#db2777", "#7c3aed"],
grid: {
top: 48,
left: 12,
right: 12,
bottom: 12,
containLabel: true
},
legend: {
top: 0,
icon: "roundRect",
textStyle: {
color: "#52607a"
}
},
tooltip: {
trigger: "axis"
},
xAxis: {
type: "category",
data: dateTextList.map(item => item.slice(5)),
axisTick: {
show: false
},
axisLabel: {
color: "#52607a"
}
},
yAxis: {
type: "value",
axisLabel: {
color: "#52607a"
},
splitLine: {
lineStyle: {
color: "rgba(148, 163, 184, .2)"
}
}
}
};
}
export function useCustomerChart(option: ComputedRef<EChartsOption> | Ref<EChartsOption>) {
const chartElement = ref<HTMLElement>();
let chart: ECharts | undefined;
watch(option, async () => {
await nextTick();
renderChart();
}, {
deep: true
});
onMounted(async () => {
await nextTick();
renderChart();
window.addEventListener("resize", resizeChart);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", resizeChart);
chart?.dispose();
chart = undefined;
});
function renderChart(): void {
if (!chartElement.value) {
return;
}
chart = chart || init(chartElement.value);
chart.setOption(option.value, true);
}
function resizeChart(): void {
chart?.resize();
}
return {
chartElement
};
}
export function useCustomerChartWindow(dateTextList: ComputedRef<string[]>) {
const sliderValue = ref<number[]>([0, 0]);
const viewportWidth = ref(window.innerWidth);
let isSliderInitialized = false;
const visibleDayCount = computed(() => Math.max(6, Math.floor(viewportWidth.value / 64)));
const maxSliderValue = computed(() => Math.max(0, dateTextList.value.length - 1));
const visibleDateTextList = computed(() => dateTextList.value.slice(sliderValue.value[0], sliderValue.value[1] + 1));
const showSlider = computed(() => visibleDayCount.value < dateTextList.value.length);
const rangeText = computed(() => {
const list = visibleDateTextList.value;
const beginText = list[0]?.slice(5) || "";
const endText = list[list.length - 1]?.slice(5) || "";
return beginText && endText ? `${beginText} 至 ${endText}` : "";
});
watch(maxSliderValue, value => {
if (!isSliderInitialized) {
sliderValue.value = getLatestSliderRange();
isSliderInitialized = true;
return;
}
const [start, end] = sliderValue.value;
if (value < end) {
sliderValue.value = [Math.max(0, value - Math.max(0, end - start)), value];
}
}, {
immediate: true
});
onMounted(() => {
window.addEventListener("resize", updateViewportWidth);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateViewportWidth);
});
function handleSliderChange(value: number | number[]): void {
if (!Array.isArray(value)) {
return;
}
const [start = 0, end = start] = value;
sliderValue.value = [start, Math.max(start, end)];
}
function updateViewportWidth(): void {
viewportWidth.value = window.innerWidth;
}
function getLatestSliderRange(): number[] {
const end = maxSliderValue.value;
const start = Math.max(0, end - visibleDayCount.value + 1);
return [start, end];
}
return {
rangeText,
showSlider,
sliderValue,
maxSliderValue,
visibleDateTextList,
handleSliderChange
};
}