index.vue 16 KB

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