|
|
@@ -1,83 +1,127 @@
|
|
|
<template>
|
|
|
- <div class="select-category">
|
|
|
- <div class="select-category__content">
|
|
|
+ <div class="select-crop">
|
|
|
+ <div class="select-crop__content" v-loading="loading">
|
|
|
<div class="page-header">
|
|
|
- <div class="page-title">完善种植品类</div>
|
|
|
- <div class="page-title">精准匹配农机服务<span class="page-title-tip">({{ multiple ? "多选" : "单选" }})</span></div>
|
|
|
+ <div class="page-title">请选择您的作物</div>
|
|
|
+ <div class="page-subtitle">已自动匹配您当地的主要作物</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="major-tabs">
|
|
|
+ <!-- 品类 -->
|
|
|
+ <div class="chip-grid crop-grid">
|
|
|
<div
|
|
|
- v-for="tab in majorTabs"
|
|
|
- :key="tab.key"
|
|
|
- class="major-tab"
|
|
|
- :class="{ active: activeTabKey === tab.key }"
|
|
|
- @click="activeTabKey = tab.key"
|
|
|
+ v-for="item in displayCrops"
|
|
|
+ :key="item.id"
|
|
|
+ class="chip-item"
|
|
|
+ :class="{ selected: isCropSelected(item) }"
|
|
|
+ @click="handleSelectCrop(item)"
|
|
|
>
|
|
|
- {{ tab.label }}
|
|
|
+ {{ item.name }}
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="search-bar">
|
|
|
- <el-icon class="search-icon"><Search /></el-icon>
|
|
|
- <input
|
|
|
- v-model="searchKeyword"
|
|
|
- class="search-input"
|
|
|
- type="text"
|
|
|
- placeholder="请输入品类名称"
|
|
|
- @keyup.enter="handleSearch"
|
|
|
+ <el-select
|
|
|
+ v-model="cropSearchValue"
|
|
|
+ class="search-select"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ placeholder="搜索其他作物"
|
|
|
+ popper-class="entry-dark-select-popper"
|
|
|
+ @change="handleCropSearchSelect"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon class="search-prefix-icon"><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ <el-option
|
|
|
+ v-for="item in cropSelectOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="String(item.id)"
|
|
|
+ :disabled="!isCropAllowed(item)"
|
|
|
/>
|
|
|
- <div class="search-btn" @click="handleSearch">搜索</div>
|
|
|
- </div>
|
|
|
+ </el-select>
|
|
|
|
|
|
- <div class="category-list" v-loading="loading">
|
|
|
- <div
|
|
|
- v-for="group in displayGroups"
|
|
|
- :key="group.name"
|
|
|
- class="category-card"
|
|
|
- >
|
|
|
- <div class="section-title">
|
|
|
- <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
|
|
|
- <span>{{ group.name }}</span>
|
|
|
+ <!-- 品种 -->
|
|
|
+ <template v-if="!multiple">
|
|
|
+ <div class="section-header">
|
|
|
+ <div class="section-title">{{ selectedCrop?.name || "" }}品种</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chip-grid variety-grid" v-loading="varietyLoading">
|
|
|
+ <div
|
|
|
+ v-for="item in displayVarieties"
|
|
|
+ :key="item.id"
|
|
|
+ class="chip-item variety-item"
|
|
|
+ :class="{ selected: String(selectedVarietyId) === String(item.id) }"
|
|
|
+ @click="handleSelectVariety(item)"
|
|
|
+ >
|
|
|
+ <span class="chip-item__text">{{ item.name }}</span>
|
|
|
+ <span v-if="String(selectedVarietyId) === String(item.id)" class="chip-item__check">
|
|
|
+ <el-icon><Check /></el-icon>
|
|
|
+ </span>
|
|
|
</div>
|
|
|
- <div class="tag-group add-tag-group">
|
|
|
- <div
|
|
|
- v-for="item in group.items"
|
|
|
- :key="item.id"
|
|
|
- class="tag-item"
|
|
|
- :class="{
|
|
|
- selected: item.selected,
|
|
|
- disabled: !isCropAllowed(item),
|
|
|
- }"
|
|
|
- @click="handleSelect(item)"
|
|
|
- >
|
|
|
- <span class="text">{{ item.name }}</span>
|
|
|
- </div>
|
|
|
+ <div
|
|
|
+ v-if="!varietyLoading && selectedCropId && !displayVarieties.length"
|
|
|
+ class="empty-tip"
|
|
|
+ >
|
|
|
+ 暂无品种
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div v-if="!loading && !displayGroups.length" class="empty-tip">暂无匹配品类</div>
|
|
|
- </div>
|
|
|
+
|
|
|
+ <el-select
|
|
|
+ v-model="varietySearchValue"
|
|
|
+ class="search-select"
|
|
|
+ filterable
|
|
|
+ clearable
|
|
|
+ placeholder="搜索其他品种"
|
|
|
+ :disabled="!selectedCropId"
|
|
|
+ popper-class="entry-dark-select-popper"
|
|
|
+ @change="handleVarietySearchSelect"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon class="search-prefix-icon"><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ <el-option
|
|
|
+ v-for="item in varietySelectOptions"
|
|
|
+ :key="item.id"
|
|
|
+ :label="item.name"
|
|
|
+ :value="String(item.id)"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </template>
|
|
|
</div>
|
|
|
|
|
|
- <div class="custom-bottom-fixed-btns">
|
|
|
- <div class="bottom-btn secondary-btn" @click="emit('prev')">上一步</div>
|
|
|
- <div class="bottom-btn primary-btn" @click="handleNext">{{ nextBtnText }}</div>
|
|
|
+ <div class="select-crop__footer">
|
|
|
+ <div class="action-btn secondary" @click="emit('prev')">上一步</div>
|
|
|
+ <div class="action-btn primary" @click="handleNext">
|
|
|
+ {{ multiple ? "下一步" : "确认品种" }}
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { computed, onMounted, ref, watch } from "vue";
|
|
|
+import { computed, onMounted, ref } from "vue";
|
|
|
import { ElMessage } from "element-plus";
|
|
|
-import { Search } from "@element-plus/icons-vue";
|
|
|
+import { Check, Search } from "@element-plus/icons-vue";
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// Props / Emits
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+const props = defineProps({
|
|
|
+ /** 农服多选品类;农户为单选品类+品种 */
|
|
|
+ multiple: { type: Boolean, default: false },
|
|
|
+});
|
|
|
+const emit = defineEmits(["prev", "next"]);
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 常量
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
const SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
|
|
|
const VARIETY_LIST_KEY = "ENTRY_SELECTED_VARIETY_LIST";
|
|
|
const EQUIPMENT_KEY = "ENTRY_SELECTED_EQUIPMENT";
|
|
|
const TASK_KEY = "ENTRY_SELECTED_FARM_TASKS";
|
|
|
const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
|
|
|
const META_KEYS = new Set(["city", "county", "county_code", "province"]);
|
|
|
-/** 与下游步骤约定一致的 majorKey */
|
|
|
const FIRST_CROP_KEY_MAP = {
|
|
|
果树: "fruit",
|
|
|
大田: "field",
|
|
|
@@ -86,107 +130,75 @@ const TAB_ORDER = ["果树", "大田"];
|
|
|
const DEFAULT_COUNTY_CODE = "440113";
|
|
|
const MAP_KEY = "CZLBZ-LJICQ-R4A5J-BN62X-YXCRJ-GNBUT";
|
|
|
const DEFAULT_POINT = "POINT(113.6142086995688 23.585836479509055)";
|
|
|
+/** 顶部默认展示的品类数量 */
|
|
|
+const DEFAULT_CROP_VISIBLE = 6;
|
|
|
|
|
|
-/** 接口 is_use:'1' 开放,'0' 未开放;会话缓存无该字段时按当前列表 crop_id 解析 */
|
|
|
-const isCropAllowed = (item) => {
|
|
|
- if (item?.is_use != null && item.is_use !== "") {
|
|
|
- return String(item.is_use) === "1";
|
|
|
- }
|
|
|
- const id = item?.id;
|
|
|
- if (id == null) return false;
|
|
|
- for (const tab of majorTabs.value) {
|
|
|
- for (const group of tab.groups || []) {
|
|
|
- const found = (group.items || []).find((i) => String(i.id) === String(id));
|
|
|
- if (found) return String(found.is_use ?? "0") === "1";
|
|
|
- }
|
|
|
- }
|
|
|
- return false;
|
|
|
-};
|
|
|
-
|
|
|
-const props = defineProps({
|
|
|
- multiple: { type: Boolean, default: false },
|
|
|
-});
|
|
|
-
|
|
|
-const emit = defineEmits(["prev", "next"]);
|
|
|
-
|
|
|
-const majorTabs = ref([]);
|
|
|
-const activeTabKey = ref("");
|
|
|
-const searchKeyword = ref("");
|
|
|
-const appliedKeyword = ref("");
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 页面状态
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
const loading = ref(false);
|
|
|
-
|
|
|
-const activeTab = computed(() =>
|
|
|
- majorTabs.value.find((tab) => tab.key === activeTabKey.value) || majorTabs.value[0]
|
|
|
+const varietyLoading = ref(false);
|
|
|
+/** 全量品类(扁平) */
|
|
|
+const allCrops = ref([]);
|
|
|
+/** 顶部展示的品类(选中项会置顶) */
|
|
|
+const displayCrops = ref([]);
|
|
|
+const selectedCropId = ref("");
|
|
|
+/** 农服多选 */
|
|
|
+const selectedCropIds = ref(new Set());
|
|
|
+const cropSearchValue = ref("");
|
|
|
+
|
|
|
+const varietyList = ref([]);
|
|
|
+const displayVarieties = ref([]);
|
|
|
+const selectedVarietyId = ref("");
|
|
|
+const varietySearchValue = ref("");
|
|
|
+
|
|
|
+let varietyReqId = 0;
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 计算属性
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+const selectedCrop = computed(() =>
|
|
|
+ allCrops.value.find((item) => String(item.id) === String(selectedCropId.value)) ||
|
|
|
+ displayCrops.value.find((item) => String(item.id) === String(selectedCropId.value)) ||
|
|
|
+ null
|
|
|
);
|
|
|
|
|
|
-const displayGroups = computed(() => {
|
|
|
- const groups = activeTab.value?.groups || [];
|
|
|
- const keyword = (appliedKeyword.value || "").trim();
|
|
|
- if (!keyword) return groups;
|
|
|
- return groups
|
|
|
- .map((group) => ({
|
|
|
- ...group,
|
|
|
- items: group.items.filter((item) => item.name.includes(keyword)),
|
|
|
- }))
|
|
|
- .filter((group) => group.items.length);
|
|
|
-});
|
|
|
+const cropSelectOptions = computed(() => allCrops.value);
|
|
|
|
|
|
-const selectedItems = computed(() => {
|
|
|
- const list = [];
|
|
|
- majorTabs.value.forEach((tab) => {
|
|
|
- tab.groups.forEach((group) => {
|
|
|
- group.items.forEach((item) => {
|
|
|
- if (item.selected) {
|
|
|
- list.push({
|
|
|
- id: item.id,
|
|
|
- name: item.name,
|
|
|
- code: item.code || "",
|
|
|
- groupName: group.name,
|
|
|
- majorKey: tab.key,
|
|
|
- majorLabel: tab.label,
|
|
|
- firstCrop: tab.firstCrop,
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
- return list;
|
|
|
-});
|
|
|
-
|
|
|
-const nextBtnText = computed(() => {
|
|
|
- if (!props.multiple) return "下一步 (2/4)";
|
|
|
- const hasField = selectedItems.value.some(
|
|
|
- (item) => item.firstCrop === "大田" || item.majorKey === "field"
|
|
|
- );
|
|
|
- const hasFruit = selectedItems.value.some(
|
|
|
- (item) => item.firstCrop === "果树" || item.majorKey === "fruit"
|
|
|
- );
|
|
|
- const total = hasField && hasFruit ? 6 : 4;
|
|
|
- return `下一步 (2/${total})`;
|
|
|
-});
|
|
|
+const varietySelectOptions = computed(() => varietyList.value);
|
|
|
|
|
|
-const mapCropGroups = (list) => {
|
|
|
- if (!Array.isArray(list)) return [];
|
|
|
- return list
|
|
|
- .map((group) => ({
|
|
|
- name: group.crop_type || "",
|
|
|
- hasAdvantage: !!group.has_advantage,
|
|
|
- items: (group.items || []).map((crop) => ({
|
|
|
- id: crop.crop_id,
|
|
|
- name: crop.crop_name,
|
|
|
- code: crop.crop_code || "",
|
|
|
- is_use: crop.is_use ?? "0",
|
|
|
- isAdvantage: !!crop.is_advantage,
|
|
|
- advantageRank: crop.advantage_rank,
|
|
|
- selected: false,
|
|
|
- })),
|
|
|
- }))
|
|
|
- .filter((group) => group.name && group.items.length);
|
|
|
+const isCropSelected = (item) => {
|
|
|
+ if (!item) return false;
|
|
|
+ if (props.multiple) return selectedCropIds.value.has(String(item.id));
|
|
|
+ return String(selectedCropId.value) === String(item.id);
|
|
|
};
|
|
|
|
|
|
-const resolveTabKey = (firstCrop) => FIRST_CROP_KEY_MAP[firstCrop] || firstCrop;
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 工具函数
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+const isCropAllowed = (item) => String(item?.is_use ?? "0") === "1";
|
|
|
+
|
|
|
+function readJson(key) {
|
|
|
+ try {
|
|
|
+ const raw = sessionStorage.getItem(key);
|
|
|
+ return raw ? JSON.parse(raw) : null;
|
|
|
+ } catch {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function writeJson(key, value) {
|
|
|
+ sessionStorage.setItem(key, JSON.stringify(value));
|
|
|
+}
|
|
|
+
|
|
|
+function clearDownstreamDrafts() {
|
|
|
+ sessionStorage.removeItem(VARIETY_LIST_KEY);
|
|
|
+ sessionStorage.removeItem(EQUIPMENT_KEY);
|
|
|
+ sessionStorage.removeItem(TASK_KEY);
|
|
|
+ sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
|
|
|
+}
|
|
|
|
|
|
-const buildTabsFromData = (data) => {
|
|
|
+function flattenCropsFromData(data) {
|
|
|
if (!data || typeof data !== "object") return [];
|
|
|
const cropKeys = Object.keys(data).filter(
|
|
|
(key) => !META_KEYS.has(key) && Array.isArray(data[key])
|
|
|
@@ -199,218 +211,374 @@ const buildTabsFromData = (data) => {
|
|
|
if (bi === -1) return -1;
|
|
|
return ai - bi;
|
|
|
});
|
|
|
- return cropKeys.map((firstCrop) => ({
|
|
|
- key: resolveTabKey(firstCrop),
|
|
|
- label: `${firstCrop}类`,
|
|
|
- firstCrop,
|
|
|
- groups: mapCropGroups(data[firstCrop]),
|
|
|
- }));
|
|
|
-};
|
|
|
-
|
|
|
-const cropTypeRank = (item) => {
|
|
|
- if (item?.firstCrop === "大田" || item?.majorKey === "field") return 0;
|
|
|
- if (item?.firstCrop === "果树" || item?.majorKey === "fruit") return 1;
|
|
|
- return 2;
|
|
|
-};
|
|
|
-
|
|
|
-const sortFieldFirst = (list) =>
|
|
|
- [...(list || [])].sort((a, b) => cropTypeRank(a) - cropTypeRank(b));
|
|
|
-
|
|
|
-const getCategorySignature = (list) =>
|
|
|
- [...(list || [])].map((item) => String(item.id)).sort().join(",");
|
|
|
|
|
|
-const clearDownstreamDrafts = () => {
|
|
|
- sessionStorage.removeItem(VARIETY_LIST_KEY);
|
|
|
- sessionStorage.removeItem(EQUIPMENT_KEY);
|
|
|
- sessionStorage.removeItem(TASK_KEY);
|
|
|
- sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
|
|
|
-};
|
|
|
-
|
|
|
-const getCachedSelectedItems = () => {
|
|
|
- try {
|
|
|
- const raw = sessionStorage.getItem(SESSION_KEY);
|
|
|
- if (!raw) return [];
|
|
|
- const list = JSON.parse(raw);
|
|
|
- if (!Array.isArray(list)) return [];
|
|
|
- // 历史缓存可能含未开放品类,进页时过滤掉
|
|
|
- const allowed = list.filter((item) => isCropAllowed(item));
|
|
|
- return props.multiple ? allowed : allowed.slice(0, 1);
|
|
|
- } catch {
|
|
|
- return [];
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-const getSelectedIds = () => getCachedSelectedItems().map((item) => String(item.id));
|
|
|
-
|
|
|
-const clearAllSelection = () => {
|
|
|
- majorTabs.value.forEach((tab) => {
|
|
|
- tab.groups.forEach((group) => {
|
|
|
- group.items.forEach((item) => {
|
|
|
- item.selected = false;
|
|
|
+ const list = [];
|
|
|
+ const seen = new Set();
|
|
|
+ cropKeys.forEach((firstCrop) => {
|
|
|
+ const majorKey = FIRST_CROP_KEY_MAP[firstCrop] || firstCrop;
|
|
|
+ (data[firstCrop] || []).forEach((group) => {
|
|
|
+ (group.items || []).forEach((crop) => {
|
|
|
+ const id = crop.crop_id;
|
|
|
+ if (id == null || seen.has(String(id))) return;
|
|
|
+ seen.add(String(id));
|
|
|
+ list.push({
|
|
|
+ id,
|
|
|
+ name: crop.crop_name || "",
|
|
|
+ code: crop.crop_code || "",
|
|
|
+ is_use: crop.is_use ?? "0",
|
|
|
+ isAdvantage: !!crop.is_advantage,
|
|
|
+ advantageRank: crop.advantage_rank,
|
|
|
+ groupName: group.crop_type || "",
|
|
|
+ firstCrop,
|
|
|
+ majorKey,
|
|
|
+ majorLabel: `${firstCrop}类`,
|
|
|
+ });
|
|
|
});
|
|
|
});
|
|
|
});
|
|
|
-};
|
|
|
+ return list;
|
|
|
+}
|
|
|
|
|
|
-const applyCachedSelection = () => {
|
|
|
- const selectedIds = new Set(getSelectedIds());
|
|
|
- if (!selectedIds.size) return;
|
|
|
- majorTabs.value.forEach((tab) => {
|
|
|
- tab.groups.forEach((group) => {
|
|
|
- group.items.forEach((item) => {
|
|
|
- item.selected = selectedIds.has(String(item.id));
|
|
|
- });
|
|
|
- });
|
|
|
- });
|
|
|
-};
|
|
|
+function buildInitialDisplayCrops(list) {
|
|
|
+ const allowed = list.filter(isCropAllowed);
|
|
|
+ const advantage = allowed
|
|
|
+ .filter((item) => item.isAdvantage)
|
|
|
+ .sort((a, b) => Number(a.advantageRank || 99) - Number(b.advantageRank || 99));
|
|
|
+ const base = advantage.length ? advantage : allowed;
|
|
|
+ return base.slice(0, DEFAULT_CROP_VISIBLE);
|
|
|
+}
|
|
|
|
|
|
-const persistSelection = () => {
|
|
|
- const list = props.multiple
|
|
|
- ? sortFieldFirst(selectedItems.value)
|
|
|
- : selectedItems.value.slice(0, 1);
|
|
|
- if (list.length) {
|
|
|
- sessionStorage.setItem(SESSION_KEY, JSON.stringify(list));
|
|
|
- } else {
|
|
|
- sessionStorage.removeItem(SESSION_KEY);
|
|
|
+/** 将某品类置顶到展示列表并选中 */
|
|
|
+function promoteCropToFront(crop) {
|
|
|
+ if (!crop) return;
|
|
|
+ const id = String(crop.id);
|
|
|
+ const rest = displayCrops.value.filter((item) => String(item.id) !== id);
|
|
|
+ displayCrops.value = [crop, ...rest];
|
|
|
+ selectedCropId.value = crop.id;
|
|
|
+ if (props.multiple) {
|
|
|
+ selectedCropIds.value = new Set([id]);
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
-const readResidentLocation = () => {
|
|
|
- try {
|
|
|
- const raw = sessionStorage.getItem(LOCATION_KEY);
|
|
|
- return raw ? JSON.parse(raw) : null;
|
|
|
- } catch {
|
|
|
- return null;
|
|
|
- }
|
|
|
-};
|
|
|
+function getResidentPoint() {
|
|
|
+ const saved = readJson(LOCATION_KEY);
|
|
|
+ return (
|
|
|
+ saved?.point ||
|
|
|
+ localStorage.getItem("MINI_USER_LOCATION_POINT") ||
|
|
|
+ DEFAULT_POINT
|
|
|
+ );
|
|
|
+}
|
|
|
|
|
|
-const parsePointCoordinate = (point) => {
|
|
|
+async function fetchAdcodeByPoint(point) {
|
|
|
const match = String(point || "").match(/POINT\s*\(([\d.\-]+)\s+([\d.\-]+)\)/i);
|
|
|
- if (!match) return null;
|
|
|
- return [Number(match[1]), Number(match[2])];
|
|
|
-};
|
|
|
-
|
|
|
-const fetchAdcodeByPoint = async (point) => {
|
|
|
- const coordinate = parsePointCoordinate(point);
|
|
|
- if (!coordinate) return "";
|
|
|
+ if (!match) return "";
|
|
|
try {
|
|
|
const { result } = await VE_API.old_mini_map.location({
|
|
|
key: MAP_KEY,
|
|
|
- location: `${coordinate[1]},${coordinate[0]}`,
|
|
|
+ location: `${match[2]},${match[1]}`,
|
|
|
});
|
|
|
const adcode = result?.ad_info?.adcode;
|
|
|
return adcode != null && adcode !== "" ? String(adcode) : "";
|
|
|
} catch {
|
|
|
return "";
|
|
|
}
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
-const resolveCountyCode = async () => {
|
|
|
- const saved = readResidentLocation();
|
|
|
+async function resolveCountyCode() {
|
|
|
+ const saved = readJson(LOCATION_KEY);
|
|
|
if (saved?.adcode) return String(saved.adcode);
|
|
|
-
|
|
|
- const point =
|
|
|
- saved?.point ||
|
|
|
- localStorage.getItem("MINI_USER_LOCATION_POINT") ||
|
|
|
- DEFAULT_POINT;
|
|
|
+ const point = saved?.point || getResidentPoint();
|
|
|
const adcode = await fetchAdcodeByPoint(point);
|
|
|
if (adcode && saved?.point) {
|
|
|
try {
|
|
|
- sessionStorage.setItem(
|
|
|
- LOCATION_KEY,
|
|
|
- JSON.stringify({ ...saved, adcode })
|
|
|
- );
|
|
|
+ writeJson(LOCATION_KEY, { ...saved, adcode });
|
|
|
} catch {
|
|
|
// ignore
|
|
|
}
|
|
|
}
|
|
|
return adcode || DEFAULT_COUNTY_CODE;
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
-const fetchCrops = async () => {
|
|
|
- loading.value = true;
|
|
|
+function mapVarieties(list) {
|
|
|
+ return (list || [])
|
|
|
+ .map((item) => {
|
|
|
+ if (typeof item === "string") return { id: item, name: item };
|
|
|
+ const name = item?.name || "";
|
|
|
+ return { id: item?.id ?? name, name };
|
|
|
+ })
|
|
|
+ .filter((item) => item.name);
|
|
|
+}
|
|
|
+
|
|
|
+function promoteVarietyToFront(variety) {
|
|
|
+ if (!variety) return;
|
|
|
+ const id = String(variety.id);
|
|
|
+ const rest = displayVarieties.value.filter((item) => String(item.id) !== id);
|
|
|
+ displayVarieties.value = [variety, ...rest];
|
|
|
+ selectedVarietyId.value = variety.id;
|
|
|
+}
|
|
|
+
|
|
|
+function buildCategoryPayload(crop) {
|
|
|
+ return {
|
|
|
+ id: crop.id,
|
|
|
+ name: crop.name,
|
|
|
+ code: crop.code || "",
|
|
|
+ groupName: crop.groupName || "",
|
|
|
+ majorKey: crop.majorKey,
|
|
|
+ majorLabel: crop.majorLabel,
|
|
|
+ firstCrop: crop.firstCrop,
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function buildVarietyPayload(crop, variety) {
|
|
|
+ return {
|
|
|
+ uid: `sel-${crop.id}-${variety.id}`,
|
|
|
+ id: variety.id,
|
|
|
+ name: variety.name,
|
|
|
+ categoryId: crop.id,
|
|
|
+ categoryName: crop.name,
|
|
|
+ cropCode: crop.code || "",
|
|
|
+ firstCrop: crop.firstCrop || "",
|
|
|
+ phenologyId: "",
|
|
|
+ phenophaseCode: "",
|
|
|
+ startTime: "",
|
|
|
+ area: "",
|
|
|
+ location: getResidentPoint(),
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+function saveCropDetailFromApi(data) {
|
|
|
+ if (data?.crop_id == null || data?.crop_id === "") return;
|
|
|
+ const cache = readJson("ENTRY_CROP_DETAIL") || {};
|
|
|
+ cache[String(data.crop_id)] = {
|
|
|
+ crop_id: data.crop_id,
|
|
|
+ crop_code: data.crop_code || "",
|
|
|
+ crop_group: data.crop_group || "",
|
|
|
+ crop_name: data.crop_name || "",
|
|
|
+ crop_type: data.crop_type || "",
|
|
|
+ };
|
|
|
+ writeJson("ENTRY_CROP_DETAIL", cache);
|
|
|
+}
|
|
|
+
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+// 业务逻辑
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+async function fetchVarietiesByCrop(cropId, { selectFirst = true } = {}) {
|
|
|
+ if (cropId == null || cropId === "") {
|
|
|
+ varietyList.value = [];
|
|
|
+ displayVarieties.value = [];
|
|
|
+ selectedVarietyId.value = "";
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const reqId = ++varietyReqId;
|
|
|
+ varietyLoading.value = true;
|
|
|
try {
|
|
|
- const countyCode = await resolveCountyCode();
|
|
|
- const res = await VE_API.entry.getCrops({
|
|
|
- county_code: countyCode,
|
|
|
- });
|
|
|
- if (res.code === 200) {
|
|
|
- const data = res.data || {};
|
|
|
- try {
|
|
|
- sessionStorage.setItem(
|
|
|
- "ENTRY_CROP_META",
|
|
|
- JSON.stringify({
|
|
|
- city: data.city || "",
|
|
|
- county: data.county || "",
|
|
|
- province: data.province || "",
|
|
|
- county_code: data.county_code || countyCode,
|
|
|
- })
|
|
|
- );
|
|
|
- } catch {
|
|
|
- // ignore
|
|
|
+ const res = await VE_API.entry.getVarietiesByCrop({ crop_id: cropId });
|
|
|
+ if (reqId !== varietyReqId) return;
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ const data = res.data;
|
|
|
+ const crop = selectedCrop.value;
|
|
|
+ if (crop && data.crop_code) {
|
|
|
+ crop.code = data.crop_code;
|
|
|
+ if (data.crop_group) crop.firstCrop = crop.firstCrop || data.crop_group;
|
|
|
}
|
|
|
- const tabs = buildTabsFromData(data);
|
|
|
- majorTabs.value = tabs;
|
|
|
- if (!tabs.some((tab) => tab.key === activeTabKey.value)) {
|
|
|
- activeTabKey.value = tabs[0]?.key || "";
|
|
|
+ if (crop && data.crop_code) {
|
|
|
+ saveCropDetailFromApi({
|
|
|
+ crop_id: cropId,
|
|
|
+ crop_code: data.crop_code,
|
|
|
+ crop_group: data.crop_group || crop.firstCrop || "",
|
|
|
+ crop_name: data.crop_name || crop.name,
|
|
|
+ crop_type: data.crop_type || crop.groupName || "",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ const list = mapVarieties(data.varieties);
|
|
|
+ varietyList.value = list;
|
|
|
+ displayVarieties.value = [...list];
|
|
|
+
|
|
|
+ const cached = readJson(VARIETY_LIST_KEY);
|
|
|
+ const cachedItem = Array.isArray(cached)
|
|
|
+ ? cached.find((row) => String(row.categoryId) === String(cropId))
|
|
|
+ : null;
|
|
|
+ if (cachedItem?.id && list.some((v) => String(v.id) === String(cachedItem.id))) {
|
|
|
+ const found = list.find((v) => String(v.id) === String(cachedItem.id));
|
|
|
+ promoteVarietyToFront(found);
|
|
|
+ } else if (selectFirst && list.length) {
|
|
|
+ promoteVarietyToFront(list[0]);
|
|
|
+ } else {
|
|
|
+ selectedVarietyId.value = "";
|
|
|
}
|
|
|
- applyCachedSelection();
|
|
|
} else {
|
|
|
- majorTabs.value = [];
|
|
|
- activeTabKey.value = "";
|
|
|
+ varietyList.value = [];
|
|
|
+ displayVarieties.value = [];
|
|
|
+ selectedVarietyId.value = "";
|
|
|
+ ElMessage.error(res.msg || "获取品种列表失败");
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ if (reqId !== varietyReqId) return;
|
|
|
+ varietyList.value = [];
|
|
|
+ displayVarieties.value = [];
|
|
|
+ selectedVarietyId.value = "";
|
|
|
+ ElMessage.error("获取品种列表失败,请稍后再试");
|
|
|
+ } finally {
|
|
|
+ if (reqId === varietyReqId) varietyLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function fetchCrops() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const countyCode = await resolveCountyCode();
|
|
|
+ const res = await VE_API.entry.getCrops({ county_code: countyCode });
|
|
|
+ if (res.code !== 200) {
|
|
|
+ allCrops.value = [];
|
|
|
+ displayCrops.value = [];
|
|
|
ElMessage.error(res.msg || "获取种植品类失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const data = res.data || {};
|
|
|
+ try {
|
|
|
+ writeJson("ENTRY_CROP_META", {
|
|
|
+ city: data.city || "",
|
|
|
+ county: data.county || "",
|
|
|
+ province: data.province || "",
|
|
|
+ county_code: data.county_code || countyCode,
|
|
|
+ });
|
|
|
+ } catch {
|
|
|
+ // ignore
|
|
|
+ }
|
|
|
+
|
|
|
+ const list = flattenCropsFromData(data);
|
|
|
+ allCrops.value = list;
|
|
|
+ let initial = buildInitialDisplayCrops(list);
|
|
|
+
|
|
|
+ const cached = readJson(SESSION_KEY);
|
|
|
+ const cachedList = Array.isArray(cached) ? cached : [];
|
|
|
+ if (props.multiple) {
|
|
|
+ const ids = new Set(
|
|
|
+ cachedList.filter((item) => isCropAllowed(item)).map((item) => String(item.id))
|
|
|
+ );
|
|
|
+ selectedCropIds.value = ids;
|
|
|
+ // 缓存选中项置顶展示
|
|
|
+ const promoted = cachedList
|
|
|
+ .map((c) => list.find((item) => String(item.id) === String(c.id)))
|
|
|
+ .filter(Boolean);
|
|
|
+ const rest = initial.filter((item) => !ids.has(String(item.id)));
|
|
|
+ displayCrops.value = [...promoted, ...rest].slice(0, Math.max(DEFAULT_CROP_VISIBLE, promoted.length));
|
|
|
+ selectedCropId.value = displayCrops.value[0]?.id || "";
|
|
|
+ } else {
|
|
|
+ const cachedOne = cachedList[0];
|
|
|
+ const found = cachedOne
|
|
|
+ ? list.find((item) => String(item.id) === String(cachedOne.id) && isCropAllowed(item))
|
|
|
+ : null;
|
|
|
+ if (found) {
|
|
|
+ const rest = initial.filter((item) => String(item.id) !== String(found.id));
|
|
|
+ displayCrops.value = [found, ...rest].slice(0, DEFAULT_CROP_VISIBLE);
|
|
|
+ selectedCropId.value = found.id;
|
|
|
+ } else {
|
|
|
+ displayCrops.value = initial;
|
|
|
+ const first = initial.find(isCropAllowed) || initial[0];
|
|
|
+ if (first) selectedCropId.value = first.id;
|
|
|
+ }
|
|
|
+ if (selectedCropId.value) {
|
|
|
+ await fetchVarietiesByCrop(selectedCropId.value, { selectFirst: true });
|
|
|
+ }
|
|
|
}
|
|
|
} catch {
|
|
|
- majorTabs.value = [];
|
|
|
- activeTabKey.value = "";
|
|
|
+ allCrops.value = [];
|
|
|
+ displayCrops.value = [];
|
|
|
ElMessage.error("获取种植品类失败,请稍后再试");
|
|
|
} finally {
|
|
|
loading.value = false;
|
|
|
}
|
|
|
-};
|
|
|
-
|
|
|
-const handleSearch = () => {
|
|
|
- appliedKeyword.value = searchKeyword.value.trim();
|
|
|
-};
|
|
|
+}
|
|
|
|
|
|
-const handleSelect = (item) => {
|
|
|
+const handleSelectCrop = async (item) => {
|
|
|
if (!isCropAllowed(item)) {
|
|
|
ElMessage.warning("该品种暂未开放");
|
|
|
return;
|
|
|
}
|
|
|
if (props.multiple) {
|
|
|
- item.selected = !item.selected;
|
|
|
- persistSelection();
|
|
|
+ const id = String(item.id);
|
|
|
+ const next = new Set(selectedCropIds.value);
|
|
|
+ if (next.has(id)) next.delete(id);
|
|
|
+ else next.add(id);
|
|
|
+ selectedCropIds.value = next;
|
|
|
+ selectedCropId.value = item.id;
|
|
|
return;
|
|
|
}
|
|
|
- const wasSelected = item.selected;
|
|
|
- clearAllSelection();
|
|
|
- item.selected = !wasSelected;
|
|
|
- persistSelection();
|
|
|
+ if (String(selectedCropId.value) === String(item.id)) return;
|
|
|
+ selectedCropId.value = item.id;
|
|
|
+ varietySearchValue.value = "";
|
|
|
+ await fetchVarietiesByCrop(item.id, { selectFirst: true });
|
|
|
};
|
|
|
|
|
|
-const handleNext = () => {
|
|
|
- const list = (props.multiple
|
|
|
- ? sortFieldFirst(selectedItems.value)
|
|
|
- : selectedItems.value.slice(0, 1)
|
|
|
- ).filter((item) => isCropAllowed(item));
|
|
|
- if (!list.length) {
|
|
|
- ElMessage.warning(props.multiple ? "请选择种植品类" : "请选择一个种植品类");
|
|
|
+const handleCropSearchSelect = async (value) => {
|
|
|
+ if (value == null || value === "") return;
|
|
|
+ const crop = allCrops.value.find((item) => String(item.id) === String(value));
|
|
|
+ cropSearchValue.value = "";
|
|
|
+ if (!crop) return;
|
|
|
+ if (!isCropAllowed(crop)) {
|
|
|
+ ElMessage.warning("该品种暂未开放");
|
|
|
return;
|
|
|
}
|
|
|
- const prevSignature = getCategorySignature(getCachedSelectedItems());
|
|
|
- const nextSignature = getCategorySignature(list);
|
|
|
- if (prevSignature !== nextSignature) {
|
|
|
- clearDownstreamDrafts();
|
|
|
+ promoteCropToFront(crop);
|
|
|
+ varietySearchValue.value = "";
|
|
|
+ if (!props.multiple) {
|
|
|
+ await fetchVarietiesByCrop(crop.id, { selectFirst: true });
|
|
|
}
|
|
|
- sessionStorage.setItem(SESSION_KEY, JSON.stringify(list));
|
|
|
- emit("next", list);
|
|
|
};
|
|
|
|
|
|
-watch(activeTabKey, () => {
|
|
|
- appliedKeyword.value = "";
|
|
|
- searchKeyword.value = "";
|
|
|
-});
|
|
|
+const handleSelectVariety = (item) => {
|
|
|
+ if (!item) return;
|
|
|
+ selectedVarietyId.value = item.id;
|
|
|
+};
|
|
|
+
|
|
|
+const handleVarietySearchSelect = (value) => {
|
|
|
+ if (value == null || value === "") return;
|
|
|
+ const variety = varietyList.value.find((item) => String(item.id) === String(value));
|
|
|
+ varietySearchValue.value = "";
|
|
|
+ if (!variety) return;
|
|
|
+ promoteVarietyToFront(variety);
|
|
|
+};
|
|
|
+
|
|
|
+const handleNext = () => {
|
|
|
+ if (props.multiple) {
|
|
|
+ const list = allCrops.value
|
|
|
+ .filter((item) => selectedCropIds.value.has(String(item.id)) && isCropAllowed(item))
|
|
|
+ .map(buildCategoryPayload);
|
|
|
+ if (!list.length) {
|
|
|
+ ElMessage.warning("请选择种植品类");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const prev = readJson(SESSION_KEY);
|
|
|
+ const prevSig = (Array.isArray(prev) ? prev : []).map((i) => String(i.id)).sort().join(",");
|
|
|
+ const nextSig = list.map((i) => String(i.id)).sort().join(",");
|
|
|
+ if (prevSig !== nextSig) clearDownstreamDrafts();
|
|
|
+ writeJson(SESSION_KEY, list);
|
|
|
+ emit("next", list);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const crop = selectedCrop.value;
|
|
|
+ if (!crop || !isCropAllowed(crop)) {
|
|
|
+ ElMessage.warning("请选择一个种植品类");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const variety =
|
|
|
+ displayVarieties.value.find((item) => String(item.id) === String(selectedVarietyId.value)) ||
|
|
|
+ varietyList.value.find((item) => String(item.id) === String(selectedVarietyId.value));
|
|
|
+ if (!variety) {
|
|
|
+ ElMessage.warning("请选择品种");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const prev = readJson(SESSION_KEY);
|
|
|
+ const prevId = Array.isArray(prev) && prev[0] ? String(prev[0].id) : "";
|
|
|
+ if (prevId !== String(crop.id)) clearDownstreamDrafts();
|
|
|
+
|
|
|
+ writeJson(SESSION_KEY, [buildCategoryPayload(crop)]);
|
|
|
+ writeJson(VARIETY_LIST_KEY, [buildVarietyPayload(crop, variety)]);
|
|
|
+ emit("next", { crop, variety });
|
|
|
+};
|
|
|
|
|
|
onMounted(() => {
|
|
|
fetchCrops();
|
|
|
@@ -418,229 +586,220 @@ onMounted(() => {
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
-.select-category {
|
|
|
- height: calc(100% - 80px);
|
|
|
+.select-crop {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
- overflow: auto;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #0f0f0f;
|
|
|
+ color: #fff;
|
|
|
|
|
|
&__content {
|
|
|
flex: 1;
|
|
|
+ min-height: 0;
|
|
|
overflow-y: auto;
|
|
|
- padding: 8px 10px 20px;
|
|
|
+ padding: 28px 16px 20px;
|
|
|
+ }
|
|
|
+
|
|
|
+ &__footer {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ padding: 16px 16px calc(16px + env(safe-area-inset-bottom));
|
|
|
}
|
|
|
|
|
|
.page-header {
|
|
|
- padding: 8px 0 20px;
|
|
|
+ margin-bottom: 18px;
|
|
|
|
|
|
.page-title {
|
|
|
- font-size: 26px;
|
|
|
- color: #005599;
|
|
|
- font-family: "PangMenZhengDao";
|
|
|
- line-height: 30px;
|
|
|
- .page-title-tip {
|
|
|
- font-size: 20px;
|
|
|
- }
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 34px;
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
|
|
|
.page-subtitle {
|
|
|
- font-size: 14px;
|
|
|
- color: rgba(46, 46, 46, 0.4);
|
|
|
+ margin-top: 6px;
|
|
|
+ font-size: 13px;
|
|
|
line-height: 20px;
|
|
|
+ color: rgba(255, 255, 255, 0.45);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .major-tabs {
|
|
|
- display: flex;
|
|
|
+ .section-header {
|
|
|
+ margin: 22px 0 12px;
|
|
|
+
|
|
|
+ .section-title {
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ line-height: 26px;
|
|
|
+ color: #fff;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .chip-grid {
|
|
|
+ display: grid;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
- .major-tab {
|
|
|
- flex: 1;
|
|
|
- height: 36px;
|
|
|
- line-height: 36px;
|
|
|
- text-align: center;
|
|
|
- border-radius: 8px;
|
|
|
- font-size: 15px;
|
|
|
- color: rgba(0, 0, 0, 0.55);
|
|
|
- background: #fff;
|
|
|
- cursor: pointer;
|
|
|
-
|
|
|
- &.active {
|
|
|
- color: #fff;
|
|
|
- background: #2199f8;
|
|
|
- font-weight: 500;
|
|
|
- }
|
|
|
+ &.crop-grid {
|
|
|
+ grid-template-columns: repeat(3, 1fr);
|
|
|
+ }
|
|
|
+
|
|
|
+ &.variety-grid {
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
+ min-height: 44px;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .search-bar {
|
|
|
+ .chip-item {
|
|
|
+ position: relative;
|
|
|
+ height: 44px;
|
|
|
+ padding: 0 10px;
|
|
|
+ border-radius: 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- height: 36px;
|
|
|
- padding: 0 12px;
|
|
|
- margin-bottom: 10px;
|
|
|
- background: rgba(255, 255, 255, 0.5);
|
|
|
- border: 1px solid rgba(33, 153, 248, 0.5);
|
|
|
- border-radius: 6px;
|
|
|
-
|
|
|
- .search-icon {
|
|
|
- color: rgba(0, 0, 0, 0.35);
|
|
|
- font-size: 16px;
|
|
|
- margin-right: 6px;
|
|
|
+ justify-content: center;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 15px;
|
|
|
+ color: rgba(255, 255, 255, 0.85);
|
|
|
+ background: #2a2a2a;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &.selected {
|
|
|
+ color: #e8c56a;
|
|
|
+ border-color: #e8c56a;
|
|
|
+ background: rgba(232, 197, 106, 0.08);
|
|
|
}
|
|
|
|
|
|
- .search-input {
|
|
|
- flex: 1;
|
|
|
- border: none;
|
|
|
- outline: none;
|
|
|
- background: transparent;
|
|
|
- font-size: 14px;
|
|
|
- color: #333;
|
|
|
+ &.variety-item {
|
|
|
+ justify-content: space-between;
|
|
|
+ padding: 0 12px;
|
|
|
+
|
|
|
+ .chip-item__text {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
|
|
|
- &::placeholder {
|
|
|
- color: rgba(0, 0, 0, 0.3);
|
|
|
+ .chip-item__check {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 20px;
|
|
|
+ height: 20px;
|
|
|
+ margin-left: 8px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #e8c56a;
|
|
|
+ color: #1a1a1a;
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 12px;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- .search-btn {
|
|
|
- flex-shrink: 0;
|
|
|
- padding-left: 10px;
|
|
|
- color: #2199f8;
|
|
|
- font-size: 14px;
|
|
|
- cursor: pointer;
|
|
|
+ &.selected {
|
|
|
+ justify-content: space-between;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- .category-list {
|
|
|
- .category-card {
|
|
|
- background: #fff;
|
|
|
+ .empty-tip {
|
|
|
+ grid-column: 1 / -1;
|
|
|
+ padding: 24px 0;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(255, 255, 255, 0.35);
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-select {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 4px;
|
|
|
+
|
|
|
+ :deep(.el-select__wrapper) {
|
|
|
+ min-height: 44px;
|
|
|
+ padding: 0 12px;
|
|
|
border-radius: 12px;
|
|
|
- padding: 14px 12px 16px;
|
|
|
- margin-bottom: 12px;
|
|
|
+ background: #1c1c1c;
|
|
|
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12) inset;
|
|
|
}
|
|
|
|
|
|
- .section-title {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 8px;
|
|
|
- margin-bottom: 4px;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: 600;
|
|
|
- color: #1a1a1a;
|
|
|
+ :deep(.el-select__wrapper.is-focused) {
|
|
|
+ box-shadow: 0 0 0 1px rgba(232, 197, 106, 0.55) inset;
|
|
|
+ }
|
|
|
|
|
|
- .title-icon {
|
|
|
- width: 15px;
|
|
|
- height: 15px;
|
|
|
- object-fit: contain;
|
|
|
- flex-shrink: 0;
|
|
|
- }
|
|
|
+ :deep(.el-select__placeholder) {
|
|
|
+ color: rgba(255, 255, 255, 0.35);
|
|
|
}
|
|
|
|
|
|
- .empty-tip {
|
|
|
- padding: 40px 0;
|
|
|
- text-align: center;
|
|
|
- color: rgba(0, 0, 0, 0.35);
|
|
|
- font-size: 14px;
|
|
|
+ :deep(.el-select__selected-item),
|
|
|
+ :deep(.el-select__input) {
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- .tag-group {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(4, 1fr);
|
|
|
- gap: 0 8px;
|
|
|
- font-size: 14px;
|
|
|
+ :deep(.el-select__caret) {
|
|
|
+ color: rgba(255, 255, 255, 0.55);
|
|
|
+ }
|
|
|
|
|
|
- .tag-item {
|
|
|
- margin-top: 10px;
|
|
|
- position: relative;
|
|
|
- border-radius: 6px;
|
|
|
- box-sizing: border-box;
|
|
|
- height: 36px;
|
|
|
- text-align: center;
|
|
|
- line-height: 36px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s;
|
|
|
- color: #000000;
|
|
|
- background: rgba(241, 241, 241, 0.12);
|
|
|
- border: 1px solid #ebebeb;
|
|
|
-
|
|
|
- .text {
|
|
|
- display: inline-flex;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
+ :deep(.el-select__prefix) {
|
|
|
+ color: rgba(255, 255, 255, 0.45);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- &.selected {
|
|
|
- border: 1px solid #2199f8;
|
|
|
- background: #e8f5ff;
|
|
|
- color: #2199f8;
|
|
|
-
|
|
|
- &::after {
|
|
|
- content: "";
|
|
|
- position: absolute;
|
|
|
- z-index: 9;
|
|
|
- top: -1px;
|
|
|
- right: -1px;
|
|
|
- width: 18px;
|
|
|
- height: 14px;
|
|
|
- background: url("@/assets/img/home/checked-bg-top.png") no-repeat bottom right / 18px 13px;
|
|
|
- }
|
|
|
- }
|
|
|
+ .search-prefix-icon {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
|
|
|
- &.disabled {
|
|
|
- color: rgba(0, 0, 0, 0.25);
|
|
|
- cursor: not-allowed;
|
|
|
- }
|
|
|
+ .action-btn {
|
|
|
+ flex: 1;
|
|
|
+ height: 48px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &.secondary {
|
|
|
+ background: #2a2a2a;
|
|
|
+ color: #fff;
|
|
|
}
|
|
|
|
|
|
- &.add-tag-group {
|
|
|
- .tag-item {
|
|
|
- color: #000000;
|
|
|
- background: rgba(241, 241, 241, 0.12);
|
|
|
- border: 1px solid #ebebeb;
|
|
|
-
|
|
|
- &.selected {
|
|
|
- border: 1px solid #2199f8;
|
|
|
- background: #e8f5ff;
|
|
|
- color: #2199f8;
|
|
|
- }
|
|
|
-
|
|
|
- &.disabled {
|
|
|
- color: rgba(0, 0, 0, 0.25);
|
|
|
- }
|
|
|
- }
|
|
|
+ &.primary {
|
|
|
+ background: linear-gradient(135deg, #f5e6b8 0%, #e8c56a 50%, #d4a84b 100%);
|
|
|
+ color: #1a1a1a;
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
+</style>
|
|
|
|
|
|
- .custom-bottom-fixed-btns {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: baseline;
|
|
|
- padding: 10px 12px 0 12px;
|
|
|
- height: 80px;
|
|
|
- background: #fff;
|
|
|
- box-sizing: border-box;
|
|
|
- box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.4);
|
|
|
-
|
|
|
- .bottom-btn {
|
|
|
- padding: 0 30px;
|
|
|
- height: 40px;
|
|
|
- line-height: 40px;
|
|
|
- font-size: 14px;
|
|
|
- border-radius: 25px;
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
+<style lang="scss">
|
|
|
+.entry-dark-select-popper {
|
|
|
+ background: #1c1c1c !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
|
|
+
|
|
|
+ .el-select-dropdown__item {
|
|
|
+ color: rgba(255, 255, 255, 0.85);
|
|
|
|
|
|
- .secondary-btn {
|
|
|
- color: #2199f8;
|
|
|
- background: #fff;
|
|
|
- border: 1px solid #2199f8;
|
|
|
+ &.is-hovering,
|
|
|
+ &:hover {
|
|
|
+ background: rgba(232, 197, 106, 0.12);
|
|
|
}
|
|
|
|
|
|
- .primary-btn {
|
|
|
- background: #2199f8;
|
|
|
- color: #fff;
|
|
|
+ &.is-selected {
|
|
|
+ color: #e8c56a;
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .el-popper__arrow::before {
|
|
|
+ background: #1c1c1c !important;
|
|
|
+ border: 1px solid rgba(255, 255, 255, 0.12) !important;
|
|
|
+ }
|
|
|
}
|
|
|
</style>
|