Commit 0cadb29f by Leo Zheng

实现了图二左侧和中部的数据接口

parent d158dda8
import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft";
import paramChecker from "../../../../util/paramChecker";
export default class ecommerceRankingStrategy extends abstractDataStrategyLeft {
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'metric', 'revenue', 'customerPrice');
return this.processData(params.query['metric']);
}
processData(metric: string): any {
const generateRandomValues = (count: number) => {
return Array.from({ length: count }, () => Math.floor(Math.random() * 5000) + 1000);
};
const shopData = generateRandomValues(4);
const productData = generateRandomValues(4);
shopData.sort((a, b) => b - a);
productData.sort((a, b) => b - a);
const formatData = (data: number[], prefix: string) => {
return data.map((value, index) => ({
name: `${prefix}${index + 1}`,
value: `${value}元`
}));
};
const result = {
shops: formatData(shopData, '商家'),
products: formatData(productData, '商品')
};
return result;
}
}
import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft";
import paramChecker from "../../../../util/paramChecker";
import excelSerialToJSDate from "../../../../util/excelDateToJSDate";
export default class revenueAnalysisStrategy extends abstractDataStrategyLeft {
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表';
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'timeFrame', '12month', '3year');
const data = this.extractor.getData(revenueAnalysisStrategy.FILENAME, revenueAnalysisStrategy.SHEETNAME);
return this.processData(data, params.query['timeFrame']);
}
processData(data: any, timeFrame: string): any {
const currentDate = new Date();
let startDate = new Date();
if (timeFrame === '12month') {
startDate.setFullYear(currentDate.getFullYear() - 1);
} else if (timeFrame === '3year') {
startDate.setFullYear(currentDate.getFullYear() - 3);
}
const monthlyRevenue: { [key: string]: { ticket: number, ecommerce: number } } = {};
let tempDate = new Date(startDate);
while (tempDate <= currentDate) {
const formattedMonth = `${tempDate.getFullYear().toString().slice(2)}/${(tempDate.getMonth() + 1).toString().padStart(2, '0')}`;
monthlyRevenue[formattedMonth] = { ticket: 0, ecommerce: Math.floor(Math.random() * 50000) + 50000 }; // Random e-commerce revenue
tempDate.setMonth(tempDate.getMonth() + 1);
}
data.forEach(row => {
const rowDate = excelSerialToJSDate(row['游玩时间']);
const rowMonth = `${rowDate.getFullYear().toString().slice(2)}/${(rowDate.getMonth() + 1).toString().padStart(2, '0')}`;
if (rowDate >= startDate && rowDate <= currentDate) {
if (monthlyRevenue[rowMonth]) {
monthlyRevenue[rowMonth].ticket += row['订单金额'];
} else {
monthlyRevenue[rowMonth] = { ticket: row['订单金额'], ecommerce: Math.floor(Math.random() * 50000) + 50000 };
}
}
});
const result = [];
for (let [key, value] of Object.entries(monthlyRevenue)) {
if (value.ticket === 0) {
value.ticket = Math.floor(Math.random() * 100000) + 10000; // Random ticket revenue if zero
}
if (value.ecommerce === 0) {
value.ecommerce = Math.floor(Math.random() * 50000) + 50000; // Random e-commerce revenue if zero
}
result.push({
key,
ticket: value.ticket,
ecommerce: value.ecommerce
});
}
return result;
}
}
...@@ -44,7 +44,7 @@ export default class monthlyVisitorCountStrategy extends abstractDataStrategyLef ...@@ -44,7 +44,7 @@ export default class monthlyVisitorCountStrategy extends abstractDataStrategyLef
} }
result.push({ result.push({
key: key, key: key,
value: value.toString() value: value
}); });
} }
......
import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft";
import paramChecker from "../../../../util/paramChecker";
import excelSerialToJSDate from "../../../../util/excelDateToJSDate";
export default class paymentMethodAnalysisStrategy extends abstractDataStrategyLeft {
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表';
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'category', 'total', 'group', 'solo');
paramChecker.checkDiscreteParam(params, 'timeFrame', '12month', '3year');
const data = this.extractor.getData(paymentMethodAnalysisStrategy.FILENAME, paymentMethodAnalysisStrategy.SHEETNAME);
return this.processData(data, params.query['category'], params.query['timeFrame']);
}
processData(data: any, category: string, timeFrame: string): any {
const currentDate = new Date();
let startDate = new Date();
if (timeFrame === '12month') {
startDate.setFullYear(currentDate.getFullYear() - 1);
} else if (timeFrame === '3year') {
startDate.setFullYear(currentDate.getFullYear() - 3);
}
const paymentMethods = {
'现金支付': 0,
'移动支付': 0,
'第三方支付': 0,
'电子支付': 0
};
const totalAmount = {
'现金支付': 0,
'移动支付': 0,
'第三方支付': 0,
'电子支付': 0
};
data.forEach(row => {
const rowDate = excelSerialToJSDate(row['游玩时间']);
if (rowDate >= startDate && rowDate <= currentDate) {
if (category === 'total' ||
(category === 'group' && row['订单游客类型'] === '团队') ||
(category === 'solo' && row['订单游客类型'] === '散客')) {
const paymentType = row['支付类型'];
const amount = row['订单金额'];
if (paymentMethods[paymentType] !== undefined) {
paymentMethods[paymentType] += 1;
totalAmount[paymentType] += amount;
}
}
}
});
let totalTransactions = 0;
Object.keys(paymentMethods).forEach(key => {
if (paymentMethods[key] === 0) {
paymentMethods[key] = Math.floor(Math.random() * 100) + 1;
}
if (totalAmount[key] === 0) {
totalAmount[key] = Math.floor(Math.random() * 50000) + 5000;
}
totalTransactions += paymentMethods[key];
});
const result = Object.entries(paymentMethods).map(([key, count]) => {
const amount = totalAmount[key];
const percentage = totalTransactions > 0 ? ((count / totalTransactions) * 100).toFixed(2) : '0.00';
return {
paymentMethod: key,
amount: parseFloat(amount.toFixed(2)),
percentage: `${percentage}%`
};
});
return result;
}
}
import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft"; import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft";
import paramChecker from "../../../../util/paramChecker";
import excelSerialToJSDate from "../../../../util/excelDateToJSDate";
export default class sightVisitorRankStrategy extends abstractDataStrategyLeft { export default class sightVisitorRankStrategy extends abstractDataStrategyLeft {
static readonly FILENAME = '票务系统.xlsx'; static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表'; static readonly SHEETNAME = '票务系统-订单主表';
execute(params?: any): any { execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'timeFrame', '12month', '3year');
const data = this.extractor.getData(sightVisitorRankStrategy.FILENAME, sightVisitorRankStrategy.SHEETNAME); const data = this.extractor.getData(sightVisitorRankStrategy.FILENAME, sightVisitorRankStrategy.SHEETNAME);
return this.processData(data, params.query['timeFrame']); return this.processData(data);
} }
processData(data: any, timeFrame: string): any { processData(data: any): any {
const currentDate = new Date();
let startDate = new Date();
if (timeFrame === '12month') {
startDate.setFullYear(currentDate.getFullYear() - 1);
} else if (timeFrame === '3year') {
startDate.setFullYear(currentDate.getFullYear() - 3);
}
const sightCounts: { [key: string]: number } = {}; const sightCounts: { [key: string]: number } = {};
data.forEach(row => { data.forEach(row => {
const rowDate = excelSerialToJSDate(row['游玩时间']); const sightName = row['景点名称'];
if (rowDate >= startDate && rowDate <= currentDate) { if (!sightCounts[sightName]) {
const sightName = row['景点名称']; sightCounts[sightName] = 0;
if (!sightCounts[sightName]) {
sightCounts[sightName] = 0;
}
sightCounts[sightName] += 1;
} }
sightCounts[sightName] += 1;
}); });
const sortedSights = Object.entries(sightCounts) const sortedSights = Object.entries(sightCounts)
.sort(([, a], [, b]) => b - a) .sort(([, a], [, b]) => b - a)
.map(([key, value]) => ({ key, value: value.toString() })); .map(([key, value]) => ({ key, value: value }));
return sortedSights; return sortedSights;
} }
......
import {abstractDataStrategyLeft} from "./abstractDataStrategyLeft";
import paramChecker from "../../../../util/paramChecker";
export default class ticketSalesAnalysisStrategy extends abstractDataStrategyLeft {
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表';
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'metric', 'amount', 'ticketCount');
const data = this.extractor.getData(ticketSalesAnalysisStrategy.FILENAME, ticketSalesAnalysisStrategy.SHEETNAME);
return this.processData(data, params.query['metric']);
}
processData(data: any, metric: string): any {
let totalSales = 0;
let totalCancellations = 0;
let conversionRate = 0;
let totalCheckedIn = 0;
let totalTickets = 0;
let cancellationCount = 0;
data.forEach(row => {
const amount = row['订单金额'];
const isCancelled = row['是否退票'] === '是';
const isCheckedIn = row['是否检票'] === '是';
totalSales += amount;
if (isCancelled) {
totalCancellations += amount;
cancellationCount += 1;
}
if (isCheckedIn) {
totalCheckedIn += amount;
}
totalTickets += 1;
});
if (totalSales > 0) {
conversionRate = (totalCheckedIn / totalSales) * 100;
}
if (metric === 'amount') {
return {
totalSales: parseFloat(totalSales.toFixed(2)),
totalCheckedIn: parseFloat(totalCheckedIn.toFixed(2)),
totalCancellations: parseFloat(totalCancellations.toFixed(2)),
conversionRate: parseFloat(conversionRate.toFixed(2))
};
} else {
return {
totalSales: totalTickets,
totalCheckedIn: totalTickets,
totalCancellations: cancellationCount,
conversionRate: parseFloat(conversionRate.toFixed(2))
};
}
}
}
/**
* abstractDataStrategyLeft.ts
* 该文件定义了一个抽象类,用于左侧数据策略的基础实现。
*/
import { dataStrategy } from "../../../dataStrategy";
import { DataExtractor } from "../../../../util/dataExtractor";
/**
* 抽象数据策略左侧类,所有具体策略类都需要继承该抽象类。
*/
export abstract class abstractDataStrategyMid implements dataStrategy {
// 实例化数据提取器
extractor = DataExtractor.getInstance();
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表';
/**
* 执行策略的方法,具体实现由子类提供。
* @param params - 可选参数。
*/
abstract execute(params?: any): any;
abstract processData(...param): any;
}
import paramChecker from "../../../../util/paramChecker";
import excelSerialToJSDate from "../../../../util/excelDateToJSDate";
import {abstractDataStrategyMid} from "./abstractDataStrategyMid";
export default class leftSideMapDataStrategy extends abstractDataStrategyMid{
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'year', 'thisyear', 'lastyear');
const data = this.extractor.getData(leftSideMapDataStrategy.FILENAME, leftSideMapDataStrategy.SHEETNAME);
return this.processData(data, params.query['year']);
}
processData(data: any, year: string): any {
const currentYear = new Date().getFullYear();
const targetYear = year === 'thisyear' ? currentYear : currentYear - 1;
const filteredData = data.filter(row => {
const playTime = excelSerialToJSDate(row['游玩时间']);
return playTime.getFullYear() === targetYear;
});
const totalVisitors = filteredData.length;
const totalTicketRevenue = filteredData.reduce((sum, row) => sum + row['订单金额'], 0);
const totalEcommerceRevenue = Math.floor(Math.random() * 1000000) + 100000; // Random e-commerce revenue
const visitorIncreaseRate = (Math.random() * 5 - 2.5).toFixed(2); // Random rate between -2.5% and +2.5%
const ticketRevenueIncreaseRate = (Math.random() * 5 - 2.5).toFixed(2); // Random rate between -2.5% and +2.5%
const ecommerceRevenueIncreaseRate = (Math.random() * 5 - 2.5).toFixed(2); // Random rate between -2.5% and +2.5%
const randomNonZeroValue = () => Math.floor(Math.random() * 10000) + 100;
const result = {
totalReception: {
total: totalVisitors === 0 ? randomNonZeroValue() : totalVisitors,
IncreaseRate: `${visitorIncreaseRate}%`,
cumulative: `${(Math.random() * 7000000 + 100000).toFixed(0)}人次`
},
ticketRevenue: {
total: totalTicketRevenue === 0 ? (randomNonZeroValue() / 100).toFixed(2) : (totalTicketRevenue / 10000).toFixed(2),
IncreaseRate: `${ticketRevenueIncreaseRate}%`,
cumulative: `${(Math.random() * 7000000 + 100000).toFixed(2)}万元`
},
ecommerceRevenue: {
total: totalEcommerceRevenue === 0 ? (randomNonZeroValue() / 100).toFixed(2) : (totalEcommerceRevenue / 10000).toFixed(2),
IncreaseRate: `${ecommerceRevenueIncreaseRate}%`,
cumulative: `${(Math.random() * 7000000 + 100000).toFixed(2)}万元`
}
};
return result;
}
}
import paramChecker from "../../../../util/paramChecker";
import {abstractDataStrategyMid} from "./abstractDataStrategyMid";
import excelSerialToJSDate from "../../../../util/excelDateToJSDate";
export default class rightSideMapDataStrategy extends abstractDataStrategyMid {
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-订单主表';
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'year', 'thisyear', 'lastyear');
paramChecker.checkDiscreteParam(params, 'sight', '安丰塘(芍跛)', '清真寺', '孔庙', '汐熙阁', '安徽第一面党旗纪念园', '八公山森林公园', '寿州古城游客中心', '珍珠泉', '随缘堂(周易)');
const data = this.extractor.getData(rightSideMapDataStrategy.FILENAME, rightSideMapDataStrategy.SHEETNAME);
return this.processData(data, params.query['year'], params.query['sight']);
}
processData(data: any, year: string, sight: string): any {
const currentYear = new Date().getFullYear();
const targetYear = year === 'thisyear' ? currentYear : currentYear - 1;
const filteredData = data.filter(row => {
const playTime = excelSerialToJSDate(row['游玩时间']);
return playTime.getFullYear() === targetYear && row['景点名称'] === sight;
});
const totalVisitors = filteredData.length;
const totalTicketRevenue = filteredData.reduce((sum, row) => sum + row['订单金额'], 0);
const totalTicketsSold = filteredData.reduce((sum, row) => sum + (row['订单金额'] > 0 ? 1 : 0), 0); // Assuming each non-zero order amount represents a sold ticket
const randomNonZeroValue = () => Math.floor(Math.random() * 10000) + 100;
const monthlyVisitorTrend = Array.from({ length: 12 }, (_, index) => ({
month: `${targetYear % 100}/${(index + 1).toString().padStart(2, '0')}`,
visitors: randomNonZeroValue()
}));
const result = {
totalVisitors: totalVisitors === 0 ? randomNonZeroValue() : totalVisitors,
totalTicketRevenue: totalTicketRevenue === 0 ? randomNonZeroValue() : totalTicketRevenue,
totalTicketsSold: totalTicketsSold === 0 ? randomNonZeroValue() : totalTicketsSold,
description: '景点介绍 - 随机生成的景点介绍。',
rating: '国家级4A级景区',
tags: ['标签一', '标签二', '标签三', '标签四'],
monthlyVisitorTrend
};
return result;
}
}
import {dataStrategy} from "../../../dataStrategy";
import {DataExtractor} from "../../../../util/dataExtractor";
export abstract class abstractCustomerProfileStrategy implements dataStrategy {
// 实例化数据提取器
extractor = DataExtractor.getInstance();
static readonly FILENAME = '票务系统.xlsx';
static readonly SHEETNAME = '票务系统-游客门票表';
/**
* 执行策略的方法,具体实现由子类提供。
* @param params - 可选参数。
*/
abstract execute(params?: any): any;
abstract processData(...param): any;
}
\ No newline at end of file
import {abstractCustomerProfileStrategy} from "./abstractCustomerProfileStrategy";
import paramChecker from "../../../../util/paramChecker";
export default class visitorGenderProfileStrategy extends abstractCustomerProfileStrategy {
execute(params?: any): any {
paramChecker.checkDiscreteParam(params, 'type', 'ecommerce', 'ticket');
const data = this.extractor.getData(visitorGenderProfileStrategy.FILENAME, visitorGenderProfileStrategy.SHEETNAME);
return this.processData(data, params.query['type']);
}
processData(data: any, type: string): any {
}
}
\ No newline at end of file
...@@ -27,6 +27,13 @@ import { eventProcessingTimeStrategy } from "./map1/strategies/right/eventProces ...@@ -27,6 +27,13 @@ import { eventProcessingTimeStrategy } from "./map1/strategies/right/eventProces
import {allEventDataStrategy} from "./map1/strategies/middle/eventDataStrategy"; import {allEventDataStrategy} from "./map1/strategies/middle/eventDataStrategy";
import monthlyVisitorCountStrategy from "./map2/strategies/left/monthlyVisitorCountStrategy"; import monthlyVisitorCountStrategy from "./map2/strategies/left/monthlyVisitorCountStrategy";
import sightVisitorRankStrategy from "./map2/strategies/left/sightVisitorRankStrategy"; import sightVisitorRankStrategy from "./map2/strategies/left/sightVisitorRankStrategy";
import monthlyRevenueStrategy from "./map2/strategies/left/monthlyRevenueStrategy";
import ticketRevenueAnalysisStrategy from "./map2/strategies/left/ticketRevenueAnalysisStrategy";
import paymentMethodAnalysisStrategy from "./map2/strategies/left/paymentMethodAnalysisStrategy";
import ecommerceRankingStrategy from "./map2/strategies/left/ecommerceRankingStrategy";
import mapDataStrategy from "./map2/strategies/middle/leftSideMapDataStrategy";
import leftSideMapDataStrategy from "./map2/strategies/middle/leftSideMapDataStrategy";
import rightSideMapDataStrategy from "./map2/strategies/middle/rightSideMapDataStrategy";
/** /**
* 策略工厂类,用于创建和管理各种数据策略。 * 策略工厂类,用于创建和管理各种数据策略。
...@@ -56,8 +63,18 @@ export class strategyFactory { ...@@ -56,8 +63,18 @@ export class strategyFactory {
'getEventProcessingTime': eventProcessingTimeStrategy, 'getEventProcessingTime': eventProcessingTimeStrategy,
// map2 // map2
'monthlyVisitor': monthlyVisitorCountStrategy,
'sightVisitorRank': sightVisitorRankStrategy // left
'monthlyVisitorCount': monthlyVisitorCountStrategy,
'sightVisitorRank': sightVisitorRankStrategy,
'monthlyRevenue': monthlyRevenueStrategy,
'ticketRevenueAnalysis': ticketRevenueAnalysisStrategy,
'paymentMethodAnalysis': paymentMethodAnalysisStrategy,
'ecommerceRanking': ecommerceRankingStrategy,
// mid
'leftSideMapData': leftSideMapDataStrategy,
'rightSideMapData': rightSideMapDataStrategy,
}; };
/** /**
......
...@@ -30,6 +30,26 @@ export function httpErrorHandler(err, req, res, next) { ...@@ -30,6 +30,26 @@ export function httpErrorHandler(err, req, res, next) {
res.success({success: false, msg: err.message, code: 507}); res.success({success: false, msg: err.message, code: 507});
next(); next();
} }
else if (err.message == 'timeFrame parameter is required.') {
res.success({success: false, msg: err.message, code: 508});
next();
}
else if (err.message == 'metric parameter is required.') {
res.success({success: false, msg: err.message, code: 509});
next();
}
else if (err.message == 'category parameter is required.') {
res.success({success: false, msg: err.message, code: 510});
next();
}
else if (err.message == 'sight parameter must be one of 安丰塘(芍跛), 清真寺, 孔庙, 汐熙阁, 安徽第一面党旗纪念园, 八公山森林公园, 寿州古城游客中心, 珍珠泉, 随缘堂(周易).') {
res.success({success: false, msg: err.message, code: 511});
next();
}
else if (err.message == 'sight parameter is required.') {
res.success({success: false, msg: err.message, code: 512});
next();
}
else { else {
res.success({success:false, msg: err.message, code: 500}); res.success({success:false, msg: err.message, code: 500});
next(); next();
......
...@@ -2,7 +2,10 @@ import * as asyncHandler from 'express-async-handler'; ...@@ -2,7 +2,10 @@ import * as asyncHandler from 'express-async-handler';
import * as szgcBiz from '../../biz/getData'; import * as szgcBiz from '../../biz/getData';
export function setMap2LeftRoutes(httpServer) { export function setMap2LeftRoutes(httpServer) {
httpServer.get('/szgc/getdata/monthlyVisitorCount', asyncHandler((req, res) => szgcBiz.getData(req, res, 'monthlyVisitor'))); httpServer.get('/szgc/getdata/monthlyVisitorCount', asyncHandler((req, res) => szgcBiz.getData(req, res, 'monthlyVisitorCount')));
httpServer.get('/szgc/getdate/sightVisitorRank', asyncHandler((req, res) => szgcBiz.getData(req, res, 'sightVisitorRank'))); httpServer.get('/szgc/getdata/sightVisitorRank', asyncHandler((req, res) => szgcBiz.getData(req, res, 'sightVisitorRank')));
httpServer.get('/szgc/getdata/monthlyRevenue', asyncHandler((req, res) => szgcBiz.getData(req, res, 'monthlyRevenue')));
httpServer.get('/szgc/getdata/ticketRevenueAnalysis', asyncHandler((req, res) => szgcBiz.getData(req, res, 'ticketRevenueAnalysis')));
httpServer.get('/szgc/getdata/paymentMethodAnalysis', asyncHandler((req, res) => szgcBiz.getData(req, res, 'paymentMethodAnalysis')));
httpServer.get('/szgc/getdata/ecommerceRanking', asyncHandler((req, res) => szgcBiz.getData(req, res, 'ecommerceRanking')));
} }
\ No newline at end of file
...@@ -2,7 +2,8 @@ import * as asyncHandler from 'express-async-handler'; ...@@ -2,7 +2,8 @@ import * as asyncHandler from 'express-async-handler';
import * as szgcBiz from '../../biz/getData'; import * as szgcBiz from '../../biz/getData';
export function setMap2MiddleRoutes(httpServer) { export function setMap2MiddleRoutes(httpServer) {
httpServer.get('/szgc/getdata/getCurrentEventCount', asyncHandler((req, res) => szgcBiz.getData(req, res, 'getCurrentEventCount'))); httpServer.get('/szgc/getdata/leftSideMapData', asyncHandler((req, res) => szgcBiz.getData(req, res, 'leftSideMapData')));
httpServer.get('/szgc/getdata/rightSideMapData', asyncHandler((req, res) => szgcBiz.getData(req, res, 'rightSideMapData')));
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment