management.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. <template >
  2. <el-card id="management">
  3. <div slot="header" class="clearfix">
  4. <searchHeader
  5. :dates="mdate"
  6. :title="'经营数据'"
  7. @changeValue="changeValue"
  8. :isShowQuert="true"
  9. :endDate="endDate"
  10. ref="searchHeader"
  11. />
  12. </div>
  13. <div
  14. class="wall"
  15. style="height: 68px"
  16. v-if="JSON.stringify(items) == '{}'"
  17. ></div>
  18. <statistic :col="5" class="statistic" :cols="12">
  19. <statistic-item @click="active = 0">
  20. <span>
  21. {{ items["TOTAL_AMOUNT"].title + "(元)" }}
  22. <el-tooltip
  23. v-if="items['TOTAL_AMOUNT'].desc"
  24. :content="items['TOTAL_AMOUNT'].desc"
  25. :open-delay="0.3"
  26. placement="top"
  27. >
  28. <i
  29. style="margin-left: 5px; cursor: pointer"
  30. class="el-icon-warning-outline"
  31. />
  32. </el-tooltip>
  33. </span>
  34. <!-- 12345678901.23 -->
  35. <span>
  36. <count-to
  37. :endVal="items['TOTAL_AMOUNT'].percent"
  38. :decimals="2"
  39. :class="'blod'"
  40. /></span>
  41. </statistic-item>
  42. <template v-for="(item, key) in items">
  43. <statistic-item
  44. v-if="item.dataType != 'TOTAL_AMOUNT'"
  45. :key="key"
  46. :class="{ active: active === key }"
  47. @click="active = key"
  48. >
  49. <span>
  50. {{ item.title + "(元)" }}
  51. <el-tooltip
  52. v-if="item.desc"
  53. :content="item.desc"
  54. :open-delay="0.3"
  55. placement="top"
  56. >
  57. <i
  58. style="margin-left: 5px; cursor: pointer"
  59. class="el-icon-warning-outline"
  60. />
  61. </el-tooltip>
  62. </span>
  63. <!-- 12345678901.23 -->
  64. <span>
  65. <count-to
  66. :endVal="item.percent"
  67. :decimals="2"
  68. :class="item.dataType == 'TOTAL_AMOUNT' ? 'blod' : ''"
  69. /></span>
  70. </statistic-item>
  71. </template>
  72. <statistic-item></statistic-item>
  73. </statistic>
  74. <div class="statisticWrap">
  75. <statistic :col="5" class="statistic" :cols="0" :isNoLine='true'>
  76. <statistic-item
  77. v-for="(item, key) in items2"
  78. :key="key"
  79. :class="{ active: active === key }"
  80. @click="active = key"
  81. >
  82. <template v-if="item.dataType != 'OTHER_AMOUNT'">
  83. <span>
  84. {{ item.title + "(元)" }}
  85. <!-- {{ item.dataType == 'OTHER_AMOUNT' ? 'other' : null }} -->
  86. <el-tooltip
  87. v-if="item.desc"
  88. :content="item.desc"
  89. :open-delay="0.3"
  90. placement="top"
  91. >
  92. <i
  93. style="margin-left: 5px; cursor: pointer"
  94. class="el-icon-warning-outline"
  95. />
  96. </el-tooltip>
  97. </span>
  98. <!-- 12345678901.23 -->
  99. <span> <count-to :endVal="item.percent" :decimals="2" /></span>
  100. </template>
  101. <el-popover
  102. placement="top"
  103. width="400"
  104. v-else
  105. trigger="hover">
  106. <descriptions :column="1" v-if="(item.indexMonthDataDetail || []).length > 0">
  107. <template v-for="(item, index) in item.indexMonthDataDetail || []">
  108. <descriptions-item :key="index" :label="orderType[item.orderType]">{{ item.totalNum | moneyFormat }}元
  109. </descriptions-item>
  110. </template>
  111. </descriptions>
  112. <div slot="reference">
  113. <span>
  114. {{ item.title + "(元)" }}
  115. <el-tooltip
  116. v-if="item.desc"
  117. :content="item.desc"
  118. :open-delay="0.3"
  119. placement="top"
  120. >
  121. <i
  122. style="margin-left: 5px; cursor: pointer"
  123. class="el-icon-warning-outline"
  124. />
  125. </el-tooltip>
  126. </span>
  127. <span style="font-size: 18px; display: block; color: rgba(0, 0, 0, 0.85);"> <count-to :endVal="item.percent" :decimals="2" /></span>
  128. </div>
  129. </el-popover>
  130. </statistic-item>
  131. </statistic>
  132. </div>
  133. <!-- 按月/按天 -->
  134. <div class="wrap">
  135. <div class="chioseBox">
  136. <el-radio-group v-model="timer" size="mini">
  137. <el-radio-button label="day">按天</el-radio-button>
  138. <el-radio-button label="month">按月</el-radio-button>
  139. </el-radio-group>
  140. </div>
  141. <!-- v-if="timer == 'day'" :settings="chartSettings" DD-->
  142. <ve-line
  143. :data="timer == 'day' ? chartData : chartDataForMoth"
  144. height="350px"
  145. :data-empty="dataEmpty"
  146. :extend="chartExtend"
  147. :legend="legend"
  148. />
  149. <!-- <ve-line
  150. v-else
  151. :data-zoom="dataZoom"
  152. :settings="{
  153. area: true,
  154. }"
  155. :data="chartDataForMoth"
  156. height="350px"
  157. :data-empty="dataEmpty"
  158. :data-zoom="dataZoom"
  159. :extend="chartExtend"
  160. :legend="legend"
  161. /> -->
  162. </div>
  163. </el-card>
  164. </template>
  165. <script>
  166. import "echarts/lib/component/dataZoom";
  167. import countTo from "vue-count-to";
  168. import veLine from "v-charts/lib/line.common";
  169. import histogram from "v-charts/lib/histogram.common";
  170. import searchHeader from "./modals/searchHeader";
  171. import { getIndex } from "../api";
  172. import { getTimes } from "@/utils";
  173. import { descs, chioseNum } from "../constant";
  174. import { orderType } from '@/constant'
  175. export default {
  176. props: ["data", "search"],
  177. components: {
  178. "ve-line": veLine,
  179. "count-to": countTo,
  180. "ve-histogram": histogram,
  181. searchHeader,
  182. },
  183. computed: {
  184. legend() {
  185. return {
  186. left: "10px",
  187. };
  188. },
  189. items() {
  190. let obj = {};
  191. let arr = [
  192. "TOTAL_AMOUNT",
  193. "FINANCE_AMOUNT",
  194. "FINANCE_BALANCE_AMOUNT",
  195. "FINANCE_PAY",
  196. ];
  197. arr.forEach((str) => {
  198. if (this.data[str]) {
  199. obj[str] = this.data[str];
  200. }
  201. });
  202. // console.log(obj);
  203. return obj;
  204. },
  205. items2() {
  206. let obj = {};
  207. let arr = [
  208. "APPLY_AMOUNT",
  209. "RENEW_AMOUNT",
  210. "VIP_AMOUNT",
  211. "PRACTICE_AMOUNT",
  212. "OTHER_AMOUNT",
  213. ];
  214. arr.forEach((str) => {
  215. if (this.data[str]) {
  216. obj[str] = this.data[str];
  217. }
  218. });
  219. return obj;
  220. },
  221. chartExtend() {
  222. return {
  223. series: {
  224. smooth: false,
  225. },
  226. yAxis: {
  227. //纵轴标尺固定
  228. minInterval: 1,
  229. type: "value",
  230. scale: true,
  231. min: 0,
  232. axisLabel: {
  233. formatter: "{value}元",
  234. },
  235. },
  236. tooltip: {
  237. axisPointer: {
  238. type: "shadow",
  239. shadowStyle: {
  240. color: "rgba(150,150,150,0.2)",
  241. },
  242. },
  243. formatter: (item) => {
  244. return [
  245. item[0].axisValueLabel,
  246. ...item.map(
  247. (d) => `<br/>${d.marker}${d.seriesName}: ${d.value[1]} 元`
  248. ),
  249. ].join("");
  250. },
  251. },
  252. };
  253. },
  254. dataZoom() {
  255. return [
  256. {
  257. type: "slider",
  258. start: 50,
  259. end: 100,
  260. filterMode: "empty",
  261. },
  262. ];
  263. },
  264. chartData() {
  265. const values = Object.values({ ...this.items, ...this.items2 });
  266. const months = {};
  267. for (const item of values) {
  268. for (const row of item.indexMonthData || []) {
  269. const key = this.$helpers.dayjs(row.month).format("YYYY-MM-DD");
  270. if (!months[key]) {
  271. months[key] = {
  272. 日期: key,
  273. };
  274. }
  275. months[key][item.title] = row.percent;
  276. }
  277. }
  278. // console.log(values);
  279. return {
  280. columns: [
  281. "日期",
  282. "总收入",
  283. "现金收入",
  284. "余额收入",
  285. "财务支出",
  286. "报名缴费收入",
  287. "网管课收入",
  288. "其他收入",
  289. "乐团续费收入",
  290. "VIP课收入",
  291. ],
  292. rows: Object.values(months),
  293. loading: true,
  294. };
  295. },
  296. chartDataForMoth() {
  297. const values = Object.values({ ...this.items, ...this.items2 });
  298. const months = {};
  299. for (const item of values) {
  300. for (const row of item.indexMonthData || []) {
  301. const key = this.$helpers.dayjs(row.month).format("YYYY-MM");
  302. if (!months[key]) {
  303. months[key] = {
  304. 月份: key,
  305. };
  306. months[key][item.title] = row.percent;
  307. } else {
  308. if (months[key][item.title]) {
  309. months[key][item.title] = (
  310. parseFloat(months[key][item.title]) + parseFloat(row.percent)
  311. ).toFixed(2);
  312. } else {
  313. months[key][item.title] = parseFloat(row.percent).toFixed(2);
  314. }
  315. }
  316. }
  317. }
  318. return {
  319. columns: [
  320. "月份",
  321. "总收入",
  322. "现金收入",
  323. "余额收入",
  324. "财务支出",
  325. "报名缴费收入",
  326. "网管课收入",
  327. "其他收入",
  328. "乐团续费收入",
  329. "VIP课收入",
  330. ],
  331. rows: Object.values(months),
  332. loading: true,
  333. };
  334. },
  335. dataEmpty() {
  336. return !this.chartData.rows.length;
  337. },
  338. },
  339. data() {
  340. // this.chartSettings = {
  341. // stack: { 总收入: [`现金收入`, `余额收入`] },
  342. // };
  343. return {
  344. orderType,
  345. active: "SHOULD_INCOME_MONEY",
  346. timer: "day",
  347. mdate: [],
  348. loading: false,
  349. endDate: "",
  350. show: true,
  351. };
  352. },
  353. mounted() {
  354. console.log(this.orderType)
  355. this.init();
  356. },
  357. methods: {
  358. init() {
  359. this.$refs.searchHeader.initStatue();
  360. this.mdate = this.getInitDate();
  361. this.endDate = this.$helpers.dayjs(new Date()).format("YYYY-MM-DD");
  362. // this.$refs.searchHeader.initStatue()
  363. this.changeValue(this.mdate);
  364. },
  365. changeValue(date) {
  366. // 请求更改数据
  367. this.mdate = date;
  368. this.isDayOrMoth(date);
  369. this.FetchDetail();
  370. },
  371. async FetchDetail() {
  372. this.loading = true;
  373. let data = [];
  374. try {
  375. const { dates, ...rest } = this.search;
  376. const res = await getIndex({
  377. ...rest,
  378. ...getTimes(this.mdate, ["startDate", "endDate"]),
  379. //
  380. dataTypes:
  381. "FINANCE_AMOUNT,FINANCE_BALANCE_AMOUNT,FINANCE_PAY,TOTAL_AMOUNT",
  382. });
  383. for (const item of res.data) {
  384. // 再循环一遍
  385. for (const key in { ...this.items, ...this.items2 }) {
  386. // console.log(key);
  387. if (item.dataType == key) {
  388. data[item.dataType] = {
  389. ...item,
  390. desc: descs[item.dataType],
  391. };
  392. }
  393. }
  394. }
  395. } catch (error) {
  396. console.log(error);
  397. }
  398. this.loading = false;
  399. this.$emit("resetDate", data);
  400. },
  401. isDayOrMoth(arr) {
  402. if (!arr || arr.length < 1) {
  403. this.timer = "day";
  404. } else {
  405. const count = this.$helpers
  406. .dayjs(arr[0])
  407. .diff(this.$helpers.dayjs(arr[1]), "day");
  408. Math.abs(count) > chioseNum
  409. ? (this.timer = "month")
  410. : (this.timer = "day");
  411. }
  412. },
  413. getInitDate() {
  414. const end = this.$helpers.dayjs(new Date()).format("YYYY-MM-DD");
  415. const start = this.$helpers
  416. .dayjs(new Date())
  417. .set("date", 1)
  418. .format("YYYY-MM-DD");
  419. return [start, end];
  420. },
  421. },
  422. };
  423. </script>
  424. <style lang="less" scoped>
  425. /deep/.description-title {
  426. margin-bottom: 0;
  427. }
  428. #management .statistic .statistic-content > span {
  429. &:first-child {
  430. font-size: 14px !important;
  431. }
  432. font-size: 18px !important;
  433. }
  434. .chioseBox {
  435. position: absolute;
  436. right: 20px;
  437. z-index: 1000;
  438. }
  439. .wrap {
  440. position: relative;
  441. }
  442. .blod {
  443. font-weight: bold;
  444. color: #14928a;
  445. font-size: 22px !important;
  446. }
  447. // .chioseBox {
  448. // position: absolute;
  449. // right: 20px;
  450. // z-index: 1000;
  451. // }
  452. // .wrap {
  453. // position: relative;
  454. // }
  455. .statisticWrap {
  456. // box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
  457. border-radius: 4px;
  458. border: 1px solid #ebeef5;
  459. padding-top: 10px;
  460. margin-bottom: 10px;
  461. position: relative;
  462. &:after {
  463. top: -16px;
  464. margin-left:calc(10% - 14px) ;
  465. border-top-width: 0;
  466. border-color: transparent;
  467. border-bottom-color: #fff;
  468. content: " ";
  469. border-width: 8px;
  470. position: absolute;
  471. display: block;
  472. width: 0;
  473. height: 0;
  474. border-style: solid;
  475. z-index: 101;
  476. }
  477. &:before {
  478. top: -20px;
  479. margin-left:calc(10% - 16px) ;
  480. border-top-width: 0;
  481. border-color: transparent;
  482. border-bottom-color: #ebeef5;
  483. content: " ";
  484. border-width: 10px;
  485. position: absolute;
  486. display: block;
  487. width: 0;
  488. height: 0;
  489. border-style: solid;
  490. z-index: 100;
  491. }
  492. }
  493. </style>