selectEquipment.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. <template>
  2. <div class="select-equipment" :class="{ 'is-service-equipment': isService }">
  3. <div class="select-equipment__content">
  4. <div class="page-header">
  5. <div class="page-title">{{ pageTitle }}</div>
  6. <div class="page-subtitle">{{ pageSubtitle }}</div>
  7. </div>
  8. <div class="toolbar">
  9. <div class="search-bar">
  10. <el-icon class="search-icon"><Search /></el-icon>
  11. <input
  12. v-model="searchKeyword"
  13. class="search-input"
  14. type="text"
  15. placeholder="请输入农机名称"
  16. @keyup.enter="handleSearch"
  17. />
  18. <div class="search-btn" @click="handleSearch">搜索</div>
  19. </div>
  20. <div class="add-btn" @click="handleAddMachine">+ 添加农机</div>
  21. </div>
  22. <div class="equipment-list" v-loading="loading">
  23. <div v-for="group in displayGroups" :key="group.name" class="equipment-card">
  24. <div class="section-title">
  25. <img class="title-icon" src="@/assets/img/home/label-icon.png" alt="" />
  26. <span>{{ group.name }}</span>
  27. </div>
  28. <div v-if="group.desc" class="section-desc">{{ group.desc }}</div>
  29. <div class="tag-group">
  30. <div
  31. v-for="item in group.items"
  32. :key="item.id"
  33. class="tag-item"
  34. :class="{ selected: selectedIds.has(String(item.id)) }"
  35. @click="toggleSelect(item)"
  36. >
  37. <span class="text">{{ item.name }}</span>
  38. </div>
  39. </div>
  40. </div>
  41. <div v-if="!loading && !displayGroups.length" class="empty-tip">暂无匹配农机</div>
  42. </div>
  43. </div>
  44. <div class="custom-bottom-fixed-btns">
  45. <div class="btns-l">
  46. <div class="bottom-btn secondary-btn" @click="emit('prev')">上一步</div>
  47. <div
  48. v-if="isLastStep && !isService"
  49. class="bottom-btn secondary-btn"
  50. :class="{ disabled: submitting }"
  51. @click="handleSkip"
  52. >
  53. 跳过
  54. </div>
  55. </div>
  56. <div class="bottom-btn primary-btn" :class="{ disabled: submitting }" @click="handleSubmit">
  57. {{ primaryBtnText }}
  58. </div>
  59. </div>
  60. <add-machine-popup
  61. v-model:show="showAddPopup"
  62. @confirm="handleAddConfirm"
  63. />
  64. <tip-popup
  65. v-model:show="showSuccessPopup"
  66. type="executeSuccess"
  67. :text="isService ? '您的信息已提交成功' : '您的信息已上传'"
  68. :text2="isService ? '' : '请等待诊断报告生成'"
  69. buttonText="完成"
  70. @confirm="handleComplete"
  71. />
  72. </div>
  73. </template>
  74. <script setup>
  75. import { computed, onMounted, reactive, ref } from "vue";
  76. import { ElMessage } from "element-plus";
  77. import { Search } from "@element-plus/icons-vue";
  78. import addMachinePopup from "./addMachinePopup.vue";
  79. import tipPopup from "@/components/popup/tipPopup.vue";
  80. const emit = defineEmits(["prev", "next", "confirm"]);
  81. const props = defineProps({
  82. isService: { type: Boolean, default: false },
  83. isLastStep: { type: Boolean, default: true },
  84. equipmentScope: { type: String, default: "" },
  85. totalSteps: { type: Number, default: 4 },
  86. });
  87. const EQUIPMENT_KEY = "ENTRY_SELECTED_EQUIPMENT";
  88. const TASK_KEY = "ENTRY_SELECTED_FARM_TASKS";
  89. const SELECTED_LIST_KEY = "ENTRY_SELECTED_VARIETY_LIST";
  90. const CATEGORY_SESSION_KEY = "ENTRY_SELECTED_CATEGORY";
  91. const LOCATION_KEY = "ENTRY_RESIDENT_LOCATION";
  92. const EDIT_UID_KEY = "ENTRY_VARIETY_EDIT_UID";
  93. const STEP_KEY = "ENTRY_INFORMATION_STEP";
  94. const loading = ref(false);
  95. const submitting = ref(false);
  96. const showAddPopup = ref(false);
  97. const searchKeyword = ref("");
  98. const appliedKeyword = ref("");
  99. const selectedIds = reactive(new Set());
  100. const equipmentGroups = ref([]);
  101. let customMachineId = 9000;
  102. const DEFAULT_GROUPS = [
  103. {
  104. name: "耕地(整地、施肥、撒肥、中耕等)",
  105. items: [
  106. { id: 101, name: "滴灌系统" },
  107. { id: 102, name: "喷灌设备" },
  108. { id: 103, name: "水肥一体机" },
  109. { id: 104, name: "水泵机组" },
  110. { id: 105, name: "施肥机" },
  111. { id: 106, name: "过滤设备" },
  112. ],
  113. },
  114. {
  115. name: "种(播种、育秧、移栽、补苗等)",
  116. items: [
  117. { id: 201, name: "拖拉机" },
  118. { id: 202, name: "旋耕机" },
  119. { id: 203, name: "开沟机" },
  120. { id: 204, name: "植保机" },
  121. { id: 205, name: "无人机" },
  122. { id: 206, name: "运输车" },
  123. ],
  124. },
  125. {
  126. name: "收(收割、烘干、运输等)",
  127. items: [
  128. { id: 301, name: "修剪机" },
  129. { id: 302, name: "采收机" },
  130. { id: 303, name: "割草机" },
  131. { id: 304, name: "粉碎机" },
  132. { id: 305, name: "发电机" },
  133. { id: 306, name: "其他设备" },
  134. ],
  135. },
  136. ];
  137. const FIELD_GROUPS = [
  138. {
  139. name: "耕地(整地、施肥、撒肥、中耕等)",
  140. items: [
  141. { id: 1001, name: "大中型拖拉机" },
  142. { id: 1002, name: "手扶拖拉机" },
  143. { id: 1003, name: "旋耕机" },
  144. { id: 1004, name: "微耕机" },
  145. { id: 1005, name: "挖掘机" },
  146. { id: 1006, name: "培土机" },
  147. { id: 1007, name: "撒肥机" },
  148. { id: 1008, name: "秸秆还田机" },
  149. { id: 1009, name: "开沟机" },
  150. { id: 1010, name: "水田打浆机" },
  151. { id: 1011, name: "深松机" },
  152. { id: 1012, name: "起垄机" },
  153. { id: 1013, name: "甘蔗开行机" },
  154. { id: 1014, name: "棉花拔杆机" },
  155. ],
  156. },
  157. {
  158. name: "种(播种、育秧、移栽、补苗等)",
  159. items: [
  160. { id: 2001, name: "插秧机" },
  161. { id: 2002, name: "一体育秧机" },
  162. { id: 2003, name: "播种机" },
  163. { id: 2004, name: "移栽机" },
  164. { id: 2005, name: "无人机播种" },
  165. { id: 2006, name: "小麦/玉米精量播种机" },
  166. { id: 2007, name: "免耕播种机" },
  167. { id: 2008, name: "花生覆膜播种机" },
  168. { id: 2009, name: "马铃薯播种机" },
  169. ],
  170. },
  171. {
  172. name: "收(收割、烘干、运输等)",
  173. items: [
  174. { id: 3001, name: "联合收割机" },
  175. { id: 3002, name: "半喂入收割机" },
  176. { id: 3003, name: "玉米联合收割机" },
  177. { id: 3004, name: "水稻收割机" },
  178. { id: 3005, name: "烘干机" },
  179. { id: 3006, name: "运粮车" },
  180. { id: 3007, name: "挂车" },
  181. ],
  182. },
  183. ];
  184. const FRUIT_GROUPS = [
  185. {
  186. name: "土壤与园地管理",
  187. desc: "(整地、除草、清园、开沟修渠等)",
  188. items: [
  189. { id: 4001, name: "旋耕机" },
  190. { id: 4002, name: "微耕机" },
  191. { id: 4003, name: "挖掘机" },
  192. { id: 4004, name: "旋耕开沟机" },
  193. { id: 4005, name: "割草机" },
  194. { id: 4006, name: "清沟机" },
  195. { id: 4007, name: "运输车" },
  196. { id: 4008, name: "开沟施肥机" },
  197. { id: 4009, name: "树盘管理机" },
  198. { id: 4010, name: "行间除草机" },
  199. { id: 4011, name: "生草播种机" },
  200. { id: 4012, name: "压草机" },
  201. ],
  202. },
  203. {
  204. name: "肥水管理",
  205. desc: "(施肥、灌水、灌药等)",
  206. items: [
  207. { id: 5001, name: "水肥一体化" },
  208. { id: 5002, name: "滴灌系统" },
  209. { id: 5003, name: "注肥/注药泵" },
  210. { id: 5004, name: "施肥枪" },
  211. { id: 5005, name: "移动水车" },
  212. { id: 5006, name: "水泵" },
  213. ],
  214. },
  215. {
  216. name: "植保打药",
  217. desc: "(冠层打药、内膛打药等)",
  218. items: [
  219. { id: 6001, name: "植保无人机" },
  220. { id: 6002, name: "风送式喷雾机" },
  221. { id: 6003, name: "背负式喷雾器" },
  222. { id: 6004, name: "柴油喷药机" },
  223. { id: 6005, name: "烟雾机" },
  224. { id: 6006, name: "高架喷雾机" },
  225. ],
  226. },
  227. ];
  228. const displayGroups = computed(() => {
  229. const keyword = (appliedKeyword.value || "").trim();
  230. if (!keyword) return equipmentGroups.value;
  231. return equipmentGroups.value
  232. .map((group) => ({
  233. ...group,
  234. items: group.items.filter((item) => item.name.includes(keyword)),
  235. }))
  236. .filter((group) => group.items.length);
  237. });
  238. const allEquipmentItems = computed(() =>
  239. equipmentGroups.value.flatMap((group) => group.items)
  240. );
  241. const pageTitle = computed(() =>
  242. props.isService ? "完善设备信息" : "请填写您的农场设备"
  243. );
  244. const pageSubtitle = computed(() =>
  245. props.isService ? "让农机调度更精准(可多选)" : "完善设备信息,让农机调度更精准"
  246. );
  247. const primaryBtnText = computed(() => {
  248. if (submitting.value) return "提交中...";
  249. return props.isLastStep ? "提交信息" : `下一步 (3/${props.isService ? props.totalSteps : 4})`;
  250. });
  251. function readJson(key) {
  252. try {
  253. const raw = sessionStorage.getItem(key);
  254. return raw ? JSON.parse(raw) : null;
  255. } catch {
  256. return null;
  257. }
  258. }
  259. function cloneGroups(groups) {
  260. return groups.map((group) => ({
  261. name: group.name,
  262. desc: group.desc || "",
  263. items: group.items.map((item) => ({ ...item })),
  264. }));
  265. }
  266. function flattenEquipment(cached) {
  267. if (!cached) return [];
  268. if (Array.isArray(cached)) return cached;
  269. return [...(cached.field || []), ...(cached.fruit || [])];
  270. }
  271. function getScopeList(cached, scope) {
  272. if (!cached) return [];
  273. if (Array.isArray(cached)) return scope === "fruit" ? [] : cached;
  274. return cached[scope] || [];
  275. }
  276. function getCurrentScope() {
  277. if (props.equipmentScope === "fruit") return "fruit";
  278. if (props.equipmentScope === "field") return "field";
  279. return "";
  280. }
  281. function restoreSelection() {
  282. const cached = readJson(EQUIPMENT_KEY);
  283. const scope = getCurrentScope();
  284. const list = scope ? getScopeList(cached, scope) : flattenEquipment(cached);
  285. list.forEach((item) => {
  286. if (item?.id == null) return;
  287. selectedIds.add(String(item.id));
  288. if (!item.custom) return;
  289. const exists = allEquipmentItems.value.some((row) => String(row.id) === String(item.id));
  290. if (exists) return;
  291. let group = equipmentGroups.value.find((row) => row.name === item.groupName);
  292. if (!group) group = equipmentGroups.value[equipmentGroups.value.length - 1];
  293. if (group) {
  294. group.items.push({ id: item.id, name: item.name, custom: true });
  295. }
  296. if (Number(item.id) >= customMachineId) customMachineId = Number(item.id);
  297. });
  298. }
  299. function saveSelectionDraft() {
  300. const scope = getCurrentScope();
  301. const selected = allEquipmentItems.value
  302. .filter((item) => selectedIds.has(String(item.id)))
  303. .map((item) => {
  304. const group = equipmentGroups.value.find((row) =>
  305. row.items.some((rowItem) => String(rowItem.id) === String(item.id))
  306. );
  307. return { ...item, scope: scope || "farmer", groupName: group?.name };
  308. });
  309. if (!props.isService) {
  310. sessionStorage.setItem(EQUIPMENT_KEY, JSON.stringify(selected));
  311. return;
  312. }
  313. const cached = readJson(EQUIPMENT_KEY);
  314. const next = {
  315. field: Array.isArray(cached) ? cached : cached?.field || [],
  316. fruit: Array.isArray(cached) ? [] : cached?.fruit || [],
  317. };
  318. if (scope === "fruit") next.fruit = selected;
  319. else next.field = selected;
  320. sessionStorage.setItem(EQUIPMENT_KEY, JSON.stringify(next));
  321. }
  322. function fetchEquipmentList() {
  323. loading.value = true;
  324. if (!props.isService) {
  325. equipmentGroups.value = cloneGroups(DEFAULT_GROUPS);
  326. } else if (props.equipmentScope === "fruit") {
  327. equipmentGroups.value = cloneGroups(FRUIT_GROUPS);
  328. } else {
  329. equipmentGroups.value = cloneGroups(FIELD_GROUPS);
  330. }
  331. loading.value = false;
  332. }
  333. const handleSearch = () => {
  334. appliedKeyword.value = searchKeyword.value;
  335. };
  336. const handleAddMachine = () => {
  337. showAddPopup.value = true;
  338. };
  339. const handleAddConfirm = ({ name }) => {
  340. const machineName = String(name || "").trim();
  341. if (!machineName) return;
  342. const CUSTOM_GROUP_NAME = "我的农机";
  343. // 已存在同名则直接选中
  344. const exists = allEquipmentItems.value.find((item) => item.name === machineName);
  345. if (exists) {
  346. selectedIds.add(String(exists.id));
  347. saveSelectionDraft();
  348. ElMessage.success("已选中该农机");
  349. return;
  350. }
  351. // 确保「自定义类」作为第一组存在
  352. let customGroup = equipmentGroups.value.find((group) => group.name === CUSTOM_GROUP_NAME);
  353. if (!customGroup) {
  354. customGroup = { name: CUSTOM_GROUP_NAME, items: [] };
  355. equipmentGroups.value.unshift(customGroup);
  356. } else {
  357. const index = equipmentGroups.value.indexOf(customGroup);
  358. if (index > 0) {
  359. equipmentGroups.value.splice(index, 1);
  360. equipmentGroups.value.unshift(customGroup);
  361. }
  362. }
  363. const newItem = {
  364. id: ++customMachineId,
  365. name: machineName,
  366. custom: true,
  367. };
  368. customGroup.items.push(newItem);
  369. selectedIds.add(String(newItem.id));
  370. saveSelectionDraft();
  371. ElMessage.success("添加成功");
  372. };
  373. const toggleSelect = (item) => {
  374. const id = String(item.id);
  375. if (selectedIds.has(id)) {
  376. selectedIds.delete(id);
  377. } else {
  378. selectedIds.add(id);
  379. }
  380. saveSelectionDraft();
  381. };
  382. function getVarietyDraftList() {
  383. const draft = readJson(SELECTED_LIST_KEY);
  384. if (Array.isArray(draft)) return draft;
  385. if (Array.isArray(draft?.list)) return draft.list;
  386. return [];
  387. }
  388. function getPhenophaseLabel(item) {
  389. return item.phenophase || item.phenologyName || String(item.phenologyId || "");
  390. }
  391. function buildPlotPayload(includeEquipment = true) {
  392. const baForm = readJson("ENTRY_BA_FORM") || {};
  393. const varietyList = getVarietyDraftList();
  394. const equipmentCache = readJson(EQUIPMENT_KEY);
  395. const equipmentList = includeEquipment ? flattenEquipment(equipmentCache) : [];
  396. const taskCache = readJson(TASK_KEY);
  397. const taskList = Array.isArray(taskCache)
  398. ? taskCache
  399. : [...(taskCache?.field || []), ...(taskCache?.fruit || [])];
  400. return {
  401. user_name: baForm.name,
  402. tel: baForm.phone,
  403. team_name: baForm.teamName || "",
  404. team_type: baForm.teamType || "",
  405. invite_type: baForm.inviteType || (props.isService ? "service" : "farmer"),
  406. crops: varietyList.map((item) => ({
  407. crop_type: item.categoryName,
  408. crop_id: Number(item.categoryId),
  409. variety_list: [Number(item.id)],
  410. phenophase: getPhenophaseLabel(item),
  411. start_time: item.startTime,
  412. plant_area: Number(item.area),
  413. point: item.location,
  414. })),
  415. farm_machines: equipmentList.map((item) => ({
  416. id: item.id,
  417. name: item.name,
  418. })),
  419. farm_tasks: taskList.map((item) => ({
  420. id: item.id,
  421. name: item.name,
  422. })),
  423. field_tasks: (taskCache?.field || []).map((item) => ({ id: item.id, name: item.name })),
  424. fruit_tasks: (taskCache?.fruit || []).map((item) => ({ id: item.id, name: item.name })),
  425. };
  426. }
  427. function clearEntrySession() {
  428. sessionStorage.removeItem(SELECTED_LIST_KEY);
  429. sessionStorage.removeItem(CATEGORY_SESSION_KEY);
  430. sessionStorage.removeItem("ENTRY_BA_FORM");
  431. sessionStorage.removeItem(LOCATION_KEY);
  432. sessionStorage.removeItem(EDIT_UID_KEY);
  433. sessionStorage.removeItem(EQUIPMENT_KEY);
  434. sessionStorage.removeItem(TASK_KEY);
  435. sessionStorage.removeItem("ENTRY_MANUAL_VIEW");
  436. sessionStorage.removeItem(STEP_KEY);
  437. sessionStorage.removeItem("ENTRY_INVITE_TYPE");
  438. }
  439. const showSuccessPopup = ref(false);
  440. async function submitEntry(includeEquipment = true) {
  441. if (submitting.value) return;
  442. const varietyList = getVarietyDraftList();
  443. if (!props.isService && !varietyList.length) {
  444. ElMessage.warning("请先完善种植品种信息");
  445. return;
  446. }
  447. const baForm = readJson("ENTRY_BA_FORM");
  448. if (!baForm?.name || !baForm?.phone) {
  449. ElMessage.warning("请先完善个人信息");
  450. return;
  451. }
  452. submitting.value = true;
  453. try {
  454. const params = buildPlotPayload(includeEquipment);
  455. console.log("entry submit params", params);
  456. // const res = await VE_API.entry.addPlotInfo(params);
  457. // if (res.code === 200) {
  458. // ElMessage.success(res.msg || "提交成功");
  459. // clearEntrySession();
  460. // emit("confirm", params);
  461. // } else {
  462. // ElMessage.error(res.msg || "提交失败,请稍后再试");
  463. // }
  464. showSuccessPopup.value = true;
  465. clearEntrySession();
  466. } catch (error) {
  467. console.error("entry submit failed", error);
  468. ElMessage.error("提交失败");
  469. } finally {
  470. submitting.value = false;
  471. }
  472. }
  473. const handleComplete = () => {
  474. showSuccessPopup.value = false;
  475. emit("confirm");
  476. };
  477. const handleSkip = () => {
  478. if (!props.isLastStep) {
  479. emit("next");
  480. return;
  481. }
  482. submitEntry(false);
  483. };
  484. const handleSubmit = () => {
  485. saveSelectionDraft();
  486. if (!props.isLastStep) {
  487. emit("next");
  488. return;
  489. }
  490. submitEntry(true);
  491. };
  492. onMounted(() => {
  493. fetchEquipmentList();
  494. restoreSelection();
  495. });
  496. </script>
  497. <style lang="scss" scoped>
  498. .select-equipment {
  499. flex: 1;
  500. min-height: 0;
  501. display: flex;
  502. flex-direction: column;
  503. overflow: hidden;
  504. &__content {
  505. flex: 1;
  506. min-height: 0;
  507. overflow-y: auto;
  508. -webkit-overflow-scrolling: touch;
  509. padding: 8px 16px 80px;
  510. }
  511. .page-header {
  512. padding: 8px 4px 16px;
  513. .page-title {
  514. font-size: 26px;
  515. color: #005599;
  516. font-family: "PangMenZhengDao";
  517. line-height: 36px;
  518. }
  519. .page-subtitle {
  520. margin-top: 4px;
  521. font-size: 14px;
  522. color: rgba(46, 46, 46, 0.4);
  523. line-height: 20px;
  524. }
  525. .text-tip {
  526. font-size: 20px;
  527. }
  528. }
  529. .toolbar {
  530. display: flex;
  531. align-items: center;
  532. gap: 10px;
  533. margin-bottom: 12px;
  534. }
  535. .search-bar {
  536. flex: 1;
  537. min-width: 0;
  538. display: flex;
  539. align-items: center;
  540. height: 36px;
  541. padding: 0 12px;
  542. background: rgba(255, 255, 255, 0.5);
  543. border: 1px solid rgba(33, 153, 248, 0.5);
  544. border-radius: 6px;
  545. box-sizing: border-box;
  546. .search-icon {
  547. color: rgba(0, 0, 0, 0.35);
  548. font-size: 16px;
  549. margin-right: 6px;
  550. flex-shrink: 0;
  551. }
  552. .search-input {
  553. flex: 1;
  554. min-width: 0;
  555. border: none;
  556. outline: none;
  557. background: transparent;
  558. font-size: 14px;
  559. color: #333;
  560. &::placeholder {
  561. color: rgba(0, 0, 0, 0.3);
  562. }
  563. }
  564. .search-btn {
  565. flex-shrink: 0;
  566. padding-left: 10px;
  567. color: #2199f8;
  568. font-size: 14px;
  569. cursor: pointer;
  570. }
  571. }
  572. .add-btn {
  573. flex-shrink: 0;
  574. height: 36px;
  575. padding: 0 12px;
  576. border-radius: 6px;
  577. background: #2199f8;
  578. color: #fff;
  579. font-size: 14px;
  580. line-height: 36px;
  581. white-space: nowrap;
  582. cursor: pointer;
  583. }
  584. .equipment-card {
  585. background: #fff;
  586. border-radius: 12px;
  587. padding: 14px 12px 16px;
  588. margin-bottom: 12px;
  589. }
  590. .section-title {
  591. display: flex;
  592. align-items: center;
  593. gap: 8px;
  594. margin-bottom: 4px;
  595. font-size: 16px;
  596. font-weight: 500;
  597. color: #1a1a1a;
  598. .title-icon {
  599. width: 15px;
  600. height: 15px;
  601. object-fit: contain;
  602. flex-shrink: 0;
  603. }
  604. }
  605. .section-desc {
  606. margin: -2px 0 2px 22px;
  607. font-size: 12px;
  608. color: rgba(0, 0, 0, 0.4);
  609. line-height: 18px;
  610. }
  611. .tag-group {
  612. display: grid;
  613. grid-template-columns: repeat(3, 1fr);
  614. gap: 0 8px;
  615. font-size: 14px;
  616. .tag-item {
  617. margin-top: 10px;
  618. position: relative;
  619. border-radius: 6px;
  620. box-sizing: border-box;
  621. height: 36px;
  622. text-align: center;
  623. line-height: 36px;
  624. cursor: pointer;
  625. color: #000;
  626. background: #f3f4f6;
  627. border: 1px solid transparent;
  628. .text {
  629. display: inline-block;
  630. max-width: 100%;
  631. overflow: hidden;
  632. text-overflow: ellipsis;
  633. white-space: nowrap;
  634. vertical-align: top;
  635. padding: 0 6px;
  636. }
  637. &.selected {
  638. border: 1px solid #2199f8;
  639. background: #e8f5ff;
  640. color: #2199f8;
  641. &::after {
  642. content: "";
  643. position: absolute;
  644. z-index: 9;
  645. top: -1px;
  646. right: -1px;
  647. width: 18px;
  648. height: 14px;
  649. background: url("@/assets/img/home/checked-bg-top.png") no-repeat bottom right / 18px 13px;
  650. }
  651. }
  652. }
  653. }
  654. .empty-tip {
  655. padding: 40px 0;
  656. text-align: center;
  657. color: rgba(0, 0, 0, 0.35);
  658. font-size: 14px;
  659. }
  660. .custom-bottom-fixed-btns {
  661. display: flex;
  662. justify-content: space-between;
  663. align-items: baseline;
  664. gap: 10px;
  665. padding: 12px 12px 0;
  666. height: 80px;
  667. background: #fff;
  668. box-sizing: border-box;
  669. box-shadow: 2px 2px 5px 0 rgba(0, 0, 0, 0.4);
  670. .btns-l {
  671. display: flex;
  672. align-items: center;
  673. gap: 10px;
  674. }
  675. .bottom-btn {
  676. padding: 0 30px;
  677. height: 40px;
  678. line-height: 40px;
  679. box-sizing: border-box;
  680. font-size: 14px;
  681. border-radius: 25px;
  682. text-align: center;
  683. white-space: nowrap;
  684. &.disabled {
  685. opacity: 0.6;
  686. pointer-events: none;
  687. }
  688. }
  689. .secondary-btn {
  690. padding: 0 26px;
  691. color: #2199f8;
  692. background: #fff;
  693. border: 1px solid #2199f8;
  694. }
  695. .primary-btn {
  696. background: #2199f8;
  697. color: #fff;
  698. }
  699. }
  700. &.is-service-equipment {
  701. .page-subtitle {
  702. font-size: 16px;
  703. font-weight: 600;
  704. color: #005599;
  705. line-height: 22px;
  706. }
  707. .tag-item {
  708. height: auto;
  709. min-height: 36px;
  710. padding: 6px 4px;
  711. display: flex;
  712. align-items: center;
  713. justify-content: center;
  714. line-height: 16px;
  715. .text {
  716. display: block;
  717. white-space: normal;
  718. overflow: visible;
  719. text-overflow: unset;
  720. font-size: 13px;
  721. line-height: 16px;
  722. padding: 0 4px;
  723. word-break: break-all;
  724. }
  725. }
  726. }
  727. }
  728. </style>