diff --git a/src/views/fishery/device/index.vue b/src/views/fishery/device/index.vue
index be77173..79244ce 100644
--- a/src/views/fishery/device/index.vue
+++ b/src/views/fishery/device/index.vue
@@ -141,6 +141,9 @@
+
+
+
@@ -750,9 +753,79 @@
-
+
+
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ (proxy?.useDict('aqu_device_type', 'open_close', 'connect_voltage_type', 'yes_no', 'loop_type', 'is_linked_ctrl'));
+const { aqu_device_type, open_close, connect_voltage_type, yes_no, loop_type, is_linked_ctrl, interval_type } = toRefs(proxy?.useDict('aqu_device_type', 'open_close', 'connect_voltage_type', 'yes_no', 'loop_type', 'is_linked_ctrl', 'interval_type'));
const deviceList = ref([]);
const buttonLoading = ref(false);
@@ -1450,6 +1526,189 @@ const closeSwitchTimingCtrlDialog = () => {
switchTimingCtrlList.value = [];
};
+// ===================== 历史曲线 =====================
+const historyDialogVisible = ref(false);
+const historyDevice = ref(null);
+const historyLoading = ref(false);
+const historyDateRange = ref([]);
+const historyActiveTab = ref('dissolvedOxygen');
+const historyChartType = ref('line');
+const historyChartRef = ref();
+const historyData = ref([]);
+let historyChartInstance: echarts.ECharts | null = null;
+const historyQueryParams = reactive({
+ serialNum: '',
+ startTime: '',
+ endTime: '',
+ intervalType: '3'
+});
+
+const historyTabConfig = {
+ dissolvedOxygen: { label: '溶解氧', unit: 'Mg/L', color: '#5470c6' },
+ temperature: { label: '水温', unit: '℃', color: '#91cc75' },
+ saturability: { label: '饱和度', unit: '%', color: '#fac858' },
+ ph: { label: 'PH', unit: 'Ph', color: '#ee6666' },
+ salinity: { label: '盐度', unit: '%', color: '#73c0de' },
+ treference: { label: '参比值', unit: '', color: '#3ba272' },
+ tfluorescence: { label: '荧光值', unit: '', color: '#fc8452' },
+ battery: { label: '电量', unit: '%', color: '#9a60b4' }
+};
+
+/** 查看历史曲线 */
+const handleViewHistory = (row: DeviceVO) => {
+ historyDevice.value = row;
+ historyQueryParams.serialNum = row.serialNum || '';
+ historyActiveTab.value = 'dissolvedOxygen';
+ historyChartType.value = 'line';
+ historyData.value = [];
+ // 默认最近7天
+ const end = new Date();
+ const start = new Date();
+ start.setDate(start.getDate() - 7);
+ const fmt = (d: Date) => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
+ historyDateRange.value = [fmt(start), fmt(end)];
+ historyDialogVisible.value = true;
+};
+
+/** 对话框打开后初始化图表并查询 */
+const onHistoryDialogOpened = async () => {
+ await nextTick();
+ initHistoryChart();
+ await fetchHistoryData();
+};
+
+/** 初始化图表 */
+const initHistoryChart = () => {
+ if (!historyChartRef.value) return;
+ if (historyChartInstance) {
+ historyChartInstance.dispose();
+ }
+ historyChartInstance = echarts.init(historyChartRef.value);
+};
+
+/** 获取历史数据 */
+const fetchHistoryData = async () => {
+ if (!historyQueryParams.serialNum) return;
+ historyLoading.value = true;
+ try {
+ if (historyDateRange.value && historyDateRange.value.length === 2) {
+ historyQueryParams.startTime = historyDateRange.value[0];
+ historyQueryParams.endTime = historyDateRange.value[1];
+ }
+ const res = await getHistoryData(historyQueryParams);
+ if (Array.isArray(res)) {
+ historyData.value = res;
+ } else if (res.data && Array.isArray(res.data)) {
+ historyData.value = res.data;
+ } else {
+ historyData.value = [];
+ }
+ await nextTick();
+ updateHistoryChart();
+ if (historyData.value.length === 0) {
+ proxy?.$modal.msgWarning('未查询到数据,请检查日期范围是否正确');
+ }
+ } catch {
+ proxy?.$modal.msgError('查询历史数据失败');
+ } finally {
+ historyLoading.value = false;
+ }
+};
+
+/** 搜索 */
+const handleHistoryQuery = () => {
+ fetchHistoryData();
+};
+
+/** 更新图表 */
+const updateHistoryChart = () => {
+ if (!historyChartInstance) {
+ initHistoryChart();
+ if (!historyChartInstance) return;
+ }
+ if (historyData.value.length === 0) {
+ historyChartInstance.setOption({
+ title: { text: '暂无数据', left: 'center', top: 'center', textStyle: { color: '#999', fontSize: 20 } },
+ tooltip: {}, grid: {}, xAxis: { show: false }, yAxis: { show: false }, series: []
+ }, true);
+ return;
+ }
+ const config = historyTabConfig[historyActiveTab.value as keyof typeof historyTabConfig];
+ const xData = historyData.value.map(item => {
+ const d = new Date(item.time.replace(' ', 'T'));
+ return `${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}:${String(d.getSeconds()).padStart(2,'0')}`;
+ });
+ const yData = historyData.value.map(item => {
+ const v = item[historyActiveTab.value as keyof DeviceSensorData];
+ return typeof v === 'number' ? v : 0;
+ });
+ historyChartInstance.setOption({
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: { type: 'cross' },
+ formatter: (params: any) => {
+ const p = params[0];
+ return `${p.axisValue}
${config.label}: ${p.value} ${config.unit}`;
+ }
+ },
+ grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
+ xAxis: {
+ type: 'category',
+ boundaryGap: historyChartType.value === 'bar',
+ data: xData,
+ axisLabel: { interval: Math.floor(xData.length / 20), rotate: 20, fontSize: 11 }
+ },
+ yAxis: {
+ type: 'value',
+ name: `${config.label}(${config.unit})`,
+ axisLabel: { formatter: `{value} ${config.unit}` }
+ },
+ series: [{
+ name: config.label,
+ type: historyChartType.value,
+ data: yData,
+ smooth: historyChartType.value === 'line',
+ symbol: 'circle',
+ symbolSize: historyChartType.value === 'line' ? 0 : 6,
+ itemStyle: { color: config.color },
+ lineStyle: historyChartType.value === 'line' ? { width: 2 } : undefined,
+ areaStyle: historyChartType.value === 'line' ? { opacity: 0.1 } : undefined,
+ emphasis: historyChartType.value === 'line' ? {
+ symbol: 'circle', symbolSize: 12,
+ itemStyle: { color: config.color, borderColor: '#fff', borderWidth: 3, shadowBlur: 8, shadowColor: config.color }
+ } : undefined
+ }]
+ }, true);
+};
+
+/** 切换图表类型 */
+const changeHistoryChartType = (type: string) => {
+ historyChartType.value = type;
+ updateHistoryChart();
+};
+
+/** 下载图表 */
+const downloadHistoryChart = () => {
+ if (!historyChartInstance) return;
+ const url = historyChartInstance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' });
+ const link = document.createElement('a');
+ link.download = `历史曲线_${historyQueryParams.serialNum}_${new Date().getTime()}.png`;
+ link.href = url;
+ link.click();
+};
+
+/** 关闭历史曲线对话框 */
+const closeHistoryDialog = () => {
+ historyDialogVisible.value = false;
+ historyDevice.value = null;
+ historyData.value = [];
+ if (historyChartInstance) {
+ historyChartInstance.dispose();
+ historyChartInstance = null;
+ }
+};
+// ===================== 历史曲线 end =====================
+
onMounted(() => {
getList();
});