Bläddra i källkod

feat:添加时间线组件

wangsisi 4 dagar sedan
förälder
incheckning
36ef15fb9e

+ 0 - 240
src/components/pageComponents/AgriculturalInteractionCard.vue

@@ -1,240 +0,0 @@
-<template>
-    <!-- 只封装时间轴区域 -->
-    <div class="timeline">
-        <div class="timeline-item" v-for="timelineItem in item.timelineList" :key="timelineItem.id">
-            <div class="timeline-left">
-                <div class="dot"></div>
-                <div class="line"></div>
-            </div>
-            <div class="timeline-right">
-                <div class="date">
-                    <span class="work-name">{{ timelineItem.farmWorkName }}</span>
-                    <!-- <span v-show="timelineItem.expectedRisk">({{ timelineItem.expectedRisk }})</span> -->
-                    <span class="ignore-btn" v-if="hasPlanPermission" @click="handleIgnore(item,timelineItem)"> 忽略 </span>
-                </div>
-                <div class="text">
-                    预计报价<span class="price">{{ timelineItem.estimatedCost }}元</span>
-                    <span class="action-detail" @click="toDetail(timelineItem, item)"
-                        >查看详情</span
-                    >
-                    <!-- <span class="action-detail" @click="showPriceSheetPopup(timelineItem.farmWorkId, item)">查看报价单</span> -->
-                </div>
-            </div>
-            <div class="timeline-action" @click="handleTimelineAction(timelineItem, item.farmId)">转入农事任务</div>
-        </div>
-    </div>
-    <!-- 新增:激活上传弹窗 -->
-    <active-upload-popup ref="activeUploadPopupRef" @handleUploadSuccess="handleUploadSuccess"></active-upload-popup>
-
-    <!-- 服务报价单 -->
-    <price-sheet-popup ref="priceSheetPopupRef"></price-sheet-popup>
-
-    <popup v-model:show="showTaskPopup" z-index="10000" round class="task-tips-popup">
-        <img class="create-farm-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
-        <div class="create-farm-text">
-            <div>
-                您确认忽略 <span class="main-text">{{ currentTask?.farmName }}</span> 的
-                <span class="main-text">{{ currentTask?.farmWorkName }}</span> 农事吗
-            </div>
-        </div>
-        <div class="create-farm-btn" @click="handlePopupBtn">确认忽略</div>
-    </popup>
-</template>
-
-<script setup>
-import { Popup } from "vant";
-import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
-import priceSheetPopup from "@/components/popup/priceSheetPopup.vue";
-import { computed, onMounted, ref } from "vue";
-import { ElMessage } from "element-plus";
-import { useRouter } from "vue-router";
-
-const router = useRouter();
-
-const props = defineProps({
-    item: {
-        type: Object,
-        required: true,
-        default: () => ({}),
-    },
-});
-
-// 检查是否有"转入农事"权限
-const hasPlanPermission = computed(() => {
-    try {
-        const userInfoStr = localStorage.getItem("localUserInfo");
-        if (!userInfoStr) return false;
-        const userInfo = JSON.parse(userInfoStr);
-        const permissions = userInfo.agriculturalPermissions || [];
-        return permissions.includes("转入农事");
-    } catch (error) {
-        console.error("解析用户信息失败:", error);
-        return false;
-    }
-});
-
-
-const executorList = ref(JSON.parse(sessionStorage.getItem("executorList")) || []);
-const activeUploadPopupRef = ref(null);
-const handleTimelineAction = (timelineItem, farmId) => {
-    if (hasPlanPermission.value) {
-        activeUploadPopupRef.value.showPopup({
-            gardenIdVal: farmId,
-            needExecutorVal: true,
-            problemTitleVal: "请选择 " + timelineItem.farmWorkName + " 执行截止时间",
-            imgDescVal: "请上传凭证(转入农事任务凭证)",
-            arrangeIdVal: timelineItem.arrangeId,
-            executorListVal: executorList.value || [],
-            farmWorkIdVal: timelineItem.farmWorkId,
-            schemeIdVal: timelineItem.schemeId,
-        });
-    } else {
-        ElMessage.warning("您暂无权限操作");
-    }
-};
-
-const emits = defineEmits(["updateList"]);
-
-const handleUploadSuccess = async () => {
-    emits("updateList");
-};
-
-const toDetail = (timelineItem, item) => {
-    console.log(timelineItem, item);
-    router.push({
-        path: "/detail_work",
-        query: { miniJson: JSON.stringify({ id: timelineItem.farmWorkId, arrangeId: timelineItem.arrangeId, farmId: item.farmId, }), },
-    });
-};
-
-// 忽略农事库
-const currentTask = ref(null);
-const showTaskPopup = ref(false);
-const handleIgnore = (item, timelineItem) => {
-    currentTask.value = { ...item, ...timelineItem };
-    showTaskPopup.value = true;
-};
-
-const handlePopupBtn = () => {
-    VE_API.home.ignoreFarmWorkLib({ farmWorkLibId: currentTask.value.farmWorkId }).then(({ code }) => {
-        if (code === 0) {
-            showTaskPopup.value = false;
-            ElMessage.success("忽略成功");
-            handleUploadSuccess();
-        }
-    });
-};
-
-const priceSheetPopupRef = ref(null);
-const showPriceSheetPopup = (id, item) => {
-    priceSheetPopupRef.value.handleShowPopup({ id, farmId: item.farmId, agriculturalId: item.agriculturalStoreId });
-};
-</script>
-
-<style scoped lang="scss">
-.timeline {
-    margin-left: -5px;
-    margin-top: 8px;
-    .timeline-item {
-        display: flex;
-        align-items: flex-start;
-        font-size: 14px;
-        color: #ffffff;
-        line-height: 22px;
-        & + .timeline-item {
-            margin-top: 10px;
-        }
-        .timeline-left {
-            width: 22px;
-            display: flex;
-            flex-direction: column;
-            align-items: center;
-            .dot {
-                width: 6px;
-                height: 6px;
-                border-radius: 50%;
-                background: #a2d5fd;
-                margin-top: 6px;
-            }
-            .line {
-                border-left: 1px dashed #a2d5fd;
-                margin-top: 4px;
-                height: 28px;
-            }
-        }
-        .timeline-right {
-            padding-left: 5px;
-            flex: 1;
-            .date {
-                color: #1d2129;
-                font-weight: 500;
-                font-size: 14px;
-                line-height: 22px;
-            }
-            .text {
-                font-size: 12px;
-                color: #d7d7d7;
-                .price {
-                    padding-left: 4px;
-                }
-                .action-detail {
-                    margin-left: 6px;
-                    color: #2199f8;
-                    border-bottom: 1px solid;
-                }
-            }
-            .work-name {
-                padding-left: 4px;
-            }
-            .ignore-btn {
-                margin-left: 6px;
-                color: rgba(29, 33, 41, 0.4);
-                font-size: 13px;
-            }
-        }
-        .timeline-action {
-            align-self: center;
-            height: 28px;
-            line-height: 28px;
-            flex: none;
-            background: rgba(33, 153, 248, 0.1);
-            color: #2199f8;
-            font-size: 12px;
-            padding: 0px 11px;
-            border-radius: 20px;
-        }
-    }
-}
-.task-tips-popup {
-    width: 90%;
-    padding: 28px 28px 20px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    .create-farm-icon {
-        width: 52px;
-        height: 52px;
-        margin-bottom: 12px;
-    }
-    .create-farm-text {
-        font-size: 19px;
-        font-weight: 500;
-        margin-bottom: 32px;
-        text-align: center;
-    }
-    .main-text {
-        color: #2199f8;
-    }
-    .create-farm-btn {
-        width: 100%;
-        box-sizing: border-box;
-        padding: 8px;
-        border-radius: 25px;
-        font-size: 16px;
-        background: #2199f8;
-        color: #fff;
-        text-align: center;
-    }
-}
-</style>

+ 0 - 64
src/components/pageComponents/CommonBox.vue

@@ -1,64 +0,0 @@
-<template>
-    <div class="common-box">
-        <div class="common-title">
-            <div class="common-title-left">
-                <img src="@/assets/img/home/label-icon.png" alt="" />
-                <span>{{ title }}</span>
-            </div>
-            <div class="common-title-right">
-                <slot name="right"></slot>
-            </div>
-        </div>
-        <div class="common-content">
-            <slot></slot>
-        </div>
-    </div>
-</template>
-
-<script setup>
-defineProps({
-    title: {
-        type: String,
-        default: "",
-    },
-});
-</script>
-
-<style scoped lang="scss">
-.common-box {
-    padding: 16px 12px;
-    background: #fff;
-    border-radius: 8px;
-
-    .common-title {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        font-size: 16px;
-        color: #000;
-        padding-bottom: 12px;
-        border-bottom: 1px solid #f5f5f5;
-        .common-title-left {
-            display: flex;
-            align-items: center;
-            gap: 6px;
-            font-weight: 500;
-            img {
-                width: 14px;
-                height: 8px;
-            }
-        }
-        .common-title-right {
-            font-size: 13px;
-            color: rgba(0, 0, 0, 0.6);
-        }
-    }
-
-    .common-content {
-        margin-top: 12px;
-    }
-}
-.common-box + .common-box {
-    margin-top: 12px;
-}
-</style>

+ 126 - 0
src/components/pageComponents/PhenologyTrackTimelineItem.vue

@@ -0,0 +1,126 @@
+<template>
+    <div class="phenology-track-item">
+        <div class="track-axis">
+            <span class="track-date-side">{{ date }}</span>
+            <div class="track-line-wrap">
+                <span class="track-dot"></span>
+                <div class="track-line"></div>
+            </div>
+        </div>
+        <div class="track-card">
+            <div class="track-card-inner">
+                <span class="track-badge">{{ date }}</span>
+                <span class="track-content">{{ content }}</span>
+            </div>
+            <div v-if="hasImages" class="track-images">
+                <img v-for="(src, i) in images" :key="i" class="track-thumb" :src="src" alt="" />
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { computed } from "vue";
+
+const props = defineProps({
+    date: {
+        type: String,
+        default: "",
+    },
+    content: {
+        type: String,
+        default: "",
+    },
+    images: {
+        type: Array,
+        default: () => [],
+    },
+});
+
+const hasImages = computed(
+    () => Array.isArray(props.images) && props.images.length > 0
+);
+</script>
+
+<style scoped lang="scss">
+/* Grid:左侧时间轴整列与右侧卡片等高,竖线随卡片高度自适应 */
+.phenology-track-item {
+    display: grid;
+    grid-template-columns: auto 1fr;
+    gap: 10px;
+    align-items: stretch;
+
+    .track-axis {
+        display: flex;
+        flex-direction: column;
+
+        .track-date-side {
+            color: #7e7e7e;
+        }
+
+        .track-line-wrap {
+            display: flex;
+            flex: 1;
+            flex-direction: column;
+            align-items: center;
+            margin-top: 3px;
+
+            .track-dot {
+                width: 6px;
+                height: 6px;
+                border-radius: 50%;
+                background: rgba(29, 33, 41, 0.2);
+                z-index: 1;
+            }
+
+            .track-line {
+                flex: 1;
+                margin-top: 2px;
+                border-left: 1px dashed rgba(29, 33, 41, 0.2);
+            }
+        }
+    }
+
+    .track-card {
+        flex: 1;
+        padding: 10px;
+        border-radius: 6px;
+        background: #f5f5f5;
+
+        .track-card-inner {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding: 5px 8px;
+            border-radius: 5px;
+            background: #ffffff;
+
+            .track-badge {
+                padding: 2px 4px;
+                border-radius: 2px;
+                font-size: 13px;
+                color: #ffffff;
+                background: #2199f8;
+            }
+
+            .track-content {
+                color: rgba(60, 60, 60, 0.5);
+            }
+        }
+
+        .track-images {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 12px;
+            margin-top: 10px;
+
+            .track-thumb {
+                width: 56px;
+                height: 56px;
+                border-radius: 8px;
+                object-fit: cover;
+            }
+        }
+    }
+}
+</style>

+ 0 - 782
src/components/taskItem.vue

@@ -1,782 +0,0 @@
-<template>
-    <div class="farm-item" :class="{ done: props.status, ['farm-item-' + itemIndex]: itemIndex }">
-        <div class="item-top">
-            <div class="top-l">
-                <el-popover
-                    placement="top"
-                    trigger="click"
-                >
-                    <template #reference>
-                        <div class="item-name">{{ itemData?.farmWorkName }}</div>
-                    </template>
-                    <div class="popover-content">{{ itemData?.farmWorkName }}</div>
-                </el-popover>
-                <div class="item-time" v-if="itemIndex === 0">
-                    {{
-                        itemData?.expectedExecuteDate
-                            ? itemData?.expectedExecuteDate
-                            : itemData?.executeDeadlineDate
-                            ? "截止到" + itemData?.executeDeadlineDate
-                            : ""
-                    }}
-                </div>
-                <div class="item-time" v-if="itemIndex !== 3">{{ itemData?.executeDate }}</div>
-                <div class="item-tag" v-if="itemIndex === 3">
-                    {{ farmWorkType[itemData?.farmWorkType || 0] }}
-                </div>
-            </div>
-            <div class="top-r" @click="toDetail(props.status, itemData.id, itemData.farmWorkLibId)">
-                {{ props.status === 0 ? "" : "查看详情" }}
-            </div>
-        </div>
-        <div class="item-box" v-if="props.status === 0">
-            <div class="img-text-wrap">
-                <div class="left-wrap">
-                    <div class="left-img">
-                        <img src="@/assets/img/home/farm.png" alt="" />
-                    </div>
-                    <div class="right-text">
-                        <div class="farm-info">
-                            {{ itemData?.farmName }}
-                            <div class="info-tag-wrap">
-                                <!-- <div class="tag-item second" v-if="itemData?.farmArea">
-                                    {{ formatArea(itemData?.farmArea) }}亩
-                                </div> -->
-                                <div class="tag-item primary">{{ itemData?.typeName }}</div>
-                                <!-- <div class="tag-item second">
-                                    普通客户
-                                </div> -->
-                                <div class="tag-item warning">托管客户</div>
-                            </div>
-                        </div>
-                        <div class="farm-addr">{{ itemData?.address }}</div>
-                    </div>
-                </div>
-            </div>
-            <slot name="footer"></slot>
-        </div>
-        <div class="item-box" v-else>
-            <div class="item-desc">
-                <div class="desc-info">
-                    <div class="desc-info-item">
-                        <span>药物处方:</span>
-                        <span class="value">{{ prescriptionText }}</span>
-                    </div>
-                </div>
-                <div class="desc-info" v-if="(itemIndex === 1 || itemIndex === 3) && itemData?.reviewDate">
-                    <div class="desc-info-item">
-                        <span>复核时间:</span>
-                        <span class="value">{{ itemData?.reviewDate }}</span>
-                    </div>
-                </div>
-                <div class="desc-info" v-if="(itemIndex === 2 && itemData?.reviewIntervalDays) || (itemIndex === 3 && itemData?.reviewIntervalDays && !itemData?.reviewDate)">
-                    <div class="desc-info-item">
-                        <span>复核时间:</span>
-                        <span class="value">{{ reviewDate }}</span>
-                    </div>
-                </div>
-                <div class="desc-info" v-if="itemIndex === 2 && !itemData.reviewImage?.length && daysDiff > 0">
-                    <div class="desc-info-item expired-text">
-                        <span
-                            >距离农事执行已 <span class="val-text">{{ daysDiff }}</span
-                            >天,请抓紧时间复核!</span
-                        >
-                    </div>
-                </div>
-                <div v-if="(itemIndex === 1 || itemIndex === 3) && itemData.reviewImage?.length" class="review-image two-image">
-                    <!-- 第一张:执行照片 -->
-                    <div class="review-image-item" v-if="itemData.executeEvidenceList?.length">
-                        <photo-provider :photo-closable="true">
-                            <photo-consumer
-                                :key="itemData.executeEvidenceList[0]"
-                                :src="base_img_url2 + itemData.executeEvidenceList[0]"
-                            >
-                                <div class="review-image-item-title">执行照片</div>
-                                <div class="img-item">
-                                    <img :src="base_img_url2 + itemData.executeEvidenceList[0] + resize" class="view-box" />
-                                </div>
-                            </photo-consumer>
-                        </photo-provider>
-                    </div>
-                    <!-- 第二张:复核照片 -->
-                    <div class="review-image-item" v-if="itemData.reviewImage?.length">
-                        <photo-provider :photo-closable="true">
-                            <photo-consumer
-                                :key="itemData.reviewImage[0]"
-                                :src="base_img_url2 + itemData.reviewImage[0]"
-                            >
-                                <div class="review-image-item-title">复核照片</div>
-                                <div class="img-item">
-                                    <img :src="base_img_url2 + itemData.reviewImage[0] + resize" class="view-box" />
-                                </div>
-                            </photo-consumer>
-                        </photo-provider>
-                    </div>
-                </div>
-                <div class="review-image" v-else-if="itemData?.executeEvidenceList?.length">
-                    <div class="review-image-item">
-                        <photo-provider :photo-closable="true">
-                            <photo-consumer
-                                v-for="src in itemData.executeEvidenceList.slice(0, 2)"
-                                :key="src"
-                                :src="base_img_url2 + src"
-                            >
-                                <div class="review-image-item-title">执行照片</div>
-                                <div class="img-item">
-                                    <img :src="base_img_url2 + src + resize" class="view-box" />
-                                </div>
-                            </photo-consumer>
-                        </photo-provider>
-                    </div>
-                </div>
-            </div>
-
-            <slot name="footer"></slot>
-
-            <!-- <div class="item-footer" v-if="props.status === 1">
-                <div
-                    class="footer-l farm-name-text van-ellipsis"
-                    :style="{ width: detailItem?.reviewImage?.length ? 'calc(100% - 100px)' : 'calc(100% - 180px)' }"
-                >
-                    来自<span class="name-text">{{ itemData.farmName || "未知农场" }}</span>
-                </div>
-                <div class="footer-r">
-                    <div class="btn warning" @click="generateReport">生成成果报告</div>
-                </div>
-            </div> -->
-        </div>
-    </div>
-
-    <!-- 处方卡片 -->
-    <Teleport to="body">
-        <detail-dialog ref="detailDialogRef" />
-    </Teleport>
-
-    <!-- 分享农事成效弹窗 -->
-    <review-popup ref="reviewPopupRef" />
-
-    <upload-execute ref="uploadExecuteRef" :onlyShare="true" />
-
-    <!-- 上传照片弹窗 -->
-    <review-upload-popup v-model="showUpload" :record-id="sectionId" @success="handleUploadSuccess" />
-</template>
-
-<script setup>
-import { onMounted, ref, computed } from "vue";
-import { useRouter } from "vue-router";
-import { base_img_url2 } from "@/api/config";
-import { ElMessage, ElMessageBox } from "element-plus";
-import detailDialog from "@/components/detailDialog.vue";
-import uploadExecute from "@/views/old_mini/task_condition/components/uploadExecute.vue";
-import reviewPopup from "@/views/old_mini/task_condition/components/reviewPopup.vue";
-import reviewUploadPopup from "@/components/popup/reviewUploadPopup.vue";
-
-const resize = "?x-oss-process=image/resize,p_120/format,webp/quality,q_100";
-
-const props = defineProps({
-    status: {
-        type: Number,
-        default: 0,
-    },
-    itemData: {
-        type: Object,
-        default: () => ({}),
-    },
-    // 0: 待完成,1:已完成,2:待复核,3:农户版已完成
-    itemIndex: {
-        type: Number,
-        default: 0,
-    },
-});
-
-const uploadExecuteRef = ref(null);
-const router = useRouter();
-const detailDialogRef = ref(null);
-const reviewPopupRef = ref(null);
-const toPage = () => {
-    router.push("/report_detail");
-};
-
-const farmWorkType = {
-    0: "预警农事",
-    1: "标准农事",
-    2: "建议农事",
-    3: "自建农事",
-};
-
-const handleFollow = ({ farmId, isFollow }) => {
-    ElMessageBox.confirm(`${isFollow ? "确定要取消关注该农场吗?" : "确定要关注该农场吗?"}`, "提示", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning",
-    })
-        .then(() => {
-            const api = isFollow ? VE_API.z_farm_work_record.unfollowFarm : VE_API.z_farm_work_record.followFarm;
-            api({ farmId }).then(({ code, msg }) => {
-                if (code === 0) {
-                    ElMessage.success(msg);
-                    emit("handleFollowSuccess");
-                } else {
-                    ElMessage.error(msg);
-                }
-            });
-        })
-        .catch(() => {});
-};
-
-// 计算距离执行时间的天数差
-const daysDiff = computed(() => {
-    if (!props.itemData?.executeDate) {
-        return 0;
-    }
-
-    const executeDate = new Date(props.itemData.executeDate);
-    const today = new Date();
-
-    // 将时间设置为 00:00:00,只比较日期
-    executeDate.setHours(0, 0, 0, 0);
-    today.setHours(0, 0, 0, 0);
-
-    // 计算天数差(毫秒转天数)
-    const diffTime = executeDate.getTime() - today.getTime();
-    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
-
-    return diffDays;
-});
-
-// 计算复核时间:executeDate + reviewIntervalDays
-const reviewDate = computed(() => {
-    if (!props.itemData?.executeDate) {
-        return "--";
-    }
-
-    const executeDate = new Date(props.itemData.executeDate);
-    const reviewIntervalDays = Number(props.itemData?.reviewIntervalDays || 0);
-
-    // 将执行日期加上间隔天数
-    executeDate.setDate(executeDate.getDate() + reviewIntervalDays);
-
-    // 格式化为 YYYY-MM-DD
-    const year = executeDate.getFullYear();
-    const month = String(executeDate.getMonth() + 1).padStart(2, "0");
-    const day = String(executeDate.getDate()).padStart(2, "0");
-
-    return `${year}-${month}-${day}`;
-});
-
-const remindUser = () => {
-    uploadExecuteRef.value.showPopup({ ...props.itemData, type: "remindUser" });
-};
-
-const showUpload = ref(false);
-const sectionId = ref(null);
-const handleUploadPhoto = () => {
-    showUpload.value = true;
-    sectionId.value = props.itemData.id;
-};
-
-const emit = defineEmits(["handleUploadSuccess", "handleFollowSuccess"]);
-
-const detailItem = ref({});
-
-// 更新触发图片(上传成功后调用)
-const updateTriggerImg = async () => {
-    if (!props.itemData?.id) return;
-
-    // 重新获取详情
-    await getItemDetail(props.itemData.id);
-    // 如果是待完成状态(status === 0),详情已经更新,itemData 会在 getSimpleList 后更新
-};
-
-const handleUploadSuccess = async () => {
-    emit("handleUploadSuccess");
-};
-
-onMounted(async () => {
-    // 若已带处方列表,直接生成;否则在需要时拉取详情后生成
-    if (props.itemData && Array.isArray(props.itemData.prescriptionList)) {
-        prescriptionText.value = buildPrescriptionText(props.itemData.prescriptionList);
-    } else if (props.status === 1 && props.itemData?.id) {
-        await getItemDetail(props.itemData.id);
-        if (Array.isArray(detailItem.value?.prescriptionList)) {
-            prescriptionText.value = buildPrescriptionText(detailItem.value.prescriptionList);
-        }
-    }
-});
-
-// 将所有需要暴露的方法放在 defineExpose 中
-defineExpose({
-    updateTriggerImg,
-});
-const prescriptionText = ref("");
-function buildPrescriptionText(list) {
-    if (!list || !Array.isArray(list) || list.length === 0 || list[0]?.pesticideFertilizerList?.length === 0) {
-        return "无处方";
-    }
-    try {
-        return list
-            .map((group) =>
-                (group.pesticideFertilizerList || [])
-                    .map((p) => p.defaultName || p.pesticideFertilizerName || "")
-                    .filter(Boolean)
-                    .join("+")
-            )
-            .filter(Boolean)
-            .join("+");
-    } catch {
-        return "无处方";
-    }
-}
-async function getItemDetail(id) {
-    const { data } = await VE_API.z_farm_work_record.getDetail({ id });
-    detailItem.value = data[0];
-}
-
-// 保留方法名以兼容,但同步返回已生成的文案
-const getPrescriptionInfo = () => prescriptionText.value;
-
-function formatArea(val) {
-    const num = typeof val === "number" ? val : parseFloat(val);
-    if (Number.isNaN(num)) return val;
-    return Number.isInteger(num) ? num : num.toFixed(2);
-}
-
-const toDetail = (status, id, farmWorkId) => {
-    if (status === 1 || status === 3) {
-        // 复核成效
-        router.push({
-            path: "/review_work",
-            query: { miniJson: JSON.stringify({ id, goBack: true }) },
-        });
-    } else {
-        detailDialogRef.value.showDialog(farmWorkId, "", false);
-    }
-};
-</script>
-
-<style lang="scss" scoped>
-.farm-item {
-    background: #cfe9ff;
-    border-radius: 14px;
-    &.done {
-        background: #ebebeb;
-        .item-top {
-            .item-name {
-                background: #c5c5c5;
-                border: none;
-                color: #fff;
-            }
-        }
-        .item-desc .copy-info .copy-text {
-            color: #000000;
-        }
-        .top-r {
-            color: rgba(0, 0, 0, 0.6);
-        }
-    }
-    &.farm-item-3 {
-        background: #fff;
-        border: 1px solid rgba(0, 0, 0, 0.1);
-        border-radius: 5px;
-        .item-top {
-            .item-name {
-                padding: 0;
-                background: none;
-                border: none;
-                color: #000000;
-            }
-        }
-        .item-box {
-            border-color: #F5F5F5;
-        }
-    }
-    .top-r {
-        flex: none;
-    }
-    &.hall {
-        background: #cfe9ff;
-        .item-top {
-            .item-name {
-                color: #ffffff;
-                border: none;
-                background: linear-gradient(180deg, #7bc5ff, #2199f8);
-            }
-        }
-    }
-    .item-top {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 8px 12px;
-        color: #2199f8;
-        font-size: 14px;
-        .top-l {
-            display: flex;
-            align-items: center;
-            font-size: 16px;
-            color: #000000;
-        }
-        .item-time {
-            padding-right: 8px;
-            flex: none;
-        }
-        .item-tag {
-            font-size: 12px;
-            color: #000000;
-            padding: 0 10px;
-            background: rgba(119, 119, 119, 0.1);
-            border-radius: 20px;
-            font-weight: normal;
-            height: 26px;
-            line-height: 26px;
-        }
-        .item-name {
-            margin-right: 8px;
-            color: #2199f8;
-            padding: 0 10px;
-            height: 29px;
-            border-radius: 4px;
-            border: 1px solid #2199f8;
-            line-height: 29px;
-            background: #fff;
-            max-width: 200px;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-            cursor: pointer;
-        }
-    }
-    .item-box {
-        background: #fff;
-        border-radius: 10px 10px 14px 14px;
-        border: 1px solid rgba(183, 183, 183, 0.1);
-        padding: 10px 12px;
-    }
-    .item-footer {
-        margin-top: 10px;
-        padding-top: 11px;
-        border-top: 1px solid rgba(0, 0, 0, 0.1);
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        font-size: 12px;
-        .footer-l {
-            color: #8b8b8b;
-            font-size: 12px;
-            &.primary-btn {
-                display: inline-flex;
-                align-items: center;
-                border: 1px solid #2199f8;
-                background: rgba(33, 153, 248, 0.1);
-                padding: 0 12px;
-                height: 32px;
-                box-sizing: border-box;
-                display: flex;
-                align-items: center;
-                border-radius: 20px;
-                color: #2199f8;
-                .share-icon {
-                    width: 12px;
-                    padding-right: 4px;
-                }
-            }
-            &.farm-name-text {
-                font-size: 14px;
-                color: #6f7274;
-                width: calc(100% - 180px);
-                .name-text {
-                    padding-left: 4px;
-                }
-            }
-        }
-        .footer-r {
-            display: flex;
-            align-items: center;
-            .btn {
-                height: 32px;
-                line-height: 32px;
-                padding: 0 12px;
-                border-radius: 20px;
-                display: flex;
-                align-items: center;
-                box-sizing: border-box;
-                &.second {
-                    // border: 1px solid #8B8B8B;
-                    // color: #8B8B8B;
-                    color: #2199f8;
-                    background: rgba(33, 153, 248, 0.1);
-                }
-                &.primary {
-                    background: #2199f8;
-                    color: #fff;
-                }
-                .btn-icon {
-                    padding-right: 4px;
-                }
-                &.warning {
-                    color: #ff953d;
-                    background: #fff;
-                    border: 1px solid #ff953d;
-                }
-                &.secondary-text {
-                    color: #2199f8;
-                    border: 1px solid #2199f8;
-                }
-                &.photo-text {
-                    background: #2199f8;
-                    color: #fff;
-                }
-            }
-            .btn + .btn {
-                margin-left: 8px;
-            }
-        }
-    }
-    .farm-text {
-        margin-bottom: 10px;
-        background: rgba(183, 183, 183, 0.1);
-        padding: 6px 8px;
-        border-radius: 5px;
-        font-size: 12px;
-        color: #909090;
-        line-height: 1.5;
-        overflow: hidden;
-        display: -webkit-box;
-        -webkit-line-clamp: 2;
-        -webkit-box-orient: vertical;
-        line-clamp: 2;
-        word-break: break-all;
-        .text-title {
-            color: #424242;
-        }
-        .text-more {
-            padding-left: 10px;
-            color: #2199f8;
-            white-space: nowrap;
-        }
-    }
-    .img-text-wrap {
-        display: flex;
-        align-items: flex-start;
-        justify-content: space-between;
-        .left-wrap {
-            display: flex;
-            align-items: center;
-            .left-img {
-                width: 40px;
-                height: 40px;
-                border-radius: 6px;
-                img {
-                    width: 100%;
-                    height: 100%;
-                    object-fit: contain;
-                }
-            }
-            .right-text {
-                padding-left: 8px;
-                .farm-info {
-                    font-size: 14px;
-                    color: #1d2129;
-                    display: flex;
-                    align-items: center;
-                    .info-tag-wrap {
-                        margin-left: 10px;
-                        display: flex;
-                        align-items: center;
-                        gap: 4px;
-                        .tag-item {
-                            height: 20px;
-                            line-height: 20px;
-                            padding: 0 8px;
-                            border-radius: 2px;
-                            font-size: 12px;
-                            &.second {
-                                color: #848282;
-                                background: rgba(148, 148, 148, 0.1);
-                            }
-                            &.primary {
-                                color: #2199f8;
-                                background: #e8f3ff;
-                            }
-                            &.warning {
-                                color: #ff953d;
-                                background: rgba(255, 149, 61, 0.1);
-                            }
-                        }
-                    }
-                }
-                .farm-addr {
-                    padding-top: 2px;
-                    font-size: 12px;
-                    color: #86909c;
-                }
-            }
-        }
-        .right-wrap {
-            display: flex;
-            align-items: center;
-            color: #ff953d;
-            font-size: 12px;
-            flex: none;
-            .click-item {
-                display: flex;
-                align-items: center;
-                gap: 2px;
-            }
-            .follow-text {
-                color: #d0d0d0;
-            }
-        }
-    }
-    .title-wrap {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        .title-l {
-            display: flex;
-            align-items: center;
-            .setting-text {
-                padding-left: 11px;
-                font-size: 12px;
-                color: #2199f8;
-            }
-        }
-        .title-r {
-            color: rgba(255, 131, 29, 0.36);
-            font-size: 12px;
-        }
-    }
-    .item-title {
-        font-size: 16px;
-        font-weight: 500;
-        margin: 2px 0 5px 0;
-    }
-    .item-desc {
-        font-size: 14px;
-        color: #bbbbbb;
-        line-height: 18px;
-        .desc-info {
-            display: flex;
-            .desc-info-item {
-                flex: 1;
-                .value {
-                    color: #666666;
-                }
-            }
-            .expired-text {
-                background: linear-gradient(90deg, rgba(33, 153, 248, 0.2) 0%, rgba(33, 153, 248, 0) 100%);
-            }
-            &.two-text {
-                .value {
-                    font-size: 12px;
-                    padding-top: 4px;
-                }
-            }
-            .expired-day {
-                color: #ff953d;
-            }
-            .expired-text {
-                margin-top: 4px;
-                padding: 6px 8px;
-                background: linear-gradient(90deg, rgba(33, 153, 248, 0.2), rgba(33, 153, 248, 0));
-                border-radius: 4px;
-                color: #2e2e2e;
-                font-size: 12px;
-                width: 100%;
-                .val-text {
-                    color: #2199f8;
-                }
-            }
-        }
-        .desc-info + .desc-info {
-            padding-top: 6px;
-        }
-        .pt-10 {
-            padding-top: 10px;
-        }
-
-        .two-image {
-            display: flex;
-            align-items: center;
-            gap: 8px;
-        }
-
-        .review-image {
-            padding-top: 6px;
-            .review-image-item {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-                position: relative;
-                flex: 1;
-                .review-image-item-title {
-                    position: absolute;
-                    top: 0;
-                    left: 0;
-                    background: rgba(54, 52, 52, 0.6);
-                    padding: 4px 10px;
-                    border-radius: 8px 0 8px 0;
-                    backdrop-filter: 4px;
-                    font-size: 12px;
-                    color: #fff;
-                }
-                ::v-deep {
-                    .PhotoConsumer {
-                        width: 100%;
-                        height: 100%;
-                        position: relative;
-                    }
-                }
-            }
-            img {
-                width: 100%;
-                height: 106px;
-                object-fit: cover;
-                border-radius: 8px;
-            }
-        }
-        .copy-info {
-            margin-top: 4px;
-            .address {
-                max-width: 80%;
-                .value {
-                    color: #666666;
-                }
-            }
-            .copy-text {
-                margin-left: 8px;
-                color: #2199f8;
-            }
-        }
-        .report-text {
-            margin-top: 10px;
-            color: #ff953d;
-            font-size: 12px;
-            height: 32px;
-            line-height: 32px;
-            border-radius: 5px;
-            display: flex;
-            align-items: center;
-            padding-left: 11px;
-            background: linear-gradient(90deg, rgba(255, 149, 61, 0.1), rgba(255, 149, 61, 0));
-            .done-icon {
-                background: #ff953d;
-                width: 10px;
-                height: 10px;
-                border-radius: 50%;
-                margin-right: 5px;
-            }
-            .right-icon {
-                margin-left: 10px;
-            }
-        }
-    }
-}
-
-.popover-content {
-    font-size: 14px;
-    color: #000;
-    line-height: 1.5;
-    word-break: break-all;
-}
-</style>

+ 32 - 0
src/views/old_mini/recordDetails/index.vue

@@ -38,6 +38,15 @@
                             <div class="confirm-btn">确认上传</div>
                         </div>
                     </div>
+                    <div class="phenology-track-section">
+                        <PhenologyTrackTimelineItem
+                            v-for="(row, idx) in phenologyTrackList"
+                            :key="idx"
+                            :date="row.date"
+                            :content="row.content"
+                            :images="row.images"
+                        />
+                    </div>
                 </div>
             </div>
         </div>
@@ -63,6 +72,7 @@
 
 <script setup>
 import customHeader from "@/components/customHeader.vue";
+import PhenologyTrackTimelineItem from "@/components/pageComponents/PhenologyTrackTimelineItem.vue";
 import { ref, onMounted } from 'vue';
 import { useRouter } from 'vue-router';
 import IndexMap from "./map/index.js";
@@ -79,6 +89,21 @@ const mapContainer = ref(null);
 const location = ref(null);
 const input = ref('');
 
+/** 物候跟踪时间轴示例数据,接入接口后可替换 */
+const phenologyTrackList = ref([
+    { date: '04/18', content: '有 60%已经来花了', images: [] },
+    {
+        date: '04/18',
+        content: '有 60% 进入 红黄叶 阶段',
+        images: [
+            'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
+            'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
+            'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
+            'https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png',
+        ],
+    },
+]);
+
 onMounted(() => {
     location.value = "POINT(113.6142086995688 23.585836479509055)";
     indexMap.initMap(location.value, mapContainer.value);
@@ -188,6 +213,13 @@ onMounted(() => {
             .card-wrap+.card-wrap {
                 margin-top: 10px;
             }
+
+            .phenology-track-section {
+                margin-top: 15px;
+                display: flex;
+                flex-direction: column;
+                gap: 16px;
+            }
         }
     }