landUseLegend.vue 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <template>
  2. <div class="land-use-legend yes-events">
  3. <div
  4. v-for="(category, index) in legendData"
  5. :key="index"
  6. class="legend-category"
  7. >
  8. <!-- 父级:可勾选 -->
  9. <div class="category-header" @click="toggleExpand(category)">
  10. <el-icon class="expand-icon" :class="{ expanded: category.expanded }"><CaretRight /></el-icon>
  11. <el-checkbox
  12. v-model="category.checked"
  13. @change="handleCategoryChange(category)"
  14. @click.stop
  15. class="category-checkbox"
  16. />
  17. <span class="category-name">{{ category.name }}</span>
  18. </div>
  19. <!-- 子级:仅显示 -->
  20. <div v-show="category.expanded" class="category-children">
  21. <div
  22. v-for="(child, childIndex) in category.children"
  23. :key="childIndex"
  24. class="child-item"
  25. >
  26. <span class="color-dot" :style="{ backgroundColor: child.color }"></span>
  27. <span class="child-name">{{ child.name }}</span>
  28. </div>
  29. </div>
  30. </div>
  31. </div>
  32. </template>
  33. <script setup>
  34. import { ref, onMounted } from "vue";
  35. import eventBus from "@/api/eventBus";
  36. const props = defineProps({
  37. data: {
  38. type: Array,
  39. default: () => [],
  40. },
  41. });
  42. const emit = defineEmits(["change"]);
  43. // 默认数据(示例数据,可根据实际需求修改)
  44. const defaultData = [
  45. {
  46. name: "作物类",
  47. checked: true,
  48. expanded: true,
  49. children: [
  50. { name: "小麦", color: "#2199F8" },
  51. { name: "油菜", color: "#FFA500" },
  52. ],
  53. },
  54. {
  55. name: "非农化非粮化类",
  56. checked: false,
  57. expanded: true,
  58. children: [
  59. { name: "撂荒", color: "#00CED1" },
  60. { name: "林木", color: "#FF69B4" },
  61. { name: "果树", color: "#90EE90" },
  62. { name: "坑塘", color: "#008080" },
  63. { name: "建设用地", color: "#FFA500" },
  64. ],
  65. },
  66. {
  67. name: "耕地类",
  68. checked: false,
  69. expanded: false,
  70. children: [
  71. { name: "水田", color: "#4169E1" },
  72. { name: "旱地", color: "#8B4513" },
  73. { name: "水浇地", color: "#228B22" },
  74. ],
  75. },
  76. ];
  77. const legendData = ref([]);
  78. // 初始化数据
  79. const initData = () => {
  80. if (props.data && props.data.length > 0) {
  81. legendData.value = props.data.map((item) => ({
  82. ...item,
  83. expanded: item.expanded !== undefined ? item.expanded : false,
  84. checked: item.checked !== undefined ? item.checked : false,
  85. }));
  86. } else {
  87. legendData.value = defaultData.map((item) => ({ ...item }));
  88. }
  89. };
  90. const fetchLandUseTypes = () => {
  91. VE_API.warning.fetchLandUseTypes().then((res) => {
  92. if (res.code === 0 && res.data && res.data.length > 0) {
  93. // 处理接口返回的数据,确保格式正确
  94. // 默认第一个勾选,全部展开
  95. let processedData = res.data.map((item, index) => ({
  96. ...item,
  97. // 第一个默认勾选,其他不勾选
  98. checked: index === 0,
  99. // 全部默认展开
  100. expanded: true,
  101. }));
  102. legendData.value = processedData;
  103. // 如果第一个默认勾选,触发 change 事件
  104. if (processedData.length > 0 && processedData[0].checked) {
  105. const firstCategory = processedData[0];
  106. emit("change", {
  107. category: firstCategory.name,
  108. checked: true,
  109. children: firstCategory.children || [],
  110. nonGrain: firstCategory.id || firstCategory.nonGrain || null,
  111. });
  112. // 通知地图更新
  113. eventBus.emit("chartList:updateMap", {
  114. statType: firstCategory.id || firstCategory.nonGrain || null,
  115. });
  116. }
  117. } else {
  118. // 如果接口返回失败,使用默认数据
  119. // 默认第一个勾选,全部展开
  120. const defaultProcessed = defaultData.map((item, index) => ({
  121. ...item,
  122. checked: index === 0,
  123. expanded: true,
  124. }));
  125. legendData.value = defaultProcessed;
  126. // 如果第一个默认勾选,触发 change 事件
  127. if (defaultProcessed.length > 0 && defaultProcessed[0].checked) {
  128. const firstCategory = defaultProcessed[0];
  129. emit("change", {
  130. category: firstCategory.name,
  131. checked: true,
  132. children: firstCategory.children || [],
  133. nonGrain: firstCategory.id || firstCategory.nonGrain || null,
  134. });
  135. // 通知地图更新
  136. eventBus.emit("chartList:updateMap", {
  137. statType: firstCategory.id || firstCategory.nonGrain || null,
  138. });
  139. }
  140. }
  141. }).catch((error) => {
  142. console.error("获取土地利用类型失败:", error);
  143. // 接口失败时使用默认数据
  144. // 默认第一个勾选,全部展开
  145. const defaultProcessed = defaultData.map((item, index) => ({
  146. ...item,
  147. checked: index === 0,
  148. expanded: true,
  149. }));
  150. legendData.value = defaultProcessed;
  151. // 如果第一个默认勾选,触发 change 事件
  152. if (defaultProcessed.length > 0 && defaultProcessed[0].checked) {
  153. const firstCategory = defaultProcessed[0];
  154. emit("change", {
  155. category: firstCategory.name,
  156. checked: true,
  157. children: firstCategory.children || [],
  158. nonGrain: firstCategory.id || firstCategory.nonGrain || null,
  159. });
  160. // 通知地图更新
  161. eventBus.emit("chartList:updateMap", {
  162. statType: firstCategory.id || firstCategory.nonGrain || null,
  163. });
  164. }
  165. });
  166. };
  167. onMounted(() => {
  168. fetchLandUseTypes();
  169. });
  170. // 切换展开/折叠
  171. const toggleExpand = (category) => {
  172. category.expanded = !category.expanded;
  173. };
  174. // 处理父级勾选变化(单选逻辑)
  175. const handleCategoryChange = (category) => {
  176. // 计算当前有多少个被选中的项(由于 v-model 已经更新了 category.checked,需要检查其他项)
  177. const otherCheckedCount = legendData.value.filter((item) => item !== category && item.checked).length;
  178. // 如果用户尝试取消,且没有其他项被选中,阻止这个操作(确保至少有一个被选中)
  179. if (!category.checked && otherCheckedCount === 0) {
  180. // 阻止取消,保持选中状态
  181. category.checked = true;
  182. return;
  183. }
  184. // 如果当前类别被选中,取消其他所有类别的选中状态
  185. if (category.checked) {
  186. legendData.value.forEach((item) => {
  187. if (item !== category) {
  188. item.checked = false;
  189. }
  190. });
  191. }
  192. const nonGrainValue = category.id || category.nonGrain || null;
  193. emit("change", {
  194. category: category.name,
  195. checked: category.checked,
  196. children: category.children,
  197. nonGrain: nonGrainValue, // 传递 nonGrain
  198. });
  199. // 通知地图更新,传递 statType 参数
  200. if (category.checked) {
  201. eventBus.emit("chartList:updateMap", {
  202. statType: nonGrainValue,
  203. });
  204. }
  205. };
  206. </script>
  207. <style lang="scss" scoped>
  208. .land-use-legend {
  209. position: fixed;
  210. right: 420px;
  211. top: 90px;
  212. background: rgba(29, 29, 29, 0.86);
  213. border: 1px solid rgba(255, 212, 137, 0.3);
  214. border-radius: 8px;
  215. padding: 16px;
  216. min-width: 170px;
  217. max-height: 80vh;
  218. z-index: 10;
  219. color: #fff;
  220. .legend-category {
  221. margin-bottom: 6px;
  222. &:last-child {
  223. margin-bottom: 0;
  224. }
  225. }
  226. .category-header {
  227. display: flex;
  228. align-items: center;
  229. cursor: pointer;
  230. .category-checkbox {
  231. margin-right: 8px;
  232. ::v-deep {
  233. .el-checkbox__inner {
  234. background-color: rgba(255, 212, 137, 0.3);
  235. border-color: rgba(255, 212, 137, 0.6);
  236. width: 16px;
  237. height: 16px;
  238. }
  239. .el-checkbox__input.is-checked .el-checkbox__inner {
  240. background-color: #f7be5a;
  241. border-color: #f7be5a;
  242. &::after {
  243. border-color: #000;
  244. border-width: 2px;
  245. left: 5px;
  246. top: 1px;
  247. transform: rotate(45deg) scaleY(1);
  248. }
  249. }
  250. }
  251. }
  252. .expand-icon {
  253. margin-right: 8px;
  254. display: inline-block;
  255. transition: transform 0.3s;
  256. color: rgba(255, 212, 137, 0.6);
  257. font-size: 18px;
  258. text-align: center;
  259. line-height: 1;
  260. &.expanded {
  261. transform: rotate(90deg);
  262. }
  263. }
  264. .category-name {
  265. font-size: 14px;
  266. color: rgba(255, 212, 137, 0.9);
  267. }
  268. }
  269. .category-children {
  270. margin-left: 47px;
  271. padding-left: 0;
  272. .child-item {
  273. display: flex;
  274. align-items: center;
  275. padding: 5px 0;
  276. font-size: 14px;
  277. color: rgba(255, 255, 255, 0.8);
  278. .color-dot {
  279. width: 12px;
  280. height: 12px;
  281. margin-right: 6px;
  282. border-radius: 2px;
  283. }
  284. }
  285. }
  286. }
  287. </style>