Sfoglia il codice sorgente

refactor: 恢复锁库

outaozhen 4 anni fa
parent
commit
b95d5446bf
20 ha cambiato i file con 1999 aggiunte e 0 eliminazioni
  1. 65 0
      src/pages/Product/Lock/LockCount/index.jsx
  2. 86 0
      src/pages/Product/Lock/LockStore/components/ChangeClient/index.jsx
  3. 60 0
      src/pages/Product/Lock/LockStore/components/ChangeClient/index.less
  4. 28 0
      src/pages/Product/Lock/LockStore/components/CopyAuthorizeStr/index.jsx
  5. 33 0
      src/pages/Product/Lock/LockStore/components/DownUpdateFile/index.jsx
  6. 157 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/Form.jsx
  7. 362 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/LockModal.jsx
  8. 38 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/LowerList.jsx
  9. 191 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/TabList.jsx
  10. 14 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/consts.js
  11. 115 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/index.jsx
  12. 29 0
      src/pages/Product/Lock/LockStore/components/LocksDetail/index.less
  13. 98 0
      src/pages/Product/Lock/LockStore/components/ReceiveLock.jsx
  14. 94 0
      src/pages/Product/Lock/LockStore/components/ReceiveNewLock.jsx
  15. 19 0
      src/pages/Product/Lock/LockStore/components/SoftwareDetail/LowerList.jsx
  16. 45 0
      src/pages/Product/Lock/LockStore/components/SoftwareDetail/StaffDetail.jsx
  17. 37 0
      src/pages/Product/Lock/LockStore/components/SoftwareDetail/TabList.jsx
  18. 49 0
      src/pages/Product/Lock/LockStore/components/SoftwareDetail/index.jsx
  19. 444 0
      src/pages/Product/Lock/LockStore/index.jsx
  20. 35 0
      src/pages/Product/Lock/model.js

+ 65 - 0
src/pages/Product/Lock/LockCount/index.jsx

@@ -0,0 +1,65 @@
+import React from 'react'
+import ProTable from '@ant-design/pro-table'
+
+const Lockcount = () => {
+  // const { modalVisible, onCancel } = props;
+  const columns = [
+    {
+      title: '办事处',
+      dataIndex: 'keyNum',
+      key: 'keyNum',
+      width: 150
+    },
+    {
+      title: '员工',
+      dataIndex: 'product',
+      key: 'product',
+      width: 150
+    },
+    {
+      title: '锁合计',
+      dataIndex: 'category',
+      key: 'category',
+      width: 550,
+      children: [
+        {
+          title: '未使用',
+          dataIndex: 'age',
+          key: 'age',
+          width: 150
+        },
+        {
+          title: '未使用',
+          dataIndex: 'kk',
+          key: 'kk',
+          width: 150
+        },
+        {
+          title: '合计',
+          dataIndex: 'nn',
+          key: 'nn',
+          width: 150
+        }
+      ]
+    }
+  ]
+
+  return (
+    <div>
+      <ProTable
+        rowKey={record => record.id}
+        search={false}
+        headerTitle="使用统计"
+        bordered
+        columns={columns}
+        // toolbar={{
+        //   search: {
+        //     allowClear: true
+        //   }
+        // }}
+      />
+    </div>
+  )
+}
+
+export default Lockcount

+ 86 - 0
src/pages/Product/Lock/LockStore/components/ChangeClient/index.jsx

@@ -0,0 +1,86 @@
+import React from 'react'
+import { Switch, LinkOne } from '@icon-park/react'
+import { useModal } from '@/components/Modal'
+import { Card, Tooltip } from 'antd'
+import consts from '@/basic/consts'
+import { useDispatch } from 'dva'
+import { isEmpty, isNullOrUnDef } from '@/utils/is'
+import ConnectClient from '@/pages/Customer/Client/components/ConnectClient'
+import ContactDetail from '@/pages/Customer/Client/components/ClientDetail'
+import styles from './index.less'
+
+const ChangeClient = props => {
+  const dispatch = useDispatch()
+  const { toggleModal, setModalProps, toggleDrawer, setDrawerProps } = useModal()
+  const { clientName = '', longleId, clientId, actionPayload } = props.dataSource
+  if (isNullOrUnDef(clientName) || isEmpty(clientName)) return null
+  const refreshClient = () => {
+    dispatch({
+      type: 'refresh/commitAction',
+      payload: actionPayload
+    })
+  }
+  const handleConnectCustomer = () => {
+    setModalProps({
+      zIndex: 1002,
+      width: '60vw',
+      modalRender: () => (
+        <ConnectClient
+          onSelect={refreshClient}
+          dataId={longleId}
+          preUrl="software"
+          showFooter={false}
+        />
+      ),
+      onCancel: () => toggleModal()
+    })
+    toggleModal(true)
+  }
+  const notValueRender = (
+    <div onClick={handleConnectCustomer} className={styles.notCompanyContent}>
+      <LinkOne size="14" />
+      <span className="ml-5px">关联客户</span>
+    </div>
+  )
+  const handleCompanyClick = () => {
+    toggleDrawer()
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <ContactDetail dataId={clientId} />
+    })
+    toggleDrawer()
+  }
+  const normalValueRender = (
+    <Card size="small">
+      <div className="flex justify-between">
+        {/* <div className={styles.nameContent} onClick={handleCompanyClick}> */}
+        <div className={styles.nameContent} onClick={handleCompanyClick}>
+          {clientName}
+        </div>
+        <div className="max-w-55px">
+          <Tooltip
+            placement="right"
+            title="更换客户"
+            className="ml-5px"
+            onClick={handleConnectCustomer}>
+            <span className={styles.switchIcon}>
+              <Switch />
+            </span>
+          </Tooltip>
+        </div>
+      </div>
+    </Card>
+  )
+  return (
+    <div className={styles.companyInput}>
+      {clientId !== consts.ENCRYPTION_ZERO ? normalValueRender : notValueRender}
+    </div>
+  )
+}
+
+export default ChangeClient

+ 60 - 0
src/pages/Product/Lock/LockStore/components/ChangeClient/index.less

@@ -0,0 +1,60 @@
+.companyInput {
+  padding: 8px 12px 6px;
+  color: @primary-color;
+  background: #f7f7f7;
+  border: 1px solid #f7f7f7;
+  &:hover {
+    background: #f2f2f2;
+    border-color: #f2f2f2;
+  }
+  :global(.ant-card-small > .ant-card-body) {
+    padding: 0.5rem;
+  }
+  .notCompanyContent {
+    cursor: pointer;
+    &:hover {
+      color: #967bbd;
+    }
+  }
+  .nameContent {
+    color: @primary-color;
+    cursor: pointer;
+    &:hover {
+      color: #967bbd;
+    }
+  }
+  .nameContentWidth {
+    width: 70vw;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+  .unlinkIcon {
+    padding: 0 3px;
+    color: #fd3995;
+    line-height: 1.15rem;
+    border: 1px solid #fd3995;
+    border-radius: 0.25rem;
+    cursor: pointer;
+    &:hover {
+      color: #ffffff;
+      background-color: #fd3995;
+      border-color: #fd3995;
+      transition: all 0.2s ease-in-out;
+    }
+  }
+  .switchIcon {
+    padding: 0 3px;
+    color: #886ab5;
+    line-height: 1.15rem;
+    border: 1px solid #886ab5;
+    border-radius: 0.25rem;
+    cursor: pointer;
+    &:hover {
+      color: #ffffff;
+      background-color: #886ab5;
+      border-color: #886ab5;
+      transition: all 0.2s ease-in-out;
+    }
+  }
+}

+ 28 - 0
src/pages/Product/Lock/LockStore/components/CopyAuthorizeStr/index.jsx

@@ -0,0 +1,28 @@
+import React from 'react'
+import { Card, message, Tooltip } from 'antd'
+import { Copy } from '@icon-park/react'
+import { CopyToClipboard } from 'react-copy-to-clipboard'
+import styles from '@/pages/Product/Lock/LockStore/components/ChangeClient/index.less'
+import { isEmpty, isNullOrUnDef } from '@/utils/is'
+
+const CopyAuthorizeStr = ({ authorizeStr }) => {
+  if (isNullOrUnDef(authorizeStr) || isEmpty(authorizeStr)) return null
+  return (
+    <div className={styles.companyInput}>
+      <Card size="small">
+        <div className="flex justify-between">
+          <div className={`${styles.nameContent} ${styles.nameContentWidth}`}>{authorizeStr}</div>
+          <CopyToClipboard onCopy={() => message.success('复制成功')} text={authorizeStr}>
+            <Tooltip placement="right" title="复制" className="ml-5px">
+              <span className={styles.switchIcon}>
+                <Copy />
+              </span>
+            </Tooltip>
+          </CopyToClipboard>
+        </div>
+      </Card>
+    </div>
+  )
+}
+
+export default CopyAuthorizeStr

+ 33 - 0
src/pages/Product/Lock/LockStore/components/DownUpdateFile/index.jsx

@@ -0,0 +1,33 @@
+import React from 'react'
+import { Card, Tooltip } from 'antd'
+import { Download } from '@icon-park/react'
+import styles from '@/pages/Product/Lock/LockStore/components/ChangeClient/index.less'
+import { isEmpty, isNullOrUnDef } from '@/utils/is'
+
+const DownUpdateFile = ({ updateFile }) => {
+  const NewupdateFile = updateFile?.replace('/upload/longle/', '')
+  if (isNullOrUnDef(updateFile) || isEmpty(updateFile)) return null
+  const handleDownExcel = () => {
+    const oa = document.createElement('a')
+    oa.href = `https://cld.smartcost.com.cn/${updateFile}`
+    oa.setAttribute('target', '_blank')
+    document.body.appendChild(oa)
+    oa.click()
+  }
+  return (
+    <div className={styles.companyInput}>
+      <Card size="small">
+        <div className="flex justify-between">
+          <div className={`${styles.nameContent} ${styles.nameContentWidth}`}>{NewupdateFile}</div>
+          <Tooltip placement="right" title="下载" className="ml-5px">
+            <span className={styles.switchIcon}>
+              <Download onClick={handleDownExcel} />
+            </span>
+          </Tooltip>
+        </div>
+      </Card>
+    </div>
+  )
+}
+
+export default DownUpdateFile

+ 157 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/Form.jsx

@@ -0,0 +1,157 @@
+import React from 'react'
+import { Row, Col, Divider } from 'antd'
+import { useModal } from '@/components/Modal'
+import { ProductLabel } from '@/pages/Product/Lock/LockStore'
+import ChangeClient from '@/pages/Product/Lock/LockStore/components/ChangeClient'
+import CopyAuthorizeStr from '../CopyAuthorizeStr'
+import DownUpdateFile from '../DownUpdateFile'
+import CompanyDetail from '@/pages/Customer/Company/components/CompanyDetail'
+import { dayjsFormat } from '@/utils/utils'
+
+export const ChangeCompMap = {
+  LONGLE: {
+    key: 'longle',
+    title: '软件锁'
+  }
+}
+
+const LockForm = props => {
+  const { longle, customerId } = props
+  const { toggleDrawer, setDrawerProps } = useModal()
+  const changeCopInputData = {
+    clientName: longle.client,
+    longleId: longle.id,
+    clientId: longle.clientId,
+    actionPayload: ChangeCompMap.LONGLE.key
+  }
+  const handleCompanyClick = () => {
+    toggleDrawer()
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <CompanyDetail dataId={customerId} />
+    })
+    toggleDrawer()
+  }
+  // 出库状态
+  const longleStockStatusNum = {
+    1: '生成',
+    2: '接收'
+  }
+  // 销售状态
+  const longleSellStatusNum = {
+    1: '借出',
+    2: '销售',
+    3: '赠送'
+  }
+  // 维护状态
+  const longlePreserveStatusNum = {
+    1: '升级',
+    2: '更换',
+    3: '收回'
+  }
+  // 使用新状态
+  // const longleNewStatusNum = {
+  //   0: '未使用',
+  //   1: '使用'
+  // }
+  return (
+    <div>
+      <Row>
+        <Col span={16}>
+          <h2 className="text-2xl pb-4">{longle.keyNum}</h2>
+        </Col>
+        <Col span={8} className="text-right text-gray-500">
+          {longle.staffName} 创建于 {longle.makeDay}
+        </Col>
+      </Row>
+      <Row>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>锁号</Col>
+        <Col span={18}>{longle.keyNum}</Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>产品</Col>
+        <Col span={18}>
+          <ProductLabel data={longle.product} />
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>期限</Col>
+        <Col span={18}>{longle.allotedTime}</Col>
+      </Row>
+      <Row>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={12}>
+          <Row>
+            <Col span={9}>出库状态</Col>
+            <Col span={13}>{longleStockStatusNum[longle.stockStatus]}</Col>
+          </Row>
+        </Col>
+        <Col span={12}>
+          <Row>
+            <Col span={9}>出库时间</Col>
+            <Col span={13}>{dayjsFormat(longle.stockTime, 'YYYY-MM-DD')}</Col>
+          </Row>
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={12}>
+          <Row>
+            <Col span={9}>销售状态</Col>
+            <Col span={13}>{longleSellStatusNum[longle.sellStatus]}</Col>
+          </Row>
+        </Col>
+        <Col span={12}>
+          <Row>
+            <Col span={9}>销售时间</Col>
+            <Col span={13}>{dayjsFormat(longle.sellTime, 'YYYY-MM-DD')}</Col>
+          </Row>
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={12}>
+          <Row>
+            <Col span={9}>维护状态</Col>
+            <Col span={13}>{longlePreserveStatusNum[longle.preserveStatus]}</Col>
+          </Row>
+        </Col>
+        <Col span={12}>
+          <Row>
+            <Col span={9}>维护时间</Col>
+            <Col span={13}>{dayjsFormat(longle.preserveTime, 'YYYY-MM-DD')}</Col>
+          </Row>
+        </Col>
+        {/* <Col span={6}>使用状态</Col> */}
+        {/* <Col span={18}>{longleStatusNum[longle.status]}</Col> */}
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>负责人</Col>
+        <Col span={18}>{longle.responsible}</Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>单位名称</Col>
+        <Col span={18}>
+          <span className="textPurple text-hex-967bbd cursor-pointer" onClick={handleCompanyClick}>
+            {longle.customerName}
+          </span>
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>客户</Col>
+        <Col span={18}>
+          <ChangeClient dataSource={changeCopInputData} />
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>注册码</Col>
+        <Col span={18}>
+          <CopyAuthorizeStr authorizeStr={longle.authorizeStr} />
+        </Col>
+        <Divider style={{ margin: '12px 0' }} />
+        <Col span={6}>升级文件</Col>
+        <Col span={18}>
+          <DownUpdateFile updateFile={longle.updateFile} />
+        </Col>
+      </Row>
+    </div>
+  )
+}
+
+export default LockForm

+ 362 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/LockModal.jsx

@@ -0,0 +1,362 @@
+import React, { useState, useEffect, useRef } from 'react'
+import { Modal, Form, Select, Input, Spin, Button, Radio, Popconfirm, message } from 'antd'
+import styles from './index.less'
+import { SearchOutlined } from '@ant-design/icons'
+import { queryClient } from '@/services/customer'
+import { queryLock } from '@/services/product'
+import consts from '@/basic/consts'
+import { useDebounceFn } from 'ahooks'
+import { apiSoftwareAddLonglelog } from '@/services/product'
+import { useDispatch, connect } from 'umi'
+// import { createUid } from '@/utils/utils'
+// import DragIniter from '@/components/Modal/src/components/DragIniter'
+import { ProFormTextArea } from '@ant-design/pro-form'
+
+export const lockTypeEnum = {
+  REMARK: 0, // 备注
+  UPDATE: 1, // 升级
+  CHANGE: 2, // 更换
+  RECOVER: 3, // 收回
+  RECEIVE: 5, // 接收
+  SALE: 6
+}
+
+const LockModal = props => {
+  const modalTitleEnum = {
+    1: '升级软件锁',
+    0: '备注信息',
+    3: '确认收回',
+    5: '确认接收'
+  }
+  const placeholderEnum = {
+    6: '搜索客户(姓名、手机、QQ、公司名称)',
+    2: '输入新锁号'
+  }
+  const [form] = Form.useForm()
+  const dispatch = useDispatch()
+  const scrollRef = useRef()
+  const { visible, onCancel, onConfirm, loading, type, longleId, keyNum, prodList } = props
+  const layout = {
+    layout: 'vertical'
+  }
+  const [state, setState] = useState({
+    loading: false,
+    longle: [],
+    client: [],
+    search: '',
+    sellStatus: 1,
+    noMore: false,
+    current: 1,
+    pageSize: 10,
+    total: 0
+  })
+
+  // 获取软件锁产品列表
+  function fetchSoftProducts() {
+    dispatch({
+      type: 'lock/fetchProdList',
+      payload: 'longleId'
+    })
+  }
+
+  const initData = async params => {
+    setState({ ...state, loading: true })
+    if (type === lockTypeEnum.CHANGE) {
+      const { data: { longle, total } = { longle: [], total: 0 }, code = -1 } = await queryLock({
+        ...params,
+        waiver: true
+      })
+      if (code === consts.RET_CODE.SUCCESS) {
+        if (params?.current === 1) {
+          scrollRef.current?.scrollTo({ top: 0 })
+          // 请求的是第一页
+          setState({ ...state, longle, total, loading: false, ...params })
+        } else {
+          setState({
+            ...state,
+            longle: state.longle.concat(longle),
+            total,
+            loading: false,
+            ...params
+          })
+        }
+      }
+    }
+    if (type === lockTypeEnum.SALE) {
+      const { data: { client, total } = { client: [], total: 0 }, code = -1 } = await queryClient({
+        ...params,
+        waiver: true
+      })
+      if (code === consts.RET_CODE.SUCCESS) {
+        if (params?.current === 1) {
+          scrollRef.current?.scrollTo({ top: 0 })
+          // 请求的是第一页
+          setState({
+            ...state,
+            client,
+            total,
+            loading: false,
+            ...params
+          })
+        } else {
+          setState({
+            ...state,
+            client: state.client.concat(client),
+            total,
+            loading: false,
+            ...params
+          })
+        }
+      }
+    }
+  }
+  const onChange = e => {
+    const { value = '' } = e.target || e.currentTarget
+    if (type === lockTypeEnum.CHANGE || type === lockTypeEnum.SALE) {
+      const params = { search: value, current: 1, pageSize: state.pageSize }
+      if (type === lockTypeEnum.SALE) {
+        params.sellStatus = state.sellStatus
+      }
+      initData(params)
+    }
+  }
+
+  const { run: handleInputChange } = useDebounceFn(onChange)
+  useEffect(() => {
+    if (visible) {
+      if (type === lockTypeEnum.CHANGE) {
+        initData({ current: 1, pageSize: 10 })
+        return
+      }
+      if (type === lockTypeEnum.SALE) {
+        initData({ current: 1, pageSize: 10, sellStatus: state.sellStatus })
+        return
+      }
+      form.resetFields()
+      if (type === lockTypeEnum.RECOVER) {
+        form.setFieldsValue({ longleId })
+      }
+    }
+  }, [visible, type])
+
+  const handleonOk = () => {
+    form.validateFields().then(values => {
+      onConfirm({ ...values, preserveStatus: type })
+      dispatch({
+        type: 'refresh/commitAction',
+        payload: 'longle'
+      })
+    })
+  }
+
+  const checkGroupOnChange = e => {
+    // const logStatusEnum = {
+    //   3: 1,
+    //   4: 2,
+    //   9: 3
+    // }
+    const { value = '' } = e.target || {}
+    initData({
+      // logStatus: logStatusEnum[state.logStatus],
+      sellStatus: value,
+      current: 1,
+      pageSize: 10,
+      search: state.search
+    })
+  }
+
+  // 关联客户
+  const connectClient = async id => {
+    const { code = -1 } = await apiSoftwareAddLonglelog({
+      clientId: id,
+      sellStatus: state.sellStatus,
+      longleId
+    })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success('新增成功')
+      dispatch({
+        type: 'refresh/commitAction',
+        payload: 'longle'
+      })
+      onCancel()
+    }
+  }
+
+  // 更换软件锁
+  const connectLongle = async id => {
+    const { code = -1 } = await apiSoftwareAddLonglelog({
+      newLongleId: id,
+      preserveStatus: type,
+      longleId
+    })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success('新增成功')
+      dispatch({
+        type: 'refresh/commitAction',
+        payload: 'longle'
+      })
+      onCancel()
+    }
+  }
+
+  const handleListScroll = () => {
+    // 未滚动到底部
+    if (
+      scrollRef.current.scrollHeight - scrollRef.current.clientHeight <=
+      scrollRef.current.scrollTop
+    ) {
+      // 已到底部
+      if (state.current * state.pageSize > state.total) {
+        setState({ ...state, noMore: true })
+        return
+      }
+      const params = { current: state.current + 1, pageSize: state.pageSize, search: state.search }
+      if (type === lockTypeEnum.SALE) params.sellStatus = state.sellStatus
+      state.current * state.pageSize < state.total && initData(params)
+    }
+  }
+
+  return (
+    <Modal
+      getContainer={false}
+      title={
+        modalTitleEnum[type] ?? (
+          <Input
+            className={styles.inputSearch}
+            placeholder={placeholderEnum[type]}
+            prefix={<SearchOutlined />}
+            onChange={handleInputChange}
+            allowClear={true}
+          />
+        )
+      }
+      visible={visible}
+      confirmLoading={loading}
+      onCancel={onCancel}
+      width={800}
+      footer={
+        type !== lockTypeEnum.UPDATE &&
+        type !== lockTypeEnum.RECOVER &&
+        type !== lockTypeEnum.RECEIVE &&
+        type !== lockTypeEnum.REMARK ? null : (
+          <div>
+            <Button className="mr-3" onClick={onCancel}>
+              取消
+            </Button>
+            <Button key="submit" type="primary" loading={loading} onClick={handleonOk}>
+              确定
+            </Button>
+          </div>
+        )
+      }>
+      {type === lockTypeEnum.SALE ? (
+        <div className="text-center mb-6">
+          <Radio.Group value={state.sellStatus} buttonStyle="solid" onChange={checkGroupOnChange}>
+            <Radio.Button value={1}>借出</Radio.Button>
+            <Radio.Button value={2}>销售</Radio.Button>
+            <Radio.Button value={3}>赠送</Radio.Button>
+          </Radio.Group>
+        </div>
+      ) : null}
+      {type === lockTypeEnum.CHANGE || type === lockTypeEnum.SALE ? (
+        <div
+          className={styles.modalContainer}
+          ref={scrollRef}
+          onScroll={handleListScroll}
+          style={{ paddingBottom: 0 }}>
+          <div className={styles.modalContent}>
+            <Spin spinning={state.loading}>
+              {type === lockTypeEnum.SALE &&
+                state.client.map((item, index) => (
+                  <div key={index} className={styles.card}>
+                    <span className="w-1/6">{item.clientName}</span>
+                    <span className="w-1/6">{item.position}</span>
+                    <span className="w-1/6">{item.telephone}</span>
+                    <span className="w-1/2 flex justify-between">
+                      <span>{item.companyName}</span>
+                      <Popconfirm
+                        title="确认选择ta"
+                        onConfirm={() => connectClient(item.id)}
+                        okText="确认"
+                        cancelText="取消">
+                        <Button type="primary" size="small">
+                          选择ta
+                        </Button>
+                      </Popconfirm>
+                    </span>
+                  </div>
+                ))}
+              {type === lockTypeEnum.CHANGE &&
+                state.longle.map(item => (
+                  <div key={item.id} className={styles.card}>
+                    <div className="w-1/3">{item.keyNum}</div>
+                    <div className="w-2/3 flex justify-between">
+                      <span>{item.product}</span>
+                      <Popconfirm
+                        title="确认选择ta"
+                        onConfirm={() => connectLongle(item.id)}
+                        okText="确认"
+                        cancelText="取消">
+                        <Button type="primary" size="small">
+                          更换
+                        </Button>
+                      </Popconfirm>
+                    </div>
+                  </div>
+                ))}
+            </Spin>
+            {state.noMore && <div className="text-center text-gray-400 my-4">已到底部</div>}
+          </div>
+        </div>
+      ) : null}
+
+      <Form {...layout} form={form}>
+        {type === lockTypeEnum.UPDATE ? (
+          <>
+            <Form.Item name="product" label="新增产品">
+              <Select
+                placeholder="请选择产品"
+                onDropdownVisibleChange={async e => {
+                  e && !prodList?.length && (await fetchSoftProducts())
+                }}
+                options={prodList.map(item => ({ value: item.title }))}
+              />
+            </Form.Item>
+          </>
+        ) : null}
+
+        {type === lockTypeEnum.REMARK ? (
+          <>
+            <ProFormTextArea name="mark" label="备注" />
+          </>
+        ) : null}
+
+        {type === lockTypeEnum.RECOVER ? (
+          <>
+            <p>
+              收回锁号 <b>{keyNum}</b>
+            </p>
+            <Form.Item name="longleId" hidden>
+              <Input />
+            </Form.Item>
+          </>
+        ) : null}
+
+        {type === lockTypeEnum.RECEIVE ? (
+          <>
+            <p>
+              确认接收锁 <b>{keyNum}</b>
+            </p>
+            <Form.Item name="longleId" hidden>
+              <Input />
+            </Form.Item>
+          </>
+        ) : null}
+      </Form>
+    </Modal>
+  )
+}
+
+export default connect(({ lock }) => ({
+  prodList: lock.prodList
+}))(LockModal)

+ 38 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/LowerList.jsx

@@ -0,0 +1,38 @@
+import { Tabs, List } from 'antd'
+import { dayjsFormat } from '@/utils/utils'
+
+const LockLowerList = props => {
+  const {
+    log: { log: logs = [] }
+  } = props
+  const { TabPane } = Tabs
+  return (
+    <div>
+      <div className="pl-4">
+        <Tabs>
+          <TabPane tab="日志" key="日志">
+            <div className="sheet-panel-record">
+              <List
+                dataSource={logs}
+                split={false}
+                renderItem={item => (
+                  <List.Item>
+                    <a>@{item.operatorName}</a> {item.content}
+                    <List.Item.Meta
+                      // avatar=
+                      // title=
+                      // description={ dayjsFormat(item.createTime, 'YYYY-MM-DD')}
+                      description={dayjsFormat(item.createTime)}
+                    />
+                  </List.Item>
+                )}
+              />
+            </div>
+          </TabPane>
+        </Tabs>
+      </div>
+    </div>
+  )
+}
+
+export default LockLowerList

+ 191 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/TabList.jsx

@@ -0,0 +1,191 @@
+import React, { useState } from 'react'
+import { Row, Col, List, Tabs, message, Button } from 'antd'
+import { dayjsFormat } from '@/utils/utils'
+import consts from '@/consts'
+import { apiSoftwareAddLonglelog } from '@/services/product'
+import { ProductLabel } from '@/pages/Product/Lock/LockStore'
+import LockModal, { lockTypeEnum } from './LockModal'
+import ClientDetail from '@/pages/Customer/Client/components/ClientDetail'
+import { servicelogEnum, showProductsStatus, showNewLongleStatus, showMarkStatus } from './consts'
+import { useDispatch } from 'dva'
+import { useModal } from '@/components/Modal'
+
+const RenderLongleCon = ({ longleService }) => {
+  return (
+    <>
+      {showProductsStatus.includes(longleService.status) ? (
+        <ProductLabel data={longleService.product} />
+      ) : null}
+      {showNewLongleStatus.includes(longleService.status) ? (
+        <span>{longleService.NewKeyNum}</span>
+      ) : null}
+      {showMarkStatus.includes(longleService.status) ? <span>{longleService.mark}</span> : null}
+    </>
+  )
+}
+
+const LockTabList = props => {
+  const { longleLog, longleId, keyNum, clientId, logStatus } = props
+  const { toggleDrawer, setDrawerProps } = useModal()
+  const dispatch = useDispatch()
+  const { TabPane } = Tabs
+
+  const [addLonglelog, setAddLonglelog] = useState({
+    type: '',
+    visible: false,
+    loading: false
+  })
+
+  const handleAddLonglelog = async values => {
+    // console.log(values)
+    setAddLonglelog({ ...addLonglelog, loading: true })
+    const { code = -1 } = await apiSoftwareAddLonglelog({
+      ...values,
+      longleId
+    })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success('新增成功')
+      // 请求完了
+      setAddLonglelog({ ...addLonglelog, loading: false, visible: false })
+      // 刷新数据
+      dispatch({
+        type: 'refresh/commitAction',
+        payload: 'longle'
+      })
+    }
+  }
+
+  const handleClientClick = () => {
+    toggleDrawer()
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <ClientDetail dataId={clientId} />
+    })
+    toggleDrawer()
+  }
+
+  return (
+    <div>
+      <div className="pl-4">
+        <Tabs>
+          <TabPane tab="记录内容" key="记录内容">
+            <div className="sheet-panel-record">
+              <Row>
+                <Col span="8">类型/时间</Col>
+                <Col span="16">服务内容</Col>
+              </Row>
+              <List
+                dataSource={longleLog}
+                split={false}
+                renderItem={item => (
+                  <List.Item>
+                    <Row className="w-full">
+                      <Col span="8">
+                        {servicelogEnum[item.status]}{' '}
+                        <span
+                          className="textPurple text-hex-967bbd cursor-pointer"
+                          onClick={handleClientClick}
+                        >
+                          {item.client}
+                        </span>
+                        <br />
+                        <a>@{item.operator}</a>{' '}
+                        <span className="text-gray-500">
+                          {dayjsFormat(item.dateline, 'YYYY-MM-DD')}
+                        </span>
+                        {/* <List.Item.Meta
+                          description={ dayjsFormat(item.updatetime, 'MM-DD HH:mm')}
+                        /> */}
+                      </Col>
+                      <Col span="16">
+                        <RenderLongleCon longleService={item} />
+                      </Col>
+                    </Row>
+                  </List.Item>
+                )}
+              />
+            </div>
+          </TabPane>
+        </Tabs>
+      </div>
+      <div className="sheet-btns pl-4 pt-4 flex">
+        <Button
+          type="primary"
+          ghost
+          size="small"
+          className="mr-3px"
+          onClick={() =>
+            setAddLonglelog({ ...setAddLonglelog, visible: true, type: lockTypeEnum.SALE })
+          }
+        >
+          借出/销售/赠送
+        </Button>
+        <Button
+          type="primary"
+          ghost
+          size="small"
+          className="mr-1"
+          onClick={() =>
+            setAddLonglelog({ ...setAddLonglelog, visible: true, type: lockTypeEnum.UPDATE })
+          }
+        >
+          升级
+        </Button>
+        <Button
+          type="primary"
+          ghost
+          size="small"
+          className="mr-1"
+          onClick={() =>
+            setAddLonglelog({
+              ...setAddLonglelog,
+              visible: true,
+              type: lockTypeEnum.CHANGE
+            })
+          }
+        >
+          更换
+        </Button>
+        <Button
+          type="primary"
+          ghost
+          size="small"
+          className="mr-1"
+          onClick={() =>
+            setAddLonglelog({ ...setAddLonglelog, visible: true, type: lockTypeEnum.RECOVER })
+          }
+        >
+          收回
+        </Button>
+        <Button
+          type="primary"
+          ghost
+          size="small"
+          onClick={() =>
+            setAddLonglelog({ ...setAddLonglelog, visible: true, type: lockTypeEnum.REMARK })
+          }
+        >
+          备注
+        </Button>
+
+        <LockModal
+          onConfirm={handleAddLonglelog}
+          loading={addLonglelog.loading}
+          visible={addLonglelog.visible}
+          onCancel={() => setAddLonglelog({ ...addLonglelog, visible: false })}
+          type={addLonglelog.type}
+          keyNum={keyNum}
+          longleId={longleId}
+          logStatus={logStatus}
+        />
+      </div>
+    </div>
+  )
+}
+
+export default LockTabList

+ 14 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/consts.js

@@ -0,0 +1,14 @@
+// 软件锁服务记录状态
+export const servicelogEnum = {
+  3: '借出',
+  4: '销售',
+  5: '升级',
+  6: '更换',
+  7: '收回',
+  8: '备注',
+  9: '赠送'
+}
+
+export const showProductsStatus = [3, 4, 5, 9]
+export const showNewLongleStatus = [6]
+export const showMarkStatus = [8]

+ 115 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/index.jsx

@@ -0,0 +1,115 @@
+import React, { useState, useEffect } from 'react'
+import { Row, Col, Tooltip } from 'antd'
+import consts from '@/consts'
+import { apiSoftwareDetail } from '@/services/product'
+import LockForm from './Form.jsx'
+import LockLowerList from './LowerList'
+import LockTabList from './TabList'
+import { connect } from 'umi'
+import { Down, Up } from '@icon-park/react'
+import { FlipOverEnum, useFlipOver } from '@/hooks/web/useFlipOver'
+
+const Index = props => {
+  const { dispatch, visible, dataId = '', refresh, orderIds = [], updateKey = 'longle' } = props
+  const shouldUpdate = refresh[updateKey] || false
+  const [state, setState] = useState({
+    longle: {},
+    log: [],
+    total: 0,
+    longleLog: []
+  })
+  const initData = async id => {
+    const {
+      data: { longle = {}, log = [], longleLog = [] } = { longle: {}, longleLog: [] },
+      code = -1
+    } = await apiSoftwareDetail({ id })
+    if (code === consts.RET_CODE.SUCCESS) {
+      if (shouldUpdate) {
+        dispatch({
+          type: 'refresh/commitAction',
+          payload: updateKey
+        })
+      }
+      setState({ ...state, longle, log, longleLog })
+    }
+  }
+  useEffect(() => {
+    if (shouldUpdate) {
+      initData(dataId)
+    }
+  }, [shouldUpdate])
+  useEffect(() => {
+    if (visible) {
+      initData(dataId)
+    }
+  }, [visible])
+
+  const { flipFn, flipConsts } = useFlipOver(initData, dataId, orderIds)
+
+  return (
+    <div>
+      <div className="sheet-box">
+        <Row>
+          <Col
+            sm={{ span: 24 }}
+            md={{ span: 24 }}
+            lg={{ span: 9 }}
+            xl={{ span: 9 }}
+            className="sheet-box-left"
+          >
+            {orderIds.length ? (
+              <div className="mb-3">
+                <Tooltip title="上一条记录">
+                  <Up
+                    fill={flipConsts.disableUpBtn ? '#BDBDBE' : '#886ab5'}
+                    size="20"
+                    onClick={() => flipFn(FlipOverEnum.UP)}
+                    className={[
+                      flipConsts.disableUpBtn ? 'cursor-not-allowed' : 'cursor-pointer'
+                    ].join(' ')}
+                  />
+                </Tooltip>
+                <Tooltip title="下一条记录">
+                  <Down
+                    fill={flipConsts.disableDownBtn ? '#BDBDBE' : '#886ab5'}
+                    size="20"
+                    onClick={() => flipFn(FlipOverEnum.DOWN)}
+                    className={[
+                      flipConsts.disableDownBtn ? 'cursor-not-allowed' : 'cursor-pointer',
+                      'ml-1'
+                    ].join(' ')}
+                  />
+                </Tooltip>
+              </div>
+            ) : null}
+            <div className="sheet-left-panel pr-4">
+              <LockForm longle={state.longle} customerId={state.longle.customerId} />
+            </div>
+          </Col>
+          <Col
+            sm={{ span: 24 }}
+            md={{ span: 24 }}
+            lg={{ span: 15 }}
+            xl={{ span: 15 }}
+            className="sheet-box-right"
+          >
+            <LockTabList
+              longleLog={state.longleLog}
+              longleId={state.longle.id}
+              clientId={state.longle.clientId}
+              keyNum={state.longle.keyNum}
+              logStatus={state.longle.status}
+            />
+            <LockLowerList log={state.log} />
+          </Col>
+        </Row>
+      </div>
+    </div>
+  )
+}
+
+// export default Index
+export default connect(({ refresh, single }) => ({
+  refresh,
+  visible: single.drawer.visible
+}))(Index)

+ 29 - 0
src/pages/Product/Lock/LockStore/components/LocksDetail/index.less

@@ -0,0 +1,29 @@
+.inputSearch {
+  width: 95%;
+}
+.modalContainer {
+  height: 53vh;
+  max-height: 53vh;
+  overflow-y: auto;
+  .modalHeader {
+    position: relative;
+    padding: 1.5rem;
+  }
+  .modalContent {
+    .card {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 1rem;
+      word-wrap: break-word;
+      background-color: #ffffff;
+      background-clip: border-box;
+      border: 1px solid rgba(0, 0, 0, 0.08);
+      border-radius: 4px;
+      box-shadow: 0 0 13px 0 rgb(74 53 107 / 8%) !important;
+      &:not(:last-of-type) {
+        margin-bottom: 1rem;
+      }
+    }
+  }
+}

+ 98 - 0
src/pages/Product/Lock/LockStore/components/ReceiveLock.jsx

@@ -0,0 +1,98 @@
+import { Button, Input, message } from 'antd'
+import React, { useState, useRef } from 'react'
+import styles from '@/pages/Customer/Company/components/ConnectCompany/index.less'
+import consts from '@/basic/consts'
+import { apiReceiveOldLock, queryOldLockList } from '@/services/product'
+import { ProductLabel } from '../index'
+import ProTable from '@ant-design/pro-table'
+import CustomModal from '@/components/Modal/src/components/CustomModal'
+// import { Attention } from '@icon-park/react'
+
+const ReceiveLock = ({ refresh }) => {
+  const tRef = useRef()
+  const [tParams, setParams] = useState({
+    search: null
+  })
+  const receiveLongle = async (name, id) => {
+    const { code = -1 } = await apiReceiveOldLock({ id })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success(`锁(${name})接收成功`)
+      tRef.current?.reload()
+      refresh()
+    }
+  }
+
+  const columns = [
+    {
+      dataIndex: 'keyNum',
+      title: '锁号'
+    },
+    {
+      dataIndex: 'category',
+      title: '办事处'
+    },
+    {
+      dataIndex: 'product',
+      title: '产品',
+      width: '60%',
+      render: text => <ProductLabel data={text} />
+    },
+    {
+      dataIndex: 'opreate',
+      title: '操作',
+      render: (_, record) => (
+        <Button size="small" onClick={() => receiveLongle(record.keyNum, record.id)}>
+          接收
+        </Button>
+      )
+    }
+  ]
+
+  const handleOnSearch = val => {
+    setParams({ ...tParams, search: val })
+  }
+  return (
+    <CustomModal
+      title={
+        <div className="flex items-center justify-between">
+          <span>
+            以下锁为系统升级前未接收的锁,请及时接收。
+            {/* <Tooltip title="新系统生成的锁,将在列表中按“本部门”展示。">
+              <Attention theme="outline" fill="#333" />
+            </Tooltip> */}
+          </span>
+          <Input.Search
+            style={{ width: '300px' }}
+            placeholder="输入锁号/产品进行搜索"
+            allowClear
+            onSearch={handleOnSearch}
+          />
+        </div>
+      }
+    >
+      <div className={styles.modalContent}>
+        <ProTable
+          rowKey={record => record.id}
+          columns={columns}
+          actionRef={tRef}
+          params={tParams}
+          toolBarRender={false}
+          search={false}
+          bordered={true}
+          scroll={{ y: 460 }}
+          request={async params => {
+            const { data: { longle, total } = { longle: [], total: 0 }, code = -1 } =
+              await queryOldLockList(params)
+            return {
+              data: longle,
+              success: code === consts.RET_CODE.SUCCESS,
+              total
+            }
+          }}
+        />
+      </div>
+    </CustomModal>
+  )
+}
+
+export default ReceiveLock

+ 94 - 0
src/pages/Product/Lock/LockStore/components/ReceiveNewLock.jsx

@@ -0,0 +1,94 @@
+import { Button, Input, message } from 'antd'
+import React, { useRef, useState } from 'react'
+import styles from '@/pages/Customer/Company/components/ConnectCompany/index.less'
+import consts from '@/basic/consts'
+import { apiReceiveLock, queryReceiveList } from '@/services/product'
+import { ProductLabel } from '../index'
+import ProTable from '@ant-design/pro-table'
+import CustomModal from '@/components/Modal/src/components/CustomModal'
+
+const ReceiveNewLock = ({ refresh }) => {
+  const tRef = useRef()
+
+  const [tParams, setParams] = useState({
+    search: null
+  })
+  const receiveLongle = async (name, id) => {
+    const { code = -1 } = await apiReceiveLock({ id })
+    if (code === consts.RET_CODE.SUCCESS) {
+      message.success(`锁(${name})接收成功`)
+      tRef.current?.reload()
+      refresh()
+    }
+  }
+
+  const columns = [
+    {
+      dataIndex: 'keyNum',
+      title: '锁号'
+    },
+    {
+      dataIndex: 'departmentName',
+      title: '部门'
+    },
+    {
+      dataIndex: 'product',
+      title: '产品',
+      width: '60%',
+      render: text => <ProductLabel data={text} />
+    },
+    {
+      dataIndex: 'opreate',
+      title: '操作',
+      render: (_, record) => (
+        <Button size="small" onClick={() => receiveLongle(record.keyNum, record.id)}>
+          接收
+        </Button>
+      )
+    }
+  ]
+
+  const handleOnSearch = val => {
+    setParams({ ...tParams, search: val })
+  }
+
+  return (
+    <CustomModal
+      title={
+        <div className="flex flex-nowrap items-center justify-between">
+          "新系统生成的锁,将在列表中按“本部门”展示。"
+          <Input.Search
+            style={{ width: '300px' }}
+            placeholder="输入锁号/产品进行搜索"
+            allowClear
+            onSearch={handleOnSearch}
+          />
+        </div>
+      }
+    >
+      <div className={styles.modalContent}>
+        <ProTable
+          rowKey={record => record.id}
+          params={tParams}
+          columns={columns}
+          actionRef={tRef}
+          toolBarRender={false}
+          search={false}
+          bordered={true}
+          scroll={{ y: 460 }}
+          request={async (params, _, filter) => {
+            const { data: { longle, total } = { longle: [], total: 0 }, code = -1 } =
+              await queryReceiveList({ ...params, ...filter })
+            return {
+              data: longle,
+              success: code === consts.RET_CODE.SUCCESS,
+              total
+            }
+          }}
+        />
+      </div>
+    </CustomModal>
+  )
+}
+
+export default ReceiveNewLock

+ 19 - 0
src/pages/Product/Lock/LockStore/components/SoftwareDetail/LowerList.jsx

@@ -0,0 +1,19 @@
+import React from 'react'
+import { Tabs } from 'antd'
+
+const LowerList = () => {
+  const { TabPane } = Tabs
+  return (
+    <div>
+      <div className="pl-4">
+        <Tabs>
+          <TabPane tab="软件锁" key="软件锁">
+            <div className="sheet-right-panel">目前功能暂时不开放</div>
+          </TabPane>
+        </Tabs>
+      </div>
+    </div>
+  )
+}
+
+export default LowerList

+ 45 - 0
src/pages/Product/Lock/LockStore/components/SoftwareDetail/StaffDetail.jsx

@@ -0,0 +1,45 @@
+import React from 'react'
+import { Row, Col, Divider } from 'antd'
+
+const StaffDetail = props => {
+  const { staff } = props
+  return (
+    <div>
+      <Row>
+        <Col span={8}>
+          <h2 className="text-gray-500">责任人</h2>
+        </Col>
+        {/* className={[ 'text-right', styles.textMuted ]} */}
+        <Col span={16} className="text-right text-gray-500">
+          注册于 {staff.hiredate}
+        </Col>
+        <Col span={22}>
+          <h2 className="text-2xl pb-4">{staff.username}</h2>
+        </Col>
+        <Col span={2} />
+      </Row>
+      <Row>
+        <Divider />
+        <Col span={6}>性别</Col>
+        <Col span={18}>{staff.gender}</Col>
+        <Divider />
+        <Col span={6}>部门</Col>
+        <Col span={18}>{staff.departmentName}</Col>
+        <Divider />
+        <Col span={6}>手机</Col>
+        <Col span={18}>{staff.avtelephoneatar}</Col>
+        <Divider />
+        <Col span={6}>电话</Col>
+        <Col span={18}>{staff.phone}</Col>
+        <Divider />
+        <Col span={6}>QQ</Col>
+        <Col span={18}>{staff.qq}</Col>
+        <Divider />
+        <Col span={6}>邮箱</Col>
+        <Col span={18}>{staff.email}</Col>
+      </Row>
+    </div>
+  )
+}
+
+export default StaffDetail

+ 37 - 0
src/pages/Product/Lock/LockStore/components/SoftwareDetail/TabList.jsx

@@ -0,0 +1,37 @@
+import React from 'react'
+import { Tabs, List } from 'antd'
+import { dayjsFormat } from '@/utils/utils'
+
+const TabList = props => {
+  const { log = [] } = props
+  const { TabPane } = Tabs
+  return (
+    <div>
+      <div className="pl-4">
+        <Tabs>
+          <TabPane tab="日志" key="日志">
+            <div className="sheet-panel-record">
+              <List
+                dataSource={log}
+                split={false}
+                renderItem={item => (
+                  <List.Item>
+                    <a>@{item.operatorName}</a> {item.content}
+                    <List.Item.Meta
+                      // avatar=
+                      // title=
+                      // description={ dayjsFormat(item.createTime, 'YYYY-MM-DD')}
+                      description={dayjsFormat(item.createTime)}
+                    />
+                  </List.Item>
+                )}
+              />
+            </div>
+          </TabPane>
+        </Tabs>
+      </div>
+    </div>
+  )
+}
+
+export default TabList

+ 49 - 0
src/pages/Product/Lock/LockStore/components/SoftwareDetail/index.jsx

@@ -0,0 +1,49 @@
+import React, { useState, useEffect } from 'react'
+import { Row, Col } from 'antd'
+import consts from '@/consts'
+import { apiStaffDetail } from '@/services/staff'
+import StaffDetail from './StaffDetail'
+import TabList from './TabList'
+import LowerList from './LowerList'
+import { connect } from 'umi'
+
+const SoftwareDetail = props => {
+  const { visible, dataId = '' } = props
+  const [state, setState] = useState({
+    loading: false,
+    staff: {},
+    log: []
+  })
+  const initData = async id => {
+    setState({ ...state, loading: true })
+    const {
+      data: { staff = {}, log = [] },
+      code = -1
+    } = await apiStaffDetail(id)
+    if (code === consts.RET_CODE.SUCCESS) {
+      setState({ ...state, staff, log, loading: false })
+    }
+  }
+  useEffect(() => {
+    visible && initData(dataId)
+  }, [visible])
+  return (
+    <div className="sheet-box">
+      <Row>
+        <Col span={12} className="sheet-box-left">
+          <div className="sheet-left-panel pr-4">
+            <StaffDetail staff={state.staff} />
+          </div>
+        </Col>
+        <Col span={12} className="sheet-box-right">
+          <LowerList />
+          <TabList />
+        </Col>
+      </Row>
+    </div>
+  )
+}
+
+export default connect(({ single }) => ({
+  visible: single.drawer.visible
+}))(SoftwareDetail)

+ 444 - 0
src/pages/Product/Lock/LockStore/index.jsx

@@ -0,0 +1,444 @@
+import React, { useEffect, useState, useRef } from 'react'
+import { Tag, Tooltip, Input, Button } from 'antd'
+import consts from '@/consts'
+import { useModel, connect } from 'umi'
+import { queryLock } from '@/services/product'
+import LocksDetail from '@/pages/Product/Lock/LockStore/components/LocksDetail'
+import ContactDetail from '@/pages/Customer/Client/components/ClientDetail'
+import SoftwareDetail from '@/pages/Product/Lock/LockStore/components/SoftwareDetail'
+import { useModal } from '@/components/Modal'
+import CompanyDetail from '@/pages/Customer/Company/components/CompanyDetail'
+import { refactorSortField } from '@/utils/utils'
+import ReceiveLock from './components/ReceiveLock'
+// import ReceiveNewLock from './components/ReceiveNewLock'
+import { PermDataTypeEunm } from '@/pages/Customer/Company/components/PermSelect'
+import BasicTable from '@/components/Table'
+import { getPermAuthCache } from '@/utils/auth'
+import FilterDatePicker from '@/components/Table/src/components/FilterDatePicker'
+// import { DownOutlined } from '@ant-design/icons'
+
+// 渲染产品标签
+export const ProductLabel = ({ data }) => {
+  if (!data) return null
+  let newData
+  if (!Array.isArray(data)) {
+    newData = data.split('+')
+  }
+  return (
+    <div className="children:m-2px">
+      {newData.map((item, idx) => (
+        <Tag color="purple" key={item + idx}>
+          {item}
+        </Tag>
+      ))}
+    </div>
+  )
+}
+
+// const longleStatusNum = {
+//   1: {
+//     text: '生成'
+//   },
+//   2: {
+//     text: '接收'
+//   },
+//   3: {
+//     text: '借出'
+//   },
+//   4: {
+//     text: '销售'
+//   },
+//   5: {
+//     text: '升级'
+//   },
+//   6: {
+//     text: '更换'
+//   },
+//   7: {
+//     text: '收回'
+//   },
+//   8: {
+//     text: '备注'
+//   },
+//   9: {
+//     text: '赠送'
+//   }
+// }
+
+// 出库状态
+const longleStockStatusNum = {
+  1: { text: '生成' },
+  2: { text: '接收' }
+}
+// 销售状态
+const longleSellStatusNum = {
+  1: { text: '借出' },
+  2: { text: '销售' },
+  3: { text: '赠送' }
+}
+// 维护状态
+const longlePreserveStatusNum = {
+  1: { text: '升级' },
+  2: { text: '更换' },
+  3: { text: '收回' }
+}
+
+const LockStore = ({ prodList = [], dispatch, shouldUpdate }) => {
+  const tRef = useRef()
+  const { toggleDrawer, setDrawerProps, toggleModal, setModalProps } = useModal()
+  // 刷新
+  const refreshData = () => {
+    tRef.current?.reload()
+  }
+  const showReceiveLockModal = () => {
+    setModalProps({
+      width: '60vw',
+      modalRender: () => <ReceiveLock refresh={refreshData} />,
+      onCancel: () => toggleModal()
+    })
+    toggleModal()
+  }
+  // const showReceiveNewLockModal = () => {
+  //   setModalProps({
+  //     width: '60vw',
+  //     modalRender: () => <ReceiveNewLock refresh={refreshData} />,
+  //     onCancel: () => toggleModal()
+  //   })
+  //   toggleModal()
+  // }
+  const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
+  const [state, setState] = useState({
+    data: [],
+    totalOld: 0, // 待接收锁总数
+    totalNew: 0,
+    orderIds: []
+  })
+
+  const handleSearch = value => {
+    setState({ ...state, params: { ...state.params, search: value } })
+  }
+
+  useEffect(() => {
+    // 获取软件锁产品列表
+    function fetchSoftProducts() {
+      dispatch({
+        type: 'lock/fetchProdList'
+      })
+    }
+    if (!prodList?.length) {
+      fetchSoftProducts()
+    }
+    if (shouldUpdate) {
+      tRef.current.reload()
+    }
+  }, [shouldUpdate])
+
+  // 展示drawer
+  const showDrawer = id => {
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh'
+        // overflowY: 'hidden'
+      },
+      children: <LocksDetail dataId={id} orderIds={state.orderIds} />
+    })
+    toggleDrawer()
+  }
+  // 展示DrawerClient
+  const showDrawerClient = id => {
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <ContactDetail dataId={id} />
+    })
+    toggleDrawer()
+  }
+  // 展示DrawerResponsible
+  const showDrawerResponsible = id => {
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <SoftwareDetail dataId={id} />
+    })
+    toggleDrawer()
+  }
+
+  // 展示单位详情抽屉
+  const showCompanyDrawer = id => {
+    setDrawerProps({
+      closable: true,
+      onClose: () => toggleDrawer(),
+      bodyStyle: {
+        height: '100vh',
+        overflowY: 'hidden'
+      },
+      children: <CompanyDetail dataId={id} updateKey="longle" />
+    })
+    toggleDrawer()
+  }
+
+  const columns = [
+    {
+      title: '锁号',
+      dataIndex: 'keyNum',
+      width: 90,
+      fixed: 'left',
+      render: (keyNum, record) => (
+        <span
+          onClick={() => showDrawer(record.id)}
+          className="text-primary cursor-pointer hover:text-[#967bbd]">
+          {keyNum}
+        </span>
+      )
+    },
+    {
+      title: '产品',
+      dataIndex: 'product',
+      key: 'product',
+      width: 200,
+      fixed: 'left',
+      filters: true,
+      filterMultiple: false,
+      valueEnum: prodList.reduce((prev, curr) => {
+        const obj = { ...prev }
+        obj[curr.title] = {
+          text: curr.title
+        }
+        return obj
+      }, {}),
+      render: (_, record) => <ProductLabel data={record.product} />
+    },
+    {
+      title: '出库状态',
+      dataIndex: 'stockStatus',
+      key: 'stockStatus',
+      width: 70,
+      render: (_, { stockStatus }) => {
+        return <>{longleStockStatusNum[stockStatus]?.text}</>
+      }
+    },
+    {
+      title: '出库时间',
+      dataIndex: 'stockTime',
+      valueType: 'date',
+      key: 'stockTime',
+      filterDropdown: filterDropdownProps => <FilterDatePicker {...filterDropdownProps} />,
+      filters: true,
+      sorter: true,
+      width: 100
+    },
+    // {
+    //   title: '状态',
+    //   dataIndex: 'status',
+    //   width: 60,
+    //   filters: true,
+    //   onFilter: true,
+    //   valueType: 'select',
+    //   valueEnum: longleStatusNum,
+    //   renderText: (_, { status }) => longleStatusNum[status]?.text
+    // },
+    {
+      title: '销售状态',
+      dataIndex: 'sellStatus',
+      key: 'sellStatus',
+      width: 70,
+      render: (_, { sellStatus }) => {
+        return <>{longleSellStatusNum[sellStatus]?.text}</>
+      }
+    },
+    {
+      title: '销售时间',
+      dataIndex: 'sellTime',
+      valueType: 'date',
+      key: 'sellTime',
+      width: 90
+    },
+    {
+      title: '维护状态',
+      dataIndex: 'preserveStatus',
+      key: 'preserveStatus',
+      width: 60,
+      render: (_, { preserveStatus }) => {
+        return <>{longlePreserveStatusNum[preserveStatus]?.text}</>
+      }
+    },
+    {
+      title: '维护时间',
+      dataIndex: 'preserveTime',
+      key: 'preserveTime',
+      valueType: 'date',
+      width: 90
+    },
+    {
+      title: '客户',
+      dataIndex: 'client',
+      key: 'client',
+      width: 150,
+      render: (text, record) => (
+        <span
+          onClick={() => showDrawerClient(record.clientId)}
+          className="text-primary cursor-pointer hover:text-[#967bbd]">
+          {text}
+        </span>
+      )
+    },
+    {
+      title: '单位名称',
+      dataIndex: 'customerName',
+      key: 'customerName',
+      width: 300,
+      render: (text, record) => (
+        <span
+          onClick={() => showCompanyDrawer(record.customerId)}
+          className="text-primary cursor-pointer hover:text-[#967bbd]">
+          {text}
+        </span>
+      )
+    },
+    {
+      title: '负责人',
+      dataIndex: 'responsible',
+      width: 80,
+      render: (text, record) => (
+        <span
+          onClick={() => showDrawerResponsible(record.responsibleId)}
+          className="text-primary cursor-pointer hover:text-[#967bbd]">
+          {text}
+        </span>
+      )
+    },
+    {
+      title: '期限',
+      dataIndex: 'allotedTime',
+      width: 90
+    },
+    {
+      title: '办事处',
+      dataIndex: 'category',
+      width: 90
+    },
+    {
+      title: '纵横部门',
+      dataIndex: 'departmentName',
+      width: 90
+    }
+  ]
+  const columnsStateMap = {
+    preserveStatus: {
+      show: false
+    },
+    preserveTime: {
+      show: false
+    }
+  }
+
+  // const dropDownMenClick = ({ key }) => {
+  //   if (key === '1') {
+  //     showReceiveLockModal()
+  //   } else {
+  //     showReceiveNewLockModal()
+  //   }
+  // }
+
+  const [defaultPermAuthCache] = getPermAuthCache(PermDataTypeEunm.PRODUCT)
+  return (
+    <BasicTable
+      rowKey={record => record.id}
+      mainMenuType="product"
+      columnStateType="LOCKSTORE"
+      search={false}
+      actionRef={tRef}
+      params={state.params}
+      columns={columns}
+      request={async (params, sort, filter) => {
+        const srotOrder = refactorSortField(sort)
+        const {
+          code = -1,
+          data: { longle = [], total = 0, totalOld = 0, totalNew = 0 } = {
+            longle: [],
+            total: 0,
+            totalOld: 0,
+            totalNew: 0
+          }
+        } = await queryLock({ ...params, ...srotOrder, ...filter })
+        setState({ ...state, totalOld, totalNew, orderIds: longle.map(item => item.id) })
+        return {
+          data: longle,
+          success: code === consts.RET_CODE.SUCCESS,
+          total
+        }
+      }}
+      columnsStateMap={columnsStateMap}
+      headerTitle={
+        // <Dropdown
+        //   trigger="click"
+        //   overlay={
+        //     <Menu onClick={dropDownMenClick}>
+        //       {state.totalOld ? <Menu.Item key="1">接收旧锁</Menu.Item> : null}
+
+        //       {state.totalNew ? <Menu.Item key="2">接收新锁</Menu.Item> : null}
+        //     </Menu>
+        //   }>
+        //   <Button>
+        //     接收锁
+        //     <DownOutlined />
+        //   </Button>
+        // </Dropdown>
+        state.totalOld ? (
+          <Button type="primary" onClick={showReceiveLockModal}>
+            接收旧锁({state.totalOld})
+          </Button>
+        ) : null
+      }
+      toolbar={{
+        search: (
+          <Tooltip placement="bottom" title="输入软件锁号">
+            <Input.Search
+              style={{ width: '300px' }}
+              allowClear={true}
+              placeholder={`在${
+                permData[PermDataTypeEunm.PRODUCT]?.find(
+                  item => item.value === defaultPermAuthCache
+                )?.label
+              }下搜索`}
+              onSearch={handleSearch}
+            />
+          </Tooltip>
+        )
+        // actions: [
+        //   <Tooltip title="产品出库时间" key="date">
+        //     <DatePicker.RangePicker
+        //       bordered={false}
+        //       onChange={time => {
+        //         const [time1, time2] = time || [null, null]
+        //         setState({
+        //           ...state,
+        //           params: {
+        //             ...state.params,
+        //             beginTime: time1 ? time1.format('YYYY-MM-DD') : null,
+        //             endTime: time2 ? time2.format('YYYY-MM-DD') : null
+        //           }
+        //         })
+        //       }}
+        //     />
+        //   </Tooltip>
+        // ]
+      }}
+    />
+  )
+}
+
+export default connect(({ lock, refresh }) => ({
+  prodList: lock.prodList,
+  shouldUpdate: refresh.longle
+}))(LockStore)

+ 35 - 0
src/pages/Product/Lock/model.js

@@ -0,0 +1,35 @@
+import consts from '@/consts'
+import { querySoftwareProducts } from '@/services/product'
+
+export default {
+  namespace: 'lock',
+  state: {
+    prodList: []
+  },
+  // 用于处理异步操作和业务逻辑,由action触发,但不能修改state
+  effects: {
+    *fetchProdList({ payload }, { call, put }) {
+      const response = yield call(querySoftwareProducts, payload)
+      if (response && response.code === consts.RET_CODE.SUCCESS) {
+        yield put({
+          type: 'changeState',
+          payload: {
+            key: 'prodList',
+            data: response.data
+          }
+        })
+      }
+      // console.log(response)
+    }
+  },
+  // reducers:用于处理同步操作,由action触发,可修改state
+  reducers: {
+    // action:是由reducers及effects的触发器,一般是第一个对象,如:{type:'add',payload:todo}
+    changeState(state, action) {
+      return {
+        ...state,
+        [action.payload.key]: action.payload.data
+      }
+    }
+  }
+}