录音时长趋势图
This commit is contained in:
@@ -55,9 +55,13 @@
|
|||||||
<!-- 图表区域 -->
|
<!-- 图表区域 -->
|
||||||
<view class="chart-section">
|
<view class="chart-section">
|
||||||
<text class="section-title">时长趋势图</text>
|
<text class="section-title">时长趋势图</text>
|
||||||
<view class="chart-placeholder">
|
<view class="chart-container">
|
||||||
<text class="placeholder-text">📊 图表区域</text>
|
<canvas
|
||||||
<text class="placeholder-desc">此处将显示录音时长趋势图表</text>
|
canvas-id="trendChart"
|
||||||
|
id="trendChart"
|
||||||
|
class="chart-canvas"
|
||||||
|
:style="{ width: chartWidth + 'px', height: chartHeight + 'px' }"
|
||||||
|
></canvas>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -140,12 +144,17 @@ export default {
|
|||||||
totalDuration: '0 分钟',
|
totalDuration: '0 分钟',
|
||||||
recordingCount: '0',
|
recordingCount: '0',
|
||||||
dataList: [],
|
dataList: [],
|
||||||
loading: false
|
loading: false,
|
||||||
|
chartWidth: 0,
|
||||||
|
chartHeight: 300,
|
||||||
|
chartData: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
// 页面加载时获取数据
|
// 页面加载时获取数据
|
||||||
this.loadData();
|
this.loadData();
|
||||||
|
// 初始化图表尺寸
|
||||||
|
this.initChartSize();
|
||||||
},
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
// 页面渲染完成后,查询实际导航栏高度并更新位置
|
// 页面渲染完成后,查询实际导航栏高度并更新位置
|
||||||
@@ -469,6 +478,165 @@ export default {
|
|||||||
dataListCount: this.dataList.length,
|
dataListCount: this.dataList.length,
|
||||||
statisticsListLength: statisticsList.length
|
statisticsListLength: statisticsList.length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 准备图表数据并绘制
|
||||||
|
this.prepareChartData(statisticsList);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化图表尺寸
|
||||||
|
*/
|
||||||
|
initChartSize() {
|
||||||
|
// 获取屏幕宽度
|
||||||
|
const systemInfo = uni.getSystemInfoSync();
|
||||||
|
const screenWidth = systemInfo.windowWidth || 375;
|
||||||
|
// 图表宽度 = 屏幕宽度 - 左右边距(60rpx = 30px) - 卡片内边距(60rpx = 30px)
|
||||||
|
this.chartWidth = screenWidth - 60;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 准备图表数据
|
||||||
|
*/
|
||||||
|
prepareChartData(statisticsList) {
|
||||||
|
if (!statisticsList || statisticsList.length === 0) {
|
||||||
|
this.chartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按日期正序排列(从旧到新,用于图表显示)
|
||||||
|
const sortedList = [...statisticsList].sort((a, b) => {
|
||||||
|
const dateA = new Date(a.statisticsDate);
|
||||||
|
const dateB = new Date(b.statisticsDate);
|
||||||
|
return dateA - dateB;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 提取日期和时长数据
|
||||||
|
this.chartData = sortedList.map(item => ({
|
||||||
|
date: item.statisticsDate,
|
||||||
|
duration: item.salesTotalDuration || 0
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 延迟绘制,确保canvas已渲染
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawChart();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制趋势图
|
||||||
|
*/
|
||||||
|
drawChart() {
|
||||||
|
if (!this.chartData || this.chartData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = uni.createCanvasContext('trendChart', this);
|
||||||
|
const width = this.chartWidth;
|
||||||
|
const height = this.chartHeight;
|
||||||
|
|
||||||
|
// 图表边距
|
||||||
|
const padding = {
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
bottom: 50,
|
||||||
|
left: 50
|
||||||
|
};
|
||||||
|
|
||||||
|
// 绘图区域
|
||||||
|
const chartWidth = width - padding.left - padding.right;
|
||||||
|
const chartHeight = height - padding.top - padding.bottom;
|
||||||
|
|
||||||
|
// 清空画布
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// 计算数据范围
|
||||||
|
const durations = this.chartData.map(item => item.duration);
|
||||||
|
const maxDuration = Math.max(...durations, 1);
|
||||||
|
const minDuration = Math.min(...durations, 0);
|
||||||
|
const range = maxDuration - minDuration || 1;
|
||||||
|
|
||||||
|
// 计算Y轴刻度(向上取整到合适的值)
|
||||||
|
const yMax = Math.ceil(maxDuration / 100) * 100;
|
||||||
|
const yStep = yMax / 5;
|
||||||
|
|
||||||
|
// 绘制Y轴刻度和标签
|
||||||
|
ctx.setFillStyle('#666666');
|
||||||
|
ctx.setFontSize(10);
|
||||||
|
for (let i = 0; i <= 5; i++) {
|
||||||
|
const y = padding.top + chartHeight - (chartHeight / 5) * i;
|
||||||
|
const value = Math.round(yStep * i);
|
||||||
|
ctx.fillText(value.toString(), 5, y + 4);
|
||||||
|
|
||||||
|
// 绘制网格线
|
||||||
|
ctx.setStrokeStyle('#e0e0e0');
|
||||||
|
ctx.setLineWidth(1);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, y);
|
||||||
|
ctx.lineTo(padding.left + chartWidth, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制X轴日期标签
|
||||||
|
const dataCount = this.chartData.length;
|
||||||
|
const xStep = chartWidth / Math.max(dataCount - 1, 1);
|
||||||
|
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const date = new Date(item.date);
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
const label = `${month}/${day}`;
|
||||||
|
|
||||||
|
ctx.setFillStyle('#666666');
|
||||||
|
ctx.setFontSize(10);
|
||||||
|
ctx.fillText(label, x - 15, height - padding.bottom + 20);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绘制折线
|
||||||
|
ctx.setStrokeStyle('#007aff');
|
||||||
|
ctx.setLineWidth(2);
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const y = padding.top + chartHeight - ((item.duration / yMax) * chartHeight);
|
||||||
|
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// 绘制数据点
|
||||||
|
ctx.setFillStyle('#007aff');
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const y = padding.top + chartHeight - ((item.duration / yMax) * chartHeight);
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 4, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 绘制坐标轴
|
||||||
|
ctx.setStrokeStyle('#333333');
|
||||||
|
ctx.setLineWidth(1);
|
||||||
|
|
||||||
|
// Y轴
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, padding.top);
|
||||||
|
ctx.lineTo(padding.left, padding.top + chartHeight);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// X轴
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, padding.top + chartHeight);
|
||||||
|
ctx.lineTo(padding.left + chartWidth, padding.top + chartHeight);
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
ctx.draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -694,21 +862,15 @@ export default {
|
|||||||
border-bottom: 1rpx solid #e9ecef;
|
border-bottom: 1rpx solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chart-placeholder {
|
.chart-container {
|
||||||
padding: 80rpx 30rpx;
|
padding: 30rpx;
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeholder-text {
|
.chart-canvas {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 48rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-desc {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666666;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-item {
|
.list-item {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,7 @@ function getSystemInfo() {
|
|||||||
windowHeight: windowInfo.windowHeight
|
windowHeight: windowInfo.windowHeight
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
common_vendor.index.__f__("warn", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:99", "[RecordingDuration] Failed to use wx.getWindowInfo, fallback to getSystemInfoSync:", e);
|
common_vendor.index.__f__("warn", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:103", "[RecordingDuration] Failed to use wx.getWindowInfo, fallback to getSystemInfoSync:", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -23,7 +23,7 @@ function getSystemInfo() {
|
|||||||
windowHeight: systemInfo.windowHeight
|
windowHeight: systemInfo.windowHeight
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:113", "[RecordingDuration] Failed to get system info:", e);
|
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:117", "[RecordingDuration] Failed to get system info:", e);
|
||||||
return {
|
return {
|
||||||
statusBarHeight: 20,
|
statusBarHeight: 20,
|
||||||
windowWidth: 375,
|
windowWidth: 375,
|
||||||
@@ -54,11 +54,15 @@ const _sfc_main = {
|
|||||||
totalDuration: "0 分钟",
|
totalDuration: "0 分钟",
|
||||||
recordingCount: "0",
|
recordingCount: "0",
|
||||||
dataList: [],
|
dataList: [],
|
||||||
loading: false
|
loading: false,
|
||||||
|
chartWidth: 0,
|
||||||
|
chartHeight: 300,
|
||||||
|
chartData: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
onLoad() {
|
onLoad() {
|
||||||
this.loadData();
|
this.loadData();
|
||||||
|
this.initChartSize();
|
||||||
},
|
},
|
||||||
onReady() {
|
onReady() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -71,7 +75,7 @@ const _sfc_main = {
|
|||||||
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
||||||
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
||||||
this.$set(this, "contentTop", adjustedHeight + "rpx");
|
this.$set(this, "contentTop", adjustedHeight + "rpx");
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:166", "[RecordingDuration] 使用实际导航栏高度:", actualHeight, "px =", actualHeightRpx, "rpx, 总高度:", totalHeight, "rpx, 调整后:", adjustedHeight, "rpx");
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:175", "[RecordingDuration] 使用实际导航栏高度:", actualHeight, "px =", actualHeightRpx, "rpx, 总高度:", totalHeight, "rpx, 调整后:", adjustedHeight, "rpx");
|
||||||
} else {
|
} else {
|
||||||
const systemInfo = getSystemInfo();
|
const systemInfo = getSystemInfo();
|
||||||
const statusBarHeight = systemInfo.statusBarHeight;
|
const statusBarHeight = systemInfo.statusBarHeight;
|
||||||
@@ -90,7 +94,7 @@ const _sfc_main = {
|
|||||||
const tabbarHeightRpx = tabbarHeight * 2;
|
const tabbarHeightRpx = tabbarHeight * 2;
|
||||||
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
||||||
this.$set(this, "contentBottom", adjustedBottom + "rpx");
|
this.$set(this, "contentBottom", adjustedBottom + "rpx");
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:190", "[RecordingDuration] 使用实际tabbar高度:", tabbarHeight, "px =", tabbarHeightRpx, "rpx, 调整后:", adjustedBottom, "rpx");
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:199", "[RecordingDuration] 使用实际tabbar高度:", tabbarHeight, "px =", tabbarHeightRpx, "rpx, 调整后:", adjustedBottom, "rpx");
|
||||||
} else {
|
} else {
|
||||||
this.$set(this, "contentBottom", "0rpx");
|
this.$set(this, "contentBottom", "0rpx");
|
||||||
}
|
}
|
||||||
@@ -105,7 +109,7 @@ const _sfc_main = {
|
|||||||
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
const totalHeight = statusBarHeightRpx + actualHeightRpx;
|
||||||
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
const adjustedHeight = Math.max(totalHeight - 4, 0);
|
||||||
this.$set(this, "contentTop", adjustedHeight + "rpx");
|
this.$set(this, "contentTop", adjustedHeight + "rpx");
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:209", "[RecordingDuration] 延迟更新导航栏高度:", actualHeight, "px =", actualHeightRpx, "rpx, 总高度:", totalHeight, "rpx, 调整后:", adjustedHeight, "rpx");
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:218", "[RecordingDuration] 延迟更新导航栏高度:", actualHeight, "px =", actualHeightRpx, "rpx, 总高度:", totalHeight, "rpx, 调整后:", adjustedHeight, "rpx");
|
||||||
}
|
}
|
||||||
}).exec();
|
}).exec();
|
||||||
const queryTabbar2 = common_vendor.index.createSelectorQuery().in(this);
|
const queryTabbar2 = common_vendor.index.createSelectorQuery().in(this);
|
||||||
@@ -115,7 +119,7 @@ const _sfc_main = {
|
|||||||
const tabbarHeightRpx = tabbarHeight * 2;
|
const tabbarHeightRpx = tabbarHeight * 2;
|
||||||
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
const adjustedBottom = Math.max(tabbarHeightRpx - 4, 0);
|
||||||
this.$set(this, "contentBottom", adjustedBottom + "rpx");
|
this.$set(this, "contentBottom", adjustedBottom + "rpx");
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:221", "[RecordingDuration] 延迟更新tabbar高度:", tabbarHeight, "px =", tabbarHeightRpx, "rpx, 调整后:", adjustedBottom, "rpx");
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:230", "[RecordingDuration] 延迟更新tabbar高度:", tabbarHeight, "px =", tabbarHeightRpx, "rpx, 调整后:", adjustedBottom, "rpx");
|
||||||
}
|
}
|
||||||
}).exec();
|
}).exec();
|
||||||
}, 200);
|
}, 200);
|
||||||
@@ -150,7 +154,7 @@ const _sfc_main = {
|
|||||||
salesPhone: loginResponse.phone || null
|
salesPhone: loginResponse.phone || null
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:258", "获取用户信息失败:", e);
|
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:267", "获取用户信息失败:", e);
|
||||||
return { salesId: null, salesPhone: null };
|
return { salesId: null, salesPhone: null };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -211,8 +215,8 @@ const _sfc_main = {
|
|||||||
const queryString = Object.keys(queryParams).filter((key) => queryParams[key] !== null && queryParams[key] !== void 0 && queryParams[key] !== "").map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
const queryString = Object.keys(queryParams).filter((key) => queryParams[key] !== null && queryParams[key] !== void 0 && queryParams[key] !== "").map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(queryParams[key])}`).join("&");
|
||||||
const baseUrl = common_config.getApiUrl("/api/audio-statistics/sales");
|
const baseUrl = common_config.getApiUrl("/api/audio-statistics/sales");
|
||||||
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:338", "[RecordingDuration] 请求参数:", queryParams);
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:347", "[RecordingDuration] 请求参数:", queryParams);
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:339", "[RecordingDuration] 请求URL:", url);
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:348", "[RecordingDuration] 请求URL:", url);
|
||||||
common_vendor.index.showLoading({
|
common_vendor.index.showLoading({
|
||||||
title: "加载中...",
|
title: "加载中...",
|
||||||
mask: true
|
mask: true
|
||||||
@@ -233,8 +237,8 @@ const _sfc_main = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const statisticsList = result.data || [];
|
const statisticsList = result.data || [];
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:374", "[RecordingDuration] 返回数据:", statisticsList);
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:383", "[RecordingDuration] 返回数据:", statisticsList);
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:375", "[RecordingDuration] 返回结果:", result);
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:384", "[RecordingDuration] 返回结果:", result);
|
||||||
this.processStatisticsData(statisticsList);
|
this.processStatisticsData(statisticsList);
|
||||||
common_vendor.index.showToast({
|
common_vendor.index.showToast({
|
||||||
title: "刷新成功",
|
title: "刷新成功",
|
||||||
@@ -246,7 +250,7 @@ const _sfc_main = {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
common_vendor.index.hideLoading();
|
common_vendor.index.hideLoading();
|
||||||
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:393", "[RecordingDuration] 加载数据失败:", error);
|
common_vendor.index.__f__("error", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:402", "[RecordingDuration] 加载数据失败:", error);
|
||||||
common_vendor.index.showToast({
|
common_vendor.index.showToast({
|
||||||
title: error.message || "加载失败,请重试",
|
title: error.message || "加载失败,请重试",
|
||||||
icon: "none",
|
icon: "none",
|
||||||
@@ -300,12 +304,126 @@ const _sfc_main = {
|
|||||||
this.dataList = formattedList;
|
this.dataList = formattedList;
|
||||||
const recordingCount = firstItem.salesRecordingCountStatistic || firstItem.salesRecordingCount || 0;
|
const recordingCount = firstItem.salesRecordingCountStatistic || firstItem.salesRecordingCount || 0;
|
||||||
this.recordingCount = String(recordingCount);
|
this.recordingCount = String(recordingCount);
|
||||||
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:466", "[RecordingDuration] 数据处理完成:", {
|
common_vendor.index.__f__("log", "at pages-subpackage/ai_features/recording_duration/recording_duration.vue:475", "[RecordingDuration] 数据处理完成:", {
|
||||||
totalDuration: this.totalDuration,
|
totalDuration: this.totalDuration,
|
||||||
recordingCount: this.recordingCount,
|
recordingCount: this.recordingCount,
|
||||||
dataListCount: this.dataList.length,
|
dataListCount: this.dataList.length,
|
||||||
statisticsListLength: statisticsList.length
|
statisticsListLength: statisticsList.length
|
||||||
});
|
});
|
||||||
|
this.prepareChartData(statisticsList);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 初始化图表尺寸
|
||||||
|
*/
|
||||||
|
initChartSize() {
|
||||||
|
const systemInfo = common_vendor.index.getSystemInfoSync();
|
||||||
|
const screenWidth = systemInfo.windowWidth || 375;
|
||||||
|
this.chartWidth = screenWidth - 60;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 准备图表数据
|
||||||
|
*/
|
||||||
|
prepareChartData(statisticsList) {
|
||||||
|
if (!statisticsList || statisticsList.length === 0) {
|
||||||
|
this.chartData = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sortedList = [...statisticsList].sort((a, b) => {
|
||||||
|
const dateA = new Date(a.statisticsDate);
|
||||||
|
const dateB = new Date(b.statisticsDate);
|
||||||
|
return dateA - dateB;
|
||||||
|
});
|
||||||
|
this.chartData = sortedList.map((item) => ({
|
||||||
|
date: item.statisticsDate,
|
||||||
|
duration: item.salesTotalDuration || 0
|
||||||
|
}));
|
||||||
|
this.$nextTick(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.drawChart();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 绘制趋势图
|
||||||
|
*/
|
||||||
|
drawChart() {
|
||||||
|
if (!this.chartData || this.chartData.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ctx = common_vendor.index.createCanvasContext("trendChart", this);
|
||||||
|
const width = this.chartWidth;
|
||||||
|
const height = this.chartHeight;
|
||||||
|
const padding = {
|
||||||
|
top: 40,
|
||||||
|
right: 20,
|
||||||
|
bottom: 50,
|
||||||
|
left: 50
|
||||||
|
};
|
||||||
|
const chartWidth = width - padding.left - padding.right;
|
||||||
|
const chartHeight = height - padding.top - padding.bottom;
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
const durations = this.chartData.map((item) => item.duration);
|
||||||
|
const maxDuration = Math.max(...durations, 1);
|
||||||
|
Math.min(...durations, 0);
|
||||||
|
const yMax = Math.ceil(maxDuration / 100) * 100;
|
||||||
|
const yStep = yMax / 5;
|
||||||
|
ctx.setFillStyle("#666666");
|
||||||
|
ctx.setFontSize(10);
|
||||||
|
for (let i = 0; i <= 5; i++) {
|
||||||
|
const y = padding.top + chartHeight - chartHeight / 5 * i;
|
||||||
|
const value = Math.round(yStep * i);
|
||||||
|
ctx.fillText(value.toString(), 5, y + 4);
|
||||||
|
ctx.setStrokeStyle("#e0e0e0");
|
||||||
|
ctx.setLineWidth(1);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, y);
|
||||||
|
ctx.lineTo(padding.left + chartWidth, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
const dataCount = this.chartData.length;
|
||||||
|
const xStep = chartWidth / Math.max(dataCount - 1, 1);
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const date = new Date(item.date);
|
||||||
|
const month = date.getMonth() + 1;
|
||||||
|
const day = date.getDate();
|
||||||
|
const label = `${month}/${day}`;
|
||||||
|
ctx.setFillStyle("#666666");
|
||||||
|
ctx.setFontSize(10);
|
||||||
|
ctx.fillText(label, x - 15, height - padding.bottom + 20);
|
||||||
|
});
|
||||||
|
ctx.setStrokeStyle("#007aff");
|
||||||
|
ctx.setLineWidth(2);
|
||||||
|
ctx.beginPath();
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const y = padding.top + chartHeight - item.duration / yMax * chartHeight;
|
||||||
|
if (index === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setFillStyle("#007aff");
|
||||||
|
this.chartData.forEach((item, index) => {
|
||||||
|
const x = padding.left + xStep * index;
|
||||||
|
const y = padding.top + chartHeight - item.duration / yMax * chartHeight;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, 4, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
});
|
||||||
|
ctx.setStrokeStyle("#333333");
|
||||||
|
ctx.setLineWidth(1);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, padding.top);
|
||||||
|
ctx.lineTo(padding.left, padding.top + chartHeight);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(padding.left, padding.top + chartHeight);
|
||||||
|
ctx.lineTo(padding.left + chartWidth, padding.top + chartHeight);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -351,7 +469,9 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|||||||
} : {}, {
|
} : {}, {
|
||||||
l: common_vendor.t($data.totalDuration),
|
l: common_vendor.t($data.totalDuration),
|
||||||
m: common_vendor.t($data.recordingCount),
|
m: common_vendor.t($data.recordingCount),
|
||||||
n: common_vendor.f($data.dataList, (item, index, i0) => {
|
n: $data.chartWidth + "px",
|
||||||
|
o: $data.chartHeight + "px",
|
||||||
|
p: common_vendor.f($data.dataList, (item, index, i0) => {
|
||||||
return {
|
return {
|
||||||
a: common_vendor.t(item.date),
|
a: common_vendor.t(item.date),
|
||||||
b: common_vendor.t(item.description),
|
b: common_vendor.t(item.description),
|
||||||
@@ -360,8 +480,8 @@ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
|
|||||||
e: index
|
e: index
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
o: $data.contentTop,
|
q: $data.contentTop,
|
||||||
p: $data.contentBottom
|
r: $data.contentBottom
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-582d7c51"]]);
|
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-582d7c51"]]);
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<view class="feature-page data-v-582d7c51"><view class="page-header data-v-582d7c51" style="{{'padding-top:' + b}}"><view class="nav-bar data-v-582d7c51"><view class="nav-left data-v-582d7c51" bindtap="{{a}}"><text class="back-icon data-v-582d7c51">←</text></view><text class="nav-title data-v-582d7c51">录音时长统计</text></view></view><scroll-view class="content-scroll data-v-582d7c51" scroll-y="true" style="{{'top:' + o + ';' + ('bottom:' + p)}}"><view class="search-toolbar data-v-582d7c51"><view class="toolbar-picker-wrapper data-v-582d7c51" bindtap="{{g}}"><view class="toolbar-picker-view data-v-582d7c51"><text class="toolbar-picker-text data-v-582d7c51">{{c}}</text><uni-icons wx:if="{{d}}" class="data-v-582d7c51" u-i="582d7c51-0" bind:__l="__l" u-p="{{d}}"></uni-icons></view><view wx:if="{{e}}" class="time-range-dropdown data-v-582d7c51"><view wx:for="{{f}}" wx:for-item="option" wx:key="b" class="{{['dropdown-item', 'data-v-582d7c51', option.c && 'dropdown-item-active']}}" catchtap="{{option.d}}"><text class="data-v-582d7c51">{{option.a}}</text></view></view></view><view class="toolbar-actions data-v-582d7c51"><view class="toolbar-btn toolbar-btn--refresh data-v-582d7c51" bindtap="{{i}}"><uni-icons wx:if="{{h}}" class="data-v-582d7c51" u-i="582d7c51-1" bind:__l="__l" u-p="{{h}}"></uni-icons><text class="data-v-582d7c51">刷新</text></view></view></view><view wx:if="{{j}}" class="dropdown-mask data-v-582d7c51" bindtap="{{k}}"></view><view class="stats-card data-v-582d7c51"><view class="stat-item data-v-582d7c51"><text class="stat-value data-v-582d7c51">{{l}}</text><text class="stat-label data-v-582d7c51">总录音时长</text></view><view class="stat-item data-v-582d7c51"><text class="stat-value data-v-582d7c51">{{m}}</text><text class="stat-label data-v-582d7c51">录音总条数</text></view></view><view class="chart-section data-v-582d7c51"><text class="section-title data-v-582d7c51">时长趋势图</text><view class="chart-placeholder data-v-582d7c51"><text class="placeholder-text data-v-582d7c51">📊 图表区域</text><text class="placeholder-desc data-v-582d7c51">此处将显示录音时长趋势图表</text></view></view><view class="data-list data-v-582d7c51"><text class="section-title data-v-582d7c51">详细数据</text><view wx:for="{{n}}" wx:for-item="item" wx:key="e" class="list-item data-v-582d7c51"><view class="item-left data-v-582d7c51"><text class="item-date data-v-582d7c51">{{item.a}}</text><text class="item-desc data-v-582d7c51">{{item.b}}</text></view><view class="item-right data-v-582d7c51"><text class="item-duration data-v-582d7c51">{{item.c}}</text><text class="item-count data-v-582d7c51">{{item.d}}</text></view></view></view></scroll-view></view>
|
<view class="feature-page data-v-582d7c51"><view class="page-header data-v-582d7c51" style="{{'padding-top:' + b}}"><view class="nav-bar data-v-582d7c51"><view class="nav-left data-v-582d7c51" bindtap="{{a}}"><text class="back-icon data-v-582d7c51">←</text></view><text class="nav-title data-v-582d7c51">录音时长统计</text></view></view><scroll-view class="content-scroll data-v-582d7c51" scroll-y="true" style="{{'top:' + q + ';' + ('bottom:' + r)}}"><view class="search-toolbar data-v-582d7c51"><view class="toolbar-picker-wrapper data-v-582d7c51" bindtap="{{g}}"><view class="toolbar-picker-view data-v-582d7c51"><text class="toolbar-picker-text data-v-582d7c51">{{c}}</text><uni-icons wx:if="{{d}}" class="data-v-582d7c51" u-i="582d7c51-0" bind:__l="__l" u-p="{{d}}"></uni-icons></view><view wx:if="{{e}}" class="time-range-dropdown data-v-582d7c51"><view wx:for="{{f}}" wx:for-item="option" wx:key="b" class="{{['dropdown-item', 'data-v-582d7c51', option.c && 'dropdown-item-active']}}" catchtap="{{option.d}}"><text class="data-v-582d7c51">{{option.a}}</text></view></view></view><view class="toolbar-actions data-v-582d7c51"><view class="toolbar-btn toolbar-btn--refresh data-v-582d7c51" bindtap="{{i}}"><uni-icons wx:if="{{h}}" class="data-v-582d7c51" u-i="582d7c51-1" bind:__l="__l" u-p="{{h}}"></uni-icons><text class="data-v-582d7c51">刷新</text></view></view></view><view wx:if="{{j}}" class="dropdown-mask data-v-582d7c51" bindtap="{{k}}"></view><view class="stats-card data-v-582d7c51"><view class="stat-item data-v-582d7c51"><text class="stat-value data-v-582d7c51">{{l}}</text><text class="stat-label data-v-582d7c51">总录音时长</text></view><view class="stat-item data-v-582d7c51"><text class="stat-value data-v-582d7c51">{{m}}</text><text class="stat-label data-v-582d7c51">录音总条数</text></view></view><view class="chart-section data-v-582d7c51"><text class="section-title data-v-582d7c51">时长趋势图</text><view class="chart-container data-v-582d7c51"><canvas canvas-id="trendChart" id="trendChart" class="chart-canvas data-v-582d7c51" style="{{'width:' + n + ';' + ('height:' + o)}}"></canvas></view></view><view class="data-list data-v-582d7c51"><text class="section-title data-v-582d7c51">详细数据</text><view wx:for="{{p}}" wx:for-item="item" wx:key="e" class="list-item data-v-582d7c51"><view class="item-left data-v-582d7c51"><text class="item-date data-v-582d7c51">{{item.a}}</text><text class="item-desc data-v-582d7c51">{{item.b}}</text></view><view class="item-right data-v-582d7c51"><text class="item-duration data-v-582d7c51">{{item.c}}</text><text class="item-count data-v-582d7c51">{{item.d}}</text></view></view></view></scroll-view></view>
|
||||||
@@ -189,19 +189,14 @@
|
|||||||
padding: 30rpx;
|
padding: 30rpx;
|
||||||
border-bottom: 1rpx solid #e9ecef;
|
border-bottom: 1rpx solid #e9ecef;
|
||||||
}
|
}
|
||||||
.chart-placeholder.data-v-582d7c51 {
|
.chart-container.data-v-582d7c51 {
|
||||||
padding: 80rpx 30rpx;
|
padding: 30rpx;
|
||||||
text-align: center;
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
.placeholder-text.data-v-582d7c51 {
|
.chart-canvas.data-v-582d7c51 {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 48rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
.placeholder-desc.data-v-582d7c51 {
|
|
||||||
display: block;
|
|
||||||
font-size: 28rpx;
|
|
||||||
color: #666666;
|
|
||||||
}
|
}
|
||||||
.list-item.data-v-582d7c51 {
|
.list-item.data-v-582d7c51 {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user