Pārlūkot izejas kodu

feat:添加录入信息页面

wangsisi 13 stundas atpakaļ
vecāks
revīzija
87d5393e44

+ 641 - 11
src/views/old_mini/entry_information/components/selectVariety.vue

@@ -1,31 +1,661 @@
 <template>
     <div class="select-variety">
         <div class="select-variety__content">
-            <!-- 选择种植品种 -->
-            种植品种
+            <div class="page-header">
+                <div class="page-title">请选择您的种植品种</div>
+                <div class="page-subtitle">请确定您的果园设施以及内容</div>
+            </div>
+
+            <!-- 品类 Tab + 品种选择,照常显示 -->
+            <div class="category-tabs">
+                <div
+                    v-for="(tab, index) in categoryTabs"
+                    :key="tab.id"
+                    class="category-tab"
+                    :class="{
+                        active: activeCategoryId === tab.id,
+                        first: index === 0,
+                        last: index === categoryTabs.length - 1,
+                    }"
+                    @click="activeCategoryId = tab.id"
+                >
+                    <span>{{ tab.name }}</span>
+                </div>
+            </div>
+
+            <div class="variety-card">
+                <el-input
+                    v-model="searchKeyword"
+                    class="search-bar"
+                    placeholder="请输入品种"
+                    @keyup.enter="handleSearch"
+                >
+                    <template #prefix>
+                        <el-icon class="search-icon"><Search /></el-icon>
+                    </template>
+                    <template #suffix>
+                        <span class="search-btn" @click="handleSearch">搜索</span>
+                    </template>
+                </el-input>
+
+                <div class="variety-grid">
+                    <div
+                        v-for="item in visibleVarieties"
+                        :key="item.id"
+                        class="variety-item"
+                        :class="{ selected: isSelected(item.id) }"
+                        @click="toggleVariety(item)"
+                    >
+                        {{ item.name }}
+                    </div>
+                </div>
+
+                <div
+                    v-if="filteredVarieties.length > DEFAULT_VISIBLE_COUNT"
+                    class="expand-trigger"
+                    @click="gridExpanded = !gridExpanded"
+                >
+                    {{ gridExpanded ? "点击收起" : "点击展开更多" }}
+                </div>
+            </div>
+
+            <!-- 默认表单卡:始终在已选卡片上方 -->
+            <div class="form-card">
+                <div class="form-card__header" @click="defaultFormExpanded = !defaultFormExpanded">
+                    <span class="title-icon"></span>
+                    <span class="title-text">选择品种</span>
+                    <el-icon class="arrow-icon" :class="{ folded: !defaultFormExpanded }">
+                        <ArrowDown />
+                    </el-icon>
+                </div>
+
+                <div v-show="defaultFormExpanded" class="form-card__body">
+                    <div class="form-row">
+                        <span class="form-label muted">起种时间</span>
+                        <el-date-picker
+                            v-model="defaultForm.startTime"
+                            class="form-picker"
+                            type="date"
+                            placeholder="请选择时间"
+                            format="YYYY-MM-DD"
+                            value-format="YYYY-MM-DD"
+                            :clearable="false"
+                        />
+                    </div>
+
+                    <div class="form-row">
+                        <span class="form-label">种植亩数</span>
+                        <el-input
+                            v-model="defaultForm.area"
+                            class="form-input"
+                            placeholder="请输入亩数"
+                            type="number"
+                        />
+                    </div>
+
+                    <div class="form-row location-row" @click="handleSelectLocation(defaultForm)">
+                        <span class="form-label">
+                            选择种植点位{{ defaultForm.location ? "(已选)" : "" }}
+                        </span>
+                        <div v-if="defaultForm.location" class="map-thumb">
+                            <div class="map-thumb__pin-label">{{ defaultForm.location.name }}</div>
+                            <div class="map-thumb__pin"></div>
+                        </div>
+                        <span v-else class="form-placeholder">请选择点位</span>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 已选品种详情卡 -->
+            <div
+                v-for="item in selectedList"
+                :key="item.id"
+                class="form-card selected-card"
+            >
+                <div class="form-card__header" @click="toggleCardExpand(item.id)">
+                    <span class="title-icon"></span>
+                    <span class="title-text">{{ item.name }}</span>
+                    <el-icon
+                        class="arrow-icon"
+                        :class="{ folded: !item.expanded }"
+                    >
+                        <ArrowDown />
+                    </el-icon>
+                    <span class="delete-btn" @click.stop="removeVariety(item.id)">删除</span>
+                </div>
+
+                <div v-show="item.expanded" class="form-card__body">
+                    <div class="form-row">
+                        <span class="form-label">起种时间</span>
+                        <el-date-picker
+                            v-model="item.startTime"
+                            class="form-picker"
+                            type="date"
+                            placeholder="请选择时间"
+                            format="YYYY-MM-DD"
+                            value-format="YYYY-MM-DD"
+                            :clearable="false"
+                        />
+                    </div>
+
+                    <div class="form-row">
+                        <span class="form-label">种植亩数</span>
+                        <el-input
+                            v-model="item.area"
+                            class="form-input"
+                            placeholder="请输入亩数"
+                            type="number"
+                        />
+                    </div>
+
+                    <div class="form-row location-row" @click="handleSelectLocation(item)">
+                        <span class="form-label">
+                            选择种植点位{{ item.location ? "(已选)" : "" }}
+                        </span>
+                        <div v-if="item.location" class="map-thumb">
+                            <div class="map-thumb__pin-label">{{ item.location.name }}</div>
+                            <div class="map-thumb__pin"></div>
+                        </div>
+                        <span v-else class="form-placeholder">请选择点位</span>
+                    </div>
+                </div>
+            </div>
         </div>
+
         <div class="custom-bottom-fixed-btns">
             <div class="bottom-btn secondary-btn" @click="emit('prev')">上一步</div>
-            <div class="bottom-btn primary-btn" @click="emit('confirm')">确定信息</div>
+            <div class="bottom-btn primary-btn" @click="handleConfirm">确定信息</div>
         </div>
     </div>
 </template>
 
 <script setup>
+import { computed, reactive, ref, watch } from "vue";
+import { ArrowDown, Search } from "@element-plus/icons-vue";
+
 const emit = defineEmits(["prev", "confirm"]);
+
+const DEFAULT_VISIBLE_COUNT = 8;
+
+const categoryTabs = ref([
+    {
+        id: 1,
+        name: "品类一",
+        varieties: [
+            { id: "1-1", name: "妃子笑" },
+            { id: "1-2", name: "桂味" },
+            { id: "1-3", name: "桂早荔" },
+            { id: "1-4", name: "黑叶" },
+            { id: "1-5", name: "灵山香荔" },
+            { id: "1-6", name: "钦州红荔" },
+            { id: "1-7", name: "贵妃红" },
+            { id: "1-8", name: "翡脆" },
+            { id: "1-9", name: "糯米糍" },
+            { id: "1-10", name: "三月红" },
+            { id: "1-11", name: "白糖罂" },
+            { id: "1-12", name: "怀枝" },
+        ],
+    },
+    {
+        id: 2,
+        name: "品类二",
+        varieties: [
+            { id: "2-1", name: "品种A" },
+            { id: "2-2", name: "品种B" },
+            { id: "2-3", name: "品种C" },
+            { id: "2-4", name: "品种D" },
+        ],
+    },
+    {
+        id: 3,
+        name: "品类三",
+        varieties: [
+            { id: "3-1", name: "品种E" },
+            { id: "3-2", name: "品种F" },
+            { id: "3-3", name: "品种G" },
+            { id: "3-4", name: "品种H" },
+        ],
+    },
+]);
+
+const activeCategoryId = ref(1);
+const searchKeyword = ref("");
+const appliedKeyword = ref("");
+const gridExpanded = ref(false);
+const defaultFormExpanded = ref(true);
+const selectedList = ref([]);
+const defaultForm = reactive({
+    startTime: "",
+    area: "",
+    location: null,
+});
+
+const currentCategory = computed(
+    () => categoryTabs.value.find((tab) => tab.id === activeCategoryId.value) || categoryTabs.value[0]
+);
+
+const filteredVarieties = computed(() => {
+    const list = currentCategory.value?.varieties || [];
+    const kw = appliedKeyword.value.trim();
+    if (!kw) return list;
+    return list.filter((item) => item.name.includes(kw));
+});
+
+const visibleVarieties = computed(() => {
+    if (gridExpanded.value) return filteredVarieties.value;
+    return filteredVarieties.value.slice(0, DEFAULT_VISIBLE_COUNT);
+});
+
+watch(activeCategoryId, () => {
+    searchKeyword.value = "";
+    appliedKeyword.value = "";
+    gridExpanded.value = false;
+});
+
+const isSelected = (id) => selectedList.value.some((item) => item.id === id);
+
+const handleSearch = () => {
+    appliedKeyword.value = searchKeyword.value;
+    gridExpanded.value = false;
+};
+
+const createSelectedItem = (variety, category) => ({
+    id: variety.id,
+    name: variety.name,
+    categoryId: category.id,
+    categoryName: category.name,
+    expanded: true,
+    startTime: "",
+    area: "",
+    location: null,
+});
+
+const toggleVariety = (variety) => {
+    const index = selectedList.value.findIndex((item) => item.id === variety.id);
+    if (index > -1) {
+        selectedList.value.splice(index, 1);
+        return;
+    }
+    selectedList.value.push(createSelectedItem(variety, currentCategory.value));
+};
+
+const removeVariety = (id) => {
+    const index = selectedList.value.findIndex((item) => item.id === id);
+    if (index > -1) selectedList.value.splice(index, 1);
+};
+
+const toggleCardExpand = (id) => {
+    const item = selectedList.value.find((v) => v.id === id);
+    if (item) item.expanded = !item.expanded;
+};
+
+const handleSelectLocation = (item) => {
+    // TODO: 跳转选点 / 打开地图选点
+    if (!item.location) {
+        item.location = { name: "桂味点位", lat: 0, lng: 0 };
+    }
+};
+
+const handleConfirm = () => {
+    emit("confirm", {
+        defaultForm: { ...defaultForm },
+        varieties: selectedList.value.map(({ id, name, categoryId, categoryName, startTime, area, location }) => ({
+            id,
+            name,
+            categoryId,
+            categoryName,
+            startTime,
+            area,
+            location,
+        })),
+    });
+};
 </script>
 
 <style lang="scss" scoped>
 .select-variety {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-    overflow: hidden;
-    padding-bottom: 80px;
-
     &__content {
-        flex: 1;
-        overflow-y: auto;
+        padding: 20px 10px 100px;
+
+        .page-header {
+            margin-bottom: 20px;
+
+            .page-title {
+                font-size: 26px;
+                color: #005599;
+                font-family: "PangMenZhengDao";
+            }
+
+            .page-subtitle {
+                color: rgba(46, 46, 46, 0.4);
+            }
+        }
+
+        .category-tabs {
+            display: flex;
+            margin-bottom: 10px;
+            height: 30px;
+
+            .category-tab {
+                --arrow: 20px;
+                flex: 1;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                color: #4e5969;
+                background: #e8f3ff;
+                clip-path: polygon(
+                    0 0,
+                    calc(100% - var(--arrow)) 0,
+                    100% 50%,
+                    calc(100% - var(--arrow)) 100%,
+                    0 100%,
+                    var(--arrow) 50%
+                );
+                margin-left: -8px;
+
+                &.first {
+                    margin-left: 0;
+                    clip-path: polygon(
+                        0 0,
+                        calc(100% - var(--arrow)) 0,
+                        100% 50%,
+                        calc(100% - var(--arrow)) 100%,
+                        0 100%
+                    );
+                }
+
+                &.last {
+                    clip-path: polygon(
+                        0 0,
+                        calc(100% - var(--arrow)) 0,
+                        100% 50%,
+                        calc(100% - var(--arrow)) 100%,
+                        0 100%,
+                        var(--arrow) 50%
+                    );
+                }
+
+                &.active {
+                    color: #fff;
+                    background: #2199f8;
+                }
+            }
+        }
+
+        .variety-card {
+            margin-bottom: 10px;
+            background: rgba(255, 255, 255, 0.5);
+            border: 1px solid #fff;
+            border-radius: 10px;
+            padding: 10px 6px;
+
+            .search-bar {
+                margin-bottom: 10px;
+
+                :deep(.el-input__wrapper) {
+                    height: 36px;
+                    padding: 0 10px;
+                    background: #fff;
+                    border-radius: 6px;
+                    box-shadow: none;
+                }
+
+                :deep(.el-input__inner) {
+                    color: #1a1a1a;
+                    line-height: 20px;
+
+                    &::placeholder {
+                        color: rgba(29, 33, 41, 0.2);
+                    }
+                }
+
+                .search-icon {
+                    font-size: 16px;
+                    color: rgba(0, 0, 0, 0.2);
+                }
+
+                .search-btn {
+                    color: #2199f8;
+                }
+            }
+
+            .variety-grid {
+                display: grid;
+                grid-template-columns: repeat(4, minmax(0, 1fr));
+                gap: 8px;
+
+                .variety-item {
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    padding: 7px 0;
+                    background: #fff;
+                    border: 1px solid transparent;
+                    border-radius: 6px;
+                    color: #565656;
+                    text-align: center;
+
+                    &.selected {
+                        color: #2199f8;
+                        background: rgba(33, 153, 248, 0.1);
+                        border-color: #2199f8;
+                    }
+                }
+            }
+
+            .expand-trigger {
+                margin-top: 10px;
+                text-align: center;
+                color: rgba(0, 0, 0, 0.6);
+            }
+        }
+
+        .form-card {
+            margin-bottom: 10px;
+            background: #fff;
+            border-radius: 12px;
+            padding: 0 14px;
+            overflow: hidden;
+
+            &__header {
+                display: flex;
+                align-items: center;
+                height: 48px;
+                border-bottom: 1px solid #f0f0f0;
+
+                .title-icon {
+                    position: relative;
+                    width: 14px;
+                    height: 14px;
+                    flex-shrink: 0;
+                    margin-right: 8px;
+
+                    &::before,
+                    &::after {
+                        content: "";
+                        position: absolute;
+                        width: 10px;
+                        height: 10px;
+                        border-radius: 50%;
+                    }
+
+                    &::before {
+                        left: 0;
+                        top: 2px;
+                        background: #2199f8;
+                        opacity: 0.85;
+                    }
+
+                    &::after {
+                        right: 0;
+                        top: 0;
+                        background: #7ec8ff;
+                        opacity: 0.9;
+                    }
+                }
+
+                .title-text {
+                    font-size: 15px;
+                    color: #4e5969;
+                    font-weight: 500;
+                }
+
+                .arrow-icon {
+                    margin-left: 4px;
+                    font-size: 14px;
+                    color: #c0c4cc;
+                    transition: transform 0.2s;
+
+                    &.folded {
+                        transform: rotate(-90deg);
+                    }
+                }
+
+                .delete-btn {
+                    margin-left: auto;
+                    font-size: 14px;
+                    color: #f53f3f;
+                    line-height: 20px;
+                }
+            }
+
+            &__body {
+                padding: 8px 0 12px;
+            }
+
+            &.selected-card {
+                .form-card__header .title-text {
+                    color: #1a1a1a;
+                }
+            }
+
+            .form-row {
+                display: flex;
+                align-items: center;
+                min-height: 52px;
+                padding: 10px 0;
+
+                .form-label {
+                    flex-shrink: 0;
+                    font-size: 15px;
+                    color: #1a1a1a;
+                    line-height: 22px;
+
+                    &.muted {
+                        color: rgba(0, 0, 0, 0.35);
+                    }
+                }
+
+                .form-placeholder {
+                    flex: 1;
+                    text-align: right;
+                    font-size: 14px;
+                    color: rgba(0, 0, 0, 0.25);
+                }
+
+                .form-input {
+                    flex: 1;
+                    min-width: 0;
+
+                    :deep(.el-input__wrapper) {
+                        box-shadow: none;
+                        background: transparent;
+                        padding: 0;
+                        justify-content: flex-end;
+                    }
+
+                    :deep(.el-input__inner) {
+                        text-align: right;
+                        font-size: 14px;
+                        color: #1a1a1a;
+
+                        &::placeholder {
+                            color: rgba(0, 0, 0, 0.25);
+                        }
+                    }
+                }
+
+                .form-picker {
+                    flex: 1;
+                    min-width: 0;
+                    width: auto;
+
+                    :deep(.el-input) {
+                        width: 100%;
+                    }
+
+                    :deep(.el-input__wrapper) {
+                        box-shadow: none;
+                        background: transparent;
+                        padding: 0;
+                        justify-content: flex-end;
+                    }
+
+                    :deep(.el-input__prefix),
+                    :deep(.el-input__suffix) {
+                        display: none;
+                    }
+
+                    :deep(.el-input__inner) {
+                        text-align: right;
+                        font-size: 14px;
+                        color: #1a1a1a;
+
+                        &::placeholder {
+                            color: rgba(0, 0, 0, 0.25);
+                        }
+                    }
+                }
+
+                &.location-row {
+                    .map-thumb {
+                        position: relative;
+                        width: 72px;
+                        height: 48px;
+                        margin-left: auto;
+                        border-radius: 6px;
+                        overflow: hidden;
+                        background:
+                            linear-gradient(135deg, rgba(80, 140, 100, 0.35), transparent 50%),
+                            linear-gradient(180deg, #c8d4c0 0%, #a8b898 45%, #8fa07a 100%);
+                        box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.06);
+
+                        &__pin-label {
+                            position: absolute;
+                            left: 50%;
+                            top: 4px;
+                            transform: translateX(-50%);
+                            max-width: 90%;
+                            padding: 1px 5px;
+                            background: rgba(0, 0, 0, 0.75);
+                            border-radius: 2px;
+                            color: #fff;
+                            font-size: 9px;
+                            line-height: 14px;
+                            white-space: nowrap;
+                            overflow: hidden;
+                            text-overflow: ellipsis;
+                            z-index: 1;
+                        }
+
+                        &__pin {
+                            position: absolute;
+                            left: 50%;
+                            top: 55%;
+                            width: 10px;
+                            height: 10px;
+                            margin-left: -5px;
+                            background: #2199f8;
+                            border: 2px solid #fff;
+                            border-radius: 50% 50% 50% 0;
+                            transform: rotate(-45deg);
+                            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
+                        }
+                    }
+                }
+            }
+        }
     }
 
     .custom-bottom-fixed-btns {