index.vue 13 KB

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