index.vue 14 KB

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