From 45c2bd0e88d3b67370e1084eff85da4dd320355f Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 22 Sep 2025 19:32:29 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8D=B3=E6=97=B6=E9=94=80=E5=94=AE=E6=8A=A5?= =?UTF-8?q?=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jsxs/components/ProductRankingTable.vue | 339 ++++ .../report/jsxs/components/SalesChart.vue | 191 +++ .../report/jsxs/components/SummaryCard.vue | 203 +++ .../report/jsxs/components/index.ts | 7 + .../report/jsxs/constants/index.ts | 207 +++ .../supermarketerp/report/jsxs/index.vue | 1480 +++++++++++++++++ .../supermarketerp/report/jsxs/utils/index.ts | 196 +++ 7 files changed, 2623 insertions(+) create mode 100644 src/views/supermarketerp/report/jsxs/components/ProductRankingTable.vue create mode 100644 src/views/supermarketerp/report/jsxs/components/SalesChart.vue create mode 100644 src/views/supermarketerp/report/jsxs/components/SummaryCard.vue create mode 100644 src/views/supermarketerp/report/jsxs/components/index.ts create mode 100644 src/views/supermarketerp/report/jsxs/constants/index.ts create mode 100644 src/views/supermarketerp/report/jsxs/index.vue create mode 100644 src/views/supermarketerp/report/jsxs/utils/index.ts diff --git a/src/views/supermarketerp/report/jsxs/components/ProductRankingTable.vue b/src/views/supermarketerp/report/jsxs/components/ProductRankingTable.vue new file mode 100644 index 0000000..000c37b --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/components/ProductRankingTable.vue @@ -0,0 +1,339 @@ + + + + + diff --git a/src/views/supermarketerp/report/jsxs/components/SalesChart.vue b/src/views/supermarketerp/report/jsxs/components/SalesChart.vue new file mode 100644 index 0000000..075ee95 --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/components/SalesChart.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/views/supermarketerp/report/jsxs/components/SummaryCard.vue b/src/views/supermarketerp/report/jsxs/components/SummaryCard.vue new file mode 100644 index 0000000..03f0f88 --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/components/SummaryCard.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/src/views/supermarketerp/report/jsxs/components/index.ts b/src/views/supermarketerp/report/jsxs/components/index.ts new file mode 100644 index 0000000..051b344 --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/components/index.ts @@ -0,0 +1,7 @@ +/** + * 即时销售报表组件导出 + */ + +export { default as SalesChart } from './SalesChart.vue'; +export { default as SummaryCard } from './SummaryCard.vue'; +export { default as ProductRankingTable } from './ProductRankingTable.vue'; diff --git a/src/views/supermarketerp/report/jsxs/constants/index.ts b/src/views/supermarketerp/report/jsxs/constants/index.ts new file mode 100644 index 0000000..f65fddc --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/constants/index.ts @@ -0,0 +1,207 @@ +/** + * 即时销售报表常量定义 + */ + +/** + * 查询类型枚举 + */ +export enum QueryType { + HOUR = 'hour', + DAY = 'day', + MONTH = 'month', +} + +/** + * 查询类型选项 + */ +export const QUERY_TYPE_OPTIONS = [ + { label: '按小时', value: QueryType.HOUR }, + { label: '按天', value: QueryType.DAY }, + { label: '按月', value: QueryType.MONTH }, +]; + +/** + * 图表类型枚举 + */ +export enum ChartType { + LINE = 'line', + BAR = 'bar', + PIE = 'pie', + AREA = 'area', +} + +/** + * 趋势类型枚举 + */ +export enum TrendType { + UP = 'up', + DOWN = 'down', + STABLE = 'stable', +} + +/** + * 排名类型枚举 + */ +export enum RankingType { + TOP_SELLING = 'topSelling', + SLOW_SELLING = 'slowSelling', +} + +/** + * 时间间隔配置 + */ +export const TIME_INTERVALS = { + [QueryType.HOUR]: 60, // 分钟 + [QueryType.DAY]: 24, // 小时 + [QueryType.MONTH]: 30, // 天 +}; + +/** + * 分页配置 + */ +export const PAGINATION_CONFIG = { + pageSizes: [10, 20, 50, 100], + defaultPageSize: 10, + defaultCurrentPage: 1, +}; + +/** + * 表格列配置 + */ +export const TABLE_COLUMNS = { + PRODUCT_RANKING: [ + { prop: 'ranking', label: '排名', width: 60, align: 'center' }, + { prop: 'productName', label: '商品名称', minWidth: 150 }, + { prop: 'categoryName', label: '分类', width: 100 }, + { prop: 'salesAmount', label: '销售额', width: 120, align: 'right' }, + { prop: 'salesQuantity', label: '销量', width: 100, align: 'right' }, + { prop: 'salesCount', label: '笔数', width: 80, align: 'right' }, + { prop: 'averagePrice', label: '均价', width: 100, align: 'right' }, + { prop: 'marketShare', label: '市场份额', width: 100, align: 'right' }, + ], + TIME_PERIOD_SALES: [ + { prop: 'timePeriodName', label: '时段', width: 120 }, + { prop: 'salesAmount', label: '销售额', width: 120, align: 'right' }, + { prop: 'salesCount', label: '销售笔数', width: 100, align: 'right' }, + { prop: 'salesQuantity', label: '销售数量', width: 100, align: 'right' }, + { prop: 'averageCustomerPrice', label: '平均客单价', width: 120, align: 'right' }, + { prop: 'customerCount', label: '客户数', width: 80, align: 'right' }, + { prop: 'productVarietyCount', label: '商品种类', width: 100, align: 'right' }, + { prop: 'yearOverYearSalesAmount', label: '同比销售额', width: 120, align: 'right' }, + { prop: 'monthOverMonthSalesAmount', label: '环比销售额', width: 120, align: 'right' }, + ], +}; + +/** + * 图表配置 + */ +export const CHART_OPTIONS = { + SALES_TREND: { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + }, + legend: { + data: ['销售额', '销售笔数'], + }, + xAxis: { + type: 'category', + }, + yAxis: [ + { + type: 'value', + name: '销售额(元)', + position: 'left', + }, + { + type: 'value', + name: '销售笔数', + position: 'right', + }, + ], + }, + CUSTOMER_ANALYSIS: { + tooltip: { + trigger: 'item', + formatter: '{a}
{b}: {c}% ({d}%)', + }, + legend: { + orient: 'vertical', + left: 'left', + }, + }, +}; + +/** + * 权限配置 + */ +export const PERMISSIONS = { + VIEW: 'supermarket_realtimeSalesReport_view', + EDIT: 'supermarket_realtimeSalesReport_edit', + EXPORT: 'supermarket_realtimeSalesReport_export', +}; + +/** + * 默认查询条件 + */ +export const DEFAULT_QUERY = { + queryType: QueryType.DAY, + startTime: '', + endTime: '', + storeId: '', + categoryId: '', + productId: '', + topLimit: 10, + current: 1, + size: 10, +}; + +/** + * 刷新间隔配置(毫秒) + */ +export const REFRESH_INTERVALS = { + REALTIME: 60000, // 1分钟 + NORMAL: 300000, // 5分钟 + SLOW: 900000, // 15分钟 +}; + +/** + * 导出配置 + */ +export const EXPORT_CONFIG = { + FORMATS: ['xlsx', 'pdf', 'csv'], + DEFAULT_FORMAT: 'xlsx', + MAX_ROWS: 10000, +}; + +/** + * 缓存配置 + */ +export const CACHE_CONFIG = { + TTL: 300000, // 5分钟 + KEY_PREFIX: 'realtime_sales_report_', +}; + +/** + * 错误消息 + */ +export const ERROR_MESSAGES = { + LOAD_DATA_FAILED: '加载数据失败', + REFRESH_DATA_FAILED: '刷新数据失败', + EXPORT_DATA_FAILED: '导出数据失败', + INVALID_QUERY_PARAMS: '查询参数无效', + NO_DATA: '暂无数据', + NETWORK_ERROR: '网络错误,请检查网络连接', +}; + +/** + * 成功消息 + */ +export const SUCCESS_MESSAGES = { + DATA_LOADED: '数据加载成功', + DATA_REFRESHED: '数据刷新成功', + DATA_EXPORTED: '数据导出成功', + CACHE_REFRESHED: '缓存刷新成功', +}; diff --git a/src/views/supermarketerp/report/jsxs/index.vue b/src/views/supermarketerp/report/jsxs/index.vue new file mode 100644 index 0000000..4a4b72b --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/index.vue @@ -0,0 +1,1480 @@ + + + + + diff --git a/src/views/supermarketerp/report/jsxs/utils/index.ts b/src/views/supermarketerp/report/jsxs/utils/index.ts new file mode 100644 index 0000000..f6895fa --- /dev/null +++ b/src/views/supermarketerp/report/jsxs/utils/index.ts @@ -0,0 +1,196 @@ +/** + * 即时销售报表工具函数 + */ + +/** + * 格式化货币 + * @param value 数值 + * @param currency 货币类型 + * @returns 格式化后的货币字符串 + */ +export const formatCurrency = (value: number, currency: string = 'CNY'): string => { + return new Intl.NumberFormat('zh-CN', { + style: 'currency', + currency, + }).format(value); +}; + +/** + * 格式化数字 + * @param value 数值 + * @returns 格式化后的数字字符串 + */ +export const formatNumber = (value: number): string => { + return new Intl.NumberFormat('zh-CN').format(value); +}; + +/** + * 格式化百分比 + * @param value 数值 + * @param decimals 小数位数 + * @returns 格式化后的百分比字符串 + */ +export const formatPercent = (value: number, decimals: number = 2): string => { + return `${(value * 100).toFixed(decimals)}%`; +}; + +/** + * 格式化日期时间 + * @param date 日期 + * @param format 格式 + * @returns 格式化后的日期字符串 + */ +export const formatDateTime = (date: Date | string, format: string = 'YYYY-MM-DD HH:mm:ss'): string => { + const d = new Date(date); + const year = d.getFullYear(); + const month = String(d.getMonth() + 1).padStart(2, '0'); + const day = String(d.getDate()).padStart(2, '0'); + const hours = String(d.getHours()).padStart(2, '0'); + const minutes = String(d.getMinutes()).padStart(2, '0'); + const seconds = String(d.getSeconds()).padStart(2, '0'); + + return format + .replace('YYYY', String(year)) + .replace('MM', month) + .replace('DD', day) + .replace('HH', hours) + .replace('mm', minutes) + .replace('ss', seconds); +}; + +/** + * 获取趋势样式类 + * @param rate 变化率 + * @returns 样式类名 + */ +export const getTrendClass = (rate: number): string => { + if (rate > 0) return 'trend-up'; + if (rate < 0) return 'trend-down'; + return 'trend-stable'; +}; + +/** + * 获取排名标签类型 + * @param ranking 排名 + * @returns 标签类型 + */ +export const getRankingTagType = (ranking: number): 'danger' | 'warning' | 'primary' => { + if (ranking <= 3) return 'danger'; + if (ranking <= 10) return 'warning'; + return 'primary'; +}; + +/** + * 计算增长率 + * @param current 当前值 + * @param previous 之前值 + * @returns 增长率 + */ +export const calculateGrowthRate = (current: number, previous: number): number => { + if (previous === 0) return current > 0 ? 100 : 0; + return ((current - previous) / previous) * 100; +}; + +/** + * 生成随机数据(用于演示) + * @param min 最小值 + * @param max 最大值 + * @returns 随机数 + */ +export const generateRandomData = (min: number, max: number): number => { + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +/** + * 生成时间序列数据 + * @param startTime 开始时间 + * @param endTime 结束时间 + * @param interval 间隔(分钟) + * @returns 时间序列数组 + */ +export const generateTimeSeries = (startTime: Date, endTime: Date, interval: number = 60): string[] => { + const times: string[] = []; + const current = new Date(startTime); + + while (current <= endTime) { + times.push(formatDateTime(current, 'HH:mm')); + current.setMinutes(current.getMinutes() + interval); + } + + return times; +}; + +/** + * 获取默认查询时间范围 + * @param type 查询类型 + * @returns 查询时间范围 + */ +export const getDefaultTimeRange = (type: 'hour' | 'day' | 'month' = 'day') => { + const now = new Date(); + const startTime = new Date(now); + + switch (type) { + case 'hour': + startTime.setHours(now.getHours() - 24); + break; + case 'day': + startTime.setDate(now.getDate() - 7); + break; + case 'month': + startTime.setMonth(now.getMonth() - 6); + break; + } + + return { + startTime: formatDateTime(startTime), + endTime: formatDateTime(now), + }; +}; + +/** + * 颜色配置 + */ +export const CHART_COLORS = { + primary: '#409EFF', + success: '#67C23A', + warning: '#E6A23C', + danger: '#F56C6C', + info: '#909399', + gradient: { + blue: ['#667eea', '#764ba2'], + green: ['#43e97b', '#38f9d7'], + orange: ['#f093fb', '#f5576c'], + purple: ['#4facfe', '#00f2fe'], + }, +}; + +/** + * 图表配置 + */ +export const CHART_CONFIG = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + }, + }, + grid: { + left: '3%', + right: '4%', + bottom: '3%', + containLabel: true, + }, + legend: { + data: [], + }, +}; + +/** + * 表格配置 + */ +export const TABLE_CONFIG = { + stripe: true, + border: false, + size: 'default' as const, + showOverflowTooltip: true, +};