| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- <template>
- <!-- <div class="add-btn">{{ t('点击新建管理分区') }}</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="primaryActiveBgStyle"></div>
- <div v-for="(item, index) in floatTabLabels" :key="item.value" class="tab-item"
- @click="changePrimaryTab(index)" :class="{ 'tab-item-active': activeTab === index }">
- {{ item.title }}
- </div>
- </div>
- <div class="tab-content-group" v-show="height !== anchors[0]">
- <template v-if="isAgriRecordTab">
- <div class="float-sub-tabs">
- <div v-for="(item, index) in agriSubTabLabels" :key="item.value" class="sub-tab-item"
- :class="{ 'sub-tab-item-active': activeSubTab === index }" @click="changeSubTab(index)">
- {{ item.title }}
- </div>
- </div>
- <div class="tab-loading" v-if="loading">{{ t('agriFile.loading') }}</div>
- <div class="tab-empty" v-else-if="currentList.length === 0">{{ t('agriFile.noData') }}</div>
- <div v-else class="tab-content-item" v-for="item in displayList" :key="item.id">
- <template v-if="item?.recordText?.length">
- <div class="time-tag">{{ item.time }}</div>
- <div class="item-info">
- {{ item.recordText }}
- <span class="blue-text">{{ item.ratio }}{{ item.showRatio ? '%' : '' }}</span>
- </div>
- </template>
- </div>
- </template>
- <div v-else-if="isRemoteSensingTab" class="remote-sensing-chart">
- <div class="remote-sensing-chart__header">
- <span class="remote-sensing-chart__title">{{ t('agriFile.remoteSensingChartTitle') }}</span>
- <div class="remote-sensing-chart__legend">
- <div
- v-for="item in remoteSensingLegendItems"
- :key="item.key"
- class="remote-sensing-chart__legend-item"
- >
- <span
- class="remote-sensing-chart__legend-line"
- :style="{ background: item.color }"
- ></span>
- <span class="remote-sensing-chart__legend-text">{{ item.label }}</span>
- </div>
- </div>
- </div>
- <div class="tab-loading" v-if="loading">{{ t('agriFile.loading') }}</div>
- <remote-sensing-chart
- v-else-if="hasRemoteSensingData"
- :chart-data="remoteSensingChartData"
- />
- <div class="tab-empty" v-else>{{ t('agriFile.noData') }}</div>
- </div>
- </div>
- </div>
- </floating-panel>
- </template>
- <script setup>
- import { useI18n } from "@/i18n";
- import { RECORD_KEY_MAP } from "@/i18n/recordTextMap";
- import { FloatingPanel } from 'vant';
- import { computed, ref, watch } from 'vue';
- import remoteSensingChart from './remoteSensingChart.vue';
- const { t } = useI18n();
- const props = defineProps({
- farmRecordData: {
- type: Object,
- default: () => ({}),
- },
- activeTab: {
- type: Number,
- default: 0,
- },
- activeSubTab: {
- type: Number,
- default: 0,
- },
- loading: {
- type: Boolean,
- default: false,
- },
- remoteSensingData: {
- type: Array,
- default: () => [],
- },
- });
- const emit = defineEmits(["update:activeTab", "update:activeSubTab"]);
- const anchors = [
- 130,
- Math.round(0.4 * window.innerHeight),
- Math.round(0.8 * window.innerHeight),
- ];
- const height = ref(anchors[0]);
- const AGRI_SUB_TAB_KEYS = ["phenology", "farming", "abnormal"];
- const floatTabLabels = computed(() => [
- { title: t("agriFile.tabAgriRecord"), value: "agriRecord" },
- { title: t("agriFile.tabRemoteSensing"), value: "remoteSensing" },
- ]);
- const agriSubTabLabels = computed(() => [
- { title: t("agriFile.tabPhenology"), value: "phenology" },
- { title: t("agriFile.tabFarming"), value: "farming" },
- { title: t("agriFile.tabAbnormal"), value: "abnormal" },
- ]);
- const currentList = ref([]);
- const isAgriRecordTab = computed(() => floatTabLabels.value[props.activeTab]?.value === "agriRecord");
- const isRemoteSensingTab = computed(() => floatTabLabels.value[props.activeTab]?.value === "remoteSensing");
- const REMOTE_SENSING_LEGEND_CONFIG = [
- { key: "zone", labelKey: "agriFile.remoteSensingLegendZone", color: "#2199F8" },
- { key: "standard", labelKey: "agriFile.remoteSensingLegendStandard", color: "#9FD1FA" },
- ];
- const remoteSensingLegendItems = computed(() =>
- REMOTE_SENSING_LEGEND_CONFIG.map(({ key, labelKey, color }) => ({
- key,
- label: t(labelKey),
- color,
- }))
- );
- const remoteSensingChartData = computed(() => {
- const raw = props.remoteSensingData;
- if (!raw) return null;
- if (!Array.isArray(raw) && raw.zoneSeries?.length) {
- return raw;
- }
- if (!Array.isArray(raw) || raw.length === 0) {
- return null;
- }
- const highlightIndex = raw.findIndex((item) => item.highlight);
- return {
- timeLabels: raw.map((item) => item.time ?? item.label ?? t("agriFile.timeAxisLabel")),
- zoneSeries: raw.map((item) => Number(item.zone ?? item.zoneValue ?? item.value)),
- standardSeries: raw.map((item) => Number(item.standard ?? item.standardValue ?? item.stdValue)),
- highlightIndex: highlightIndex >= 0 ? highlightIndex : undefined,
- };
- });
- const hasRemoteSensingData = computed(() => {
- const data = remoteSensingChartData.value;
- return Boolean(data?.zoneSeries?.length && data?.standardSeries?.length);
- });
- const activeSubTabValue = computed(
- () => agriSubTabLabels.value[props.activeSubTab]?.value || AGRI_SUB_TAB_KEYS[0]
- );
- const displayList = computed(() =>
- currentList.value.map((item) => ({
- ...item,
- recordText: t(RECORD_KEY_MAP[item.record] || item.record),
- showRatio: activeSubTabValue.value !== "farming" && String(item.ratio ?? "").length > 0,
- }))
- );
- const syncCurrentList = () => {
- if (!isAgriRecordTab.value) {
- currentList.value = [];
- return;
- }
- currentList.value = props.farmRecordData?.[activeSubTabValue.value] || [];
- };
- const changePrimaryTab = (index) => {
- emit("update:activeTab", index);
- syncCurrentList();
- };
- const changeSubTab = (index) => {
- emit("update:activeSubTab", index);
- syncCurrentList();
- };
- const primaryActiveBgStyle = computed(() => ({
- transform: `translateX(${props.activeTab * 100}%)`,
- }));
- watch(
- () => props.farmRecordData,
- () => {
- syncCurrentList();
- },
- { deep: true, immediate: true }
- );
- watch(() => props.activeTab, () => {
- syncCurrentList();
- });
- watch(() => props.activeSubTab, () => {
- syncCurrentList();
- });
- </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);
- &.custom-panel {
- background: transparent;
- ::v-deep {
- .van-floating-panel__header {
- background: #fff;
- border-radius: 10px 10px 0 0;
- }
- .van-floating-panel__content {
- background: transparent;
- margin-top: -1px;
- }
- }
- }
- }
- .file-float-content {
- padding: 0 10px 10px;
- background: #fff;
- border-radius: 0 0 10px 10px;
- .float-tabs {
- position: relative;
- border-radius: 4px;
- padding: 3px;
- background: #E9E9E9;
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- align-items: center;
- overflow: hidden;
- .tab-active-bg {
- position: absolute;
- top: 3px;
- left: 3px;
- width: calc((100% - 6px) / 2);
- 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 {
- color: #0D0D0D;
- }
- }
- }
- .float-sub-tabs {
- display: flex;
- align-items: center;
- gap: 8px;
- margin-bottom: 10px;
- .sub-tab-item {
- height: 26px;
- line-height: 24px;
- padding: 0 10px;
- font-size: 12px;
- color: #767676;
- background: #e9e9e9;
- border-radius: 4px;
- border: 1px solid transparent;
- box-sizing: border-box;
- cursor: pointer;
- transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
- &.sub-tab-item-active {
- color: #2199f8;
- background: #fff;
- border-color: #2199f8;
- }
- }
- }
- .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;
- min-width: fit-content;
- box-sizing: border-box;
- }
- .item-info {
- color: rgba(60, 60, 60, 0.5);
- line-height: 21px;
- }
- .blue-text {
- color: #2199f8;
- }
- }
- .remote-sensing-chart {
- &__header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- gap: 12px;
- }
- &__legend {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- flex-wrap: wrap;
- gap: 12px;
- }
- &__legend-item {
- display: flex;
- align-items: center;
- gap: 6px;
- }
- &__legend-line {
- width: 14px;
- height: 4px;
- border-radius: 2px;
- }
- &__legend-text {
- font-size: 12px;
- color: #666666;
- line-height: 18px;
- }
- }
- }
- }
- </style>
|