douhl hace 11 meses
padre
commit
6c7d33ff25

BIN
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/line.png


BIN
fhKeeper/formulahousekeeper/customerBuler-crm/src/assets/trend.png


+ 94 - 0
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/api.ts

@@ -0,0 +1,94 @@
+import { post, get } from '@/utils/request';
+
+export type RequestProps = {
+  /**
+   * @description 0-本月 1-本周 2-本年
+   */
+  dateType?: 0 | 1 | 2;
+  /**
+   * @description 0-仅本人 1-本人及下属 2-仅本部门 3-本部门及下属部门
+   */
+  queryType?: 0 | 1 | 2 | 3;
+  startDate?: string;
+  endDate?: string;
+};
+
+export type SummaryData = {
+  code: string;
+  data: {
+    clueDataSummary: {
+      changeNum: number;
+      newNum: number;
+    };
+    businessOpportunityDataSummary: {
+      winning: number;
+      allAmountOfMoney: number;
+      losting: number;
+      newNum: number;
+    };
+    customDataSummary: {
+      closeDealNum: number;
+      newNum: number;
+    };
+  };
+};
+
+export async function getSummaryData(
+  payload?: RequestProps
+): Promise<SummaryData> {
+  return await post('/order/dataSummary', payload);
+}
+
+export type StageData = {
+  code: string;
+  data: {
+    dataMap: {
+      stageName: string;
+      num: number;
+    }[];
+  };
+};
+
+export async function getStageData(payload?: RequestProps): Promise<StageData> {
+  return await post('/order/businessOpportunityStage', payload);
+}
+
+export type BulletinData = {
+  code: string;
+  data: {
+    custom: {
+      customPromote: string;
+      customCount: number;
+    };
+    businessOpportunity: {
+      businessOpportunityCount: number;
+      businessOpportunityPromote: string;
+    };
+    contacts: {
+      contactsCount: number;
+      contactsPromote: string;
+    };
+    salesOrder: {
+      salesOrderCount: number;
+      salesOrderPromote: string;
+    };
+    salesOrdersPrice: {
+      salesOrderPricePromote: string;
+      salesOrdersPrice: number;
+    };
+    clue: {
+      cluePromote: string;
+      clueCount: number;
+    };
+    businessOpportunityPrice: {
+      businessOpportunityPromote: string;
+      businessOpportunityPrice: number;
+    };
+  };
+};
+
+export async function getBulletinData(
+  payload?: RequestProps
+): Promise<BulletinData> {
+  return await post('/order/salesKit', payload);
+}

+ 13 - 3
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/components/SimpleCard.vue

@@ -2,10 +2,20 @@
   <div
     class="bg-gray-200 rounded-sm p-2 flex flex-col gap-1 text-xs text-gray-600"
   >
-    <span>转成交易</span>
+    <span>{{ title }}</span>
     <span class="text-gray-900">
-      <span>{{ 1 }}</span>
-      {{ '个' }}
+      <span>{{ number === null || number === undefined ? '--' : number }}</span>
+      {{ unit }}
     </span>
   </div>
 </template>
+
+<script lang="ts" setup>
+type Props = {
+  title: string;
+  number?: number;
+  unit: '个' | '人' | '元';
+};
+
+defineProps<Props>();
+</script>

+ 24 - 6
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/components/TrendCard.vue

@@ -3,25 +3,43 @@
     <div class="flex flex-col justify-between text-gray-500 text-xs gap-1">
       <div class="">{{ title }}</div>
       <div class="text-gray-800">
-        <span class="text-sm">{{ number }}</span>
+        <span class="text-sm">{{
+          number === null || number === undefined ? '--' : number
+        }}</span>
         {{ unit }}
       </div>
       <div>
         <span>较上月 </span>
-        <span class="text-xs text-red-600 inline-flex items-center">
-          <span>100%</span>
-          <el-icon><Top /></el-icon>
+        <span
+          class="text-xs text-red-600 inline-flex items-center"
+          v-if="compare"
+        >
+          <span>{{ compare.includes('-') ? compare.slice(1) : compare }}</span>
+          <el-icon>
+            <Bottom v-if="compare.includes('-')" />
+            <Top v-else />
+          </el-icon>
         </span>
+        <span v-else>--</span>
       </div>
     </div>
-    <div>11</div>
+    <div>
+      <img
+        width="60"
+        :style="{
+          transform: compare?.includes('-') ? 'rotate(66deg)' : 'rotate(180deg)'
+        }"
+        src="../../../assets/line.png"
+      />
+    </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 type Props = {
+  compare?: string;
   title: string;
-  number: number;
+  number?: number;
   unit: '个' | '人' | '元';
 };
 

+ 317 - 40
fhKeeper/formulahousekeeper/customerBuler-crm/src/pages/analysis/index.vue

@@ -1,18 +1,75 @@
 <script lang="ts" setup>
-import { ref, reactive } from 'vue';
+import { ref, reactive, onMounted, watchEffect } from 'vue';
 import TrendCard from './components/TrendCard.vue';
 import SimpleCard from './components/SimpleCard.vue';
 import Divider from './components/Divider.vue';
 import Echarts from '@/components/ReEcharts/index.vue';
 import { EChartsOption } from 'echarts';
+import { dayjs } from 'element-plus';
+import { ElLoading } from 'element-plus';
+import {
+  getSummaryData,
+  getBulletinData,
+  BulletinData,
+  getStageData,
+  RequestProps,
+  StageData,
+  SummaryData
+} from './api';
 
-const prompt = reactive({ summary: {}, stage: {}, bulletin: {} });
+const permissionOptions = [
+  {
+    label: '仅本人',
+    value: 0
+  },
+  {
+    label: '本人及下属',
+    value: 1
+  },
+  {
+    label: '仅本部门',
+    value: 2
+  },
+  {
+    label: '本部门及下属部门',
+    value: 3
+  }
+];
+const dateOptions = [
+  {
+    label: '本月',
+    value: 0
+  },
+  {
+    label: '本周',
+    value: 1
+  },
+  {
+    label: '本年',
+    value: 2
+  }
+];
 
-const option: EChartsOption = {
+const defineDate = ref<[Date, Date] | ''>('');
+
+const bulletinPrompt = reactive({ permission: undefined, date: undefined });
+const summaryPrompt = reactive({ permission: undefined, date: undefined });
+const stagePrompt = reactive({ permission: undefined, date: undefined });
+
+const requestData = reactive<{
+  bulletin: BulletinData['data'] | null;
+  summary: SummaryData['data'] | null;
+  stage: StageData['data'] | null;
+}>({
+  summary: null,
+  stage: null,
+  bulletin: null
+});
+
+const chartOptions: EChartsOption = reactive({
   grid: { top: 0, bottom: 20, left: 60 },
   yAxis: {
-    type: 'category',
-    data: ['验证客户', '赢单', '输单']
+    type: 'category'
   },
   xAxis: {
     type: 'value'
@@ -20,11 +77,67 @@ const option: EChartsOption = {
   series: [
     {
       barWidth: 20,
-      data: [0, 20, 40, 60, 80, 100],
+      data: [10, 30, 30, 30, 30],
       type: 'bar'
     }
   ]
+});
+
+const queryBulletin = async (payload?: RequestProps) => {
+  const bulletinResult = await getBulletinData(payload);
+  requestData.bulletin = bulletinResult.data;
 };
+
+const querySummary = async (payload?: RequestProps) => {
+  const summaryResult = await getSummaryData(payload);
+  requestData.summary = summaryResult.data;
+};
+
+const queryStage = async (payload?: RequestProps) => {
+  const stageResult = await getStageData(payload);
+  const data = stageResult.data;
+
+  // @ts-ignore
+  chartOptions.yAxis.data = data.dataMap.map((map) => map.stageName);
+  // @ts-ignore
+  chartOptions.series[0].data = data.dataMap.map((map) => map.num);
+};
+
+watchEffect(() => {
+  queryBulletin({
+    ...(bulletinPrompt.date === 'defineDate'
+      ? {
+          startDate: dayjs(defineDate.value[0]).valueOf(),
+          endData: dayjs(defineDate.value[1]).valueOf()
+        }
+      : { dateType: bulletinPrompt.date }),
+    queryType: bulletinPrompt.permission
+  });
+});
+
+watchEffect(() => {
+  queryStage({
+    ...(stagePrompt.date === 'defineDate'
+      ? {
+          startDate: dayjs(defineDate.value[0]).valueOf(),
+          endData: dayjs(defineDate.value[1]).valueOf()
+        }
+      : { dateType: stagePrompt.date }),
+    queryType: stagePrompt.permission
+  });
+});
+
+watchEffect(() => {
+  querySummary({
+    ...(summaryPrompt.date === 'defineDate'
+      ? {
+          startDate: dayjs(defineDate.value[0]).valueOf(),
+          endData: dayjs(defineDate.value[1]).valueOf()
+        }
+      : { dateType: summaryPrompt.date }),
+    queryType: summaryPrompt.permission
+  });
+});
 </script>
 
 <template>
@@ -34,25 +147,98 @@ const option: EChartsOption = {
     <section class="flex-[4]">
       <div class="flex gap-3 mb-4">
         <div class="w-40">
-          <el-select size="small"></el-select>
+          <el-select
+            clearable
+            size="small"
+            :model-value="bulletinPrompt.permission"
+            @change="(value) => (bulletinPrompt.permission = value)"
+          >
+            <el-option
+              v-for="permission in permissionOptions"
+              :label="permission.label"
+              :value="permission.value"
+            />
+          </el-select>
         </div>
         <div class="w-40">
-          <el-select size="small"></el-select>
+          <el-select
+            clearable
+            size="small"
+            :model-value="bulletinPrompt.date"
+            @change="(value) => (bulletinPrompt.date = value)"
+          >
+            <el-option
+              v-for="date in dateOptions"
+              :label="date.label"
+              :value="date.value"
+            />
+            <el-option v-if="defineDate" label="自定义" value="defineDate" />
+          </el-select>
         </div>
       </div>
       <div class="border-gray-200 border rounded p-3">
         <div class="flex gap-1.5 items-center mb-3">
+          <img width="16" src="../../assets/trend.png" />
           <div class="text-sm font-medium">销售简报</div>
           <el-icon><QuestionFilled class="text-gray-500 text-sm" /></el-icon>
         </div>
         <div class="grid xl:grid-cols-4 lg:grid-cols-3 grid-cols-2 gap-4">
-          <TrendCard title="新增客户" :number="4" unit="人" />
-          <TrendCard title="新增联系人" :number="4" unit="人" />
-          <TrendCard title="新增商机" :number="4" unit="个" />
-          <TrendCard title="新增销售订单" :number="4" unit="个" />
-          <TrendCard title="销售订单金额" :number="4" unit="元" />
-          <TrendCard title="商机金额" :number="4" unit="元" />
-          <TrendCard title="新增线索" :number="4" unit="个" />
+          <TrendCard
+            title="新增客户"
+            unit="人"
+            :number="requestData?.bulletin?.custom.customCount"
+            :compare="requestData?.bulletin?.custom.customPromote"
+          />
+          <TrendCard
+            title="新增联系人"
+            unit="人"
+            :number="requestData?.bulletin?.contacts.contactsCount"
+            :compare="requestData?.bulletin?.contacts.contactsPromote"
+          />
+          <TrendCard
+            title="新增商机"
+            unit="个"
+            :number="
+              requestData?.bulletin?.businessOpportunity
+                .businessOpportunityCount
+            "
+            :compare="
+              requestData?.bulletin?.businessOpportunity
+                .businessOpportunityPromote
+            "
+          />
+          <TrendCard
+            title="新增销售订单"
+            unit="个"
+            :number="requestData?.bulletin?.salesOrder.salesOrderCount"
+            :compare="requestData?.bulletin?.salesOrder.salesOrderPromote"
+          />
+          <TrendCard
+            title="销售订单金额"
+            unit="元"
+            :number="requestData?.bulletin?.salesOrdersPrice.salesOrdersPrice"
+            :compare="
+              requestData?.bulletin?.salesOrdersPrice.salesOrderPricePromote
+            "
+          />
+          <TrendCard
+            title="商机金额"
+            unit="元"
+            :number="
+              requestData?.bulletin?.businessOpportunityPrice
+                .businessOpportunityPrice
+            "
+            :compare="
+              requestData?.bulletin?.businessOpportunityPrice
+                .businessOpportunityPromote
+            "
+          />
+          <TrendCard
+            title="新增线索"
+            unit="个"
+            :number="requestData?.bulletin?.clue.clueCount"
+            :compare="requestData?.bulletin?.clue.cluePromote"
+          />
         </div>
       </div>
       <div class="my-8 flex gap-4 items-start">
@@ -60,67 +246,158 @@ const option: EChartsOption = {
           <div class="text-sm font-medium">数据汇总</div>
           <div class="flex gap-3 mb-8 mt-2">
             <div class="w-40">
-              <el-select size="small"></el-select>
+              <el-select
+                clearable
+                size="small"
+                :model-value="summaryPrompt.permission"
+                @change="(value) => (summaryPrompt.permission = value)"
+              >
+                <el-option
+                  v-for="permission in permissionOptions"
+                  :label="permission.label"
+                  :value="permission.value"
+                />
+              </el-select>
             </div>
             <div class="w-40">
-              <el-select size="small"></el-select>
+              <el-select
+                clearable
+                size="small"
+                :model-value="summaryPrompt.date"
+                @change="(value) => (summaryPrompt.date = value)"
+              >
+                <el-option
+                  v-for="date in dateOptions"
+                  :label="date.label"
+                  :value="date.value"
+                />
+                <el-option
+                  v-if="defineDate"
+                  label="自定义"
+                  value="defineDate"
+                />
+              </el-select>
             </div>
           </div>
           <Divider title="客户汇总" />
           <div class="my-6 grid grid-cols-4 gap-2">
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
+            <SimpleCard
+              title="新增客户"
+              unit="个"
+              :number="requestData.summary?.customDataSummary.newNum"
+            />
+            <SimpleCard
+              title="转成交客户"
+              unit="个"
+              :number="requestData.summary?.customDataSummary.closeDealNum"
+            />
           </div>
           <Divider title="商机汇总" />
           <div class="my-6 grid grid-cols-4 gap-2">
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
+            <SimpleCard
+              title="新增商机"
+              unit="个"
+              :number="
+                requestData.summary?.businessOpportunityDataSummary.newNum
+              "
+            />
+            <SimpleCard
+              title="赢单商机"
+              unit="个"
+              :number="
+                requestData.summary?.businessOpportunityDataSummary.winning
+              "
+            />
+            <SimpleCard
+              title="输单商机"
+              unit="个"
+              :number="
+                requestData.summary?.businessOpportunityDataSummary.losting
+              "
+            />
+            <SimpleCard
+              title="商机总金额"
+              unit="元"
+              :number="
+                requestData.summary?.businessOpportunityDataSummary
+                  .allAmountOfMoney
+              "
+            />
           </div>
           <Divider title="线索汇总" />
           <div class="my-6 grid grid-cols-4 gap-2">
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
-            <SimpleCard />
+            <SimpleCard
+              title="新增线索"
+              unit="个"
+              :number="requestData.summary?.clueDataSummary.newNum"
+            />
+            <SimpleCard
+              title="线索转商机"
+              unit="个"
+              :number="requestData.summary?.clueDataSummary.changeNum"
+            />
           </div>
         </div>
         <div class="border-gray-200 border rounded p-3 flex-1">
           <div class="text-sm font-medium">商机阶段</div>
           <div class="flex gap-3 mb-8 mt-2">
             <div class="w-40">
-              <el-select size="small"></el-select>
+              <el-select
+                clearable
+                size="small"
+                :model-value="stagePrompt.permission"
+                @change="(value) => (stagePrompt.permission = value)"
+              >
+                <el-option
+                  v-for="permission in permissionOptions"
+                  :label="permission.label"
+                  :value="permission.value"
+                />
+              </el-select>
             </div>
             <div class="w-40">
-              <el-select size="small"></el-select>
+              <el-select
+                clearable
+                size="small"
+                :model-value="stagePrompt.date"
+                @change="(value) => (stagePrompt.date = value)"
+              >
+                <el-option
+                  v-for="date in dateOptions"
+                  :label="date.label"
+                  :value="date.value"
+                />
+                <el-option
+                  v-if="defineDate"
+                  label="自定义"
+                  value="defineDate"
+                />
+              </el-select>
             </div>
           </div>
           <div class="h-60">
-            <Echarts :option="option"></Echarts>
+            <Echarts :option="chartOptions"></Echarts>
           </div>
         </div>
       </div>
     </section>
     <nav class="flex-1 min-w-60 sticky right-0 top-9">
       <div class="border border-gray-200 rounded w-3/4 text-sm text-gray-500">
-        <div class="p-2 bg-sky-100 border-l-2 border-blue-700">仅本人</div>
-        <div class="p-2">本人及下属</div>
-        <div class="p-2">仅本部门</div>
-        <div class="p-2">本部门及下属</div>
+        <div class="p-2" v-for="primission in permissionOptions">
+          {{ primission.label }}
+        </div>
       </div>
       <div class="border border-gray-200 rounded text-sm text-gray-500 mt-4">
-        <div class="p-2 bg-sky-100 border-l-2 border-blue-700">本月</div>
-        <div class="p-2">本周</div>
-        <div class="p-2">本年</div>
+        <div class="p-2" v-for="date in dateOptions">{{ date.label }}</div>
         <div class="p-2">
           <div>自定义</div>
           <el-date-picker
             type="daterange"
             class="max-w-full mt-2"
-          ></el-date-picker>
+            v-model="defineDate"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+          />
         </div>
       </div>
     </nav>