Quellcode durchsuchen

新增上传组件

huangxw vor 2 Wochen
Ursprung
Commit
5a153eeba0

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

@@ -148,4 +148,12 @@ $colors: (
 .home_icon{
     width: 40rpx;
     height: 40rpx;
+}
+.startline-title {
+    line-height: 1;
+    padding-left: 30rpx;
+    font-size: 32rpx;
+    color: #333;
+    font-weight: 600;
+    border-left: 4rpx solid $u-primary;
 }

+ 312 - 102
src/components/ut-upload/ut-upload.vue

@@ -1,15 +1,57 @@
 <template>
-	<u-upload :width="width" :height="height" :fileList="fileList" :accept="accept" :uploadIcon="uploadIcon"
-		:uploadText="uploadText" @afterRead="afterRead" @delete="deletePic" :multiple="multiple"
-		:maxCount="maxCount">
-	</u-upload>
+    <template v-if="style == 'card'">
+        <view class="ut-upload-card">
+            <template v-for="(item, index) in fileList" :key="index">
+                <view :style="{ width, height }" class="card-item">
+                    <template v-if="accept === 'image'">
+                        <image :src="item.status === 'uploading' && item.tempUrl ? item.tempUrl : item.url" :mode="mode" style="width: 100%; height: 100%" @click="onPreview(index)"></image>
+                    </template>
+                    <template v-else-if="accept === 'video'">
+                        <video :src="item.status === 'uploading' && item.tempUrl ? item.tempUrl : item.url" controls style="width: 100%; height: 100%" @click="onPreview(index)"></video>
+                    </template>
+                    <template v-if="accept === 'file'">
+                        <view class="d-flex flex-cln file-box" @click="onPreview(index)">
+                            <view class="flex1">
+                                <view class="f-s-28 c-primary up-line-2">{{ item.fileName || '文件' }}</view>
+                                <view class="f-s-24 c-999">{{ item.fileSize ? (item.fileSize / 1024).toFixed(2) + ' KB' : '' }}</view>
+                            </view>
+                            <view class="d-flex j-ed">
+                                <image :src="getFileIconByUrl(item.tempUrl || item.url)" mode="aspectFit" style="width: 40rpx; height: 40rpx;" />
+                            </view>
+                        </view>
+                    </template>
+                    <view v-if="item.status === 'uploading'" class="uploading-mask">
+                        <view class="uploading-text">上传中...</view>
+                    </view>
+                    <view class="del-btn" @click.stop="onDelete(index)">
+                        <up-icon name="close" color="#fff" size="32rpx"></up-icon>
+                    </view>
+                </view>
+            </template>
+            <view v-if="(fileList.length < maxCount)" :style="{ width, height }" @click="clickBtnUpload" class="card-item btn-select d-flex flex-cln j-c a-c">
+                <view class="mb-10">
+                    <up-icon :color="iconColor" :name="iconName" :iconSize="iconSize"></up-icon>
+                </view>
+                <view class="f-s-24 c-primary">{{ uploadText }}</view>
+            </view>
+        </view>
+    </template>
 </template>
 
 <script setup lang="ts">
 import upload from '@/utils/upload';
+import { fileExt, isUrl } from '@/utils/ruoyi';
+import { getFileIconByUrl } from '@/utils/common';
 
 interface FileItem {
     url: string;
+    fileName?: string;
+    fileSize?: number;
+    // 封面图
+    coverUrl?: string;
+    tempUrl?: string;
+    status?: 'uploading' | 'done' | 'error';
+    progress?: number;
 }
 
 interface UploadEvent {
@@ -28,24 +70,36 @@ interface Props {
     multiple: boolean;
     uploadText: string;
     uploadIcon: string;
-    accept: string;
-    isArr: boolean;
-    keyUrl: string;
-    isObject: boolean;
+    accept: string; // image/video/file
+    uploadUrl?: string;
+    uploadTimeout?: number;
+    extension?: string | string[]; // 限制选择的文件扩展名(仅在 accept==='file' 生效)
+    valueType: 'string' | 'array' | 'object';
+    style: 'card' | 'list';
+    iconName: string;
+    iconColor: string;
+    iconSize: string | number;
+    mode: 'aspectFill' | 'aspectFit' | 'widthFix' | 'heightFix' | 'top' | 'bottom' | 'center' | 'left' | 'right' | 'top left' | 'top right' | 'bottom left' | 'bottom right';
 }
 
 const props = withDefaults(defineProps<Props>(), {
     modelValue: () => [],
     maxCount: 1,
-    width: '200rpx',
-    height: '200rpx',
+    width: '210rpx',
+    height: '210rpx',
     multiple: true,
     uploadText: '点击上传',
     uploadIcon: 'plus',
     accept: 'image',
-    isArr: false,
-    keyUrl: '',
-    isObject: false
+    uploadUrl: '/resource/oss/upload',
+    uploadTimeout: 600000,
+    extension: () => ['pdf'],
+    valueType: 'string',
+    style: 'card',
+    iconName: 'plus', // plus/camera
+    iconColor: '#37a954',
+    iconSize: '30rpx',
+    mode: 'aspectFill',
 });
 
 const emit = defineEmits<{
@@ -54,109 +108,265 @@ const emit = defineEmits<{
 }>();
 
 const fileList = ref<FileItem[]>([]);
+const buildFileName = (path: string, kind: 'image' | 'video' | 'file') => {
+    const ext = fileExt(path) || 'dat';
+    const prefix = kind === 'image' ? 'img' : kind === 'video' ? 'video' : 'file';
+    return `${prefix}_${Date.now()}.${ext}`;
+};
+const clickBtnUpload = async () => {
+    // 判断是图片/视频/文件不同的上传
+    // 使用chooseMedia选择图片或视频 chooseMessageFile选择文件
+    try {
+        const remain = props.maxCount - fileList.value.length;
+        if (remain <= 0) {
+            uni.showToast({ title: '已达最大上传数量', icon: 'none' });
+            return;
+        }
 
-watch(() => props.modelValue, (ov) => {
-    if (ov) {
-        if (props.isObject) {
-            fileList.value = [ov as FileItem];
-        } else if (props.isArr) {
-            if (props.keyUrl) {
-                fileList.value = (ov as any[]).map(url => ({ url: url[props.keyUrl] }));
-            } else {
-                fileList.value = (ov as string[]).map(url => ({ url }));
+        if (props.accept === 'file') {
+            const res: any = await uni.chooseMessageFile({
+                count: props.multiple ? remain : 1,
+                type: 'file',
+                extension: Array.isArray(props.extension) ? props.extension : [props.extension]
+            });
+            const files = (res?.tempFiles || []) as Array<{ name?: string; size: number; path: string }>;
+            for (const f of files) {
+                const name = f.name || buildFileName(f.path, 'file');
+                const placeholder: FileItem = { url: '', fileName: name, fileSize: f.size, tempUrl: f.path, status: 'uploading' };
+                const idx = fileList.value.push(placeholder) - 1;
+                try {
+                    const upRes = await upload({ url: props.uploadUrl!, filePath: f.path, name: 'file', timeout: props.uploadTimeout });
+                    const serverUrl = (upRes as any)?.data?.url || (upRes as any)?.data?.fileUrl || (upRes as any)?.data?.path || (upRes as any)?.data?.uri;
+                    if (upRes.code === 200) {
+                        const current = fileList.value[idx] || placeholder;
+                        fileList.value.splice(idx, 1, { ...current, url: serverUrl || current.tempUrl || '', status: 'done' });
+                        uni.showToast({ title: '上传成功', icon: 'success' });
+                        emitCurrentValue();
+                    } else {
+                        const current = fileList.value[idx] || placeholder;
+                        fileList.value.splice(idx, 1, { ...current, status: 'error' });
+                        uni.showToast({ title: '上传失败', icon: 'none' });
+                    }
+                } catch (err) {
+                    console.error('upload file error:', err);
+                    const current = fileList.value[idx] || placeholder;
+                    fileList.value.splice(idx, 1, { ...current, status: 'error' });
+                }
             }
         } else {
-            const list = (ov as string).split(',');
-            fileList.value = list.map(item => ({
-                url: item
-            }));
+            const res: any = await uni.chooseMedia({
+                count: props.multiple ? remain : 1,
+                mediaType: props.accept === 'image' ? ['image'] : ['video'],
+                sourceType: ['album', 'camera'],
+            });
+            const files = (res?.tempFiles || []) as Array<{ tempFilePath: string; size: number; thumbTempFilePath?: string }>;
+            for (const f of files) {
+                const name = buildFileName(f.tempFilePath, props.accept === 'image' ? 'image' : 'video');
+                const placeholder: FileItem = { url: '', fileName: name, fileSize: f.size, tempUrl: f.tempFilePath, status: 'uploading' };
+                const idx = fileList.value.push(placeholder) - 1;
+                try {
+                    const upRes = await upload({ url: props.uploadUrl!, filePath: f.tempFilePath, name: 'file', timeout: props.uploadTimeout });
+                    const serverUrl = (upRes as any)?.data?.url || (upRes as any)?.data?.fileUrl || (upRes as any)?.data?.path || (upRes as any)?.data?.uri;
+                    if (upRes.code === 200) {
+                        const current = fileList.value[idx] || placeholder;
+                        fileList.value.splice(idx, 1, { ...current, url: serverUrl || current.tempUrl || '', status: 'done' });
+                        uni.showToast({ title: '上传成功', icon: 'success' });
+                        emitCurrentValue();
+                    } else {
+                        const current = fileList.value[idx] || placeholder;
+                        fileList.value.splice(idx, 1, { ...current, status: 'error' });
+                        uni.showToast({ title: '上传失败', icon: 'none' });
+                    }
+                } catch (err) {
+                    console.error('upload media error:', err);
+                    const current = fileList.value[idx] || placeholder;
+                    fileList.value.splice(idx, 1, { ...current, status: 'error' });
+                }
+            }
         }
-    }
-});
 
-const deletePic = (event: DeleteEvent) => {
-    fileList.value.splice(event.index, 1);
-    const urls = fileList.value.map(({ url }) => url);
-    const imgs = urls.toString();
-    
-    if (props.isObject) {
-        emit('update:modelValue', null);
-        emit('change', null);
-    } else if (props.isArr) {
-        if (props.keyUrl) {
-            emit('update:modelValue', fileList.value.map(({ url }) => ({ [props.keyUrl]: url })));
-            emit('change', fileList.value.map(({ url }) => ({ [props.keyUrl]: url })));
-        } else {
-            emit('update:modelValue', urls);
-            emit('change', urls);
-        }
-    } else {
-        emit('update:modelValue', imgs);
-        emit('change', imgs);
+        // 最后再统一触发一次
+        emitCurrentValue();
+    } catch (e) {
+        console.log('upload error:', e);
     }
 };
 
-const afterRead = async (event: UploadEvent) => {
-    const files = event.file;
-    console.log(files);
-    
-    // 先使用本地临时路径进行预览展示
-    const startIndex = fileList.value.length;
-    const tempItems = files.map(({ url }) => ({ url }));
-    fileList.value = fileList.value.concat(tempItems);
-
-    // 同步上传并在成功后用服务端URL替换临时预览
-    const promises = files.map(({ url }) => upload({
-        filePath: url,
-        url: '/resource/oss/upload'
-    }));
 
-    try {
-        const res = await Promise.all(promises);
-        const uploaded: FileItem[] = [];
-        res.forEach(({ code, data }) => {
-            if (code === 200) {
-                uploaded.push(data);
-            } else {
-                uploaded.push({ url: '' });
-            }
+const onPreview = (index: number) => {
+    const item = fileList.value[index];
+    if (!item) return;
+    // 文件类型:上传中也允许预览本地临时文件
+    if (props.accept === 'file') {
+        const localPath = item.status === 'uploading' && item.tempUrl
+            ? item.tempUrl
+            : (!isUrl(item.url) ? item.url : '');
+        if (localPath) {
+            uni.openDocument({
+                filePath: localPath,
+                showMenu: true,
+                fail: () => {
+                    uni.showToast({ title: '打开文件失败', icon: 'none' });
+                }
+            });
+            return;
+        }
+        if (item.url && isUrl(item.url)) {
+            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' });
+                }
+            });
+        }
+        return;
+    }
+    // 图片/视频:上传中不允许预览
+    if (item?.status === 'uploading') return;
+    if (props.accept === 'image') {
+        const urls = fileList.value.map((i) => i.url);
+        uni.previewImage({
+            urls,
+            current: urls[index]
         });
-
-        // 替换对应位置的临时项为服务器返回的URL(成功的替换,失败的不动或清理)
-        uploaded.forEach((item, i) => {
-            const targetIndex = startIndex + i;
-            if (item.url) {
-                fileList.value[targetIndex] = item;
-            } else {
-                // 上传失败则移除该临时项
-                fileList.value.splice(targetIndex, 1);
-            }
+        return;
+    }
+    if (props.accept === 'video') {
+        // #ifdef MP-WEIXIN
+        const sources = fileList.value.map((i) => ({ url: i.url, type: 'video', poster: i.coverUrl || '' }));
+        // @ts-ignore
+        wx.previewMedia({
+            sources,
+            current: index
         });
+        // #endif
+        // #ifndef MP-WEIXIN
+        uni.showToast({ title: '当前平台暂不支持视频预览', icon: 'none' });
+        // #endif
+    }
+};
 
-        const urls = fileList.value.slice(0); // 当前所有成功项
-        const imgs = urls.map(({ url }) => url).toString();
-
-        if (props.isObject) {
-            emit('update:modelValue', urls[0]);
-            emit('change', urls[0]);
-        } else if (props.isArr) {
-            if (props.keyUrl) {
-                emit('update:modelValue', urls.map(({ url }) => ({ [props.keyUrl]: url })));
-                emit('change', urls.map(({ url }) => ({ [props.keyUrl]: url })));
-            } else {
-                emit('update:modelValue', urls.map(({ url }) => url));
-                emit('change', urls.map(({ url }) => url));
+const onDelete = (index: number) => {
+    fileList.value.splice(index, 1);
+    const urls = fileList.value.map((i) => i.url);
+    let out: any;
+    if (props.valueType === 'string') {
+        out = urls[0] || '';
+    } else if (props.valueType === 'array') {
+        out = fileList.value;
+    } else {
+        out = props.multiple ? fileList.value : fileList.value[0] || null;
+    }
+    emit('update:modelValue', out);
+    emit('change', out);
+};
+function emitCurrentValue() {
+    const validList = fileList.value;
+    const urls = validList.map((i) => i.url).filter(Boolean);
+    let out: any;
+    if (props.valueType === 'string') {
+        out = urls[0] || '';
+    } else if (props.valueType === 'array') {
+        out = validList;
+    } else {
+        out = props.multiple ? validList : validList[0] || null;
+    }
+    emit('update:modelValue', out);
+    emit('change', out);
+}
+watch(
+    () => props.modelValue,
+    (val) => {
+        if (!val) {
+            fileList.value = [];
+            return;
+        }
+        if (props.valueType === 'string') {
+            if (typeof val === 'string') {
+                fileList.value = val ? [{ url: val }] : [];
+            }
+        } else if (props.valueType === 'array') {
+            if (Array.isArray(val)) {
+                if (val.length && typeof val[0] === 'string') {
+                    fileList.value = (val as string[]).map((u) => ({ url: u }));
+                } else {
+                    fileList.value = val as FileItem[];
+                }
             }
         } else {
-            emit('update:modelValue', imgs);
-            emit('change', imgs);
+            if (Array.isArray(val)) {
+                fileList.value = val as FileItem[];
+            } else if (typeof val === 'object' && (val as any).url) {
+                fileList.value = [val as FileItem];
+            }
         }
-    } catch (error) {
-        console.error('Upload failed:', error);
-        // 全部上传失败则回滚新增的临时项
-        fileList.value.splice(startIndex, tempItems.length);
-    }
-};
+    },
+    { immediate: true }
+);
 </script>
 
-<style></style>
+<style lang="scss" scoped>
+.ut-upload-card {
+    display: flex;
+    gap: 10rpx;
+    flex-wrap: wrap;
+    .card-item {
+        width: 210rpx;
+        height: 210rpx;
+        border-radius: 8rpx;
+        overflow: hidden;
+        border: 1rpx solid #f8f8f8;
+        position: relative;
+    }
+    .file-box {
+        width: 100%;
+        height: 100%;
+        box-sizing: border-box;
+        padding: 20rpx;
+    }
+}
+.del-btn {
+    position: absolute;
+    top: 0rpx;
+    right: 0rpx;
+    width: 48rpx;
+    height: 48rpx;
+    border-radius: 50%;
+    background: rgba(0, 0, 0, 0.35);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 2;
+}
+.uploading-mask {
+    position: absolute;
+    left: 0;
+    top: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.4);
+    color: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 1;
+}
+.uploading-text {
+    font-size: 24rpx;
+}
+</style>

+ 250 - 161
src/plant/base/base-edit/index.vue

@@ -1,125 +1,212 @@
 <template>
-    <z-paging class="" ref="paging" bgColor="#fff" safe-area-inset-bottom scroll-with-animation>
+    <z-paging ref="paging" bgColor="#F7F7F7" safe-area-inset-bottom paging-class="paging-btm-shadow" scroll-with-animation>
         <template #top>
             <up-navbar title="添加基地" :fixed="false"></up-navbar>
-            <view class="bg-fff mb-10">
+            <view class="bg-fff mb-20">
                 <view class="pd-5"></view>
-                <ut-tabs v-model="activeTab" :tabs="tabs" mode="btw"></ut-tabs>
+                <ut-tabs v-model="activeTab" :tabs="tabs" mode="btw" @change="changeActiveTab"></ut-tabs>
             </view>
         </template>
-        <view class="pd-24">
-            <up-alert type="primary" fontSize="24rpx" description="注意:基地不强制与品种挂钩!基地地址以行政村为界,可成片集中或相对集中,跨村则视为另一基地(连片跨村除外)。"></up-alert>
-            <view class="pd-10"></view>
-            <up-form class="p-rtv" labelPosition="top" :model="form" :rules="rules" labelWidth="auto" ref="upFormRef">
-                <!-- 校验定位:基地类型 -->
-                <!-- 基地信息部分 -->
-                <view>
-                    <view class="h-1" id="baseTypepppp"></view>
-                    <ut-action-sheet v-model="form.baseType" :tabs="pt_base_type" title="选择基地类型">
-                        <up-form-item borderBottom label="基地类型" required prop="baseType">
-                            <view v-if="form.baseType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(pt_base_type, form.baseType) }}</view>
-                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地类型</view>
-                            <template #right>
-                                <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
-                            </template>
-                        </up-form-item>
-                    </ut-action-sheet>
-                    <!-- 基地名称 -->
-                    <view class="h-1" id="baseNamepppp"></view>
-                    <up-form-item borderBottom label="基地名称" required prop="baseName">
-                        <up-input v-model="form.baseName" placeholder="请输入基地名称" border="none" clearable></up-input>
-                    </up-form-item>
-                    <!-- 基地编号 -->
-                    <view class="h-1" id="baseCodepppp"></view>
-                    <up-form-item borderBottom label="基地编号" prop="baseCode">
-                        <up-input v-model="form.baseCode" placeholder="请输入基地编号" border="none" clearable></up-input>
-                    </up-form-item>
-                    <!-- 校验定位:建设时间 -->
-                    <view class="h-1" id="buildDatepppp"></view>
-                    <ut-datetime-picker v-model="form.buildDate" mode="date" dateFields="month">
-                        <up-form-item borderBottom label="建设时间" required prop="buildDate">
-                            <up-input v-model="form.buildDate" placeholder="请选择基地建设年份" border="none" clearable></up-input>
-                        </up-form-item>
-                    </ut-datetime-picker>
-                    <!-- 选择基地组织方式 -->
-                    <view class="h-1" id="orgTypepppp"></view>
-                    <ut-action-sheet v-model="form.orgType" :tabs="pt_org_type" title="选择基地组织方式">
-                        <up-form-item borderBottom label="基地组织方式" required prop="orgType">
-                            <view v-if="form.orgType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(pt_org_type, form.orgType) }}</view>
-                            <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地组织方式</view>
-                            <template #right>
-                                <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
-                            </template>
-                        </up-form-item>
-                    </ut-action-sheet>
-                    <!-- 选择基地负责人 -->
-                    <!-- 校验定位:基地负责人 -->
-                    <view class="h-1" id="contactIdpppp"></view>
-                    <up-form-item @click="selectCpyMember" borderBottom label="基地负责人" required prop="contactId">
-                        <view v-if="form.contactId" class="f-s-30 c-333 f-w-5 flex1">{{ form.contactName }}</view>
-                        <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地负责人</view>
+        <up-form class="p-rtv" labelPosition="top" :model="form" :rules="rules" labelWidth="auto" ref="upFormRef">
+            <!-- 基本信息 -->
+            <view class="pd-24" id="base12345">
+                <view class="startline-title">基本信息</view>
+            </view>
+            <view class="pd-24 bg-#fff">
+                <up-alert type="primary" fontSize="24rpx" description="注意:基地不强制与品种挂钩!基地地址以行政村为界,可成片集中或相对集中,跨村则视为另一基地(连片跨村除外)。"></up-alert>
+                <view class="pd-10"></view>
+                <view class="h-1" id="baseTypepppp"></view>
+                <ut-action-sheet v-model="form.baseInfo.baseType" :tabs="pt_base_type" title="选择基地类型">
+                    <up-form-item borderBottom label="基地类型" required prop="baseType">
+                        <view v-if="form.baseInfo.baseType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(pt_base_type, form.baseInfo.baseType) }}</view>
+                        <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地类型</view>
                         <template #right>
                             <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
                         </template>
                     </up-form-item>
-                    <!-- 填写基地联系电话 -->
-                    <view class="h-1" id="contactTelpppp"></view>
-                    <up-form-item borderBottom label="基地联系电话" required prop="contactTel">
-                        <up-input v-model="form.contactTel" placeholder="请输入基地联系电话" border="none" clearable></up-input>
+                </ut-action-sheet>
+                <!-- 基地名称 -->
+                <view class="h-1" id="baseNamepppp"></view>
+                <up-form-item borderBottom label="基地名称" required prop="baseName">
+                    <up-input v-model="form.baseInfo.baseName" placeholder="请输入基地名称" border="none" clearable></up-input>
+                </up-form-item>
+                <!-- 基地编号 -->
+                <view class="h-1" id="baseCodepppp"></view>
+                <up-form-item borderBottom label="基地编号" prop="baseCode">
+                    <up-input v-model="form.baseInfo.baseCode" placeholder="请输入基地编号" border="none" clearable></up-input>
+                </up-form-item>
+                <!-- 校验定位:建设时间 -->
+                <view class="h-1" id="buildDatepppp"></view>
+                <ut-datetime-picker v-model="form.baseInfo.buildDate" mode="date" dateFields="month">
+                    <up-form-item borderBottom label="建设时间" required prop="buildDate">
+                        <up-input v-model="form.baseInfo.buildDate" placeholder="请选择基地建设年份" border="none" clearable></up-input>
                     </up-form-item>
-                    <!-- 是否Gap基地 -->
-                    <view class="h-1" id="gapFlagpppp"></view>
-                    <up-form-item borderBottom label="是否为Gap基地" required prop="gapFlag">
-                        <up-radio-group v-model="form.gapFlag">
-                            <up-radio :customStyle="{ marginRight: '60rpx' }" v-for="(item, index) in yes_no" :key="index" :label="item.label" :name="item.value"></up-radio>
-                        </up-radio-group>
-                    </up-form-item>
-                    <view class="h-1" id="adcodepppp"></view>
-                    <up-form-item @click="showArea = true" borderBottom label="基地具体地址" required prop="gapInfo.adcode">
-                        <!-- <up-input v-model="form.address" placeholder="请选择基地所在省/市/县/镇(乡)" border="none" clearable></up-input> -->
-                        <view v-if="form?.gapInfo?.adcode" class="f-s-30 c-333 f-w-5 flex1">{{ form?.gapInfo?.adcodeName }}</view>
-                        <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地所在省/市/县/镇(乡)</view>
+                </ut-datetime-picker>
+                <!-- 选择基地组织方式 -->
+                <view class="h-1" id="orgTypepppp"></view>
+                <ut-action-sheet v-model="form.baseInfo.orgType" :tabs="pt_org_type" title="选择基地组织方式">
+                    <up-form-item borderBottom label="基地组织方式" required prop="orgType">
+                        <view v-if="form.baseInfo.orgType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(pt_org_type, form.baseInfo.orgType) }}</view>
+                        <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地组织方式</view>
                         <template #right>
                             <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
                         </template>
                     </up-form-item>
-                    <up-form-item borderBottom prop="gapInfo.address">
-                        <up-input v-model="form.gapInfo.address" placeholder="请填写村级以下的具体地址信息" border="none" clearable></up-input>
-                    </up-form-item>
-                    <up-form-item borderBottom>
-                        <view class="flex1 ov-hd">
-                            <view class="d-flex a-c mb-10" style="margin-bottom: 5px">
-                                <view class="f-s-30 c-#666">基地范围</view>
-                            </view>
-                            <view class="bg-#ccc d-flex ov-hd p-rtv" @click="mapDrawArea">
-                                <image class="w-full h-380" v-if="form.gapInfo?.basePic" :src="form.gapInfo.basePic" mode="widthFix" />
-                                <image class="w-full h-380" v-else src="@/static/images/plant/base/select_base_gap.png" mode="widthFix" />
-                                <view v-if="!form.gapInfo?.basePic" class="btn-aree-center d-flex flex-cln a-c j-c">
-                                    <image class="w-52 h-52 mb-10" src="@/static/images/plant/base/draw_area_icon.png" mode="widthFix" />
-                                    <view class="c-primary f-s-28 f-w-400">点击绘制基地范围</view>
-                                </view>
-                            </view>
+                </ut-action-sheet>
+                <!-- 选择基地负责人 -->
+                <!-- 校验定位:基地负责人 -->
+                <view class="h-1" id="contactIdpppp"></view>
+                <up-form-item @click="selectCpyMember" borderBottom label="基地负责人" required prop="contactId">
+                    <view v-if="form.baseInfo.contactId" class="f-s-30 c-333 f-w-5 flex1">{{ form.baseInfo.contactName }}</view>
+                    <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地负责人</view>
+                    <template #right>
+                        <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
+                    </template>
+                </up-form-item>
+                <!-- 填写基地联系电话 -->
+                <view class="h-1" id="contactTelpppp"></view>
+                <up-form-item borderBottom label="基地联系电话" required prop="contactTel">
+                    <up-input v-model="form.baseInfo.contactTel" placeholder="请输入基地联系电话" border="none" clearable></up-input>
+                </up-form-item>
+                <!-- 是否Gap基地 -->
+                <view class="h-1" id="gapFlagpppp"></view>
+                <up-form-item borderBottom label="是否为Gap基地" required prop="gapFlag">
+                    <up-radio-group v-model="form.baseInfo.gapFlag">
+                        <up-radio :customStyle="{ marginRight: '60rpx' }" v-for="(item, index) in yes_no" :key="index" :label="item.label" :name="item.value"></up-radio>
+                    </up-radio-group>
+                </up-form-item>
+                <view class="h-1" id="adcodepppp"></view>
+                <up-form-item @click="showArea = true" borderBottom label="基地具体地址" required prop="baseInfo.gapInfo.adcode">
+                    <!-- <up-input v-model="form.address" placeholder="请选择基地所在省/市/县/镇(乡)" border="none" clearable></up-input> -->
+                    <view v-if="form?.baseInfo?.gapInfo?.adcode" class="f-s-30 c-333 f-w-5 flex1">{{ form?.baseInfo?.gapInfo?.adcodeName }} </view>
+                    <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地所在省/市/县/镇(乡)</view>
+                    <template #right>
+                        <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
+                    </template>
+                </up-form-item>
+                <up-form-item borderBottom prop="baseInfo.gapInfo.address">
+                    <up-input v-model="form.baseInfo.gapInfo.address" placeholder="请填写村级以下的具体地址信息" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item borderBottom>
+                    <view class="flex1 ov-hd">
+                        <view class="d-flex a-c mb-10" style="margin-bottom: 5px">
+                            <view class="f-s-30 c-#666">基地范围</view>
                         </view>
-                    </up-form-item>
-                    <view class="h-1" id="areaPppp"></view>
-                    <up-form-item required borderBottom label="基地面积" prop="gapInfo.area">
-                        <up-input v-model="form.gapInfo.area" placeholder="地块绘制后自动带出可修改" border="none" clearable></up-input>
-                        <template #right>
-                            <span>{{ form.gapInfo.areaUnit }}</span>
-                        </template>
-                    </up-form-item>
-                    <!-- 基地经纬度 -->
-                    <view class="h-1" id="lnglatpppp"></view>
-                    <up-form-item required label="基地经纬度" prop="longitudeLatitude">
-                        <view class="flex1 d-flex a-c">
-                            <up-input v-model="form.gapInfo.lng" border="bottom" placeholder="70-150内的经度数值" clearable></up-input>
-                            <view class="pd-5"></view>
-                            <up-input v-model="form.gapInfo.lat" placeholder="4-53内的纬度数值" border="bottom" clearable></up-input>
+                        <view class="bg-#ccc d-flex ov-hd p-rtv" @click="mapDrawArea">
+                            <image class="w-full h-380" v-if="form.baseInfo.gapInfo?.basePic" :src="form.baseInfo.gapInfo.basePic" mode="widthFix" />
+                            <image class="w-full h-380" v-else src="@/static/images/plant/base/select_base_gap.png" mode="widthFix" />
+                            <view v-if="!form.baseInfo.gapInfo?.basePic" class="btn-aree-center d-flex flex-cln a-c j-c">
+                                <image class="w-52 h-52 mb-10" src="@/static/images/plant/base/draw_area_icon.png" mode="widthFix" />
+                                <view class="c-primary f-s-28 f-w-400">点击绘制基地范围</view>
+                            </view>
                         </view>
-                    </up-form-item>
-                </view>
-            </up-form>
-        </view>
+                    </view>
+                </up-form-item>
+                <view class="h-1" id="areaPppp"></view>
+                <up-form-item required borderBottom label="基地面积" prop="baseInfo.gapInfo.area">
+                    <up-input v-model="form.baseInfo.gapInfo.area" placeholder="地块绘制后自动带出可修改" border="none" clearable></up-input>
+                    <template #right>
+                        <span>{{ form.baseInfo.gapInfo.areaUnit }}</span>
+                    </template>
+                </up-form-item>
+                <!-- 基地经纬度 -->
+                <view class="h-1" id="lnglatpppp"></view>
+                <up-form-item required label="基地经纬度" prop="lnglat">
+                    <view class="flex1 d-flex a-c">
+                        <up-input v-model="form.baseInfo.gapInfo.lng" border="none" placeholder="70-150内的经度数值" clearable></up-input>
+                        <view class="pd-5"></view>
+                        <up-input v-model="form.baseInfo.gapInfo.lat" placeholder="4-53内的纬度数值" border="none" clearable></up-input>
+                    </view>
+                </up-form-item>
+            </view>
+            <!-- 地块/圈舍/组培架信息 -->
+            <view class="pd-24" id="plot12345">
+                <view class="startline-title">地块/圈舍/组培架信息</view>
+            </view>
+            <view class="pd-24" id="environment12345">
+                <view class="startline-title">选址依据及环境信息</view>
+            </view>
+            <view class="pd-24 bg-#fff mb-10">
+                <view class="f-s-32 c-#333 f-w-600">选址依据</view>
+                <up-form-item label="是否道地产区">
+                    <up-radio-group v-model="form.environmentInfo.daoStatus">
+                        <up-radio :customStyle="{ marginRight: '60rpx' }" v-for="(item, index) in yes_no" :key="index" :label="item.label" :name="item.value"></up-radio>
+                    </up-radio-group>
+                </up-form-item>
+            </view>
+            <view class="pd-24 bg-#fff mb-10">
+                <view class="f-s-32 c-#333 f-w-600">环境信息</view>
+                <up-form-item label="土壤类型" prop="environmentInfo.soilType" borderBottom>
+                    <up-input v-model="form.environmentInfo.soilType" placeholder="请输入土壤类型" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item label="土壤质地" prop="environmentInfo.soilTexture" borderBottom>
+                    <up-input v-model="form.environmentInfo.soilTexture" placeholder="请输入土壤质地" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item label="有机质含量" prop="environmentInfo.organic" borderBottom>
+                    <up-input v-model="form.environmentInfo.organic" placeholder="请输入有机质含量" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item label="土壤PH值" prop="environmentInfo.soilPh" borderBottom>
+                    <up-input v-model="form.environmentInfo.soilPh" placeholder="请输入土壤PH值" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item label="水源类型" prop="environmentInfo.waterType" borderBottom>
+                    <up-input v-model="form.environmentInfo.waterType" placeholder="请输入水源类型" border="none" clearable></up-input>
+                </up-form-item>
+                <up-form-item label="无霜期" prop="environmentInfo.notFrost" borderBottom>
+                    <up-input v-model="form.environmentInfo.notFrost" placeholder="请输入无霜期" border="none" clearable></up-input>
+                    <template #right>
+                        <span>天</span>
+                    </template>
+                </up-form-item>
+                <up-form-item label="年降水量" prop="environmentInfo.precipitation" borderBottom>
+                    <up-input v-model="form.environmentInfo.precipitation" placeholder="请输入年降水量" border="none" clearable></up-input>
+                    <template #right>
+                        <span>mm</span>
+                    </template>
+                </up-form-item>
+                <up-form-item label="年平均气温" prop="environmentInfo.avgTem" borderBottom>
+                    <up-input v-model="form.environmentInfo.avgTem" placeholder="请输入年平均气温" border="none" clearable></up-input>
+                    <template #right>
+                        <span>℃</span>
+                    </template>
+                </up-form-item>
+                <up-form-item label="年绝对最高气温" prop="environmentInfo.maxTem" borderBottom>
+                    <up-input v-model="form.environmentInfo.maxTem" placeholder="请输入年绝对最高气温" border="none" clearable></up-input>
+                    <template #right>
+                        <span>℃</span>
+                    </template>
+                </up-form-item>
+                <up-form-item label="年绝对最低气温" prop="environmentInfo.minTem" borderBottom>
+                    <up-input v-model="form.environmentInfo.minTem" placeholder="请输入年绝对最低气温" border="none" clearable></up-input>
+                    <template #right>
+                        <span>℃</span>
+                    </template>
+                </up-form-item>
+                 <up-form-item label="年日照时数" prop="environmentInfo.sunshineHours" borderBottom>
+                    <up-input v-model="form.environmentInfo.sunshineHours" placeholder="请输入年日照时数" border="none" clearable></up-input>
+                    <template #right>
+                        <span>小时</span>
+                    </template>
+                </up-form-item>
+                 <up-form-item label="海拔" prop="environmentInfo.altitude" borderBottom>
+                    <up-input v-model="form.environmentInfo.altitude" placeholder="请输入海拔" border="none" clearable></up-input>
+                    <template #right>
+                        <span>米</span>
+                    </template>
+                </up-form-item>
+                <!-- 基地图片 -->
+                <up-form-item label="水、土壤、大气等环评报告" prop="environmentInfo.report" borderBottom>
+                    <ut-upload v-model="form.environmentInfo.report" :max-count="9" valueType="array" accept="file"></ut-upload>
+                </up-form-item>
+                <!-- 基地图片 -->
+                <up-form-item label="基地图片" prop="environmentInfo.basePic" borderBottom>
+                    <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>
+            </view>
+        </up-form>
         <template #bottom>
             <view class="pd-20 d-flex">
                 <up-button @click="saveBaseInfo" class="mr-30" color="#F19F18" type="primary">暂存</up-button>
@@ -145,48 +232,29 @@ const paging = ref<any>(null);
 const upFormRef = ref<any>(null);
 const showArea = ref(false);
 const form = ref<any>({
-    baseType: '',
-    baseName: '',
-    baseCode: '',
-    gapInfo: {
-        adcode: '',
-        adcodeName: '',
-        address: '',
-        areaUnit: '亩',
-        area: undefined,
-    },
-    randomCode: generateUniqueId(),
-    // 当前位置经纬度
-    longitudeLatitude: {
-        lng: '',
-        lat: '',
-    },
-    coordinates: [],
-});
-const rules = reactive({
-    baseType: [{ required: true, message: '请选择基地类型', trigger: ['change'] }],
-    baseName: [{ required: true, message: '请输入基地名称', trigger: ['blur', 'change'] }],
-    buildDate: [{ required: true, message: '请选择基地建设年份', trigger: ['change'] }],
-    orgType: [{ required: true, message: '请选择基地组织方式', trigger: ['change'] }],
-    contactId: [{ required: true, message: '请选择基地负责人', trigger: ['change'] }],
-    contactTel: [{ required: true, message: '请输入基地联系电话', trigger: ['blur'] }],
-    gapFlag: [{ required: true, message: '请选择是否为Gap基地', trigger: ['change'] }],
-    'gapInfo.adcode': [{ required: true, message: '请选择基地所在省/市/县/镇(乡)', trigger: ['change'] }],
-    'gapInfo.area': [{ required: true, message: '请填写基地面积', trigger: ['change'] }],
-    longitudeLatitude: [
-        {
-            message: '请填写有效的经纬度(经度70-150,纬度4-53)',
-            validator: (rule: any, value: any) => {
-                const lng = Number(form.value?.gapInfo?.lng);
-                const lat = Number(form.value?.gapInfo?.lat);
-                if (!lng || !lat) return false;
-                if (Number.isNaN(lng) || Number.isNaN(lat)) return false;
-                return lng >= 70 && lng <= 150 && lat >= 4 && lat <= 53;
-            },
-            trigger: ['blur', 'change'],
+    baseInfo: {
+        baseType: '',
+        baseName: '',
+        baseCode: '',
+        gapInfo: {
+            adcode: '',
+            adcodeName: '',
+            address: '',
+            areaUnit: '亩',
+            area: undefined,
+        },
+        randomCode: generateUniqueId(),
+        // 当前位置经纬度
+        longitudeLatitude: {
+            lng: '',
+            lat: '',
         },
-    ],
+        coordinates: [],
+    }, // 基地信息
+    landInfoList: [], // 地块/圈舍/组培架信息
+    environmentInfo: {}, // 基地环境信息
 });
+const rules = reactive({});
 // saveInfo和submitForm防抖
 // 暂存基地信息
 const saveBaseInfo = async () => {
@@ -255,19 +323,23 @@ const submitForm = async () => {
 const mapDrawArea = async () => {
     uni.$on('mapAreaData', (data: any) => {
         // 这里可以将 data 赋值给 form 中的相应字段
-        form.value.gapInfo.basePic = data.gapInfo.basePic;
-        form.value.gapInfo.area = String(data.gapInfo.area);
-        form.value.gapInfo.areaUnit = data.gapInfo.areaUnit;
-        form.value.coordinates = data.coordinates;
+        form.value.baseInfo.gapInfo.basePic = data.gapInfo.basePic;
+        form.value.baseInfo.gapInfo.area = String(data.gapInfo.area);
+        form.value.baseInfo.gapInfo.areaUnit = data.gapInfo.areaUnit;
+        form.value.baseInfo.coordinates = data.coordinates;
+        form.value.baseInfo.gapInfo.lng = data.gapInfo.lng;
+        form.value.baseInfo.gapInfo.lat = data.gapInfo.lat;
+        form.value.baseInfo.lnglat = `${data.gapInfo.lng},${data.gapInfo.lat}`;
+        form.value.baseInfo.zoom = data.zoom;
         uni.$off('mapAreaData');
     });
-    if (form.value.gapInfo?.basePic) {
+    if (form.value.baseInfo.gapInfo?.basePic) {
         // 暂存地图当前数据后跳转
         await useClientRequest.post('/plt-api/app/gapCertificationInfo/painting', {
-            randomCode: form.value.id || form.value.randomCode,
+            randomCode: form.value.id || form.value.baseInfo.randomCode,
             value: JSON.stringify({
-                gapInfo: form.value.gapInfo,
-                coordinates: form.value.coordinates,
+                gapInfo: form.value.baseInfo.gapInfo,
+                coordinates: form.value.baseInfo.coordinates,
             }),
         });
     }
@@ -275,19 +347,20 @@ const mapDrawArea = async () => {
         type: 'navigateTo',
         url: '/tools/map-draw-area/index',
         params: {
-            drawId: form.value.id || form.value.randomCode,
-            unit: form.value.gapInfo.areaUnit || '亩',
-            lng: form.value.gapInfo.lng || form.value.longitudeLatitude.lng || '',
-            lat: form.value.gapInfo.lat || form.value.longitudeLatitude.lat || '',
+            drawId: form.value.id || form.value.baseInfo.randomCode,
+            unit: form.value.baseInfo.gapInfo.areaUnit || '亩',
+            lng: form.value.baseInfo.gapInfo.lng || form.value.longitudeLatitude.lng || '',
+            lat: form.value.baseInfo.gapInfo.lat || form.value.longitudeLatitude.lat || '',
+            zoom: form.value.baseInfo.zoom,
         },
     });
 };
 
 const selectCpyMember = () => {
     uni.$on('selectCpyMember', (item: any) => {
-        form.value.contactId = item.userInfo?.id;
-        form.value.contactName = item.userInfo?.name;
-        form.value.contactTel = item.userInfo?.phone;
+        form.value.baseInfo.contactId = item.userInfo?.id;
+        form.value.baseInfo.contactName = item.userInfo?.name;
+        form.value.baseInfo.contactTel = item.userInfo?.phone;
         uni.$off('selectCpyMember');
     });
     uni.$u.route({
@@ -296,7 +369,12 @@ const selectCpyMember = () => {
     });
 };
 const confirmArea = (area: any) => {
-    form.value.gapInfo.adcodeName = area.fullName;
+    form.value.baseInfo.gapInfo.adcodeName = area.fullName;
+};
+// value label value
+const changeActiveTab = (value: any) => {
+    let scrollId = `${value.value}12345`;
+    paging.value?.scrollIntoViewById(scrollId, 30, true);
 };
 onLoad((optins: any) => {
     if (optins.id) {
@@ -327,6 +405,16 @@ const getLocationByAddress = async () => {
         lat: location.latitude,
     };
 };
+const openVideo = () => {
+    uni.previewMedia({
+        sources: [{
+            url: 'https://fileserver.yujin.shuziyunyao.com/oss-file/smart-trace/2026/01/05/18e8bf8161874f938826905fa2034e06.mp4',
+            type: 'video',
+
+        }],
+        current: 0
+    })
+}
 </script>
 <style lang="scss" scoped>
 .z-paging-wrap {
@@ -336,6 +424,7 @@ const getLocationByAddress = async () => {
     bottom: 0;
     left: 0;
 }
+
 .btn-aree-center {
     position: absolute;
     top: 0;

+ 59 - 46
src/plant/base/gap-base-info-edit/index.vue

@@ -1,5 +1,6 @@
 <template>
-    <z-paging class="" ref="paging" bgColor="#fff" paging-class="paging-btm-shadow" safe-area-inset-bottom scroll-with-animation>
+    <z-paging class="" ref="paging" bgColor="#fff" paging-class="paging-btm-shadow" safe-area-inset-bottom
+        scroll-with-animation>
         <template #top>
             <ut-navbar title="添加GAP基地信息" :fixed="false" border></ut-navbar>
         </template>
@@ -11,7 +12,9 @@
                     <view class="h-1" id="gapBaseTypepppp"></view>
                     <ut-action-sheet v-model="form.gapBaseType" :tabs="pt_base_type" title="选择基地类型">
                         <up-form-item borderBottom label="基地类型" required prop="baseType">
-                            <view v-if="form.gapBaseType" class="f-s-30 c-333 f-w-5 flex1">{{ selectDictLabel(pt_base_type, form.gapBaseType) }}</view>
+                            <view v-if="form.gapBaseType" class="f-s-30 c-333 f-w-5 flex1">{{
+                                selectDictLabel(pt_base_type,
+                                form.gapBaseType) }}</view>
                             <view v-else class="f-s-30 c-ccc f-w-4 flex1">请选择基地类型</view>
                             <template #right>
                                 <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
@@ -19,7 +22,7 @@
                         </up-form-item>
                     </ut-action-sheet>
                     <!-- 基地名称 -->
-                    <view class="h-1" id="baseNamepppp"></view>
+                    <view class="h-1" id="gapBaseNamepppp"></view>
                     <up-form-item borderBottom label="基地名称" required prop="gapBaseName">
                         <up-input v-model="form.gapBaseName" placeholder="请输入基地名称" border="none" clearable></up-input>
                     </up-form-item>
@@ -40,9 +43,11 @@
                             </view>
                             <view class="bg-#ccc d-flex ov-hd p-rtv" @click="mapDrawArea">
                                 <image class="w-full h-380" v-if="form?.basePic" :src="form?.basePic" mode="widthFix" />
-                                <image class="w-full h-380" v-else src="@/static/images/plant/base/select_base_gap.png" mode="widthFix" />
+                                <image class="w-full h-380" v-else src="@/static/images/plant/base/select_base_gap.png"
+                                    mode="widthFix" />
                                 <view v-if="!form?.basePic" class="btn-aree-center d-flex flex-cln a-c j-c">
-                                    <image class="w-52 h-52 mb-10" src="@/static/images/plant/base/draw_area_icon.png" mode="widthFix" />
+                                    <image class="w-52 h-52 mb-10" src="@/static/images/plant/base/draw_area_icon.png"
+                                        mode="widthFix" />
                                     <view class="c-primary f-s-28 f-w-400">点击绘制基地范围</view>
                                 </view>
                             </view>
@@ -58,30 +63,33 @@
                         </template>
                     </up-form-item>
                     <up-form-item borderBottom prop="address">
-                        <up-input v-model="form.address" placeholder="请填写村级以下的具体地址信息" border="none" clearable></up-input>
+                        <up-input v-model="form.address" placeholder="请填写村级以下的具体地址信息" border="none"
+                            clearable></up-input>
                     </up-form-item>
 
                     <view class="h-1" id="areaPppp"></view>
                     <up-form-item required borderBottom label="基地面积" prop="area">
                         <up-input v-model="form.area" placeholder="地块绘制后自动带出可修改" border="none" clearable></up-input>
                         <template #right>
-                            <span>{{ form.gapInfo.areaUnit }}</span>
+                            <span>{{ form.areaUnit }}</span>
                         </template>
                     </up-form-item>
                     <!-- 基地经纬度 -->
-                    <view class="h-1" id="longitudeLatitudepppp"></view>
-                    <up-form-item required label="基地经纬度" prop="longitudeLatitude">
+                    <view class="h-1" id="lnglatpppp"></view>
+                    <up-form-item required label="基地经纬度" prop="lnglat">
                         <view class="flex1 d-flex a-c">
-                            <up-input v-model="form.lng" border="bottom" placeholder="70-150内的经度数值" clearable></up-input>
+                            <up-input v-model="form.lng" border="bottom" placeholder="70-150内的经度数值"
+                                clearable></up-input>
                             <view class="pd-5"></view>
                             <up-input v-model="form.lat" placeholder="4-53内的纬度数值" border="bottom" clearable></up-input>
                         </view>
                     </up-form-item>
                     <!-- 校验定位:建设时间 -->
                     <view class="h-1" id="ratedDatepppp"></view>
-                    <ut-datetime-picker v-model="form.ratedDate" mode="date" dateFields="month">
+                    <ut-datetime-picker v-model="form.ratedDate" mode="date" dateFields="day">
                         <up-form-item borderBottom label="获评GAP基地时间" required prop="ratedDate">
-                            <up-input v-model="form.ratedDate" placeholder="请选择获评GAP基地时间" border="none" clearable></up-input>
+                            <up-input v-model="form.ratedDate" placeholder="请选择获评GAP基地时间" border="none"
+                                clearable></up-input>
                             <template #right>
                                 <up-icon size="22rpx" color="#2A6D52" name="arrow-down-fill"></up-icon>
                             </template>
@@ -99,9 +107,9 @@
                         </view>
                     </up-form-item>
                     <!-- 官方公示网址 -->
-                    <view class="h-1" id="certUrlpppp"></view>
-                    <up-form-item borderBottom label="官方公示网址" prop="certUrl">
-                        <up-input v-model="form.certUrl" placeholder="请输入官方公示网址" border="none" clearable></up-input>
+                    <view class="h-1" id="urlpppp"></view>
+                    <up-form-item borderBottom label="官方公示网址" prop="url" required>
+                        <up-input v-model="form.url" placeholder="请输入官方公示网址" border="none" clearable></up-input>
                     </up-form-item>
                 </view>
             </up-form>
@@ -112,7 +120,7 @@
             </view>
         </template>
     </z-paging>
-    <ut-picker-area v-show:show="showArea" v-model="form.adcode" @confirm="confirmArea"></ut-picker-area>
+    <ut-picker-area v-model:show="showArea" v-model="form.adcode" @confirm="confirmArea"></ut-picker-area>
 </template>
 <script setup lang="ts">
 import { useUserStore } from '@/store';
@@ -124,7 +132,6 @@ const tabs = reactive([
     { label: '地块信息', value: 'plot' },
     { label: '选址依据及环境信息', value: 'environment' },
 ]);
-const activeTab = ref('base');
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { yes_no, pt_org_type, pt_base_type } = toRefs<any>(proxy?.useDict('yes_no', 'pt_org_type', 'pt_base_type'));
 const paging = ref<any>(null);
@@ -133,15 +140,12 @@ const showArea = ref(false);
 const form = ref<any>({
     sourceType: '2', // 基地来源 1-GAP系统 2-手动录入
     gapBaseType: '',
-    baseName: '',
-    baseCode: '',
-    gapInfo: {
-        adcode: '',
-        adcodeName: '',
-        address: '',
-        areaUnit: '亩',
-        area: undefined,
-    },
+    adcode: '',
+    adcodeName: '',
+    address: '',
+    areaUnit: '亩',
+    basePic: '',
+    area: undefined,
     randomCode: generateUniqueId(),
     // 当前位置经纬度
     longitudeLatitude: {
@@ -149,30 +153,37 @@ const form = ref<any>({
         lat: '',
     },
     coordinates: [],
+    zoom: undefined,
+    url: '',
 });
 const rules = reactive({
-    baseType: [{ required: true, message: '请选择基地类型', trigger: ['change'] }],
-    baseName: [{ required: true, message: '请输入基地名称', trigger: ['blur', 'change'] }],
-    buildDate: [{ required: true, message: '请选择基地建设年份', trigger: ['change'] }],
-    orgType: [{ required: true, message: '请选择基地组织方式', trigger: ['change'] }],
-    contactId: [{ required: true, message: '请选择基地负责人', trigger: ['change'] }],
-    contactTel: [{ required: true, message: '请输入基地联系电话', trigger: ['blur'] }],
-    gapFlag: [{ required: true, message: '请选择是否为Gap基地', trigger: ['change'] }],
-    adcode: [{ required: true, message: '请选择基地所在省/市/县/镇(乡)', trigger: ['change'] }],
-    area: [{ required: true, message: '请填写基地面积', trigger: ['change'] }],
-    longitudeLatitude: [
+    gapBaseType: [{ required: true, message: '请选择基地类型' }],
+    gapBaseName: [{ required: true, message: '请输入基地名称' }],
+    medicineId: [{ required: true, message: '请选择种养殖品种' }],
+    basePic: [{ required: true, message: '请绘制基地范围' }],
+    adcode: [{ required: true, message: '请选择基地具体地址' }],
+    area: [{ required: true, message: '请输入基地面积' }],
+    lnglat: [
+        { required: true, message: '请输入基地经纬度' },
         {
-            message: '请填写有效的经纬度(经度70-150,纬度4-53)',
-            validator: (rule: any, value: any) => {
-                const lng = Number(form.value?.lng);
-                const lat = Number(form.value?.lat);
-                if (!lng || !lat) return false;
-                if (Number.isNaN(lng) || Number.isNaN(lat)) return false;
-                return lng >= 70 && lng <= 150 && lat >= 4 && lat <= 53;
+            validator: (_rule: any, value: any) => {
+                const lng = parseFloat(form.value.lng);
+                const lat = parseFloat(form.value.lat);
+                if (isNaN(lng) || lng < 70 || lng > 150) {
+                    return Promise.reject('经度请输入70-150内的数值');
+                }
+                if (isNaN(lat) || lat < 4 || lat > 53) {
+                    return Promise.reject('纬度请输入4-53内的数值');
+                }
+                return Promise.resolve();
             },
-            trigger: ['blur', 'change'],
-        },
+            trigger: 'blur',
+        }
     ],
+    ratedDate: [{ required: true, message: '请选择获评GAP基地时间' }],
+    certFile: [{ required: true, message: '请上传官方公示证明材料' }],
+    url: [{ required: true, message: '请输入官方公示网址' }],
+
 });
 // saveInfo和submitForm防抖
 // 暂存基地信息
@@ -245,6 +256,7 @@ const mapDrawArea = async () => {
         form.value.coordinates = data.coordinates;
         form.value.lng = data.mapInfo.lng;
         form.value.lat = data.mapInfo.lat;
+        form.value.lnglat = String(data.mapInfo.lng) + ',' + String(data.mapInfo.lat);
         form.value.zoom = data.mapInfo.zoom;
         uni.$off('mapAreaData');
     });
@@ -287,7 +299,7 @@ const selectMedicine = () => {
     });
 };
 const confirmArea = (area: any) => {
-    form.value.gapInfo.adcodeName = area.fullName;
+    form.value.adcodeName = area.fullName;
 };
 onLoad((optins: any) => {
     if (optins.id) {
@@ -318,6 +330,7 @@ const getLocationByAddress = async () => {
         lat: location.latitude,
     };
 };
+
 </script>
 <style lang="scss" scoped>
 .z-paging-wrap {

+ 9 - 43
src/plant/base/gap-base-info/index.vue

@@ -26,20 +26,20 @@
                                 <view class="c-#999 f-s-24 pb-20">{{ item?.ratedDate }}获评</view>
                                 <view class="d-flex pb-5">
                                     <view class="w-50%">
-                                        <text class="c-#666 f-s-28">种养殖品种:</text>
-                                        <text class="c-#333 f-s-28 f-w-5">{{ item?.medicineName }}</text>
+                                        <text class="c-#666 f-s-28">种养殖品种</text>
+                                        <text class="c-#333 f-s-28 f-w-5">{{ item?.medicineName || '-' }}</text>
                                     </view>
                                     <view class="w-50%">
-                                        <text class="c-#666 f-s-28">基地面积:</text>
-                                        <text class="c-#333 f-s-28 f-w-5">{{ item?.area }}</text>
+                                        <text class="c-#666 f-s-28">基地面积</text>
+                                        <text class="c-#333 f-s-28 f-w-5">{{ item?.area }}{{ item?.areaUnit || '亩' }}</text>
                                     </view>
                                 </view>
                                 <view class="pb-20">
-                                    <text class="c-#666 f-s-28">基地地址:</text>
+                                    <text class="c-#666 f-s-28">基地地址</text>
                                     <text class="c-#333 f-s-28 f-w-5">{{ item?.address }}</text>
                                 </view>
                                 <view v-if="+item?.res == 2" class="pl-5 pr-5 pt-20 pb-20 border-top-#f7f7f7 c-#FC333F">
-                                    <text class="f-s-28">审核不通过原因:</text>
+                                    <text class="f-s-28">审核不通过原因</text>
                                     <text class="f-s-28">{{ item?.msg }}</text>
                                 </view>
                                 <image v-if="+item?.res == 2" class="w-145" src="/static/images/plant/resFailed.png" style="position: absolute; top: 0; right: 0" mode="widthFix" />
@@ -78,43 +78,8 @@
 import { copyText } from '@/utils/public';
 import { useClientRequest } from '@/utils/request';
 import { useInfoStore } from '@/store';
-interface ListItem {
-    rows: MedicineBase[];
-    code: number;
-    msg: string;
-    total: number;
-}
-interface MedicineBase {
-    id: number;
-    sourceType: string;
-    gapBaseName: string;
-    sn: string;
-    medicineName: string;
-    medicineId: number;
-    area: number;
-    basePic: string;
-    lng: number;
-    lat: number;
-    adcode: string;
-    address: string;
-    ratedDate: string;
-    certFile: Array<{
-        fileName: string;
-        url: string;
-        fileSize: number;
-    }>;
-    res: string; // 0-待审核 1-通过 2-审核不通过
-    auditor: number;
-    msg: string;
-    coordinates: Array<
-        Array<{
-            lng: number;
-            lat: number;
-        }>
-    >;
-}
 const paging = ref();
-const list = ref<MedicineBase[]>([]);
+const list = ref<any[]>([]);
 const placeholder = ref('搜基地名称、品种、基地地址');
 const tabs = ref([
     { label: '全部', value: '' },
@@ -132,6 +97,7 @@ const form = ref({
     keywords: '',
     type: '',
 });
+
 const query = async (pageNum: number, pageSize: number) => {
     const params = {
         pageNum,
@@ -140,7 +106,7 @@ const query = async (pageNum: number, pageSize: number) => {
     };
     // const res = await cpyList(params);
     ///app/gapCertificationInfo/pageList
-    const res = await useClientRequest.get<ListItem>('/plt-api/app/gapCertificationInfo/pageList', params);
+    const res = await useClientRequest.get<any>('/plt-api/app/gapCertificationInfo/pageList', params);
     const { rows } = res;
     paging.value.complete(rows);
 };

+ 29 - 1
src/utils/common.ts

@@ -357,4 +357,32 @@ export const isOpenSetting = async (scope?: string): Promise<boolean> => {
 
     // 非微信小程序平台默认返回可用
     return true;
-}
+}
+// 根据传入url获取文件后缀名,根据文件后缀名返回相应的图片icon
+export const getFileIconByUrl = (url: string) => {
+    const fileExtension = url.substring(url.lastIndexOf('.') + 1).toLowerCase();
+    const iconMap = {
+        jpg: 'jpg',
+        jpeg: 'jpg',
+        png: 'png',
+        gif: 'jpg',
+        pdf: 'pdf',
+        doc: 'doc',
+        docx: 'doc',
+        xls: 'xlsx',
+        xlsx: 'xlsx',
+        txt: 'txt',
+        // 大写后缀映射
+        JPG: 'jpg',
+        JPEG: 'jpg',
+        PNG: 'png',
+        GIF: 'jpg',
+        PDF: 'pdf',
+        DOC: 'doc',
+        DOCX: 'doc',
+        XLS: 'xlsx',
+        XLSX: 'xlsx',
+        TXT: 'txt'
+    };
+    return `https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images/file-type-sub/${iconMap?.[fileExtension] || 'def'}.png`; // 默认图标
+};

+ 2 - 0
src/utils/request.ts

@@ -68,6 +68,8 @@ export const request = ({ url, method = 'GET', data = {}, isToken = true, header
                     uni.hideLoading();
                     useInfoStore().removeToken();
                     let fullPath = recursiveDecodeURIComponent(getCurrentPage()?.$page?.fullPath);
+                    console.log(fullPath, '====');
+                    
                     const isLoginPage = recursiveDecodeURIComponent(fullPath).indexOf('/pages/login/login') !== -1;
                     if (isLoginPage) {
                         return;

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
stats.html


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.