index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  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 personnelSearch from '@/components/translationComponent/personnelSearch/personnelSearch.vue';
  10. import {
  11. getSummaryData,
  12. getBulletinData,
  13. BulletinData,
  14. getStageData,
  15. RequestProps,
  16. StageData,
  17. SummaryData
  18. } from './api';
  19. const selectVal = ref<string | number>('')
  20. const selectChange = (val: any) => {
  21. console.log(val, '<===== 当前的数据')
  22. console.log(selectVal.value, '<===== 双向绑定的数据')
  23. }
  24. const permissionOptions = [
  25. {
  26. label: '仅本人',
  27. value: 0
  28. },
  29. {
  30. label: '本人及下属',
  31. value: 1
  32. },
  33. {
  34. label: '仅本部门',
  35. value: 2
  36. },
  37. {
  38. label: '本部门及下属部门',
  39. value: 3
  40. }
  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. <personnel-search v-model="selectVal" size="small" :disabled="false" multiple placeholder="你好世界" @change="selectChange"></personnel-search>
  176. <el-select
  177. ref="select1"
  178. size="small"
  179. :model-value="bulletinPrompt.date"
  180. @change="(value: any) => (bulletinPrompt.date = value)"
  181. >
  182. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  183. <el-option label="自定义" value="ignore" disabled>
  184. <div class="flex gap-2 w-80">
  185. <el-date-picker
  186. size="small"
  187. :clearable="false"
  188. type="daterange"
  189. class="w-12"
  190. v-model="bulletinPrompt.sliceDate"
  191. start-placeholder="开始日期"
  192. end-placeholder="结束日期"
  193. />
  194. <el-button
  195. size="small"
  196. type="primary"
  197. @click="
  198. () => {
  199. select1?.blur();
  200. bulletinPrompt.date = 'ignore';
  201. }
  202. "
  203. >确认</el-button
  204. >
  205. </div>
  206. </el-option>
  207. </el-select>
  208. </div>
  209. </div>
  210. <div class="border-gray-200 border rounded p-3">
  211. <div class="flex gap-1.5 items-center mb-3">
  212. <img width="16" src="../../assets/trend.png" />
  213. <div class="text-sm font-medium">销售简报</div>
  214. <el-icon><QuestionFilled class="text-gray-500 text-sm" /></el-icon>
  215. </div>
  216. <div class="grid xl:grid-cols-4 lg:grid-cols-3 grid-cols-2 gap-4">
  217. <TrendCard
  218. title="新增客户"
  219. unit="人"
  220. :number="requestData?.bulletin?.custom.customCount"
  221. :compare="requestData?.bulletin?.custom.customPromote"
  222. />
  223. <TrendCard
  224. title="新增联系人"
  225. unit="人"
  226. :number="requestData?.bulletin?.contacts.contactsCount"
  227. :compare="requestData?.bulletin?.contacts.contactsPromote"
  228. />
  229. <TrendCard
  230. title="新增商机"
  231. unit="个"
  232. :number="requestData?.bulletin?.businessOpportunity.businessOpportunityCount"
  233. :compare="requestData?.bulletin?.businessOpportunity.businessOpportunityPromote"
  234. />
  235. <TrendCard
  236. title="新增销售订单"
  237. unit="个"
  238. :number="requestData?.bulletin?.salesOrder.salesOrderCount"
  239. :compare="requestData?.bulletin?.salesOrder.salesOrderPromote"
  240. />
  241. <TrendCard
  242. title="销售订单金额"
  243. unit="元"
  244. :number="requestData?.bulletin?.salesOrdersPrice.salesOrdersPrice"
  245. :compare="requestData?.bulletin?.salesOrdersPrice.salesOrderPricePromote"
  246. />
  247. <TrendCard
  248. title="商机金额"
  249. unit="元"
  250. :number="requestData?.bulletin?.businessOpportunityPrice.businessOpportunityPrice"
  251. :compare="requestData?.bulletin?.businessOpportunityPrice.businessOpportunityPromote"
  252. />
  253. <TrendCard
  254. title="新增线索"
  255. unit="个"
  256. :number="requestData?.bulletin?.clue.clueCount"
  257. :compare="requestData?.bulletin?.clue.cluePromote"
  258. />
  259. </div>
  260. </div>
  261. <div class="my-8 flex gap-4 items-start">
  262. <div class="border-gray-200 border rounded p-3 flex-1">
  263. <div class="text-sm font-medium">数据汇总</div>
  264. <div class="flex gap-3 mb-8 mt-2">
  265. <div class="w-40">
  266. <el-select
  267. clearable
  268. size="small"
  269. :model-value="summaryPrompt.permission"
  270. @change="(value: any) => (summaryPrompt.permission = value)"
  271. >
  272. <el-option
  273. v-for="permission in permissionOptions"
  274. :label="permission.label"
  275. :value="permission.value"
  276. />
  277. </el-select>
  278. </div>
  279. <div class="w-40">
  280. <el-select
  281. ref="select2"
  282. clearable
  283. size="small"
  284. :model-value="summaryPrompt.date"
  285. @change="(value: any) => (summaryPrompt.date = value)"
  286. >
  287. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  288. <el-option label="自定义" value="ignore" disabled>
  289. <div class="flex gap-2 w-80">
  290. <el-date-picker
  291. size="small"
  292. :clearable="false"
  293. type="daterange"
  294. class="w-12"
  295. v-model="summaryPrompt.sliceDate"
  296. start-placeholder="开始日期"
  297. end-placeholder="结束日期"
  298. />
  299. <el-button
  300. size="small"
  301. type="primary"
  302. @click="
  303. () => {
  304. select2?.blur();
  305. summaryPrompt.date = 'ignore';
  306. }
  307. "
  308. >确认</el-button
  309. >
  310. </div>
  311. </el-option>
  312. </el-select>
  313. </div>
  314. </div>
  315. <Divider title="客户汇总" />
  316. <div class="my-6 grid grid-cols-4 gap-2">
  317. <SimpleCard
  318. title="新增客户"
  319. unit="个"
  320. :number="requestData.summary?.customDataSummary.newNum"
  321. />
  322. <SimpleCard
  323. title="转成交客户"
  324. unit="个"
  325. :number="requestData.summary?.customDataSummary.closeDealNum"
  326. />
  327. </div>
  328. <Divider title="商机汇总" />
  329. <div class="my-6 grid grid-cols-4 gap-2">
  330. <SimpleCard
  331. title="新增商机"
  332. unit="个"
  333. :number="requestData.summary?.businessOpportunityDataSummary.newNum"
  334. />
  335. <SimpleCard
  336. title="赢单商机"
  337. unit="个"
  338. :number="requestData.summary?.businessOpportunityDataSummary.winning"
  339. />
  340. <SimpleCard
  341. title="输单商机"
  342. unit="个"
  343. :number="requestData.summary?.businessOpportunityDataSummary.losting"
  344. />
  345. <SimpleCard
  346. title="商机总金额"
  347. unit="元"
  348. :number="requestData.summary?.businessOpportunityDataSummary.allAmountOfMoney"
  349. />
  350. </div>
  351. <Divider title="线索汇总" />
  352. <div class="my-6 grid grid-cols-4 gap-2">
  353. <SimpleCard
  354. title="新增线索"
  355. unit="个"
  356. :number="requestData.summary?.clueDataSummary.newNum"
  357. />
  358. <SimpleCard
  359. title="线索转商机"
  360. unit="个"
  361. :number="requestData.summary?.clueDataSummary.changeNum"
  362. />
  363. </div>
  364. </div>
  365. <div class="border-gray-200 border rounded p-3 flex-1">
  366. <div class="text-sm font-medium">商机阶段</div>
  367. <div class="flex gap-3 mb-8 mt-2">
  368. <div class="w-40">
  369. <el-select
  370. clearable
  371. size="small"
  372. :model-value="stagePrompt.permission"
  373. @change="(value: any) => (stagePrompt.permission = value)"
  374. >
  375. <el-option
  376. v-for="permission in permissionOptions"
  377. :label="permission.label"
  378. :value="permission.value"
  379. />
  380. </el-select>
  381. </div>
  382. <div class="w-40">
  383. <el-select
  384. ref="select3"
  385. clearable
  386. size="small"
  387. :model-value="stagePrompt.date"
  388. @change="(value: any) => (stagePrompt.date = value)"
  389. >
  390. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  391. <el-option label="自定义" value="ignore" disabled>
  392. <div class="flex gap-2 w-80">
  393. <el-date-picker
  394. size="small"
  395. :clearable="false"
  396. type="daterange"
  397. class="w-12"
  398. v-model="stagePrompt.sliceDate"
  399. start-placeholder="开始日期"
  400. end-placeholder="结束日期"
  401. />
  402. <el-button
  403. size="small"
  404. type="primary"
  405. @click="
  406. () => {
  407. select3?.blur();
  408. stagePrompt.date = 'ignore';
  409. }
  410. "
  411. >确认</el-button
  412. >
  413. </div>
  414. </el-option>
  415. </el-select>
  416. </div>
  417. </div>
  418. <div class="h-60">
  419. <Echarts :option="chartOptions"></Echarts>
  420. </div>
  421. </div>
  422. </div>
  423. </section>
  424. </div>
  425. </template>