|
|
@@ -0,0 +1,339 @@
|
|
|
+<template>
|
|
|
+ <popup
|
|
|
+ v-model:show="showValue"
|
|
|
+ round
|
|
|
+ closeable
|
|
|
+ class="add-zone-info-popup"
|
|
|
+ :close-on-click-overlay="false"
|
|
|
+ teleport="body"
|
|
|
+ >
|
|
|
+ <div class="add-zone-info-popup__content">
|
|
|
+ <div class="form-block">
|
|
|
+ <div class="form-block__title">{{ t("agriFile.plantCrop") }}</div>
|
|
|
+ <div class="form-block__row">
|
|
|
+ <el-select
|
|
|
+ v-model="form.categoryId"
|
|
|
+ class="form-select"
|
|
|
+ :placeholder="t('agriFile.cropCategory')"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ :loading="categoryLoading"
|
|
|
+ @change="handleCategoryChange"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in categoryOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ <el-select
|
|
|
+ v-model="form.varietyId"
|
|
|
+ class="form-select"
|
|
|
+ :placeholder="t('agriFile.cropVariety')"
|
|
|
+ clearable
|
|
|
+ filterable
|
|
|
+ :loading="varietyLoading"
|
|
|
+ :disabled="!form.categoryId"
|
|
|
+ >
|
|
|
+ <el-option
|
|
|
+ v-for="item in varietyOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="item.id"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-block">
|
|
|
+ <div class="form-block__title">{{ startingPointQuestion }}</div>
|
|
|
+ <div class="form-date-wrap">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="form.plantDate"
|
|
|
+ class="form-date"
|
|
|
+ type="date"
|
|
|
+ :placeholder="t('agriFile.selectTime')"
|
|
|
+ format="YYYY-MM-DD"
|
|
|
+ value-format="YYYY-MM-DD"
|
|
|
+ :editable="false"
|
|
|
+ :clearable="false"
|
|
|
+ :prefix-icon="null"
|
|
|
+ style="width: 100%"
|
|
|
+ />
|
|
|
+ <el-icon class="form-date-wrap__icon"><Clock /></el-icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="form-block">
|
|
|
+ <div class="form-block__title">{{ t("agriFile.regionName") }}</div>
|
|
|
+ <el-input
|
|
|
+ v-model="form.zoneName"
|
|
|
+ :placeholder="t('agriFile.regionNamePlaceholder')"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="add-zone-info-popup__confirm" @click="handleConfirm">
|
|
|
+ {{ t("agriFile.confirmArea") }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </popup>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, reactive, ref, watch } from "vue";
|
|
|
+import { Popup } from "vant";
|
|
|
+import { ElMessage } from "element-plus";
|
|
|
+import { Clock } from "@element-plus/icons-vue";
|
|
|
+import { useI18n } from "@/i18n";
|
|
|
+
|
|
|
+const { t } = useI18n();
|
|
|
+
|
|
|
+const FIRST_CROPS = ["果树", "大田"];
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ show: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits(["update:show", "confirm"]);
|
|
|
+
|
|
|
+const form = reactive({
|
|
|
+ categoryId: "",
|
|
|
+ varietyId: "",
|
|
|
+ plantDate: "",
|
|
|
+ zoneName: "",
|
|
|
+});
|
|
|
+
|
|
|
+const categoryLoading = ref(false);
|
|
|
+const varietyLoading = ref(false);
|
|
|
+const categoryOptions = ref([]);
|
|
|
+const varietyOptions = ref([]);
|
|
|
+const startingPointQuestion = ref(t("agriFile.plantStartPoint"));
|
|
|
+const categoryLoaded = ref(false);
|
|
|
+
|
|
|
+const showValue = computed({
|
|
|
+ get: () => props.show,
|
|
|
+ set: (value) => emit("update:show", value),
|
|
|
+});
|
|
|
+
|
|
|
+const flattenThirdCrops = (list) => {
|
|
|
+ if (!Array.isArray(list)) return [];
|
|
|
+ const map = new Map();
|
|
|
+ list.forEach((group) => {
|
|
|
+ (group?.third_crops || []).forEach((crop) => {
|
|
|
+ if (crop?.id == null || !crop?.name) return;
|
|
|
+ map.set(String(crop.id), {
|
|
|
+ id: crop.id,
|
|
|
+ name: crop.name,
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ return Array.from(map.values());
|
|
|
+};
|
|
|
+
|
|
|
+const fetchCategoryOptions = async () => {
|
|
|
+ if (categoryLoaded.value || categoryLoading.value) return;
|
|
|
+ categoryLoading.value = true;
|
|
|
+ try {
|
|
|
+ const results = await Promise.all(
|
|
|
+ FIRST_CROPS.map((firstCrop) =>
|
|
|
+ VE_API.entry.getCropHierarchy({ first_crop: firstCrop })
|
|
|
+ )
|
|
|
+ );
|
|
|
+ const merged = [];
|
|
|
+ results.forEach((res) => {
|
|
|
+ if (res?.code === 200) {
|
|
|
+ merged.push(...flattenThirdCrops(res.data));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ // 按 id 去重(果树/大田可能有交叉)
|
|
|
+ const uniqueMap = new Map();
|
|
|
+ merged.forEach((item) => uniqueMap.set(String(item.id), item));
|
|
|
+ categoryOptions.value = Array.from(uniqueMap.values());
|
|
|
+ categoryLoaded.value = true;
|
|
|
+ } catch {
|
|
|
+ categoryOptions.value = [];
|
|
|
+ ElMessage.error("获取种植品类失败,请稍后再试");
|
|
|
+ } finally {
|
|
|
+ categoryLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const fetchVarietyOptions = async (cropId) => {
|
|
|
+ if (cropId == null || cropId === "") {
|
|
|
+ varietyOptions.value = [];
|
|
|
+ startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ varietyLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await VE_API.entry.getVarietiesByCrop({
|
|
|
+ crop_id: cropId,
|
|
|
+ });
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ varietyOptions.value = (res.data.varieties || []).map((item) => ({
|
|
|
+ id: item.id,
|
|
|
+ name: item.name,
|
|
|
+ }));
|
|
|
+ startingPointQuestion.value =
|
|
|
+ res.data.starting_point_question || t("agriFile.plantStartPoint");
|
|
|
+ } else {
|
|
|
+ varietyOptions.value = [];
|
|
|
+ startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
+ ElMessage.error(res.msg || "获取品种列表失败");
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ varietyOptions.value = [];
|
|
|
+ startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
+ ElMessage.error("获取品种列表失败,请稍后再试");
|
|
|
+ } finally {
|
|
|
+ varietyLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const resetForm = () => {
|
|
|
+ form.categoryId = "";
|
|
|
+ form.varietyId = "";
|
|
|
+ form.plantDate = "";
|
|
|
+ form.zoneName = "";
|
|
|
+ varietyOptions.value = [];
|
|
|
+ startingPointQuestion.value = t("agriFile.plantStartPoint");
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => props.show,
|
|
|
+ (val) => {
|
|
|
+ if (!val) return;
|
|
|
+ resetForm();
|
|
|
+ fetchCategoryOptions();
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+const handleCategoryChange = (cropId) => {
|
|
|
+ form.varietyId = "";
|
|
|
+ fetchVarietyOptions(cropId);
|
|
|
+};
|
|
|
+
|
|
|
+const handleConfirm = () => {
|
|
|
+ if (!form.categoryId) {
|
|
|
+ ElMessage.warning(t("agriFile.pleaseSelectCategory"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!form.varietyId) {
|
|
|
+ ElMessage.warning(t("agriFile.pleaseSelectVariety"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!form.plantDate) {
|
|
|
+ ElMessage.warning(t("agriFile.pleaseSelectPlantDate"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const zoneName = form.zoneName.trim();
|
|
|
+ if (!zoneName) {
|
|
|
+ ElMessage.warning(t("agriFile.pleaseInputRegionName"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const category = categoryOptions.value.find(
|
|
|
+ (item) => String(item.id) === String(form.categoryId)
|
|
|
+ );
|
|
|
+ const variety = varietyOptions.value.find(
|
|
|
+ (item) => String(item.id) === String(form.varietyId)
|
|
|
+ );
|
|
|
+
|
|
|
+ emit("confirm", {
|
|
|
+ zone_name: zoneName,
|
|
|
+ category_id: form.categoryId,
|
|
|
+ category_name: category?.name || "",
|
|
|
+ variety_id: form.varietyId,
|
|
|
+ variety_name: variety?.name || "",
|
|
|
+ plant_date: form.plantDate,
|
|
|
+ });
|
|
|
+ emit("update:show", false);
|
|
|
+};
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.add-zone-info-popup {
|
|
|
+ width: 330px;
|
|
|
+
|
|
|
+ :deep(.van-popup__close-icon) {
|
|
|
+ color: #333;
|
|
|
+ font-size: 18px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__content {
|
|
|
+ padding: 24px 16px 20px;
|
|
|
+ background: linear-gradient(360deg, #ffffff 74.2%, #d1ebff 100%);
|
|
|
+ border-radius: 16px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-block {
|
|
|
+ & + .form-block {
|
|
|
+ margin-top: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__title {
|
|
|
+ margin-bottom: 10px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__row {
|
|
|
+ display: flex;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-select {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-select__wrapper),
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ min-height: 40px;
|
|
|
+ border-radius: 4px;
|
|
|
+ box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1) inset;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-date-editor.el-input) {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-date-editor .el-input__wrapper) {
|
|
|
+ padding-left: 12px;
|
|
|
+ padding-right: 36px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .form-date-wrap {
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ &__icon {
|
|
|
+ position: absolute;
|
|
|
+ right: 12px;
|
|
|
+ top: 50%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ color: rgba(0, 0, 0, 0.35);
|
|
|
+ font-size: 16px;
|
|
|
+ pointer-events: none;
|
|
|
+ z-index: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ &__confirm {
|
|
|
+ margin-top: 24px;
|
|
|
+ padding: 10px;
|
|
|
+ border-radius: 25px;
|
|
|
+ background: #2199f8;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 16px;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|