addTree.vue 13.6 KB
<template>
  <view class="container">
    <up-form :model="formData" ref="formRef" label-width="140rpx" border-bottom>
      <up-form-item label="名称" prop="treetype" required>
        <up-input v-model="formData.treetype" placeholder="请输入名称" maxlength="30" border="none"/>
      </up-form-item>

      <!-- ✅ 核心修复1:给同行的两个表单外层包一层 弹性布局容器,彻底解决错位问题 -->
      <view class="form-row-wrap">
        <up-row gutter="10">
          <up-col span="6">
            <up-form-item label="胸径" prop="dbh" required>
              <up-input v-model="formData.dbh" placeholder="请输入" maxlength="10" border="none" input-align="left"/>
              <template #right>
                <text style="padding-left: 12rpx;color:#ccc;font-size:12px">厘米</text>
              </template>
            </up-form-item>
          </up-col>
          <up-col span="6">
            <up-form-item label="高度" prop="treeheight">
              <up-input v-model="formData.treeheight" placeholder="请输入" maxlength="10" border="none"
                        input-align="left"/>
              <template #right>
                <text style="padding-left: 12rpx;color:#ccc;font-size:14px;">米</text>
              </template>
            </up-form-item>
          </up-col>
        </up-row>
      </view>

      <up-form-item label="位置" prop="growlocation" required class="location-form-item" @click="openMap">
        <up-input v-model="formData.growlocation" placeholder="请选择" readonly border="none"/>
        <template #right>
          <up-icon name="map" size="22"></up-icon>
        </template>
      </up-form-item>

      <up-row gutter="10">
        <up-col span="6">
          <up-form-item label="经度" prop="longitude" :borderBottom="false">
            <up-input v-model="formData.longitude" placeholder="" readonly border="none"/>
          </up-form-item>
        </up-col>
        <up-col span="6">
          <up-form-item label="纬度" prop="latitude" :borderBottom="false">
            <up-input v-model="formData.latitude" placeholder="" readonly border="none"/>
          </up-form-item>
        </up-col>
      </up-row>

      <up-form-item label="管护单位" prop="managedutyunit" required>
        <up-input v-model="formData.managedutyunit" placeholder="请输入" maxlength="30" border="none"/>
      </up-form-item>

      <up-form-item label="权属分类" prop="oldtreeownershipText" required arrow @click="handleActionSheetOpen('ownership')">
        <up-input v-model="formData.oldtreeownershipText" placeholder="请选择" readonly border="none"
                  bg-color="transparent" />
      </up-form-item>

      <up-form-item label="图片信息" prop="treeImgList" required>
        <up-upload
            :file-list="treeImgs.imgList.value || []"
            @after-read="treeImgs.uploadImgs"
            @delete="treeImgs.deleteImg"
            multiple
            :width="70"
            :height="70"
            :max-count="treeImgs.uploadConfig.maxCount"
            :upload-text="treeImgs.uploadConfig.uploadText"
            :size-type="treeImgs.uploadConfig.sizeType"
        ></up-upload>
      </up-form-item>

      <view
          class="animated-area"
          :style="{
          height: isShow ? contentHeight + 'px' : '0',
          opacity: isShow ? 1 : 0,
          overflow: 'hidden'
        }"
      >
        <up-row gutter="10">
          <up-col span="6">
            <up-form-item label="拉丁文" prop="latinname">
              <up-input v-model="formData.latinname" placeholder="请输入" maxlength="30" border="none"/>
            </up-form-item>
          </up-col>
          <up-col span="6">
            <up-form-item label="级别" arrow @click="handleActionSheetOpen('level')">
              <up-input v-model="formData.treeleveltext" placeholder="请选择" readonly border="none"
                        bg-color="transparent"/>
            </up-form-item>
          </up-col>
        </up-row>

        <up-form-item label="生长环境" prop="growthenvironment">
          <up-input v-model="formData.growthenvironment" placeholder="请输入" maxlength="50" border="none"/>
        </up-form-item>

        <!-- ✅ 核心修复2:所有同行的表单都统一加样式,预防其他行也错位 -->
        <view class="form-row-wrap">
          <up-row gutter="10">
            <up-col span="6">
              <up-form-item label="预估树龄" prop="estimationtreeage">
                <up-input v-model="formData.estimationtreeage" placeholder="请输入" maxlength="10" border="none"
                          input-align="left"/>
                <template #right>
                  <text style="padding-left:12rpx;color:#ccc;font-size:14px">年</text>
                </template>
              </up-form-item>
            </up-col>
            <up-col span="6">
              <up-form-item label="干周" prop="weekday">
                <up-input v-model="formData.weekday" placeholder="请输入" maxlength="10" border="none"
                          input-align="left"/>
                <template #right>
                  <text style="padding-left:12rpx;color:#ccc;font-size:14px">厘米</text>
                </template>
              </up-form-item>
            </up-col>
          </up-row>
        </view>

        <view class="form-row-wrap">
          <up-row gutter="10">
            <up-col span="6">
              <up-form-item label="东西冠幅" prop="canopyeastwest">
                <up-input v-model="formData.canopyeastwest" placeholder="请输入" maxlength="10" border="none"
                          input-align="left"/>
                <template #right>
                  <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
                </template>
              </up-form-item>
            </up-col>
            <up-col span="6">
              <up-form-item label="南北冠幅" prop="canopysouthnorth">
                <up-input v-model="formData.canopysouthnorth" placeholder="请输入" maxlength="10" border="none"
                          input-align="left"/>
                <template #right>
                  <text style="padding-left:12rpx;color:#ccc;font-size:14px">米</text>
                </template>
              </up-form-item>
            </up-col>
          </up-row>
        </view>
      </view>

      <up-button
          @click="toggleArea"
          type="primary"
          plain
          size="large"
          style="margin-top:20rpx"
      >
        {{ isShow ? '- 隐藏区域' : '+ 显示区域' }}
      </up-button>
    </up-form>

    <view style="height: 60px;width: 100%"></view>

    <view class="fixed-bottom-btn-wrap">
      <up-button type="primary" @click="submit" :loading="loadingFlag" size="large" bold>提交</up-button>
    </view>

    <up-action-sheet
        :show="showActionSheet"
        :actions="currentActionSheetData.list"
        :title="currentActionSheetData.title"
        @close="handleActionSheetClose"
        @select="handleActionSheetSelect"
    ></up-action-sheet>

  </view>
</template>

<script setup>
import { ref, reactive, nextTick } from 'vue'
import { onReady, onLoad, onShow } from '@dcloudio/uni-app';
import { addTree } from "@/api/tree-archive/tree-archive.js";
import { useUploadImgs } from '@/common/utils/useUploadImgs'
import { useUserStore } from '@/pinia/user';

// ========== 状态管理 ==========
const userStore = useUserStore();
const formRef = ref(null)
const isShow = ref(false)
const contentHeight = ref(200)
const treeOwnershipData = ref([])
const treeLevelData = ref([])
const loadingFlag = ref(false)
const showActionSheet = ref(false);
const currentActionSheetData = reactive({ type: '', list: [], title: '' });

// 图片上传配置
const treeImgs = useUploadImgs({
  maxCount: 3,
  uploadText: '选择图片',
  sizeType: ['compressed'],
  formRef: formRef,
  fieldName: 'treeImgList'
})
if (!Array.isArray(treeImgs.rawImgList.value)) treeImgs.rawImgList.value = [];

// 表单数据
const formData = reactive({
  treetype: '',
  treeheight: '',
  dbh: '',
  treelevel: '',
  treeleveltext: '',
  managedutyunit: '',
  oldtreeownership: '',
  oldtreeownershipText: '',
  latinname: '',
  estimationtreeage: '',
  canopysouthnorth: '',
  canopyeastwest: '',
  weekday: '',
  growlocation: '',
  growthenvironment: '',
  treeImgList: [],
  address: '',
  latitude: '',
  longitude: '',
  road: '',
  maintainunit: ''
})

// ✅ 核心修复3:修复所有校验规则里的 语法错误 【trigger: ['blur','change]'] → trigger: ['blur','change']】
const rules = reactive({
  treetype: [{required: true, message: '请输入名称', trigger: ['blur','change']}],
  treeheight: [{max: 10, message: '树高不能超过10个字符', trigger: ['blur','change']}, {
    pattern: /^\d+(\.\d{1,2})?$/,
    message: '格式不正确',
    trigger: ['blur','change']
  }],
  dbh: [{required: true, message: '请输入胸径',trigger: ['blur','change']}, {
    max: 10,
    message: '胸径不能超过10个字符',
    trigger: ['blur','change']
  }, {pattern: /^\d+(\.\d{1,2})?$/, message: '格式不正确',trigger: ['blur','change']}],
  estimationtreeage: [{max: 10, message: '预估树龄不能超过10个字符', trigger: ['blur','change']}, {
    pattern: /^\d+(\.\d{1,2})?$/,
    message: '格式不正确',
    trigger: ['blur','change']
  }],
  weekday: [{max: 10, message: '干周不能超过10个字符', trigger: ['blur','change']}, {
    pattern: /^\d+(\.\d{1,2})?$/,
    message: '格式不正确',
    trigger: ['blur','change']
  }],
  canopyeastwest: [{max: 10, message: '东西冠幅不能超过10个字符',trigger: ['blur','change']}, {
    pattern: /^\d+(\.\d{1,2})?$/,
    message: '格式不正确',
    trigger: ['blur','change']
  }],
  canopysouthnorth: [{max: 10, message: '南北冠幅不能超过10个字符', trigger: ['blur','change']}, {
    pattern: /^\d+(\.\d{1,2})?$/,
    message: '格式不正确',
    trigger: ['blur','change']
  }],
  growlocation: [{required: true, message: '请地图选择位置', trigger: ['blur','change']}],
  managedutyunit: [{required: true, message: '请输入管护单位', trigger: ['blur','change']}],
  oldtreeownershipText: [{required: true, message: '请选择权属分类', trigger: ['blur','change']}],
  treeImgList: [treeImgs.imgValidateRule]
})

// 生命周期
onLoad((options) => {
  formData.road = options.roadId
})
onShow(async () => {
  treeLevelData.value = uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_level'))
  treeOwnershipData.value =  uni.$dict.transformLabelValueToNameValue(uni.$dict.getDictSimpleList('tree_ownership'))
})
onReady(() => {
  nextTick(() => {
    formRef.value?.setRules(rules);
  });
});

const toggleArea = () => {
  isShow.value = !isShow.value
}

const handleActionSheetOpen = (type) => {
  const configMap = {
    ownership: { title: '请选择权属分类', list: treeOwnershipData.value },
    level: { title: '请选择树木级别', list: treeLevelData.value }
  };
  Object.assign(currentActionSheetData, configMap[type], { type });
  showActionSheet.value = true;
};

const handleActionSheetClose = () => {
  showActionSheet.value = false;
  Object.assign(currentActionSheetData, { type: '', list: [], title: '' });
};

const handleActionSheetSelect = (e) => {
  const { type } = currentActionSheetData;
  if (type === 'ownership') {
    formData.oldtreeownership = e.value
    formData.oldtreeownershipText = e.name
    formRef.value?.validateField('oldtreeownershipText');
  } else if (type === 'level') {
    formData.treelevel = e.value
    formData.treeleveltext = e.name
  }
  handleActionSheetClose();
};

// 打开地图选址
const openMap = () => {
  uni.chooseLocation({
    success: (res) => {
      formData.growlocation = res.address
      formData.latitude = res.latitude
      formData.longitude = res.longitude
    },
    fail: (err) => {
      console.error('地图选择失败', err);
      if (err.errMsg.includes('auth deny')) {
        uni.showToast({title: '请授权位置权限', icon: 'none'});
      }
    }
  });
}

// 表单提交核心方法
const submit = async () => {
  if (loadingFlag.value) return
  const valid = await formRef.value.validate()
  if (!valid) return
  const uploadImgUrls = treeImgs.getSuccessImgUrls()
  formData.maintainunit = uni.getStorageSync('userInfo')?.belongCompanyId || ''
  formData.treeImgList = uploadImgUrls
  loadingFlag.value = true
  try {
    const res = await addTree(formData)
    uni.showToast({title: "新增成功", icon: "success"});
    uni.redirectTo({url: '/pages-sub/data/tree-archive/index'});
  } catch (err) {
    uni.showToast({title: "新增失败,请重试", icon: "none"});
    console.error(err)
  } finally {
    loadingFlag.value = false
  }
}
</script>

<style scoped lang="scss">
.container {
  padding: 25rpx;
  box-sizing: border-box;
  background: #fff;
}

.animated-area {
  transition: all 0.3s ease-out;
  margin-top: 10rpx;
}

.location-form-item {
  padding-right: 10rpx;
}

// ✅ ✅ ✅ 核心修复样式:解决校验提示导致的布局错位问题,优先级最高
.form-row-wrap {
  width: 100%;
  display: flex;
  flex-direction: column;
  // 关键:让校验提示文字 不撑开单元格,而是相对父容器定位
  :deep(.u-form-item) {
    position: relative;
    margin-bottom: 0 !important;
  }
  // 关键:校验错误提示文字 绝对定位在输入框下方,两行的提示互不影响
  :deep(.u-form-item__body__right__message ) {
    position: absolute;
    left: 0;
    bottom: -20rpx;
    line-height: 20rpx;
    font-size: 22rpx;
    color: #f56c6c;
    width: 100%;
    box-sizing: border-box;
  }
}
// 给同行表单底部留足提示文字的间距,防止和下一行重叠
.form-row-wrap + .u-form-item {
  margin-top: 25rpx !important;
}
</style>