|
|
@@ -75,6 +75,20 @@
|
|
|
</view>
|
|
|
|
|
|
<scroll-view class="content" scroll-y>
|
|
|
+ <!-- 溯源结论卡:先给结论,再给证据 -->
|
|
|
+ <view v-if="traceDetail.batch?.exists && traceDetail.traceConclusionCard" class="card cardLevel1 cardConclusion">
|
|
|
+ <view class="sectionHeader">
|
|
|
+ <view class="sectionHeaderLeft">
|
|
|
+ <text class="sectionEn">Conclusion</text>
|
|
|
+ <text class="sectionTitle">溯源结论</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="conclusionBody">
|
|
|
+ <text class="conclusionLead">{{ traceDetail.traceConclusionCard.conclusion }}</text>
|
|
|
+ <text class="conclusionDesc">{{ traceDetail.traceConclusionCard.desc }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 农场信息(合并为单卡:图片+名称+地区+介绍连续表达) -->
|
|
|
<view v-if="traceDetail.farm?.name" class="card cardLevel2 cardFarm">
|
|
|
<view class="sectionHeader">
|
|
|
@@ -104,6 +118,80 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
+ <!-- 农事时间轴:关键种植过程 -->
|
|
|
+ <view v-if="traceDetail.batch?.exists && traceDetail.farmTimeline?.length" class="card cardLevel1 cardTimeline">
|
|
|
+ <view class="sectionHeader">
|
|
|
+ <view class="sectionHeaderLeft">
|
|
|
+ <text class="sectionEn">Process</text>
|
|
|
+ <text class="sectionTitle">种植过程记录</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="timelineList">
|
|
|
+ <view
|
|
|
+ v-for="(item, index) in traceDetail.farmTimeline"
|
|
|
+ :key="`timeline-${index}`"
|
|
|
+ :class="{
|
|
|
+ 'timelineItem': true,
|
|
|
+ 'is-key': item.stage === 'test',
|
|
|
+ 'is-current': item.stage === 'pack'
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <view class="timelineLeft">
|
|
|
+ <view class="timelineDot" />
|
|
|
+ <view v-if="index !== traceDetail.farmTimeline.length - 1" class="timelineLine" />
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="timelineRight">
|
|
|
+ <text v-if="item.time" class="timelineTime">{{ item.time }}</text>
|
|
|
+ <text class="timelineTitle">{{ item.title }}</text>
|
|
|
+ <text class="timelineDesc">{{ item.desc }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="timelineFootnote">
|
|
|
+ <text>以上信息真实有效,可追溯验证</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 种植现场实时画面 -->
|
|
|
+ <view v-if="traceDetail.batch?.exists && traceDetail.camera" class="card cardLevel1 cardLive">
|
|
|
+ <view class="sectionHeader">
|
|
|
+ <view class="sectionHeaderLeft">
|
|
|
+ <text class="sectionEn">Live Scene</text>
|
|
|
+ <text class="sectionTitle">种植现场</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="liveMediaWrap">
|
|
|
+ <image
|
|
|
+ v-if="traceDetail.camera.coverImage"
|
|
|
+ class="liveCover"
|
|
|
+ :src="traceDetail.camera.coverImage"
|
|
|
+ mode="aspectFill"
|
|
|
+ />
|
|
|
+ <view v-else class="livePlaceholder">
|
|
|
+ <view class="livePlayIcon">
|
|
|
+ <view class="livePlayDot" />
|
|
|
+ <view class="livePlayDot dot2" />
|
|
|
+ <view class="livePlayDot dot3" />
|
|
|
+ </view>
|
|
|
+ <text class="livePlaceholderMain">实时画面接入中</text>
|
|
|
+ <text class="livePlaceholderSub">请稍候查看现场情况</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="liveStatusPill" :class="cameraStatus.value">
|
|
|
+ <view class="liveStatusDot" />
|
|
|
+ <text>{{ cameraStatusText }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <view class="liveDesc">
|
|
|
+ <text>{{ traceDetail.camera.desc }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 批次信息(可选:batch 可能不存在) -->
|
|
|
<view v-if="traceDetail.batch?.exists" class="card cardLevel1 cardBatch">
|
|
|
<view class="sectionHeader">
|
|
|
@@ -336,12 +424,62 @@ const MOCK_TRACE_DETAILS = {
|
|
|
no: '',
|
|
|
fileUrl: '',
|
|
|
images: ['']
|
|
|
+ },
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ farmTimeline: [
|
|
|
+ {
|
|
|
+ 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: '2026-03-30',
|
|
|
+ stage: 'harvest',
|
|
|
+ title: '成熟采收',
|
|
|
+ desc: '本批次产品进入成熟采收阶段'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ time: '2026-03-31',
|
|
|
+ stage: 'test',
|
|
|
+ title: '安全检测',
|
|
|
+ desc: '已完成安全检测,相关结果可查'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ time: '2026-04-01',
|
|
|
+ stage: 'pack',
|
|
|
+ title: '包装出库',
|
|
|
+ desc: '已完成包装整理,进入销售流通环节'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ camera: {
|
|
|
+ liveUrl: '',
|
|
|
+ coverImage: '',
|
|
|
+ status: 'online',
|
|
|
+ desc: '来自农场实景画面,种植环境真实呈现'
|
|
|
}
|
|
|
},
|
|
|
reportPending: {
|
|
|
product: {
|
|
|
name: '',
|
|
|
- spec: '',
|
|
|
+ spec: '',
|
|
|
image: '',
|
|
|
intro: ''
|
|
|
},
|
|
|
@@ -371,12 +509,19 @@ const MOCK_TRACE_DETAILS = {
|
|
|
no: '',
|
|
|
fileUrl: '',
|
|
|
images: []
|
|
|
- }
|
|
|
+ },
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ farmTimeline: []
|
|
|
},
|
|
|
certificatePending: {
|
|
|
product: {
|
|
|
name: '',
|
|
|
- spec: '',
|
|
|
+ spec: '',
|
|
|
image: '',
|
|
|
intro: ''
|
|
|
},
|
|
|
@@ -408,7 +553,14 @@ const MOCK_TRACE_DETAILS = {
|
|
|
status: '',
|
|
|
statusBadge: { text: '', type: '' },
|
|
|
emptyMessage: ''
|
|
|
- }
|
|
|
+ },
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ farmTimeline: []
|
|
|
},
|
|
|
batchNotFound: {
|
|
|
product: {
|
|
|
@@ -429,7 +581,14 @@ const MOCK_TRACE_DETAILS = {
|
|
|
emptyMessage: ''
|
|
|
},
|
|
|
report: null,
|
|
|
- certificate: null
|
|
|
+ certificate: null,
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ farmTimeline: []
|
|
|
},
|
|
|
batchOfflined: {
|
|
|
product: {
|
|
|
@@ -450,7 +609,14 @@ const MOCK_TRACE_DETAILS = {
|
|
|
emptyMessage: ''
|
|
|
},
|
|
|
report: null,
|
|
|
- certificate: null
|
|
|
+ certificate: null,
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ farmTimeline: []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -468,7 +634,7 @@ onLoad((opts) => {
|
|
|
const batchId = fullPath.split('/').filter(Boolean).pop()
|
|
|
|
|
|
// 优先使用路由参数中的 id,其次使用 URL 路径中的 id
|
|
|
- const finalId = batchId
|
|
|
+ const finalId = batchId || 1
|
|
|
loadData(finalId)
|
|
|
|
|
|
routeOptions.value = opts || {}
|
|
|
@@ -592,9 +758,140 @@ const traceDetail = computed(() => {
|
|
|
fileUrl: certFiles.length > 0 ? certFiles[0].url : '',
|
|
|
images: certFiles.map(f => f.url),
|
|
|
emptyMessage: '合格证待补充,上传完成后可查看大图。'
|
|
|
+ },
|
|
|
+ traceConclusionCard: {
|
|
|
+ title: '溯源结论',
|
|
|
+ conclusion: '本批次产品来源清晰,已完成安全检测,过程可查',
|
|
|
+ desc: '检测材料与溯源数据已展示,可继续查看',
|
|
|
+ passed: true
|
|
|
+ },
|
|
|
+ // 时间轴逻辑:演示态使用 mock + 真实态使用真实数据
|
|
|
+ farmTimeline: (() => {
|
|
|
+ // 判断是否有真实时间轴数据
|
|
|
+ const hasRealTimeline = Array.isArray(data.farmTimeline) && data.farmTimeline.length > 0
|
|
|
+
|
|
|
+ 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 (data.produceDate && !stageSet.has('harvest')) {
|
|
|
+ timeline.push({
|
|
|
+ time: data.produceDate,
|
|
|
+ stage: 'harvest',
|
|
|
+ title: '成熟采收',
|
|
|
+ desc: '本批次产品进入成熟采收阶段'
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 安全检测
|
|
|
+ if (data.reports && data.reports.length > 0 && !stageSet.has('test')) {
|
|
|
+ const validDates = data.reports
|
|
|
+ .map(r => r.reportDate)
|
|
|
+ .filter(d => d && /^\d{4}-\d{2}-\d{2}/.test(d))
|
|
|
+ .sort()
|
|
|
+ if (validDates.length > 0) {
|
|
|
+ timeline.push({
|
|
|
+ time: validDates[0],
|
|
|
+ stage: 'test',
|
|
|
+ title: '安全检测',
|
|
|
+ desc: '已完成安全检测,相关结果可查'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 包装出库
|
|
|
+ if (data.packageDate && !stageSet.has('pack')) {
|
|
|
+ timeline.push({
|
|
|
+ time: data.packageDate,
|
|
|
+ stage: 'pack',
|
|
|
+ title: '包装出库',
|
|
|
+ desc: '已完成包装整理,进入销售流通环节'
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // stage 顺序权重
|
|
|
+ const stageOrder = {
|
|
|
+ planting: 1,
|
|
|
+ care: 2,
|
|
|
+ inspection: 3,
|
|
|
+ harvest: 4,
|
|
|
+ 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 || '',
|
|
|
+ status: data.camera?.status || 'online',
|
|
|
+ desc: data.camera?.liveUrl
|
|
|
+ ? '当前为种植现场实时画面,现场环境与种植状态可见'
|
|
|
+ : '来自农场实景画面,种植环境真实呈现'
|
|
|
}
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
+// 种植现场状态:三态逻辑
|
|
|
+const cameraStatus = computed(() => {
|
|
|
+ const camera = traceDetail.value?.camera
|
|
|
+ if (!camera) return 'offline'
|
|
|
+
|
|
|
+ const hasLiveUrl = !!camera.liveUrl
|
|
|
+
|
|
|
+ // 有 liveUrl 但无封面图 → loading(信号接入中)
|
|
|
+ // 有 liveUrl 且有封面图 → online(可播放)
|
|
|
+ // 无 liveUrl → offline
|
|
|
+ if (hasLiveUrl && !camera.coverImage) {
|
|
|
+ return 'loading'
|
|
|
+ }
|
|
|
+ if (hasLiveUrl || camera.coverImage) {
|
|
|
+ return 'online'
|
|
|
+ }
|
|
|
+ return 'offline'
|
|
|
+})
|
|
|
+
|
|
|
+const cameraStatusText = computed(() => {
|
|
|
+ const map = {
|
|
|
+ loading: '连接中',
|
|
|
+ online: '实时在线',
|
|
|
+ offline: '暂未连接'
|
|
|
+ }
|
|
|
+ return map[cameraStatus.value] || '暂未连接'
|
|
|
+})
|
|
|
+
|
|
|
const reportImages = computed(() => {
|
|
|
const items = traceDetail.value?.report?.items
|
|
|
if (Array.isArray(items)) {
|
|
|
@@ -1033,6 +1330,273 @@ function previewDoc(kind, index) {
|
|
|
background: rgba(252, 248, 244, 0.74);
|
|
|
}
|
|
|
|
|
|
+/* 检测结论卡:结论型卡片,比普通资料卡更有结论感 */
|
|
|
+.cardConclusion {
|
|
|
+ background: rgba(252, 250, 244, 0.82);
|
|
|
+}
|
|
|
+
|
|
|
+.conclusionBody {
|
|
|
+ padding-top: 6rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.conclusionLead {
|
|
|
+ display: block;
|
|
|
+ font-size: 32rpx;
|
|
|
+ font-weight: 680;
|
|
|
+ line-height: 1.5;
|
|
|
+ letter-spacing: 0.01em;
|
|
|
+ color: rgba(24, 38, 30, 0.96);
|
|
|
+ margin-bottom: 14rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.conclusionDesc {
|
|
|
+ display: block;
|
|
|
+ font-size: 20rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 1.9;
|
|
|
+ color: rgba(84, 106, 93, 0.5);
|
|
|
+}
|
|
|
+
|
|
|
+/* 农事时间轴:关键种植过程 */
|
|
|
+.cardTimeline {
|
|
|
+ background: rgba(252, 250, 244, 0.82);
|
|
|
+}
|
|
|
+
|
|
|
+.timelineList {
|
|
|
+ padding-top: 4rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineItem {
|
|
|
+ display: flex;
|
|
|
+ padding-bottom: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineItem:last-child {
|
|
|
+ padding-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineLeft {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ width: 40rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineDot {
|
|
|
+ width: 12rpx;
|
|
|
+ height: 12rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(39, 113, 76, 0.5);
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineLine {
|
|
|
+ width: 1rpx;
|
|
|
+ flex: 1;
|
|
|
+ background: rgba(39, 113, 76, 0.2);
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineRight {
|
|
|
+ flex: 1;
|
|
|
+ padding-left: 24rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineTime {
|
|
|
+ display: block;
|
|
|
+ font-size: 19rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ color: rgba(84, 106, 93, 0.5);
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+ letter-spacing: 0.02em;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineTitle {
|
|
|
+ display: block;
|
|
|
+ font-size: 27rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: rgba(27, 41, 33, 0.88);
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+ line-height: 1.3;
|
|
|
+}
|
|
|
+
|
|
|
+.timelineDesc {
|
|
|
+ display: block;
|
|
|
+ font-size: 21rpx;
|
|
|
+ font-weight: 400;
|
|
|
+ line-height: 1.7;
|
|
|
+ color: rgba(84, 106, 93, 0.75);
|
|
|
+}
|
|
|
+
|
|
|
+/* 关键节点(安全检测):轻强调 */
|
|
|
+.timelineItem.is-key .timelineDot {
|
|
|
+ background: rgba(39, 113, 76, 0.7);
|
|
|
+}
|
|
|
+
|
|
|
+.timelineItem.is-key .timelineTitle {
|
|
|
+ font-weight: 600;
|
|
|
+ color: rgba(27, 41, 33, 0.9);
|
|
|
+}
|
|
|
+
|
|
|
+/* 当前节点(包装出库):主高亮 */
|
|
|
+.timelineItem.is-current .timelineDot {
|
|
|
+ background: #2f7d55;
|
|
|
+ box-shadow: 0 0 0 6rpx rgba(47, 125, 85, 0.12);
|
|
|
+}
|
|
|
+
|
|
|
+.timelineItem.is-current .timelineTitle {
|
|
|
+ font-weight: 700;
|
|
|
+ color: rgba(20, 60, 40, 0.95);
|
|
|
+}
|
|
|
+
|
|
|
+/* 底部可信来源说明 */
|
|
|
+.timelineFootnote {
|
|
|
+ margin-top: 20rpx;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: rgba(84, 106, 93, 0.45);
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
+/* 种植现场实时画面模块 */
|
|
|
+.cardLive {
|
|
|
+ background: rgba(252, 250, 244, 0.82);
|
|
|
+}
|
|
|
+
|
|
|
+.liveMediaWrap {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+ height: 380rpx;
|
|
|
+ border-radius: 20rpx;
|
|
|
+ overflow: hidden;
|
|
|
+ background: linear-gradient(
|
|
|
+ 160deg,
|
|
|
+ rgba(39, 113, 76, 0.04) 0%,
|
|
|
+ rgba(39, 113, 76, 0.015) 50%,
|
|
|
+ rgba(39, 113, 76, 0.05) 100%
|
|
|
+ );
|
|
|
+ border: 1rpx solid rgba(39, 75, 57, 0.08);
|
|
|
+ box-shadow: inset 0 2rpx 12rpx rgba(39, 113, 76, 0.04);
|
|
|
+}
|
|
|
+
|
|
|
+.liveCover {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlaceholder {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ background: transparent;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlayIcon {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlayDot {
|
|
|
+ width: 8rpx;
|
|
|
+ height: 8rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(39, 113, 76, 0.35);
|
|
|
+ animation: livePulse 1.4s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlayDot.dot2 {
|
|
|
+ animation-delay: 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlayDot.dot3 {
|
|
|
+ animation-delay: 0.4s;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes livePulse {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 0.3;
|
|
|
+ transform: scale(0.8);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 1;
|
|
|
+ transform: scale(1.1);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.livePlaceholderMain {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(39, 113, 76, 0.5);
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+
|
|
|
+.livePlaceholderSub {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: rgba(84, 106, 93, 0.35);
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusPill {
|
|
|
+ position: absolute;
|
|
|
+ top: 20rpx;
|
|
|
+ right: 20rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8rpx;
|
|
|
+ padding: 8rpx 18rpx;
|
|
|
+ border-radius: 30rpx;
|
|
|
+ font-size: 20rpx;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusDot {
|
|
|
+ width: 10rpx;
|
|
|
+ height: 10rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusPill.online {
|
|
|
+ background: rgba(47, 125, 85, 0.1);
|
|
|
+ color: #2f7d55;
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusPill.online .liveStatusDot {
|
|
|
+ background: #2f7d55;
|
|
|
+ animation: statusPulse 2s ease-in-out infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes statusPulse {
|
|
|
+ 0%, 100% {
|
|
|
+ opacity: 1;
|
|
|
+ box-shadow: 0 0 0 0 rgba(47, 125, 85, 0.4);
|
|
|
+ }
|
|
|
+ 50% {
|
|
|
+ opacity: 0.7;
|
|
|
+ box-shadow: 0 0 0 4rpx rgba(47, 125, 85, 0);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusPill.offline {
|
|
|
+ background: rgba(84, 106, 93, 0.08);
|
|
|
+ color: rgba(84, 106, 93, 0.55);
|
|
|
+}
|
|
|
+
|
|
|
+.liveStatusPill.offline .liveStatusDot {
|
|
|
+ background: rgba(84, 106, 93, 0.4);
|
|
|
+}
|
|
|
+
|
|
|
+.liveDesc {
|
|
|
+ margin-top: 16rpx;
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: rgba(84, 106, 93, 0.5);
|
|
|
+ line-height: 1.6;
|
|
|
+}
|
|
|
+
|
|
|
/* ================================================
|
|
|
微动效:让页面"活起来"
|
|
|
================================================ */
|
|
|
@@ -1070,6 +1634,11 @@ function previewDoc(kind, index) {
|
|
|
animation: cardEnter 0.5s cubic-bezier(0.22, 0.78, 0.35, 1) 0.15s both;
|
|
|
}
|
|
|
|
|
|
+/* 检测结论卡 */
|
|
|
+.cardConclusion {
|
|
|
+ animation: cardEnter 0.5s cubic-bezier(0.22, 0.78, 0.35, 1) 0.2s both;
|
|
|
+}
|
|
|
+
|
|
|
/* 农场卡 */
|
|
|
.cardFarm {
|
|
|
animation: cardEnter 0.5s cubic-bezier(0.22, 0.78, 0.35, 1) 0.25s both;
|
|
|
@@ -1264,8 +1833,8 @@ function previewDoc(kind, index) {
|
|
|
|
|
|
.sectionTitle {
|
|
|
font-size: 27rpx;
|
|
|
- font-weight: 680;
|
|
|
- color: rgba(60, 72, 64, 0.82);
|
|
|
+ font-weight: 620;
|
|
|
+ color: rgba(60, 72, 64, 0.72);
|
|
|
}
|
|
|
|
|
|
.statusBadge {
|