Forráskód Böngészése

完成第二阶段任务开发(种植过程记录、种植环境记录)

jiuling 1 hónapja
szülő
commit
a0c75ca966
3 módosított fájl, 236 hozzáadás és 53 törlés
  1. 14 1
      api/base/index.js
  2. 16 0
      api/device.js
  3. 206 52
      pages/trace/detail.vue

+ 14 - 1
api/base/index.js

@@ -1,7 +1,20 @@
 import { http, Method } from '@/utils/request.js'
 
 /**
-  * 获取车辆作业详细信息
+ * 根据设备类型获取设备列表
+ * @param {Object} params - 查询参数
+ * @returns {Promise} 设备列表
+ */
+export function listWeather(params) {
+  return http.request({
+    url: `/base/weather/listWeatherStatisticsJyhy`,
+    method: Method.POST,
+    data: params
+  });
+}
+
+/**
+  * 获取批次详细信息
 */
 export function getTraceDetail(id){
   return http.request({

+ 16 - 0
api/device.js

@@ -15,6 +15,22 @@ const wvpUsername = "admin" // 用户名
 const wvpPassword = "af7b951b3a30e898e2684ffe8d20a961" // 密码(MD5加密)
 const wvpServer = "https://nxy.gbdfarm.com:9000/pro-uniapp/wvp"// WVP服务器地址
 
+
+/**
+ * 根据设备类型获取设备列表
+ * @param {string} type - 设备类型(monitor-监控设备,sensor-采集设备,control-控制设备,irrigation-灌溉设备,tractor-农机设备)
+ * @param {Object} params - 查询参数
+ * @returns {Promise} 设备列表
+ */
+export function fetchDevicesByType(params) {
+  return http.request({
+    url: `/base/device/typeList`,
+    method: Method.POST,
+	needToken: true,
+    data: params
+  });
+}
+
 /**
  * 登录wvp获取token
  */

+ 206 - 52
pages/trace/detail.vue

@@ -430,9 +430,9 @@
 							</svg>
 						</view>
 
-						<!-- 日期标签:独立于绘图区 -->
+						<!-- 日期标签:根据数据量和日期跨度智能显示 -->
 						<view class="chartLabels">
-							<text v-for="(label, idx) in traceDetail.environment.chart.labels" :key="`label-${idx}`">{{ label }}</text>
+							<text v-for="(item, idx) in chartXAxisLabels" :key="`label-${idx}`" :class="{'label-hidden': item.hidden}">{{ item.label }}</text>
 						</view>
 					</view>
 
@@ -615,13 +615,14 @@
 <script setup>
 import { computed, ref, reactive } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
-import {getTraceDetail } from '@/api/base/index.js'
+import {getTraceDetail, listWeather } from '@/api/base/index.js'
 import {
 	getDeviceCollectorDetail,
 	getChannels,
 	playStart,
 	pause ,
-	querySnap
+	querySnap,
+	fetchDevicesByType
 } from '@/api/device.js'
 import Jessibuca from '@/components/common/jessibuca.vue'
 // 页面状态选择(H5 演示用):通过路由参数传入 state 即可切换 mock 场景
@@ -687,7 +688,7 @@ const isH5IOS = computed(() => {
 })
 // 响应式数据
 const deviceInfo = reactive({
-	deviceId: '34020000001110000001',
+	deviceId: '',
 	name: '设备加载中...',
 	status: 'off',
 	location: '正在获取位置...',
@@ -703,6 +704,17 @@ const deviceInfo = reactive({
 	fmp4StreamUrl: '',        // fmp4 流地址(App Android 用)
 	imageUrl: ''              // 设备图片(如果有)
 })
+
+// 气象数据(独立响应式变量,用于土壤养分趋势图)
+const weatherData = ref({
+	labels: [],
+	values: {
+		n: [],
+		p: [],
+		k: []
+	},
+	items:[]
+})
 const MOCK_TRACE_DETAILS = {
 	normal: {
 		product: {
@@ -1056,11 +1068,10 @@ onLoad((opts) => {
 	// 优先使用路由参数中的 id,其次使用 URL 路径中的 id
 	const finalId = batchId || 1
 	loadData(finalId)
-	queryChannels()
+	// queryChannels()
 	routeOptions.value = opts || {}
 	mockStateKey.value = resolveStateKey(opts || {})
 	startTimeUpdate()
-	
 	// #ifdef MP-WEIXIN
 	setTimeout(() => {
 		livePlayerContext.value = uni.createVideoContext('myVideo')
@@ -1083,8 +1094,38 @@ onLoad((opts) => {
 	}, 500)
 	// #endif
 })
+// 加载设备列表
+const loadDeviceList = (currentFieldId) => {
+  // 构建查询参数
+  const params = {
+    pageNum: 1,
+    pageSize: 10,
+    deviceType: 'monitor',
+    fieldId: currentFieldId || undefined
+  };
+  
+  // 调用API获取设备列表
+  fetchDevicesByType(params)
+    .then(res => {
+      console.log("res", res);
+      if (res.data.code === 200 && res.data.rows) {
+        const { rows, total: totalCount } = res.data;
+        queryChannels(rows[0].deviceId)// 根据设备ID查询摄像头
+      } else {
+        handleApiError(res);
+      }
+    })
+    .catch(error => {
+      console.error('获取设备列表失败', error);
+      uni.showToast({
+        title: '获取设备列表失败',
+        icon: 'none'
+      });
+    })
+}
 // 根据设备id获取通道列表
-	const queryChannels = () => {
+	const queryChannels = (deviceId) => {
+		deviceInfo.deviceId = deviceId
 		console.log('========== [诊断] queryChannels 被调用 ==========')
 		getChannels(deviceInfo.deviceId)
 			.then(response => {
@@ -1194,10 +1235,51 @@ const loadData = async (batchId) => {
   loading.value = true
 
   try {
-    // 调用接口
     const res = await getTraceDetail(batchId)
-    traceInfo.value = res.data.data
-    console.log('接口返回数据:', res)
+
+    if (res && res.data && res.data.data) {
+      traceInfo.value = res.data.data
+    } else {
+      traceInfo.value = {}
+    }
+	loadDeviceList(res.data.data.fieldId) // 根据地块ID查询设备信息
+	
+	const beginCollectTime = formatDateTime(traceInfo.value.startPlantingTime);
+	const endCollectTime = formatDateTime(traceInfo.value.packageDate);
+	const paramsMap = {
+		beginCollectTime:beginCollectTime,
+		endCollectTime:endCollectTime,
+		params: {
+			plotId: traceInfo.value.fieldId
+		}
+	}
+	
+	// 查询气象数据并更新到 weatherData
+	listWeather(paramsMap).then(res => {
+		if (res && res.data && res.data.data) {
+			const data = res.data.data
+			// 更新气象数据到独立的响应式变量
+			weatherData.value = {
+				labels: data.weatherDate || [],
+				values: {
+					n: data.weatherAvgSoilN || [],
+					p: data.weatherAvgSoilP || [],
+					k: data.weatherAvgSoilK || []
+				},
+				items:[
+					{ label: '气温', value: data.weatherRealtimeData.temperature+'℃', status: '种植期平均' },
+					{ label: '湿度', value: data.weatherRealtimeData.humidity+'%', status: '种植期平均' },
+					{ label: '降雨量', value: data.weatherRealtimeData.rainfallA+'mm', status: '种植期累计' },
+					{ label: '光照', value: '充足', status: '表现稳定' }
+				]
+			}
+			console.log('气象数据已更新:', weatherData.value)
+		}
+	}).catch(err => {
+		console.error('获取气象数据失败:', err)
+	})
+	
+    console.log('接口返回数据:', traceInfo.value)
   } catch (error) {
     console.error('加载失败', error)
     // 错误提示已在拦截器中自动处理
@@ -1208,6 +1290,12 @@ const loadData = async (batchId) => {
     traceLoaded.value = true
   }
 }
+// 工具函数:把日期字符串转为 年月日时分秒格式
+const formatDateTime = (dateStr) => {
+  if (!dateStr) return '';
+  // 截取日期部分 + 拼接 00:00:00
+  return dateStr.substring(0, 10) + ' 00:00:00';
+};
 // 视频播放错误处理
 	const onVideoError = (e) => {
 		console.error('========== [诊断] 视频播放错误 ==========')
@@ -1886,27 +1974,42 @@ const traceDetail = computed(() => {
 		// 时间轴逻辑:演示态使用 mock + 真实态使用真实数据
 		farmTimeline: (() => {
 			// 判断是否有真实时间轴数据
-			const hasRealTimeline = Array.isArray(data.farmTimeline) && data.farmTimeline.length > 0
-
+			const hasRealTimeline = data.startPlantingTime == '' || data.startPlantingTime == null
+			
 			let timeline = []
-
-			if (!hasRealTimeline) {
-				// 演示态:使用完整 mock 时间轴(用于页面展示验证)
-				timeline = [
-					{ time: '2026-03-01', stage: 'planting', title: '开始种植', desc: '本批次产品进入种植管理阶段' },
-					{ time: '2026-03-12', stage: 'care', title: '生长期养护', desc: '种植期间已持续开展灌溉、施肥等日常养护' },
-					{ time: '2026-03-20', stage: 'inspection', title: '田间巡检', desc: '种植期间已持续开展长势检查与环境巡检' },
-					{ time: data.produceDate || '', stage: 'harvest', title: '成熟采收', desc: '本批次产品进入成熟采收阶段' },
-					{ time: (data.reports?.[0]?.reportDate) || '', stage: 'test', title: '安全检测', desc: '已完成安全检测,相关结果可查' },
-					{ time: data.packageDate || '', stage: 'pack', title: '包装日期:出库', desc: '已完成包装日期:整理,进入销售流通环节' }
-				]
-			} else {
-				// 真实态:使用真实数据 + 补充系统节点
-				timeline = [...data.farmTimeline]
-				const stageSet = new Set(timeline.map(n => n.stage))
-
+			if(!hasRealTimeline){
+				// 开始种植
+				if (data.startPlantingTime) {
+					timeline.push({
+						time: data.startPlantingTime,
+						stage: 'planting',
+						title: '开始种植',
+						desc: '本批次产品进入种植管理阶段'
+					})
+				}
+				
+				// 生长养护
+				if (data.growingPeriodTime) {
+					timeline.push({
+						time: data.growingPeriodTime,
+						stage: 'care',
+						title: '生长期养护',
+						desc: '种植期间已持续开展灌溉、施肥等日常养护'
+					})
+				}
+				
+				// 田间巡检
+				if (data.inspectionTime) {
+					timeline.push({
+						time: data.inspectionTime,
+						stage: 'inspection',
+						title: '田间巡检',
+						desc: '种植期间已持续开展长势检查与环境巡检'
+					})
+				}
+				
 				// 成熟采收
-				if (data.produceDate && !stageSet.has('harvest')) {
+				if (data.produceDate) {
 					timeline.push({
 						time: data.produceDate,
 						stage: 'harvest',
@@ -1914,9 +2017,9 @@ const traceDetail = computed(() => {
 						desc: '本批次产品进入成熟采收阶段'
 					})
 				}
-
+				
 				// 安全检测
-				if (data.reports && data.reports.length > 0 && !stageSet.has('test')) {
+				if (data.reports && data.reports.length > 0) {
 					const validDates = data.reports
 						.map(r => r.reportDate)
 						.filter(d => d && /^\d{4}-\d{2}-\d{2}/.test(d))
@@ -1929,10 +2032,20 @@ const traceDetail = computed(() => {
 							desc: '已完成安全检测,相关结果可查'
 						})
 					}
+				}else{
+					const validDates = data.certificate
+					if (validDates) {
+						timeline.push({
+							time: validDates.certIssueDate, 
+							stage: 'test',
+							title: '安全检测',
+							desc: '已完成安全检测,相关结果可查'
+						})
+					}
 				}
-
-				// 包装日期:出库
-				if (data.packageDate && !stageSet.has('pack')) {
+				
+				// 包装出库
+				if (data.packageDate) {
 					timeline.push({
 						time: data.packageDate,
 						stage: 'pack',
@@ -1941,7 +2054,7 @@ const traceDetail = computed(() => {
 					})
 				}
 			}
-
+				
 			// stage 顺序权重
 			const stageOrder = {
 				planting: 1,
@@ -1951,26 +2064,27 @@ const traceDetail = computed(() => {
 				test: 5,
 				pack: 6
 			}
-
+				
 			// 按业务阶段排序,同 stage 内再按时间
 			timeline.sort((a, b) => {
 				const orderA = stageOrder[a.stage] || 99
 				const orderB = stageOrder[b.stage] || 99
-
+				
 				// 先按 stage 排序
 				if (orderA !== orderB) {
 					return orderA - orderB
 				}
-
+				
 				// 同 stage 再按时间
 				if (!a.time) return -1
 				if (!b.time) return 1
-
+				
 				return a.time > b.time ? 1 : -1
 			})
-
+				
 			return timeline.slice(0, 6)
-		})(),
+		}
+	)(),
 		camera: {
 			liveUrl: data.camera?.liveUrl || '',
 			coverImage: data.camera?.coverImage || '',
@@ -1983,19 +2097,14 @@ const traceDetail = computed(() => {
 			summary: '本批次种植期环境稳定,整体处于适宜生长区间',
 			chart: {
 				type: 'npk_trend',
-				labels: ['03-01', '03-06', '03-10', '03-15', '03-21', '03-27', '04-01'],
+				labels: weatherData.value.labels,
 				values: {
-					n: [26.2, 25.8, 26.4, 25.9, 26.6, 26.1, 26.4],
-					p: [20.5, 20.1, 20.4, 20.0, 20.3, 20.2, 20.4],
-					k: [14.2, 14.6, 14.3, 14.7, 14.4, 14.8, 14.5]
+					n: weatherData.value.values.n,
+					p: weatherData.value.values.p,
+					k: weatherData.value.values.k
 				}
 			},
-			items: [
-				{ label: '气温', value: '22.3℃', status: '种植期平均' },
-				{ label: '湿度', value: '45%', status: '种植期平均' },
-				{ label: '降雨量', value: '118mm', status: '种植期累计' },
-				{ label: '光照', value: '充足', status: '表现稳定' }
-			],
+			items: weatherData.value.items,
 			current: {
 				temperature: '24.5℃',
 				humidity: '42.2%'
@@ -2260,6 +2369,47 @@ const chartPaths = computed(() => chartData.value.paths)
 
 const chartAreas = computed(() => chartData.value.areas)
 
+// 智能计算 X 轴标签:根据数据量和日期跨度决定哪些显示
+const chartXAxisLabels = computed(() => {
+	const labels = traceDetail.value?.environment?.chart?.labels || []
+	if (!labels.length) return []
+	
+	// 如果标签数量 <= 7,全部显示
+	if (labels.length <= 7) {
+		return labels.map(label => ({ label, hidden: false }))
+	}
+	
+	// 数量 > 7,需要智能抽取
+	const result = []
+	const total = labels.length
+	
+	// 计算最大显示数量(根据容器宽度,每个标签最小约 60px)
+	const avgSpacing = 700 / total
+	
+	if (avgSpacing < 60) {
+		// 标签太密集,只显示首尾 + 每隔一段显示一个
+		const step = Math.max(1, Math.floor(total / 6))
+		for (let i = 0; i < total; i++) {
+			if (i === 0 || i === total - 1 || i % step === 0) {
+				result.push({ label: labels[i], hidden: false })
+			} else {
+				result.push({ label: '', hidden: true })
+			}
+		}
+	} else {
+		// 标签间距足够,每隔一个显示
+		for (let i = 0; i < total; i++) {
+			if (i % 2 === 0) {
+				result.push({ label: labels[i], hidden: false })
+			} else {
+				result.push({ label: '', hidden: true })
+			}
+		}
+	}
+	
+	return result
+})
+
 const reportImages = computed(() => {
 	const items = traceDetail.value?.report?.items
 	if (Array.isArray(items)) {
@@ -3208,6 +3358,10 @@ function previewDoc(kind, index) {
 	color: rgba(84, 106, 93, 0.4);
 }
 
+.chartLabels .label-hidden {
+	visibility: hidden;
+}
+
 .environmentChartLegend {
 	display: flex;
 	justify-content: center;