index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. <script lang="ts" setup>
  2. import { ref, reactive, onMounted, watchEffect } from 'vue';
  3. import TrendCard from './components/TrendCard.vue';
  4. import SimpleCard from './components/SimpleCard.vue';
  5. import Divider from './components/Divider.vue';
  6. import Echarts from '@/components/ReEcharts/index.vue';
  7. import { EChartsOption } from 'echarts';
  8. import { dayjs } from 'element-plus';
  9. import {
  10. getSummaryData,
  11. getBulletinData,
  12. BulletinData,
  13. getStageData,
  14. RequestProps,
  15. StageData,
  16. SummaryData
  17. } from './api';
  18. const permissionOptions = [
  19. {
  20. label: '仅本人',
  21. value: 0
  22. },
  23. {
  24. label: '本人及下属',
  25. value: 1
  26. },
  27. {
  28. label: '仅本部门',
  29. value: 2
  30. },
  31. {
  32. label: '本部门及下属部门',
  33. value: 3
  34. }
  35. ];
  36. const dateOptions = [
  37. {
  38. label: '本月',
  39. value: 0
  40. },
  41. {
  42. label: '本周',
  43. value: 1
  44. },
  45. {
  46. label: '本年',
  47. value: 2
  48. }
  49. ];
  50. const defineDate = ref<[Date, Date] | ''>('');
  51. const bulletinPrompt = reactive({ permission: undefined, date: undefined });
  52. const summaryPrompt = reactive({ permission: undefined, date: undefined });
  53. const stagePrompt = reactive({ permission: undefined, date: undefined });
  54. const requestData = reactive<{
  55. bulletin: BulletinData['data'] | null;
  56. summary: SummaryData['data'] | null;
  57. stage: StageData['data'] | null;
  58. }>({
  59. summary: null,
  60. stage: null,
  61. bulletin: null
  62. });
  63. const chartOptions: EChartsOption = reactive({
  64. grid: { top: 0, bottom: 20, left: 60 },
  65. yAxis: {
  66. type: 'category'
  67. },
  68. xAxis: {
  69. type: 'value'
  70. },
  71. series: [
  72. {
  73. barWidth: 20,
  74. data: [10, 30, 30, 30, 30],
  75. type: 'bar'
  76. }
  77. ]
  78. });
  79. const queryBulletin = async (payload?: RequestProps) => {
  80. const bulletinResult = await getBulletinData(payload);
  81. requestData.bulletin = bulletinResult.data;
  82. };
  83. const querySummary = async (payload?: RequestProps) => {
  84. const summaryResult = await getSummaryData(payload);
  85. requestData.summary = summaryResult.data;
  86. };
  87. const queryStage = async (payload?: RequestProps) => {
  88. const stageResult = await getStageData(payload);
  89. const data = stageResult.data;
  90. // @ts-ignore
  91. chartOptions.yAxis.data = data.dataMap.map((map) => map.stageName);
  92. // @ts-ignore
  93. chartOptions.series[0].data = data.dataMap.map((map) => map.num);
  94. };
  95. watchEffect(() => {
  96. queryBulletin({
  97. ...(bulletinPrompt.date === 'defineDate'
  98. ? {
  99. startDate: dayjs(defineDate.value[0]).valueOf(),
  100. endData: dayjs(defineDate.value[1]).valueOf()
  101. }
  102. : { dateType: bulletinPrompt.date }),
  103. queryType: bulletinPrompt.permission
  104. });
  105. });
  106. watchEffect(() => {
  107. queryStage({
  108. ...(stagePrompt.date === 'defineDate'
  109. ? {
  110. startDate: dayjs(defineDate.value[0]).valueOf(),
  111. endData: dayjs(defineDate.value[1]).valueOf()
  112. }
  113. : { dateType: stagePrompt.date }),
  114. queryType: stagePrompt.permission
  115. });
  116. });
  117. watchEffect(() => {
  118. querySummary({
  119. ...(summaryPrompt.date === 'defineDate'
  120. ? {
  121. startDate: dayjs(defineDate.value[0]).valueOf(),
  122. endData: dayjs(defineDate.value[1]).valueOf()
  123. }
  124. : { dateType: summaryPrompt.date }),
  125. queryType: summaryPrompt.permission
  126. });
  127. });
  128. </script>
  129. <template>
  130. <div
  131. class="m-5 bg-white min-h-full p-4 rounded relative flex gap-12 items-start"
  132. >
  133. <section class="flex-[4]">
  134. <div class="flex gap-3 mb-4">
  135. <div class="w-40">
  136. <el-select
  137. clearable
  138. size="small"
  139. :model-value="bulletinPrompt.permission"
  140. @change="(value) => (bulletinPrompt.permission = value)"
  141. >
  142. <el-option
  143. v-for="permission in permissionOptions"
  144. :label="permission.label"
  145. :value="permission.value"
  146. />
  147. </el-select>
  148. </div>
  149. <div class="w-40">
  150. <el-select
  151. clearable
  152. size="small"
  153. :model-value="bulletinPrompt.date"
  154. @change="(value) => (bulletinPrompt.date = value)"
  155. >
  156. <el-option
  157. v-for="date in dateOptions"
  158. :label="date.label"
  159. :value="date.value"
  160. />
  161. <el-option v-if="defineDate" label="自定义" value="defineDate" />
  162. </el-select>
  163. </div>
  164. </div>
  165. <div class="border-gray-200 border rounded p-3">
  166. <div class="flex gap-1.5 items-center mb-3">
  167. <img width="16" src="../../assets/trend.png" />
  168. <div class="text-sm font-medium">销售简报</div>
  169. <el-icon><QuestionFilled class="text-gray-500 text-sm" /></el-icon>
  170. </div>
  171. <div class="grid xl:grid-cols-4 lg:grid-cols-3 grid-cols-2 gap-4">
  172. <TrendCard
  173. title="新增客户"
  174. unit="人"
  175. :number="requestData?.bulletin?.custom.customCount"
  176. :compare="requestData?.bulletin?.custom.customPromote"
  177. />
  178. <TrendCard
  179. title="新增联系人"
  180. unit="人"
  181. :number="requestData?.bulletin?.contacts.contactsCount"
  182. :compare="requestData?.bulletin?.contacts.contactsPromote"
  183. />
  184. <TrendCard
  185. title="新增商机"
  186. unit="个"
  187. :number="
  188. requestData?.bulletin?.businessOpportunity
  189. .businessOpportunityCount
  190. "
  191. :compare="
  192. requestData?.bulletin?.businessOpportunity
  193. .businessOpportunityPromote
  194. "
  195. />
  196. <TrendCard
  197. title="新增销售订单"
  198. unit="个"
  199. :number="requestData?.bulletin?.salesOrder.salesOrderCount"
  200. :compare="requestData?.bulletin?.salesOrder.salesOrderPromote"
  201. />
  202. <TrendCard
  203. title="销售订单金额"
  204. unit="元"
  205. :number="requestData?.bulletin?.salesOrdersPrice.salesOrdersPrice"
  206. :compare="
  207. requestData?.bulletin?.salesOrdersPrice.salesOrderPricePromote
  208. "
  209. />
  210. <TrendCard
  211. title="商机金额"
  212. unit="元"
  213. :number="
  214. requestData?.bulletin?.businessOpportunityPrice
  215. .businessOpportunityPrice
  216. "
  217. :compare="
  218. requestData?.bulletin?.businessOpportunityPrice
  219. .businessOpportunityPromote
  220. "
  221. />
  222. <TrendCard
  223. title="新增线索"
  224. unit="个"
  225. :number="requestData?.bulletin?.clue.clueCount"
  226. :compare="requestData?.bulletin?.clue.cluePromote"
  227. />
  228. </div>
  229. </div>
  230. <div class="my-8 flex gap-4 items-start">
  231. <div class="border-gray-200 border rounded p-3 flex-1">
  232. <div class="text-sm font-medium">数据汇总</div>
  233. <div class="flex gap-3 mb-8 mt-2">
  234. <div class="w-40">
  235. <el-select
  236. clearable
  237. size="small"
  238. :model-value="summaryPrompt.permission"
  239. @change="(value) => (summaryPrompt.permission = value)"
  240. >
  241. <el-option
  242. v-for="permission in permissionOptions"
  243. :label="permission.label"
  244. :value="permission.value"
  245. />
  246. </el-select>
  247. </div>
  248. <div class="w-40">
  249. <el-select
  250. clearable
  251. size="small"
  252. :model-value="summaryPrompt.date"
  253. @change="(value) => (summaryPrompt.date = value)"
  254. >
  255. <el-option
  256. v-for="date in dateOptions"
  257. :label="date.label"
  258. :value="date.value"
  259. />
  260. <el-option
  261. v-if="defineDate"
  262. label="自定义"
  263. value="defineDate"
  264. />
  265. </el-select>
  266. </div>
  267. </div>
  268. <Divider title="客户汇总" />
  269. <div class="my-6 grid grid-cols-4 gap-2">
  270. <SimpleCard
  271. title="新增客户"
  272. unit="个"
  273. :number="requestData.summary?.customDataSummary.newNum"
  274. />
  275. <SimpleCard
  276. title="转成交客户"
  277. unit="个"
  278. :number="requestData.summary?.customDataSummary.closeDealNum"
  279. />
  280. </div>
  281. <Divider title="商机汇总" />
  282. <div class="my-6 grid grid-cols-4 gap-2">
  283. <SimpleCard
  284. title="新增商机"
  285. unit="个"
  286. :number="
  287. requestData.summary?.businessOpportunityDataSummary.newNum
  288. "
  289. />
  290. <SimpleCard
  291. title="赢单商机"
  292. unit="个"
  293. :number="
  294. requestData.summary?.businessOpportunityDataSummary.winning
  295. "
  296. />
  297. <SimpleCard
  298. title="输单商机"
  299. unit="个"
  300. :number="
  301. requestData.summary?.businessOpportunityDataSummary.losting
  302. "
  303. />
  304. <SimpleCard
  305. title="商机总金额"
  306. unit="元"
  307. :number="
  308. requestData.summary?.businessOpportunityDataSummary
  309. .allAmountOfMoney
  310. "
  311. />
  312. </div>
  313. <Divider title="线索汇总" />
  314. <div class="my-6 grid grid-cols-4 gap-2">
  315. <SimpleCard
  316. title="新增线索"
  317. unit="个"
  318. :number="requestData.summary?.clueDataSummary.newNum"
  319. />
  320. <SimpleCard
  321. title="线索转商机"
  322. unit="个"
  323. :number="requestData.summary?.clueDataSummary.changeNum"
  324. />
  325. </div>
  326. </div>
  327. <div class="border-gray-200 border rounded p-3 flex-1">
  328. <div class="text-sm font-medium">商机阶段</div>
  329. <div class="flex gap-3 mb-8 mt-2">
  330. <div class="w-40">
  331. <el-select
  332. clearable
  333. size="small"
  334. :model-value="stagePrompt.permission"
  335. @change="(value) => (stagePrompt.permission = value)"
  336. >
  337. <el-option
  338. v-for="permission in permissionOptions"
  339. :label="permission.label"
  340. :value="permission.value"
  341. />
  342. </el-select>
  343. </div>
  344. <div class="w-40">
  345. <el-select
  346. clearable
  347. size="small"
  348. :model-value="stagePrompt.date"
  349. @change="(value) => (stagePrompt.date = value)"
  350. >
  351. <el-option
  352. v-for="date in dateOptions"
  353. :label="date.label"
  354. :value="date.value"
  355. />
  356. <el-option
  357. v-if="defineDate"
  358. label="自定义"
  359. value="defineDate"
  360. />
  361. </el-select>
  362. </div>
  363. </div>
  364. <div class="h-60">
  365. <Echarts :option="chartOptions"></Echarts>
  366. </div>
  367. </div>
  368. </div>
  369. </section>
  370. <nav class="flex-1 min-w-60 sticky right-0 top-9 max-lg:hidden">
  371. <div class="border border-gray-200 rounded w-3/4 text-sm text-gray-500">
  372. <div class="p-2" v-for="primission in permissionOptions">
  373. {{ primission.label }}
  374. </div>
  375. </div>
  376. <div class="border border-gray-200 rounded text-sm text-gray-500 mt-4">
  377. <div class="p-2" v-for="date in dateOptions">{{ date.label }}</div>
  378. <div class="p-2">
  379. <div>自定义</div>
  380. <el-date-picker
  381. type="daterange"
  382. class="max-w-full mt-2"
  383. v-model="defineDate"
  384. start-placeholder="开始日期"
  385. end-placeholder="结束日期"
  386. />
  387. </div>
  388. </div>
  389. </nav>
  390. </div>
  391. </template>