Jelajahi Sumber

Merge branches 'master' and 'master' of http://192.168.1.41:3000/outaozhen/cldV2react

outaozhen 5 tahun lalu
induk
melakukan
cc34020e77

+ 1 - 1
config/defaultSettings.js

@@ -10,7 +10,7 @@ const proSettings = {
   colorWeak: false,
   title: 'CLD.V2',
   pwa: false,
-  iconfontUrl: '//at.alicdn.com/t/font_2276974_nbe38935rtf.js',
+  iconfontUrl: '//at.alicdn.com/t/font_2276974_g91snduoax.js',
   splitMenus: true
 }
 export default proSettings

+ 2 - 2
src/pages/Customer/Business/components/BusinessDetail/Form.jsx

@@ -8,7 +8,7 @@ import ChangeStaff from '../ChangeStaff/index'
 import Step from './Step/index'
 
 const Form = props => {
-  const { business } = props
+  const { business, groupStatus } = props
 
   const changeCopInputData = {
     customerName: business.customerName,
@@ -77,7 +77,7 @@ const Form = props => {
           <h2 className="text-2xl pb-4">{business.name}</h2>
         </Col>
         <Col span={2} />
-        <Step />
+        <Step statusId={business.businessStatusId} id={business.id} groupStatus={groupStatus} />
       </Row>
       <EditableForm
         dataSource={business}

+ 148 - 40
src/pages/Customer/Business/components/BusinessDetail/Step/index.jsx

@@ -1,48 +1,156 @@
-import { Select } from 'antd'
-import React from 'react'
+/* eslint-disable no-nested-ternary */
+import { Modal, Tooltip, Menu, Dropdown, message } from 'antd'
+import React, { useMemo } from 'react'
 import styles from './index.less'
+import { useRequest, useDispatch } from 'umi'
+import { changeGroupStatus } from '@/services/customer'
+import { CloseOne, SmilingFaceWithSquintingEyes } from '@icon-park/react'
+import { MinusCircleOutlined, DownOutlined } from '@ant-design/icons'
 
-const Step = () => {
+const Step = ({ id, statusId, groupStatus = [] }) => {
+  const dispatch = useDispatch()
+  const { run: tryChangeStatus } = useRequest(params => changeGroupStatus(params), {
+    manual: true,
+    onSuccess: () => {
+      dispatch({
+        type: 'refresh/commitAction',
+        payload: 'business'
+      })
+    }
+  })
+
+  const stateMap = {
+    1: {
+      class: 'state-fail',
+      text: '输单',
+      icon: <CloseOne className="mr-1" />,
+      fontColor: 'text-red-500'
+    },
+    2: {
+      class: 'state-suc',
+      text: '赢单',
+      icon: <SmilingFaceWithSquintingEyes className="mr-1" />,
+      fontColor: 'text-primary'
+    },
+    3: {
+      class: 'state-invalid',
+      text: '无效',
+      icon: <MinusCircleOutlined className="mr-1" />,
+      fontColor: 'text-gray-500'
+    }
+  }
+  const op = useMemo(() => {
+    const mainItems = groupStatus.filter(item => !item.endProcess).sort((a, b) => a.sort - b.sort)
+    const otherItems = groupStatus.filter(item => item.endProcess).sort((a, b) => a.sort - b.sort)
+    const selectedItemIdx = mainItems.findIndex(item => item.selected)
+    const endItemIdx = otherItems.findIndex(item => item.id === statusId)
+    const isSelected = mainItems.findIndex(item => item.selected) !== -1
+    const isEnd = otherItems.findIndex(item => item.id === statusId) !== -1
+    return { mainItems, otherItems, isSelected, isEnd, selectedItemIdx, endItemIdx }
+  }, [id, statusId])
+
+  const menu = (
+    <Menu
+      className="w-120px"
+      onClick={e => {
+        if (!op.isSelected) {
+          return message.error('请选选择阶段再结束')
+        }
+        return Modal.confirm({
+          title: '提示',
+          content: `确认进行${op.otherItems.find(item => item.id === e.key).name}操作`,
+          onOk: () => {
+            tryChangeStatus({ id, businessStatusId: e.key })
+          }
+        })
+      }}>
+      {op.otherItems.map(item => (
+        <Menu.Item key={item.id}>
+          <div className="flex justify-between items-center">
+            <span className={stateMap[item.key]?.fontColor}>
+              {stateMap[item.key]?.icon}
+              {stateMap[item.key]?.text}
+            </span>
+            <span>{item.percentage}%</span>
+          </div>
+        </Menu.Item>
+      ))}
+    </Menu>
+  )
   return (
     <div className={['busi-state', styles.businessStep].join(' ')}>
-      <div className="busi-state-item state-suc">
-        <span className="title ">验证客户</span>
-        <div className="state-circle circle-left" />
-        <div className="state-arrow arrow-right" />
-      </div>
-      <div className="busi-state-item state-doing">
-        <span className="title">需求分析</span>
-        <div className="state-arrow arrow-left" />
-        <div className="state-arrow arrow-right" />
-      </div>
-      <div className="busi-state-item state-doing">
-        <span className="title">需求分析</span>
-        <div className="state-arrow arrow-left" />
-        <div className="state-arrow arrow-right" />
-      </div>
-      <div className="busi-state-item state-doing">
-        <span className="title">需求分析</span>
-        <div className="state-arrow arrow-left" />
-        <div className="state-arrow arrow-right" />
-      </div>
-      <div className="busi-state-item state-undo">
-        <span className="title">方案/报价</span>
-        <div className="state-arrow arrow-left" />
-        <div className="state-arrow arrow-right" />
-      </div>
-      <div className="busi-state-item state-undo">
+      {op.mainItems.map((item, idx) => (
+        <Tooltip
+          placement="bottom"
+          title={
+            <div>
+              <p>{item.name}</p>
+              <p>赢单率{item.percentage}%</p>
+            </div>
+          }
+          key={item.id}>
+          <div
+            onClick={() => {
+              if (!op.isEnd) {
+                if (idx < op.selectedItemIdx) {
+                  return
+                }
+                if (idx > op.selectedItemIdx + 1) {
+                  return
+                }
+                Modal.confirm({
+                  title: '提示',
+                  content: `确认进入${item.name}阶段`,
+                  onOk: () => {
+                    tryChangeStatus({ id, businessStatusId: item.id })
+                  }
+                })
+              }
+            }}
+            className={`busi-state-item ${
+              op.isEnd
+                ? item.selected
+                  ? 'state-suc'
+                  : 'state-invalid'
+                : op.selectedItemIdx === idx
+                ? 'state-doing'
+                : op.selectedItemIdx > idx
+                ? 'state-suc'
+                : 'state-undo'
+            }`}>
+            <span className="title ">{item.name}</span>
+            {idx === 0 ? (
+              <>
+                <div className="state-circle circle-left " />
+                <div className="state-arrow arrow-right" />
+              </>
+            ) : (
+              <>
+                <div className="state-arrow arrow-left " />
+                <div className="state-arrow arrow-right" />
+              </>
+            )}
+          </div>
+        </Tooltip>
+      ))}
+      <div
+        className={`busi-state-item ${
+          op.isEnd ? stateMap[op.otherItems[op.endItemIdx].endProcess].class : 'state-undo'
+        }`}>
         <span className="title">
-          <Select
-            dropdownStyle={{ width: '60px' }}
-            placeholder="结束"
-            dropdownMatchSelectWidth={false}
-            bordered={false}
-            options={[
-              { label: '赢单', value: '1' },
-              { label: '输单', value: '2' },
-              { label: '无效', value: '3' }
-            ]}
-          />
+          {op.isEnd ? (
+            <>
+              {stateMap[op.otherItems[op.endItemIdx].endProcess].icon}
+              {stateMap[op.otherItems[op.endItemIdx].endProcess].text}
+            </>
+          ) : (
+            <Dropdown overlay={menu}>
+              <div>
+                <span className="mr-1">结束</span>
+                <DownOutlined />
+              </div>
+            </Dropdown>
+          )}
         </span>
         <div className="state-arrow arrow-left" />
         <div className="state-circle circle-right" />

+ 12 - 4
src/pages/Customer/Business/components/BusinessDetail/index.jsx

@@ -18,14 +18,14 @@ const Detail = props => {
     business: {},
     log: {},
     serviceLog: {},
-    // software: {},
+    groupStatus: [],
     linkClient: {} // 关联的联系人
   })
 
   const initData = async id => {
     setState({ ...state, loading: true })
     const {
-      data: { business = {}, log = {}, linkClient = {}, service = {} },
+      data: { business = {}, log = {}, groupStatus = [], linkClient = {}, service = {} },
       code = -1
     } = await getBusinessDetailById({ id })
     if (code === consts.RET_CODE.SUCCESS) {
@@ -35,7 +35,15 @@ const Detail = props => {
           payload: 'business'
         })
       }
-      setState({ ...state, business, serviceLog: service, log, linkClient, loading: false })
+      setState({
+        ...state,
+        business,
+        groupStatus,
+        serviceLog: service,
+        log,
+        linkClient,
+        loading: false
+      })
     }
   }
 
@@ -80,7 +88,7 @@ const Detail = props => {
               </div>
             ) : null}
             <div className="sheet-left-panel pr-15px">
-              <Form business={state.business} />
+              <Form business={state.business} groupStatus={state.groupStatus} />
             </div>
           </Col>
           <Col span={12} className="sheet-box-right">

+ 21 - 5
src/pages/Customer/Business/index.jsx

@@ -1,5 +1,4 @@
 import React, { useState, useEffect, useRef } from 'react'
-import { Select } from 'antd'
 import { connect } from 'umi'
 import { getBusinessList } from '@/services/customer'
 import ProTable from '@ant-design/pro-table'
@@ -9,12 +8,14 @@ import Detail from './components/BusinessDetail'
 import { useModal } from '@/components/Modal'
 import { useTableScroll } from '@/hooks/useAutoTable'
 import CompanyDetail from '../Company/components/CompanyDetail'
+import { Funnel } from '@ant-design/charts'
 
 const Business = ({ dispatch, groupList, shouldUpdate }) => {
   const tRef = useRef(null)
   const { toggleDrawer, setDrawerProps } = useModal()
   const [state, setState] = useState({
-    orderIds: []
+    orderIds: [],
+    funnelData: groupList[0]?.items || []
   })
   useEffect(() => {
     if (!groupList.length) {
@@ -106,6 +107,11 @@ const Business = ({ dispatch, groupList, shouldUpdate }) => {
       title: '创建人'
     }
   ]
+  const conifg = {
+    xField: 'name',
+    yField: 'percentage',
+    legend: false
+  }
   const { getScrollRef, redoHeight } = useTableScroll(columns)
   return (
     <div className="h-full w-full flex flex-row justify-between">
@@ -113,19 +119,29 @@ const Business = ({ dispatch, groupList, shouldUpdate }) => {
         <div className="p-4 border-b-1 border-solid border-black border-opacity-10 bg-[#f7f9fa] flex justify-around items-center">
           <div className="w-1/2 text-md">商机漏斗</div>
           <div className="w-1/2">
-            {groupList[0]?.value && (
+            {/* {groupList[0]?.value && (
               <Select
                 size="small"
                 className="w-full"
                 bordered={false}
                 options={groupList}
                 defaultValue={groupList[0]?.value}
+                onChange={(_, option) => {
+                  setState({
+                    ...state,
+                    funnelData: option.items
+                      .filter(item => !item.endProcess)
+                      .map(item => ({ ...item, percentage: parseInt(item.percentage) }))
+                  })
+                }}
               />
-            )}
+            )} */}
           </div>
         </div>
         <div className="p-4 bg-white " style={{ height: 'calc(100% - 1rem*2 - 20px)' }}>
-          11
+          <div className="max-h-1/3">
+            <Funnel data={state.funnelData} {...conifg} />
+          </div>
         </div>
       </div>
       <div className="w-17/20">

+ 7 - 0
src/services/customer.js

@@ -214,3 +214,10 @@ export async function linkBusiWithClient(params) {
     data: params
   })
 }
+
+/** 设置商机状态 */
+export async function changeGroupStatus(params) {
+  return request.post('/business/change/group/status', {
+    data: params
+  })
+}