index.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. <template>
  2. <div class="content">
  3. <div style="background-color: white; padding: 20px">
  4. <el-form :inline="true" :model="queryForm">
  5. <el-form-item label="国家">
  6. <el-select
  7. v-model="queryForm.countryId"
  8. placeholder="请选择"
  9. clearable
  10. filterable
  11. @change="onQuery"
  12. >
  13. <el-option
  14. v-for="item in countryData"
  15. :label="item.name"
  16. :value="item.id"
  17. :key="item.id"
  18. >
  19. </el-option>
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="日期">
  23. <el-date-picker
  24. v-model="queryForm.timeArr"
  25. type="daterange"
  26. unlink-panels
  27. range-separator="-"
  28. start-placeholder="开始日期"
  29. end-placeholder="结束日期"
  30. value-format="YYYY-MM-DD"
  31. @change="onQuery"
  32. />
  33. </el-form-item>
  34. <el-form-item>
  35. <el-button type="primary" @click="onQuery">搜索</el-button>
  36. </el-form-item>
  37. </el-form>
  38. </div>
  39. <el-row style="margin-top: 20px" type="flex">
  40. <el-col :span="15">
  41. <div
  42. style="
  43. margin-right: 20px;
  44. background-color: white;
  45. padding: 20px 0 0 20px;
  46. height: 100%;
  47. "
  48. >
  49. <TitleInfo :content="'产品统计'"></TitleInfo>
  50. <div
  51. style="padding-top: 20px; display: flex; flex-wrap: wrap"
  52. v-loading="loadingOne"
  53. >
  54. <div style="width: 33%; padding: 0 20px 20px 0">
  55. <div
  56. style="
  57. padding: 20px;
  58. background-color: #f4f4f5;
  59. border-radius: 5px;
  60. font-size: 12px !important;
  61. color: #909399 !important;
  62. "
  63. >
  64. <div style="margin-bottom: 20px; display: flex">
  65. <div style="background: #0084ff; padding: 5px">
  66. <img
  67. style="width: 20px; height: 20px; border-radius: 5px"
  68. src="@/assets/images/portrait/iconm_kehd.png"
  69. alt=""
  70. />
  71. </div>
  72. <div
  73. style="
  74. margin-left: 20px;
  75. height: 30px;
  76. line-height: 30px;
  77. font-weight: 700;
  78. "
  79. >
  80. 总计
  81. </div>
  82. </div>
  83. <div style="display: flex">
  84. <div style="width: 50%">
  85. <span>新增 (款)</span>
  86. <span
  87. style="color: black; font-weight: 700; margin-left: 20px"
  88. >{{ allData.productStatistics.newTotal }}</span
  89. >
  90. </div>
  91. <div style="width: 50%">
  92. <span>总计 (款)</span>
  93. <span
  94. style="color: black; font-weight: 700; margin-left: 20px"
  95. >{{ allData.productStatistics.total }}</span
  96. >
  97. </div>
  98. </div>
  99. </div>
  100. </div>
  101. <template v-if="productType && productType.length > 0">
  102. <div
  103. style="width: 33%; padding: 0 20px 20px 0"
  104. v-for="(item, index) in productType"
  105. :key="index"
  106. >
  107. <div
  108. style="
  109. padding: 20px;
  110. background-color: #f4f4f5;
  111. border-radius: 5px;
  112. font-size: 12px !important;
  113. color: #909399 !important;
  114. "
  115. >
  116. <div style="margin-bottom: 20px; display: flex">
  117. <div style="background: #0084ff; padding: 5px">
  118. <img
  119. style="width: 20px; height: 20px; border-radius: 5px"
  120. src="@/assets/images/portrait/iconm_kehd.png"
  121. alt=""
  122. />
  123. </div>
  124. <div
  125. style="
  126. margin-left: 20px;
  127. height: 30px;
  128. line-height: 30px;
  129. font-weight: 700;
  130. "
  131. >
  132. {{ item.label }}
  133. </div>
  134. </div>
  135. <div style="display: flex">
  136. <div style="width: 50%">
  137. <span>新增 (款)</span>
  138. <span
  139. style="
  140. color: black;
  141. font-weight: 700;
  142. margin-left: 20px;
  143. "
  144. >{{ getNum(item, "typeNewTotal") }}</span
  145. >
  146. </div>
  147. <div style="width: 50%">
  148. <span>总计 (款)</span>
  149. <span
  150. style="
  151. color: black;
  152. font-weight: 700;
  153. margin-left: 20px;
  154. "
  155. >{{ getNum(item, "typeTotal") }}</span
  156. >
  157. </div>
  158. </div>
  159. </div>
  160. </div>
  161. </template>
  162. </div>
  163. </div>
  164. </el-col>
  165. <el-col :span="9">
  166. <div style="background-color: white; padding: 20px; height: 100%">
  167. <TitleInfo :content="'产品分布'"></TitleInfo>
  168. <div ref="mainOne" style="height: calc(100% - 24px)"></div>
  169. </div>
  170. </el-col>
  171. </el-row>
  172. <el-row style="margin-top: 20px" type="flex">
  173. <el-col :span="12">
  174. <div
  175. style="
  176. margin-right: 20px;
  177. background-color: white;
  178. padding: 20px;
  179. height: 100%;
  180. "
  181. >
  182. <TitleInfo :content="'产品分类排行'"></TitleInfo>
  183. <div style="padding-top: 20px" v-loading="loadingTwo">
  184. <el-table
  185. :data="allData.productTypeRanking"
  186. :default-sort="{ prop: 'contractQuantity', order: 'descending' }"
  187. style="width: 100%"
  188. max-height="30vh"
  189. @sort-change="sortChangeTwo"
  190. >
  191. <el-table-column type="index" width="50" />
  192. <el-table-column label="产品类型" prop="name" min-width="140" />
  193. <el-table-column
  194. label="销售量"
  195. prop="contractQuantity"
  196. sortable
  197. width="120"
  198. />
  199. <el-table-column
  200. label="销售额"
  201. prop="contractAmount"
  202. sortable
  203. width="120"
  204. />
  205. <el-table-column
  206. label="采购量"
  207. prop="purchaseQuantity"
  208. sortable
  209. width="120"
  210. />
  211. <el-table-column
  212. label="采购额"
  213. prop="purchaseAmount"
  214. sortable
  215. width="120"
  216. />
  217. <!-- <el-table-column label="生产总量" prop="name" sortable width="120" /> -->
  218. </el-table>
  219. </div>
  220. </div>
  221. </el-col>
  222. <el-col :span="12">
  223. <div style="background-color: white; padding: 20px; height: 100%">
  224. <TitleInfo :content="'产品排行'"></TitleInfo>
  225. <div style="padding-top: 20px" v-loading="loadingThree">
  226. <el-table
  227. :data="allData.productRanking"
  228. :default-sort="{ prop: 'contractQuantity', order: 'descending' }"
  229. style="width: 100%"
  230. border
  231. max-height="30vh"
  232. @sort-change="sortChangeThree"
  233. >
  234. <el-table-column type="index" width="50" />
  235. <el-table-column label="产品名称" prop="name" min-width="140" />
  236. <el-table-column
  237. label="销售量"
  238. prop="contractQuantity"
  239. sortable
  240. width="120"
  241. />
  242. <el-table-column
  243. label="销售额"
  244. prop="contractAmount"
  245. sortable
  246. width="120"
  247. />
  248. <el-table-column
  249. label="采购量"
  250. prop="purchaseQuantity"
  251. sortable
  252. width="120"
  253. />
  254. <el-table-column
  255. label="采购额"
  256. prop="purchaseAmount"
  257. sortable
  258. width="120"
  259. />
  260. <!-- <el-table-column label="生产总量" prop="name" sortable width="120" /> -->
  261. </el-table>
  262. </div>
  263. </div>
  264. </el-col>
  265. </el-row>
  266. <div style="margin-top: 20px; background-color: white; padding: 20px">
  267. <TitleInfo :content="'销售趋势'"></TitleInfo>
  268. <div ref="mainTwo" style="height: 40vh"></div>
  269. </div>
  270. </div>
  271. </template>
  272. <script setup>
  273. import * as echarts from "echarts";
  274. import TitleInfo from "@/components/TitleInfo/index.vue";
  275. import { reactive, ref } from "vue";
  276. const { proxy } = getCurrentInstance();
  277. const productType = ref([]);
  278. const countryData = ref([]);
  279. const queryForm = reactive({
  280. countryId: "",
  281. beginTime: "",
  282. endTime: "",
  283. timeArr: [],
  284. });
  285. const getDict = () => {
  286. proxy.getDictOne(["product_type"]).then((res) => {
  287. productType.value = res["product_type"].map((x) => ({
  288. label: x.dictValue,
  289. value: x.dictKey,
  290. }));
  291. });
  292. };
  293. getDict();
  294. const getCountryData = () => {
  295. proxy.post("/customizeArea/list", { parentId: "0" }).then((res) => {
  296. countryData.value = res;
  297. let endData = new Date();
  298. let beginDate = new Date();
  299. beginDate.setMonth(0);
  300. beginDate.setDate(1);
  301. queryForm.timeArr = [
  302. proxy.parseTime(beginDate, "{y}-{m}-{d}"),
  303. proxy.parseTime(endData, "{y}-{m}-{d}"),
  304. ];
  305. queryForm.beginTime = queryForm.timeArr[0];
  306. queryForm.endTime = queryForm.timeArr[1];
  307. getData();
  308. });
  309. };
  310. getCountryData();
  311. const onQuery = () => {
  312. queryForm.beginTime = queryForm.timeArr[0];
  313. queryForm.endTime = queryForm.timeArr[1];
  314. getData();
  315. };
  316. const loadingOne = ref(null);
  317. const loadingTwo = ref(null);
  318. const loadingThree = ref(null);
  319. const allData = reactive({
  320. productStatistics: {
  321. newTotal: "",
  322. total: "",
  323. typeList: [],
  324. },
  325. productDistribution: [],
  326. productTypeRanking: [],
  327. productRanking: [],
  328. });
  329. const getNum = (item, label) => {
  330. let text = "";
  331. if (
  332. allData.productStatistics.typeList &&
  333. allData.productStatistics.typeList.length > 0
  334. ) {
  335. let data = allData.productStatistics.typeList.filter(
  336. (row) => row.type === item.value
  337. );
  338. if (data && data.length > 0) {
  339. text = data[0][label];
  340. }
  341. }
  342. return text;
  343. };
  344. const optionOne = reactive({
  345. data: {
  346. tooltip: {
  347. trigger: "item",
  348. },
  349. legend: {
  350. top: "5%",
  351. left: "center",
  352. },
  353. series: [
  354. {
  355. name: "产品分布",
  356. type: "pie",
  357. radius: ["40%", "70%"],
  358. avoidLabelOverlap: false,
  359. itemStyle: {
  360. borderRadius: 10,
  361. borderColor: "#fff",
  362. borderWidth: 2,
  363. },
  364. label: {
  365. show: false,
  366. position: "center",
  367. },
  368. emphasis: {
  369. label: {
  370. show: true,
  371. fontSize: 40,
  372. fontWeight: "bold",
  373. },
  374. },
  375. labelLine: {
  376. show: false,
  377. },
  378. data: [],
  379. },
  380. ],
  381. },
  382. });
  383. const optionTwo = reactive({
  384. data: {
  385. tooltip: {
  386. trigger: "axis",
  387. },
  388. legend: {
  389. data: ["月度销售额", "月成交单数"],
  390. },
  391. grid: {
  392. left: "3%",
  393. right: "4%",
  394. bottom: "3%",
  395. containLabel: true,
  396. },
  397. toolbox: {
  398. feature: {
  399. saveAsImage: {},
  400. },
  401. },
  402. xAxis: {
  403. type: "category",
  404. boundaryGap: false,
  405. data: [],
  406. },
  407. yAxis: {
  408. type: "value",
  409. },
  410. series: [
  411. {
  412. name: "月度销售额",
  413. type: "line",
  414. data: [],
  415. },
  416. {
  417. name: "月成交单数",
  418. type: "line",
  419. data: [],
  420. },
  421. ],
  422. },
  423. });
  424. const mainOne = ref(null);
  425. const mainTwo = ref(null);
  426. let myChartOne = null;
  427. let myChartTwo = null;
  428. const productTypeRankingProp = ref("contractQuantity");
  429. const productTypeRankingOrder = ref("descending");
  430. const productTypeRankingSort = ref({
  431. contractQuantity: 10,
  432. contractAmount: 20,
  433. purchaseQuantity: 30,
  434. purchaseAmount: 40,
  435. });
  436. const productRankingProp = ref("contractQuantity");
  437. const productRankingOrder = ref("descending");
  438. const productRankingSort = ref({
  439. contractQuantity: 10,
  440. contractAmount: 20,
  441. purchaseQuantity: 30,
  442. purchaseAmount: 40,
  443. });
  444. const orderBy = ref({
  445. ascending: 10,
  446. descending: 20,
  447. });
  448. const getData = () => {
  449. loadingOne.value = true;
  450. proxy.post("/productInfo/productStatistics", queryForm).then(
  451. (res) => {
  452. allData.productStatistics = res;
  453. setTimeout(() => {
  454. loadingOne.value = false;
  455. }, 200);
  456. },
  457. (err) => {
  458. console.log(err);
  459. setTimeout(() => {
  460. loadingOne.value = false;
  461. }, 200);
  462. }
  463. );
  464. proxy.post("/productInfo/productDistribution", queryForm).then((res) => {
  465. if (res && res.length > 0) {
  466. optionOne.data.series[0].data = res.map((item) => {
  467. return {
  468. value: item.count,
  469. name: item.name,
  470. type: item.type,
  471. };
  472. });
  473. } else {
  474. optionOne.data.series[0].data = [];
  475. }
  476. myChartOne.setOption(optionOne.data);
  477. myChartOne.resize();
  478. });
  479. proxy.post("/contract/saleTrend", queryForm).then((res) => {
  480. if (res && res.length > 0) {
  481. optionTwo.data.xAxis.data = res.map((item) => {
  482. return item.month;
  483. });
  484. optionTwo.data.series[0].data = res.map((item) => {
  485. return item.contractAmount;
  486. });
  487. optionTwo.data.series[1].data = res.map((item) => {
  488. return item.contractQuantity;
  489. });
  490. } else {
  491. optionTwo.data.xAxis.data = [];
  492. optionTwo.data.series[0].data = [];
  493. optionTwo.data.series[1].data = [];
  494. }
  495. myChartTwo.setOption(optionTwo.data);
  496. myChartTwo.resize();
  497. });
  498. getProductTypeRanking();
  499. getProductRanking();
  500. };
  501. const sortChangeTwo = ({ prop, order }) => {
  502. productTypeRankingProp.value = prop;
  503. productTypeRankingOrder.value = order;
  504. getProductTypeRanking();
  505. };
  506. const sortChangeThree = ({ prop, order }) => {
  507. productRankingProp.value = prop;
  508. productRankingOrder.value = order;
  509. getProductRanking();
  510. };
  511. const getProductTypeRanking = () => {
  512. loadingTwo.value = true;
  513. let query = proxy.deepClone(queryForm);
  514. query.pageNum = 1;
  515. query.pageSize = 10;
  516. query.sort = productTypeRankingSort.value[productTypeRankingProp.value] || 10;
  517. query.orderBy = orderBy.value[productTypeRankingOrder.value] || 20;
  518. proxy.post("/productInfo/productClassifyRanking", query).then(
  519. (res) => {
  520. allData.productTypeRanking = res;
  521. setTimeout(() => {
  522. loadingTwo.value = false;
  523. }, 200);
  524. },
  525. (err) => {
  526. console.log(err);
  527. setTimeout(() => {
  528. loadingTwo.value = false;
  529. }, 200);
  530. }
  531. );
  532. };
  533. const getProductRanking = () => {
  534. loadingThree.value = true;
  535. let query = proxy.deepClone(queryForm);
  536. query.pageNum = 1;
  537. query.pageSize = 10;
  538. query.sort = productRankingSort.value[productRankingProp.value] || 10;
  539. query.orderBy = orderBy.value[productRankingOrder.value] || 20;
  540. proxy.post("/productInfo/productRanking", query).then(
  541. (res) => {
  542. allData.productRanking = res.rows;
  543. setTimeout(() => {
  544. loadingThree.value = false;
  545. }, 200);
  546. },
  547. (err) => {
  548. console.log(err);
  549. setTimeout(() => {
  550. loadingThree.value = false;
  551. }, 200);
  552. }
  553. );
  554. };
  555. onMounted(() => {
  556. myChartOne = echarts.init(mainOne.value);
  557. window.addEventListener("resize", () => {
  558. myChartOne.resize();
  559. });
  560. myChartTwo = echarts.init(mainTwo.value);
  561. window.addEventListener("resize", () => {
  562. myChartTwo.resize();
  563. });
  564. });
  565. </script>
  566. <style lang="scss" scoped>
  567. .content {
  568. margin: 20px;
  569. }
  570. :deep(.el-form-item) {
  571. margin-bottom: 0px;
  572. }
  573. </style>