index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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="h-[800px] w-full bg-[red] mb-5">
  161. <iframe
  162. src="http://47.101.180.183:9080/webapp/agent"
  163. style="border: none;"
  164. class="w-full h-full"
  165. />
  166. </div>
  167. <div class="flex gap-3 mb-4">
  168. <div class="w-40">
  169. <el-select
  170. size="small"
  171. :model-value="bulletinPrompt.permission"
  172. @change="(value: any) => (bulletinPrompt.permission = value)"
  173. >
  174. <el-option
  175. v-for="permission in permissionOptions"
  176. :label="permission.label"
  177. :value="permission.value"
  178. />
  179. </el-select>
  180. </div>
  181. <div class="w-40">
  182. <el-select
  183. ref="select1"
  184. size="small"
  185. :model-value="bulletinPrompt.date"
  186. @change="(value: any) => (bulletinPrompt.date = value)"
  187. >
  188. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  189. <el-option label="自定义" value="ignore" disabled>
  190. <div class="flex gap-2 w-80">
  191. <el-date-picker
  192. size="small"
  193. :clearable="false"
  194. type="daterange"
  195. class="w-12"
  196. v-model="bulletinPrompt.sliceDate"
  197. start-placeholder="开始日期"
  198. end-placeholder="结束日期"
  199. />
  200. <el-button
  201. size="small"
  202. type="primary"
  203. @click="
  204. () => {
  205. select1?.blur();
  206. bulletinPrompt.date = 'ignore';
  207. }
  208. "
  209. >确认</el-button
  210. >
  211. </div>
  212. </el-option>
  213. </el-select>
  214. </div>
  215. </div>
  216. <div class="border-gray-200 border rounded p-3">
  217. <div class="flex gap-1.5 items-center mb-3">
  218. <img width="16" src="../../assets/trend.png" />
  219. <div class="text-sm font-medium">销售简报</div>
  220. <el-icon><QuestionFilled class="text-gray-500 text-sm" /></el-icon>
  221. </div>
  222. <div class="grid xl:grid-cols-4 lg:grid-cols-3 grid-cols-2 gap-4">
  223. <TrendCard
  224. title="新增客户"
  225. unit="人"
  226. :selectVal="bulletinPrompt"
  227. :number="requestData?.bulletin?.custom.customCount"
  228. :compare="requestData?.bulletin?.custom.customPromote"
  229. />
  230. <TrendCard
  231. title="新增联系人"
  232. unit="人"
  233. :selectVal="bulletinPrompt"
  234. :number="requestData?.bulletin?.contacts.contactsCount"
  235. :compare="requestData?.bulletin?.contacts.contactsPromote"
  236. />
  237. <TrendCard
  238. title="新增商机"
  239. unit="个"
  240. :selectVal="bulletinPrompt"
  241. :number="requestData?.bulletin?.businessOpportunity.businessOpportunityCount"
  242. :compare="requestData?.bulletin?.businessOpportunity.businessOpportunityPromote"
  243. />
  244. <TrendCard
  245. title="新增销售订单"
  246. unit="个"
  247. :selectVal="bulletinPrompt"
  248. :number="requestData?.bulletin?.salesOrder.salesOrderCount"
  249. :compare="requestData?.bulletin?.salesOrder.salesOrderPromote"
  250. />
  251. <TrendCard
  252. title="销售订单金额"
  253. unit="元"
  254. :selectVal="bulletinPrompt"
  255. :number="requestData?.bulletin?.salesOrdersPrice.salesOrdersPrice"
  256. :compare="requestData?.bulletin?.salesOrdersPrice.salesOrderPricePromote"
  257. />
  258. <TrendCard
  259. title="商机金额"
  260. unit="元"
  261. :selectVal="bulletinPrompt"
  262. :number="requestData?.bulletin?.businessOpportunityPrice.businessOpportunityPrice"
  263. :compare="requestData?.bulletin?.businessOpportunityPrice.businessOpportunityPromote"
  264. />
  265. <TrendCard
  266. title="新增线索"
  267. unit="个"
  268. :selectVal="bulletinPrompt"
  269. :number="requestData?.bulletin?.clue.clueCount"
  270. :compare="requestData?.bulletin?.clue.cluePromote"
  271. />
  272. </div>
  273. </div>
  274. <div class="my-8 flex gap-4 items-start">
  275. <div class="border-gray-200 border rounded p-3 flex-1">
  276. <div class="text-sm font-medium">数据汇总</div>
  277. <div class="flex gap-3 mb-8 mt-2">
  278. <div class="w-40">
  279. <el-select
  280. clearable
  281. size="small"
  282. :model-value="summaryPrompt.permission"
  283. @change="(value: any) => (summaryPrompt.permission = value)"
  284. >
  285. <el-option
  286. v-for="permission in permissionOptions"
  287. :label="permission.label"
  288. :value="permission.value"
  289. />
  290. </el-select>
  291. </div>
  292. <div class="w-40">
  293. <el-select
  294. ref="select2"
  295. clearable
  296. size="small"
  297. :model-value="summaryPrompt.date"
  298. @change="(value: any) => (summaryPrompt.date = value)"
  299. >
  300. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  301. <el-option label="自定义" value="ignore" disabled>
  302. <div class="flex gap-2 w-80">
  303. <el-date-picker
  304. size="small"
  305. :clearable="false"
  306. type="daterange"
  307. class="w-12"
  308. v-model="summaryPrompt.sliceDate"
  309. start-placeholder="开始日期"
  310. end-placeholder="结束日期"
  311. />
  312. <el-button
  313. size="small"
  314. type="primary"
  315. @click="
  316. () => {
  317. select2?.blur();
  318. summaryPrompt.date = 'ignore';
  319. }
  320. "
  321. >确认</el-button
  322. >
  323. </div>
  324. </el-option>
  325. </el-select>
  326. </div>
  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?.customDataSummary.newNum"
  334. />
  335. <SimpleCard
  336. title="转成交客户"
  337. unit="个"
  338. :number="requestData.summary?.customDataSummary.closeDealNum"
  339. v-if="false"
  340. />
  341. </div>
  342. <Divider title="商机汇总" />
  343. <div class="my-6 grid grid-cols-4 gap-2">
  344. <SimpleCard
  345. title="新增商机"
  346. unit="个"
  347. :number="requestData.summary?.businessOpportunityDataSummary.newNum"
  348. />
  349. <SimpleCard
  350. title="赢单商机"
  351. unit="个"
  352. :number="requestData.summary?.businessOpportunityDataSummary.winning"
  353. />
  354. <SimpleCard
  355. title="输单商机"
  356. unit="个"
  357. :number="requestData.summary?.businessOpportunityDataSummary.losting"
  358. />
  359. <SimpleCard
  360. title="商机总金额"
  361. unit="元"
  362. :number="requestData.summary?.businessOpportunityDataSummary.allAmountOfMoney"
  363. />
  364. </div>
  365. <Divider title="线索汇总" />
  366. <div class="my-6 grid grid-cols-4 gap-2">
  367. <SimpleCard
  368. title="新增线索"
  369. unit="个"
  370. :number="requestData.summary?.clueDataSummary.newNum"
  371. />
  372. <SimpleCard
  373. title="线索转商机"
  374. unit="个"
  375. :number="requestData.summary?.clueDataSummary.changeNum"
  376. />
  377. </div>
  378. </div>
  379. <div class="border-gray-200 border rounded p-3 flex-1">
  380. <div class="text-sm font-medium">商机阶段</div>
  381. <div class="flex gap-3 mb-8 mt-2">
  382. <div class="w-40">
  383. <el-select
  384. clearable
  385. size="small"
  386. :model-value="stagePrompt.permission"
  387. @change="(value: any) => (stagePrompt.permission = value)"
  388. >
  389. <el-option
  390. v-for="permission in permissionOptions"
  391. :label="permission.label"
  392. :value="permission.value"
  393. />
  394. </el-select>
  395. </div>
  396. <div class="w-40">
  397. <el-select
  398. ref="select3"
  399. clearable
  400. size="small"
  401. :model-value="stagePrompt.date"
  402. @change="(value: any) => (stagePrompt.date = value)"
  403. >
  404. <el-option v-for="date in dateOptions" :label="date.label" :value="date.value" />
  405. <el-option label="自定义" value="ignore" disabled>
  406. <div class="flex gap-2 w-80">
  407. <el-date-picker
  408. size="small"
  409. :clearable="false"
  410. type="daterange"
  411. class="w-12"
  412. v-model="stagePrompt.sliceDate"
  413. start-placeholder="开始日期"
  414. end-placeholder="结束日期"
  415. />
  416. <el-button
  417. size="small"
  418. type="primary"
  419. @click="
  420. () => {
  421. select3?.blur();
  422. stagePrompt.date = 'ignore';
  423. }
  424. "
  425. >确认</el-button
  426. >
  427. </div>
  428. </el-option>
  429. </el-select>
  430. </div>
  431. </div>
  432. <div class="h-60">
  433. <Echarts :option="chartOptions"></Echarts>
  434. </div>
  435. </div>
  436. </div>
  437. </section>
  438. </div>
  439. </template>