left-bottom.vue 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. <template>
  2. <div class="left_bottom">
  3. <div class="left_bottom-title flex">
  4. <div class="flex items-center">
  5. <img src="@/assets/img/home/title_arrow.png" alt="" />
  6. 预警热点
  7. </div>
  8. <el-dropdown @command="handleCommand">
  9. <el-button link class="link-button">
  10. {{ areaText }}
  11. <el-icon class="el-icon--right">
  12. <arrow-down />
  13. </el-icon>
  14. </el-button>
  15. <template #dropdown>
  16. <el-dropdown-menu>
  17. <el-dropdown-item
  18. v-for="item in areaList"
  19. :key="item.id"
  20. :command="item"
  21. >{{ item.areaName }}</el-dropdown-item
  22. >
  23. </el-dropdown-menu>
  24. </template>
  25. </el-dropdown>
  26. </div>
  27. <div class="left_bottom-content">
  28. <template v-if="list.length">
  29. <v-chart
  30. class="chart"
  31. :option="option"
  32. :loading="loading"
  33. :loading-options="loadingOptions"
  34. />
  35. </template>
  36. <template v-else>
  37. <EmptyCom></EmptyCom>
  38. </template>
  39. </div>
  40. </div>
  41. </template>
  42. <script setup lang="ts">
  43. import { onMounted, ref, watch } from "vue";
  44. import { ArrowDown } from "@element-plus/icons-vue";
  45. import { getArea, hotSpot } from "@/api/home";
  46. import dayjs from "dayjs";
  47. import EmptyCom from "@/components/empty-com";
  48. import {
  49. getCurrentCityCode,
  50. getCurrentCityName,
  51. loadingOptions,
  52. } from "@/utils/constants";
  53. import { graphic } from "echarts/core";
  54. import { arraySortByKey } from "@/utils/tools";
  55. const props = defineProps({
  56. dateArray: {
  57. type: Array,
  58. default: () => [],
  59. },
  60. });
  61. const date = ref([]);
  62. const loading = ref(false);
  63. const option = ref<any>({});
  64. watch(
  65. () => props.dateArray,
  66. (val: any) => {
  67. date.value = val;
  68. },
  69. { immediate: true }
  70. );
  71. watch(
  72. () => props.dateArray,
  73. () => {
  74. getData();
  75. }
  76. );
  77. const areaCode = ref(getCurrentCityCode());
  78. const areaText = ref(getCurrentCityName());
  79. const list = ref<any>([]);
  80. const getData = async () => {
  81. loading.value = true;
  82. try {
  83. const { result } = await hotSpot({
  84. StartTime: dayjs(date.value[0]).format("YYYY-MM-DD"),
  85. EndTime: dayjs(date.value[1]).format("YYYY-MM-DD"),
  86. AreaCode: areaCode.value,
  87. });
  88. list.value = result;
  89. /*
  90. list.value.push({
  91. hotspotId: "1925",
  92. hotspotName: "不按规定的内容和方式明码标价",
  93. sumCount: 20,
  94. hotspotSpliceName: "市场管理-物价-价格违法违规行为-不执行法定的价格干预措施、紧急措施-不按规定的内容和方式明码标价"
  95. })
  96. */
  97. const category = arraySortByKey(result, "sumCount");
  98. const charts = {
  99. // 按顺序排列从大到小
  100. cityList: category.map((item: any) => item.hotspotName),
  101. cityData: category.map((item: any) => item.sumCount),
  102. };
  103. const top10CityList = charts.cityList;
  104. const top10CityData = charts.cityData;
  105. const color = ["#ff9500", "#02d8f9", "#027fff"];
  106. const color1 = ["#ffb349", "#70e9fc", "#4aa4ff"];
  107. let lineY = [];
  108. let lineT = [];
  109. for (let i = 0; i < charts.cityList.length; i++) {
  110. let x = i;
  111. if (x > 1) {
  112. x = 2;
  113. }
  114. const data = {
  115. name: charts.cityList[i],
  116. color: color[x],
  117. value: top10CityData[i],
  118. barGap: "-100%",
  119. itemStyle: {
  120. show: true,
  121. color: new graphic.LinearGradient(
  122. 0,
  123. 0,
  124. 1,
  125. 0,
  126. [
  127. {
  128. offset: 0,
  129. color: color[x],
  130. },
  131. {
  132. offset: 1,
  133. color: color1[x],
  134. },
  135. ],
  136. false
  137. ),
  138. borderRadius: 10,
  139. },
  140. emphasis: {
  141. itemStyle: {
  142. shadowBlur: 15,
  143. shadowColor: "rgba(0, 0, 0, 0.1)",
  144. },
  145. },
  146. };
  147. const data1 = {
  148. value: top10CityData[0],
  149. itemStyle: {
  150. color: "#001235",
  151. borderRadius: 10,
  152. },
  153. };
  154. lineY.push(data);
  155. lineT.push(data1);
  156. }
  157. option.value = {
  158. backgroundColor: "rgba(0, 0, 0, 0)",
  159. title: {
  160. show: false,
  161. },
  162. tooltip: {
  163. trigger: "axis",
  164. axisPointer: {
  165. type: "shadow",
  166. },
  167. formatter: function (params: any) {
  168. let tar;
  169. if (params[0].seriesIndex === 0) {
  170. tar = params[0];
  171. } else {
  172. tar = params[1];
  173. }
  174. return tar.marker + tar.name + "<br/>" + " 当前数量: " + tar.value;
  175. },
  176. enterable: true, //滚动条
  177. confine: true,
  178. extraCssText: "max-width:90%;max-height:83%;overflow:auto;",
  179. //改变提示框的位置 不超出屏幕显示
  180. position: function (point, params, dom, rect, size) {
  181. //其中point为当前鼠标的位置,
  182. //size中有两个属性:viewSize和contentSize,分别为外层div和tooltip提示框的大小
  183. // 鼠标坐标和提示框位置的参考坐标系是:以外层div的左上角那一点为原点,x轴向右,y轴向下
  184. // 提示框位置
  185. let x = 0; // x坐标位置
  186. let y = 0; // y坐标位置
  187. // 当前鼠标位置
  188. const pointX = point[0];
  189. const pointY = point[1];
  190. // 提示框大小
  191. const boxWidth = size.contentSize[0];
  192. const boxHeight = size.contentSize[1];
  193. // boxWidth > pointX 说明鼠标左边放不下提示框
  194. if (boxWidth > pointX) {
  195. x = 5;
  196. } else {
  197. // 左边放的下
  198. x = pointX - boxWidth;
  199. }
  200. // boxHeight > pointY 说明鼠标上边放不下提示框
  201. if (boxHeight > pointY) {
  202. y = 5;
  203. } else {
  204. // 上边放得下
  205. y = pointY - boxHeight;
  206. }
  207. return [x, y];
  208. },
  209. },
  210. grid: {
  211. borderWidth: 0,
  212. top: "5%",
  213. left: "5%",
  214. right: "15%",
  215. bottom: "10%",
  216. },
  217. color: color,
  218. yAxis: [
  219. {
  220. type: "category",
  221. inverse: true,
  222. axisTick: {
  223. show: false,
  224. },
  225. axisLine: {
  226. show: false,
  227. },
  228. axisLabel: {
  229. show: false,
  230. inside: false,
  231. },
  232. data: top10CityList,
  233. },
  234. {
  235. type: "category",
  236. inverse: true,
  237. axisLine: {
  238. show: false,
  239. },
  240. axisTick: {
  241. show: false,
  242. },
  243. axisLabel: {
  244. show: true,
  245. inside: false,
  246. verticalAlign: "middle",
  247. lineHeight: "40",
  248. color: "#fff",
  249. fontSize: "14",
  250. fontFamily: "PingFangSC-Regular",
  251. formatter: function (val) {
  252. return `${val}`;
  253. },
  254. },
  255. splitArea: {
  256. show: false,
  257. },
  258. splitLine: {
  259. show: false,
  260. },
  261. data: top10CityData,
  262. },
  263. ],
  264. xAxis: {
  265. type: "value",
  266. axisTick: {
  267. show: false,
  268. },
  269. axisLine: {
  270. show: false,
  271. },
  272. splitLine: {
  273. show: false,
  274. },
  275. axisLabel: {
  276. show: false,
  277. },
  278. },
  279. series: [
  280. {
  281. type: "bar",
  282. zLevel: 2,
  283. barWidth: "10px",
  284. data: lineY,
  285. label: {
  286. color: "#b3ccf8",
  287. show: true,
  288. position: [0, "-18px"],
  289. fontSize: 16,
  290. width: 40,
  291. formatter: function (a) {
  292. let num = "";
  293. let str = "";
  294. if (a.dataIndex + 1 < 10) {
  295. num = "0" + (a.dataIndex + 1);
  296. } else {
  297. num = a.dataIndex + 1;
  298. }
  299. let names = "";
  300. if (a.name.length > 25) {
  301. names = a.name.slice(0, 25) + "...";
  302. } else {
  303. names = a.name;
  304. }
  305. if (a.dataIndex === 0) {
  306. str = `{color1|${num}} {color4|${names}}`;
  307. } else if (a.dataIndex === 1) {
  308. str = `{color2|${num}} {color4|${names}}`;
  309. } else {
  310. str = `{color3|${num}} {color4|${names}}`;
  311. }
  312. return str;
  313. },
  314. rich: {
  315. color1: {
  316. color: "#ff9500",
  317. },
  318. color2: {
  319. color: "#02d8f9",
  320. },
  321. color3: {
  322. color: "#027fff",
  323. },
  324. color4: {
  325. color: "#e5eaff",
  326. },
  327. },
  328. },
  329. },
  330. ],
  331. };
  332. loading.value = false;
  333. } catch (e) {
  334. console.log(e);
  335. loading.value = false;
  336. }
  337. };
  338. // 选择省市区
  339. const handleCommand = (command: any) => {
  340. areaCode.value = command.id;
  341. areaText.value = command.areaName;
  342. getData();
  343. };
  344. const areaList = ref([]);
  345. const getAreaData = async () => {
  346. try {
  347. const res = await getArea();
  348. areaList.value = res.result ?? [];
  349. } catch (e) {
  350. console.log(e);
  351. }
  352. };
  353. onMounted(() => {
  354. getData();
  355. getAreaData();
  356. });
  357. </script>
  358. <style scoped lang="scss">
  359. .left_bottom {
  360. padding: 0 30px;
  361. &-title {
  362. font-size: 20px;
  363. color: #fff;
  364. justify-content: space-between;
  365. align-items: center;
  366. }
  367. &-content {
  368. height: calc(100% - 30px);
  369. }
  370. }
  371. :deep(.link-button) {
  372. cursor: pointer;
  373. color: #7dbdec;
  374. display: flex;
  375. align-items: center;
  376. }
  377. </style>