shaogen1995 10 mesiacov pred
rodič
commit
1aef49e1eb

+ 119 - 124
src/components/ZupOne/index.tsx

@@ -1,41 +1,41 @@
-import React, { useCallback, useMemo, useRef, useState } from "react";
-import styles from "./index.module.scss";
-import ImageLazy from "@/components/ImageLazy";
+import React, { useCallback, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import ImageLazy from '@/components/ImageLazy'
 import {
   PlusOutlined,
   EyeOutlined,
   CloseOutlined,
   DownloadOutlined,
-  UploadOutlined,
-} from "@ant-design/icons";
-import store from "@/store";
-import { baseURL } from "@/utils/http";
-import classNames from "classnames";
-import { Button } from "antd";
-import { MessageFu } from "@/utils/message";
-import { fileDomInitialFu } from "@/utils/domShow";
-import { API_upFile } from "@/store/action/layout";
-import { forwardRef, useImperativeHandle } from "react";
-import MyPopconfirm from "../MyPopconfirm";
+  UploadOutlined
+} from '@ant-design/icons'
+import store from '@/store'
+import { baseURL } from '@/utils/http'
+import classNames from 'classnames'
+import { Button } from 'antd'
+import { MessageFu } from '@/utils/message'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { API_upFile } from '@/store/action/layout'
+import { forwardRef, useImperativeHandle } from 'react'
+import MyPopconfirm from '../MyPopconfirm'
 
-type MyTypeType = "thumb" | "video" | "audio" | "model" | "pdf";
+type MyTypeType = 'thumb' | 'video' | 'audio' | 'model' | 'pdf' | 'equb'
 
 // 这个组件 只处理 上传 一张图片或者 视频 音频 模型 pdf 的情况
 
 type Props = {
-  fileCheck: boolean; //有没有点击过确定
-  size: number; //上传附件大小(M)
-  dirCode: string; //文件的code码
-  myUrl: string; //请求地址
-  format: string[]; //上传格式 ["image/jpeg", "image/png"] ["video/mp4"] ,application/pdf
-  formatTxt: string; //上传图片提示
-  checkTxt: string;
-  upTxt: string;
-  myType: MyTypeType;
-  isLook?: boolean; //是不是查看
-  fromData?: any;
-  ref: any; //当前自己的ref,给父组件调用
-};
+  fileCheck: boolean //有没有点击过确定
+  size: number //上传附件大小(M)
+  dirCode: string //文件的code码
+  myUrl: string //请求地址
+  format: string[] //上传格式 ["image/jpeg", "image/png"] ["video/mp4"] ,application/pdf
+  formatTxt: string //上传图片提示
+  checkTxt: string
+  upTxt: string
+  myType: MyTypeType
+  isLook?: boolean //是不是查看
+  fromData?: any
+  ref: any //当前自己的ref,给父组件调用
+}
 
 function ZupOne(
   {
@@ -49,116 +49,119 @@ function ZupOne(
     upTxt,
     myType,
     isLook = false,
-    fromData,
+    fromData
   }: Props,
   ref: any
 ) {
   const [fileUrl, setFileUrl] = useState({
-    fileName: "",
-    filePath: "",
-  });
+    fileName: '',
+    filePath: ''
+  })
 
-  const myInput = useRef<HTMLInputElement>(null);
+  const myInput = useRef<HTMLInputElement>(null)
 
   // 上传封面图
   const handeUpPhoto = useCallback(
     async (e: React.ChangeEvent<HTMLInputElement>) => {
       if (e.target.files) {
         // 拿到files信息
-        const filesInfo = e.target.files[0];
+        const filesInfo = e.target.files[0]
         // console.log("-----", filesInfo.type);
 
         // 校验格式
-        const type = format;
+        const type = format
 
-        if (myType === "pdf") {
-          if (!filesInfo.type.includes("pdf")) {
-            e.target.value = "";
-            return MessageFu.warning(`只支持${formatTxt}格式!`);
+        if (myType === 'pdf') {
+          if (!filesInfo.type.includes('pdf')) {
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
+          }
+        } else if (myType === 'equb') {
+          if (!filesInfo.name.endsWith('.equb')) {
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
           }
         } else {
           if (!type.includes(filesInfo.type)) {
-            e.target.value = "";
-            return MessageFu.warning(`只支持${formatTxt}格式!`);
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
           }
         }
 
         // 校验大小
         if (filesInfo.size > size * 1024 * 1024) {
-          e.target.value = "";
-          return MessageFu.warning(`最大支持${size}M!`);
+          e.target.value = ''
+          return MessageFu.warning(`最大支持${size}M!`)
         }
         // 创建FormData对象
-        const fd = new FormData();
+        const fd = new FormData()
         // 把files添加进FormData对象(‘photo’为后端需要的字段)
-        let myTypeRes: string = myType;
-        if (["pdf"].includes(myTypeRes)) myTypeRes = "doc";
-        fd.append("type", myTypeRes);
-        fd.append("dirCode", dirCode);
-        fd.append("file", filesInfo);
+        let myTypeRes: string = myType
+        if (['pdf', 'equb'].includes(myTypeRes)) myTypeRes = 'doc'
+        fd.append('type', myTypeRes)
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
 
         if (fromData) {
           for (const k in fromData) {
-            if (fromData[k]) fd.append(k, fromData[k]);
+            if (fromData[k]) fd.append(k, fromData[k])
           }
         }
 
-        e.target.value = "";
+        e.target.value = ''
 
         try {
-          const res = await API_upFile(fd, myUrl);
+          const res = await API_upFile(fd, myUrl)
           if (res.code === 0) {
-            MessageFu.success("上传成功!");
-            setFileUrl(res.data);
+            MessageFu.success('上传成功!')
+            setFileUrl(res.data)
           }
-          fileDomInitialFu();
+          fileDomInitialFu()
         } catch (error) {
-          fileDomInitialFu();
+          fileDomInitialFu()
         }
       }
     },
     [dirCode, format, formatTxt, fromData, myType, myUrl, size]
-  );
+  )
 
   // 让父组件调用的 回显 附件 地址
-  const setFileComFileFu = useCallback(
-    (valObj: { fileName: string; filePath: string }) => {
-      setFileUrl(valObj);
-    },
-    []
-  );
+  const setFileComFileFu = useCallback((valObj: { fileName: string; filePath: string }) => {
+    setFileUrl(valObj)
+  }, [])
 
   // 让父组件调用的返回 附件 名字和路径
   const fileComFileResFu = useCallback(() => {
-    return fileUrl;
-  }, [fileUrl]);
+    return fileUrl
+  }, [fileUrl])
 
   // 可以让父组件调用子组件的方法
   useImperativeHandle(ref, () => ({
     setFileComFileFu,
-    fileComFileResFu,
-  }));
+    fileComFileResFu
+  }))
 
   const acceptRes = useMemo(() => {
-    let accept = ".png,.jpg,.jpeg";
-    if (myType === "video") accept = ".mp4";
-    else if (myType === "audio") accept = ".mp3";
-    else if (myType === "model") accept = ".4dage";
-    else if (myType === "pdf") accept = ".pdf";
-    return accept;
-  }, [myType]);
+    let accept = '.png,.jpg,.jpeg'
+    if (myType === 'video') accept = '.mp4'
+    else if (myType === 'audio') accept = '.mp3'
+    else if (myType === 'model') accept = '.4dage'
+    else if (myType === 'pdf') accept = '.pdf'
+    else if (myType === 'equb') accept = '.equb'
+    return accept
+  }, [myType])
 
   // 点击 预览(除了图片)
   const lookFileNoImgFu = useCallback(
     (type: MyTypeType) => {
-      if (type === "pdf" || type === "thumb") {
+      if (type === 'pdf' || type === 'thumb') {
         // 新窗口打开
-        window.open(baseURL + fileUrl.filePath);
-      } else {
+        window.open(baseURL + fileUrl.filePath)
+      } else if (type !== 'equb') {
         store.dispatch({
-          type: "layout/lookDom",
-          payload: { src: fileUrl.filePath, type },
-        });
+          type: 'layout/lookDom',
+          payload: { src: fileUrl.filePath, type }
+        })
       }
 
       // if (type === "pdf") {
@@ -166,28 +169,28 @@ function ZupOne(
       // }
     },
     [fileUrl.filePath]
-  );
+  )
 
   return (
     <div className={styles.ZupOne}>
       <input
-        id="upInput"
-        type="file"
+        id='upInput'
+        type='file'
         accept={acceptRes}
         ref={myInput}
-        onChange={(e) => handeUpPhoto(e)}
+        onChange={e => handeUpPhoto(e)}
       />
-      {myType === "thumb" ? (
+      {myType === 'thumb' ? (
         <div
-          hidden={fileUrl.filePath !== ""}
-          className="file_upIcon"
+          hidden={fileUrl.filePath !== ''}
+          className='file_upIcon'
           onClick={() => myInput.current?.click()}
         >
           <PlusOutlined rev={undefined} />
         </div>
       ) : (
         <Button
-          hidden={fileUrl.filePath !== ""}
+          hidden={fileUrl.filePath !== ''}
           onClick={() => myInput.current?.click()}
           icon={<UploadOutlined rev={undefined} />}
         >
@@ -196,49 +199,42 @@ function ZupOne(
       )}
 
       {/* 为图片的情况-------------- */}
-      {myType === "thumb" ? (
-        <div className="file_img" hidden={fileUrl.filePath === ""}>
-          {fileUrl ? (
-            <ImageLazy width={100} height={100} src={fileUrl.filePath} noLook />
-          ) : null}
+      {myType === 'thumb' ? (
+        <div className='file_img' hidden={fileUrl.filePath === ''}>
+          {fileUrl ? <ImageLazy width={100} height={100} src={fileUrl.filePath} noLook /> : null}
 
           {/* 删除 */}
-          <div className="file_closeBox" hidden={isLook}>
+          <div className='file_closeBox' hidden={isLook}>
             <MyPopconfirm
-              txtK="删除"
-              onConfirm={() => setFileUrl({ fileName: "", filePath: "" })}
+              txtK='删除'
+              onConfirm={() => setFileUrl({ fileName: '', filePath: '' })}
               Dom={<CloseOutlined rev={undefined} />}
             />
           </div>
 
           {/* 预览 下载 */}
-          <div className="file_lookBox">
+          <div className='file_lookBox'>
             <EyeOutlined
               onClick={() =>
                 store.dispatch({
-                  type: "layout/lookBigImg",
-                  payload: { url: baseURL + fileUrl.filePath, show: true },
+                  type: 'layout/lookBigImg',
+                  payload: { url: baseURL + fileUrl.filePath, show: true }
                 })
               }
               rev={undefined}
             />
-            <a
-              href={baseURL + fileUrl.filePath}
-              download
-              target="_blank"
-              rel="noreferrer"
-            >
+            <a href={baseURL + fileUrl.filePath} download target='_blank' rel='noreferrer'>
               <DownloadOutlined rev={undefined} />
             </a>
           </div>
         </div>
       ) : fileUrl.filePath ? (
-        <div className="fileInfo">
-          <div className="upSuccTxt">{fileUrl.fileName}</div>
+        <div className='fileInfo'>
+          <div className='upSuccTxt'>{fileUrl.fileName}</div>
           {/* 视频预览 */}
           <div
-            className="clearCover"
-            hidden={!fileUrl.filePath}
+            className='clearCover'
+            hidden={!fileUrl.filePath || myType === 'equb'}
             onClick={() => lookFileNoImgFu(myType)}
           >
             <EyeOutlined rev={undefined} />
@@ -247,36 +243,35 @@ function ZupOne(
           <a
             href={baseURL + fileUrl.filePath}
             download
-            target="_blank"
-            className="clearCover"
-            rel="noreferrer"
+            target='_blank'
+            className='clearCover'
+            rel='noreferrer'
           >
             <DownloadOutlined rev={undefined} />
           </a>
           {/* 视频删除 */}
 
-          <MyPopconfirm
-            txtK="删除"
-            onConfirm={() => setFileUrl({ fileName: "", filePath: "" })}
-            Dom={<CloseOutlined className="clearCover" rev={undefined} />}
-          />
+          {isLook ? null : (
+            <MyPopconfirm
+              txtK='删除'
+              onConfirm={() => setFileUrl({ fileName: '', filePath: '' })}
+              Dom={<CloseOutlined className='clearCover' rev={undefined} />}
+            />
+          )}
         </div>
       ) : null}
 
-      <div className="fileBoxRow_r_tit" hidden={isLook}>
+      <div className='fileBoxRow_r_tit' hidden={isLook}>
         格式要求:支持{formatTxt}格式;最大支持{size}M。{upTxt}
         <br />
         <div
-          className={classNames(
-            "noUpThumb",
-            !fileUrl.filePath && fileCheck ? "noUpThumbAc" : ""
-          )}
+          className={classNames('noUpThumb', !fileUrl.filePath && fileCheck ? 'noUpThumbAc' : '')}
         >
           {checkTxt}
         </div>
       </div>
     </div>
-  );
+  )
 }
 
-export default forwardRef(ZupOne);
+export default forwardRef(ZupOne)

+ 126 - 0
src/pages/A1manage/A1add/index.module.scss

@@ -0,0 +1,126 @@
+.A1add {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 12;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 24px;
+
+  :global {
+    .A1aMain {
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      textarea {
+        min-height: 75px !important;
+      }
+
+      .A1fromRow {
+        position: relative;
+        width: 800px;
+
+        .A1_1Frow {
+          position: absolute;
+          top: 0px;
+          left: 600px;
+          width: 200px;
+        }
+
+        .A1_4Frow {
+          position: absolute;
+          left: 400px;
+          top: 4px;
+        }
+
+        .A1_6Frow {
+          position: absolute;
+          left: 200px;
+          top: 5px;
+          color: #999;
+          font-size: 12px;
+        }
+      }
+
+      .ant-form {
+        width: 800px;
+
+        // .ant-input-affix-wrapper{
+        //   width: 800px;
+        // }
+        .formRow {
+          display: flex;
+
+          .formLeft {
+            position: relative;
+            top: 3px;
+            width: 100px;
+            text-align: right;
+
+            & > span {
+              color: #ff4d4f;
+            }
+          }
+
+          .formRight {
+            width: calc(100% - 100px);
+          }
+
+          .formRight5Tit {
+            position: relative;
+            top: -10px;
+            transition: top 0.2s;
+            margin: 5px 0;
+            color: #ff4d4f;
+            opacity: 0;
+            pointer-events: none;
+          }
+
+          .formRight5TitErr {
+            opacity: 1;
+            top: 0;
+          }
+        }
+
+        .A1abtn {
+          position: absolute;
+          z-index: 10;
+          left: 1200px;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+      }
+    }
+
+    // 从查看进入
+    .A1aMainLook {
+      // 左边的 label 也不让选中
+      label {
+        pointer-events: none;
+      }
+
+      .ant-picker {
+        pointer-events: none;
+      }
+
+      .ant-checkbox-wrapper {
+        pointer-events: none;
+      }
+
+      .ant-input-number {
+        pointer-events: none;
+      }
+
+      .ant-select {
+        pointer-events: none;
+      }
+
+      .ant-radio-wrapper {
+        pointer-events: none;
+      }
+    }
+  }
+}

+ 328 - 0
src/pages/A1manage/A1add/index.tsx

@@ -0,0 +1,328 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import classNames from 'classnames'
+import { A1EditInfoType } from '../data'
+import { Button, Cascader, Form, FormInstance, Input, InputNumber, Select } from 'antd'
+import { A1_APIgetInfo, A1_APIsave } from '@/store/action/A1manage'
+import { MessageFu } from '@/utils/message'
+import ZupOne from '@/components/ZupOne'
+import TextArea from 'antd/es/input/TextArea'
+import dayjs from 'dayjs'
+import { A2tableType, A2TreeType } from '@/types'
+import MyPopconfirm from '@/components/MyPopconfirm'
+
+type Props = {
+  editInfo: A1EditInfoType
+  closeFu: () => void
+  addTableFu: () => void
+  editTableFu: () => void
+  storageArr: A2TreeType[]
+  exhibitTypeArr: A2tableType[]
+}
+
+function A1add({ editInfo, closeFu, addTableFu, editTableFu, storageArr, exhibitTypeArr }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 封面图的ref
+  const ZupThumbRef = useRef<any>(null)
+
+  // txt的ref
+  const ZupTxtRef = useRef<any>(null)
+
+  // 编辑/查看 进入页面 获取信息
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await A1_APIgetInfo(id)
+    if (res.code === 0) {
+      const data = res.data
+
+      let storageIds = []
+      if (data.ancestor) storageIds = data.ancestor.split(',').map((v: string) => Number(v))
+      storageIds.push(Number(data.storageId))
+
+      const obj = {
+        ...data,
+        storageIds,
+        exhibitTypeId: Number(data.exhibitTypeId),
+        year: data.year ? data.year : null
+      }
+
+      FormBoxRef.current?.setFieldsValue(obj)
+
+      // 设置封面图
+      ZupThumbRef.current?.setFileComFileFu({
+        fileName: '',
+        filePath: data.thumb
+      })
+
+      // 设置txt
+      ZupTxtRef.current?.setFileComFileFu({
+        fileName: data.fileName,
+        filePath: data.filePath
+      })
+    }
+  }, [])
+
+  // 附件 是否 已经点击过确定
+  const [fileCheck, setFileCheck] = useState(false)
+
+  useEffect(() => {
+    if (editInfo.id > 0) {
+      getInfoFu(editInfo.id)
+    } else {
+      FormBoxRef.current?.setFieldsValue({
+        sort: 999,
+        year: dayjs().get('year')
+      })
+    }
+  }, [editInfo.id, getInfoFu])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    setFileCheck(true)
+  }, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      setFileCheck(true)
+
+      const coverUrl1 = ZupThumbRef.current?.fileComFileResFu()
+      // 没有传 封面图
+      if (!coverUrl1.filePath) return MessageFu.warning('请上传封面图!')
+
+      let fileName = ''
+      let filePath = ''
+      // txt附件
+      const ZupTxtRefObj = ZupTxtRef.current?.fileComFileResFu()
+      fileName = ZupTxtRefObj.fileName
+      filePath = ZupTxtRefObj.filePath
+      if (!filePath) return MessageFu.warning('请上传equb附件!')
+
+      let storageId: null | number = null
+
+      if (values.storageIds && values.storageIds.length) {
+        storageId = values.storageIds[values.storageIds.length - 1]
+      }
+
+      const obj = {
+        ...values,
+        id: editInfo.id > 0 ? editInfo.id : null,
+        thumb: coverUrl1.filePath,
+        storageId,
+        fileName,
+        filePath
+      }
+
+      // if (obj) {
+      //   console.log(123, obj);
+      //   return;
+      // }
+
+      const res = await A1_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success(`${editInfo.txt}成功!`)
+        editInfo.id > 0 ? editTableFu() : addTableFu()
+        closeFu()
+      }
+    },
+    [addTableFu, closeFu, editInfo.id, editInfo.txt, editTableFu]
+  )
+
+  // 年份
+  const [ageSelect, setAgeSelect] = useState<{ value: string; label: string }[]>([])
+
+  useEffect(() => {
+    const arr: { value: string; label: string }[] = []
+    const nowYear = dayjs().get('year')
+    const num = nowYear - 1900
+    for (let i = 0; i <= num; i++) {
+      const temp = nowYear - i + ''
+      arr.push({
+        value: temp,
+        label: temp
+      })
+    }
+
+    setAgeSelect(arr)
+  }, [])
+
+  return (
+    <div className={styles.A1add}>
+      <div className={classNames('A1aMain', editInfo.txt === '查看' ? 'A1aMainLook' : '')}>
+        <Form
+          ref={FormBoxRef}
+          name='basic'
+          labelCol={{ span: 3 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete='off'
+          scrollToFirstError
+        >
+          <Form.Item label='书名' name='name' rules={[{ required: true, message: '请输入书名!' }]}>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+
+          <Form.Item label='简介' name='description'>
+            <TextArea
+              readOnly={editInfo.txt === '查看'}
+              maxLength={200}
+              showCount
+              placeholder='请输入内容'
+            />
+          </Form.Item>
+
+          <Form.Item label='作者' name='author'>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+
+          <Form.Item label='出版社' name='press'>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+
+          <Form.Item label='出版年份' name='year'>
+            <Select
+              allowClear
+              placeholder='请选择年份'
+              style={{ width: 200 }}
+              options={ageSelect}
+            />
+          </Form.Item>
+
+          {/* 封面 */}
+          <div className='formRow'>
+            <div className='formLeft'>
+              <span>* </span>
+              封面:
+            </div>
+            <div className='formRight'>
+              <ZupOne
+                ref={ZupThumbRef}
+                isLook={editInfo.txt === '查看'}
+                fileCheck={fileCheck}
+                size={5}
+                dirCode={'A1manage'}
+                myUrl='cms/book/upload'
+                format={['image/jpeg', 'image/png']}
+                formatTxt='png、jpg和jpeg'
+                checkTxt='请上传封面图!'
+                upTxt='最多1张'
+                myType='thumb'
+              />
+            </div>
+          </div>
+          {editInfo.txt === '查看' ? <br /> : null}
+
+          <Form.Item
+            label='中图法分类'
+            name='storageIds'
+            rules={[{ required: true, message: '请选择中图法分类!' }]}
+          >
+            <Cascader
+              allowClear={false}
+              changeOnSelect
+              style={{ width: 300 }}
+              options={storageArr}
+              fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+              placeholder='请选择'
+            />
+          </Form.Item>
+
+          <Form.Item
+            label='展示分类'
+            name='exhibitTypeId'
+            rules={[{ required: true, message: '请选择展示分类!' }]}
+          >
+            <Select
+              placeholder='请选择'
+              style={{ width: 200 }}
+              fieldNames={{ label: 'name', value: 'id' }}
+              options={exhibitTypeArr}
+            />
+          </Form.Item>
+
+          <Form.Item label='ISBN编号' name='num'>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+          {/* 附件 */}
+          <div className='formRow'>
+            <div className='formLeft'>
+              <span>* </span>
+              附件:
+            </div>
+            <div className='formRight'>
+              <ZupOne
+                ref={ZupTxtRef}
+                isLook={editInfo.txt === '查看'}
+                fileCheck={fileCheck}
+                size={30}
+                dirCode='A1manage'
+                myUrl='cms/book/upload'
+                format={['']}
+                formatTxt='equb'
+                checkTxt='请上传equb附件!'
+                upTxt='最多1个'
+                myType='equb'
+              />
+            </div>
+          </div>
+          {editInfo.txt === '查看' ? <br /> : null}
+
+          <div className='A1fromRow'>
+            <Form.Item
+              label='排序值'
+              name='sort'
+              rules={[{ required: true, message: '请输入排序值!' }]}
+            >
+              <InputNumber min={1} max={999} precision={0} placeholder='请输入' />
+            </Form.Item>
+            <div className='A1_6Frow' hidden={editInfo.txt === '查看'}>
+              请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <Form.Item className='A1abtn'>
+            {editInfo.txt === '查看' ? (
+              <Button onClick={closeFu}>返回</Button>
+            ) : (
+              <>
+                <Button type='primary' htmlType='submit'>
+                  提交
+                </Button>
+                <br />
+                <br />
+                <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+              </>
+            )}
+          </Form.Item>
+        </Form>
+      </div>
+    </div>
+  )
+}
+
+const MemoA1add = React.memo(A1add)
+
+export default MemoA1add

+ 16 - 0
src/pages/A1manage/data.ts

@@ -0,0 +1,16 @@
+export type A1FromDataType = {
+  searchKey: string
+  num: string
+
+  storageArr: number[] | undefined
+  storageId: number | null
+  exhibitTypeId: number | null
+
+  pageNum: number
+  pageSize: number
+}
+
+export type A1EditInfoType = {
+  id: number
+  txt: '新增' | '编辑' | '查看' | ''
+}

+ 28 - 3
src/pages/A1manage/index.module.scss

@@ -1,5 +1,30 @@
-.A1manage{
-  :global{
-
+.A1manage {
+  position: relative;
+  :global {
+    .A1top {
+      border-radius: 10px;
+      background-color: #fff;
+      padding: 15px 24px;
+      display: flex;
+      justify-content: space-between;
+      & > div {
+        display: flex;
+        .A1TopRow {
+          display: flex;
+          align-items: center;
+          margin-right: 20px;
+          .ant-select-selection-placeholder {
+            color: black;
+          }
+        }
+      }
+    }
+    .A1tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 77px);
+      background-color: #fff;
+    }
   }
 }

+ 212 - 6
src/pages/A1manage/index.tsx

@@ -1,14 +1,220 @@
-import React from "react";
-import styles from "./index.module.scss";
- function A1manage() {
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, Input, Select } from 'antd'
+import { useDispatch, useSelector } from 'react-redux'
+import { A1EditInfoType, A1FromDataType } from './data'
+import { A1_APIdel, A1_APIgetList } from '@/store/action/A1manage'
+import { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import { A1tableType } from '@/types'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A2_APIgetList1, A2_APIgetList2 } from '@/store/action/A2classify'
+import MyTable from '@/components/MyTable'
+import { A1tableC } from '@/utils/tableData'
+import A1add from './A1add'
+
+const fromDataBase: A1FromDataType = {
+  searchKey: '',
+  num: '',
+
+  storageArr: undefined,
+  storageId: null,
+  exhibitTypeId: null,
+  pageNum: 1,
+  pageSize: 10
+}
+
+function A1manage() {
+  const dispatch = useDispatch()
+
+  // 获取中图法分类 和 展示分类
+
+  useEffect(() => {
+    dispatch(A2_APIgetList1())
+    dispatch(A2_APIgetList2({ pageNum: 1, pageSize: 9999, searchKey: '' }))
+  }, [dispatch])
+
+  const { treeData: storageArr, tableInfo: exhibitTypeObj } = useSelector(
+    (state: RootState) => state.A2classify
+  )
+
+  const [fromData, setFromData] = useState(fromDataBase)
+
+  const getListFu = useCallback(() => {
+    const obj: A1FromDataType = {
+      ...fromData,
+      storageId: null
+    }
+    if (fromData.storageArr && fromData.storageArr.length)
+      obj.storageId = fromData.storageArr[fromData.storageArr.length - 1]
+    dispatch(A1_APIgetList(fromData))
+  }, [dispatch, fromData])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  const [inputKey, setInputKey] = useState(1)
+
+  // 输入框的输入
+  const timeRef = useRef(-1)
+  const txtChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey' | 'num') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value, pageNum: 1 })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...fromDataBase })
+  }, [])
+
+  const tableInfo = useSelector((state: RootState) => state.A1manage.tableInfo)
+
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A1_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: A1tableType) => (
+          <>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setEditInfo({ id: item.id, txt: '查看' })}
+            >
+              查看
+            </Button>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setEditInfo({ id: item.id, txt: '编辑' })}
+            >
+              编辑
+            </Button>
+            <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+          </>
+        )
+      }
+    ]
+  }, [delTableFu])
+
+  //查看、新增、编辑
+  const [editInfo, setEditInfo] = useState<A1EditInfoType>({
+    id: 0,
+    txt: ''
+  })
 
   return (
     <div className={styles.A1manage}>
-      <h1>A1manage</h1>
+      <div className='pageTitle'>图书管理 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
+
+      {/* 顶部筛选 */}
+      <div className='A1top'>
+        <div>
+          <div className='A1TopRow'>
+            <span>搜索:</span>
+            <Input
+              key={inputKey}
+              maxLength={20}
+              style={{ width: 200 }}
+              placeholder='请输入书名/作者/出版社'
+              allowClear
+              onChange={e => txtChangeFu(e, 'searchKey')}
+            />
+            &nbsp;
+            <Input
+              key={inputKey + 1}
+              maxLength={20}
+              style={{ width: 200 }}
+              placeholder='请输入完整ISBN编号'
+              allowClear
+              onChange={e => txtChangeFu(e, 'num')}
+            />
+          </div>
+
+          <div className='A1TopRow'>
+            <span>中图法分类:</span>
+            <Cascader
+              changeOnSelect
+              style={{ width: 300 }}
+              options={storageArr}
+              fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+              value={fromData.storageArr}
+              placeholder='全部'
+              onChange={e =>
+                setFromData({
+                  ...fromData,
+                  storageArr: e as number[]
+                })
+              }
+            />
+          </div>
+          <div className='A1TopRow'>
+            <span>展示分类:</span>
+            <Select
+              allowClear
+              placeholder='全部'
+              style={{ width: 200 }}
+              value={fromData.exhibitTypeId}
+              fieldNames={{ label: 'name', value: 'id' }}
+              onChange={e => setFromData({ ...fromData, pageNum: 1, exhibitTypeId: e })}
+              options={exhibitTypeObj.list}
+            />
+          </div>
+        </div>
+        <div>
+          <Button onClick={resetSelectFu}>重置</Button>&emsp;
+          <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '新增' })}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className='A1tableBox'>
+        <MyTable
+          yHeight={625}
+          list={tableInfo.list}
+          columnsTemp={A1tableC}
+          lastBtn={tableLastBtn}
+          pageNum={fromData.pageNum}
+          pageSize={fromData.pageSize}
+          total={tableInfo.total}
+          onChange={(pageNum, pageSize) => setFromData({ ...fromData, pageNum, pageSize })}
+        />
+      </div>
+
+      {/* 新增 / 编辑 / 查看 */}
+      {editInfo.id ? (
+        <A1add
+          editInfo={editInfo}
+          closeFu={() => setEditInfo({ id: 0, txt: '新增' })}
+          addTableFu={resetSelectFu}
+          editTableFu={getListFu}
+          storageArr={storageArr}
+          exhibitTypeArr={exhibitTypeObj.list}
+        ></A1add>
+      ) : null}
     </div>
   )
 }
 
-const MemoA1manage = React.memo(A1manage);
+const MemoA1manage = React.memo(A1manage)
 
-export default MemoA1manage;
+export default MemoA1manage

+ 39 - 0
src/store/action/A1manage.ts

@@ -0,0 +1,39 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+
+/**
+ *图书管理-列表
+ */
+
+export const A1_APIgetList = (data: any): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post('cms/book/pageList', data)
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total
+      }
+      dispatch({ type: 'A1/getList', payload: obj })
+    }
+  }
+}
+/**
+ * 图书管理-删除
+ */
+export const A1_APIdel = (id: number) => {
+  return http.get(`cms/book/remove/${id}`)
+}
+
+/**
+ * 图书管理-获取详情
+ */
+export const A1_APIgetInfo = (id: number) => {
+  return http.get(`cms/book/detail/${id}`)
+}
+
+/**
+ * 图书管理-新增、编辑
+ */
+export const A1_APIsave = (data: any) => {
+  return http.post('cms/book/save', data)
+}

+ 28 - 0
src/store/reducer/A1manage.ts

@@ -0,0 +1,28 @@
+import { A1tableType } from '@/types'
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as A1tableType[],
+    total: 0
+  }
+}
+
+// 定义 action 类型
+type Props = {
+  type: 'A1/getList'
+  payload: { list: A1tableType[]; total: number }
+}
+
+// reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case 'A1/getList':
+      return { ...state, tableInfo: action.payload }
+
+    default:
+      return state
+  }
+}

+ 2 - 0
src/store/reducer/index.ts

@@ -3,6 +3,7 @@ import { combineReducers } from 'redux'
 
 // 导入 登录 模块的 reducer
 import A0Layout from './layout'
+import A1manage from './A1manage'
 import A2classify from './A2classify'
 import Z1user from './Z1user'
 import Z2log from './Z2log'
@@ -10,6 +11,7 @@ import Z2log from './Z2log'
 // 合并 reducer
 const rootReducer = combineReducers({
   A0Layout,
+  A1manage,
   A2classify,
   Z1user,
   Z2log

+ 25 - 0
src/types/api/A1manage.d.ts

@@ -0,0 +1,25 @@
+export type A1tableType = {
+  ancestor: string
+  auditDesc: string
+  author: string
+  createTime: string
+  creatorId: number
+  creatorName: string
+  description: string
+  display: number
+  exhibitTypeId: string
+  exhibitTypeName: string
+  fileName: string
+  filePath: string
+  id: number
+  input: string
+  name: string
+  num: string
+  press: string
+  sort: number
+  storageId: string
+  storageName: string
+  thumb: string
+  updateTime: string
+  year: string
+}

+ 1 - 0
src/types/index.d.ts

@@ -1,4 +1,5 @@
 export * from './api/layot'
+export * from './api/A1manage'
 export * from './api/A2classify'
 export * from './api/Z1user'
 export * from './api/Z2log'

+ 13 - 0
src/utils/tableData.ts

@@ -14,6 +14,19 @@
 //     ["text", "创建日期",'description', 50,A],
 //   ];
 
+export const A1tableC = [
+  ['txt', '书名', 'name'],
+  ['img', '封面', 'thumb'],
+  ['txt', '作者', 'author'],
+  ['txt', '出版社', 'press'],
+  ['txt', '中图法分类', 'storageName'],
+  ['txt', '展示分类', 'exhibitTypeName'],
+  ['txt', 'ISBN编号', 'num'],
+  ['txt', '排序值', 'sort'],
+  ['txt', '编辑人', 'creatorName'],
+  ['txt', '编辑时间', 'updateTime']
+]
+
 export const A2tableC = [
   ['txt', '分类名称', 'name'],
   ['txt', '排序值', 'sort'],