huangxw 2 veckor sedan
förälder
incheckning
7e19af2fcb

+ 3 - 0
src/assets/styles/public.scss

@@ -164,3 +164,6 @@ $colors: (
     width: 38rpx;
     height: 38rpx;
 }
+.info-border-bottom {
+    border-bottom: 1rpx solid #F2F2F2;
+}

+ 209 - 145
src/components/ut-album/ut-album.vue

@@ -1,190 +1,254 @@
 <template>
-    <view class="ut-album" :style="albumStyle">
-        <view v-for="(item, index) in displayUrls" :key="index" class="album-item" :style="getItemStyle(index)"
-            @click="handleItemClick(index)">
-            <image :src="getThumbnailUrl(index)" :mode="singleMode" class="album-image" />
-            <!-- 超出数量显示 +N -->
-            <view v-if="index === maxCount - 1 && urls.length > maxCount" class="album-more">
-                <text class="more-text">+{{ urls.length - maxCount }}</text>
+    <view class="ut-album-card" :style="albumStyle">
+        <template v-for="(item, index) in displayItems" :key="index">
+            <view class="card-item" :style="{ width: itemSize, height: itemSize }" @click="onPreview(index)">
+                <!-- 图片缩略图 -->
+                <template v-if="item.type === 'image'">
+                    <image class="thumb" :src="getThumb(item)" mode="aspectFill" />
+                </template>
+
+                <!-- 视频占位图 or 封面 -->
+                <template v-else-if="item.type === 'video'">
+                    <view class="video-box">
+                        <image v-if="item.coverUrl" class="thumb" :src="item.coverUrl" mode="aspectFill" />
+                        <view v-else class="video-placeholder">VIDEO</view>
+                        <view class="play-mask">
+                            <up-icon name="play-circle" color="#fff" size="48rpx"></up-icon>
+                        </view>
+                    </view>
+                </template>
+
+                <!-- 文件卡片 -->
+                <template v-else>
+                    <view class="file-box d-flex flex-cln">
+                        <view class="flex1">
+                            <view class="f-s-28 c-primary up-line-2">{{ item.name || '文件' }}</view>
+                            <view class="f-s-24 c-999">{{ formatSize(item.size) }}</view>
+                        </view>
+                        <view class="d-flex j-ed">
+                            <image :src="getFileIconByUrl(item.url)" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
+                        </view>
+                    </view>
+                </template>
+
+                <!-- 超出数量显示 +N -->
+                <view v-if="index === maxCount - 1 && items.length > maxCount" class="album-more">
+                    <text class="more-text">+{{ items.length - maxCount }}</text>
+                </view>
             </view>
-        </view>
+        </template>
     </view>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue';
+import { computed } from 'vue';
+import { fileExt, isUrl } from '@/utils/ruoyi';
+import { getFileIconByUrl } from '@/utils/common';
 
+// 兼容旧用法:urls,同时支持 modelValue 作为输入
 const props = defineProps({
-    urls: {
-        type: Array,
-        default: []
-    },
-    singleMode: {
-        type: String,
-        default: 'aspectFill'
-    },
-    space: {
-        type: String,
-        default: '14rpx'
-    },
-    rowCount: {
-        type: Number,
-        default: 3
-    },
-    keyName: {
-        type: String,
-        default: ''
-    },
-    unit: {
-        type: String,
-        default: 'rpx'
-    },
-    // 统一的图片尺寸(单个和多个都使用这个)
-    multipleSize: {
-        type: String,
-        default: '160rpx'
-    },
-    // 兼容旧版本的单张图片尺寸(如果没有设置multipleSize则使用这个)
-    singleMaxSize: {
-        type: String,
-        default: '180rpx'
-    },
-    previewFullImage: {
-        type: Boolean,
-        default: true
-    },
-    maxCount: {
-        type: Number,
-        default: 9
-    },
-    // 缩略图系数
-    factor: {
-        type: Number,
-        default: 4
-    },
-    t: {
-        type: String,
-        default: '0'
-    }
-})
-
-// 显示的图片数组(限制数量)
-const displayUrls = computed(() => {
-    return props.urls.slice(0, props.maxCount);
+    // 通用输入:字符串 | 字符串数组 | 对象/对象数组
+    modelValue: { type: [String, Array, Object], default: null },
+    urls: { type: [Array, String, Object], default: () => [] },
+    keyName: { type: String, default: 'url' },
+    nameKey: { type: String, default: 'fileName' },
+    sizeKey: { type: String, default: 'fileSize' },
+    coverKey: { type: String, default: 'coverUrl' },
+    maxCount: { type: Number, default: 9 },
+    // 卡片网格样式
+    space: { type: String, default: '10rpx' },
+    multipleSize: { type: String, default: '210rpx' },
+    // 预览
+    previewFullImage: { type: Boolean, default: true },
+    // 缩略图缩放因子(像素)
+    factor: { type: Number, default: 4 },
+    // 缓存破坏参数
+    t: { type: String, default: '0' }
 });
 
-// 计算相册容器样式
-const albumStyle = computed(() => {
-    return {
-        display: 'flex',
-        flexWrap: 'wrap',
-        gap: props.space,
-        width: '100%'
-    };
-});
+const itemSize = computed(() => props.multipleSize);
 
-// 统一的图片尺寸(单个和多个都使用相同尺寸)
-const getImageSize = () => {
-    // 统一使用 multipleSize
-    return props.multipleSize;
-};
-
-// 获取原始图片URL
-const getImageUrl = (index) => {
-    const item = props.urls[index];
-    if (props.keyName && typeof item === 'object') {
-        return item[props.keyName] || '';
-    }
-    return item || '';
-}
-
-// 获取缩略图URL
-const getThumbnailUrl = (index) => {
-    const url = getImageUrl(index);
-    if (!url) return '';
+const albumStyle = computed(() => ({ display: 'flex', flexWrap: 'wrap', gap: props.space, width: '100%' }));
 
-    const size = uni.$u.getPx(getImageSize()) * props.factor;
+const inputValue = computed(() => (props.modelValue !== null && props.modelValue !== undefined ? props.modelValue : props.urls));
 
-    return `${url}?w=${size}&h=${size}&t=${props.t}`;
-}
+// 解析后缀,识别文件类型
+const getTypeByUrl = (url) => {
+    if (!url) return 'file';
+    const u = (url.split('?')[0] || '').toLowerCase();
+    const ext = (u.split('.').pop() || '').trim();
+    const imgExt = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
+    const videoExt = ['mp4', 'mov', 'avi', 'mkv', 'webm', 'm4v'];
+    if (imgExt.includes(ext)) return 'image';
+    if (videoExt.includes(ext)) return 'video';
+    return 'file';
+};
 
-// 获取原图URL(移除参数)
-const getOriginalUrl = (index) => {
-    const url = getImageUrl(index);
-    return url ? url.split('?')[0] : '';
-}
+// 将输入统一为 items
+const items = computed(() => {
+    const v = inputValue.value;
+    const out = [];
+    if (!v) return out;
+    const pushItem = (raw) => {
+        if (!raw) return;
+        let url = '';
+        let name = '';
+        let size = undefined;
+        let coverUrl = '';
+        if (typeof raw === 'string') {
+            url = raw;
+        } else if (typeof raw === 'object') {
+            url = raw[props.keyName] || raw.url || '';
+            name = raw[props.nameKey] || raw.name || '';
+            size = raw[props.sizeKey] || raw.size;
+            coverUrl = raw[props.coverKey] || raw.coverUrl || '';
+        }
+        if (!url) return;
+        const originalUrl = (url.split('?')[0] || url);
+        const type = getTypeByUrl(originalUrl);
+        out.push({ type, url, originalUrl, name, size, coverUrl });
+    };
+    if (Array.isArray(v)) {
+        v.forEach(pushItem);
+    } else {
+        pushItem(v);
+    }
+    return out;
+});
 
-// 获取所有原图URL数组
-const getAllOriginalUrls = () => {
-    return props.urls.map((item, index) => getOriginalUrl(index));
-}
+const displayItems = computed(() => items.value.slice(0, props.maxCount));
 
-// 获取单个图片项的样式 - 统一宽高
-const getItemStyle = (index) => {
-    const size = getImageSize();
+// 生成图片缩略图地址(保持原图预览)
+const getThumb = (item) => {
+    if (!item?.url) return '';
+    const size = (uni?.$u?.getPx ? uni.$u.getPx(props.multipleSize) : 160) * props.factor;
+    // 保留原有参数的简单拼接策略
+    return `${item.url}?w=${size}&h=${size}&t=${props.t}`;
+};
 
-    // 无论单张还是多张,都使用相同的尺寸
-    return {
-        width: size,
-        height: size,
-        flexShrink: 0
-    };
-}
+const formatSize = (bytes) => {
+    if (!bytes && bytes !== 0) return '';
+    const kb = bytes / 1024;
+    if (kb < 1024) return kb.toFixed(2) + ' KB';
+    const mb = kb / 1024;
+    return mb.toFixed(2) + ' MB';
+};
 
-// 处理图片点击
-const handleItemClick = (index) => {
-    if (props.previewFullImage) {
-        previewImg(index);
-    }
-}
+// 预览逻辑:图片/视频/文件
+const onPreview = (displayIndex) => {
+    const item = displayItems.value[displayIndex];
+    if (!item) return;
+    if (item.type === 'image') return previewImages(item);
+    if (item.type === 'video') return previewVideo(item, displayIndex);
+    return previewFile(item);
+};
 
-// 预览图片(使用原图)
-const previewImg = (index) => {
+const previewImages = (clickedItem) => {
     if (!props.previewFullImage) return;
+    const allImages = items.value.filter((i) => i.type === 'image');
+    const urls = allImages.map((i) => i.originalUrl);
+    const current = urls.indexOf(clickedItem.originalUrl);
+    if (!urls.length) return;
+    uni.previewImage({ urls, current: current >= 0 ? current : 0 });
+};
 
-    const originalUrls = getAllOriginalUrls();
+const previewVideo = (item, index) => {
+    // #ifdef MP-WEIXIN
+    const allVideos = items.value.filter((i) => i.type === 'video');
+    const sources = allVideos.map((i) => ({ url: i.url, type: 'video', poster: i.coverUrl || '' }));
+    const current = allVideos.findIndex((i) => i.url === item.url);
+    // @ts-ignore
+    wx.previewMedia({ sources, current: current >= 0 ? current : 0 });
+    // #endif
+    // #ifndef MP-WEIXIN
+    uni.showToast({ title: '当前平台暂不支持视频预览', icon: 'none' });
+    // #endif
+};
 
-    if (originalUrls.length > 0) {
-        uni.previewImage({
-            urls: originalUrls,
-            current: index
-        });
+const previewFile = (item) => {
+    // 优先打开本地文件
+    if (item.url && !isUrl(item.url)) {
+        return uni.openDocument({ filePath: item.url, showMenu: true });
     }
+    if (!item.url || !isUrl(item.url)) return;
+    uni.showLoading({ title: '打开中...' });
+    uni.downloadFile({
+        url: item.url,
+        success: (res) => {
+            uni.openDocument({
+                filePath: res.tempFilePath,
+                showMenu: true,
+                success: () => uni.hideLoading(),
+                fail: () => {
+                    uni.hideLoading();
+                    uni.showToast({ title: '打开文件失败', icon: 'none' });
+                }
+            });
+        },
+        fail: () => {
+            uni.hideLoading();
+            uni.showToast({ title: '文件下载失败', icon: 'none' });
+        }
+    });
 };
 </script>
 
 <style lang="scss" scoped>
-.ut-album {
+.ut-album-card {
+    display: flex;
+    gap: 10rpx;
+    flex-wrap: wrap;
     width: 100%;
-
-    .album-item {
-        position: relative;
+    .card-item {
+        background-color: #FAFAFA;
         border-radius: 8rpx;
         overflow: hidden;
-        border: 1rpx solid #f8f8f8;
-
-
-        .album-image {
+        border: 1rpx solid #ccc;
+        position: relative;
+        .thumb {
             width: 100%;
             height: 100%;
-            border-radius: 8rpx;
             object-fit: cover;
-            box-sizing: border-box;
             background-color: #ccc;
+            display: block;
+        }
+        .video-box {
+            width: 100%;
+            height: 100%;
+            position: relative;
+            .video-placeholder {
+                width: 100%;
+                height: 100%;
+                color: #fff;
+                background: #000;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                font-size: 28rpx;
+            }
+            .play-mask {
+                position: absolute;
+                top: 0;left: 0;right: 0;bottom: 0;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                background: rgba(0,0,0,0.15);
+            }
+        }
+        .file-box {
+            width: 100%;
+            height: 100%;
+            box-sizing: border-box;
+            padding: 20rpx;
         }
-
         .album-more {
             position: absolute;
-            top: 0;
-            left: 0;
-            right: 0;
-            bottom: 0;
+            top: 0;left: 0;right: 0;bottom: 0;
             background: rgba(0, 0, 0, 0.2);
             display: flex;
             align-items: center;
             justify-content: center;
             border-radius: 8rpx;
-
             .more-text {
                 color: #fff;
                 font-size: 32rpx;

+ 2 - 1
src/main.ts

@@ -2,7 +2,7 @@ import { createSSRApp } from 'vue';
 import App from './App.vue';
 import pinia from './store';
 import 'uno.css';
-import { selectDictLabel, selectDictLabels } from './utils/ruoyi';
+import { previewImage, selectDictLabel, selectDictLabels } from './utils/ruoyi';
 import { useDict } from '@/utils/dict';
 import uviewPlus, { setConfig } from 'uview-plus';
 import { navigateBackOrHome, showToast } from '@/utils/common';
@@ -86,6 +86,7 @@ export function createApp() {
     app.config.globalProperties.selectDictLabel = selectDictLabel;
     app.config.globalProperties.selectDictLabels = selectDictLabels;
     app.config.globalProperties.useDict = useDict;
+    app.config.globalProperties.previewImage = previewImage;
     return {
         app,
         Pinia: { pinia },

+ 6 - 3
src/pages/plant/base/index.vue

@@ -95,16 +95,16 @@
                                     </view>
                                 </view>
                                 <view class="c-333 f-s-28 pd-5">
-                                    <text class="c-#666">基地地址:</text>
+                                    <text class="c-#666">基地地址</text>
                                     <text class="f-w-5">{{ item?.gapInfo?.address || '-' }}</text>
                                 </view>
                                 <view class="c-333 f-s-28 pd-5 d-flex">
-                                    <text class="c-#666 w-s-no">当前在地品种:</text>
+                                    <text class="c-#666 w-s-no">当前在地品种</text>
                                     <text class="ov-hd tx-ov w-s-no f-w-5">{{ item.plantingVarieties?.map((items) => items.variety).join('、') || '-' }}</text>
                                     <text v-if="item.plantingVarieties?.length" class="flex1 w-s-no f-w-5">等{{ item.plantingVarieties?.length }}个品种</text>
                                 </view>
                                 <view class="pd-10"></view>
-                                <view class="p-rtv">
+                                <view v-if="item.gapInfo?.basePic" class="p-rtv">
                                     <up-image width="100%" :src="item.gapInfo?.basePic" mode="widthFix"> </up-image>
                                     <view class="pl-20 pr-20 pt-10 pb-10 bg-#00000080 c-#ccc f-s-20" style="position: absolute; bottom: 140rpx; right: 0; border-radius: 10rpx 0 0 10rpx"> {{ item?.contactName }}负责</view>
                                     <view class="pl-20 pr-20 pt-10 pb-10 bg-#00000080 c-#ccc f-s-20" style="position: absolute; bottom: 80rpx; right: 0; border-radius: 10rpx 0 0 10rpx"> 公司+合作社</view>
@@ -311,6 +311,9 @@ onMounted(() => {
         })
         .exec();
     getSpecies();
+    uni.$on('refreshBaseList', () => {
+       onRefresh();
+    });
 });
 onShow(() => {
     getSpecies();

+ 310 - 5
src/plant/base/base-detail/index.vue

@@ -1,5 +1,5 @@
 <template>
-    <z-paging ref="paging" bgColor="#F7F7F7" safe-area-inset-bottom paging-class="paging-btm-shadow" scroll-with-animation>
+    <z-paging ref="paging" bgColor="#F7F7F7" safe-area-inset-bottom paging-class="paging-btm-shadow" refresher-only @onRefresh="onRefresh" scroll-with-animation>
         <template #top>
             <ut-navbar title="基地详情" :fixed="false" border></ut-navbar>
             <view class="bg-fff mb-20">
@@ -7,30 +7,334 @@
                 <ut-tabs v-model="activeTab" :tabs="tabs" mode="btw" @change="changeActiveTab"></ut-tabs>
             </view>
         </template>
-      
+        <view class="p-rtv">
+            <!-- 基本信息 -->
+            <view ref="baseRef" class="pd-24" id="base12345">
+                <view class="startline-title">基本信息</view>
+            </view>
+            <view class="pd-24 bg-#fff mb-10">
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地类型:</span>
+                    <span class="c-#333">{{ selectDictLabel(pt_base_type, form?.baseInfo?.baseType) || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地名称:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.baseName || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地编号:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.baseCode || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">建设时间:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.buildDate || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地组织方式:</span>
+                    <span class="c-#333">{{ selectDictLabel(pt_org_type, form?.baseInfo?.orgType) || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地负责人:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.contactName || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">联系电话:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.contactTel || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">是否为GAP基地:</span>
+                    <span class="c-#333">{{ selectDictLabel(yes_no, form?.baseInfo?.gapFlag) || '-' }}</span>
+                </view>
+                <view v-if="form?.baseInfo?.gapId" class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">关联GAP信息:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.gapBaseInfo?.name || form?.baseInfo?.gapId || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地所在地区:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.gapInfo?.adcodeName || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">详细地址:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.gapInfo?.address || '-' }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <view class="f-s-30 c-#666 mb-10">基地范围:</view>
+                    <view class="bg-#f7f7f7 d-flex ov-hd p-rtv">
+                        <image @click="previewImage([form?.baseInfo?.gapInfo?.basePic])" class="w-full" v-if="form?.baseInfo?.gapInfo?.basePic" preview :src="form?.baseInfo?.gapInfo?.basePic" mode="'widthFix'"></image>
+                        <view v-else class="w-full h-120 d-flex a-c j-c c-999">暂无基地范围</view>
+                    </view>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地面积:</span>
+                    <span class="c-#333">{{ form?.baseInfo?.gapInfo?.area || '-' }}{{ form?.baseInfo?.gapInfo?.areaUnit || mapUnitByBaseType[form?.baseInfo?.baseType || '1'] }}</span>
+                </view>
+                <view class="f-s-30 pd2-16-0 info-border-bottom">
+                    <span class="c-#666">基地经纬度:</span>
+                    <span class="c-#333">E{{ form?.baseInfo?.gapInfo?.lng || '-' }}, N{{ form?.baseInfo?.gapInfo?.lat || '-' }}</span>
+                </view>
+            </view>
+            <!-- 地块/圈舍/组培架信息 -->
+            <view ref="plotRef" class="pd-24" id="plot12345">
+                <view class="startline-title" v-if="form?.baseInfo?.baseType == '1'">地块信息</view>
+                <view class="startline-title" v-else-if="form?.baseInfo?.baseType == '2'">圈舍信息</view>
+                <view class="startline-title" v-else-if="form?.baseInfo?.baseType == '3'">组培架信息</view>
+            </view>
+            <view class="pd-24 bg-#fff mb-10">
+                <template v-if="Array.isArray(form?.landInfoList) && form.landInfoList.length">
+                    <view v-for="(item, index) in form.landInfoList" :key="index" class="plot-item pd-24 mb-20">
+                        <view class="d-flex mb-16">
+                            <view class="f-s-32 f-w-5 c-#333 flex1 mr-10">{{ item?.landName || '-' }}</view>
+                            <view v-if="item?.contactName" class="f-s-24 c-#333">负责人:{{ item?.contactName }}</view>
+                        </view>
+                        <template v-if="form?.baseInfo?.baseType == '3'">
+                            <view class="d-flex flex-wrap gap-10">
+                                <view class="f-s-28 c-#666 w-310">
+                                    <span>层数:</span>
+                                    <span class="c-#333 f-w-5">{{ item?.layers || '-' }}</span>
+                                </view>
+                                <view class="f-s-28 c-#666 w-310">
+                                    <span>每层容量:</span>
+                                    <span class="c-#333 f-w-5">{{ item?.capacityAmount || '-' }}{{ item?.capacityUnit || '-' }}</span>
+                                </view>
+                                <view class="f-s-28 c-#666 w-310">
+                                    <span>总容量:</span>
+                                    <span class="c-#333 f-w-5">{{ (item?.capacityAmount || 0) * (item?.layers || 0) || '-' }}{{ item?.capacityUnit || '-' }}</span>
+                                </view>
+                                <view class="f-s-28 c-#666 w-310">
+                                    <span v-if="item?.lightType" class="mr-10">{{ selectDictLabel(pt_light_type, item?.lightType) }}</span>
+                                    <span v-if="item?.tempeStatus">{{ +item?.tempeStatus ? '有独立温控' : '无独立温控' }}</span>
+                                </view>
+                            </view>
+                        </template>
+                        <template v-else>
+                            <view class="f-s-28 c-#666">
+                                <span>面积:</span>
+                                <span class="c-#333 f-w-5">{{ item?.area || '-' }}{{ item?.areaUnit || '-' }}</span>
+                            </view>
+                        </template>
+                    </view>
+                </template>
+                <view v-else class="c-999 f-s-28">暂无信息</view>
+            </view>
+
+            <!-- 选址依据及环境信息 -->
+            <view class="pd-24" id="environment12345">
+                <view class="startline-title">选址依据及环境信息</view>
+            </view>
+            <!-- 基地类型 1:种植基地 -->
+            <template v-if="form?.baseInfo?.baseType == '1'">
+                <view class="pd-24 bg-#fff mb-10">
+                    <view class="f-s-32 c-#333 f-w-600">选址依据</view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">是否道地产区:</span>
+                        <span class="c-#333">{{ selectDictLabel(yes_no, form?.environmentInfo?.daoStatus) || '-' }}</span>
+                    </view>
+                    <view v-if="form?.environmentInfo?.adaptFile?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地选址依据/标准:</view>
+                        <ut-album :urls="form?.environmentInfo?.adaptFile"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.accordFile?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">适应性证明材料:</view>
+                        <ut-album :urls="form?.environmentInfo?.accordFile"></ut-album>
+                    </view>
+                </view>
+                <view class="pd-24 bg-#fff mb-10">
+                    <view class="f-s-32 c-#333 f-w-600">环境信息</view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">土壤类型:</span>
+                        <span class="c-#333">{{ selectDictLabel(pt_soil_type, form?.environmentInfo?.soilType) || '-' }}</span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">土壤质地:</span>
+                        <span class="c-#333">{{ selectDictLabel(pt_soil_texture, form?.environmentInfo?.soilTexture) || '-' }}</span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">有机质含量:</span>
+                        <span class="c-#333">{{ form?.environmentInfo?.organic || '-' }}</span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">土壤PH值:</span>
+                        <span class="c-#333">{{ form?.environmentInfo?.soilPh || '-' }}</span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">水源类型:</span>
+                        <span class="c-#333">{{ selectDictLabel(pt_water_type, form?.environmentInfo?.waterType) || '-' }}</span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">无霜期:</span><span class="c-#333">{{ form?.environmentInfo?.notFrost || '-' }}<text v-if="form?.environmentInfo?.notFrost">天</text></span>
+                    </view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年降水量:</span><span class="c-#333">{{ form?.environmentInfo?.precipitation || '-' }}<text v-if="form?.environmentInfo?.precipitation">mm</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年平均气温:</span><span class="c-#333">{{ form?.environmentInfo?.avgTem || '-' }}<text v-if="form?.environmentInfo?.avgTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年绝对最高气温:</span><span class="c-#333">{{ form?.environmentInfo?.maxTem || '-' }}<text v-if="form?.environmentInfo?.maxTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年绝对最低气温:</span><span class="c-#333">{{ form?.environmentInfo?.minTem || '-' }}<text v-if="form?.environmentInfo?.minTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年日照时数:</span><span class="c-#333">{{ form?.environmentInfo?.sunshineHours || '-' }}<text v-if="form?.environmentInfo?.sunshineHours">小时</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">海拔:</span><span class="c-#333">{{ form?.environmentInfo?.altitude || '-' }}<text v-if="form?.environmentInfo?.altitude">米</text></span></view
+                    >
+                    <view v-if="form?.environmentInfo?.report?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">水、土壤、大气等环评报告:</view>
+                        <ut-album :urls="form?.environmentInfo?.report"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.basePic" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地图片:</view>
+                        <ut-album :urls="form?.environmentInfo?.basePic"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.baseVoice?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地视频:</view>
+                        <ut-album :urls="form?.environmentInfo?.baseVoice"></ut-album>
+                    </view>
+                </view>
+            </template>
+
+            <!-- 基地类型 2:养殖基地 -->
+            <template v-else-if="form?.baseInfo?.baseType == '2'">
+                <view class="pd-24 bg-#fff mb-10">
+                    <view class="f-s-32 c-#333 f-w-600">选址依据</view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom">
+                        <span class="c-#666">是否道地产区:</span>
+                        <span class="c-#333">{{ selectDictLabel(yes_no, form?.environmentInfo?.daoStatus) || '-' }}</span>
+                    </view>
+                    <view v-if="form?.environmentInfo?.adaptFile?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地选址依据/标准:</view>
+                        <ut-album :urls="form?.environmentInfo?.adaptFile"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.accordFile?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">适应性证明材料:</view>
+                        <ut-album :urls="form?.environmentInfo?.accordFile"></ut-album>
+                    </view>
+                </view>
+                <view class="pd-24 bg-#fff mb-10">
+                    <view class="f-s-32 c-#333 f-w-600">环境信息</view>
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">水源类型:</span><span class="c-#333">{{ selectDictLabel(pt_water_type, form?.environmentInfo?.waterType) || '-' }}</span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">无霜期:</span><span class="c-#333">{{ form?.environmentInfo?.notFrost || '-' }}<text v-if="form?.environmentInfo?.notFrost">天</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年降水量:</span><span class="c-#333">{{ form?.environmentInfo?.precipitation || '-' }}<text v-if="form?.environmentInfo?.precipitation">mm</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年平均气温:</span><span class="c-#333">{{ form?.environmentInfo?.avgTem || '-' }}<text v-if="form?.environmentInfo?.avgTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年绝对最高气温:</span><span class="c-#333">{{ form?.environmentInfo?.maxTem || '-' }}<text v-if="form?.environmentInfo?.maxTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年绝对最低气温:</span><span class="c-#333">{{ form?.environmentInfo?.minTem || '-' }}<text v-if="form?.environmentInfo?.minTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">年日照时数:</span><span class="c-#333">{{ form?.environmentInfo?.sunshineHours || '-' }}<text v-if="form?.environmentInfo?.sunshineHours">小时</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">海拔:</span><span class="c-#333">{{ form?.environmentInfo?.altitude || '-' }}<text v-if="form?.environmentInfo?.altitude">米</text></span></view
+                    >
+                    <view v-if="form?.environmentInfo?.report?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">水、大气等环评报告:</view>
+                        <ut-album :urls="form?.environmentInfo?.report"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.basePic" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地图片:</view>
+                        <ut-album :urls="form?.environmentInfo?.basePic"></ut-album>
+                    </view>
+                    <view v-if="form?.environmentInfo?.baseVoice?.length" class="f-s-30 pd2-16-0 info-border-bottom">
+                        <view class="c-#666 mb-10">基地视频:</view>
+                        <ut-album :urls="form?.environmentInfo?.baseVoice"></ut-album>
+                    </view>
+                </view>
+            </template>
+
+            <!-- 基地类型 3:组培基地 -->
+            <template v-else-if="form?.baseInfo?.baseType == '3'">
+                <view class="pd-24 bg-#fff mb-10">
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">温度:</span><span class="c-#333">{{ form?.environmentInfo?.avgTem || '-' }}<text v-if="form?.environmentInfo?.avgTem">℃</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">湿度:</span><span class="c-#333">{{ form?.environmentInfo?.humidity || '-' }}<text v-if="form?.environmentInfo?.humidity">%</text></span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">洁净度:</span><span class="c-#333">{{ form?.environmentInfo?.clean || '-' }}</span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">光照:</span><span class="c-#333">{{ form?.environmentInfo?.light || '-' }}</span></view
+                    >
+                    <view class="f-s-30 pd2-16-0 info-border-bottom"
+                        ><span class="c-#666">光照周期:</span><span class="c-#333">{{ form?.environmentInfo?.lightCycle || '-' }}</span></view
+                    >
+                </view>
+            </template>
+        </view>
         <template #bottom>
             <view class="pd-20 d-flex">
-                
+                <up-button class="mr-20" color="#18BECA">去标记为三无一全基地{{ '>' }}</up-button>
+                <up-button @click="" type="primary">修改</up-button>
             </view>
         </template>
     </z-paging>
 </template>
 <script setup lang="ts">
 import { useClientRequest } from '@/utils/request';
+import { getUrlParams, recursiveDecodeURIComponent } from '@/utils/ruoyi';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { yes_no, pt_org_type, pt_base_type, pt_water_type, pt_soil_type, pt_soil_texture, pt_light_type, pt_capacity_unit } = toRefs<any>(proxy?.useDict('yes_no', 'pt_org_type', 'pt_base_type', 'pt_water_type', 'pt_soil_type', 'pt_soil_texture', 'pt_light_type', 'pt_capacity_unit'));
 const tabs = reactive([
     { label: '基本信息', value: 'base' },
     { label: '地块信息', value: 'plot' },
     { label: '选址依据及环境信息', value: 'environment' },
 ]);
 const activeTab = ref('base');
+const paging = ref<any>(null);
 const mapUnitByBaseType: any = {
     '1': '亩',
     '2': '平方米',
     '3': '平方米',
 };
-const changeActiveTab = (value: string) => {
-    activeTab.value = value;
+const form = ref<any>({});
+// 将字符串 / 数组(string|object[]) 统一为 string[]
+const toUrlArray = (val: any) => {
+    if (!val) return [] as string[];
+    if (typeof val === 'string') return val ? [val] : [];
+    if (Array.isArray(val)) {
+        if (!val.length) return [];
+        if (typeof val[0] === 'string') return val as string[];
+        return (val as any[]).map((i: any) => i?.url).filter(Boolean);
+    }
+    if (typeof val === 'object' && (val as any).url) return [(val as any).url];
+    return [] as string[];
+};
+const changeActiveTab = (value: any) => {
+    const v = typeof value === 'string' ? value : value?.value;
+    const id = `${v}12345`;
+    paging.value?.scrollIntoViewById(id, 30, true);
+};
+const did = ref('');
+// 根据id获取基地详情
+const getDetailById = async (id: string) => {
+    const res = await useClientRequest.get(`/plt-api/app/base/getInfoAllById/${id}`);
+    if (res && res.code === 200) {
+        form.value = res.data || {};
+    }
 };
+const onRefresh = () => {
+    getDetailById(did.value);
+    paging.value?.complete();
+};
+
+onLoad((options: any) => {
+    did.value = options?.id || getUrlParams(recursiveDecodeURIComponent(options?.q))?.id || '';
+    getDetailById(did.value);
+});
 </script>
 <style lang="scss" scoped>
 .z-paging-wrap {
@@ -48,6 +352,7 @@ const changeActiveTab = (value: string) => {
     bottom: 0;
     left: 0;
 }
+
 .plot-item {
     border: 1rpx solid rgba($u-primary, 0.4);
     border-radius: 10rpx;

+ 18 - 9
src/plant/base/base-edit/index.vue

@@ -352,8 +352,8 @@
                         <ut-upload v-model="form.environmentInfo.basePic" :max-count="9"></ut-upload>
                     </up-form-item>
                     <!-- 基地视频 -->
-                    <up-form-item label="基地视频" prop="environmentInfo.baseVideo" borderBottom>
-                        <ut-upload v-model="form.environmentInfo.baseVideo" :max-count="1" valueType="array" accept="video"></ut-upload>
+                    <up-form-item label="基地视频" prop="environmentInfo.baseVoice" borderBottom>
+                        <ut-upload v-model="form.environmentInfo.baseVoice" :max-count="1" valueType="array" accept="video"></ut-upload>
                     </up-form-item>
                 </view>
             </template>
@@ -435,8 +435,8 @@
                         <ut-upload v-model="form.environmentInfo.basePic" :max-count="9"></ut-upload>
                     </up-form-item>
                     <!-- 基地视频 -->
-                    <up-form-item label="基地视频" prop="environmentInfo.baseVideo" borderBottom>
-                        <ut-upload v-model="form.environmentInfo.baseVideo" :max-count="1" valueType="array" accept="video"></ut-upload>
+                    <up-form-item label="基地视频" prop="environmentInfo.baseVoice" borderBottom>
+                        <ut-upload v-model="form.environmentInfo.baseVoice" :max-count="1" valueType="array" accept="video"></ut-upload>
                     </up-form-item>
                 </view>
             </template>
@@ -478,9 +478,8 @@
     <FormPlot v-if="showPlotEdit" v-model:show="showPlotEdit" v-model="rowFormEdit" :title="plotTitle" :dict="{ pt_light_type, pt_capacity_unit, yes_no }" @submit="submitFormPlotEdit"></FormPlot>
 </template>
 <script setup lang="ts">
-import { useUserStore } from '@/store';
 import { useClientRequest } from '@/utils/request';
-import { getRect, isOpenSetting } from '@/utils/common';
+import { isOpenSetting } from '@/utils/common';
 import { generateUniqueId } from '@/utils/public';
 import FormPlot from './models/form-plot.vue';
 // import { $uGetRect } from 'uview-plus'
@@ -509,8 +508,8 @@ const form = ref<any>({
         gapFlag: '',
         buildDate: '',
         orgType: '',
-        contactId: '',
-        contactName: '',
+        contactId: undefined,
+        contactName: undefined,
         gapId: undefined,
         gapInfo: {
             adcode: '',
@@ -579,6 +578,13 @@ const saveBaseInfo = async () => {
                     title: '保存成功',
                     icon: 'success',
                 });
+                uni.$emit('refreshBaseList');
+                // 返回上一页
+                setTimeout(() => {
+                    uni.navigateBack({
+                        delta: 1,
+                    });
+                }, 1500);
             } catch (error) {
                 uni.hideLoading();
             }
@@ -613,9 +619,12 @@ const submitForm = async () => {
                     title: '提交成功',
                     icon: 'success',
                 });
+                uni.$emit('refreshBaseList');
                 // 返回上一页
                 setTimeout(() => {
-                    uni.navigateBack();
+                    uni.navigateBack({
+                        delta: 1,
+                    });
                 }, 1500);
             } catch (error) {
                 uni.hideLoading();

+ 7 - 7
src/types/module.d.ts

@@ -1,14 +1,14 @@
-
 import type { useDict } from '@/utils/dict';
 import type { addDateRange, handleTree, selectDictLabel, selectDictLabels, parseTime } from '@/utils/ruoyi';
 import type { getConfigKey, updateConfigByKey } from '@/api/system/config';
 import type { download as rd } from '@/utils/request';
 
 declare module '@vue/runtime-core' {
-  interface ComponentCustomProperties {
-    useDict: typeof useDict;
-    selectDictLabel: typeof selectDictLabel;
-    selectDictLabels: typeof selectDictLabels;
-   navigateBackOrHome: typeof navigateBackOrHome;
-  }
+    interface ComponentCustomProperties {
+        useDict: typeof useDict;
+        selectDictLabel: typeof selectDictLabel;
+        selectDictLabels: typeof selectDictLabels;
+        navigateBackOrHome: typeof navigateBackOrHome;
+        previewImage: typeof import('@/utils/ruoyi').previewImage;
+    }
 }

+ 22 - 0
src/utils/ruoyi.ts

@@ -410,4 +410,26 @@ export const recursiveDecodeURIComponentSimple = (obj: any): any => {
         return decodedObj;
     }
     return obj;
+};
+// 将url提取参数
+export const getUrlParams = (url: string): Record<string, string> => {
+    const params: Record<string, string> = {};
+    const queryString = url.split('?')[1];
+    if (!queryString) {
+        return params;
+    }
+    const pairs = queryString.split('&');
+    for (const pair of pairs) {
+        const [key, value] = pair.split('=');
+        params[decodeURIComponent(key)] = decodeURIComponent(value || '');
+    }
+    return params;
+}
+// 预览图片方法
+export const previewImage = (urls: string | string[], current?: string) => {
+    const urlList = Array.isArray(urls) ? urls : [urls];
+    uni.previewImage({
+        urls: urlList,
+        current: current || urlList[0],
+    });
 };