jinx před 1 týdnem
rodič
revize
c29a9a42f1

+ 8 - 3
src/components/MyTable/index.tsx

@@ -82,7 +82,7 @@ const MyTable = forwardRef<MyTableMethods, MyTableProps>(
 
     // 表格内容定制化
     const tableComObj = useCallback(
-      (key: string, val: string[], id?: any, backFu?: (id: number) => void) => {
+      (key: string, val: string[], id?: any, backFu?: (id: any) => void) => {
         const obj = {
           // 超链接打开
           A: (
@@ -180,8 +180,13 @@ const MyTable = forwardRef<MyTableMethods, MyTableProps>(
               tempCom = tempCom.substring(0, v[3]) + '...'
             }
 
-            if (v[4]) {
-              tempCom = tableComObj(v[4], [tempCom, item[v[2]]], item.id, v[5])
+              if (v[4]) {
+              tempCom = tableComObj(
+                v[4],
+                [tempCom, item[v[2]]],
+                v[6] ? item[v[6]] : item.id,
+                v[5]
+              )
             } else if ((item[v[2]] || '').length >= v[3]) {
               tempCom = (
                 <span style={{ cursor: 'pointer' }} title={item[v[2]]}>

+ 132 - 118
src/pages/A1check/A1ledger/ImportData.tsx

@@ -55,20 +55,6 @@ const detailApiCandidates = [
   'cms/importLedger/checkResultPage',
   'cms/importLedger/pageDetail'
 ]
-const importSuccessApiCandidates = [
-  'cms/importLedger/importSuccessData',
-  'cms/importLedger/importSuccess',
-  'cms/importLedger/confirmImport',
-  'cms/importLedger/confirm',
-  'cms/importLedger/saveSuccessData'
-]
-const cancelImportApiCandidates = [
-  'cms/importLedger/cancelImport',
-  'cms/importLedger/cancel',
-  'cms/importLedger/deleteImport',
-  'cms/importLedger/removeImport'
-]
-
 const pickValue = (item: any, keys: string[], defaultValue: any = emptyTxt) => {
   for (const key of keys) {
     const value = item?.[key]
@@ -88,11 +74,11 @@ const normalizeRecord = (item: any, index: number): ImportRecordType => ({
   fileName: pickValue(item, ['fileName', 'excelName', 'originFileName', 'originalName', 'name']),
   importCount: pickValue(
     item,
-    ['importCount', 'importNum', 'dataCount', 'totalCount', 'totalNum', 'pcsRegister'],
+    ['pcsTotal', 'importCount', 'importNum', 'dataCount', 'totalCount', 'totalNum', 'pcsRegister'],
     0
   ),
-  successCount: pickValue(item, ['successCount', 'successNum', 'okCount', 'okNum'], 0),
-  failCount: pickValue(item, ['failCount', 'failNum', 'errorCount', 'errorNum'], 0),
+  successCount: pickValue(item, ['pcsSuccess', 'successCount', 'successNum', 'okCount', 'okNum'], 0),
+  failCount: pickValue(item, ['pcsError', 'failCount', 'failNum', 'errorCount', 'errorNum'], 0),
   importDate: pickDateValue(item, ['importTime', 'uploadTime', 'createTime', 'updateTime']),
   importUser: pickValue(item, ['importUser', 'importUserName', 'creatorName', 'updateByName']),
   raw: item
@@ -113,11 +99,15 @@ const normalizeCheckResult = (value: any) => {
 
 const normalizeDetail = (item: any, index: number): ImportDetailType => {
   const result = normalizeCheckResult(
-    pickValue(item, ['checkResult', 'validateResult', 'result', 'status', 'successFlag'], '')
+    pickValue(
+      item,
+      ['importIsTrue', 'checkResult', 'validateResult', 'result', 'status', 'successFlag'],
+      ''
+    )
   )
   const failReason = pickValue(
     item,
-    ['failReason', 'errorMsg', 'msg', 'reason', 'remark'],
+    ['importErrorMsg', 'failReason', 'errorMsg', 'msg', 'reason', 'remark'],
     result === '成功' ? emptyTxt : ''
   )
 
@@ -125,7 +115,7 @@ const normalizeDetail = (item: any, index: number): ImportDetailType => {
     id: pickValue(item, ['id', 'detailId', 'rowId'], `detail-${index}`),
     rowNum: pickValue(
       item,
-      ['rowNum', 'rowNo', 'lineNum', 'excelRowNum', 'sort', 'index'],
+      ['importRow', 'rowNum', 'rowNo', 'lineNum', 'excelRowNum', 'sort', 'index'],
       index + 1
     ),
     num: pickValue(
@@ -140,10 +130,62 @@ const normalizeDetail = (item: any, index: number): ImportDetailType => {
   }
 }
 
+const parseSnapValue = (value: any) => {
+  if (!value) return null
+  if (Array.isArray(value) || typeof value === 'object') return value
+
+  if (typeof value === 'string') {
+    try {
+      return JSON.parse(value)
+    } catch (error) {
+      return null
+    }
+  }
+
+  return null
+}
+
 const getInlineDetailList = (item: any) => {
+  const snapValue = parseSnapValue(item?.snap)
+  if (Array.isArray(snapValue)) return snapValue
+
+  if (snapValue && typeof snapValue === 'object') {
+    const snapList = pickValue(
+      snapValue,
+      [
+        'detailList',
+        'details',
+        'checkList',
+        'checkResultList',
+        'resultList',
+        'importDetailList',
+        'importList',
+        'dataList',
+        'list',
+        'rows',
+        'records'
+      ],
+      null
+    )
+    if (Array.isArray(snapList)) return snapList
+  }
+
   const detailList = pickValue(
     item,
-    ['detailList', 'details', 'checkList', 'checkResultList', 'resultList'],
+    [
+      'snap',
+      'detailList',
+      'details',
+      'checkList',
+      'checkResultList',
+      'resultList',
+      'importDetailList',
+      'importList',
+      'dataList',
+      'list',
+      'rows',
+      'records'
+    ],
     null
   )
   return Array.isArray(detailList) ? detailList : []
@@ -184,14 +226,6 @@ const buildDetailPayload = (record: ImportRecordType, query: DetailQueryType) =>
   }
 }
 
-const buildActionPayload = (record: ImportRecordType) => ({
-  id: record.id,
-  importId: pickValue(record.raw, ['importId'], record.id),
-  batchId: pickValue(record.raw, ['batchId'], record.id),
-  logId: pickValue(record.raw, ['logId'], record.id),
-  fileName: record.fileName
-})
-
 const silentPost = async (url: string, data: any) => {
   const { token } = getTokenInfo()
   try {
@@ -213,17 +247,10 @@ const silentPost = async (url: string, data: any) => {
   }
 }
 
-const requestWithCandidates = async (paths: string[], payload: any) => {
-  for (const path of paths) {
-    const res = await silentPost(path, payload)
-    if (res) return res
-  }
-  return null
-}
-
 function ImportData({ closeFu }: Props) {
   const uploadBtnRef = useRef<UpBtnMethods>(null)
   const detailApiPathRef = useRef('')
+  const reuploadPendingRef = useRef(false)
 
   const [formData, setFormData] = useState({ pageNum: 1, pageSize: 10 })
   const [tableInfo, setTableInfo] = useState({ list: [] as ImportRecordType[], total: 0 })
@@ -243,19 +270,20 @@ function ImportData({ closeFu }: Props) {
     total: 0
   })
   const [detailLoading, setDetailLoading] = useState(false)
-  const [actionLoading, setActionLoading] = useState<'confirm' | 'cancel' | ''>('')
+  const [actionLoading, setActionLoading] = useState<'confirm' | ''>('')
+  const [uploadResultData, setUploadResultData] = useState<any>(null)
 
   const fetchImportPage = useCallback(async (payload: { pageNum: number; pageSize: number }) => {
-    const res = await http.post('cms/importLedger/pageList', payload)
+    const res = await http.post('cms/importLedger/importPage', payload)
     if (res.code !== 0) return { list: [] as ImportRecordType[], total: 0 }
 
-    const records = (res.data?.records || []).map((item: any, index: number) =>
-      normalizeRecord(item, index)
+    const records = (res.data?.records || res.data?.list || res.data?.rows || []).map(
+      (item: any, index: number) => normalizeRecord(item, index)
     )
 
     return {
       list: records,
-      total: res.data?.total || 0
+      total: res.data?.total || res.data?.count || res.data?.totalCount || records.length
     }
   }, [])
 
@@ -291,32 +319,24 @@ function ImportData({ closeFu }: Props) {
   const resolveUploadRecord = useCallback(
     async (res: any, file: File) => {
       const uploadData = Array.isArray(res?.data) ? res.data[0] : res?.data
-      const latestPage = await fetchImportPage({ pageNum: 1, pageSize: formData.pageSize })
-
-      setTableInfo(latestPage)
-      setFormData(prev => ({
-        ...prev,
-        pageNum: 1
-      }))
-
-      const latestMatch =
-        latestPage.list.find((item: ImportRecordType) => item.fileName === file.name) ||
-        latestPage.list[0] ||
-        null
-
-      if (latestMatch) return latestMatch
-
-      if (uploadData && typeof uploadData === 'object') {
-        return normalizeRecord({ ...uploadData, fileName: file.name }, 0)
-      }
-
-      return normalizeRecord({ fileName: file.name }, 0)
+      const uploadRecord =
+        uploadData && typeof uploadData === 'object'
+          ? normalizeRecord(
+              {
+                ...uploadData,
+                fileName: pickValue(uploadData, ['fileName'], file.name)
+              },
+              0
+            )
+          : null
+      return uploadRecord || normalizeRecord({ fileName: file.name }, 0)
     },
-    [fetchImportPage, formData.pageSize]
+    []
   )
 
   const handleUploadSuccess = useCallback(
     async (res: any, file: File) => {
+      setUploadResultData(res?.data ?? null)
       const record = await resolveUploadRecord(res, file)
       openDetailModal(record, 'uploadResult')
     },
@@ -332,10 +352,8 @@ function ImportData({ closeFu }: Props) {
         normalizeDetail(item, index)
       )
       const filteredList = filterInlineDetails(normalizedList, detailQuery)
-      const start = (detailQuery.pageNum - 1) * detailQuery.pageSize
-      const end = start + detailQuery.pageSize
       setDetailTableInfo({
-        list: filteredList.slice(start, end),
+        list: filteredList,
         total: filteredList.length
       })
       return
@@ -343,6 +361,22 @@ function ImportData({ closeFu }: Props) {
 
     setDetailLoading(true)
     try {
+      if (detailModalMode === 'view') {
+        const res = await http.get(`cms/importLedger/importDetail/${detailInfo.id}`)
+        const detailData = res.code === 0 ? res.data : null
+        const detailList = Array.isArray(detailData) ? detailData : getInlineDetailList(detailData)
+        const normalizedList = detailList.map((item: any, index: number) =>
+          normalizeDetail(item, index)
+        )
+        const filteredList = filterInlineDetails(normalizedList, detailQuery)
+
+        setDetailTableInfo({
+          list: filteredList,
+          total: filteredList.length
+        })
+        return
+      }
+
       const payload = buildDetailPayload(detailInfo, detailQuery)
       const tryPaths = detailApiPathRef.current
         ? [detailApiPathRef.current]
@@ -352,18 +386,14 @@ function ImportData({ closeFu }: Props) {
         const res = await silentPost(path, payload)
         if (res?.data) {
           detailApiPathRef.current = path
-          const records = (res.data.records || res.data.list || res.data.rows || []).map(
-            (item: any, index: number) => normalizeDetail(item, index)
+          const records = getInlineDetailList(res.data).map((item: any, index: number) =>
+            normalizeDetail(item, index)
           )
+          const filteredList = filterInlineDetails(records, detailQuery)
 
           setDetailTableInfo({
-            list: records,
-            total:
-              res.data.total ||
-              res.data.count ||
-              res.data.totalCount ||
-              res.data.recordTotal ||
-              records.length
+            list: filteredList,
+            total: filteredList.length
           })
           return
         }
@@ -373,30 +403,34 @@ function ImportData({ closeFu }: Props) {
     } finally {
       setDetailLoading(false)
     }
-  }, [detailInfo, detailQuery])
+  }, [detailInfo, detailModalMode, detailQuery])
 
   useEffect(() => {
     getDetailListFu()
   }, [getDetailListFu])
 
   const closeDetailModal = useCallback(() => {
+    reuploadPendingRef.current = false
     setDetailModalMode('view')
     setActionLoading('')
+    setUploadResultData(null)
     setDetailInfo(null)
     setDetailTableInfo({ list: [], total: 0 })
   }, [])
 
-  const refreshListToFirstPage = useCallback(async () => {
-    const latestPage = await fetchImportPage({ pageNum: 1, pageSize: formData.pageSize })
-    setTableInfo(latestPage)
-    setFormData(prev => ({
-      ...prev,
-      pageNum: 1
-    }))
-  }, [fetchImportPage, formData.pageSize])
+  const handleUploadFileSelected = useCallback(() => {
+    if (!reuploadPendingRef.current) return
+    reuploadPendingRef.current = false
+    closeDetailModal()
+  }, [closeDetailModal])
+
+  const reuploadFu = useCallback(() => {
+    reuploadPendingRef.current = true
+    uploadBtnRef.current?.openPicker()
+  }, [])
 
   const importSuccessData = useCallback(async () => {
-    if (!detailInfo) return
+    if (!detailInfo || !uploadResultData) return
     if (Number(detailInfo.successCount || 0) <= 0) {
       MessageFu.warning('当前没有可导入的校验成功数据')
       return
@@ -404,36 +438,24 @@ function ImportData({ closeFu }: Props) {
 
     setActionLoading('confirm')
     try {
-      const res = await requestWithCandidates(
-        importSuccessApiCandidates,
-        buildActionPayload(detailInfo)
-      )
+      const res = await http.post('cms/importLedger/importRow', uploadResultData)
+
 
-      if (!res) {
+      if (res.code !== 0) return
+      /*
         MessageFu.warning('暂未匹配到导入接口,请联系管理员确认')
         return
       }
 
       MessageFu.success(res.msg || '导入成功')
+      */
+      MessageFu.success(res.msg || '导入成功')
       closeDetailModal()
-      await refreshListToFirstPage()
-    } finally {
-      setActionLoading('')
-    }
-  }, [closeDetailModal, detailInfo, refreshListToFirstPage])
-
-  const cancelImport = useCallback(async () => {
-    if (!detailInfo) return
-
-    setActionLoading('cancel')
-    try {
-      await requestWithCandidates(cancelImportApiCandidates, buildActionPayload(detailInfo))
-      closeDetailModal()
-      await refreshListToFirstPage()
+      await getListFu()
     } finally {
       setActionLoading('')
     }
-  }, [closeDetailModal, detailInfo, refreshListToFirstPage])
+  }, [closeDetailModal, detailInfo, getListFu, uploadResultData])
 
   const modalTitle = useMemo(() => {
     if (!detailInfo) return '上传数据校验结果'
@@ -469,7 +491,8 @@ function ImportData({ closeFu }: Props) {
             tit='上传藏品数据'
             url='cms/importLedger/uploadExcel'
             width={140}
-            backFu={() => setFormData(prev => ({ ...prev, pageNum: 1 }))}
+            backFu={getListFu}
+            onFileSelected={handleUploadFileSelected}
             onSuccess={handleUploadSuccess}
           />
         </div>
@@ -579,9 +602,7 @@ function ImportData({ closeFu }: Props) {
             scrollX={160}
             loading={detailLoading}
             list={detailTableInfo.list}
-            total={detailTableInfo.total}
-            pageNum={detailQuery.pageNum}
-            pageSize={detailQuery.pageSize}
+            pagingInfo={false}
             rowKey='id'
             columnsTemp={[
               ['txt', '行数', 'rowNum', 100],
@@ -590,19 +611,12 @@ function ImportData({ closeFu }: Props) {
               ['txt', '校验结果', 'result', 120],
               ['txt', '失败原因', 'failReason', 360]
             ]}
-            onChange={(pageNum, pageSize) =>
-              setDetailQuery(prev => ({
-                ...prev,
-                pageNum,
-                pageSize
-              }))
-            }
           />
         </div>
 
         {detailModalMode === 'uploadResult' ? (
           <div className={styles.A1ImportFooter}>
-            <Button onClick={() => uploadBtnRef.current?.openPicker()}>重新上传表格</Button>
+            <Button onClick={reuploadFu}>重新上传表格</Button>
             <Button
               type='primary'
               loading={actionLoading === 'confirm'}
@@ -611,7 +625,7 @@ function ImportData({ closeFu }: Props) {
             >
               导入校验成功的数据
             </Button>
-            <Button loading={actionLoading === 'cancel'} onClick={cancelImport}>
+            <Button onClick={closeDetailModal}>
               取消导入
             </Button>
           </div>

+ 32 - 2
src/pages/A1check/A1ledger/index.tsx

@@ -9,14 +9,44 @@ import { openLink } from '@/utils/history'
 import { A1_APIgetList, A1_APIreset } from '@/store/action/A1check/A1ledger'
 import { useSelector } from 'react-redux'
 import { RootState } from '@/store'
+import { baseUrlTemp, envFlag } from '@/utils/http'
+import { getTokenInfo } from '@/utils/storage'
+import dayjs from 'dayjs'
 import ImportData from './ImportData'
 
 function A1ledger() {
   const topDomRef = useRef<any>(null)
   const [showImportData, setShowImportData] = useState(false)
+  const [exportLoading, setExportLoading] = useState(false)
 
   const tableInfo = useSelector((state: RootState) => state.A1ledger.tableInfo)
+  const onDownLoadData = useCallback(async () => {
+    setExportLoading(true)
+    try {
+      const { token } = getTokenInfo()
+      const requestBaseURL = envFlag ? '/api/' : `${baseUrlTemp}/api/`
+      const response = await fetch(`${requestBaseURL}cms/importLedger/export`, {
+        method: 'GET',
+        headers: token ? { token } : undefined
+      })
 
+      if (!response.ok) throw new Error('download failed')
+
+      const blob = await response.blob()
+      const blobUrl = URL.createObjectURL(blob)
+      const link = document.createElement('a')
+      link.href = blobUrl
+      link.download = `${dayjs().format('YYYY-MM-DD-HH-mm-ss')}-盘库总账.xlsx`
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+      setTimeout(() => URL.revokeObjectURL(blobUrl), 100)
+    } catch (e) {
+      MessageFu.error('下载失败')
+    } finally {
+      setExportLoading(false)
+    }
+  }, [])
   const resetFu = useCallback(async () => {
     const res = await A1_APIreset()
     if (res.code === 0) {
@@ -29,12 +59,12 @@ function A1ledger() {
     () => (
       <>
         <AAbtn txt={1} onClick={() => setShowImportData(true)} tit='导入数据' />
-        <AAbtn txt={1} onClick={() => MessageFu.warning('功能开发中')} tit='导出数据' />
+        <AAbtn txt={1} onClick={() => onDownLoadData()} tit='导出数据' loading={exportLoading} />
         <AAbtn txt={1} onClick={() => topDomRef.current.clickSearch()} tit='查询' />
         <AAbtn txt={2} onClick={() => topDomRef.current.clickReset()} />
       </>
     ),
-    []
+    [exportLoading, onDownLoadData]
   )
 
   return (

+ 298 - 5
src/pages/A1check/A2manage/A2add/index.module.scss

@@ -49,6 +49,18 @@
       }
     }
 
+    .A2addTitleText {
+      font-size: 18px;
+      font-weight: 700;
+      color: #5a4335;
+      line-height: 1.2;
+    }
+
+    .A2addTitleBtn {
+      margin-left: auto;
+      flex: 0 0 auto;
+    }
+
     .A2addForm {
       display: flex;
       flex-wrap: wrap;
@@ -74,14 +86,33 @@
     }
 
     .A2addGroup {
-      width: 33.3333%;
       display: flex;
+      width: 33.3333%;
 
       &:not(:last-child) {
         border-right: 1px solid #ebe2d7;
       }
     }
 
+    .A2addGroupWide {
+      flex: 2 1 0;
+      width: auto;
+    }
+
+    .A2addGroupLarge {
+      flex: 3 1 0;
+      width: auto;
+    }
+    .A2addGroupLarge-t {
+      flex: 4 1 0;
+      width: auto;
+    }
+
+    .A2addGroupSmall {
+      flex: 2 1 0;
+      width: auto;
+    }
+
     .A2addLabel {
       width: 102px;
       flex: 0 0 102px;
@@ -93,6 +124,50 @@
       font-size: 14px;
     }
 
+    .A2addValue {
+      flex: 1 1 auto;
+      min-width: 0;
+      display: flex;
+      align-items: center;
+      padding: 0 14px;
+      color: #5a4335;
+      background-color: #fff;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .A2addValueBetween {
+      justify-content: space-between;
+      gap: 12px;
+    }
+
+    .A2addValueCenter {
+      justify-content: center;
+      text-align: center;
+      padding-left: 8px;
+      padding-right: 8px;
+    }
+
+    .A2addValueText {
+      min-width: 0;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    .A2addSubmitTag {
+      flex: 0 0 auto;
+    }
+
+    .A2addValueMulti {
+      white-space: pre-wrap;
+      align-items: flex-start;
+      padding-top: 12px;
+      padding-bottom: 12px;
+      line-height: 1.6;
+    }
+
     .A2addRow {
       .ant-input,
       .ant-select,
@@ -127,12 +202,76 @@
       }
     }
 
-    .A2addSearch {
+    .A2addRowDetailTop,
+    .A2addRowDetailBottom {
+      width: 100%;
+    }
+
+    .A2addGroupStatus {
+      flex: 1 1 0;
+      width: auto;
+
+      .A2addValue {
+        font-size: 13px;
+      }
+    }
+
+    .A2addGoodsBar {
       display: flex;
       align-items: center;
+      gap: 16px;
+      margin-bottom: 8px;
+    }
+
+    .A2addTabs {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      flex: 0 0 auto;
+    }
+
+    .A2addTabItem {
+      height: 34px;
+      padding: 0 16px;
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      color: #7b6a58;
+      background-color: #fff;
+      border: 1px solid #d9cbbb;
+      border-radius: 18px;
+      cursor: pointer;
+
+      &.active {
+        color: #fff;
+        background-color: #aa8f72;
+        border-color: #aa8f72;
+      }
+    }
+
+    .A2addGoodsInfo {
+      color: #7b6a58;
+      font-size: 14px;
+      white-space: nowrap;
+    }
+
+    .A2addGoodsBtn {
+      margin-left: auto;
+    }
+
+    .A2addSearch {
+      display: flex;
+      align-items: flex-start;
+      gap: 14px 16px;
+      margin-bottom: 0;
+    }
+
+    .A2addSearchFields {
+      flex: 1 1 auto;
+      min-width: 0;
+      display: flex;
       flex-wrap: wrap;
       gap: 14px 12px;
-      margin-bottom: 0;
     }
 
     .A2addSearchItem {
@@ -178,24 +317,29 @@
     }
 
     .A2addSearchBtn {
-      margin-left: auto;
+      flex: 0 0 auto;
       display: flex;
       align-items: center;
       gap: 10px;
+      justify-content: flex-end;
+      white-space: nowrap;
     }
 
     .A2addTable {
       flex: 1 1 auto;
       min-height: 0;
       padding-top: 15px;
+      padding-bottom: 10px;
+      overflow: hidden;
     }
 
     .A2addFooter {
+      flex: 0 0 auto;
       display: flex;
       align-items: center;
 
       gap: 12px;
-      padding: 16px;
+      // padding: 16px;
       background-color: #fcf9f5;
       border-radius: 14px;
       // box-shadow: 0 -4px 18px rgba(92, 66, 38, 0.05);
@@ -326,3 +470,152 @@
     }
   }
 }
+
+.A2ValidateTable {
+  :global {
+    .ant-table-cell {
+      padding: 10px !important;
+    }
+  }
+}
+
+.A2ValidateFooter {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  margin-top: 18px;
+
+  :global {
+    .ant-btn {
+      min-width: 96px;
+      height: 34px;
+      border-radius: 8px;
+    }
+  }
+}
+
+.A2ValidateModalWrap {
+  :global {
+    .ant-modal-content {
+      border-radius: 14px;
+    }
+  }
+}
+
+.A2FillTableWrap {
+  flex: 1 1 auto;
+  min-height: 0;
+  padding-top: 16px;
+  display: flex;
+  flex-direction: column;
+}
+
+.A2FillTable {
+  flex: 1 1 auto;
+  min-height: 0;
+
+  :global {
+    .ant-spin-nested-loading,
+    .ant-spin-container {
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      min-height: 0;
+    }
+
+    .ant-table {
+      background: transparent;
+      flex: 1 1 auto;
+      min-height: 0;
+    }
+
+    .ant-table-container {
+      border-radius: 10px;
+      overflow: hidden;
+    }
+
+    .ant-table-body {
+      min-height: 0;
+    }
+
+    .ant-table-cell {
+      height: auto;
+      vertical-align: top;
+      padding-top: 10px !important;
+      padding-bottom: 10px !important;
+    }
+
+    .ant-select {
+      width: 100%;
+    }
+
+    .ant-select-selector {
+      height: 34px !important;
+      border-radius: 6px !important;
+      align-items: center;
+    }
+
+    .ant-pagination {
+      margin-bottom: 0;
+      padding-top: 12px;
+    }
+
+    .ant-select-selection-item,
+    .ant-select-selection-placeholder {
+      line-height: 32px !important;
+    }
+
+    .ant-select-multiple {
+      .ant-select-selector {
+        height: auto !important;
+        min-height: 34px !important;
+        padding-top: 3px !important;
+        padding-bottom: 3px !important;
+        align-items: flex-start !important;
+      }
+
+      .ant-select-selection-wrap {
+        align-items: flex-start;
+        max-width: 200px;
+      }
+
+      .ant-select-selection-overflow {
+        width: 200px;
+        max-width: 200px;
+        flex-wrap: wrap;
+        gap: 4px 0;
+      }
+
+      .ant-select-selection-overflow-item {
+        max-width: 100%;
+        flex: 0 0 auto;
+      }
+
+      .ant-select-selection-item {
+        height: auto !important;
+        line-height: 20px !important;
+        white-space: normal;
+        margin-top: 0 !important;
+        margin-bottom: 0 !important;
+      }
+
+      .ant-select-selection-item-content {
+        white-space: normal;
+      }
+
+      .ant-select-selection-placeholder {
+        line-height: 20px !important;
+        white-space: normal;
+        inset-block-start: 50%;
+        transform: translateY(-50%);
+        max-width: 200px;
+      }
+    }
+  }
+}
+
+.A2FillRemarkLink {
+  color: #6e5848;
+  text-decoration: underline;
+  cursor: pointer;
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1225 - 167
src/pages/A1check/A2manage/A2add/index.tsx


+ 5 - 114
src/pages/A1check/A2manage/A2info/index.tsx

@@ -1,123 +1,14 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react'
-import styles from './index.module.scss'
-import { GoodsType } from '@/pages/AAnew/data'
-import { Button } from 'antd'
-import AAbtn from '@/pages/ZcomPage/AAbtn'
-import { A2_APIgetInfo, A2_APIgetListSon } from '@/store/action/A1check/A2manage'
-import { MessageFu } from '@/utils/message'
-import TopSearch from '@/pages/ZcomPage/TopSearch'
-import { A2columns, A2topArr } from '../data'
-import { openLink } from '@/utils/history'
-import { useSelector } from 'react-redux'
-import { RootState } from '@/store'
-
-const txtArr: any[] = [
-  { key: 'name', name: '盘点名称' },
-  { key: 'effect', name: '生效状态' },
-  { key: 'handler', name: '经办人' },
-  { key: 'remark', name: '盘点说明', width: '66%' },
-  { key: 'owner', name: '负责人' }
-]
+import React from 'react'
+import A2add from '../A2add'
 
 type Props = {
   sId: number
   closeFu: () => void
+  saveSuccessFu?: () => void
 }
 
-function A2info({ sId, closeFu }: Props) {
-  const [info, setInfo] = useState({} as GoodsType)
-
-  const getInfoFu = useCallback(async () => {
-    const res = await A2_APIgetInfo(sId)
-    if (res.code === 0) {
-      setInfo(res.data)
-    }
-  }, [sId])
-
-  useEffect(() => {
-    getInfoFu()
-  }, [getInfoFu])
-
-  const tab2Ref = useRef<any>(null)
-
-  const tableInfoSon = useSelector((state: RootState) => state.A2manage.tableInfoSon)
-
-  return (
-    <div className={styles.A2info}>
-      {info.id ? (
-        <>
-          <div className='A2Itit'>
-            <div className='A2Itit1'>盘点详情</div>
-            <div className='A2Itit2'>
-              <AAbtn txt={2} onClick={closeFu} tit='返回' />
-            </div>
-          </div>
-
-          <div className='A2top'>
-            {txtArr.map((item, index) => (
-              <div key={index} className='A2topRow' style={{ width: item.width || '33%' }}>
-                <div>{item.name}</div>
-                <p title={item.resFu ? item.resFu(info) : info[item.key]}>
-                  {item.resFu ? item.resFu(info) : info[item.key] || '(空)'}
-                </p>
-              </div>
-            ))}
-          </div>
-
-          <div className='A2Itit'>
-            <div className='A2Itit1'>
-              盘点藏品{' '}
-              <span>
-                账物相符 {info.pcsConform} | 账物不符 {info.pcsUnConform} | 待登记 {info.pcsHold}
-              </span>
-            </div>
-            <div className='A2Itit2'>
-              <AAbtn txt={1} onClick={() => MessageFu.warning('功能开发中')} tit='新增' />
-            </div>
-          </div>
-
-          <TopSearch
-            waiId={info.id}
-            classKey='A2info'
-            yHeight={450}
-            ref={tab2Ref}
-            leftArr={A2topArr}
-            rightBtn={
-              <>
-                <AAbtn txt={1} onClick={() => tab2Ref.current.clickSearch()} tit='查询' />
-                <AAbtn txt={2} onClick={() => tab2Ref.current.clickReset()} />
-              </>
-            }
-            waiWidth={190}
-            sonWidth='12%'
-            getListAPI={A2_APIgetListSon}
-            tableInfo={tableInfoSon}
-            columnsTemp={A2columns('详情')}
-            tableLastBtn={[
-              {
-                title: '操作',
-                fixed: 'right',
-                width: 100,
-                render: (item: any) => {
-                  return (
-                    <Button
-                      size='small'
-                      type='text'
-                      onClick={() =>
-                        openLink(`/goodsLook/${item.numName || null}/${item.num || null}`)
-                      }
-                    >
-                      查看
-                    </Button>
-                  )
-                }
-              }
-            ]}
-          />
-        </>
-      ) : null}
-    </div>
-  )
+function A2info({ sId, closeFu, saveSuccessFu }: Props) {
+  return <A2add detailId={sId} mode='view' closeFu={closeFu} saveSuccessFu={saveSuccessFu} />
 }
 
 const MemoA2info = React.memo(A2info)

+ 5 - 3
src/pages/A1check/A2manage/data.ts

@@ -50,7 +50,7 @@ export const A2topArr = [
   }
 ]
 
-export const A2columns = (val: '列表' | '详情') => {
+export const A2columns = (val: '列表' | '详情', lookFu?: (id: any) => void) => {
   const arr1 = [
     ['index', '序号', 100],
     ['txt2', '编号类型', 'numName', 150],
@@ -63,10 +63,12 @@ export const A2columns = (val: '列表' | '详情') => {
     ['txt', '账物不符(件)', 'pcsUnConform'],
     ['txt', '不符情形', 'unInfo'],
     ['txt', '不符原因', 'reasonInfo'],
-    ['txt', '备注', 'remark']
+    ['txt', '备注', 'remark'],
+    ['txt', '经办人', 'handler'],
+    ['txt', '编辑时间', 'updateTime']
   ]
   const arr2 = [
-    ['txt', '盘点名称', 'name'],
+    ['text', '盘点名称', 'name', 20, 'S', lookFu, 'firmVersionId'],
     ['txt', '经办人', 'handler'],
     ['txt', '负责人', 'owner'],
     ['txt', '生效状态', 'effect']

+ 3 - 0
src/pages/A1check/A2manage/index.module.scss

@@ -12,6 +12,9 @@
         #AAbtn {
           margin-left: 5px;
         }
+        > button {
+          margin-right: 10px;
+        }
       }
     }
     #TopSearch {

+ 49 - 41
src/pages/A1check/A2manage/index.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useRef, useState } from 'react'
+import React, { useCallback, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
 import { Button } from 'antd'
 import AAbtn from '@/pages/ZcomPage/AAbtn'
@@ -21,6 +21,17 @@ function A2manage() {
   const tab2Ref = useRef<any>(null)
 
   const tableInfo = useSelector((state: RootState) => state.A2manage.tableInfo2)
+  const tableInfo2Temp = useMemo(
+    () => ({
+      ...tableInfo,
+      list: (tableInfo.list || []).map((item: any) => ({
+        ...item,
+        firmVersionId:
+          item.firmVersionId || item.checkFirmVersionId || item.versionId || item.waiId || item.id
+      }))
+    }),
+    [tableInfo]
+  )
 
   // 按盘点批次-点击查看
   const [lookId, setLookId] = useState(0)
@@ -36,46 +47,41 @@ function A2manage() {
     }, 50)
   }, [])
 
+  const lookSuccessFu = useCallback(() => {
+    setTimeout(() => {
+      tab1Ref.current?.clickReset()
+    }, 50)
+  }, [])
+
+  const openLookFu = useCallback((id: any) => {
+    const tempId = Number(id || 0)
+    if (!tempId) return
+    setLookId(tempId)
+  }, [])
+
   return (
     <div className={styles.A2manage}>
-      <div className='pageTitle'>{lookId ? '实盘详情' : addFlag ? '新增盘点单' : '盘库管理'}</div>
-
-      <div className='A2top'>
-        <div>
-          {tabArr.map(item => (
-            <Button
-              onClick={() => setTabAc(item)}
-              type={tabAc === item ? 'primary' : 'default'}
-              size='large'
-              key={item}
-            >
-              {item}
-            </Button>
-          ))}
-        </div>
-        <div>
-          <AAbtn
-            width={120}
-            txt={1}
-            onClick={() => setAddFlag(true)}
-            tit='新增盘点单'
-          />
-          {/* <AAbtn
-            txt={1}
-            onClick={() => downloadFileByUrl('./myData/xlsx/实盘数据-导入.xlsx')}
-            tit='下载模板'
-          />
-          <UpBtn
-            tit='导入盘点结果'
-            url='cms/importFirm/uploadExcel'
-            width={120}
-            backFu={() => {
-              if (tabAc === '按盘点批次') tab1Ref.current.clickReset()
-              else if (tabAc === '按藏品明细') tab2Ref.current.clickReset()
-            }}
-          /> */}
+      {!lookId && !addFlag ? <div className='pageTitle'>库内盘点结果</div> : null}
+
+      {!lookId && !addFlag ? (
+        <div className='A2top'>
+          <div>
+            {tabArr.map(item => (
+              <Button
+                onClick={() => setTabAc(item)}
+                type={tabAc === item ? 'primary' : 'default'}
+                size='large'
+                key={item}
+              >
+                {item}
+              </Button>
+            ))}
+          </div>
+          <div>
+            <AAbtn width={120} txt={1} onClick={() => setAddFlag(true)} tit='新增盘点单' />
+          </div>
         </div>
-      </div>
+      ) : null}
 
       {tabAc === '按盘点批次' ? <A2tab1 setLookIdFu={id => setLookId(id)} ref={tab1Ref} /> : null}
 
@@ -94,8 +100,8 @@ function A2manage() {
           waiWidth={190}
           sonWidth='12%'
           getListAPI={A2_APIgetList2}
-          tableInfo={tableInfo}
-          columnsTemp={A2columns('列表')}
+          tableInfo={tableInfo2Temp}
+          columnsTemp={A2columns('列表', openLookFu)}
           tableLastBtn={[
             {
               title: '操作',
@@ -120,7 +126,9 @@ function A2manage() {
       ) : null}
 
       {/* 按盘点批次-点击查看详情页 */}
-      {lookId ? <A2info sId={lookId} closeFu={() => setLookId(0)} /> : null}
+      {lookId ? (
+        <A2info sId={lookId} closeFu={() => setLookId(0)} saveSuccessFu={lookSuccessFu} />
+      ) : null}
 
       {addFlag ? <A2add closeFu={closeAddFu} saveSuccessFu={addSuccessFu} /> : null}
     </div>

+ 14 - 0
src/pages/A1check/A3result/index.module.scss

@@ -6,3 +6,17 @@
   // :global {
   // }
 }
+
+.A3resultExportMenu {
+  :global {
+    .ant-dropdown-menu-item {
+      justify-content: center;
+      text-align: center;
+    }
+
+    .ant-dropdown-menu-title-content {
+      width: 100%;
+      text-align: center;
+    }
+  }
+}

+ 78 - 12
src/pages/A1check/A3result/index.tsx

@@ -6,7 +6,7 @@ import { A3topArr } from './data'
 import { A3_APIgetList } from '@/store/action/A1check/A3result'
 import { useSelector } from 'react-redux'
 import { RootState } from '@/store'
-import { Button } from 'antd'
+import { Button, Dropdown } from 'antd'
 import { openLink } from '@/utils/history'
 import dayjs from 'dayjs'
 
@@ -31,6 +31,59 @@ const columnsTemp = [
   ['txt', '负责人', 'checkOwner']
 ]
 
+const exportColumnsMap = {
+  workSheet: [
+    ['index', '序号', 100],
+    ['txt2', '编号类型', 'numName', 150],
+    ['txt2', '编号', 'num', 150],
+    ['txt2', '名称', 'name', 150],
+    ['txt', '级别', 'level'],
+    ['txt', '类别', 'type'],
+    ['txt', '盘点状态', 'status'],
+    ['txt', '最新盘点名称', 'checkName'],
+    ['txt', '盘点时间', 'updateTime'],
+    ['txt', '经办人', 'checkHandler'],
+    ['txt', '负责人', 'checkOwner']
+  ],
+  diffSheet: [
+    ['index', '序号', 100],
+    ['txt2', '编号类型', 'numName', 150],
+    ['txt2', '编号', 'num', 150],
+    ['txt2', '名称', 'name', 150],
+    ['txt', '藏品总登记账具体数量(件)', 'pcsRegister'],
+    ['txt', '账物相符(件)', 'pcsConform'],
+    ['txt', '账物不符(件)', 'pcsUnConform'],
+    ['txt', '不符情形', 'unInfo'],
+    ['txt', '不符原因', 'reasonInfo'],
+    ['txt', '备注', 'remark'],
+    ['txt', '最新盘点名称', 'checkName'],
+    ['txt', '盘点时间', 'updateTime'],
+    ['txt', '经办人', 'checkHandler'],
+    ['txt', '负责人', 'checkOwner']
+  ],
+  summarySheet: [
+    ['index', '序号', 100],
+    ['txt2', '编号类型', 'numName', 150],
+    ['txt2', '编号', 'num', 150],
+    ['txt2', '名称', 'name', 150],
+    ['txt', '盘点状态', 'status'],
+    ['txt', '相符情况', 'conform'],
+    ['txt', '藏品总登记账具体数量(件)', 'pcsRegister'],
+    ['txt', '账物相符(件)', 'pcsConform'],
+    ['txt', '账物不符(件)', 'pcsUnConform'],
+    ['txt', '最新盘点名称', 'checkName'],
+    ['txt', '盘点时间', 'updateTime'],
+    ['txt', '经办人', 'checkHandler'],
+    ['txt', '负责人', 'checkOwner']
+  ]
+} as const
+
+const exportMenuItems = [
+  { key: 'workSheet', label: '盘点清点工作表' },
+  { key: 'diffSheet', label: '账物不符情况表' },
+  { key: 'summarySheet', label: '清点结果统计表' }
+]
+
 function A3result() {
   const topDomRef = useRef<any>(null)
 
@@ -38,27 +91,40 @@ function A3result() {
 
   // --------------右侧按钮
   const rightBtn = useMemo(() => {
+    const exportByType = (key: keyof typeof exportColumnsMap, label: string) => {
+      topDomRef.current.exportXlsx(
+        exportColumnsMap[key],
+        `${label}${dayjs(new Date()).format('YYYY-MM-DD HH:mm')}`
+      )
+    }
+
     return (
       <>
+        <Dropdown
+          trigger={['click']}
+          overlayClassName={styles.A3resultExportMenu}
+          menu={{
+            items: exportMenuItems,
+            onClick: ({ key }) =>
+              exportByType(
+                key as keyof typeof exportColumnsMap,
+                exportMenuItems.find(item => item.key === key)?.label || '馆内盘点结果'
+              )
+          }}
+        >
+          <div>
+            <AAbtn txt={1} onClick={() => undefined} tit='导出数据' />
+          </div>
+        </Dropdown>
         <AAbtn txt={1} onClick={() => topDomRef.current.clickSearch()} tit='查询' />
         <AAbtn txt={2} onClick={() => topDomRef.current.clickReset()} />
-        <AAbtn
-          txt={1}
-          onClick={() =>
-            topDomRef.current.exportXlsx(
-              columnsTemp,
-              '盘库结果' + dayjs(new Date()).format('YYYY-MM-DD HH:mm')
-            )
-          }
-          tit='导出'
-        />
       </>
     )
   }, [])
 
   return (
     <div className={styles.A3result}>
-      <div className='pageTitle'>盘库结果</div>
+      <div className='pageTitle'>馆内盘点结果</div>
 
       <TopSearch
         yHeight={650}

+ 1 - 1
src/pages/Layout/index.tsx

@@ -42,7 +42,7 @@ function Layout() {
 
   // 获取用户权限信息
   const getUserAuthFu = useCallback(async (userInfo: any) => {
-    // 一级用户 只有 盘库结果汇总
+    // 一级用户 只有 馆内盘点结果汇总
     const pageIds1 = [5]
 
     // 二级用户有 1 2 3 4

+ 11 - 0
src/pages/ZcomPage/AAbtn/index.module.scss

@@ -16,6 +16,17 @@
   }
 }
 
+.AAbtnLoading {
+  cursor: not-allowed;
+  pointer-events: none;
+  opacity: 0.9;
+  gap: 6px;
+}
+
+.AAbtnLoadingIcon {
+  font-size: 14px;
+}
+
 .AAbtn2 {
   color: var(--themeColor);
 }

+ 10 - 3
src/pages/ZcomPage/AAbtn/index.tsx

@@ -2,6 +2,7 @@ import React, { useMemo } from 'react'
 import styles from './index.module.scss'
 import classNames from 'classnames'
 import { Popconfirm } from 'antd'
+import { LoadingOutlined } from '@ant-design/icons'
 
 const btnObj = {
   1: {
@@ -24,9 +25,10 @@ type Props = {
   tit?: string
   txtArr?: string[]
   width?: number
+  loading?: boolean
 }
 
-function AAbtn({ txt, onClick, tit, txtArr, width }: Props) {
+function AAbtn({ txt, onClick, tit, txtArr, width, loading = false }: Props) {
   const info = useMemo(() => {
     return btnObj[txt]
   }, [txt])
@@ -62,9 +64,14 @@ function AAbtn({ txt, onClick, tit, txtArr, width }: Props) {
             backgroundImage: `url(${info.url})`,
             width: width ? width + 'px' : ''
           }}
-          onClick={onClick}
-          className={classNames(styles.AAbtn, styles[`AAbtn${txt}`])}
+          onClick={loading ? undefined : onClick}
+          className={classNames(
+            styles.AAbtn,
+            styles[`AAbtn${txt}`],
+            loading ? styles.AAbtnLoading : ''
+          )}
         >
+          {loading ? <LoadingOutlined className={styles.AAbtnLoadingIcon} rev={undefined} /> : null}
           {tit || info.name}
         </div>
       )}

+ 6 - 2
src/pages/ZcomPage/UpBtn.tsx

@@ -14,9 +14,10 @@ type Props = {
   tit?: string
   width?: number
   onSuccess?: (res: any, file: File) => void | Promise<void>
+  onFileSelected?: (file: File) => void
 }
 
-function UpBtn({ url, backFu, tit, width, onSuccess }: Props, ref: any) {
+function UpBtn({ url, backFu, tit, width, onSuccess, onFileSelected }: Props, ref: any) {
   const myInput = useRef<HTMLInputElement>(null)
 
   // 上传文件
@@ -25,6 +26,9 @@ function UpBtn({ url, backFu, tit, width, onSuccess }: Props, ref: any) {
       if (e.target.files) {
         // 拿到files信息
         const filesInfo = e.target.files[0]
+        if (!filesInfo) return
+
+        if (onFileSelected) onFileSelected(filesInfo)
 
         let fileNmae: string = filesInfo.name
         fileNmae = fileNmae.toLowerCase()
@@ -59,7 +63,7 @@ function UpBtn({ url, backFu, tit, width, onSuccess }: Props, ref: any) {
         }
       }
     },
-    [backFu, onSuccess, url]
+    [backFu, onFileSelected, onSuccess, url]
   )
 
   useImperativeHandle(ref, () => ({

+ 2 - 2
src/setupProxy.js

@@ -4,8 +4,8 @@ module.exports = function (app) {
   app.use(
     '/api',
     createProxyMiddleware({
-      target: 'https://sit-ciwu.4dage.com',
-      // target: 'http://192.168.0.65:8096',
+      // target: 'https://sit-ciwu.4dage.com',
+      target: 'http://192.168.0.65:8096',
       changeOrigin: true,
       secure: false
     })

+ 4 - 4
src/store/action/A1check/A2manage.ts

@@ -2,7 +2,7 @@ import { AppDispatch } from '@/store'
 import http from '@/utils/http'
 
 /**
- * 盘库管理-按盘点批次 - 获取分页列表
+ * 库内盘点结果-按盘点批次 - 获取分页列表
  */
 export const A2_APIgetList1 = (data: any, exportFlag?: boolean): any => {
   if (exportFlag) return http.post('cms/checkFirmVersion/pageList', data)
@@ -20,7 +20,7 @@ export const A2_APIgetList1 = (data: any, exportFlag?: boolean): any => {
   }
 }
 /**
- * 盘库管理-按藏品明细 - 获取分页列表
+ * 库内盘点结果-按藏品明细 - 获取分页列表
  */
 export const A2_APIgetList2 = (data: any, exportFlag?: boolean): any => {
   if (exportFlag) return http.post('cms/importFirm/pageList', data)
@@ -39,7 +39,7 @@ export const A2_APIgetList2 = (data: any, exportFlag?: boolean): any => {
 }
 
 /**
- * 盘库管理-按盘点批次-里面的列表
+ * 库内盘点结果-按盘点批次-里面的列表
  */
 export const A2_APIgetListSon = (data: any): any => {
   return async (dispatch: AppDispatch) => {
@@ -55,7 +55,7 @@ export const A2_APIgetListSon = (data: any): any => {
 }
 
 /**
- * 盘库管理-按盘点批次-详情
+ * 库内盘点结果-按盘点批次-详情
  */
 export const A2_APIgetInfo = (id: number) => {
   return http.get(`cms/checkFirmVersion/detail/${id}`)

+ 1 - 1
src/store/action/A1check/A3result.ts

@@ -2,7 +2,7 @@ import { AppDispatch } from '@/store'
 import http from '@/utils/http'
 
 /**
- * 盘库结果 - 获取分页列表
+ * 馆内盘点结果 - 获取分页列表
  */
 export const A3_APIgetList = (data: any, exportFlag?: boolean): any => {
   if (exportFlag) return http.post('cms/checkMatch/pageList', data)