|
|
@@ -1,11 +1,26 @@
|
|
|
<template>
|
|
|
+
|
|
|
+<div class="add-btn">点击新建管理分区</div>
|
|
|
+
|
|
|
<floating-panel class="file-float-panel" :class="{'custom-panel': height === anchors[0]}" v-model:height="height" :anchors="anchors">
|
|
|
<div class="file-float-content">
|
|
|
<div class="float-tabs">
|
|
|
+ <div class="tab-active-bg" :style="activeBgStyle"></div>
|
|
|
<div class="tab-item" @click="changeTab(0)" :class="{ 'tab-item-active': activeTab === 0 }">物候记录</div>
|
|
|
<div class="tab-item" @click="changeTab(1)" :class="{ 'tab-item-active': activeTab === 1 }">异常记录</div>
|
|
|
<div class="tab-item" @click="changeTab(2)" :class="{ 'tab-item-active': activeTab === 2 }">农事记录</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class="tab-content-group" v-show="height !== anchors[0]">
|
|
|
+ <div class="tab-loading" v-if="isCurrentTabLoading">加载中...</div>
|
|
|
+ <div class="tab-empty" v-else-if="currentList.length === 0">暂无记录</div>
|
|
|
+ <div class="tab-content-item" v-for="item in currentList" :key="item.id">
|
|
|
+ <div class="time-tag">{{ item.date }}</div>
|
|
|
+ <div class="item-info">
|
|
|
+ {{ item.textPrefix }} <span class="blue-text">{{ item.highlight }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</floating-panel>
|
|
|
</template>
|
|
|
@@ -13,7 +28,7 @@
|
|
|
<script setup>
|
|
|
|
|
|
import { FloatingPanel } from 'vant';
|
|
|
-import { ref } from 'vue';
|
|
|
+import { computed, ref } from 'vue';
|
|
|
const anchors = [
|
|
|
130,
|
|
|
Math.round(0.4 * window.innerHeight),
|
|
|
@@ -22,13 +37,74 @@ const anchors = [
|
|
|
const height = ref(anchors[0]);
|
|
|
|
|
|
const activeTab = ref(0);
|
|
|
+const dataMap = ref({ 0: [], 1: [], 2: [] });
|
|
|
+const loadingMap = ref({ 0: false, 1: false, 2: false });
|
|
|
+const loadedMap = ref({ 0: false, 1: false, 2: false });
|
|
|
+
|
|
|
const changeTab = (index) => {
|
|
|
activeTab.value = index;
|
|
|
+ ensureTabData(index);
|
|
|
};
|
|
|
|
|
|
+const activeBgStyle = computed(() => ({
|
|
|
+ transform: `translateX(${activeTab.value * 100}%)`,
|
|
|
+}));
|
|
|
+
|
|
|
+const currentList = computed(() => dataMap.value[activeTab.value] || []);
|
|
|
+const isCurrentTabLoading = computed(() => !!loadingMap.value[activeTab.value]);
|
|
|
+
|
|
|
+function getMockDataByTab(tab) {
|
|
|
+ if (tab === 0) {
|
|
|
+ return [
|
|
|
+ { id: "p1", date: "04/18", textPrefix: "管理分区一出现 新梢占比为", highlight: "15 %" },
|
|
|
+ { id: "p2", date: "04/19", textPrefix: "管理分区二进入 花穗萌动", highlight: "60 %" },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ if (tab === 1) {
|
|
|
+ return [
|
|
|
+ { id: "a1", date: "04/18", textPrefix: "管理分区三发现 异常叶片占比", highlight: "8 %" },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ return [
|
|
|
+ { id: "w1", date: "04/17", textPrefix: "管理分区一完成 修枝作业进度", highlight: "100 %" },
|
|
|
+ { id: "w2", date: "04/18", textPrefix: "管理分区二完成 灌溉作业进度", highlight: "100 %" },
|
|
|
+ ];
|
|
|
+}
|
|
|
+
|
|
|
+async function ensureTabData(tab) {
|
|
|
+ if (loadedMap.value[tab] || loadingMap.value[tab]) return;
|
|
|
+ loadingMap.value[tab] = true;
|
|
|
+ try {
|
|
|
+ // TODO: 替换为真实接口请求,按 tab 类型获取不同记录
|
|
|
+ const list = await new Promise((resolve) => {
|
|
|
+ setTimeout(() => resolve(getMockDataByTab(tab)), 250);
|
|
|
+ });
|
|
|
+ dataMap.value[tab] = list || [];
|
|
|
+ loadedMap.value[tab] = true;
|
|
|
+ } finally {
|
|
|
+ loadingMap.value[tab] = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+ensureTabData(0);
|
|
|
+
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
+
|
|
|
+.add-btn {
|
|
|
+ position: fixed;
|
|
|
+ top: 50%;
|
|
|
+ left: 50%;
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
+ color: #fff;
|
|
|
+ border-radius: 20px;
|
|
|
+ padding: 0 20px;
|
|
|
+ background: #2199f8;
|
|
|
+ height: 40px;
|
|
|
+ line-height: 40px;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
.file-float-panel {
|
|
|
left: 12px;
|
|
|
width: calc(100% - 24px);
|
|
|
@@ -51,24 +127,80 @@ const changeTab = (index) => {
|
|
|
background: #fff;
|
|
|
border-radius: 0 0 10px 10px;
|
|
|
.float-tabs {
|
|
|
+ position: relative;
|
|
|
border-radius: 4px;
|
|
|
padding: 3px;
|
|
|
background: #E9E9E9;
|
|
|
- display: flex;
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
|
align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
+ overflow: hidden;
|
|
|
+
|
|
|
+ .tab-active-bg {
|
|
|
+ position: absolute;
|
|
|
+ top: 3px;
|
|
|
+ left: 3px;
|
|
|
+ width: calc((100% - 6px) / 3);
|
|
|
+ height: 26px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ transition: transform 0.25s ease;
|
|
|
+ }
|
|
|
+
|
|
|
.tab-item {
|
|
|
+ position: relative;
|
|
|
+ z-index: 1;
|
|
|
flex: 1;
|
|
|
height: 26px;
|
|
|
line-height: 26px;
|
|
|
text-align: center;
|
|
|
color: #767676;
|
|
|
border-radius: 4px;
|
|
|
+ transition: color 0.2s ease;
|
|
|
&.tab-item-active {
|
|
|
- background: #fff;
|
|
|
color: #0D0D0D;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .tab-content-group {
|
|
|
+ padding-top: 12px;
|
|
|
+
|
|
|
+ .tab-loading,
|
|
|
+ .tab-empty {
|
|
|
+ text-align: center;
|
|
|
+ color: #9a9a9a;
|
|
|
+ font-size: 13px;
|
|
|
+ padding: 14px 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-content-item + .tab-content-item {
|
|
|
+ margin-top: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .tab-content-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 10px;
|
|
|
+
|
|
|
+ .time-tag {
|
|
|
+ color: #2199F8;
|
|
|
+ background: rgba(33, 153, 248, 0.1);
|
|
|
+ font-size: 12px;
|
|
|
+ height: 21px;
|
|
|
+ line-height: 21px;
|
|
|
+ padding: 0 6px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .item-info {
|
|
|
+ color: rgba(60, 60, 60, 0.5);
|
|
|
+ line-height: 21px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .blue-text {
|
|
|
+ color: #2199f8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|