刘秀芳 пре 1 недеља
родитељ
комит
7ec5b10a0b

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "my-project",
+  "name": "adopt-mini-h5",
   "private": true,
   "version": "0.0.0",
   "type": "module",

+ 4 - 0
src/api/modules/lj_home.js

@@ -9,4 +9,8 @@ export default {
     url: config.base_dev_url + 'z_farm_buy/getLightTree',
     type: 'get',
   },
+  farmList: {
+    url: config.base_dev_url + 'z_farm_buy/farmList',
+    type: 'get',
+  },
 }

BIN
src/assets/font/PangMenZhengDao.TTF


BIN
src/assets/img/old_mini/gybg.png


+ 59 - 52
src/assets/less/index.less

@@ -1,77 +1,84 @@
 :root {
-  --home-header-height: 44rem;
-  --footer-height: 56rem;
-  --footer-bottom-offset: 30rem;
-  --footer-safe-gap: 22rem;
-  --footer-safe-bottom: calc(
-    var(--footer-bottom-offset) + var(--footer-height) + var(--footer-safe-gap)
-  );
-  --main-bg: rgb(21, 23, 36);
-  --second-btn-color: rgb(58, 58, 70);
-  --footer-color: black;
-  --mask-dark: #000000bb;
-  --mask-light: rgba(0, 0, 0, 0.45);
-  --second-text-color: #999;
-  --mask-white: rgba(0, 0, 0, 0.4);
-  --second-btn-color-tran: rgba(58, 58, 70, 0.4);
-  --primary-btn-color: #fe2c55;
+    --home-header-height: 44rem;
+    --footer-height: 56rem;
+    --footer-bottom-offset: 30rem;
+    --footer-safe-gap: 22rem;
+    --footer-safe-bottom: calc(var(--footer-bottom-offset) + var(--footer-height) + var(--footer-safe-gap));
+    --main-bg: rgb(21, 23, 36);
+    --second-btn-color: rgb(58, 58, 70);
+    --footer-color: black;
+    --mask-dark: #000000bb;
+    --mask-light: rgba(0, 0, 0, 0.45);
+    --second-text-color: #999;
+    --mask-white: rgba(0, 0, 0, 0.4);
+    --second-btn-color-tran: rgba(58, 58, 70, 0.4);
+    --primary-btn-color: #fe2c55;
 }
 
 * {
-  user-select: none;
-  -webkit-tap-highlight-color: transparent;
+    user-select: none;
+    -webkit-tap-highlight-color: transparent;
 }
 
 html,
 body {
-  width: 100%;
-  height: 100%;
-  background: var(--main-bg);
-  font-size: 1px;
-  margin: 0;
-  padding: 0;
-  overflow: hidden;
+    width: 100%;
+    height: 100%;
+    background: var(--main-bg);
+    font-size: 1px;
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
 }
 
 #app {
-  height: 100%;
-  width: 100%;
-  position: relative;
-  font-size: 14rem;
+    height: 100%;
+    width: 100%;
+    position: relative;
+    font-size: 14rem;
 }
 
 .slide {
-  touch-action: none;
-  height: 100%;
-  width: 100%;
-  position: relative;
-  overflow: hidden;
-
-  .slide-list {
+    touch-action: none;
     height: 100%;
     width: 100%;
-    display: flex;
     position: relative;
-  }
+    overflow: hidden;
 
-  &.vertical {
-    height: 100%;
-  }
+    .slide-list {
+        height: 100%;
+        width: 100%;
+        display: flex;
+        position: relative;
+    }
+
+    &.vertical {
+        height: 100%;
+    }
 }
 
 .flex-direction-column {
-  flex-direction: column;
+    flex-direction: column;
 }
 
 .global-notice {
-  position: fixed;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  background: rgba(0, 0, 0, 0.75);
-  color: white;
-  padding: 12rem 20rem;
-  border-radius: 8rem;
-  font-size: 14rem;
-  z-index: 9999;
+    position: fixed;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    background: rgba(0, 0, 0, 0.75);
+    color: white;
+    padding: 12rem 20rem;
+    border-radius: 8rem;
+    font-size: 14rem;
+    z-index: 9999;
+}
+
+@font-face {
+    font-family: "PangMenZhengDao";
+    src: url("@/assets/font/PangMenZhengDao.TTF");
+}
+.ol-zoom {
+    /*隐藏地图左上角的+-号*/
+    display: none;
 }

+ 73 - 0
src/components/BaseHeader.vue

@@ -0,0 +1,73 @@
+<template>
+    <div class="custom-header" :style="{background:bgColor}">
+        <div class="back" @click="goback">
+            <Icon icon="eva:arrow-ios-back-fill" class="icon" />
+        </div>
+        <div class="title">{{name}}</div>
+    </div>
+</template>
+
+<script setup>
+import { useRouter } from 'vue-router'
+import { Icon } from '@iconify/vue'
+const router = useRouter();
+const props = defineProps({
+    name:{
+        type:String,
+        defalut:''
+    },
+    bgColor:{
+        type:String,
+        defalut:'#fff'
+    },
+    isGoBack:{
+        type:Boolean,
+        defalut:false
+    },
+})
+
+const emit = defineEmits(["goback"])
+
+
+const goback = () => {
+  if(!props.isGoBack){
+    router.go(-1);
+  }else{
+    emit('goback')
+  }
+};
+
+</script>
+
+<style lang="less" scoped>
+.custom-header {
+    background: #fff;
+    width: 100%;
+    height: 40px;
+    font-size: 17px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    z-index: 12;
+
+    .back,
+    .close {
+        display: flex;
+        align-items: center;
+        color: #656565;
+        position: absolute;
+        left: 12px;
+        z-index: 10;
+
+        .icon {
+            font-size: 22px;
+            color: rgba(0, 0, 0, 0.9);
+        }
+    }
+
+    .title {
+        font-weight: bold;
+    }
+}
+</style>

+ 0 - 6
src/router/globalRoutes.js

@@ -5,10 +5,4 @@ export default [
     meta: { title: '农场美景' },
     component: () => import('@/views/home/index.vue'),
   },
-  {
-    path: '/adopt_map',
-    name: 'AdoptMap',
-    meta: { title: '领养地图' },
-    component: () => import('@/views/adopt_map/index.vue'),
-  },
 ]

+ 9 - 3
src/router/mainRoutes.js

@@ -1,9 +1,9 @@
 export default [
     {
-        path: '/guard-map',
-        name: 'GuardMap',
+        path: '/adopt_map',
+        name: 'AdoptMap',
         meta: { title: '守护地图' },
-        component: () => import('@/views/guard-map/index.vue'),
+        component: () => import('@/views/adopt_map/index.vue'),
     },
     {
         path: '/adopt_map_select',
@@ -12,6 +12,12 @@ export default [
         component: () => import('@/views/adopt_map/index.vue'),
     },
     {
+        path: '/farm-select',
+        name: 'FarmSelect',
+        meta: { title: '切换农场' },
+        component: () => import('@/views/farm-select/index.vue'),
+    },
+    {
         path: '/my-guard',
         name: 'MyGuard',
         meta: { title: '我的守护' },

+ 0 - 21
src/utils/ol-map/VectorStyle.js

@@ -11,7 +11,6 @@ import Icon from "ol/style/Icon";
 import Circle from "ol/style/Circle";
 import Photo from "ol-ext/style/Photo";
 import config from '../../api/config.js'
-import gybgImg from '@/assets/img/old_mini/gybg.png'
 
 const { base_img_url2 } = config
 /**
@@ -60,26 +59,6 @@ class VectorStyle{
 		}
 	}
 
-  	getGardenReportStyle(gardenPointLayer){
-		let style1 = new Style({
-			image: new Photo({
-				src: gybgImg,
-				radius: 36,
-				shadow: 0,
-				crop: false,
-				onload: function () {
-					gardenPointLayer.layer.changed();
-				},
-				displacement: [70, 0],
-				stroke: new Stroke({
-					width: 2,
-					color: "#fdfcfc00",
-				}),
-			}),
-		});
-		return style1
-	}
-
 	getLineStyle(fillColor, strokeColor, strokeWidth){
 		let style = new Style({
 			stroke: new Stroke({

+ 178 - 40
src/views/adopt_map/index.vue

@@ -3,28 +3,38 @@
         <div class="garden-info" v-if="gardenObj && !enterSelectTree">
             <div class="panel-title">
                 <div class="title-l">
-                    <div class="title-info">
-                        <div class="title-garden">{{ gardenObj.farmName || '果园地图' }}</div>
-                        <div class="btn-second" v-if="gardenObj.youweiIndex != null">
-                            有味指数
-                            <text>{{ gardenObj.youweiIndex }}分</text>
-                        </div>
+                    <div class="title-garden-box">
+                        <img :src="farmAvatar" class="farm-avatar" alt="" />
+                        <span class="title-garden">{{ gardenObj.farmName || '农场名称' }}</span>
                     </div>
+                    <div class="btn-switch" @click="onSwitchFarm">切换农场</div>
                 </div>
                 <div class="title-r" v-if="gardenObj.people != null">
-                    <span class="guard-label">守护人数</span>
-                    <span class="guard-val">{{ gardenObj.people }}</span>
+                    <div class="avatar-stack" v-if="adoptAvatars.length">
+                        <img
+                            v-for="(avatar, idx) in adoptAvatars"
+                            :key="idx"
+                            :src="avatar"
+                            class="stack-avatar"
+                            alt=""
+                        />
+                    </div>
+                    <span class="adopt-text">
+                        已有 <span class="adopt-val">{{ gardenObj.people }}</span> 人认养
+                    </span>
                 </div>
             </div>
         </div>
 
         <div class="select-tips" v-if="enterSelectTree">请选择您要守护的农场区域</div>
+        <div class="select-btn" :class="{ active: selectedRegion }" v-if="enterSelectTree">我选好了</div>
+        <div class="select-btn invite-btn" v-if="!enterSelectTree">邀请好友守护</div>
 
         <div ref="mapRef" class="map"></div>
 
         <!-- <div class="top-mask"></div> -->
 
-        <!-- <BaseFooter /> -->
+        <BaseFooter v-if="!enterSelectTree" :init-tab="2" />
     </div>
 </template>
 
@@ -32,11 +42,12 @@
 import IndexMap from './map/index.js'
 import RegionLayer from './map/regionLayer.js'
 import ClusterPointsLayer from './map/clusterPointsLayer.js'
-import { onMounted, onUnmounted, ref } from 'vue'
+import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { useStore } from 'vuex'
 import { SET_TOKEN } from '@/store/modules/app/type.js'
-// import BaseFooter from '@/components/BaseFooter.vue'
+import { _checkImgUrl } from '@/utils'
+import BaseFooter from '@/components/BaseFooter.vue'
 
 const store = useStore()
 const route = useRoute()
@@ -47,10 +58,43 @@ let regionLayer = null
 let clusterPointsLayer = null
 
 const enterSelectTree = ref(false)
+const selectedRegion = ref(null)
 const gardenObj = ref(null)
 const currenFarmId = ref(null)
 
 const defaultCenter = 'POINT(111.0105596 21.77287165)'
+const DEFAULT_AVATAR = 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png'
+
+const farmAvatar = computed(() => {
+    const obj = gardenObj.value || {}
+    return _checkImgUrl(obj.farmImg || obj.farmPic || obj.cover || obj.pic) || DEFAULT_AVATAR
+})
+
+const adoptAvatars = computed(() => {
+    const obj = gardenObj.value || {}
+    const list = obj.userList || obj.avatarList || obj.buyUserList || obj.userAvatarList || []
+    if (Array.isArray(list) && list.length) {
+        return list.slice(0, 3).map((item) => {
+            if (typeof item === 'string') return _checkImgUrl(item) || DEFAULT_AVATAR
+            return _checkImgUrl(item.avatar || item.headImg || item.userAvatar) || DEFAULT_AVATAR
+        })
+    }
+    if (obj.people > 0) {
+        return [DEFAULT_AVATAR, DEFAULT_AVATAR, DEFAULT_AVATAR]
+    }
+    return []
+})
+
+function onSwitchFarm() {
+    router.push({
+        path: '/farm-select',
+        query: { ...route.query },
+    })
+}
+
+async function reloadMapData() {
+    await Promise.all([getGardenInfo(), getBlueRegionList(), getPointList()])
+}
 
 function isEnterSelectTreeQuery(val) {
     if (val === undefined || val === null || val === '') return false
@@ -262,19 +306,28 @@ onMounted(async () => {
     regionLayer = new RegionLayer(indexMap.kmap, {
         enterSelectTree: enterSelectTree.value,
         onRegionSelect: (region) => {
-            console.log('[adopt_map] 选中分区', region)
+            selectedRegion.value = region
         },
     })
     if (!enterSelectTree.value) {
         clusterPointsLayer = new ClusterPointsLayer(indexMap.kmap)
     }
 
-    await Promise.all([getGardenInfo(), getBlueRegionList(), getPointList()])
+    await reloadMapData()
 
     regionLayer.setEnterSelectTree(enterSelectTree.value)
 
 })
 
+watch(
+    () => route.query.farmId,
+    async (newId, oldId) => {
+        if (!newId || String(newId) === String(oldId)) return
+        currenFarmId.value = newId
+        await reloadMapData()
+    },
+)
+
 </script>
 
 <style scoped lang="less">
@@ -304,55 +357,140 @@ onMounted(async () => {
         border-radius: 30px;
     }
 
+    .select-btn {
+        position: absolute;
+        z-index: 3;
+        bottom: 60px;
+        left: 50%;
+        transform: translateX(-50%);
+        background: linear-gradient(141.52deg, #FFE6B2 7.21%, #FFA617 95.86%);
+        color: #000000;
+        font-size: 16px;
+        width: 152px;
+        text-align: center;
+        line-height: 40px;
+        border: 1px solid #FFFFFF;
+        box-sizing: border-box;
+        height: 40px;
+        border-radius: 20px;
+        font-family: "PangMenZhengDao";
+        opacity: 0.4;
+
+        &.active,
+        &.invite-btn {
+            opacity: 1;
+        }
+        &.invite-btn {
+            box-shadow: 0px 0px 8px 0px #F3CA88;
+            bottom: 120px;
+        }
+    }
+
     .garden-info {
         position: absolute;
         z-index: 3;
-        top: 0;
-        left: 0;
-        width: 100%;
+        top: 10px;
+        left: 12px;
+        right: 0;
+        width: auto;
 
         .panel-title {
             display: flex;
             align-items: center;
             justify-content: space-between;
-            padding: 12px;
+            min-width: 0;
+            gap: 8px;
 
             .title-l {
+                display: flex;
+                align-items: center;
+                gap: 2px;
+                min-width: 0;
                 flex: 1;
+                overflow: hidden;
+                background: rgba(0, 0, 0, 0.5);
+                padding: 4px;
+                border-radius: 20px;
+                border: 0.5px solid rgba(255, 255, 255, 0.5);
+
+                .title-garden-box {
+                    display: flex;
+                    align-items: center;
+                    gap: 4px;
+                    min-width: 0;
+                    flex: 1;
+                    overflow: hidden;
+                }
+
+                .farm-avatar {
+                    width: 32px;
+                    height: 32px;
+                    border-radius: 50%;
+                    object-fit: cover;
+                    flex-shrink: 0;
+                }
 
                 .title-garden {
                     color: #fff;
                     font-size: 14px;
-                    font-weight: 600;
-                    padding-bottom: 5px;
+                    min-width: 0;
+                    flex: 1;
+                    white-space: nowrap;
+                    overflow: hidden;
+                    text-overflow: ellipsis;
                 }
+            }
 
-                .btn-second {
-                    padding: 0 15px;
-                    border-radius: 20px;
-                    font-size: 12px;
-                    color: #f3c11d;
-                    border: 1px solid #f3c11d;
-                    background: rgba(255, 217, 94, 0.28);
-                    height: 21px;
-                    line-height: 20px;
-                    width: fit-content;
-                }
+            .btn-switch {
+                flex-shrink: 0;
+                width: 68px;
+                text-align: center;
+                height: 28px;
+                line-height: 28px;
+                background: #FFA617;
+                border-radius: 20px;
+                color: #fff;
+                font-size: 12px;
+                cursor: pointer;
             }
 
             .title-r {
+                background: rgba(0, 0, 0, 0.6);
+                padding: 4px;
+                border-radius: 20px 0 0 20px;
                 display: flex;
-                flex-direction: column;
                 align-items: center;
-                font-size: 12px;
-                padding: 6px 12px;
-                background: rgba(255, 255, 255, 0.9);
-                border-radius: 8px;
-
-                .guard-val {
-                    color: #f3c11d;
-                    font-weight: 600;
-                    font-size: 16px;
+                gap: 5px;
+                flex-shrink: 0;
+                margin-left: auto;
+
+                .avatar-stack {
+                    display: flex;
+                    align-items: center;
+
+                    .stack-avatar {
+                        width: 22px;
+                        height: 22px;
+                        border-radius: 50%;
+                        border: 1px solid #fff;
+                        object-fit: cover;
+                        margin-left: -8px;
+
+                        &:first-child {
+                            margin-left: 0;
+                        }
+                    }
+                }
+
+                .adopt-text {
+                    font-size: 12px;
+                    color: #fff;
+                    white-space: nowrap;
+
+                    .adopt-val {
+                        color: #F9BE00;
+                        font-weight: 500;
+                    }
                 }
             }
         }

+ 1 - 1
src/views/adopt_map/map/regionLayer.js

@@ -10,7 +10,7 @@ import Overlay from 'ol/Overlay'
 import { getCenter } from 'ol/extent'
 import { createRegionInfoCard, updateRegionInfoCard } from './regionInfoCard.js'
 
-const FILL_DEFAULT = 'rgba(0, 0, 0, 0.06)'
+const FILL_DEFAULT = 'rgba(0, 0, 0, 0.4)'
 const FILL_SELECTED = 'rgba(255, 166, 23, 0.25)'
 const STROKE_DEFAULT = '#ffffff'
 const STROKE_SELECTED = '#FFA617'

+ 178 - 0
src/views/farm-select/index.vue

@@ -0,0 +1,178 @@
+<template>
+    <div class="farm-select-page">
+        <BaseHeader name="切换农场" />
+        <div class="farm-list">
+            <div
+                v-for="farm in farmList"
+                :key="farm.id"
+                class="farm-card"
+                :class="{ active: isCurrentFarm(farm) }"
+                @click="onSelectFarm(farm)"
+            >
+                <div class="farm-info">
+                    <div class="farm-name">{{ farm.farmName }}</div>
+                    <div class="farm-address">{{ farm.address }}</div>
+                </div>
+                <div class="farm-action">
+                    <span v-if="isCurrentFarm(farm)" class="btn-current">当前农场</span>
+                    <span v-else class="btn-set-default">设为默认农场</span>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import BaseHeader from '@/components/BaseHeader.vue'
+
+const DEFAULT_FARM_KEY = 'DEFAULT_FARM_ID'
+
+const MOCK_FARMS = [
+    {
+        id: 766,
+        farmName: '从化荔博园合作社',
+        address: '广东省广州市从化区某某街道',
+    },
+    {
+        id: 101532,
+        farmName: '莞荔园',
+        address: '广东省东莞市某某街道',
+    },
+    {
+        id: 768,
+        farmName: '从化荔博园合作社',
+        address: '广东省广州市从化区某某街道',
+    },
+    {
+        id: 769,
+        farmName: '从化荔博园合作社',
+        address: '广东省广州市从化区某某街道',
+    },
+]
+
+const route = useRoute()
+const router = useRouter()
+const farmList = ref([])
+const currentFarmId = ref(null)
+
+function normalizeFarm(item) {
+    const id = item.id ?? item.farmId
+    const farmName = item.farmName || item.name || '农场名称'
+    const address =
+        item.address ||
+        [item.province, item.city, item.district, item.street].filter(Boolean).join('') ||
+        '暂无地址'
+    return { id, farmName, address }
+}
+
+function isCurrentFarm(farm) {
+    return String(farm.id) === String(currentFarmId.value)
+}
+
+function onSelectFarm(farm) {
+    if (isCurrentFarm(farm)) return
+    localStorage.setItem(DEFAULT_FARM_KEY, String(farm.id))
+    router.replace({
+        path: '/adopt_map',
+        query: {
+            ...route.query,
+            farmId: farm.id,
+        },
+    })
+}
+
+async function fetchFarmList() {
+    // try {
+    //     const { data } = await VE_API.lj_home.farmList()
+    //     const list = Array.isArray(data) ? data : data?.list || data?.records || []
+    //     if (list.length) {
+    //         farmList.value = list.map(normalizeFarm)
+    //         return
+    //     }
+    // } catch (err) {
+    //     console.warn('[farm-select] farmList 请求失败,使用本地数据', err)
+    // }
+    farmList.value = MOCK_FARMS
+}
+
+onMounted(async () => {
+    currentFarmId.value = route.query.farmId || localStorage.getItem(DEFAULT_FARM_KEY) || 766
+    await fetchFarmList()
+})
+</script>
+
+<style scoped lang="less">
+.farm-select-page {
+    min-height: 100vh;
+    background: #F2F3F5;
+}
+
+.farm-list {
+    padding: 12px;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+}
+
+.farm-card {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 10px;
+    padding: 10px 12px;
+    background: #fff;
+    border-radius: 8px;
+    border: 1px solid transparent;
+    cursor: pointer;
+    box-sizing: border-box;
+
+    &.active {
+        border-color: #FFA617;
+
+        .farm-name {
+            color: #ffa617;
+        }
+    }
+
+    .farm-info {
+        flex: 1;
+        min-width: 0;
+    }
+
+    .farm-name {
+        font-size: 14px;
+        color: #1D2129;
+        line-height: 22px;
+    }
+
+    .farm-address {
+        font-size: 12px;
+        color: rgba(32, 32, 32, 0.4);
+        line-height: 18px;
+    }
+
+    .farm-action {
+        flex-shrink: 0;
+    }
+
+    .btn-current {
+        display: inline-block;
+        padding: 0 8px;
+        background: #FFA617;
+        border-radius: 2px;
+        color: #fff;
+        font-size: 12px;
+        height: 22px;
+        text-align: center;
+        line-height: 22px;
+    }
+
+    .btn-set-default {
+        color: #ffa617;
+        font-size: 12px;
+        white-space: nowrap;
+    }
+}
+</style>

+ 0 - 47
src/views/guard-map/index.vue

@@ -1,47 +0,0 @@
-<template>
-  <div class="tab-page">
-    <div class="page-body">
-      <h1>守护地图</h1>
-      <p class="desc">地图功能开发中</p>
-    </div>
-    <BaseFooter :init-tab="2" />
-  </div>
-</template>
-
-<script setup lang="ts">
-import BaseFooter from '@/components/BaseFooter.vue'
-</script>
-
-<style scoped lang="less">
-.tab-page {
-  width: 100%;
-  height: 100%;
-  min-height: calc(var(--vh, 1vh) * 100);
-  background: var(--main-bg);
-  position: relative;
-  overflow: hidden;
-}
-
-.page-body {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  height: calc(var(--vh, 1vh) * 100 - 80rem);
-  padding: 20rem;
-  box-sizing: border-box;
-
-  h1 {
-    margin: 0 0 12rem;
-    font-size: 20rem;
-    color: #fff;
-    font-weight: 500;
-  }
-
-  .desc {
-    margin: 0;
-    font-size: 14rem;
-    color: rgba(255, 255, 255, 0.5);
-  }
-}
-</style>