DispatchAction使用要点

本文介绍如何在Struts2框架中使用DispatchAction基类实现方法映射,包括自定义PersonAction类并覆盖add和search方法,以及如何在struts-config.xml中配置参数method属性以实现不同方法的调用。

注意:继承自dispatchaction;

去掉execute()方法;

add(),search()方法的定义完全照搬execute()。

public class PersonAction extends DispatchAction{
public ActionForward add(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
...
...
}

public ActionForward search(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
...
...
}
}
2 struts-config.xml的配置,注意在<action>的配置中加上parameter=method属性,其他属性根据需要配置
<action path="/person"
parameter="method"
scope="request"
type="com.***.PersonAction"
validate="false">
3请求中的路径,通过method=add来定位到action中的add()方法
<html:form action="/PersonAction.do?method=add">

<template> <div class="content"> <HeaderFilter v-model:dateRange="dateRange" v-model:selectedMetric="selectedMetric" v-model:selectedOneSite="selectedOneSite" v-model:selectedAdType="selectedAdType" :metricOptions="metricOptions" :siteOptions="siteOptions" :loading="loading" :show-date="true" :show-metric="true" :show-one-site="true" :show-ad-type="true" @filter-change="handleFilterChange" /> <div class="chart-wrapper"> <div ref="chartDom" class="chart-container"></div> <div v-show="loading" class="loading-overlay"> <div class="loader"></div> </div> </div> </div> </template> <script setup> import { getAdLineChartApI } from '@/api/adunit.js' import * as echarts from 'echarts' import { ref, onMounted, onBeforeUnmount } from 'vue' import HeaderFilter from '@/components/HeaderFilter/index.vue' import { allMetricName } from '@/utils/common.js' const metricOptions = ref(allMetricName) const props = defineProps({ siteOptions: { type: Array, default: () => [] } }) // 状态管理 const chartDom = ref(null) let myChart = null const loading = ref(false) const dateRange = ref((() => { const formatDate = (date) => { return date.toISOString().slice(0, 10) } const end = new Date() const start = new Date() start.setTime(start.getTime() - 7 * 24 * 60 * 60 * 1000) return [formatDate(start), formatDate(end)] })()) const selectedMetric = ref('imps') const selectedOneSite = ref('') const selectedAdType = ref('') // 或者 ref([]) 如果是多选 // 添加一个标志位,避免重复调用 const hasInitialized = ref(false) // 提取设置默认站点的逻辑 const setDefaultSite = (siteOptions) => { if (siteOptions && siteOptions.length > 0 && (!selectedOneSite.value || selectedOneSite.value === '')) { selectedOneSite.value = siteOptions[0].value console.log('Set default selectedOneSite:', selectedOneSite.value) return true } return false } const initializeChart = () => { // 如果已经初始化过,直接返回 if (hasInitialized.value) return hasInitialized.value = true // 尝试从本地存储加载设置 const hasSavedSettings = loadChartSettingsFromStorage() // 如果有保存的设置,直接获取数据 if (hasSavedSettings) { nextTick(() => { fetchAndUpdateChart() }) return } // 检查是否有 siteOptions 数据并设置默认值 if (props.siteOptions && props.siteOptions.length > 0) { const hasSetDefault = setDefaultSite(props.siteOptions) if (hasSetDefault) { nextTick(() => { fetchAndUpdateChart() }) } } } watch( () => props.siteOptions, (newSiteOptions) => { console.log('siteOptions changed:', newSiteOptions) // 只在未初始化时设置默认站点并获取数据 if (!hasInitialized.value && newSiteOptions && newSiteOptions.length > 0) { const hasSetDefault = setDefaultSite(newSiteOptions) if (hasSetDefault) { hasInitialized.value = true nextTick(() => { fetchAndUpdateChart() }) } } } ) // 初始化图表(只执行一次) const initChart = () => { if (!chartDom.value) return myChart = echarts.init(chartDom.value) } // 处理窗口大小变化 const handleResize = () => { if (myChart) { myChart.resize() } } // 转换API数据为图表格式 - 增强健壮性 const transformData = (rawData) => { try { // 1. 处理空数据情况 if (!rawData || !Array.isArray(rawData)) { return { dates: [], series: [] } } // 2. 提取所有日期并排序 const datesSet = new Set() rawData.forEach(item => { if (item?.data_date) { datesSet.add(item.data_date) } }) const dates = Array.from(datesSet).sort((a, b) => new Date(a) - new Date(b)) // 3. 按 ad_unit_name 分组数据 const groupedData = {} rawData.forEach(item => { if (!item?.ad_unit_name) return const adUnitName = item.ad_unit_name if (!groupedData[adUnitName]) { groupedData[adUnitName] = [] } groupedData[adUnitName].push(item) }) // 4. 为每个广告单元创建数据系列 const series = [] Object.keys(groupedData).forEach(adUnitName => { // 如果选择了特定的广告类型,则只显示选中的类型 if (selectedAdType.value && selectedAdType.value.length > 0) { if (!selectedAdType.value.includes(adUnitName)) { return; // 跳过未选中的广告类型 } } const data = dates.map(date => { const item = groupedData[adUnitName].find(i => i.data_date === date) return item ? (item[selectedMetric.value] || 0) : 0 }) if (data.length > 0) { series.push({ name: adUnitName, // 使用 ad_unit_name 作为系列名称 type: 'line', data: data, showSymbol: data.length <= 10, emphasis: { focus: 'series' }, smooth: true, id: `series-${adUnitName.replace(/\s+/g, '-')}` }) } }) return { dates, series } } catch (error) { console.error('数据转换失败:', error) return { dates: [], series: [] } } } const allAdTypes = ref([]) // 获取数据并更新图表 const fetchAndUpdateChart = async () => { if (!dateRange.value || dateRange.value.length !== 2) return // 确保图表已初始化 if (!myChart) { initChart() } loading.value = true try { const [startAt, endAt] = dateRange.value // 构造请求参数 const params = { start_at: startAt, end_at: endAt } // 如果选择了网站,则添加到参数中 if (selectedOneSite.value && selectedOneSite.value !== '') { console.log('Sending site_url:', selectedOneSite.value) params.site_urls = [selectedOneSite.value] } // 如果选择了广告类型,则添加到参数中 if (selectedAdType.value) { params.ad_unit_names = [selectedAdType.value] // 根据API要求调整参数名 } const response = await getAdLineChartApI(params) // 添加API响应检查 if (!response || !response.data) { throw new Error('API响应无效') } // 提取所有广告类型 const adTypes = [...new Set(response.data.map(item => item.ad_unit_name).filter(Boolean))] allAdTypes.value = adTypes // 如果还没有选择广告类型,则默认选择所有 if (!selectedAdType.value || selectedAdType.value.length === 0) { selectedAdType.value = adTypes } const { dates, series } = transformData(response.data) console.log(dates, 12313123); updateChart(dates, series) } catch (error) { console.error("获取折线图数据失败:", error) // 出错时显示空图表 updateChart([], []) } finally { loading.value = false } } // 更新图表配置 - 完全重写 const updateChart = (dates, series) => { console.log(dates, 11); let safeSeries = series if (!Array.isArray(safeSeries)) { safeSeries = [] } // 如果没有有效数据,创建空数据系列 if (safeSeries.length === 0) { safeSeries = [{ name: 'The selected link has no data for the moment.', type: 'line', data: [], id: 'empty-series' }] } myChart.clear() const allLegendNames = safeSeries.map(s => s.name); // 根据图例数量决定行数 let legendConfig; if (allLegendNames.length <= 6) { // 少量图例,使用单行 legendConfig = [{ data: allLegendNames, type: 'plain', bottom: 40, left: 'center', width: '95%', itemGap: 15, icon: 'circle', itemWidth: 12, itemHeight: 12, textStyle: { fontSize: 12 }, formatter: function (name) { return name; }, selectedMode: true }]; // 设置底部边距 var gridBottom = '15%'; } else if (allLegendNames.length <= 12) { // 中等数量图例,使用两行 const halfIndex = Math.ceil(allLegendNames.length / 2); const firstRowLegends = allLegendNames.slice(0, halfIndex); const secondRowLegends = allLegendNames.slice(halfIndex); legendConfig = [ { data: firstRowLegends, type: 'plain', bottom: 100, left: 'center', width: '95%', itemGap: 15, icon: 'circle', itemWidth: 12, itemHeight: 12, textStyle: { fontSize: 12 }, formatter: function (name) { return name; }, selectedMode: true }, { data: secondRowLegends, type: 'plain', bottom: 60, left: 'center', width: '95%', icon: 'circle', itemWidth: 12, itemHeight: 12, textStyle: { fontSize: 12 }, formatter: function (name) { return name; }, selectedMode: true } ]; // 设置底部边距 var gridBottom = '25%'; } else { // 大量图例,使用滚动图例 legendConfig = [{ data: allLegendNames, type: 'scroll', bottom: 100, left: 'center', width: '95%', itemGap: 10, icon: 'circle', itemWidth: 12, itemHeight: 12, textStyle: { fontSize: 12 }, pageButtonItemGap: 5, pageButtonGap: 5, pageButtonPosition: 'end', pageFormatter: '{current}/{total}', pageIcons: { horizontal: [ 'path://M10 12L16 6L10 0L8 2L12 6L8 10L10 12Z', 'path://M6 12L0 6L6 0L8 2L4 6L8 10L6 12Z' ] }, pageIconSize: 15, pageTextStyle: { color: '#333', fontSize: 12 }, selectedMode: true }]; // 设置底部边距 var gridBottom = '15%'; } // 为每个系列添加高亮效果配置 const seriesWithHighlight = safeSeries.map(s => { return { ...s, emphasis: { focus: 'series', // 高亮当前系列 lineStyle: { width: 4 // 高亮时线条加粗 } } } }); const option = { tooltip: { trigger: 'axis', axisPointer: { type: 'line', label: { show: false } }, // 自定义 tooltip 内容,实现高亮效果 formatter: function (params) { // 获取当前悬停的系列索引 const hoverSeriesIndex = params[0].seriesIndex; let html = `<div style="font-weight: bold; margin-bottom: 10px; font-size: 14px;">${params[0].axisValue}</div>`; // 遍历所有参数,构建 tooltip 内容 params.forEach(param => { const isHovered = param.seriesIndex === hoverSeriesIndex; const opacity = isHovered ? '1' : '0.3'; // 悬停项不透明,其他项半透明 const fontWeight = isHovered ? 'bold' : 'normal'; const backgroundColor = isHovered ? 'rgba(0, 0, 0, 0.05)' : 'transparent'; const value = param.value; const metricLabel = getMetricLabel(selectedMetric.value); html += ` <div style="opacity: ${opacity}; font-weight: ${fontWeight}; background-color: ${backgroundColor}; padding: 5px 10px; border-radius: 4px; margin: 2px 0; transition: all 0.2s ease;"> <span style="display: flex; align-items: center; justify-content: space-between;"> <span style="display: flex; align-items: center;"> <span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background: ${param.color}; margin-right: 8px;"></span> <span>${param.seriesName}</span> </span> <span style="font-weight: ${fontWeight}; margin-left: 15px;">${value}</span> </span> </div> `; }); return html; } }, legend: legendConfig, grid: { left: '3%', right: '4%', bottom: gridBottom, containLabel: true }, xAxis: { type: 'category', boundaryGap: false, data: dates || [], axisLabel: { rotate: (dates && dates.length > 8) ? 45 : 0, interval: 0 } }, yAxis: { type: 'value', name: '' }, series: seriesWithHighlight } myChart.setOption(option); // 添加鼠标离开图表区域时取消高亮的效果 myChart.getZr().on('mouseout', () => { myChart.dispatchAction({ type: 'downplay' }); }); } const getMetricLabel = (value) => { return metricOptions.value.find(opt => opt.value === value)?.label || value } const saveChartSettingsToStorage = () => { try { const settings = { selectedMetric: selectedMetric.value, selectedOneSite: selectedOneSite.value, selectedAdType: selectedAdType.value, dateRange: dateRange.value }; localStorage.setItem('adunitChartSettings', JSON.stringify(settings)); } catch (error) { console.error('保存图表设置失败:', error); } }; // 从本地存储加载设置 const loadChartSettingsFromStorage = () => { try { const savedSettings = localStorage.getItem('adunitChartSettings'); if (savedSettings) { const settings = JSON.parse(savedSettings); // 恢复各项设置 if (settings.selectedMetric) { selectedMetric.value = settings.selectedMetric; } if (settings.selectedOneSite) { selectedOneSite.value = settings.selectedOneSite; } if (settings.selectedAdType) { selectedAdType.value = settings.selectedAdType; } if (settings.dateRange) { dateRange.value = settings.dateRange; } return true; } } catch (error) { console.error('加载图表设置失败:', error); } return false; }; const debounce = (func, delay) => { let timeoutId return function (...args) { clearTimeout(timeoutId) timeoutId = setTimeout(() => func.apply(this, args), delay) } } const debouncedHandleFilterChange = debounce(() => { fetchAndUpdateChart() }, 500) const handleFilterChange = () => { saveChartSettingsToStorage(); debouncedHandleFilterChange() } // 生命周期钩子 onMounted(() => { // 初始化图表 initChart() console.log('lineChart mounted, siteOptions:', props.siteOptions) // 初始化图表数据 initializeChart(); }) onBeforeUnmount(() => { if (myChart) { window.removeEventListener('resize', handleResize) myChart.dispose() myChart = null } }) </script> <style scoped> .content { padding: 20px; background-color: #fff; position: relative; border-radius: 15px; margin-top: 20px; } .chart-container { width: 100%; height: 550px; min-height: 800px; /* height: calc(100vh - 200px); */ background-color: #fff; border-radius: 15px; } /* 如果需要调整加载动画的位置 */ .chart-wrapper .loading-overlay { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(255, 255, 255, 0.7); display: flex; justify-content: center; align-items: center; z-index: 9999; border-radius: 15px; } :global(.echarts-tooltip) { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important; border-radius: 6px !important; border: none !important; padding: 12px !important; } :global(.echarts-tooltip .highlighted-item) { background-color: rgba(0, 0, 0, 0.05) !important; border-radius: 4px !important; } </style> 有点bug他默认高亮的第一条的数据,我选择第二条折线的时候,还是高亮的第一条
最新发布
08-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值