|
@@ -2,8 +2,11 @@
|
|
|
<div class="select-equipment" :class="{ 'is-service-equipment': isService }">
|
|
<div class="select-equipment" :class="{ 'is-service-equipment': isService }">
|
|
|
<div class="select-equipment__content">
|
|
<div class="select-equipment__content">
|
|
|
<div class="page-header">
|
|
<div class="page-header">
|
|
|
- <div class="page-title">{{ pageTitle }}</div>
|
|
|
|
|
- <div class="page-subtitle">{{ pageSubtitle }}</div>
|
|
|
|
|
|
|
+ <div class="page-title has-tag-wrap">
|
|
|
|
|
+ 完善设备信息
|
|
|
|
|
+ <span v-if="cropGroupLabel" class="page-tag">{{ cropGroupLabel }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="page-title">让农机调度更精准<span class="page-tag-tip">(可多选)</span></div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="toolbar">
|
|
<div class="toolbar">
|
|
@@ -85,162 +88,64 @@ import { Search } from "@element-plus/icons-vue";
|
|
|
import addMachinePopup from "./addMachinePopup.vue";
|
|
import addMachinePopup from "./addMachinePopup.vue";
|
|
|
import tipPopup from "@/components/popup/tipPopup.vue";
|
|
import tipPopup from "@/components/popup/tipPopup.vue";
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// Props / Emits
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
const emit = defineEmits(["prev", "next", "confirm"]);
|
|
const emit = defineEmits(["prev", "next", "confirm"]);
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
|
|
|
+ /** 农服录入:大田/果树分步选设备;农户录入为 false */
|
|
|
isService: { type: Boolean, default: false },
|
|
isService: { type: Boolean, default: false },
|
|
|
|
|
+ /** 当前是否为录入流程最后一步(决定主按钮文案与是否可提交) */
|
|
|
isLastStep: { type: Boolean, default: true },
|
|
isLastStep: { type: Boolean, default: true },
|
|
|
|
|
+ /** 农服场景下的设备范围:field | fruit | "" */
|
|
|
equipmentScope: { type: String, default: "" },
|
|
equipmentScope: { type: String, default: "" },
|
|
|
totalSteps: { type: Number, default: 4 },
|
|
totalSteps: { type: Number, default: 4 },
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const EQUIPMENT_KEY = "ENTRY_SELECTED_EQUIPMENT";
|
|
|
|
|
-const TASK_KEY = "ENTRY_SELECTED_FARM_TASKS";
|
|
|
|
|
-const SELECTED_LIST_KEY = "ENTRY_SELECTED_VARIETY_LIST";
|
|
|
|
|
-const CATEGORY_SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
|
|
|
|
|
-const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
|
|
|
|
|
-const EDIT_UID_KEY = "ENTRY_VARIETY_EDIT_UID";
|
|
|
|
|
-const STEP_KEY = "ENTRY_INFORMATION_STEP";
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// Session 键名(与录入流程其他步骤共享)
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+const SESSION = {
|
|
|
|
|
+ EQUIPMENT: "ENTRY_SELECTED_EQUIPMENT",
|
|
|
|
|
+ TASK: "ENTRY_SELECTED_FARM_TASKS",
|
|
|
|
|
+ VARIETY: "ENTRY_SELECTED_VARIETY_LIST",
|
|
|
|
|
+ CATEGORY: "ENTRY_SELECTED_CATEGORY",
|
|
|
|
|
+ LOCATION: "ENTRY_RESIDENT_LOCATION",
|
|
|
|
|
+ EDIT_UID: "ENTRY_VARIETY_EDIT_UID",
|
|
|
|
|
+ STEP: "ENTRY_INFORMATION_STEP",
|
|
|
|
|
+ BA_FORM: "ENTRY_BA_FORM",
|
|
|
|
|
+ MANUAL_VIEW: "ENTRY_MANUAL_VIEW",
|
|
|
|
|
+ INVITE_TYPE: "ENTRY_INVITE_TYPE",
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+/** 用户手动添加的农机归入此分组,始终置顶 */
|
|
|
|
|
+const CUSTOM_GROUP_NAME = "我的农机";
|
|
|
|
|
+/** 自定义农机 ID 起始值,避免与接口 machine_id 冲突 */
|
|
|
|
|
+const CUSTOM_MACHINE_ID_START = 9000;
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 页面状态
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
const submitting = ref(false);
|
|
const submitting = ref(false);
|
|
|
const showAddPopup = ref(false);
|
|
const showAddPopup = ref(false);
|
|
|
|
|
+const showSuccessPopup = ref(false);
|
|
|
const searchKeyword = ref("");
|
|
const searchKeyword = ref("");
|
|
|
const appliedKeyword = ref("");
|
|
const appliedKeyword = ref("");
|
|
|
|
|
+/** 已选农机 ID 集合(Set 便于 O(1) 切换) */
|
|
|
const selectedIds = reactive(new Set());
|
|
const selectedIds = reactive(new Set());
|
|
|
|
|
+/** 按农事阶段分组的农机列表(接口 + 自定义项) */
|
|
|
const equipmentGroups = ref([]);
|
|
const equipmentGroups = ref([]);
|
|
|
-let customMachineId = 9000;
|
|
|
|
|
-
|
|
|
|
|
-const DEFAULT_GROUPS = [
|
|
|
|
|
- {
|
|
|
|
|
- name: "耕地(整地、施肥、撒肥、中耕等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 101, name: "滴灌系统" },
|
|
|
|
|
- { id: 102, name: "喷灌设备" },
|
|
|
|
|
- { id: 103, name: "水肥一体机" },
|
|
|
|
|
- { id: 104, name: "水泵机组" },
|
|
|
|
|
- { id: 105, name: "施肥机" },
|
|
|
|
|
- { id: 106, name: "过滤设备" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "种(播种、育秧、移栽、补苗等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 201, name: "拖拉机" },
|
|
|
|
|
- { id: 202, name: "旋耕机" },
|
|
|
|
|
- { id: 203, name: "开沟机" },
|
|
|
|
|
- { id: 204, name: "植保机" },
|
|
|
|
|
- { id: 205, name: "无人机" },
|
|
|
|
|
- { id: 206, name: "运输车" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "收(收割、烘干、运输等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 301, name: "修剪机" },
|
|
|
|
|
- { id: 302, name: "采收机" },
|
|
|
|
|
- { id: 303, name: "割草机" },
|
|
|
|
|
- { id: 304, name: "粉碎机" },
|
|
|
|
|
- { id: 305, name: "发电机" },
|
|
|
|
|
- { id: 306, name: "其他设备" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
-];
|
|
|
|
|
-
|
|
|
|
|
-const FIELD_GROUPS = [
|
|
|
|
|
- {
|
|
|
|
|
- name: "耕地(整地、施肥、撒肥、中耕等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 1001, name: "大中型拖拉机" },
|
|
|
|
|
- { id: 1002, name: "手扶拖拉机" },
|
|
|
|
|
- { id: 1003, name: "旋耕机" },
|
|
|
|
|
- { id: 1004, name: "微耕机" },
|
|
|
|
|
- { id: 1005, name: "挖掘机" },
|
|
|
|
|
- { id: 1006, name: "培土机" },
|
|
|
|
|
- { id: 1007, name: "撒肥机" },
|
|
|
|
|
- { id: 1008, name: "秸秆还田机" },
|
|
|
|
|
- { id: 1009, name: "开沟机" },
|
|
|
|
|
- { id: 1010, name: "水田打浆机" },
|
|
|
|
|
- { id: 1011, name: "深松机" },
|
|
|
|
|
- { id: 1012, name: "起垄机" },
|
|
|
|
|
- { id: 1013, name: "甘蔗开行机" },
|
|
|
|
|
- { id: 1014, name: "棉花拔杆机" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "种(播种、育秧、移栽、补苗等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 2001, name: "插秧机" },
|
|
|
|
|
- { id: 2002, name: "一体育秧机" },
|
|
|
|
|
- { id: 2003, name: "播种机" },
|
|
|
|
|
- { id: 2004, name: "移栽机" },
|
|
|
|
|
- { id: 2005, name: "无人机播种" },
|
|
|
|
|
- { id: 2006, name: "小麦/玉米精量播种机" },
|
|
|
|
|
- { id: 2007, name: "免耕播种机" },
|
|
|
|
|
- { id: 2008, name: "花生覆膜播种机" },
|
|
|
|
|
- { id: 2009, name: "马铃薯播种机" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "收(收割、烘干、运输等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 3001, name: "联合收割机" },
|
|
|
|
|
- { id: 3002, name: "半喂入收割机" },
|
|
|
|
|
- { id: 3003, name: "玉米联合收割机" },
|
|
|
|
|
- { id: 3004, name: "水稻收割机" },
|
|
|
|
|
- { id: 3005, name: "烘干机" },
|
|
|
|
|
- { id: 3006, name: "运粮车" },
|
|
|
|
|
- { id: 3007, name: "挂车" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
-];
|
|
|
|
|
-
|
|
|
|
|
-const FRUIT_GROUPS = [
|
|
|
|
|
- {
|
|
|
|
|
- name: "土壤与园地管理",
|
|
|
|
|
- desc: "(整地、除草、清园、开沟修渠等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 4001, name: "旋耕机" },
|
|
|
|
|
- { id: 4002, name: "微耕机" },
|
|
|
|
|
- { id: 4003, name: "挖掘机" },
|
|
|
|
|
- { id: 4004, name: "旋耕开沟机" },
|
|
|
|
|
- { id: 4005, name: "割草机" },
|
|
|
|
|
- { id: 4006, name: "清沟机" },
|
|
|
|
|
- { id: 4007, name: "运输车" },
|
|
|
|
|
- { id: 4008, name: "开沟施肥机" },
|
|
|
|
|
- { id: 4009, name: "树盘管理机" },
|
|
|
|
|
- { id: 4010, name: "行间除草机" },
|
|
|
|
|
- { id: 4011, name: "生草播种机" },
|
|
|
|
|
- { id: 4012, name: "压草机" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "肥水管理",
|
|
|
|
|
- desc: "(施肥、灌水、灌药等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 5001, name: "水肥一体化" },
|
|
|
|
|
- { id: 5002, name: "滴灌系统" },
|
|
|
|
|
- { id: 5003, name: "注肥/注药泵" },
|
|
|
|
|
- { id: 5004, name: "施肥枪" },
|
|
|
|
|
- { id: 5005, name: "移动水车" },
|
|
|
|
|
- { id: 5006, name: "水泵" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
- {
|
|
|
|
|
- name: "植保打药",
|
|
|
|
|
- desc: "(冠层打药、内膛打药等)",
|
|
|
|
|
- items: [
|
|
|
|
|
- { id: 6001, name: "植保无人机" },
|
|
|
|
|
- { id: 6002, name: "风送式喷雾机" },
|
|
|
|
|
- { id: 6003, name: "背负式喷雾器" },
|
|
|
|
|
- { id: 6004, name: "柴油喷药机" },
|
|
|
|
|
- { id: 6005, name: "烟雾机" },
|
|
|
|
|
- { id: 6006, name: "高架喷雾机" },
|
|
|
|
|
- ],
|
|
|
|
|
- },
|
|
|
|
|
-];
|
|
|
|
|
-
|
|
|
|
|
|
|
+/** 页头品类标签,如「大田类」 */
|
|
|
|
|
+const cropGroupLabel = ref("");
|
|
|
|
|
+let customMachineId = CUSTOM_MACHINE_ID_START;
|
|
|
|
|
+
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 计算属性
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+/** 按关键词过滤后的展示分组 */
|
|
|
const displayGroups = computed(() => {
|
|
const displayGroups = computed(() => {
|
|
|
- const keyword = (appliedKeyword.value || "").trim();
|
|
|
|
|
|
|
+ const keyword = appliedKeyword.value.trim();
|
|
|
if (!keyword) return equipmentGroups.value;
|
|
if (!keyword) return equipmentGroups.value;
|
|
|
return equipmentGroups.value
|
|
return equipmentGroups.value
|
|
|
.map((group) => ({
|
|
.map((group) => ({
|
|
@@ -250,21 +155,21 @@ const displayGroups = computed(() => {
|
|
|
.filter((group) => group.items.length);
|
|
.filter((group) => group.items.length);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+/** 扁平化所有农机项,供选中态与草稿保存使用 */
|
|
|
const allEquipmentItems = computed(() =>
|
|
const allEquipmentItems = computed(() =>
|
|
|
equipmentGroups.value.flatMap((group) => group.items)
|
|
equipmentGroups.value.flatMap((group) => group.items)
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
-const pageTitle = computed(() =>
|
|
|
|
|
- props.isService ? "完善设备信息" : "请填写您的农场设备"
|
|
|
|
|
-);
|
|
|
|
|
-const pageSubtitle = computed(() =>
|
|
|
|
|
- props.isService ? "让农机调度更精准(可多选)" : "完善设备信息,让农机调度更精准"
|
|
|
|
|
-);
|
|
|
|
|
const primaryBtnText = computed(() => {
|
|
const primaryBtnText = computed(() => {
|
|
|
if (submitting.value) return "提交中...";
|
|
if (submitting.value) return "提交中...";
|
|
|
- return props.isLastStep ? "提交信息" : `下一步 (3/${props.isService ? props.totalSteps : 4})`;
|
|
|
|
|
|
|
+ if (props.isLastStep) return "提交信息";
|
|
|
|
|
+ const total = props.isService ? props.totalSteps : 4;
|
|
|
|
|
+ return `下一步 (3/${total})`;
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// Session 读写
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
function readJson(key) {
|
|
function readJson(key) {
|
|
|
try {
|
|
try {
|
|
|
const raw = sessionStorage.getItem(key);
|
|
const raw = sessionStorage.getItem(key);
|
|
@@ -274,12 +179,64 @@ function readJson(key) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function cloneGroups(groups) {
|
|
|
|
|
- return groups.map((group) => ({
|
|
|
|
|
- name: group.name,
|
|
|
|
|
- desc: group.desc || "",
|
|
|
|
|
- items: group.items.map((item) => ({ ...item })),
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+function writeJson(key, value) {
|
|
|
|
|
+ sessionStorage.setItem(key, JSON.stringify(value));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 品类与设备范围(农服分大田/果树两步)
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+function getSelectedCategories() {
|
|
|
|
|
+ const list = readJson(SESSION.CATEGORY);
|
|
|
|
|
+ return Array.isArray(list) ? list : [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isFieldCategory(item) {
|
|
|
|
|
+ return item?.firstCrop === "大田" || item?.majorKey === "field";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function isFruitCategory(item) {
|
|
|
|
|
+ return item?.firstCrop === "果树" || item?.majorKey === "fruit";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 当前步骤应对应的品类:农服按 scope 取大田/果树,农户取第一个 */
|
|
|
|
|
+function getCategoryForMachines() {
|
|
|
|
|
+ const categories = getSelectedCategories();
|
|
|
|
|
+ const scope = getCurrentScope();
|
|
|
|
|
+ if (scope === "fruit") return categories.find(isFruitCategory) ?? null;
|
|
|
|
|
+ if (scope === "field") return categories.find(isFieldCategory) ?? null;
|
|
|
|
|
+ return categories[0] ?? null;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function getCurrentScope() {
|
|
|
|
|
+ if (props.equipmentScope === "fruit") return "fruit";
|
|
|
|
|
+ if (props.equipmentScope === "field") return "field";
|
|
|
|
|
+ return "";
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 农机数据结构转换
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+/** 接口 machines(按 stage 分组)→ 页面分组结构 */
|
|
|
|
|
+function mapMachinesToGroups(machines) {
|
|
|
|
|
+ if (!Array.isArray(machines)) return [];
|
|
|
|
|
+ return machines
|
|
|
|
|
+ .map((group) => ({
|
|
|
|
|
+ name: group.stage || "",
|
|
|
|
|
+ desc: "",
|
|
|
|
|
+ items: (group.items || []).map((item) => ({
|
|
|
|
|
+ id: item.machine_id,
|
|
|
|
|
+ name: item.display_name || item.machine_name || "",
|
|
|
|
|
+ machineName: item.machine_name || "",
|
|
|
|
|
+ isCropSpecific: !!item.is_crop_specific,
|
|
|
|
|
+ })),
|
|
|
|
|
+ }))
|
|
|
|
|
+ .filter((group) => group.name && group.items.length);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 农服缓存为 { field, fruit };农户为数组 */
|
|
|
|
|
+function isScopedEquipmentCache(cached) {
|
|
|
|
|
+ return cached && !Array.isArray(cached) && (cached.field || cached.fruit);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function flattenEquipment(cached) {
|
|
function flattenEquipment(cached) {
|
|
@@ -288,88 +245,152 @@ function flattenEquipment(cached) {
|
|
|
return [...(cached.field || []), ...(cached.fruit || [])];
|
|
return [...(cached.field || []), ...(cached.fruit || [])];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function getScopeList(cached, scope) {
|
|
|
|
|
|
|
+function getScopeEquipmentList(cached, scope) {
|
|
|
if (!cached) return [];
|
|
if (!cached) return [];
|
|
|
if (Array.isArray(cached)) return scope === "fruit" ? [] : cached;
|
|
if (Array.isArray(cached)) return scope === "fruit" ? [] : cached;
|
|
|
return cached[scope] || [];
|
|
return cached[scope] || [];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function getCurrentScope() {
|
|
|
|
|
- if (props.equipmentScope === "fruit") return "fruit";
|
|
|
|
|
- if (props.equipmentScope === "field") return "field";
|
|
|
|
|
- return "";
|
|
|
|
|
|
|
+function findGroupByItemId(itemId) {
|
|
|
|
|
+ return equipmentGroups.value.find((group) =>
|
|
|
|
|
+ group.items.some((row) => String(row.id) === String(itemId))
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function updateCropGroupLabel(category, apiCropGroup) {
|
|
|
|
|
+ if (apiCropGroup) {
|
|
|
|
|
+ cropGroupLabel.value = `${apiCropGroup}类`;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ cropGroupLabel.value =
|
|
|
|
|
+ category?.majorLabel ||
|
|
|
|
|
+ (category?.firstCrop ? `${category.firstCrop}类` : "");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 选中态:恢复 / 持久化
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+/** 从 session 恢复当前 scope 下的选中项;自定义项若不在列表中则补回分组 */
|
|
|
function restoreSelection() {
|
|
function restoreSelection() {
|
|
|
- const cached = readJson(EQUIPMENT_KEY);
|
|
|
|
|
|
|
+ const cached = readJson(SESSION.EQUIPMENT);
|
|
|
const scope = getCurrentScope();
|
|
const scope = getCurrentScope();
|
|
|
- const list = scope ? getScopeList(cached, scope) : flattenEquipment(cached);
|
|
|
|
|
|
|
+ const list = scope ? getScopeEquipmentList(cached, scope) : flattenEquipment(cached);
|
|
|
|
|
+
|
|
|
list.forEach((item) => {
|
|
list.forEach((item) => {
|
|
|
if (item?.id == null) return;
|
|
if (item?.id == null) return;
|
|
|
selectedIds.add(String(item.id));
|
|
selectedIds.add(String(item.id));
|
|
|
|
|
+
|
|
|
if (!item.custom) return;
|
|
if (!item.custom) return;
|
|
|
- const exists = allEquipmentItems.value.some((row) => String(row.id) === String(item.id));
|
|
|
|
|
|
|
+ const exists = allEquipmentItems.value.some(
|
|
|
|
|
+ (row) => String(row.id) === String(item.id)
|
|
|
|
|
+ );
|
|
|
if (exists) return;
|
|
if (exists) return;
|
|
|
- let group = equipmentGroups.value.find((row) => row.name === item.groupName);
|
|
|
|
|
- if (!group) group = equipmentGroups.value[equipmentGroups.value.length - 1];
|
|
|
|
|
- if (group) {
|
|
|
|
|
- group.items.push({ id: item.id, name: item.name, custom: true });
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const group =
|
|
|
|
|
+ equipmentGroups.value.find((row) => row.name === item.groupName) ||
|
|
|
|
|
+ equipmentGroups.value.at(-1);
|
|
|
|
|
+ group?.items.push({ id: item.id, name: item.name, custom: true });
|
|
|
|
|
+
|
|
|
|
|
+ if (Number(item.id) >= customMachineId) {
|
|
|
|
|
+ customMachineId = Number(item.id);
|
|
|
}
|
|
}
|
|
|
- if (Number(item.id) >= customMachineId) customMachineId = Number(item.id);
|
|
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function saveSelectionDraft() {
|
|
|
|
|
|
|
+/** 将当前选中项写入 session;农服按 field/fruit 分桶存储 */
|
|
|
|
|
+function buildSelectedEquipmentPayload() {
|
|
|
const scope = getCurrentScope();
|
|
const scope = getCurrentScope();
|
|
|
- const selected = allEquipmentItems.value
|
|
|
|
|
|
|
+ return allEquipmentItems.value
|
|
|
.filter((item) => selectedIds.has(String(item.id)))
|
|
.filter((item) => selectedIds.has(String(item.id)))
|
|
|
- .map((item) => {
|
|
|
|
|
- const group = equipmentGroups.value.find((row) =>
|
|
|
|
|
- row.items.some((rowItem) => String(rowItem.id) === String(item.id))
|
|
|
|
|
- );
|
|
|
|
|
- return { ...item, scope: scope || "farmer", groupName: group?.name };
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ .map((item) => ({
|
|
|
|
|
+ ...item,
|
|
|
|
|
+ scope: scope || "farmer",
|
|
|
|
|
+ groupName: findGroupByItemId(item.id)?.name,
|
|
|
|
|
+ }));
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function saveSelectionDraft() {
|
|
|
|
|
+ const selected = buildSelectedEquipmentPayload();
|
|
|
|
|
+ const scope = getCurrentScope();
|
|
|
|
|
+
|
|
|
if (!props.isService) {
|
|
if (!props.isService) {
|
|
|
- sessionStorage.setItem(EQUIPMENT_KEY, JSON.stringify(selected));
|
|
|
|
|
|
|
+ writeJson(SESSION.EQUIPMENT, selected);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- const cached = readJson(EQUIPMENT_KEY);
|
|
|
|
|
- const next = {
|
|
|
|
|
- field: Array.isArray(cached) ? cached : cached?.field || [],
|
|
|
|
|
- fruit: Array.isArray(cached) ? [] : cached?.fruit || [],
|
|
|
|
|
- };
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const cached = readJson(SESSION.EQUIPMENT);
|
|
|
|
|
+ const next = isScopedEquipmentCache(cached)
|
|
|
|
|
+ ? { field: cached.field || [], fruit: cached.fruit || [] }
|
|
|
|
|
+ : { field: Array.isArray(cached) ? cached : [], fruit: [] };
|
|
|
|
|
+
|
|
|
if (scope === "fruit") next.fruit = selected;
|
|
if (scope === "fruit") next.fruit = selected;
|
|
|
else next.field = selected;
|
|
else next.field = selected;
|
|
|
- sessionStorage.setItem(EQUIPMENT_KEY, JSON.stringify(next));
|
|
|
|
|
|
|
+ writeJson(SESSION.EQUIPMENT, next);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function fetchEquipmentList() {
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 数据加载
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+async function fetchEquipmentList() {
|
|
|
|
|
+ const category = getCategoryForMachines();
|
|
|
|
|
+ const cropId = category?.id;
|
|
|
|
|
+ updateCropGroupLabel(category);
|
|
|
|
|
+
|
|
|
|
|
+ if (cropId == null || cropId === "") {
|
|
|
|
|
+ equipmentGroups.value = [];
|
|
|
|
|
+ ElMessage.warning("请先选择种植品类");
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
loading.value = true;
|
|
loading.value = true;
|
|
|
- if (!props.isService) {
|
|
|
|
|
- equipmentGroups.value = cloneGroups(DEFAULT_GROUPS);
|
|
|
|
|
- } else if (props.equipmentScope === "fruit") {
|
|
|
|
|
- equipmentGroups.value = cloneGroups(FRUIT_GROUPS);
|
|
|
|
|
- } else {
|
|
|
|
|
- equipmentGroups.value = cloneGroups(FIELD_GROUPS);
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await VE_API.entry.getMachines({ crop_id: cropId });
|
|
|
|
|
+ if (res.code === 200) {
|
|
|
|
|
+ const data = res.data || {};
|
|
|
|
|
+ updateCropGroupLabel(category, data.crop_group);
|
|
|
|
|
+ equipmentGroups.value = mapMachinesToGroups(data.machines);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ equipmentGroups.value = [];
|
|
|
|
|
+ ElMessage.error(res.msg || "获取农机列表失败");
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ equipmentGroups.value = [];
|
|
|
|
|
+ ElMessage.error("获取农机列表失败,请稍后再试");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false;
|
|
|
}
|
|
}
|
|
|
- loading.value = false;
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleSearch = () => {
|
|
|
|
|
- appliedKeyword.value = searchKeyword.value;
|
|
|
|
|
-};
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 交互:搜索 / 选择 / 自定义农机
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+function handleSearch() {
|
|
|
|
|
+ appliedKeyword.value = searchKeyword.value.trim();
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const handleAddMachine = () => {
|
|
|
|
|
|
|
+function handleAddMachine() {
|
|
|
showAddPopup.value = true;
|
|
showAddPopup.value = true;
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const handleAddConfirm = ({ name }) => {
|
|
|
|
|
|
|
+function ensureCustomGroupTop() {
|
|
|
|
|
+ let customGroup = equipmentGroups.value.find((g) => g.name === CUSTOM_GROUP_NAME);
|
|
|
|
|
+ if (!customGroup) {
|
|
|
|
|
+ customGroup = { name: CUSTOM_GROUP_NAME, items: [] };
|
|
|
|
|
+ equipmentGroups.value.unshift(customGroup);
|
|
|
|
|
+ return customGroup;
|
|
|
|
|
+ }
|
|
|
|
|
+ const index = equipmentGroups.value.indexOf(customGroup);
|
|
|
|
|
+ if (index > 0) {
|
|
|
|
|
+ equipmentGroups.value.splice(index, 1);
|
|
|
|
|
+ equipmentGroups.value.unshift(customGroup);
|
|
|
|
|
+ }
|
|
|
|
|
+ return customGroup;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function handleAddConfirm({ name }) {
|
|
|
const machineName = String(name || "").trim();
|
|
const machineName = String(name || "").trim();
|
|
|
if (!machineName) return;
|
|
if (!machineName) return;
|
|
|
|
|
|
|
|
- const CUSTOM_GROUP_NAME = "我的农机";
|
|
|
|
|
-
|
|
|
|
|
- // 已存在同名则直接选中
|
|
|
|
|
const exists = allEquipmentItems.value.find((item) => item.name === machineName);
|
|
const exists = allEquipmentItems.value.find((item) => item.name === machineName);
|
|
|
if (exists) {
|
|
if (exists) {
|
|
|
selectedIds.add(String(exists.id));
|
|
selectedIds.add(String(exists.id));
|
|
@@ -378,42 +399,26 @@ const handleAddConfirm = ({ name }) => {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 确保「自定义类」作为第一组存在
|
|
|
|
|
- let customGroup = equipmentGroups.value.find((group) => group.name === CUSTOM_GROUP_NAME);
|
|
|
|
|
- if (!customGroup) {
|
|
|
|
|
- customGroup = { name: CUSTOM_GROUP_NAME, items: [] };
|
|
|
|
|
- equipmentGroups.value.unshift(customGroup);
|
|
|
|
|
- } else {
|
|
|
|
|
- const index = equipmentGroups.value.indexOf(customGroup);
|
|
|
|
|
- if (index > 0) {
|
|
|
|
|
- equipmentGroups.value.splice(index, 1);
|
|
|
|
|
- equipmentGroups.value.unshift(customGroup);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const newItem = {
|
|
|
|
|
- id: ++customMachineId,
|
|
|
|
|
- name: machineName,
|
|
|
|
|
- custom: true,
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const customGroup = ensureCustomGroupTop();
|
|
|
|
|
+ const newItem = { id: ++customMachineId, name: machineName, custom: true };
|
|
|
customGroup.items.push(newItem);
|
|
customGroup.items.push(newItem);
|
|
|
selectedIds.add(String(newItem.id));
|
|
selectedIds.add(String(newItem.id));
|
|
|
saveSelectionDraft();
|
|
saveSelectionDraft();
|
|
|
ElMessage.success("添加成功");
|
|
ElMessage.success("添加成功");
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const toggleSelect = (item) => {
|
|
|
|
|
|
|
+function toggleSelect(item) {
|
|
|
const id = String(item.id);
|
|
const id = String(item.id);
|
|
|
- if (selectedIds.has(id)) {
|
|
|
|
|
- selectedIds.delete(id);
|
|
|
|
|
- } else {
|
|
|
|
|
- selectedIds.add(id);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (selectedIds.has(id)) selectedIds.delete(id);
|
|
|
|
|
+ else selectedIds.add(id);
|
|
|
saveSelectionDraft();
|
|
saveSelectionDraft();
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
|
|
+// 提交:组装 payload / 清 session
|
|
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
function getVarietyDraftList() {
|
|
function getVarietyDraftList() {
|
|
|
- const draft = readJson(SELECTED_LIST_KEY);
|
|
|
|
|
|
|
+ const draft = readJson(SESSION.VARIETY);
|
|
|
if (Array.isArray(draft)) return draft;
|
|
if (Array.isArray(draft)) return draft;
|
|
|
if (Array.isArray(draft?.list)) return draft.list;
|
|
if (Array.isArray(draft?.list)) return draft.list;
|
|
|
return [];
|
|
return [];
|
|
@@ -423,15 +428,20 @@ function getPhenophaseLabel(item) {
|
|
|
return item.phenophase || item.phenologyName || String(item.phenologyId || "");
|
|
return item.phenophase || item.phenologyName || String(item.phenologyId || "");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function flattenTasks(taskCache) {
|
|
|
|
|
+ if (!taskCache) return [];
|
|
|
|
|
+ if (Array.isArray(taskCache)) return taskCache;
|
|
|
|
|
+ return [...(taskCache.field || []), ...(taskCache.fruit || [])];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/** 汇总各步骤 session 数据,供 addPlotInfo 提交 */
|
|
|
function buildPlotPayload(includeEquipment = true) {
|
|
function buildPlotPayload(includeEquipment = true) {
|
|
|
- const baForm = readJson("ENTRY_BA_FORM") || {};
|
|
|
|
|
|
|
+ const baForm = readJson(SESSION.BA_FORM) || {};
|
|
|
const varietyList = getVarietyDraftList();
|
|
const varietyList = getVarietyDraftList();
|
|
|
- const equipmentCache = readJson(EQUIPMENT_KEY);
|
|
|
|
|
|
|
+ const equipmentCache = readJson(SESSION.EQUIPMENT);
|
|
|
const equipmentList = includeEquipment ? flattenEquipment(equipmentCache) : [];
|
|
const equipmentList = includeEquipment ? flattenEquipment(equipmentCache) : [];
|
|
|
- const taskCache = readJson(TASK_KEY);
|
|
|
|
|
- const taskList = Array.isArray(taskCache)
|
|
|
|
|
- ? taskCache
|
|
|
|
|
- : [...(taskCache?.field || []), ...(taskCache?.fruit || [])];
|
|
|
|
|
|
|
+ const taskCache = readJson(SESSION.TASK);
|
|
|
|
|
+ const taskList = flattenTasks(taskCache);
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
user_name: baForm.name,
|
|
user_name: baForm.name,
|
|
@@ -456,35 +466,30 @@ function buildPlotPayload(includeEquipment = true) {
|
|
|
id: item.id,
|
|
id: item.id,
|
|
|
name: item.name,
|
|
name: item.name,
|
|
|
})),
|
|
})),
|
|
|
- field_tasks: (taskCache?.field || []).map((item) => ({ id: item.id, name: item.name })),
|
|
|
|
|
- fruit_tasks: (taskCache?.fruit || []).map((item) => ({ id: item.id, name: item.name })),
|
|
|
|
|
|
|
+ field_tasks: (taskCache?.field || []).map((item) => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ })),
|
|
|
|
|
+ fruit_tasks: (taskCache?.fruit || []).map((item) => ({
|
|
|
|
|
+ id: item.id,
|
|
|
|
|
+ name: item.name,
|
|
|
|
|
+ })),
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function clearEntrySession() {
|
|
function clearEntrySession() {
|
|
|
- sessionStorage.removeItem(SELECTED_LIST_KEY);
|
|
|
|
|
- sessionStorage.removeItem(CATEGORY_SESSION_KEY);
|
|
|
|
|
- sessionStorage.removeItem("ENTRY_BA_FORM");
|
|
|
|
|
- sessionStorage.removeItem(LOCATION_KEY);
|
|
|
|
|
- sessionStorage.removeItem(EDIT_UID_KEY);
|
|
|
|
|
- sessionStorage.removeItem(EQUIPMENT_KEY);
|
|
|
|
|
- sessionStorage.removeItem(TASK_KEY);
|
|
|
|
|
- sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
|
|
|
|
|
- sessionStorage.removeItem(STEP_KEY);
|
|
|
|
|
- sessionStorage.removeItem("ENTRY_INVITE_TYPE");
|
|
|
|
|
|
|
+ Object.values(SESSION).forEach((key) => sessionStorage.removeItem(key));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const showSuccessPopup = ref(false);
|
|
|
|
|
-
|
|
|
|
|
async function submitEntry(includeEquipment = true) {
|
|
async function submitEntry(includeEquipment = true) {
|
|
|
if (submitting.value) return;
|
|
if (submitting.value) return;
|
|
|
- const varietyList = getVarietyDraftList();
|
|
|
|
|
|
|
|
|
|
- if (!props.isService && !varietyList.length) {
|
|
|
|
|
|
|
+ if (!props.isService && !getVarietyDraftList().length) {
|
|
|
ElMessage.warning("请先完善种植品种信息");
|
|
ElMessage.warning("请先完善种植品种信息");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- const baForm = readJson("ENTRY_BA_FORM");
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const baForm = readJson(SESSION.BA_FORM);
|
|
|
if (!baForm?.name || !baForm?.phone) {
|
|
if (!baForm?.name || !baForm?.phone) {
|
|
|
ElMessage.warning("请先完善个人信息");
|
|
ElMessage.warning("请先完善个人信息");
|
|
|
return;
|
|
return;
|
|
@@ -513,30 +518,31 @@ async function submitEntry(includeEquipment = true) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleComplete = () => {
|
|
|
|
|
|
|
+function handleComplete() {
|
|
|
showSuccessPopup.value = false;
|
|
showSuccessPopup.value = false;
|
|
|
emit("confirm");
|
|
emit("confirm");
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const handleSkip = () => {
|
|
|
|
|
|
|
+/** 农户最后一步可跳过设备选择,仍提交其余信息 */
|
|
|
|
|
+function handleSkip() {
|
|
|
if (!props.isLastStep) {
|
|
if (!props.isLastStep) {
|
|
|
emit("next");
|
|
emit("next");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
submitEntry(false);
|
|
submitEntry(false);
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-const handleSubmit = () => {
|
|
|
|
|
|
|
+function handleSubmit() {
|
|
|
saveSelectionDraft();
|
|
saveSelectionDraft();
|
|
|
if (!props.isLastStep) {
|
|
if (!props.isLastStep) {
|
|
|
emit("next");
|
|
emit("next");
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
submitEntry(true);
|
|
submitEntry(true);
|
|
|
-};
|
|
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-onMounted(() => {
|
|
|
|
|
- fetchEquipmentList();
|
|
|
|
|
|
|
+onMounted(async () => {
|
|
|
|
|
+ await fetchEquipmentList();
|
|
|
restoreSelection();
|
|
restoreSelection();
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
@@ -554,17 +560,35 @@ onMounted(() => {
|
|
|
min-height: 0;
|
|
min-height: 0;
|
|
|
overflow-y: auto;
|
|
overflow-y: auto;
|
|
|
-webkit-overflow-scrolling: touch;
|
|
-webkit-overflow-scrolling: touch;
|
|
|
- padding: 8px 16px 80px;
|
|
|
|
|
|
|
+ padding: 12px 10px 80px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.page-header {
|
|
.page-header {
|
|
|
- padding: 8px 4px 16px;
|
|
|
|
|
|
|
+ padding: 8px 0 16px;
|
|
|
|
|
|
|
|
.page-title {
|
|
.page-title {
|
|
|
font-size: 26px;
|
|
font-size: 26px;
|
|
|
color: #005599;
|
|
color: #005599;
|
|
|
font-family: "PangMenZhengDao";
|
|
font-family: "PangMenZhengDao";
|
|
|
- line-height: 36px;
|
|
|
|
|
|
|
+ line-height: 30px;
|
|
|
|
|
+ .page-tag-tip {
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+ }
|
|
|
|
|
+ &.has-tag-wrap {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 6px;
|
|
|
|
|
+ }
|
|
|
|
|
+ .page-tag {
|
|
|
|
|
+ height: 24px;
|
|
|
|
|
+ line-height: 24px;
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+ background: #2199F8;
|
|
|
|
|
+ color: #FFFFFF;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ padding: 0 6px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.page-subtitle {
|
|
.page-subtitle {
|
|
@@ -682,22 +706,25 @@ onMounted(() => {
|
|
|
position: relative;
|
|
position: relative;
|
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
|
box-sizing: border-box;
|
|
box-sizing: border-box;
|
|
|
- height: 36px;
|
|
|
|
|
|
|
+ min-height: 36px;
|
|
|
|
|
+ padding: 6px 4px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
- line-height: 36px;
|
|
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
color: #000;
|
|
color: #000;
|
|
|
background: #f3f4f6;
|
|
background: #f3f4f6;
|
|
|
border: 1px solid transparent;
|
|
border: 1px solid transparent;
|
|
|
|
|
|
|
|
.text {
|
|
.text {
|
|
|
- display: inline-block;
|
|
|
|
|
- max-width: 100%;
|
|
|
|
|
- overflow: hidden;
|
|
|
|
|
- text-overflow: ellipsis;
|
|
|
|
|
- white-space: nowrap;
|
|
|
|
|
- vertical-align: top;
|
|
|
|
|
- padding: 0 6px;
|
|
|
|
|
|
|
+ display: block;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ padding: 0 4px;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 16px;
|
|
|
|
|
+ white-space: normal;
|
|
|
|
|
+ word-break: break-all;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
&.selected {
|
|
&.selected {
|
|
@@ -778,27 +805,6 @@ onMounted(() => {
|
|
|
color: #005599;
|
|
color: #005599;
|
|
|
line-height: 22px;
|
|
line-height: 22px;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- .tag-item {
|
|
|
|
|
- height: auto;
|
|
|
|
|
- min-height: 36px;
|
|
|
|
|
- padding: 6px 4px;
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- justify-content: center;
|
|
|
|
|
- line-height: 16px;
|
|
|
|
|
-
|
|
|
|
|
- .text {
|
|
|
|
|
- display: block;
|
|
|
|
|
- white-space: normal;
|
|
|
|
|
- overflow: visible;
|
|
|
|
|
- text-overflow: unset;
|
|
|
|
|
- font-size: 13px;
|
|
|
|
|
- line-height: 16px;
|
|
|
|
|
- padding: 0 4px;
|
|
|
|
|
- word-break: break-all;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
</style>
|
|
</style>
|