瀏覽代碼

first commit

shaogen1995 1 月之前
當前提交
831717a1e5
共有 100 個文件被更改,包括 8154 次插入0 次删除
  1. 12 0
      .editorconfig
  2. 3 0
      .env
  3. 23 0
      .gitignore
  4. 11 0
      .prettierrc.js
  5. 39 0
      .vscode/settings.json
  6. 11 0
      README.md
  7. 10 0
      config-overrides.js
  8. 78 0
      package.json
  9. 8 0
      path.tsconfig.json
  10. 二進制
      public/favicon.ico
  11. 17 0
      public/index.html
  12. 二進制
      public/templates/1.docx
  13. 二進制
      public/templates/10.docx
  14. 二進制
      public/templates/11.docx
  15. 二進制
      public/templates/12.docx
  16. 二進制
      public/templates/13.docx
  17. 二進制
      public/templates/14.docx
  18. 二進制
      public/templates/15.docx
  19. 二進制
      public/templates/16.docx
  20. 二進制
      public/templates/2.docx
  21. 二進制
      public/templates/3.docx
  22. 二進制
      public/templates/4.docx
  23. 二進制
      public/templates/5.docx
  24. 二進制
      public/templates/6.docx
  25. 二進制
      public/templates/7.docx
  26. 二進制
      public/templates/8.docx
  27. 二進制
      public/templates/9.docx
  28. 73 0
      src/App.tsx
  29. 二進制
      src/assets/img/IMGerror.png
  30. 二進制
      src/assets/img/NoPower.png
  31. 二進制
      src/assets/img/loading.gif
  32. 二進制
      src/assets/img/loginBac.jpg
  33. 二進制
      src/assets/img/logo.png
  34. 二進制
      src/assets/img/user.png
  35. 407 0
      src/assets/styles/base.css
  36. 564 0
      src/assets/styles/base.less
  37. 21 0
      src/components/AsyncSpinLoding/index.module.scss
  38. 15 0
      src/components/AsyncSpinLoding/index.tsx
  39. 32 0
      src/components/AuthRoute/index.tsx
  40. 51 0
      src/components/ImageLazy/index.module.scss
  41. 63 0
      src/components/ImageLazy/index.tsx
  42. 66 0
      src/components/LookDom/index.module.scss
  43. 46 0
      src/components/LookDom/index.tsx
  44. 29 0
      src/components/Message/index.tsx
  45. 64 0
      src/components/MyPopconfirm.tsx
  46. 65 0
      src/components/MyTable/index.module.scss
  47. 355 0
      src/components/MyTable/index.tsx
  48. 26 0
      src/components/NoPower/index.module.scss
  49. 80 0
      src/components/NoPower/index.tsx
  50. 24 0
      src/components/NotFound/index.tsx
  51. 10 0
      src/components/SpinLoding/index.module.scss
  52. 13 0
      src/components/SpinLoding/index.tsx
  53. 43 0
      src/components/UpAsyncLoding/index.module.scss
  54. 38 0
      src/components/UpAsyncLoding/index.tsx
  55. 41 0
      src/components/YtableVideo/index.module.scss
  56. 36 0
      src/components/YtableVideo/index.tsx
  57. 77 0
      src/components/Z3upFiles/data.ts
  58. 69 0
      src/components/Z3upFiles/index.module.scss
  59. 238 0
      src/components/Z3upFiles/index.tsx
  60. 32 0
      src/components/ZGaddNow/data.ts
  61. 67 0
      src/components/ZGaddNow/index.module.scss
  62. 305 0
      src/components/ZGaddNow/index.tsx
  63. 23 0
      src/components/ZGaddNow/type.d.ts
  64. 82 0
      src/components/ZRichText/index.module.scss
  65. 202 0
      src/components/ZRichText/index.tsx
  66. 208 0
      src/components/ZRichTexts/index.module.scss
  67. 411 0
      src/components/ZRichTexts/index.tsx
  68. 39 0
      src/components/Zexport/index.module.scss
  69. 114 0
      src/components/Zexport/index.tsx
  70. 38 0
      src/components/ZflowTable/index.module.scss
  71. 86 0
      src/components/ZflowTable/index.tsx
  72. 63 0
      src/components/ZupAudio/index.module.scss
  73. 140 0
      src/components/ZupAudio/index.tsx
  74. 127 0
      src/components/ZupFile/index.module.scss
  75. 224 0
      src/components/ZupFile/index.tsx
  76. 22 0
      src/components/ZupFileTable/index.module.scss
  77. 133 0
      src/components/ZupFileTable/index.tsx
  78. 101 0
      src/components/ZupOne/index.module.scss
  79. 295 0
      src/components/ZupOne/index.tsx
  80. 216 0
      src/components/ZupTypes/index.module.scss
  81. 618 0
      src/components/ZupTypes/index.tsx
  82. 35 0
      src/index.tsx
  83. 4 0
      src/pages/A2_query/A22antique/index.module.scss
  84. 9 0
      src/pages/A2_query/A22antique/index.tsx
  85. 4 0
      src/pages/A2_query/A23media/index.module.scss
  86. 9 0
      src/pages/A2_query/A23media/index.tsx
  87. 50 0
      src/pages/A3_ledger/A32Routing/A32set/index.module.scss
  88. 94 0
      src/pages/A3_ledger/A32Routing/A32set/index.tsx
  89. 32 0
      src/pages/A3_ledger/A32Routing/A32table/index.module.scss
  90. 192 0
      src/pages/A3_ledger/A32Routing/A32table/index.tsx
  91. 33 0
      src/pages/A3_ledger/A32Routing/data.ts
  92. 106 0
      src/pages/A3_ledger/A32Routing/index.module.scss
  93. 477 0
      src/pages/A3_ledger/A32Routing/index.tsx
  94. 73 0
      src/pages/A3_ledger/C1ledger/data.ts
  95. 96 0
      src/pages/A3_ledger/C1ledger/index.module.scss
  96. 345 0
      src/pages/A3_ledger/C1ledger/index.tsx
  97. 151 0
      src/pages/A3_ledger/C1ledger/type.d.ts
  98. 31 0
      src/pages/A3_ledger/ComPage/C4import/C4imgModal/index.module.scss
  99. 109 0
      src/pages/A3_ledger/ComPage/C4import/C4imgModal/index.tsx
  100. 0 0
      src/pages/A3_ledger/ComPage/C4import/C4impImg/index.module.scss

+ 12 - 0
.editorconfig

@@ -0,0 +1,12 @@
+root = true # 控制配置文件 .editorconfig 是否生效的字段
+ 
+[**] # 匹配全部文件
+indent_style = space # 缩进风格,可选space|tab
+indent_size = 2 # 缩进的空格数
+charset = utf-8 # 设置字符集
+trim_trailing_whitespace = true # 删除一行中的前后空格
+insert_final_newline = true # 设为true表示使文件以一个空白行结尾
+end_of_line = lf
+ 
+[**.md] # 匹配md文件
+trim_trailing_whitespace = false

+ 3 - 0
.env

@@ -0,0 +1,3 @@
+# .env.production
+GENERATE_SOURCEMAP = false
+# 关闭映射

+ 23 - 0
.gitignore

@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

+ 11 - 0
.prettierrc.js

@@ -0,0 +1,11 @@
+module.exports = {
+  printWidth: 100, // 一行的字符数,如果超过会进行换行
+  tabWidth: 2, // 一个tab代表几个空格数,默认就是2
+  useTabs: false, // 是否启用tab取代空格符缩进,.editorconfig设置空格缩进,所以设置为false
+  semi: false, // 行尾是否使用分号,默认为true
+  singleQuote: true, // 字符串是否使用单引号
+  trailingComma: "none", // 对象或数组末尾是否添加逗号 none| es5| all
+  jsxSingleQuote: true, // 在jsx里是否使用单引号,你看着办
+  bracketSpacing: true, // 对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
+  arrowParens: "avoid", // 箭头函数如果只有一个参数则省略括号
+};

+ 39 - 0
.vscode/settings.json

@@ -0,0 +1,39 @@
+{
+  "search.exclude": {
+    "/node_modules": true,
+    "dist": true,
+    "pnpm-lock.sh": true
+  },
+  "editor.formatOnSave": true,
+  "[javascript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[javascriptreact]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[typescript]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[typescriptreact]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[json]": {
+    "editor.defaultFormatter": "vscode.json-language-features"
+  },
+  "[html]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[markdown]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[css]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[less]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "[scss]": {
+    "editor.defaultFormatter": "esbenp.prettier-vscode"
+  },
+  "liveServer.settings.port": 5502
+}

+ 11 - 0
README.md

@@ -0,0 +1,11 @@
+1.npm 有问题的话试试用 yarn
+
+2.测试堡垒机存放目录
+227/data/data/museum_sichuan_leshan_storage_data
+
+3.蓝湖地址
+
+4.测试域名
+https://sit-leshancms.4dage.com
+接口地址在后面拼接:/api/doc.html#/home
+测试网址在后面拼接:/backstage

+ 10 - 0
config-overrides.js

@@ -0,0 +1,10 @@
+const path = require('path')
+const { override, addWebpackAlias } = require('customize-cra')
+
+// 添加 @ 别名
+const webpackAlias = addWebpackAlias({
+  '@': path.resolve(__dirname, 'src')
+})
+
+// 导出要进行覆盖的 webpack 配置
+module.exports = override(webpackAlias)

+ 78 - 0
package.json

@@ -0,0 +1,78 @@
+{
+  "name": "demo2",
+  "version": "0.1.0",
+  "private": true,
+  "dependencies": {
+    "@ant-design/cssinjs": "^1.5.6",
+    "@testing-library/jest-dom": "^5.16.5",
+    "@testing-library/react": "^13.4.0",
+    "@testing-library/user-event": "^13.5.0",
+    "@types/jest": "^27.5.2",
+    "@types/node": "^16.18.3",
+    "@types/react": "^18.0.24",
+    "@types/react-dom": "^18.0.8",
+    "antd": "^5.8.3",
+    "antd-mobile": "^5.30.0",
+    "axios": "^1.1.3",
+    "braft-editor": "^2.3.9",
+    "braft-utils": "^3.0.12",
+    "dayjs": "^1.11.10",
+    "dingtalk-jsapi": "^3.1.0",
+    "docxtemplater": "^3.61.1",
+    "docxtemplater-image-module-free": "^1.1.1",
+    "echarts": "^5.6.0",
+    "exceljs": "^4.4.0",
+    "file-saver": "^2.0.5",
+    "gdt-jsapi": "^1.9.51",
+    "js-base64": "^3.7.3",
+    "js-export-excel": "^1.1.4",
+    "jszip-utils": "^0.1.0",
+    "lodash": "^4.17.21",
+    "pizzip": "^3.1.8",
+    "query-string": "^9.2.1",
+    "react": "^18.2.0",
+    "react-dom": "^18.2.0",
+    "react-redux": "^8.0.4",
+    "react-router-dom": "5.3",
+    "react-scripts": "5.0.1",
+    "react-sortablejs": "^6.1.4",
+    "redux": "^4.2.0",
+    "redux-devtools-extension": "^2.13.9",
+    "redux-thunk": "^2.4.1",
+    "sass": "^1.55.0",
+    "typescript": "^4.8.4",
+    "web-vitals": "^2.1.4"
+  },
+  "scripts": {
+    "dev": "react-app-rewired start",
+    "build": "react-app-rewired build",
+    "test": "react-app-rewired test",
+    "eject": "react-scripts eject"
+  },
+  "eslintConfig": {
+    "extends": [
+      "react-app",
+      "react-app/jest"
+    ]
+  },
+  "browserslist": {
+    "production": [
+      ">0.2%",
+      "not dead",
+      "not op_mini all"
+    ],
+    "development": [
+      "last 1 chrome version",
+      "last 1 firefox version",
+      "last 1 safari version"
+    ]
+  },
+  "devDependencies": {
+    "@types/history": "^5.0.0",
+    "@types/lodash": "^4.17.16",
+    "@types/react-router-dom": "^5.3.3",
+    "customize-cra": "^1.0.0",
+    "react-app-rewired": "^2.2.1"
+  },
+  "homepage": "."
+}

+ 8 - 0
path.tsconfig.json

@@ -0,0 +1,8 @@
+{
+    "compilerOptions": {
+      "baseUrl": "./",
+      "paths": {
+        "@/*": ["src/*"]
+      }
+    }
+  }

二進制
public/favicon.ico


+ 17 - 0
public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="zh">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta name="theme-color" content="#000000" />
+    <meta name="description" content="Web site created using create-react-app" />
+    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
+    <link rel="shortcut icon" href="./favicon.ico" type="image/x-icon" />
+
+    <title>乐山市博物馆馆藏管理系统</title>
+  </head>
+  <body>
+    <noscript>You need to enable JavaScript to run this app.</noscript>
+    <div id="root"></div>
+  </body>
+</html>

二進制
public/templates/1.docx


二進制
public/templates/10.docx


二進制
public/templates/11.docx


二進制
public/templates/12.docx


二進制
public/templates/13.docx


二進制
public/templates/14.docx


二進制
public/templates/15.docx


二進制
public/templates/16.docx


二進制
public/templates/2.docx


二進制
public/templates/3.docx


二進制
public/templates/4.docx


二進制
public/templates/5.docx


二進制
public/templates/6.docx


二進制
public/templates/7.docx


二進制
public/templates/8.docx


二進制
public/templates/9.docx


+ 73 - 0
src/App.tsx

@@ -0,0 +1,73 @@
+import '@/assets/styles/base.css'
+// 关于路由
+import React from 'react'
+import { Router, Route, Switch } from 'react-router-dom'
+import history from './utils/history'
+import AuthRoute from './components/AuthRoute'
+import SpinLoding from './components/SpinLoding'
+import AsyncSpinLoding from './components/AsyncSpinLoding'
+import { Image } from 'antd'
+import { useSelector } from 'react-redux'
+import store, { RootState } from './store'
+import UpAsyncLoding from './components/UpAsyncLoding'
+import MessageCom from './components/Message'
+import LookDom from './components/LookDom'
+import Zexport from './components/Zexport'
+const Layout = React.lazy(() => import('./pages/Layout'))
+const Login = React.lazy(() => import('./pages/Login'))
+
+export default function App() {
+  // 从仓库中获取查看图片的信息
+  const { lookBigImg, exInfo } = useSelector((state: RootState) => state.A0Layout)
+
+  return (
+    <>
+      {/* 关于路由 */}
+      <Router history={history}>
+        <React.Suspense fallback={<SpinLoding />}>
+          <Switch>
+            <Route path='/login' component={Login} />
+            {/*  打印页面 */}
+            {/* <Route
+              path='/logPage/:key'
+              component={React.lazy(() => import('./pages/W_log/index'))}
+            /> */}
+            <AuthRoute path='/' component={Layout} />
+          </Switch>
+        </React.Suspense>
+      </Router>
+
+      {/* 发送请求的加载组件 */}
+      <AsyncSpinLoding />
+
+      {/* 所有图片点击预览查看大图 */}
+      {lookBigImg.show ? (
+        <Image
+          preview={{
+            visible: lookBigImg.show,
+            src: lookBigImg.url,
+            onVisibleChange: value => {
+              // 清除仓库信息
+              store.dispatch({
+                type: 'layout/lookBigImg',
+                payload: { url: '', show: false }
+              })
+            }
+          }}
+        />
+      ) : null}
+
+      {/* 上传附件的进度条元素 */}
+      <UpAsyncLoding />
+
+      {/* 查看视频音频 */}
+      <LookDom />
+
+      {/* antd 轻提示 ---兼容360浏览器 */}
+      <MessageCom />
+
+      {/* 所有批量导出 */}
+      {exInfo.show ? <Zexport /> : null}
+    </>
+  )
+}

二進制
src/assets/img/IMGerror.png


二進制
src/assets/img/NoPower.png


二進制
src/assets/img/loading.gif


二進制
src/assets/img/loginBac.jpg


二進制
src/assets/img/logo.png


二進制
src/assets/img/user.png


+ 407 - 0
src/assets/styles/base.css

@@ -0,0 +1,407 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+html {
+  height: 100%;
+  font-size: 14px;
+}
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+i {
+  font-style: normal;
+}
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+}
+ul {
+  list-style: none;
+}
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+/* 主题色 */
+:root {
+  --themeColor: #9f1927;
+  --themeColor2: #d3b453;
+}
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity 0.5s;
+  background-color: #fff;
+  border-radius: 10px;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  align-items: center;
+  color: #666666;
+  font-size: 16px;
+  padding-bottom: 20px;
+}
+.noFindPage > p {
+  margin-bottom: 10px;
+}
+.noFindPage > div {
+  width: 80%;
+  margin-top: 8%;
+  display: flex;
+  justify-content: space-around;
+}
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+.ant-popconfirm {
+  width: 240px;
+}
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+  /* 普通文字按钮的颜色 */
+  /* 按钮的危险颜色 */
+  /* antd分页器样式 */
+  /* 表格的图片居中 */
+  /* antd图片预览组件 */
+  /* antd表格居中 */
+}
+#root a {
+  text-decoration: none;
+  color: black;
+  outline: none;
+}
+#root .iconHoverTit {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+#root .iconHoverTitTxt {
+  background-color: var(--themeColor);
+  color: #fff;
+  width: 16px;
+  height: 16px;
+  line-height: 16px;
+  text-align: center;
+  font-size: 12px;
+  border-radius: 50%;
+}
+#root .ant-btn-text {
+  color: var(--themeColor);
+}
+#root .ant-btn-text:disabled {
+  cursor: not-allowed;
+  color: rgba(0, 0, 0, 0.25);
+}
+#root .ant-btn-text.ant-btn-dangerous {
+  color: #ff4d4d;
+}
+#root .tableImgAuto {
+  display: flex;
+  justify-content: center;
+}
+#root .tableImgAuto .TvideoBox {
+  cursor: pointer;
+  width: 60px;
+  height: 60px;
+  position: relative;
+}
+#root .tableImgAuto .TvideoBox .TvideoBoxLook {
+  position: absolute;
+  z-index: 10;
+  opacity: 0;
+  transition: opacity 0.3s;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 14px;
+  color: #fff;
+  background-color: rgba(0, 0, 0, 0.6);
+}
+#root .tableImgAuto .TvideoBox .TvideoBoxLook .anticon-eye {
+  font-size: 18px;
+}
+#root .tableImgAuto .TvideoBox video {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+#root .tableImgAuto .TvideoBox:hover .TvideoBoxLook {
+  opacity: 1;
+}
+#root .ant-image {
+  display: none;
+}
+#root .ant-table-cell {
+  text-align: center !important;
+}
+#root #A2Table3 .ant-table-row-expand-icon {
+  background-color: var(--themeColor);
+  color: #fff;
+}
+#root #A2Table3 .ant-table-cell-with-append {
+  display: flex;
+  justify-content: flex-start;
+}
+[hidden] {
+  display: none !important;
+}
+#upInput {
+  display: none;
+}
+#upInput2 {
+  display: none;
+}
+#upInputAudio {
+  display: none;
+}
+.pageTitle {
+  font-size: 18px;
+  font-weight: 700;
+  position: absolute;
+  z-index: 11;
+  top: -56px;
+  left: -18px;
+  padding-left: 40px;
+}
+.pageTitle::before {
+  position: absolute;
+  left: 20px;
+  top: 50%;
+  transform: translateY(-50%);
+  content: '';
+  width: 6px;
+  height: 20px;
+  background-color: var(--themeColor);
+}
+.mySorrl::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 5px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+.mySorrl::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: var(--themeColor);
+}
+.mySorrl::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.8) !important;
+}
+.ant-image-preview-mask {
+  z-index: 10000 !important;
+}
+.ant-image-preview-wrap {
+  z-index: 10001 !important;
+}
+.ant-image-preview-operations-wrapper {
+  z-index: 10002 !important;
+}
+.Y2xia {
+  display: block;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+}
+.D1GtNum {
+  cursor: pointer;
+  text-decoration: underline;
+}
+.D1GtNumAc {
+  color: var(--themeColor);
+}
+.Y1info {
+  width: 100%;
+  margin-top: 5px;
+  height: calc(100% - 35px);
+  font-size: 16px;
+  padding: 15px;
+  overflow-y: auto;
+}
+.Y1info .Y1tit {
+  font-size: 18px;
+  padding-left: 15px;
+  font-weight: 700;
+  position: relative;
+  margin-bottom: 15px;
+  color: var(--themeColor);
+}
+.Y1info .Y1tit::before {
+  position: absolute;
+  left: 0px;
+  top: 50%;
+  transform: translateY(-50%);
+  content: '';
+  width: 6px;
+  height: 18px;
+  background-color: var(--themeColor);
+}
+.Y1info .Y1row {
+  width: 100%;
+  display: flex;
+  margin-bottom: 10px;
+}
+.Y1info .Y1row .Y1rowll {
+  width: 120px;
+  text-align: right;
+  font-weight: 700;
+}
+.Y1info .Y1row .Y1rowrr {
+  width: calc(100% - 120px);
+  word-wrap: break-word;
+  max-height: 130px;
+  overflow-y: auto;
+  white-space: pre-wrap;
+}
+.Y1info .Y1rowZ .Y1z1 {
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: space-between;
+}
+.Y1info .Y1rowZ .Y1z1 .Y1rowZll {
+  width: 48%;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+.Y1info .Y1rowZ .Y1z1 .Y1rowZrr {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 48%;
+}
+.Y1info .Y1rowZ .Y1z2 {
+  margin-bottom: 20px;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  width: 100%;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 .y1z2_1row1 {
+  margin-bottom: 10px;
+  width: 48%;
+  display: flex;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 .y1z2_1row1 .y1z2_1r11 {
+  width: 130px;
+  text-align: right;
+  font-weight: 700;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 .y1z2_1row1 .y1z2_1r12 {
+  width: calc(100% - 130px);
+  word-wrap: break-word;
+  max-height: 130px;
+  overflow-y: auto;
+  white-space: pre-wrap;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 .y1z2_1row1Full {
+  width: 100%;
+}
+.Y1info .Y1rowZ .Y1z2 .y1z2_1 .y1z2_1row1Full .y1z2_1r12 {
+  max-height: 1000px;
+}
+.Y1info .Y22com .Y22sta {
+  font-weight: 700;
+  font-size: 18px;
+  margin-bottom: 20px;
+}
+.Y1info .Y22com .Y22No {
+  font-size: 16px;
+  letter-spacing: 4px;
+  height: 200px;
+  display: flex;
+  align-items: center;
+}
+.Y1info .Y22com .Y22Info {
+  width: 800px;
+  border: 1px solid #ccc;
+  border-bottom: none;
+}
+.Y1info .Y22com .Y22Info .Y22row {
+  display: flex;
+}
+.Y1info .Y22com .Y22Info .Y22row > div {
+  width: 200px;
+  text-align: center;
+  padding: 8px 4px;
+  border-bottom: 1px solid #ccc;
+}
+.Y1info .Y22com .Y22Info .Y22row > div:nth-of-type(1) {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.Y1info .Y22com .Y22Info .Y22row > div:nth-of-type(2) {
+  width: 600px;
+}
+.Y1info .Y22com .Y22Info .Y22row1 {
+  background-color: #eaeaea;
+}
+.Y1info .Y22com .Y22Info .Y22rowNo {
+  height: 40px;
+  line-height: 40px;
+  text-align: center;
+  border-bottom: 1px solid #ccc;
+}
+.Y1info .Y33com .Y33top {
+  margin-bottom: 20px;
+  display: flex;
+  justify-content: space-around;
+}
+.Y1info .Y33com .Y33top .ant-btn {
+  margin-right: 15px;
+}
+.Y1info .Y33com .Y33no {
+  font-size: 16px;
+  letter-spacing: 4px;
+  height: 200px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.C22revampBox {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 100;
+  padding: 30px;
+  background-color: rgba(0, 0, 0, 0.6);
+}
+.C22revampBox > div {
+  position: relative;
+  width: 100%;
+  height: 100%;
+}

+ 564 - 0
src/assets/styles/base.less

@@ -0,0 +1,564 @@
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+}
+
+html {
+  height: 100%;
+  font-size: 14px;
+  // user-select: none;
+}
+
+body {
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB',
+    'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  height: 100%;
+  color: black;
+}
+
+i {
+  font-style: normal;
+}
+
+img {
+  max-width: 100%;
+  max-height: 100%;
+  vertical-align: middle;
+  // object-fit: cover;
+}
+
+ul {
+  list-style: none;
+}
+
+body {
+  overflow: auto;
+  overflow-y: overlay;
+}
+
+/* 文本域取消下拉 */
+textarea {
+  resize: none !important;
+  min-height: 100px !important;
+}
+
+/* 主题色 */
+:root {
+  --themeColor: #9f1927;
+  --themeColor2: #d3b453;
+}
+
+/* 找不到页面 */
+.noFindPage {
+  opacity: 0;
+  transition: opacity 0.5s;
+  background-color: #fff;
+  border-radius: 10px;
+  display: flex;
+  justify-content: center;
+  flex-direction: column;
+  align-items: center;
+  color: #666666;
+  font-size: 16px;
+  padding-bottom: 20px;
+
+  & > p {
+    margin-bottom: 10px;
+  }
+  & > div {
+    width: 80%;
+    margin-top: 8%;
+    display: flex;
+    justify-content: space-around;
+  }
+}
+
+/* 兼容360浏览器的下拉框 */
+.ant-select-selector {
+  position: relative;
+  background-color: #ffffff;
+  border: 1px solid #d9d9d9;
+  transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
+}
+
+// 气泡框闪烁问题
+.ant-popconfirm {
+  width: 240px;
+}
+
+// 重置antd样式
+#root {
+  width: 100vw;
+  height: 100vh;
+  min-width: 1600px;
+  min-height: 900px;
+  overflow: auto;
+  overflow-y: overlay;
+
+  a {
+    text-decoration: none;
+    color: black;
+    outline: none;
+  }
+
+  // ?的提示
+  .iconHoverTit {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+  }
+
+  .iconHoverTitTxt {
+    background-color: var(--themeColor);
+    color: #fff;
+    width: 16px;
+    height: 16px;
+    line-height: 16px;
+    text-align: center;
+    font-size: 12px;
+    border-radius: 50%;
+  }
+
+  // a {
+  //   color: var(--themeColor);
+  // }
+
+  /* 普通文字按钮的颜色 */
+  .ant-btn-text {
+    color: var(--themeColor);
+  }
+
+  .ant-btn-text:disabled {
+    cursor: not-allowed;
+    color: rgba(0, 0, 0, 0.25);
+  }
+
+  /* 按钮的危险颜色 */
+  .ant-btn-text.ant-btn-dangerous {
+    color: #ff4d4d;
+  }
+
+  /* antd分页器样式 */
+  // .ant-pagination .ant-pagination-item {
+  //   border-radius: 50%;
+  //   border: 1px solid #999;
+  //   background-color: transparent !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item-active {
+  //   background-color: var(--themeColor) !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item-active a {
+  //   color: #fff !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item:hover {
+  //   background-color: var(--themeColor) !important;
+  // }
+
+  // .ant-pagination .ant-pagination-item:hover a {
+  //   color: #fff !important;
+  // }
+
+  // .ant-pagination-prev {
+  //   border-radius: 50% !important;
+  //   border: 1px solid #999;
+  // }
+
+  // .ant-pagination-prev:hover {
+  //   background-color: var(--themeColor);
+  // }
+
+  // .ant-pagination-prev:hover button {
+  //   color: #fff;
+  // }
+
+  // .ant-pagination-next {
+  //   border-radius: 50% !important;
+  //   border: 1px solid #999;
+  // }
+
+  // .ant-pagination-next:hover {
+  //   background-color: var(--themeColor);
+  // }
+
+  // .ant-pagination-next:hover button {
+  //   color: #fff;
+  // }
+
+  // .ant-pagination-disabled {
+  //   border: 1px solid #ccc;
+  // }
+
+  // .ant-pagination-disabled:hover {
+  //   background-color: transparent;
+  // }
+
+  /* 表格的图片居中 */
+  .tableImgAuto {
+    display: flex;
+    justify-content: center;
+    .TvideoBox {
+      cursor: pointer;
+      width: 60px;
+      height: 60px;
+      position: relative;
+      .TvideoBoxLook {
+        position: absolute;
+        z-index: 10;
+        opacity: 0;
+        transition: opacity 0.3s;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 14px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, 0.6);
+        .anticon-eye {
+          font-size: 18px;
+        }
+      }
+      video {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      &:hover {
+        .TvideoBoxLook {
+          opacity: 1;
+        }
+      }
+    }
+  }
+
+  /* antd图片预览组件 */
+  .ant-image {
+    display: none;
+  }
+
+  /* antd表格居中 */
+
+  .ant-table-cell {
+    text-align: center !important;
+  }
+
+  // 树型 表格 定制化
+  #A2Table3 {
+    .ant-table-row-expand-icon {
+      background-color: var(--themeColor);
+      color: #fff;
+    }
+
+    .ant-table-cell-with-append {
+      display: flex;
+      justify-content: flex-start;
+    }
+  }
+}
+
+[hidden] {
+  display: none !important;
+}
+
+#upInput {
+  display: none;
+}
+
+#upInput2 {
+  display: none;
+}
+
+#upInputAudio {
+  display: none;
+}
+
+// 页面标题
+.pageTitle {
+  font-size: 18px;
+  font-weight: 700;
+  position: absolute;
+  z-index: 11;
+  top: -56px;
+  left: -18px;
+  padding-left: 40px;
+
+  &::before {
+    position: absolute;
+    left: 20px;
+    top: 50%;
+    transform: translateY(-50%);
+    content: '';
+    width: 6px;
+    height: 20px;
+    background-color: var(--themeColor);
+  }
+}
+
+// 滚动条
+.mySorrl::-webkit-scrollbar {
+  /*滚动条整体样式*/
+  width: 5px;
+  /*高宽分别对应横竖滚动条的尺寸*/
+  height: 1px;
+}
+
+.mySorrl::-webkit-scrollbar-thumb {
+  /*滚动条里面小方块*/
+  border-radius: 10px;
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  background: var(--themeColor);
+}
+
+.mySorrl::-webkit-scrollbar-track {
+  /*滚动条里面轨道*/
+  -webkit-box-shadow: inset 0 0 5px transparent;
+  border-radius: 10px;
+  background: transparent;
+}
+.ant-image-preview-operations {
+  background-color: rgba(0, 0, 0, 0.8) !important;
+}
+.ant-image-preview-mask {
+  z-index: 10000 !important;
+}
+.ant-image-preview-wrap {
+  z-index: 10001 !important;
+}
+.ant-image-preview-operations-wrapper {
+  z-index: 10002 !important;
+}
+
+// .ant-notification-notice {
+//   max-height: 500px !important;
+//   overflow-y: auto !important;
+// }
+
+// 热点页面打开透明的变化
+// #Y1cathet {
+//   animation: Y1cathet 0.5s linear forwards;
+// }
+// @keyframes Y1cathet {
+//   0% {
+//     right: -700px;
+//   }
+
+//   100% {
+//     right: 0;
+//   }
+// }
+
+.Y2xia {
+  display: block;
+  width: 100%;
+  height: 100%;
+  text-align: center;
+}
+
+// 打开侧边栏高亮
+.D1GtNum {
+  cursor: pointer;
+  text-decoration: underline;
+  // &:hover {
+  //   color: var(--themeColor);
+  // }
+}
+
+.D1GtNumAc {
+  color: var(--themeColor);
+}
+
+// 侧边栏藏品详情
+.Y1info {
+  width: 100%;
+  margin-top: 5px;
+  height: calc(100% - 35px);
+  font-size: 16px;
+  padding: 15px;
+  overflow-y: auto;
+  .Y1tit {
+    font-size: 18px;
+    padding-left: 15px;
+    font-weight: 700;
+    position: relative;
+    margin-bottom: 15px;
+    color: var(--themeColor);
+    &::before {
+      position: absolute;
+      left: 0px;
+      top: 50%;
+      transform: translateY(-50%);
+      content: '';
+      width: 6px;
+      height: 18px;
+      background-color: var(--themeColor);
+    }
+  }
+
+  .Y1row {
+    width: 100%;
+    display: flex;
+    margin-bottom: 10px;
+    .Y1rowll {
+      width: 120px;
+      text-align: right;
+      font-weight: 700;
+    }
+    .Y1rowrr {
+      width: calc(100% - 120px);
+      word-wrap: break-word;
+      max-height: 130px;
+      overflow-y: auto;
+      white-space: pre-wrap;
+    }
+  }
+
+  // ------------------------档案信息
+  .Y1rowZ {
+    .Y1z1 {
+      margin-bottom: 20px;
+      display: flex;
+      justify-content: space-between;
+      .Y1rowZll {
+        width: 48%;
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+      }
+      .Y1rowZrr {
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        width: 48%;
+      }
+    }
+
+    .Y1z2 {
+      margin-bottom: 20px;
+      .y1z2_1 {
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: space-between;
+        width: 100%;
+        .y1z2_1row1 {
+          margin-bottom: 10px;
+          width: 48%;
+          display: flex;
+          .y1z2_1r11 {
+            width: 130px;
+            text-align: right;
+            font-weight: 700;
+          }
+          // .y1z2_1r11long {
+          //   width: 132px;
+          //   position: relative;
+          //   left: -6px;
+          // }
+          .y1z2_1r12 {
+            width: calc(100% - 130px);
+            word-wrap: break-word;
+            max-height: 130px;
+            overflow-y: auto;
+            white-space: pre-wrap;
+          }
+        }
+        .y1z2_1row1Full {
+          width: 100%;
+          .y1z2_1r12 {
+            max-height: 1000px;
+          }
+        }
+      }
+    }
+  }
+
+  // ------------------------库存信息
+  .Y22com {
+    .Y22sta {
+      font-weight: 700;
+      font-size: 18px;
+      margin-bottom: 20px;
+    }
+    .Y22No {
+      font-size: 16px;
+      letter-spacing: 4px;
+      height: 200px;
+      display: flex;
+      align-items: center;
+    }
+    .Y22Info {
+      width: 800px;
+      border: 1px solid #ccc;
+      border-bottom: none;
+      .Y22row {
+        display: flex;
+        // align-items: center;
+        & > div {
+          width: 200px;
+          text-align: center;
+          padding: 8px 4px;
+          border-bottom: 1px solid #ccc;
+          &:nth-of-type(1) {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+          }
+          &:nth-of-type(2) {
+            width: 600px;
+          }
+        }
+      }
+      .Y22row1 {
+        background-color: #eaeaea;
+      }
+      .Y22rowNo {
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        border-bottom: 1px solid #ccc;
+      }
+    }
+  }
+
+  // ------------------------藏品附件
+  .Y33com {
+    .Y33top {
+      margin-bottom: 20px;
+      display: flex;
+      justify-content: space-around;
+      .ant-btn {
+        margin-right: 15px;
+      }
+    }
+    .Y33no {
+      font-size: 16px;
+      letter-spacing: 4px;
+      height: 200px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+    }
+  }
+}
+
+// 点击藏品编辑
+.C22revampBox {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 100;
+  padding: 30px;
+  background-color: rgba(0, 0, 0, 0.6);
+  & > div {
+    position: relative;
+    width: 100%;
+    height: 100%;
+  }
+}

+ 21 - 0
src/components/AsyncSpinLoding/index.module.scss

@@ -0,0 +1,21 @@
+.AsyncSpinLoding {
+  opacity: 0;
+  pointer-events: none;
+  transition: all .5s;
+  position: fixed;
+  z-index: 9998;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  // background-color: rgba(0, 0, 0, .6);
+  background-color: transparent;
+  :global{
+    .ant-spin-spinning{
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%,-50%);
+    }
+  }
+}

+ 15 - 0
src/components/AsyncSpinLoding/index.tsx

@@ -0,0 +1,15 @@
+import styles from "./index.module.scss";
+import { Spin } from "antd";
+import React from "react";
+
+function AsyncSpinLoding() {
+  return (
+    <div id="AsyncSpinLoding" className={styles.AsyncSpinLoding}>
+      <Spin size="large" />
+    </div>
+  );
+}
+
+const MemoAsyncSpinLoding = React.memo(AsyncSpinLoding);
+
+export default MemoAsyncSpinLoding;

+ 32 - 0
src/components/AuthRoute/index.tsx

@@ -0,0 +1,32 @@
+import { hasToken } from '@//utils/storage'
+// import { MessageFu } from "@/utils/message";
+import React from 'react'
+import { Redirect, Route } from 'react-router-dom'
+
+type AtahType = {
+  path: string
+  component: React.FC
+  [x: string]: any
+}
+
+export default function AuthRoute({ path, component: Com, ...rest }: AtahType) {
+  return (
+    <Route
+      path={path}
+      {...rest}
+      render={() => {
+        if (hasToken()) return <Com />
+        else {
+          // MessageFu.warning("登录失效!");
+          return (
+            <Redirect
+              to={{
+                pathname: '/login'
+              }}
+            />
+          )
+        }
+      }}
+    />
+  )
+}

+ 51 - 0
src/components/ImageLazy/index.module.scss

@@ -0,0 +1,51 @@
+.ImageLazy {
+  position: relative;
+
+  :global {
+    .lazyBox {
+      width: 100%;
+      height: 100%;
+      position: relative;
+
+      .adm-image {
+        width: 100%;
+        height: 100%;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .lookImg {
+        cursor: pointer;
+        transition: opacity .3s;
+        opacity: 0;
+        pointer-events: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 18px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, .6);
+
+        &>div {
+          font-size: 14px;
+        }
+      }
+
+      &:hover {
+        .lookImg {
+          opacity: 1;
+          pointer-events: auto;
+        }
+      }
+    }
+  }
+
+}

+ 63 - 0
src/components/ImageLazy/index.tsx

@@ -0,0 +1,63 @@
+import React, { useCallback, useState } from 'react'
+import styles from './index.module.scss'
+import { baseURL } from '@/utils/http'
+import imgLoding from '@/assets/img/loading.gif'
+import imgErr from '@/assets/img/IMGerror.png'
+import { EyeOutlined } from '@ant-design/icons'
+import store from '@/store'
+import { Image } from 'antd-mobile'
+
+type Props = {
+  width?: number | string
+  height?: number | string
+  src: string
+  srcBig?: string
+  noLook?: boolean
+  offline?: boolean
+}
+
+function ImageLazy({ width = 100, height = 100, src, srcBig, noLook, offline = false }: Props) {
+  // 默认不能预览图片,加载成功之后能预览
+  const [lookImg, setLookImg] = useState(false)
+
+  // 图片加载完成
+  const onLoad = useCallback(() => {
+    setLookImg(true)
+  }, [])
+
+  // 点击预览图片
+  const lookBigImg = useCallback(() => {
+    store.dispatch({
+      type: 'layout/lookBigImg',
+      payload: { url: offline ? src : srcBig ? baseURL + srcBig : baseURL + src, show: true }
+    })
+  }, [offline, src, srcBig])
+
+  return (
+    <div className={styles.ImageLazy} style={{ width: width, height: height }} id='ImageLazy'>
+      <div className='lazyBox'>
+        <Image
+          lazy
+          onLoad={onLoad}
+          src={src ? (offline ? src : baseURL + src) : ''}
+          placeholder={<img src={imgLoding} alt='' />}
+          fallback={<img src={imgErr} alt='' />}
+          fit='cover'
+        />
+
+        {/* 图片预览 */}
+        {noLook || !lookImg ? null : (
+          <div className='lookImg' onClick={lookBigImg}>
+            <EyeOutlined rev={undefined} />
+            &nbsp;
+            <div>预览</div>
+          </div>
+        )}
+      </div>
+    </div>
+  )
+}
+
+const MemoImageLazy = React.memo(ImageLazy)
+
+export default MemoImageLazy

+ 66 - 0
src/components/LookDom/index.module.scss

@@ -0,0 +1,66 @@
+.LookDom {
+  transition: opacity 0.3s;
+  position: fixed;
+  z-index: 9991;
+  opacity: 0;
+  pointer-events: none;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  background-color: rgba(0, 0, 0, 0.6);
+
+  :global {
+    .close {
+      color: #fff;
+      position: absolute;
+      right: 70px;
+      top: 70px;
+      font-size: 30px;
+      cursor: pointer;
+      z-index: 10;
+    }
+
+    .viedoBox {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 800px;
+      height: 500px;
+
+      video {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .audioBox {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      height: 60px;
+
+      audio {
+        width: 100%;
+        height: 100%;
+      }
+    }
+
+    .modelBox {
+      background-color: rgba(0, 0, 0, 0.6);
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+
+      iframe {
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+}

+ 46 - 0
src/components/LookDom/index.tsx

@@ -0,0 +1,46 @@
+import React from 'react'
+import { CloseCircleOutlined } from '@ant-design/icons'
+import styles from './index.module.scss'
+import { useSelector } from 'react-redux'
+import store, { RootState } from '@/store'
+import { baseURL } from '@/utils/http'
+function LookDom() {
+  const { src, type, flag } = useSelector((state: RootState) => state.A0Layout.lookDom)
+  return (
+    <div className={styles.LookDom} style={src ? { opacity: 1, pointerEvents: 'auto' } : {}}>
+      {src ? (
+        <>
+          {type === 'video' ? (
+            <div className='viedoBox'>
+              <video autoPlay controls src={flag ? src : baseURL + src}></video>
+            </div>
+          ) : type === 'audio' ? (
+            <div className='audioBox'>
+              <audio autoPlay controls src={flag ? src : baseURL + src}></audio>
+            </div>
+          ) : (
+            <div className='modelBox'>
+              <iframe src={`model.html?m=${baseURL + src}`} frameBorder='0' title='model'></iframe>
+            </div>
+          )}
+
+          <div
+            className='close'
+            onClick={() =>
+              store.dispatch({
+                type: 'layout/lookDom',
+                payload: { src: '', type: '', flag: false }
+              })
+            }
+          >
+            <CloseCircleOutlined rev={undefined} />
+          </div>
+        </>
+      ) : null}
+    </div>
+  )
+}
+
+const MemoLookDom = React.memo(LookDom)
+
+export default MemoLookDom

+ 29 - 0
src/components/Message/index.tsx

@@ -0,0 +1,29 @@
+import React, { useEffect } from "react";
+import { message } from "antd";
+import { useSelector } from "react-redux";
+import { RootState } from "@/store";
+
+function MessageCom() {
+  // 从仓库中获取 antd 轻提示信息
+  const messageReducerInfo = useSelector(
+    (state: RootState) => state.A0Layout.message
+  );
+
+  const [messageApi, contextHolder] = message.useMessage();
+
+  useEffect(() => {
+    if (messageReducerInfo.txt) {
+      messageApi.open({
+        type: messageReducerInfo.type,
+        content: messageReducerInfo.txt,
+        duration: messageReducerInfo.duration,
+      });
+    }
+  }, [messageApi, messageReducerInfo]);
+
+  return <>{contextHolder}</>;
+}
+
+const MemoMessage = React.memo(MessageCom);
+
+export default MemoMessage;

+ 64 - 0
src/components/MyPopconfirm.tsx

@@ -0,0 +1,64 @@
+import React, { useMemo } from 'react'
+import { Button, Popconfirm } from 'antd'
+
+type Props = {
+  txtK:
+    | '删除'
+    | '取消'
+    | '重置密码'
+    | '退出登录'
+    | '清空数据'
+    | '撤回'
+    | '恢复'
+    | '导入当前合格数据'
+    | '取消关注'
+    | '清空'
+  onConfirm: () => void
+  Dom?: React.ReactNode
+  loc?: 'bottom'
+  disabled?: boolean
+}
+
+function MyPopconfirm({ txtK, onConfirm, Dom, loc, disabled }: Props) {
+  const txt = useMemo(() => {
+    const obj = {
+      删除: ['删除后无法恢复,是否删除?', '删除'],
+      取消: ['放弃编辑后,信息将不会保存!', '放弃'],
+      重置密码: ['密码重制后为Aa147852,是否重置?', '重置'],
+      退出登录: ['确定退出吗?', '确定'],
+      清空数据: ['确定清空吗?', '确定'],
+      撤回: ['确定撤回吗?', '确定'],
+      恢复: ['确定恢复吗?', '确定'],
+      导入当前合格数据: ['确定导入吗?如数据太大可能需要等待', '确定'],
+      取消关注: ['确定取消关注吗?', '确定'],
+      清空: ['确定清空数据嘛?', '确定']
+    }
+    return Reflect.get(obj, txtK) || ['', '']
+  }, [txtK])
+
+  return (
+    <Popconfirm
+      disabled={disabled}
+      placement={loc}
+      title={txt[0]}
+      okText={txt[1]}
+      cancelText='取消'
+      onConfirm={onConfirm}
+      okButtonProps={{ loading: false }}
+    >
+      {Dom ? (
+        Dom
+      ) : ['删除', '恢复', '导入当前合格数据', '清空数据'].includes(txtK) ? (
+        <Button disabled={disabled} size='small' type='text' danger>
+          {txtK}
+        </Button>
+      ) : (
+        <Button>{txtK}</Button>
+      )}
+    </Popconfirm>
+  )
+}
+
+const MemoMyPopconfirm = React.memo(MyPopconfirm)
+
+export default MemoMyPopconfirm

+ 65 - 0
src/components/MyTable/index.module.scss

@@ -0,0 +1,65 @@
+.MyTable {
+  :global {
+    .ant-table-body {
+      overflow-y: auto !important;
+      overflow-y: overlay !important;
+
+      .ant-table-row {
+        .ant-table-cell {
+          padding: 10px;
+
+          a {
+            color: var(--themeColor) !important;
+          }
+        }
+      }
+      .tabx {
+        color: #ff4d4d;
+      }
+      .MTclick {
+        cursor: pointer;
+        text-decoration: underline;
+        &:hover {
+          color: var(--themeColor);
+        }
+      }
+      // .MTclickAc {
+      //   color: var(--themeColor);
+      // }
+    }
+
+    .NODATA {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      color: #666666;
+      font-size: 16px;
+      // img {
+      //   width: 150px;
+      // }
+      & > p {
+        margin-bottom: 10px;
+      }
+      div {
+        width: 80%;
+        margin-top: 8%;
+        display: flex;
+        justify-content: space-around;
+      }
+    }
+  }
+}
+
+.MyTableNull {
+  :global {
+    .ant-table-body {
+      .ant-table-placeholder {
+        .ant-table-cell {
+          padding: 0 !important;
+          border-bottom: none !important;
+        }
+      }
+    }
+  }
+}

+ 355 - 0
src/components/MyTable/index.tsx

@@ -0,0 +1,355 @@
+import React, {
+  forwardRef,
+  useCallback,
+  useEffect,
+  useImperativeHandle,
+  useMemo,
+  useRef,
+  useState
+} from 'react'
+import styles from './index.module.scss'
+import { DatePicker, Form, FormInstance, Input, Table, TableProps } from 'antd'
+import ImageLazy from '../ImageLazy'
+import classNames from 'classnames'
+import { resJiLianFu } from '@/utils/history'
+import { baseURL } from '@/utils/http'
+import dayjs from 'dayjs'
+
+interface MyTableProps extends Omit<TableProps, 'onChange'> {
+  yHeight?: number //设置表格的高度
+  list: any //表格数据
+  columnsTemp: any[][] //表格展示
+  total?: number //总数
+  pageNum?: number
+  pageSize?: number
+  pagingInfo?: any | boolean
+  onChange?: (pageNum: number, pageSize: number) => void
+  lastBtn?: any //后面的操作按钮
+  startBtn?: any
+  classKey?: string //一个组件多次使用的时候要传递,分别设置style
+  // 表格简单的合并
+  merge?: { type: string; num: number; loc: 'rowSpan' | 'colSpan' }
+  // 定制化表头
+  myTitle?: { name: string; Com: React.ReactNode }
+  // 为空的定制字段
+  isNull?: string
+  // 设置宽度
+  widthSet?: any
+  rowKey?: string
+  readOnly?: boolean
+  // 没有数据的时候展示
+  emptyText?: boolean
+}
+
+export interface MyTableMethods {
+  form: FormInstance<any>
+}
+
+export const myTableTransferSize = (item: any) => {
+  let danWei = isNaN(item.sizeUnit) ? item.sizeUnit : resJiLianFu(item.sizeUnit, ' ')
+
+  let txt1 = item.sizeL ? `通长${item.sizeL}` : ''
+  let txt2 = item.sizeW ? `通宽${item.sizeW}` : ''
+  let txt3 = item.sizeH ? `通高${item.sizeH}` : ''
+
+  txt1 = txt1 ? txt1 + danWei : ''
+  txt2 = txt2 ? txt2 + danWei : ''
+  txt3 = txt3 ? txt3 + danWei : ''
+
+  let arr = [txt1, txt2, txt3]
+  arr = arr.filter(v => v)
+
+  if (!txt1 && !txt2 && !txt3) return '(空)'
+  else return arr.join(' - ')
+}
+
+export const getDesensitizeTxt = (txt: string, frontLen = 3, endLen = 4) => {
+  if (!txt) return txt
+  const totalVisible = frontLen + endLen
+  if (txt.length <= totalVisible) {
+    return txt
+  }
+  const frontStr = txt.substring(0, frontLen)
+  const endStr = endLen > 0 ? txt.substring(txt.length - endLen) : ''
+  const maskStr = '*'.repeat(txt.length - frontLen - endLen)
+
+  return frontStr + maskStr + endStr
+}
+
+const MyTable = forwardRef<MyTableMethods, MyTableProps>(
+  (
+    {
+      yHeight,
+      list,
+      columnsTemp,
+      total,
+      pageNum = 1,
+      pageSize = 10,
+      pagingInfo = {
+        showQuickJumper: true,
+        position: ['bottomCenter'],
+        showSizeChanger: true
+      },
+      onChange,
+      lastBtn = [],
+      startBtn = [],
+      classKey = '',
+      merge,
+      myTitle,
+      isNull = '(空)',
+      widthSet,
+      rowKey = 'id',
+      readOnly,
+      emptyText,
+      ...rest
+    },
+    ref
+  ) => {
+    // 点击操作高亮
+    const [clickAc, setClickAc] = useState(0)
+    const [form] = Form.useForm()
+
+    // 表格内容定制化
+    const tableComObj = useCallback(
+      (key: string, val: string[], id?: any, backFu?: (id: number) => void) => {
+        const obj = {
+          // 超链接打开
+          A: (
+            <a href={val[1]} target='_blank' title={val[1]} rel='noreferrer'>
+              {val[0]}
+            </a>
+          ),
+          // 点击触发事件
+          S: (
+            <span
+              className={classNames('MTclick', clickAc === id ? 'MTclickAc' : '')}
+              onClick={() => {
+                if (id && backFu) {
+                  backFu(id)
+                  setClickAc(id)
+                }
+              }}
+            >
+              {val[1]}
+            </span>
+          )
+        }
+        return Reflect.get(obj, key)
+      },
+      [clickAc]
+    )
+
+    useEffect(() => {
+      const dom = document.querySelector(`.MyTable${classKey} .ant-table-body`) as HTMLDivElement
+
+      if (dom && yHeight) dom.style.height = yHeight + 'px'
+    }, [classKey, yHeight])
+
+    // 页码变化
+    const paginationChange = useCallback(
+      () => (pageNum: number, pageSize: number) => {
+        if (onChange) {
+          onChange(pageNum, pageSize)
+        }
+      },
+      [onChange]
+    )
+
+    const dataChangeFu = useCallback(
+      (v: any) => {
+        /**
+         * index:序号
+         * txt:正常数据
+         * img:图片
+         * txtChange:判断显示不同字段
+         * text:文字比较多的情况
+         */
+
+        const obj = {
+          index: (_: any, __: any, index: number) => index + 1 + (pageNum - 1) * pageSize,
+          txt: (item: any) =>
+            v[3] && !item[v[2]] ? (
+              <div dangerouslySetInnerHTML={{ __html: v[3] }}></div>
+            ) : item[v[2]] && item[v[2]] !== '0' ? (
+              item[v[2]]
+            ) : (
+              isNull
+            ),
+          txtArr: (item: any) => (
+            <div dangerouslySetInnerHTML={{ __html: (item[v[2]] || []).join('<br/>') }}></div>
+          ),
+          // 日期去掉00:00:00
+          dateRes: (item: any) => {
+            let res = item[v[2]] ? dayjs(item[v[2]]).format('YYYY-MM-DD') : isNull
+            return res
+          },
+          // 多个字段拼接
+          ping: (item: any) => (item[v[2]] || '') + (resJiLianFu(item[v[3]]) || '') || isNull,
+          // 这个模块特有的级联控制
+          txtC: (item: any) =>
+            v[1] === '年代' && item[v[2]] === '其他' ? '其他' : resJiLianFu(item[v[2]]),
+          // 尺寸
+          size: myTableTransferSize,
+          img: (item: any) =>
+            v[3] && !item[v[2]] ? (
+              <div dangerouslySetInnerHTML={{ __html: v[3] }}></div>
+            ) : (
+              <div className='tableImgAuto'>
+                <ImageLazy
+                  width={60}
+                  height={60}
+                  srcBig={item.thumbPc || item.filePath}
+                  src={item[v[2]] || item.thumb}
+                  offline={(item[v[2]] || item.thumb).includes('http')}
+                />
+              </div>
+            ),
+          // 附件大小
+          fileSize: (item: any) => {
+            if (item[v[2]]) {
+              const resTxt = (item[v[2]] / 1024).toFixed(2)
+              if (resTxt === '0.00') return '0.01'
+              else return resTxt
+            } else return isNull
+          },
+          txtChange: (item: any) => Reflect.get(v[3], item[v[2]]) || v[4] || isNull,
+          text: (item: any) => {
+            let tempCom: any = item[v[2]] || isNull
+
+            if (tempCom.length >= v[3]) {
+              tempCom = tempCom.substring(0, v[3]) + '...'
+            }
+
+            if (v[4]) {
+              tempCom = tableComObj(v[4], [tempCom, item[v[2]]], item.id, v[5])
+            } else if ((item[v[2]] || '').length >= v[3]) {
+              tempCom = (
+                <span style={{ cursor: 'pointer' }} title={item[v[2]]}>
+                  {tempCom}
+                </span>
+              )
+            }
+
+            return tempCom
+          },
+          input: (item: any) => {
+            return (
+              <Form.Item noStyle name={`${item.id}-${v[2]}`}>
+                <Input
+                  allowClear
+                  readOnly={readOnly}
+                  maxLength={v[3]?.maxLength}
+                  placeholder={v[3]?.placeholder || '请输入'}
+                />
+              </Form.Item>
+            )
+          },
+          datePicker: (item: any) => {
+            return (
+              <Form.Item noStyle name={`${item.id}-${v[2]}`}>
+                <DatePicker disabled={readOnly} {...(v[3] || {})} />
+              </Form.Item>
+            )
+          },
+          custom: (item: any) => {
+            return (
+              <Form.Item noStyle name={`${item.id}-${v[2]}`}>
+                {v[3].render(readOnly)}
+              </Form.Item>
+            )
+          },
+          desensitize: (item: any) => {
+            const txt = item[v[2]]
+            if (!txt) return isNull
+            const frontLen = v[3]?.frontLen || 3
+            const endLen = v[3]?.endLen || 4
+
+            return getDesensitizeTxt(txt, frontLen, endLen)
+          }
+        }
+
+        return Reflect.get(obj, v[0])
+      },
+      [isNull, pageNum, pageSize, readOnly, tableComObj]
+    )
+
+    const columns = useMemo(() => {
+      const arr: any = columnsTemp.map((v: any) => ({
+        title: myTitle && v.includes(myTitle.name) ? myTitle.Com : v[1],
+        render: dataChangeFu(v),
+        width: widthSet && Reflect.get(widthSet, v[2]) ? Reflect.get(widthSet, v[2]) : 'auto',
+        onCell:
+          merge && v.includes(merge.type)
+            ? // {rowSpan:3}
+              (item: any, index: number) => ({
+                rowSpan: index === 0 ? merge.num : 0
+              })
+            : ''
+      }))
+
+      return arr
+    }, [columnsTemp, dataChangeFu, merge, myTitle, widthSet])
+
+    useImperativeHandle(ref, () => ({
+      form
+    }))
+
+    // 空数据列表会闪一下的问题
+    const timerrRef = useRef(-1)
+    const [isLoding, setIsLoding] = useState(false)
+    useEffect(() => {
+      timerrRef.current = window.setTimeout(() => {
+        setIsLoding(true)
+      }, 500)
+      return () => {
+        clearTimeout(timerrRef.current)
+      }
+    }, [])
+
+    return (
+      <Form form={form} component={false}>
+        <Table
+          className={classNames(
+            `${styles.MyTable} MyTable${classKey}`,
+            emptyText ? styles.MyTableNull : ''
+          )}
+          scroll={{ y: yHeight ? yHeight : '' }}
+          dataSource={list}
+          columns={[...startBtn, ...columns, ...lastBtn]}
+          rowKey={rowKey}
+          pagination={
+            pagingInfo
+              ? {
+                  ...pagingInfo,
+                  current: pageNum,
+                  pageSize: pageSize,
+                  total: total,
+                  onChange: paginationChange()
+                }
+              : false
+          }
+          {...rest}
+          locale={
+            emptyText
+              ? {
+                  emptyText: (
+                    <div
+                      className='NODATA'
+                      style={{ height: yHeight ? yHeight : 500, opacity: isLoding ? '1' : '0' }}
+                    >
+                      <img src={baseURL + `/baseData/staImg/build.png`} alt='' />
+                      <p>暂无相关搜索结果,请更换关键字搜索</p>
+                    </div>
+                  )
+                }
+              : {}
+          }
+        />
+      </Form>
+    )
+  }
+)
+
+const MemoMyTable = React.memo(MyTable)
+
+export default MemoMyTable

+ 26 - 0
src/components/NoPower/index.module.scss

@@ -0,0 +1,26 @@
+.NoPower {
+  background-color: #fff;
+  border-radius: 10px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  color: #666666;
+  font-size: 16px;
+  padding-bottom: 20px;
+
+  :global {
+    img {
+      width: 300px;
+    }
+    p {
+      margin-bottom: 10px;
+    }
+    div {
+      width: 80%;
+      margin-top: 8%;
+      display: flex;
+      justify-content: space-around;
+    }
+  }
+}

+ 80 - 0
src/components/NoPower/index.tsx

@@ -0,0 +1,80 @@
+import React, { useEffect, useState } from 'react'
+import styles from './index.module.scss'
+import { baseURL } from '@/utils/http'
+
+const objData = {
+  // 500
+  1: {
+    txt1: '抱歉,系统错误,',
+    txt2: '您可以选择尝试清除缓存或联系管理员',
+    imgName: '500'
+  },
+
+  // 网络错误
+  2: {
+    txt1: '网络错误,',
+    txt2: '请检查网络连接是否正常',
+    imgName: 'err'
+  },
+  // 建设中页面
+  3: {
+    txt1: '正在建设中,敬请期待',
+    txt2: '',
+    imgName: 'isBuild'
+  },
+  // 查询为空
+  4: {
+    txt1: '暂无相关搜索结果,请更换关键字搜索',
+    txt2: '',
+    imgName: 'build'
+  },
+  // 详情页为空
+  5: {
+    txt1: '暂无相关内容,请退回上一页',
+    txt2: '',
+    imgName: 'null'
+  },
+  // 404
+  6: {
+    txt1: '抱歉,系统错误,',
+    txt2: '您可以选择尝试清除缓存或联系管理员',
+    imgName: '404'
+  }
+}
+
+function NoPower() {
+  const [info, setInfo] = useState({
+    txt1: '',
+    txt2: '',
+    imgName: ''
+  })
+
+  useEffect(() => {
+    const urlAll = window.location.href
+    const arr = urlAll.split('?k=')
+    if (arr && arr.length > 1) {
+      const num = arr[1]
+
+      const obj = Reflect.get(objData, num)
+      if (obj.imgName) setInfo(obj)
+    } else {
+      setInfo({
+        txt1: '抱歉,您暂无当前页面的访问权限,',
+        txt2: '请联系管理员',
+        imgName: 'noPower'
+      })
+    }
+  }, [])
+
+  return (
+    <div className={styles.NoPower}>
+      <img src={baseURL + `/baseData/staImg/${info.imgName}.png`} alt='' />
+      <p>{info.txt1} </p>
+      <p>{info.txt2}</p>
+    </div>
+  )
+}
+
+const MemoNoPower = React.memo(NoPower)
+
+export default MemoNoPower

+ 24 - 0
src/components/NotFound/index.tsx

@@ -0,0 +1,24 @@
+import { baseURL } from '@/utils/http'
+import { useEffect, useRef } from 'react'
+
+export default function NotFound() {
+  const timeRef = useRef(-1)
+
+  useEffect(() => {
+    timeRef.current = window.setTimeout(() => {
+      const dom: HTMLDivElement = document.querySelector('.noFindPage')!
+      dom.style.opacity = '1'
+    }, 500)
+    return () => {
+      clearTimeout(timeRef.current)
+    }
+  }, [])
+
+  return (
+    <div className='noFindPage'>
+      <img src={baseURL + '/baseData/staImg/404.png'} alt='' />
+      <p>抱歉,系统错误, </p>
+      <p>您可以选择尝试清除缓存或联系管理员</p>
+    </div>
+  )
+}

+ 10 - 0
src/components/SpinLoding/index.module.scss

@@ -0,0 +1,10 @@
+.SpinLoding {
+  position: relative;
+  z-index: 9999;
+  width: 100%;
+  height: 100%;
+  background-color: #fff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}

+ 13 - 0
src/components/SpinLoding/index.tsx

@@ -0,0 +1,13 @@
+import styles from "./index.module.scss";
+import { Spin } from "antd";
+import React from "react";
+function SpinLoding() {
+  return (
+    <div className={styles.SpinLoding}>
+      <Spin size='large'/>
+    </div>
+  );
+}
+const MemoSpinLoding = React.memo(SpinLoding);
+
+export default MemoSpinLoding;

+ 43 - 0
src/components/UpAsyncLoding/index.module.scss

@@ -0,0 +1,43 @@
+.UpAsyncLoding {
+  opacity: 0;
+  pointer-events: none;
+  position: fixed;
+  z-index: 10000;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, .4);
+
+  :global {
+    .progressBox {
+      position: absolute;
+      top: 60%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 500px;
+      height: 6px;
+      border-radius: 3px;
+      border: 1px solid var(--themeColor);
+      overflow: hidden;
+
+      #progress {
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 0%;
+        height: 100%;
+        background-color: var(--themeColor);
+      }
+
+    }
+
+    .closeUpBtn {
+      position: absolute;
+      top: 70%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+    }
+
+  }
+}

+ 38 - 0
src/components/UpAsyncLoding/index.tsx

@@ -0,0 +1,38 @@
+import store, { RootState } from "@/store";
+import { Button } from "antd";
+import React, { useCallback } from "react";
+import { useSelector } from "react-redux";
+import styles from "./index.module.scss";
+function UpAsyncLoding() {
+  // 从仓库中获取取消上传的函数
+  const closeUpFile = useSelector(
+    (state: RootState) => state.A0Layout.closeUpFile
+  );
+
+  const btnClose = useCallback(() => {
+    closeUpFile.fu();
+
+    setTimeout(() => {
+      store.dispatch({
+        type: "layout/closeUpFile",
+        payload: { fu: () => {}, state: false },
+      });
+    }, 200);
+  }, [closeUpFile]);
+
+  return (
+    <div id="UpAsyncLoding" className={styles.UpAsyncLoding}>
+      <div className="progressBox">
+        <div id="progress"></div>
+      </div>
+      {/* 手动取消上传按钮 */}
+      <div className="closeUpBtn">
+        <Button onClick={btnClose}>取消上传</Button>
+      </div>
+    </div>
+  );
+}
+
+const MemoUpAsyncLoding = React.memo(UpAsyncLoding);
+
+export default MemoUpAsyncLoding;

+ 41 - 0
src/components/YtableVideo/index.module.scss

@@ -0,0 +1,41 @@
+.YtableVideo {
+  display: flex;
+  justify-content: center;
+  :global {
+    .TvideoBox {
+      cursor: pointer;
+      width: 60px;
+      height: 60px;
+      position: relative;
+      .TvideoBoxLook {
+        position: absolute;
+        z-index: 10;
+        opacity: 0;
+        transition: opacity 0.3s;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 14px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, 0.6);
+        .anticon-eye {
+          font-size: 18px;
+        }
+      }
+      video {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      &:hover {
+        .TvideoBoxLook {
+          opacity: 1;
+        }
+      }
+    }
+  }
+}

+ 36 - 0
src/components/YtableVideo/index.tsx

@@ -0,0 +1,36 @@
+import React from 'react'
+import styles from './index.module.scss'
+import store from '@/store'
+import { EyeOutlined } from '@ant-design/icons'
+import { baseURL } from '@/utils/http'
+
+type Props = {
+  src: string
+}
+
+function YtableVideo({ src }: Props) {
+  return (
+    <div className={styles.YtableVideo}>
+      <div className='TvideoBox'>
+        <div
+          className='TvideoBoxLook'
+          onClick={() =>
+            store.dispatch({
+              type: 'layout/lookDom',
+              payload: { src: baseURL + src, type: 'video', flag: true }
+            })
+          }
+        >
+          <EyeOutlined />
+          &nbsp;
+          <div>预览</div>
+        </div>
+        <video src={baseURL + src}></video>
+      </div>
+    </div>
+  )
+}
+
+const MemoYtableVideo = React.memo(YtableVideo)
+
+export default MemoYtableVideo

+ 77 - 0
src/components/Z3upFiles/data.ts

@@ -0,0 +1,77 @@
+import store from '@/store'
+import { baseURL } from '@/utils/http'
+
+// 查看 权限 图片 /视频 、音频
+export const authFilesLookFu = (name: string, url: string) => {
+  let flag = false
+
+  const nameRes = name ? name : ''
+
+  // pdf和txt 直接新窗口打开
+  // const arr0: ('.pdf' | '.txt')[] = ['.pdf', '.txt']
+  // arr0.forEach(v => {
+  //   if (nameRes.toLowerCase().endsWith(v)) {
+  //     if (url) window.open(baseURL + url)
+  //     flag = true
+  //   }
+  // })
+
+  // 图片使用 antd的图片预览组件
+  const arr1 = ['.png', '.jpg', '.jpeg', '.gif']
+  arr1.forEach(v => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      if (url)
+        store.dispatch({
+          type: 'layout/lookBigImg',
+          payload: {
+            url: baseURL + url,
+            show: true
+          }
+        })
+
+      flag = true
+    }
+  })
+
+  // 视频和音频 使用自己的封装的组件
+  let type: '' | 'video' | 'audio' = ''
+  const arr2 = ['.mp3', '.wav']
+  arr2.forEach(v => {
+    if (nameRes.toLowerCase().endsWith(v)) {
+      type = 'audio'
+      flag = true
+    }
+  })
+
+  if (nameRes.toLowerCase().endsWith('.mp4')) {
+    type = 'video'
+    flag = true
+  }
+
+  if (type && url)
+    store.dispatch({
+      type: 'layout/lookDom',
+      payload: {
+        src: url,
+        type
+      }
+    })
+
+  // docx xlsx使用插件
+  // const arr3: (".docx" | ".xlsx")[] = [".docx", ".xlsx"];
+  // arr3.forEach((v) => {
+  //   if (nameRes.toLowerCase().endsWith(v)) {
+  //     if (url) {
+  //       noAuth
+  //         ? store.dispatch({
+  //             type: "layout/fileLookUrl",
+  //             payload: { url: baseURL + url, name: v },
+  //           })
+  //         : urlChangeFu(url, false, v, nameRes);
+  //     }
+  //     flag = true;
+  //   }
+  // });
+
+  return flag
+}

+ 69 - 0
src/components/Z3upFiles/index.module.scss

@@ -0,0 +1,69 @@
+.Z3upFiles {
+  position: relative;
+  width: 100%;
+  height: 100%;
+
+  :global {
+    .Z3files {
+      width: 500px;
+      // padding-top: 6px;
+
+      .Z3filesRow {
+        display: flex;
+        margin-top: 5px;
+        // justify-content: space-between;
+        align-items: center;
+        font-size: 16px;
+        // border-bottom: 1px dashed #999;
+        padding-bottom: 5px;
+
+        .Z3files1 {
+          max-width: calc(100% - 130px);
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+
+        .Z3files2 {
+          display: flex;
+          width: 120px;
+          justify-content: flex-end;
+
+          & > span {
+            cursor: pointer;
+          }
+
+          a {
+            color: black;
+          }
+        }
+      }
+    }
+
+    .fileTit {
+      margin-top: 14px;
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+
+      .noUpThumb {
+        position: relative;
+        overflow: hidden;
+        opacity: 0;
+        transition: top 0.2s;
+        color: #ff4d4f;
+        top: -10px;
+      }
+
+      .noUpThumbAc {
+        top: 0;
+        opacity: 1;
+      }
+    }
+
+    .lookNone {
+      position: relative;
+      top: 4px;
+      left: 10px;
+    }
+  }
+}

+ 238 - 0
src/components/Z3upFiles/index.tsx

@@ -0,0 +1,238 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { FileImgListType } from '@/types'
+import { API_upFile, fileTypeRes } from '@/store/action/layout'
+import { MessageFu } from '@/utils/message'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { forwardRef, useImperativeHandle } from 'react'
+import { Button, Popconfirm } from 'antd'
+import { EyeOutlined, UploadOutlined, CloseOutlined, DownloadOutlined } from '@ant-design/icons'
+import classNames from 'classnames'
+import { baseURL } from '@/utils/http'
+import { authFilesLookFu } from './data'
+
+const size = Infinity
+
+export type GoodFileType = {
+  createTime: string
+  creatorId: number
+  creatorName: string
+  description: string
+  display?: any
+  effect: string | null
+  fileName: string
+  filePath: string
+  fileSize: string
+  goodName: string
+  goodNum: string
+  goodNumName: string
+  id: number
+  moduleId?: any
+  moduleName: string
+  parentId?: any
+  thumb: string
+  type: string
+  updateTime: string
+}
+
+type Props = {
+  max: number //最多传多少个文件
+  isLook: boolean //是否是查看
+  ref: any //当前自己的ref,给父组件调用
+  fileCheck: boolean
+  dirCode: string //文件的code码
+  myUrl: string
+  fromData?: any
+  lookData: FileImgListType[] //编辑或者 查看 回显
+  accept?: string
+  // result:成果 | list:清单
+  tips?: string
+  // 文件大小
+  noShowList?: boolean //不回显列表
+  fileRes?: (obj: GoodFileType) => void
+}
+
+function Z3upFiles(
+  {
+    max,
+    isLook,
+    fileCheck,
+    dirCode,
+    myUrl,
+    fromData,
+    lookData,
+    accept = '*',
+    tips = '',
+    noShowList = false,
+    fileRes
+  }: Props,
+  ref: any
+) {
+  const [fileList, setFileList] = useState<FileImgListType[]>([])
+
+  useEffect(() => {
+    if (lookData && lookData.length > 0) setFileList(lookData)
+  }, [lookData])
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传文件
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+
+        // 校验格式
+        if (!filesInfo.name.includes('.zip') && accept !== '*') {
+          e.target.value = ''
+          return MessageFu.warning(`只支持zip格式!`)
+        }
+
+        // 校验大小
+        if (size && filesInfo.size > size * 1024 * 1024) {
+          e.target.value = ''
+          return MessageFu.warning(`最大支持${size}M!`)
+        }
+
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+
+        const typeRes = fileTypeRes(filesInfo.name)
+
+        fd.append('file', filesInfo)
+        fd.append('type', typeRes)
+        fd.append('dirCode', dirCode)
+        fd.append('isDb', 'true')
+
+        // 开启压缩图片
+        fd.append('isCompress', 'true')
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k])
+          }
+        }
+
+        e.target.value = ''
+
+        try {
+          const res = await API_upFile(fd, myUrl)
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            setFileList([...fileList, res.data])
+
+            if (fileRes) {
+              fileRes(res.data)
+            }
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [accept, dirCode, fileList, fileRes, fromData, myUrl]
+  )
+
+  // 列表删除某一个文件
+  const delImgListFu = useCallback(
+    async (id: number) => {
+      setFileList(fileList.filter(v => v.id !== id))
+    },
+    [fileList]
+  )
+
+  // 让父组件调用,拿到 附件信息
+  const filesIdRes = useCallback(() => {
+    return fileList.map(v => v.id)
+  }, [fileList])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    filesIdRes
+  }))
+
+  return (
+    <div className={styles.Z3upFiles} id='Z3upFiles'>
+      <input
+        id='upInput'
+        type='file'
+        accept={accept}
+        ref={myInput}
+        onChange={e => handeUpPhoto(e)}
+      />
+      <div className='Z3Btn'>
+        {fileList.length < max && !isLook ? (
+          <Button
+            onClick={() => myInput.current?.click()}
+            icon={<UploadOutlined rev={undefined} />}
+            type={noShowList ? 'primary' : 'default'}
+          >
+            上传
+          </Button>
+        ) : null}
+
+        {noShowList ? null : (
+          <div className='Z3files'>
+            {fileList.map(v => (
+              <div className='Z3filesRow' key={v.id}>
+                <div className='Z3files1' title={v.fileName}>
+                  {v.fileName}
+                </div>
+                <div className='Z3files2'>
+                  {authFilesLookFu(v.fileName, '') ? (
+                    <>
+                      <EyeOutlined
+                        rev={undefined}
+                        title='查看'
+                        onClick={() => authFilesLookFu(v.fileName, v.filePath)}
+                      />
+                      &emsp;
+                    </>
+                  ) : null}
+                  <a
+                    title='下载'
+                    href={baseURL + v.filePath}
+                    download={v.fileName}
+                    target='_blank'
+                    rel='noreferrer'
+                  >
+                    <DownloadOutlined rev={undefined} />
+                  </a>
+                  &emsp;
+                  <Popconfirm
+                    title='删除后无法恢复,是否删除?'
+                    okText='删除'
+                    cancelText='取消'
+                    onConfirm={() => delImgListFu(v.id)}
+                    okButtonProps={{ loading: false }}
+                  >
+                    <CloseOutlined rev={undefined} title='删除' hidden={isLook} />
+                  </Popconfirm>
+                </div>
+              </div>
+            ))}
+          </div>
+        )}
+
+        <div className='fileTit' hidden={isLook || noShowList}>
+          {tips ? tips : null}
+          <br />
+          <div
+            className={classNames(
+              'noUpThumb',
+              fileList.length <= 0 && fileCheck ? 'noUpThumbAc' : ''
+            )}
+          >
+            请上传视频!
+          </div>
+        </div>
+      </div>
+      {isLook && fileList.length <= 0 ? <div className='lookNone'>(空)</div> : null}
+    </div>
+  )
+}
+
+export default forwardRef(Z3upFiles)

+ 32 - 0
src/components/ZGaddNow/data.ts

@@ -0,0 +1,32 @@
+import { B3nowFormType, B3nowSearchType } from './type'
+
+export const B3nowArr1: B3nowSearchType = [
+  { name: '藏品编号', key: 'num', type: '输入框' },
+  { name: '藏品名称', key: 'name', type: '输入框' },
+  { name: '编号类型', nameKey: '藏品编号类型', key: 'numName', type: '下拉框' },
+  { name: '文物级别', key: 'dictLevel', type: '下拉框' },
+  { name: '文物类别', key: 'dictType', type: '级联' }
+]
+
+export const B3nowArr2: B3nowSearchType = [
+  { name: '年代', key: 'dictAge', type: '级联' },
+  { name: '质地', nameKey: '单一质地', key: 'dictTexture3', type: '级联' },
+  { name: '完残程度', key: 'dictTorn', type: '级联' },
+  { name: '来源', key: 'source', type: '级联' },
+  { name: '入藏状态', key: 'statusCollect', type: '下拉框' },
+  { name: '库存状态', key: 'statusStorage', type: '下拉框' }
+]
+export const B3baseFormData: B3nowFormType = {
+  num: '',
+  name: '',
+  numName: '',
+  dictLevel: '',
+  dictType: '',
+  dictAge: '',
+  dictTexture3: '',
+  dictTorn: '',
+  source: '',
+  statusCollect: null,
+  statusStorage: null,
+  isFocus: null
+}

+ 67 - 0
src/components/ZGaddNow/index.module.scss

@@ -0,0 +1,67 @@
+// 新增弹窗页面
+.ZGaddNow {
+  :global {
+    .B3Gtit {
+      display: flex;
+      justify-content: space-between;
+    }
+
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ant-modal {
+      width: 1600px !important;
+      min-width: 1600px;
+      top: 40px !important;
+    }
+
+    .ant-modal-body {
+      border-top: 1px solid #ccc;
+    }
+
+    .B3GaMain {
+      padding-top: 15px;
+      .B3GaTop {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 15px;
+        .B3Gatopll {
+          display: flex;
+          & > div {
+            position: relative;
+            margin-right: 15px;
+            display: flex;
+            align-items: center;
+            .ant-input {
+              width: 160px;
+            }
+          }
+          .ant-select {
+            width: 130px;
+          }
+        }
+        .ant-select-selection-placeholder {
+          color: black;
+        }
+      }
+      .ant-table-cell {
+        padding: 8px !important;
+        text-align: center !important;
+      }
+      .ant-btn-text {
+        color: var(--themeColor);
+      }
+      .tableImgAuto {
+        display: flex;
+        justify-content: center;
+      }
+      .B3GaMainBtn {
+        position: relative;
+        top: 15px;
+        text-align: center;
+      }
+    }
+  }
+}

+ 305 - 0
src/components/ZGaddNow/index.tsx

@@ -0,0 +1,305 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, Checkbox, CheckboxProps, Input, Modal, Select } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import MyTable from '@/components/MyTable'
+import { B3eTableC } from '@/utils/tableData'
+import { cascaderObjFu, openGoodsInfoFu } from '@/utils/history'
+import { MessageFu } from '@/utils/message'
+import { C1GoodType } from '@/pages/A3_ledger/C1ledger/type'
+import { API_goodsNowAdd } from '@/store/action/C1ledger'
+import { selectObj } from '@/utils/select'
+import { B3nowSearchType } from './type'
+import { B3baseFormData, B3nowArr1, B3nowArr2 } from './data'
+
+type Props = {
+  /** 菜单权限id,用于数据鉴权 */
+  menuId: number
+  /** 订单id,用于数据鉴权 */
+  orderId?: number
+  closeFu: () => void
+  nowSta: { key: string; id: string }
+  // key:1=入馆 2=入藏 3-登记 4-删除 5-入库 6-出库 7-藏品编辑
+  dataResFu: (data: C1GoodType[]) => void
+  isOne?: boolean //从藏品编辑进来 只能选择一个藏品
+  oldCheckArr: C1GoodType[]
+  canObj?: any //其他页面带过来的参数
+  // 藏品登记-只选择一个
+  register?: boolean
+  httpType?: 'get' | 'post'
+  // 不需要的搜索条件
+  searchFilterKey?: string[]
+  checkboxProps?: (item: C1GoodType) => Omit<CheckboxProps, 'checked' | 'onChange'>
+}
+
+function ZGaddNow({
+  nowSta,
+  menuId,
+  orderId,
+  closeFu,
+  isOne = false,
+  dataResFu,
+  oldCheckArr,
+  canObj,
+  register,
+  httpType = 'post',
+  searchFilterKey = [],
+  checkboxProps
+}: Props) {
+  // 入藏状态特别过滤
+  const RuChangSelect = useMemo(() => {
+    let arr = selectObj['入藏状态']
+    if (nowSta.key === '入藏') arr = arr.filter(v => v.label === '已入馆')
+    return arr
+  }, [nowSta.key])
+
+  const [formData, setFormData] = useState(B3baseFormData)
+  const formDataRef = useRef(B3baseFormData)
+
+  useEffect(() => {
+    formDataRef.current = { ...formData }
+  }, [formData])
+
+  // 点击搜索的 时间戳
+  const [timeKey, setTimeKey] = useState(0)
+
+  // 点击搜索
+  const clickSearch = useCallback(() => {
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [])
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setFormData(B3baseFormData)
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [])
+
+  const [tableList, setTableList] = useState<C1GoodType[]>([])
+  const [tableListAll, setTableListAll] = useState<C1GoodType[]>([])
+
+  // 封装发送请求的函数
+  const getList = useCallback(async () => {
+    let canObjTemp = canObj || {}
+    const obj = {
+      ...formDataRef.current,
+      ...canObjTemp
+    }
+    const res = await API_goodsNowAdd(nowSta.id, obj, httpType)
+    if (res.code === 0) {
+      setTableList(res.data)
+      if (timeKey === 0) setTableListAll(res.data)
+    }
+  }, [canObj, httpType, nowSta.id, timeKey])
+
+  useEffect(() => {
+    getList()
+  }, [getList])
+
+  // 多选
+  const [checkArr, setCheckArr] = useState<C1GoodType[]>([])
+
+  // 显示条数的
+  // const [checkNum, setCheckNum] = useState<C1GoodType[]>([])
+
+  // 旧数组
+  useEffect(() => {
+    setCheckArr(oldCheckArr)
+  }, [oldCheckArr])
+
+  // 过滤掉 tableList 里面没有的id
+  const resNum = useMemo(() => {
+    const tableIds = tableListAll.map(v => v.id)
+    const arr = checkArr.filter(v => tableIds.includes(v.id))
+    return arr.length
+  }, [checkArr, tableListAll])
+
+  const checkFu = useCallback(
+    (item: C1GoodType) => {
+      // 藏品编辑只能单选
+      if (isOne) {
+        if (checkArr && checkArr.length && item.id === checkArr[0].id) {
+          setCheckArr([])
+        } else setCheckArr([item])
+      } else {
+        if (checkArr.map(v => v.id).includes(item.id))
+          setCheckArr(checkArr.filter(v => v.id !== item.id))
+        else setCheckArr([...checkArr, item])
+      }
+    },
+    [checkArr, isOne]
+  )
+
+  const startBtn = useMemo(() => {
+    return [
+      {
+        title: isOne ? '单选' : '选择',
+        width: 50,
+        render: (item: C1GoodType) => (
+          <Checkbox
+            {...checkboxProps?.(item)}
+            checked={checkArr.map(v => v.id).includes(item.id)}
+            onChange={() => checkFu(item)}
+          />
+        )
+      },
+      {
+        title: '藏品编号',
+        render: (item: C1GoodType) => item.num || '(空)'
+      }
+    ]
+  }, [checkArr, checkFu, checkboxProps, isOne])
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '入藏状态',
+        render: (item: C1GoodType) =>
+          (selectObj['入藏状态'].find(c => c.value === item.statusCollect) || { label: '(空)' })
+            .label
+      },
+      {
+        title: '库存状态',
+        render: (item: C1GoodType) =>
+          (selectObj['库存状态'].find(c => c.value === item.statusStorage) || { label: '(空)' })
+            .label
+      },
+      {
+        title: '操作',
+        render: (item: C1GoodType) => {
+          return (
+            <Button
+              size='small'
+              type='text'
+              onClick={() => openGoodsInfoFu(item.id, menuId, '', orderId)}
+            >
+              查看
+            </Button>
+          )
+        }
+      }
+    ]
+  }, [menuId, orderId])
+
+  // 点击提交
+  const btnOk = useCallback(() => {
+    dataResFu(checkArr)
+    if (!isOne) MessageFu.success('提交成功')
+    if (register) MessageFu.success(checkArr.length ? '信息带入成功' : '信息清空成功')
+    closeFu()
+  }, [checkArr, closeFu, dataResFu, isOne, register])
+
+  // 顶部筛选
+  const searchDom = useCallback(
+    (arr: B3nowSearchType) => {
+      return arr.map(item => {
+        return (
+          <div key={item.name}>
+            <span>{item.name}:</span>
+            {item.type === '输入框' ? (
+              <Input
+                value={formData[item.key as 'num']}
+                onChange={e => setFormData({ ...formData, [item.key]: e.target.value })}
+                style={{ width: 195 }}
+                placeholder={item.name}
+                maxLength={30}
+              />
+            ) : item.type === '下拉框' ? (
+              <Select
+                options={
+                  item.name === '入藏状态'
+                    ? RuChangSelect
+                    : selectObj[(item.nameKey || item.name) as '入藏状态']
+                }
+                placeholder='全部'
+                allowClear={true}
+                value={formData[item.key] || null}
+                onChange={e => setFormData({ ...formData, [item.key]: e })}
+              />
+            ) : (
+              <Cascader
+                options={cascaderObjFu()[item.nameKey || item.name]}
+                placeholder='全部'
+                fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+                allowClear={true}
+                value={formData[item.key] ? (formData[item.key] as string).split(',') : []}
+                onChange={e => setFormData({ ...formData, [item.key]: e ? e.join(',') : '' })}
+              />
+            )}
+          </div>
+        )
+      })
+    },
+    [RuChangSelect, formData]
+  )
+
+  return (
+    <Modal
+      wrapClassName={styles.ZGaddNow}
+      open={true}
+      title={
+        <div className='B3Gtit'>
+          <div>选择藏品</div> {register ? <div></div> : <div>已选中 {resNum} 条</div>}
+        </div>
+      }
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='B3GaMain'>
+        <div className='B3GaTop'>
+          <div className='B3Gatopll'>
+            {searchDom(B3nowArr1.filter(i => !searchFilterKey.includes(i.key)))}
+          </div>
+          <div>
+            <Checkbox
+              checked={formData.isFocus === 1}
+              onChange={e => setFormData({ ...formData, isFocus: e.target.checked ? 1 : null })}
+            >
+              我关注的
+            </Checkbox>
+          </div>
+        </div>
+
+        <div className='B3GaTop'>
+          <div className='B3Gatopll'>
+            {searchDom(B3nowArr2.filter(i => !searchFilterKey.includes(i.key)))}
+          </div>
+          <div>
+            <Button type='primary' onClick={clickSearch}>
+              查询
+            </Button>
+            &emsp;
+            <Button onClick={resetSelectFu}>重置</Button>
+          </div>
+        </div>
+
+        {/* 表格 */}
+        <MyTable
+          yHeight={575}
+          classKey='ZGaddNow'
+          list={tableList}
+          columnsTemp={B3eTableC}
+          startBtn={startBtn}
+          lastBtn={tableLastBtn}
+          pagingInfo={false}
+        />
+
+        <div className='B3GaMainBtn'>
+          <Button type='primary' disabled={resNum === 0 && isOne && !register} onClick={btnOk}>
+            提交
+          </Button>
+          &emsp;
+          <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+        </div>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoZGaddNow = React.memo(ZGaddNow)
+
+export default MemoZGaddNow

+ 23 - 0
src/components/ZGaddNow/type.d.ts

@@ -0,0 +1,23 @@
+export type B3nowFormType = {
+  num: string
+  name: string
+  numName: string
+  dictLevel: string
+  dictType: string
+  dictAge: string
+  dictTexture3: string
+  dictTorn: string
+  source: string
+  statusCollect: number | null
+  statusStorage: number | null
+  isFocus: 0 | 1 | null
+}
+
+export type B3nowFormKeyType = keyof B3nowFormType
+
+export type B3nowSearchType = {
+  name: string
+  nameKey?: string
+  key: B3nowFormKeyType
+  type: '输入框' | '下拉框' | '级联'
+}[]

+ 82 - 0
src/components/ZRichText/index.module.scss

@@ -0,0 +1,82 @@
+.ZRichText {
+  width: 1000px;
+  height: 100%;
+
+  :global {
+    .txtBox {
+      width: 100%;
+      height: 100%;
+      border: 1px solid #ccc;
+
+      a{
+        color: #fff !important;
+      }
+
+      // 隐藏媒体功能
+      .control-item.media {
+        display: none;
+      }
+
+      .bf-container {
+        height:100%;
+      }
+
+      .bf-content {
+        height: calc(100% - 92px);
+        padding-bottom: 0px;
+      }
+
+
+
+      .bf-controlbar {
+        position: relative;
+
+        .upImgBox {
+          position: absolute;
+          bottom: 13px;
+          right: 15px;
+          cursor: pointer;
+          color: var(--themeColor);
+          // display: none;
+        }
+
+        .upImgBoxNo {
+          display: none;
+        }
+
+      }
+    }
+
+    .noUpThumb {
+      position: relative;
+      overflow: hidden;
+      opacity: 0;
+      transition: top .2s;
+      color: #ff4d4f;
+      top: -10px;
+    }
+
+    .noUpThumbAc {
+      top: 0;
+      opacity: 1;
+    }
+
+    .bf-media .bf-image {
+      float: initial !important;
+      display: block;
+      margin: 10px auto;
+      text-align: center;
+
+      // 不让拖动放大缩小图片(会报错)
+      .bf-csize-icon {
+        display: none !important;
+      }
+
+      img {
+        max-width: 500px;
+        max-height: 300px;
+      }
+    }
+
+  }
+}

+ 202 - 0
src/components/ZRichText/index.tsx

@@ -0,0 +1,202 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+
+// 引入编辑器组件
+
+// 安装---npm install braft-editor --save --force
+// npm install braft-utils --save --force
+import { ContentUtils } from 'braft-utils'
+import BraftEditor from 'braft-editor'
+// 引入编辑器样式
+import 'braft-editor/dist/index.css'
+
+import classNames from 'classnames'
+import { MessageFu } from '@/utils/message'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { baseURL } from '@/utils/http'
+
+import { forwardRef, useImperativeHandle } from 'react'
+import { API_upFile } from '@/store/action/layout'
+
+type Props = {
+  check: boolean //表单校验,为fasle表示不校验
+  dirCode: string //文件的code码
+  isLook: boolean //是否是查看进来
+  ref: any //当前自己的ref,给父组件调用
+  myUrl: string //上传的api地址
+  full?: boolean
+}
+
+function ZRichText({ check, dirCode, isLook, myUrl, full }: Props, ref: any) {
+  // 添加 上传 图片的dom
+  useEffect(() => {
+    setTimeout(() => {
+      const dom = document.querySelector('.bf-controlbar')!
+      const div = document.createElement('div')
+      div.className = 'upImgBox'
+      // div.title = "上传图片";
+      div.innerHTML = '上传图片/视频'
+      div.onclick = async () => {
+        myInput.current?.click()
+      }
+      dom.appendChild(div)
+    }, 20)
+
+    // 监听 富文本 的 class 变化,在全屏的时候会 富文本会添加上 fullscreen 的类
+    // 修复顶部样式冲突问题
+
+    const editorDom = document.querySelector('.bf-container') as HTMLDivElement
+
+    const observer = new MutationObserver(() => {
+      // console.log("change");
+      const dom = document.querySelector('.layoutRightTop') as HTMLDivElement
+
+      if (editorDom.className.includes('fullscreen')) dom.style.zIndex = '-1'
+      else dom.style.zIndex = '100'
+    })
+
+    observer.observe(editorDom, {
+      attributes: true
+    })
+
+    // 销毁监听
+    return () => {
+      observer.disconnect()
+    }
+  }, [])
+
+  useEffect(() => {
+    const controlbarDom = document.querySelectorAll('.txtBox .bf-controlbar ')
+    const contentDom = document.querySelectorAll('.txtBox .bf-content ')
+    if (controlbarDom) {
+      controlbarDom.forEach((v: any) => {
+        v.style.display = isLook ? 'none' : 'block'
+      })
+      contentDom.forEach((v: any) => {
+        v.style.height = isLook ? '100%' : ''
+      })
+    }
+  }, [isLook])
+
+  // 编辑器文本
+  const [editorValue, setEditorValue] = useState(
+    // 初始内容
+    BraftEditor.createEditorState('')
+  )
+
+  // 判断 富文本是否为空
+  const isTxtFlag = useMemo(() => {
+    const txt: string = editorValue.toHTML()
+    if (txt.replaceAll('<p>', '').replaceAll('</p>', '').replaceAll(' ', '') === '') {
+      return true
+    } else return false
+  }, [editorValue])
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传图片
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+
+        let type = ['image/jpeg', 'image/png', 'video/mp4']
+        let size = 5
+        let txt = '图片只支持png、jpg和jpeg格式!'
+        let txt2 = '图片最大支持5M!'
+
+        const isVideoFlag = filesInfo.name.endsWith('.mp4')
+
+        // 校验格式
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = ''
+          if (isVideoFlag) {
+            // 上传视频
+            size = 500
+            txt = '视频只支持mp4格式!'
+            txt2 = '视频最大支持500M!'
+          }
+
+          return MessageFu.warning(txt)
+        }
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = ''
+          return MessageFu.warning(txt2)
+        }
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append('type', isVideoFlag ? 'video' : 'img')
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
+
+        e.target.value = ''
+
+        try {
+          const res = await API_upFile(fd, myUrl)
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            // 在光标位置插入图片
+            const newTxt = ContentUtils.insertMedias(editorValue, [
+              {
+                type: 'IMAGE',
+                url: baseURL + res.data.filePath
+              }
+            ])
+
+            setEditorValue(newTxt)
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [dirCode, editorValue, myUrl]
+  )
+
+  // 让父组件调用的 回显 富文本
+  const ritxtShowFu = useCallback((val: string) => {
+    setEditorValue(BraftEditor.createEditorState(val))
+  }, [])
+
+  // 让父组件调用的返回 富文本信息 和 表单校验 isTxtFlag为ture表示未通过校验
+  const fatherBtnOkFu = useCallback(() => {
+    return { val: editorValue.toHTML(), flag: isTxtFlag }
+  }, [editorValue, isTxtFlag])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    ritxtShowFu,
+    fatherBtnOkFu
+  }))
+
+  return (
+    <div className={styles.ZRichText} style={{ width: full ? '100%' : '' }}>
+      <input
+        id='upInput'
+        type='file'
+        accept='.png,.jpg,.jpeg,.mp4'
+        ref={myInput}
+        onChange={e => handeUpPhoto(e)}
+      />
+
+      <div className='txtBox'>
+        <BraftEditor
+          readOnly={isLook}
+          placeholder='请输入内容'
+          value={editorValue}
+          onChange={e => setEditorValue(e)}
+          imageControls={['remove']}
+        />
+      </div>
+      <div className={classNames('noUpThumb', check && isTxtFlag ? 'noUpThumbAc' : '')}>
+        请输入正文!
+      </div>
+    </div>
+  )
+}
+
+export default forwardRef(ZRichText)

+ 208 - 0
src/components/ZRichTexts/index.module.scss

@@ -0,0 +1,208 @@
+.ZRichTexts {
+  width: 1000px;
+
+  :global {
+    // 正文
+    .formRightZW {
+      width: 1000px;
+      top: -3px;
+      position: relative;
+      display: flex;
+      align-items: center;
+      // justify-content: space-between;
+      height: 32px;
+
+      .formRightZWRR {
+        display: flex;
+      }
+    }
+
+    // 从查看进来
+    .formRightZWLook {
+      .ant-checkbox-wrapper {
+        pointer-events: none;
+      }
+    }
+
+    .txtBox {
+      width: 100%;
+      position: relative;
+
+      a {
+        color: #fff !important;
+      }
+
+      // 隐藏媒体功能
+      .control-item.media {
+        display: none;
+      }
+
+      button {
+        &:nth-of-type(7) {
+          display: none !important;
+        }
+        &:nth-of-type(8) {
+          display: none !important;
+        }
+        &:nth-of-type(18) {
+          display: none !important;
+        }
+        &:nth-of-type(19) {
+          display: none !important;
+        }
+      }
+
+      .bf-container {
+        height: 100%;
+      }
+
+      .bf-content {
+        height: auto;
+        max-height: 300px;
+        padding-bottom: 0px;
+      }
+      .fullscreen {
+        .bf-content {
+          max-height: 3000px;
+        }
+      }
+
+      .bf-controlbar {
+        position: relative;
+
+        .upImgBox {
+          position: absolute;
+          bottom: 13px;
+          right: 15px;
+          cursor: pointer;
+          color: var(--themeColor);
+          // display: none;
+        }
+
+        .upImgBoxNo {
+          display: none;
+        }
+      }
+
+      .zztxtRow {
+        margin-bottom: 20px;
+        border: 1px solid #ccc;
+
+        .zztxtRow1 {
+          padding: 0 20px;
+          height: 40px;
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          background-color: #e8e8e8;
+
+          .zztxtRow1_1 {
+            display: flex;
+            align-items: center;
+
+            .zztxtRow1_1_1 {
+              font-weight: 700;
+              font-size: 16px;
+              margin-right: 20px;
+            }
+
+            .zztxtRow1_1_2 {
+              display: flex;
+              align-items: center;
+              width: 580px;
+              height: 32px;
+            }
+          }
+        }
+
+        .zztxtRow1_2 {
+          display: flex;
+          align-items: center;
+
+          .anticon {
+            cursor: pointer;
+            font-size: 18px;
+          }
+
+          .zztxtRow1_2Icon {
+            position: relative;
+            top: 2px;
+          }
+
+          .zztxtRow1_2IconNo {
+            pointer-events: none;
+            opacity: 0.2;
+          }
+        }
+      }
+
+      .zztxtRowErr {
+        border-color: #ff4d4f;
+      }
+    }
+
+    // 从查看进来
+    .txtBoxLook {
+      .button-remove {
+        display: none !important;
+      }
+      .bf-controlbar {
+        pointer-events: auto !important;
+        display: flex;
+        justify-content: flex-end;
+        button {
+          display: none;
+          &:last-child {
+            display: inline-block;
+          }
+        }
+        div {
+          display: none;
+        }
+        .separator-line {
+          display: none;
+        }
+      }
+    }
+
+    .noUpThumb {
+      position: relative;
+      overflow: hidden;
+      opacity: 0;
+      transition: top 0.2s;
+      color: #ff4d4f;
+      top: -20px;
+    }
+
+    .noUpThumbAc {
+      top: -10px;
+      opacity: 1;
+    }
+
+    .bf-media .bf-image {
+      float: initial !important;
+      display: block;
+      margin: 0px auto;
+      text-align: center;
+
+      // 不让拖动放大缩小图片(会报错)
+      .bf-csize-icon {
+        display: none !important;
+      }
+
+      img {
+        max-width: 500px;
+        max-height: 300px;
+      }
+    }
+
+    // .bf-video-wrap
+    // 折叠菜单 高度问题
+    .bf-dropdown .dropdown-content {
+      display: none;
+    }
+    .bf-dropdown.active .dropdown-content {
+      display: block;
+    }
+  }
+}

+ 411 - 0
src/components/ZRichTexts/index.tsx

@@ -0,0 +1,411 @@
+import React, { useCallback, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+
+// 引入编辑器组件
+
+// 安装---npm install braft-editor --save --force
+// npm install braft-utils --save --force
+import { ContentUtils } from 'braft-utils'
+import BraftEditor from 'braft-editor'
+// 引入编辑器样式
+import 'braft-editor/dist/index.css'
+
+import classNames from 'classnames'
+import { MessageFu } from '@/utils/message'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { baseURL } from '@/utils/http'
+
+import { forwardRef, useImperativeHandle } from 'react'
+import { API_upFile } from '@/store/action/layout'
+import ZupAudio, { ZupAudioType } from '../ZupAudio'
+import { Button, Checkbox, Input } from 'antd'
+import { ArrowDownOutlined, DeleteOutlined, ArrowUpOutlined } from '@ant-design/icons'
+import MyPopconfirm from '../MyPopconfirm'
+
+export type SectionArrType = {
+  id: number
+  name: string
+  txt: any
+  fileInfo: ZupAudioType
+}
+
+type Props = {
+  check: boolean //表单校验,为fasle表示不校验
+  dirCode: string //文件的code码
+  isLook: boolean //是否是查看进来
+  ref: any //当前自己的ref,给父组件调用
+  myUrl: string //上传的api地址
+  isOne?: boolean //只显示单个富文本
+  upAudioBtnNone?: boolean //是否能上传无障碍音频
+  params?: Record<string, string | number> //一些上传的其它参数
+}
+
+function ZRichTexts(
+  { check, dirCode, isLook, myUrl, isOne = false, upAudioBtnNone = false, params }: Props,
+  ref: any
+) {
+  const [sectionArr, setSectionArr] = useState<SectionArrType[]>([
+    {
+      id: Date.now(),
+      name: '',
+      txt: BraftEditor.createEditorState(''),
+      fileInfo: { fileName: '', filePath: '' }
+    }
+  ])
+
+  // 是否按章节发布
+  const [isSection, setIsSection] = useState(false)
+
+  // 当前上传 图片
+  const nowIndexRef = useRef(0)
+
+  // 判断 富文本是否为空
+  const isTxtFlag = useMemo(() => {
+    let flag = false
+
+    // 不是按章节发布,检查第一个富文本
+    if (!isSection) {
+      const txt = sectionArr[0].txt.toText()
+      const txtHtml = sectionArr[0].txt.toHTML()
+      const txtRes = txt.replaceAll('\n', '').replaceAll(' ', '')
+      if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
+    } else {
+      // 按章节发布  检查 所有的 标题 和富文本
+      sectionArr.forEach(v => {
+        if (!v.name) flag = true
+        const txt = v.txt.toText()
+        const txtHtml = sectionArr[0].txt.toHTML()
+        const txtRes = txt.replaceAll('\n', '').replaceAll(' ', '')
+        if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
+      })
+    }
+
+    return flag
+  }, [isSection, sectionArr])
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传图片
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+
+        let type = ['image/jpeg', 'image/png']
+        // let size = 5
+        let txt = '图片只支持png、jpg和jpeg格式!'
+        // let txt2 = '图片最大支持5M!'
+
+        // 校验格式
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = ''
+          return MessageFu.warning(txt)
+        }
+
+        // 校验大小
+        // if (filesInfo.size > size * 1024 * 1024) {
+        //   e.target.value = ''
+        //   return MessageFu.warning(txt2)
+        // }
+
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append('type', 'img')
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
+
+        if (params) {
+          Object.keys(params).forEach(key => {
+            fd.append(key, `${params[key]}`)
+          })
+        }
+
+        e.target.value = ''
+
+        try {
+          const res = await API_upFile(fd, myUrl)
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            // 在光标位置插入图片
+            const newTxt = ContentUtils.insertMedias(sectionArr[nowIndexRef.current].txt, [
+              {
+                type: 'IMAGE',
+                url: baseURL + res.data.filePath
+              }
+            ])
+            const arr = [...sectionArr]
+            arr[nowIndexRef.current].txt = newTxt
+            setSectionArr(arr)
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [dirCode, myUrl, params, sectionArr]
+  )
+
+  // 让父组件调用的 回显 富文本
+  const ritxtShowFu = useCallback((val: any) => {
+    if (val) {
+      setIsSection(val.isSection || false)
+      if (val.txtArr) {
+        const arr = val.txtArr.map((v: any) => ({
+          ...v,
+          txt: BraftEditor.createEditorState(v.txt)
+        }))
+        setSectionArr(arr)
+      }
+    }
+  }, [])
+
+  // 让父组件调用的返回 富文本信息 和 表单校验 isTxtFlag为ture表示未通过校验
+  const fatherBtnOkFu = useCallback(() => {
+    const arr: any[] = []
+
+    sectionArr.forEach((v, i) => {
+      arr.push({
+        ...v,
+        txt: v.txt.toHTML()
+      })
+    })
+
+    const obj = {
+      isSection: isSection, //是否按章节发布
+      txtArr: arr
+    }
+
+    return { val: obj, flag: isTxtFlag }
+  }, [isSection, isTxtFlag, sectionArr])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    ritxtShowFu,
+    fatherBtnOkFu
+  }))
+
+  // 点击新增章节
+  const addSectionFu = useCallback(() => {
+    if (sectionArr.length >= 20) return MessageFu.warning('最多存在20个章节')
+    setSectionArr([
+      ...sectionArr,
+      {
+        id: Date.now(),
+        name: '',
+        txt: BraftEditor.createEditorState(''),
+        fileInfo: { fileName: '', filePath: '' }
+      }
+    ])
+  }, [sectionArr])
+
+  // 章节音频上传成功
+  const upSectionFu = useCallback(
+    (info: ZupAudioType, index: number) => {
+      const arr = [...sectionArr]
+      arr[index].fileInfo = info
+      setSectionArr(arr)
+    },
+    [sectionArr]
+  )
+
+  // 章节音频删除
+  const delSectionFu = useCallback(
+    (index: number) => {
+      // console.log("ppppppppp", index);
+
+      const arr = [...sectionArr]
+      arr[index].fileInfo = { fileName: '', filePath: '' }
+      setSectionArr(arr)
+    },
+    [sectionArr]
+  )
+
+  // 整个章节的删除
+  const delSectionAllFu = useCallback(
+    (id: number) => {
+      setSectionArr(sectionArr.filter(v => v.id !== id))
+    },
+    [sectionArr]
+  )
+
+  // 整个章节的位移
+  const moveSectionFu = useCallback(
+    (index: number, num: number) => {
+      const arr = [...sectionArr]
+      const temp = arr[index]
+      arr[index] = arr[index + num]
+      arr[index + num] = temp
+      setSectionArr(arr)
+    },
+    [sectionArr]
+  )
+
+  // 单个富文本是否输入完整
+  const isOneTxtFlag = useCallback(
+    (name: string, txt: any) => {
+      let flag = false
+      if (!name && isSection) flag = true
+      const txt2 = txt.toText()
+      const txtHtml = txt.toHTML()
+      const txtRes = txt2.replaceAll('\n', '').replaceAll(' ', '')
+      if (!txtRes && !txtHtml.includes('class="media-wrap')) flag = true
+      return flag
+    },
+    [isSection]
+  )
+
+  return (
+    <div className={styles.ZRichTexts}>
+      <input
+        id='upInput'
+        type='file'
+        accept='.png,.jpg,.jpeg'
+        ref={myInput}
+        onChange={e => handeUpPhoto(e)}
+      />
+
+      <div className={classNames('formRightZW', isLook ? 'formRightZWLook' : '')}>
+        {isOne ? (
+          <div></div>
+        ) : (
+          <Checkbox checked={isSection} onChange={e => setIsSection(e.target.checked)}>
+            按章节发布
+          </Checkbox>
+        )}
+
+        {isSection ? (
+          <Button hidden={isLook} type='primary' onClick={addSectionFu}>
+            新增章节
+          </Button>
+        ) : (
+          <div className='formRightZWRR'>
+            {upAudioBtnNone ? null : (
+              <ZupAudio
+                fileInfo={sectionArr[0].fileInfo}
+                upDataFu={info => upSectionFu(info, 0)}
+                delFu={() => delSectionFu(0)}
+                dirCode={dirCode}
+                myUrl={myUrl}
+                isLook={isLook}
+              />
+            )}
+
+            <div hidden={isLook}>
+              <Button
+                onClick={() => {
+                  nowIndexRef.current = 0
+                  myInput.current?.click()
+                }}
+              >
+                上传图片
+              </Button>
+            </div>
+          </div>
+        )}
+      </div>
+
+      <div className={classNames('txtBox', isLook ? 'txtBoxLook' : '')}>
+        {sectionArr.map((item, index) => (
+          <div
+            className={classNames(
+              'zztxtRow',
+              isOneTxtFlag(item.name, item.txt) && check ? 'zztxtRowErr' : ''
+            )}
+            key={item.id}
+            hidden={!isSection && index > 0}
+          >
+            {/* 顶部 */}
+            <div className='zztxtRow1' hidden={!isSection && index === 0}>
+              <div className='zztxtRow1_1'>
+                <div className='zztxtRow1_1_1'>章节 {index + 1}</div>
+                <div className='zztxtRow1_1_2'>
+                  标题:
+                  <Input
+                    readOnly={isLook}
+                    value={item.name}
+                    placeholder='请输入内容'
+                    maxLength={100}
+                    showCount
+                    style={{ width: 400 }}
+                    onChange={e => {
+                      const arr = [...sectionArr]
+                      arr[index].name = e.target.value.replace(/\s+/g, '')
+                      setSectionArr(arr)
+                    }}
+                  />
+                  &emsp;
+                  <Button
+                    hidden={isLook}
+                    onClick={() => {
+                      nowIndexRef.current = index
+                      myInput.current?.click()
+                    }}
+                  >
+                    上传图片
+                  </Button>
+                </div>
+              </div>
+              <div className='zztxtRow1_2'>
+                <ZupAudio
+                  fileInfo={item.fileInfo}
+                  upDataFu={info => upSectionFu(info, index)}
+                  delFu={() => delSectionFu(index)}
+                  dirCode={dirCode}
+                  myUrl={myUrl}
+                  isLook={isLook}
+                />
+                &emsp;
+                <div
+                  hidden={isLook}
+                  className={classNames('zztxtRow1_2Icon', index === 0 ? 'zztxtRow1_2IconNo' : '')}
+                  onClick={() => moveSectionFu(index, -1)}
+                >
+                  <ArrowUpOutlined title='上移' />
+                </div>
+                &emsp;
+                <div
+                  hidden={isLook}
+                  className={classNames(
+                    'zztxtRow1_2Icon',
+                    index === sectionArr.length - 1 ? 'zztxtRow1_2IconNo' : ''
+                  )}
+                  onClick={() => moveSectionFu(index, 1)}
+                >
+                  <ArrowDownOutlined title='下移' />
+                </div>
+                &emsp;
+                {isLook || sectionArr.length <= 1 ? null : (
+                  <MyPopconfirm
+                    txtK='删除'
+                    onConfirm={() => delSectionAllFu(item.id)}
+                    Dom={<DeleteOutlined title='删除' className='ZTbox2X' />}
+                  />
+                )}
+              </div>
+            </div>
+            {/* 主体 */}
+            <BraftEditor
+              readOnly={isLook}
+              placeholder={isLook ? '(空)' : '请输入内容'}
+              value={item.txt}
+              onChange={e => {
+                const arr = [...sectionArr]
+                arr[index].txt = e
+                setSectionArr(arr)
+              }}
+              imageControls={['remove']}
+            />
+          </div>
+        ))}
+      </div>
+      <div className={classNames('noUpThumb', check && isTxtFlag ? 'noUpThumbAc' : '')}>
+        {`请完整输入${isSection ? '标题/' : ''}正文!`}
+      </div>
+    </div>
+  )
+}
+
+export default forwardRef(ZRichTexts)

+ 39 - 0
src/components/Zexport/index.module.scss

@@ -0,0 +1,39 @@
+.Zexport {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 800px !important;
+    }
+
+    .ZexMain {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+      .ant-checkbox-wrapper {
+        margin-bottom: 10px;
+        margin-right: 10px;
+      }
+
+      .ZexBtn {
+        margin-top: 20px;
+        text-align: center;
+      }
+      .ZexTit {
+        position: relative;
+        opacity: 0;
+        pointer-events: none;
+        color: #ff4d4f;
+        margin-top: 10px;
+        text-align: center;
+        top: -10px;
+        transition: top 0.3s;
+      }
+      .ZexTitAc {
+        top: 0px;
+        opacity: 1;
+      }
+    }
+  }
+}

+ 114 - 0
src/components/Zexport/index.tsx

@@ -0,0 +1,114 @@
+import React, { useCallback, useEffect, useMemo, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Checkbox, Modal } from 'antd'
+import { useSelector } from 'react-redux'
+import store, { RootState } from '@/store'
+import { ExInfoType } from '@/store/reducer/layout'
+import { MessageFu } from '@/utils/message'
+import classNames from 'classnames'
+import dayjs from 'dayjs'
+import ExportJsonExcel from 'js-export-excel'
+
+function Zexport() {
+  const { exInfo } = useSelector((state: RootState) => state.A0Layout)
+
+  const [arr, setArr] = useState<{ label: string; value: boolean }[]>([])
+
+  useEffect(() => {
+    setArr(exInfo.arr.map(v => ({ label: v.txt, value: true })))
+  }, [exInfo.arr])
+
+  // 选中的txt集合
+  const txtAcArr = useMemo(() => {
+    return arr.filter(v => v.value).map(c => c.label)
+  }, [arr])
+
+  const btnOk = useCallback(() => {
+    if (txtAcArr.length === 0) return MessageFu.warning('最少选择一个字段')
+
+    const keyArr = exInfo.arr.filter(v => txtAcArr.includes(v.txt)).map(c => c.key)
+
+    const name = exInfo.name + dayjs(new Date()).format('YYYY-MM-DD HH:mm')
+
+    const numArr = keyArr.map(v => 10)
+
+    const resData = exInfo.data.map(v => ({
+      ...v,
+      date: v.date ? dayjs(v.date).format('YYYY-MM-DD') : ''
+    }))
+
+    // if (1 + 1 === 2) {
+    //   console.log('---', resData, keyArr, txtAcArr)
+
+    //   return
+    // }
+
+    const option = {
+      fileName: name,
+      datas: [
+        {
+          sheetData: resData,
+          sheetName: name,
+          sheetFilter: keyArr,
+          sheetHeader: txtAcArr,
+          columnWidths: numArr
+        }
+      ]
+    }
+
+    const toExcel = new ExportJsonExcel(option) //new
+    toExcel.saveExcel() //保存
+    MessageFu.success('导出成功')
+    store.dispatch({ type: 'layout/exInfo', payload: {} as ExInfoType })
+  }, [exInfo, txtAcArr])
+
+  return (
+    <Modal
+      wrapClassName={styles.Zexport}
+      destroyOnClose
+      open={true}
+      title='导出设置'
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='ZexMain'>
+        {arr.map(v => (
+          <Checkbox
+            key={v.label}
+            checked={v.value}
+            onChange={e =>
+              setArr(
+                arr.map(c => ({
+                  label: c.label,
+                  value: c.label === v.label ? !c.value : c.value
+                }))
+              )
+            }
+          >
+            {v.label}
+          </Checkbox>
+        ))}
+
+        <div className='ZexBtn'>
+          <Button type='primary' onClick={btnOk} disabled={txtAcArr.length === 0}>
+            导出
+          </Button>
+          &emsp;
+          <Button
+            onClick={() => store.dispatch({ type: 'layout/exInfo', payload: {} as ExInfoType })}
+          >
+            取消
+          </Button>
+        </div>
+        <div className={classNames('ZexTit', txtAcArr.length === 0 ? 'ZexTitAc' : '')}>
+          最少选择一个字段
+        </div>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoZexport = React.memo(Zexport)
+
+export default MemoZexport

+ 38 - 0
src/components/ZflowTable/index.module.scss

@@ -0,0 +1,38 @@
+.ZflowTable {
+  position: absolute;
+  z-index: 999;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.6);
+  padding: 50px 80px;
+  border-radius: 10px;
+
+  :global {
+    .B3Fbox {
+      width: 100%;
+      height: 100%;
+      background-color: #fff;
+      border-radius: 10px;
+      padding: 20px;
+      .B3Ftop {
+        margin-bottom: 24px;
+        border-bottom: 1px solid #ccc;
+        padding-bottom: 15px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        & > div {
+          color: var(--themeColor);
+          font-size: 18px;
+          font-weight: 700;
+          padding-left: 18px;
+        }
+      }
+      .ant-table-cell {
+        padding: 8px !important;
+      }
+    }
+  }
+}

+ 86 - 0
src/components/ZflowTable/index.tsx

@@ -0,0 +1,86 @@
+import React, { useMemo, useState } from 'react'
+import styles from './index.module.scss'
+// import { useParams } from 'react-router-dom'
+import MyTable from '@/components/MyTable'
+import { Button } from 'antd'
+import { B3FtableC } from '@/utils/tableData'
+import X2lookText from '@/pages/X_stock/X2lookText'
+import { textFu } from '@/utils/history'
+
+export type ZflowTableType = {
+  createTime: string
+  creatorId: number
+  creatorName: string
+  id: number
+  isAuto?: any
+  name: string
+  orderId: number
+  rtfOpinion: string
+  status?: any
+  updateTime: string
+  isUse: 0 | 1
+}
+type Props = {
+  tableArr: ZflowTableType[]
+  closeFu: () => void
+}
+
+function ZflowTable({ tableArr, closeFu }: Props) {
+  // const { key, id } = useParams<any>()
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '审批结果',
+        render: (item: ZflowTableType) => {
+          let txt1 = item.status === 1 ? '同意' : item.status === 2 ? '不同意' : '待处理'
+          if (item.isUse === 0) txt1 = '(空)'
+          const txt2 = item.isAuto === 1 ? '(自动)' : ''
+          return txt1 + txt2
+        }
+      },
+      {
+        title: '审批意见',
+        render: (item: ZflowTableType) => {
+          if (textFu(item.rtfOpinion)) {
+            return (
+              <Button size='small' type='text' onClick={() => setLook(textFu(item.rtfOpinion))}>
+                查看
+              </Button>
+            )
+          } else return '(空)'
+        }
+      }
+    ]
+  }, [])
+
+  //查看富文本信息
+  const [look, setLook] = useState('')
+
+  return (
+    <div className={styles.ZflowTable} hidden={tableArr.length === 0}>
+      <div className='B3Fbox'>
+        <div className='B3Ftop'>
+          <div>申请记录</div>
+          <Button onClick={closeFu}>关闭</Button>
+        </div>
+        {/* 表格 */}
+        <MyTable
+          yHeight={570}
+          classKey='ZflowTable'
+          list={tableArr}
+          columnsTemp={B3FtableC}
+          lastBtn={tableLastBtn}
+          pagingInfo={false}
+        />
+
+        {/* 查看富文本 */}
+        {look ? <X2lookText closeFu={() => setLook('')} text={look} /> : null}
+      </div>
+    </div>
+  )
+}
+
+const MemoZflowTable = React.memo(ZflowTable)
+
+export default MemoZflowTable

+ 63 - 0
src/components/ZupAudio/index.module.scss

@@ -0,0 +1,63 @@
+// 上传无障碍音频的样式
+.ZupAudio {
+  margin-left: 20px;
+  width: 180px;
+  height: 32px;
+  // position: relative;
+  // top: -4px;
+  border: 1px solid #ccc;
+  border-radius: 16px;
+  background-color: #fff;
+
+  :global {
+
+    .ZupAudio1 {
+      cursor: pointer;
+
+      display: flex;
+      justify-content: center;
+
+      .anticon-upload {
+        font-size: 20px;
+      }
+
+      .ZupAudio1_1 {
+        line-height: 15px;
+        color: #aaa5cb;
+        margin-left: 10px;
+
+        &>p {
+          &:nth-of-type(2) {
+            font-size: 10px;
+            // color: red;
+            opacity: .7;
+          }
+        }
+      }
+    }
+
+    .ZupAudio2 {
+      padding: 0 10px;
+      height: 100%;
+      display: flex;
+      align-items: center;
+
+      .anticon {
+        font-size: 18px;
+        cursor: pointer;
+
+      }
+
+      &>div {
+        height: 100%;
+        line-height: 30px;
+        width: calc(100% - 35px);
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+    }
+
+
+  }
+}

+ 140 - 0
src/components/ZupAudio/index.tsx

@@ -0,0 +1,140 @@
+import React, { useCallback, useRef } from "react";
+import styles from "./index.module.scss";
+import { MessageFu } from "@/utils/message";
+import { API_upFile } from "@/store/action/layout";
+import { fileDomInitialFu } from "@/utils/domShow";
+import { UploadOutlined, DeleteOutlined, EyeOutlined } from "@ant-design/icons";
+import store from "@/store";
+import MyPopconfirm from "../MyPopconfirm";
+
+export type ZupAudioType = {
+  fileName: string;
+  filePath: string;
+};
+
+type Props = {
+  fileInfo: ZupAudioType;
+  upDataFu: (info: ZupAudioType) => void;
+  delFu: () => void;
+  dirCode: string;
+  myUrl: string;
+  size?: number;
+  isLook?: boolean;
+};
+
+function ZupAudio({
+  size = 10,
+  isLook = false,
+  fileInfo,
+  upDataFu,
+  delFu,
+  dirCode,
+  myUrl,
+}: Props) {
+  // 上传 无障碍音频的 点击
+  const handeUpAudio = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0];
+        // console.log("-----", filesInfo);
+
+        // 校验格式
+        const type = ["audio/mpeg"];
+        if (!type.includes(filesInfo.type)) {
+          e.target.value = "";
+          return MessageFu.warning(`只支持.mp3格式!`);
+        }
+
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = "";
+          return MessageFu.warning(`最大支持${size}M!`);
+        }
+        // 创建FormData对象
+        const fd = new FormData();
+
+        fd.append("type", "audio");
+        fd.append("dirCode", dirCode);
+        fd.append("file", filesInfo);
+        e.target.value = "";
+
+        try {
+          const res = await API_upFile(fd, myUrl);
+          if (res.code === 0) {
+            MessageFu.success("上传成功!");
+            // console.log(res);
+            upDataFu(res.data);
+          }
+          fileDomInitialFu();
+        } catch (error) {
+          fileDomInitialFu();
+        }
+      }
+    },
+    [dirCode, myUrl, size, upDataFu]
+  );
+
+  // 上传附近的ref
+  const myInput = useRef<HTMLInputElement>(null);
+
+  return (
+    <div
+      className={styles.ZupAudio}
+      id="upInputAudioBox"
+      hidden={isLook && !fileInfo.filePath}
+    >
+      {/* 上传无障碍音频 */}
+      <input
+        id="upInputAudio"
+        type="file"
+        accept=".mp3"
+        onChange={(e) => handeUpAudio(e)}
+        ref={myInput}
+      />
+      {fileInfo.filePath ? (
+        <div className="ZupAudio2">
+          <div title={fileInfo.fileName}>{fileInfo.fileName}</div>
+          <EyeOutlined
+            title="预览"
+            onClick={() =>
+              store.dispatch({
+                type: "layout/lookDom",
+                payload: { src: fileInfo.filePath, type: "audio" },
+              })
+            }
+          />
+
+          {isLook ? null : (
+            <>
+              &nbsp;
+              <MyPopconfirm
+                txtK="删除"
+                onConfirm={delFu}
+                Dom={
+                  <DeleteOutlined
+                    title="删除"
+                    className="ZTbox2X"
+                    rev={undefined}
+                  />
+                }
+              />
+            </>
+          )}
+        </div>
+      ) : (
+        <div className="ZupAudio1" onClick={() => myInput.current?.click()}>
+          <UploadOutlined />
+          <div className="ZupAudio1_1">
+            <p>上传无障碍音频</p>
+            <p>支持{size}MB以下mp3格式</p>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+}
+
+const MemoZupAudio = React.memo(ZupAudio);
+
+export default MemoZupAudio;

+ 127 - 0
src/components/ZupFile/index.module.scss

@@ -0,0 +1,127 @@
+.ZupFile {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 100;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.6);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 2% 0;
+
+  :global {
+    .ZupFileBox {
+      width: 1200px;
+      height: 100%;
+      background-color: #fff;
+      border-radius: 10px;
+      position: relative;
+      overflow: hidden;
+    }
+    .ZupFilell {
+      padding: 15px 40px;
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      .ant-upload-wrapper .ant-upload-drag {
+        position: absolute;
+        left: 2%;
+        top: 50%;
+        transform: translateY(-50%);
+        width: 300px;
+        height: 300px;
+      }
+
+      .ant-upload-drag {
+        height: 160px;
+      }
+
+      .ant-upload-list {
+        position: absolute;
+        left: 350px;
+        width: 800px;
+        height: 680px;
+        overflow-y: auto;
+        padding-right: 100px;
+      }
+
+      .ant-upload-list-item-container {
+        transition: none !important;
+        height: 40px !important;
+        margin-top: 15px;
+      }
+
+      // 删除之后的延迟问题
+      .ant-upload-list-item-container.ant-upload-animate-leave {
+        margin-top: 0px;
+        height: 0px !important;
+        overflow: hidden;
+      }
+
+      .ant-upload-list-item-action {
+        // display: none !important;
+        opacity: 1 !important;
+      }
+
+      .ant-upload-list-item-actions .anticon {
+        color: #ff4d4f !important;
+        display: none;
+      }
+
+      // 自定义列表
+      .custom-upload-item {
+        position: relative;
+        display: flex;
+
+        .mySelect {
+          & > span {
+            color: #ff4d4f;
+            margin-left: 15px;
+          }
+        }
+        .mySelectIcon {
+          cursor: pointer;
+          position: absolute;
+          right: 0;
+          top: 0;
+          height: 100%;
+          width: 30px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          color: #ff4d4f;
+        }
+      }
+
+      .ant-upload-list-item-name {
+        width: 220px;
+      }
+    }
+
+    .ZupFileBtnX {
+      position: absolute;
+      bottom: 8%;
+      left: 50%;
+      transform: translateX(-50%);
+    }
+  }
+}
+
+.ZupFileMo {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 600px !important;
+    }
+
+    .ant-modal-body {
+      padding-top: 15px !important;
+      border-top: 1px solid #ccc;
+    }
+  }
+}

+ 224 - 0
src/components/ZupFile/index.tsx

@@ -0,0 +1,224 @@
+import React, { useCallback, useMemo, useState } from 'react'
+import { Upload, Button, UploadProps, Select } from 'antd'
+import { InboxOutlined } from '@ant-design/icons'
+import { MessageFu } from '@/utils/message'
+import styles from './index.module.scss'
+import { B1Xtype } from '@/pages/B_enterTibet/B1collect/data'
+import { baseURL, envFlag } from '@/utils/http'
+import { fileTypeRes } from '@/store/action/layout'
+// import { getTokenFu } from '@/utils/storage'
+import { API_C2dels } from '@/store/action/C2files'
+import MyPopconfirm from '../MyPopconfirm'
+import { DeleteOutlined } from '@ant-design/icons'
+
+const { Dragger } = Upload
+
+type Props = {
+  tableList: B1Xtype[]
+  moduleId: number
+  isShow: boolean
+  closeFu: () => void
+  succFu: (obj: any) => void
+}
+
+function ZupFile({ tableList, moduleId, isShow, closeFu, succFu }: Props) {
+  // 附件列表
+  const [fileList, setFileList] = useState<any[]>([])
+
+  // const timeRef = useRef(-1)
+
+  const FileProps: UploadProps = {
+    name: 'file',
+    // 支持上传文件夹
+    directory: true,
+    multiple: true,
+    // action: `${baseURL}${envFlag ? '' : '/api/'}cms/orderCollect/upload?tkToken=${getTokenFu()}`,
+    action: `${baseURL}${envFlag ? '' : '/api/'}cms/orderCollect/upload`,
+
+    // headers: {
+    //   token: getTokenFu()
+    // },
+    // 上传的额外参数
+    data: file => ({
+      dirCode: 'collectUpFiles',
+      isDb: 'true',
+      type: fileTypeRes(file.name),
+      moduleId,
+      isCompress: 'true'
+    }),
+    fileList,
+    onChange(info: any) {
+      setFileList([...info.fileList])
+
+      const { status } = info.file
+
+      // 检查请求状态
+      const response = info.file.response || {}
+
+      if (status !== 'uploading') {
+        if (response.code !== 0) {
+          setFileList(info.fileList.filter((v: any) => v.uid !== info.file.uid))
+          MessageFu.error(`${info.file.name} 上传失败;${response.msg}`)
+        }
+        if (response.code === 5001 || response.code === 5002) {
+          MessageFu.warning('登录超时!')
+          return false
+        }
+
+        // console.log(info.file, info.fileList);
+      }
+      if (status === 'done' && response.code === 0) {
+        // console.log("-----", info);
+        setFileList(
+          info.fileList.map((v: any) => ({
+            ...v,
+            mySelect:
+              info.file.uid === v.uid
+                ? {
+                    id: info.file.response.data.id,
+                    goodsId: undefined,
+                    name: info.file.name
+                  }
+                : v.mySelect
+          }))
+        )
+      } else if (status === 'error') {
+        MessageFu.error(`${info.file.name} 上传失败.`)
+        // 去掉列表中的失败状态文件
+        setFileList(info.fileList.filter((v: any) => v.uid !== info.file.uid))
+      }
+    },
+    // 自定义列表
+    itemRender: (originNode, file: any) => {
+      const obj = file.mySelect || { goodsId: undefined }
+
+      return (
+        <span className='custom-upload-item'>
+          {originNode}
+
+          {file.status === 'done' ? (
+            <>
+              <span className='mySelect'>
+                <Select
+                  getPopupContainer={() => document.querySelector('#A1OMain')!}
+                  style={{ width: 200 }}
+                  placeholder='请选择'
+                  value={obj.goodsId}
+                  onChange={e => selectChangeFu(e, file.uid, file.mySelect.id)}
+                  options={tableList.map(v => ({
+                    value: v.id,
+                    label: v.name
+                  }))}
+                />
+                <span hidden={obj.goodsId}>请选择藏品</span>
+              </span>
+              <MyPopconfirm
+                txtK='删除'
+                onConfirm={async () => {
+                  if (file.percent === 100) {
+                    // console.log("-----还没有发请求删除", file);
+                    const id = file.response.data.id
+                    // 已经上传完成,发请求删除
+                    const res = await API_C2dels([id])
+
+                    if (res.code === 0) {
+                      MessageFu.success('删除成功!')
+                      setFileList(fileList.filter((v: any) => v.uid !== file.uid))
+                    }
+                  } else {
+                    MessageFu.success('删除成功!')
+                    setFileList(fileList.filter((v: any) => v.uid !== file.uid))
+                  }
+                }}
+                Dom={
+                  <div className='mySelectIcon'>
+                    <DeleteOutlined />
+                  </div>
+                }
+              />
+            </>
+          ) : null}
+        </span>
+      )
+    }
+  }
+
+  // 下拉框改变
+  const selectChangeFu = useCallback(
+    (val: number, uid: string, id: number) => {
+      setFileList(
+        fileList.map(v => ({
+          ...v,
+          mySelect:
+            v.uid === uid
+              ? {
+                  ...v.mySelect,
+                  goodsId: v.mySelect.id === id ? val : v.mySelect.goodsId
+                }
+              : v.mySelect
+        }))
+      )
+    },
+    [fileList]
+  )
+
+  // 需要提交的数据
+  const arrRes = useMemo(() => {
+    return fileList.map((v: any) => v.mySelect || { goodsId: undefined })
+  }, [fileList])
+
+  // 点击提交
+  const btnOk = useCallback(async () => {
+    const obj: any = {}
+    arrRes.forEach(v => {
+      if (obj[v.goodsId]) obj[v.goodsId].push(v.id)
+      else obj[v.goodsId] = [v.id]
+    })
+
+    succFu(obj)
+    setFileList([])
+    closeFu()
+  }, [arrRes, closeFu, succFu])
+
+  return (
+    <div className={styles.ZupFile} hidden={!isShow}>
+      <div className='ZupFileBox' id='A1OMain'>
+        <div className='ZupFilell'>
+          {/* 文件夹上传区域(支持拖拽) */}
+          <Dragger {...FileProps}>
+            <p className='ant-upload-drag-icon'>
+              <InboxOutlined />
+            </p>
+            <p className='ant-upload-text'>点击或拖拽文件夹到此区域上传</p>
+            <p className='ant-upload-hint'>支持上传整个文件夹及子目录文件</p>
+          </Dragger>
+        </div>
+      </div>
+
+      {/* 关闭按钮 */}
+      <div className='ZupFileBtnX'>
+        <Button
+          type='primary'
+          onClick={btnOk}
+          disabled={fileList.length === 0 || arrRes.some(v => !v.goodsId)}
+        >
+          提交
+        </Button>
+        &emsp;
+        <Button onClick={closeFu}>关闭</Button>
+        {fileList.length ? (
+          <>
+            &emsp;
+            <MyPopconfirm
+              txtK='清空'
+              onConfirm={() => setFileList([])}
+              Dom={<Button danger>清空数据</Button>}
+            />
+          </>
+        ) : null}
+      </div>
+    </div>
+  )
+}
+
+export default ZupFile

+ 22 - 0
src/components/ZupFileTable/index.module.scss

@@ -0,0 +1,22 @@
+.ZupFileTable {
+  padding-right: 20px;
+  :global {
+    .ZZtop {
+      margin-top: 24px;
+      display: flex;
+      justify-content: space-between;
+      border-bottom: 1px solid #ccc;
+      margin-bottom: 20px;
+      & > h3 {
+        font-size: 18px;
+        font-weight: 700;
+        padding-left: 18px;
+        margin-bottom: 17px;
+        color: var(--themeColor);
+      }
+    }
+    .ant-table-cell {
+      padding: 8px !important;
+    }
+  }
+}

+ 133 - 0
src/components/ZupFileTable/index.tsx

@@ -0,0 +1,133 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { FileListType } from '../ZupTypes'
+import { Button } from 'antd'
+import { MessageFu } from '@/utils/message'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import MyTable from '../MyTable'
+import MyPopconfirm from '../MyPopconfirm'
+import { ZypFileTable } from '@/utils/tableData'
+import { baseURL } from '@/utils/http'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import { API_C2dels } from '@/store/action/C2files'
+
+type Props = {
+  listTemp: FileListType[]
+  dirCode: string //文件的code码
+  myUrl: string //请求地址
+  fromData?: any
+}
+
+function ZupFileTable({ listTemp, dirCode, myUrl, fromData }: Props) {
+  const [list, setList] = useState<FileListType[]>([])
+
+  useEffect(() => {
+    setList(listTemp)
+  }, [listTemp])
+
+  // 点击上传附件按钮
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传附件的处理函数
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+
+        // 校验大小
+        // if (filesInfo.size > size * 1024 * 1024) {
+        //   e.target.value = ''
+        //   return MessageFu.warning(`最大支持${size}M!`)
+        // }
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append('dirCode', dirCode)
+        fd.append('isDb', 'true')
+        fd.append('type', 'doc')
+        fd.append('binding', '1')
+        fd.append('file', filesInfo)
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k])
+          }
+        }
+
+        // 开启压缩图片
+        fd.append('isCompress', 'true')
+
+        e.target.value = ''
+
+        const res = await API_upFile(fd, myUrl)
+
+        try {
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            setList([...list, res.data])
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [dirCode, fromData, list, myUrl]
+  )
+
+  const userInfo = useSelector((state: RootState) => state.A0Layout.userInfo)
+
+  // 点击删除
+  const delFu = useCallback(
+    async (data: number[]) => {
+      const res = await API_C2dels(data)
+      if (res.code === 0) {
+        MessageFu.success('删除成功')
+        setList(list.filter(v => v.id !== data[0]))
+      }
+    },
+    [list]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: FileListType) => (
+          <>
+            <Button size='small' type='text'>
+              <a href={baseURL + item.filePath} download target='_blank' rel='noreferrer'>
+                下载
+              </a>
+            </Button>
+            {userInfo.id === item.creatorId ? (
+              <MyPopconfirm txtK='删除' onConfirm={() => delFu([item.id])} />
+            ) : null}
+          </>
+        )
+      }
+    ]
+  }, [delFu, userInfo.id])
+
+  return (
+    <div className={styles.ZupFileTable}>
+      <input id='upInput' type='file' accept='*' ref={myInput} onChange={e => handeUpPhoto(e)} />
+      <div className='ZZtop'>
+        <h3>归档附件</h3>
+        <Button type='primary' onClick={() => myInput.current?.click()}>
+          上传归档
+        </Button>
+      </div>
+
+      {/* 表格 */}
+      <MyTable list={list} columnsTemp={ZypFileTable} lastBtn={tableLastBtn} pagingInfo={false} />
+    </div>
+  )
+}
+
+const MemoZupFileTable = React.memo(ZupFileTable)
+
+export default MemoZupFileTable

+ 101 - 0
src/components/ZupOne/index.module.scss

@@ -0,0 +1,101 @@
+.ZupOne {
+  width: 100%;
+  height: 100%;
+  position: relative;
+
+  :global {
+    .file_upIcon {
+      color: #a6a6a6;
+      border-radius: 3px;
+      cursor: pointer;
+      font-size: 30px;
+      width: 100px;
+      height: 100px;
+      border: 1px dashed #797979;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+
+    .file_img {
+      width: 100px;
+      height: 126px;
+      position: relative;
+      margin-top: 10px;
+
+      .file_closeBox {
+        position: absolute;
+        right: -10px;
+        top: -10px;
+        z-index: 99;
+        background-color: rgba(0, 0, 0, 0.8);
+        width: 20px;
+        height: 20px;
+        border-radius: 50%;
+        font-size: 16px;
+        color: #fff;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+      }
+
+      .file_lookBox {
+        width: 100%;
+        background-color: rgba(0, 0, 0, 0.6);
+        color: #fff;
+        display: flex;
+        justify-content: space-around;
+
+        & > a {
+          color: #fff !important;
+        }
+
+        font-size: 16px;
+      }
+    }
+
+    .file_imgYuan {
+      #ImageLazy {
+        border-radius: 50%;
+        overflow: hidden;
+      }
+    }
+
+    .fileInfo {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+
+      .clearCover {
+        margin-left: 20px;
+        cursor: pointer;
+        font-size: 16px;
+      }
+
+      & > a {
+        color: black;
+      }
+    }
+
+    .fileBoxRow_r_tit {
+      height: 46px;
+      margin-top: 5px;
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+    }
+
+    .noUpThumb {
+      position: relative;
+      overflow: hidden;
+      opacity: 0;
+      transition: top 0.2s;
+      color: #ff4d4f;
+      top: -10px;
+    }
+
+    .noUpThumbAc {
+      top: 0;
+      opacity: 1;
+    }
+  }
+}

+ 295 - 0
src/components/ZupOne/index.tsx

@@ -0,0 +1,295 @@
+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'
+
+type MyTypeType = 'thumb' | 'video' | 'audio' | 'model' | 'pdf' | 'epub'
+
+// 这个组件 只处理 上传 一张图片或者 视频 音频 模型 pdf 的情况
+
+type Props = {
+  fileCheck: boolean //有没有点击过确定
+  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,给父组件调用
+  isTouXiang?: boolean //圆形头像展示
+}
+
+function ZupOne(
+  {
+    fileCheck,
+    dirCode,
+    myUrl,
+    format,
+    formatTxt,
+    checkTxt,
+    upTxt,
+    myType,
+    isLook = false,
+    fromData,
+    isTouXiang
+  }: Props,
+  ref: any
+) {
+  const [fileUrl, setFileUrl] = useState({
+    fileName: '',
+    filePath: '',
+    thumb: '' //压缩图
+  })
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传封面图
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+        // console.log("-----", filesInfo.type);
+
+        // 校验格式
+        const type = format
+
+        if (myType === 'pdf') {
+          if (!filesInfo.type.includes('pdf')) {
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
+          }
+        } else if (myType === 'epub') {
+          if (!filesInfo.name.endsWith('.epub')) {
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
+          }
+        } else {
+          if (!type.includes(filesInfo.type)) {
+            e.target.value = ''
+            return MessageFu.warning(`只支持${formatTxt}格式!`)
+          }
+        }
+
+        // 校验大小
+        // if (filesInfo.size > size * 1024 * 1024) {
+        //   e.target.value = ''
+        //   return MessageFu.warning(`最大支持${size}M!`)
+        // }
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        let myTypeRes: string = myType
+        if (['pdf', 'epub'].includes(myTypeRes)) myTypeRes = 'doc'
+        fd.append('type', myTypeRes === 'thumb' ? 'img' : myTypeRes)
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k])
+          }
+        }
+
+        // 开启压缩图片
+        fd.append('isCompress', 'true')
+
+        e.target.value = ''
+
+        try {
+          const res = await API_upFile(fd, myUrl)
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            setFileUrl(res.data)
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [dirCode, format, formatTxt, fromData, myType, myUrl]
+  )
+
+  // 让父组件调用的 回显 附件 地址
+  const setFileComFileFu = useCallback(
+    (valObj: { fileName: string; filePath: string; thumb: string }) => {
+      setFileUrl(valObj)
+    },
+    []
+  )
+
+  // 让父组件调用的返回 附件 名字和路径
+  const fileComFileResFu = useCallback(() => {
+    return fileUrl
+  }, [fileUrl])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    setFileComFileFu,
+    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'
+    else if (myType === 'epub') accept = '.epub'
+    return accept
+  }, [myType])
+
+  // 点击 预览(除了图片)
+  const lookFileNoImgFu = useCallback(
+    (type: MyTypeType) => {
+      if (type === 'pdf' || type === 'thumb') {
+        // 新窗口打开
+        window.open(baseURL + fileUrl.filePath)
+      } else if (type !== 'epub') {
+        store.dispatch({
+          type: 'layout/lookDom',
+          payload: { src: fileUrl.filePath, type }
+        })
+      }
+
+      // if (type === "pdf") {
+      // } else {
+      // }
+    },
+    [fileUrl.filePath]
+  )
+
+  return (
+    <div className={styles.ZupOne}>
+      <input
+        id='upInput'
+        type='file'
+        accept={acceptRes}
+        ref={myInput}
+        onChange={e => handeUpPhoto(e)}
+      />
+      {myType === 'thumb' ? (
+        <div
+          hidden={fileUrl.filePath !== ''}
+          className='file_upIcon'
+          onClick={() => myInput.current?.click()}
+        >
+          <PlusOutlined rev={undefined} />
+        </div>
+      ) : (
+        <Button
+          hidden={fileUrl.filePath !== ''}
+          onClick={() => myInput.current?.click()}
+          icon={<UploadOutlined rev={undefined} />}
+        >
+          上传
+        </Button>
+      )}
+
+      {/* 为图片的情况-------------- */}
+      {myType === 'thumb' ? (
+        <div
+          className={classNames('file_img', isTouXiang ? 'file_imgYuan' : '')}
+          hidden={fileUrl.filePath === ''}
+        >
+          {fileUrl ? (
+            <ImageLazy
+              width={100}
+              height={100}
+              srcBig={fileUrl.filePath}
+              src={fileUrl.thumb}
+              noLook
+            />
+          ) : null}
+
+          {/* 删除 */}
+          <div className='file_closeBox' hidden={isLook}>
+            <MyPopconfirm
+              txtK='删除'
+              onConfirm={() => setFileUrl({ fileName: '', filePath: '', thumb: '' })}
+              Dom={<CloseOutlined rev={undefined} />}
+            />
+          </div>
+
+          {/* 预览 下载 */}
+          <div className='file_lookBox' hidden={isTouXiang}>
+            <EyeOutlined
+              onClick={() =>
+                store.dispatch({
+                  type: 'layout/lookBigImg',
+                  payload: { url: baseURL + fileUrl.filePath, show: true }
+                })
+              }
+              rev={undefined}
+            />
+            <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='clearCover'
+            hidden={!fileUrl.filePath || myType === 'epub'}
+            onClick={() => lookFileNoImgFu(myType)}
+          >
+            <EyeOutlined rev={undefined} />
+          </div>
+          {/* 视频下载 */}
+          <a
+            href={baseURL + fileUrl.filePath}
+            download
+            target='_blank'
+            className='clearCover'
+            rel='noreferrer'
+          >
+            <DownloadOutlined rev={undefined} />
+          </a>
+          {/* 视频删除 */}
+
+          {isLook ? null : (
+            <MyPopconfirm
+              txtK='删除'
+              onConfirm={() => setFileUrl({ fileName: '', filePath: '', thumb: '' })}
+              Dom={<CloseOutlined className='clearCover' rev={undefined} />}
+            />
+          )}
+        </div>
+      ) : null}
+
+      <div className='fileBoxRow_r_tit' hidden={isLook}>
+        格式要求:支持{formatTxt}格式。{upTxt}
+        <br />
+        <div
+          className={classNames('noUpThumb', !fileUrl.filePath && fileCheck ? 'noUpThumbAc' : '')}
+        >
+          {checkTxt}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default forwardRef(ZupOne)

+ 216 - 0
src/components/ZupTypes/index.module.scss

@@ -0,0 +1,216 @@
+.ZupTypes {
+  padding-top: 3px;
+
+  :global {
+    .ZTboxTit {
+      margin-left: 10px;
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+      position: relative;
+      top: 4px;
+    }
+
+    .ZTbox {
+      margin-top: 20px;
+      display: flex;
+
+      .ZTbox1 {
+        position: relative;
+        top: 3px;
+        width: 60px;
+
+        & > span {
+          color: #ff4d4f;
+        }
+      }
+
+      .ZTbox2 {
+        width: calc(100% - 60px);
+        margin-left: 5px;
+        display: flex;
+        font-size: 16px;
+        align-items: center;
+
+        .ZTbox2Look {
+          margin-left: 20px;
+          cursor: pointer;
+        }
+
+        .ZTbox2Down {
+          margin-left: 15px;
+          color: black;
+        }
+
+        .ZTbox2X {
+          cursor: pointer;
+          margin-left: 15px;
+        }
+      }
+    }
+
+    // 图片
+    .ZTboxImgMain {
+      margin-top: 20px;
+
+      .ZTboxImgBox {
+        display: flex;
+
+        .ZTbox1 {
+          position: relative;
+          top: 3px;
+          width: 60px;
+
+          & > span {
+            color: #ff4d4f;
+          }
+        }
+
+        .ZTbox1Img {
+          width: calc(100% - 60px);
+          display: flex;
+          flex-wrap: wrap;
+
+          .ZTbox1ImgIcon {
+            margin-right: 20px;
+            color: #a6a6a6;
+            border-radius: 3px;
+            cursor: pointer;
+            font-size: 30px;
+            width: 100px;
+            height: 100px;
+            border: 1px dashed #797979;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+          }
+
+          .ZTbox1ImgRow {
+            // content-visibility:auto;
+            margin-right: 20px;
+            width: 100px;
+            height: 125px;
+            position: relative;
+            margin-bottom: 20px;
+            cursor: move;
+            position: relative;
+
+            // 第一张作为封面
+            .ZTbox1ImgRowCover {
+              font-size: 12px;
+              line-height: 22px;
+              position: absolute;
+              left: 0;
+              top: 0;
+              width: 100%;
+              height: 24px;
+              background-color: rgba(0, 0, 0, 0.8);
+              color: #fff;
+              text-align: center;
+              pointer-events: none;
+            }
+
+            // 修改图片名字
+            .ZTbox1ImgRowName {
+              font-size: 12px;
+              line-height: 22px;
+              position: absolute;
+              left: 0;
+              bottom: 25px;
+              width: 100%;
+              cursor: pointer;
+              height: 24px;
+              padding: 0 3px;
+              background-color: rgba(0, 0, 0, 0.8);
+              color: #fff;
+              text-align: center;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+
+              &:hover {
+                color: var(--themeColor);
+              }
+            }
+
+            .ZTbox1ImgRowIcon {
+              width: 100%;
+              background-color: rgba(0, 0, 0, 0.6);
+              color: #fff;
+              display: flex;
+              justify-content: space-around;
+              font-size: 16px;
+
+              a {
+                color: #fff !important;
+              }
+            }
+
+            .ZTbox1ImgRowX {
+              cursor: pointer;
+              position: absolute;
+              right: -10px;
+              top: -10px;
+              z-index: 99;
+              background-color: rgba(0, 0, 0, 0.8);
+              width: 20px;
+              height: 20px;
+              border-radius: 50%;
+              font-size: 16px;
+              color: #fff;
+              display: flex;
+              justify-content: center;
+              align-items: center;
+            }
+          }
+        }
+      }
+    }
+
+    .ZcheckTxt {
+      position: relative;
+      overflow: hidden;
+      opacity: 0;
+      transition: top 0.2s;
+      color: #ff4d4f;
+      top: -10px;
+      margin-bottom: 10px;
+    }
+
+    .ZcheckTxtAc {
+      top: 2px;
+      opacity: 1;
+    }
+  }
+}
+
+// 查看情况
+.ZupTypesLook {
+  :global {
+    .ZTbox1ImgRowName {
+      cursor: default !important;
+      color: #fff !important;
+    }
+
+    .ZTbox1ImgRow {
+      cursor: default !important;
+    }
+
+    .ZTbox1ImgRowX {
+      display: none !important;
+    }
+  }
+}
+
+// 修改图片名字的弹窗
+.ZupTypesMo {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ZupTypesMoBtn {
+      margin-top: 24px;
+      text-align: center;
+    }
+  }
+}

+ 618 - 0
src/components/ZupTypes/index.tsx

@@ -0,0 +1,618 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Checkbox, Input, Modal, Radio } from 'antd'
+import { forwardRef, useImperativeHandle } from 'react'
+import { baseURL } from '@/utils/http'
+import {
+  PlusOutlined,
+  CloseCircleOutlined,
+  UploadOutlined,
+  CloseOutlined,
+  DownloadOutlined,
+  EyeOutlined
+} from '@ant-design/icons'
+import { MessageFu } from '@/utils/message'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import store from '@/store'
+import ImageLazy from '../ImageLazy'
+import classNames from 'classnames'
+import MyPopconfirm from '../MyPopconfirm'
+// import { A2_APIchangeImgName } from "@/store/action/A2exhibition";
+
+const imgSize = Infinity
+
+export type FileListType = {
+  fileName: string
+  thumb?: string
+  filePath: string
+  id: number
+  type: 'model' | 'img' | 'audio' | 'video'
+  imgName: string
+  creatorId: number
+}
+
+type Props = {
+  ref: any //当前自己的ref,给父组件调用
+  selecFlag: string //筛选的字符串 模型/图片/音频/视频
+  fileCheck: boolean //有没有点击过确定
+  dirCode: string //文件的code码
+  myUrl: string //请求地址
+  isEdit: boolean //是否是编辑
+  isLook?: boolean //是不是查看
+  modelSize?: number //模型文件大小限制
+  imgLength?: number //图片数量限制
+  audioSize?: number //音频大小限制
+  videoSize?: number //视频大小限制
+  videoTit?: string //视频上传的提示语
+  isTypeShow?: boolean //默认就选中
+  isUpName?: boolean //是否能修改图片名字
+  lastImgTxt?: string //加载最后面的上传提示
+  oneIsCover?: boolean //是否将第一张作为封面
+  isOneType?: boolean //是否类型为单选
+  fromData?: any
+}
+
+function ZupTypes(
+  {
+    selecFlag,
+    fileCheck,
+    dirCode,
+    myUrl,
+    isEdit,
+    isLook = false,
+    modelSize = 500,
+    imgLength = 9,
+    audioSize = 10,
+    videoSize = 500,
+    videoTit = '',
+    isTypeShow = false,
+    isUpName = false,
+    lastImgTxt = '',
+    oneIsCover = false,
+    isOneType = false,
+    fromData
+  }: Props,
+  ref: any
+) {
+  // 筛选
+  const [typeCheck, setTypeCheck] = useState<string[]>([])
+
+  // 筛选数组
+  const typeCheckArr = useMemo(() => {
+    const arr = [
+      { label: '图片', value: 'img' },
+      { label: '模型', value: 'model' },
+      { label: '音频', value: 'audio' },
+      { label: '视频', value: 'video' }
+    ]
+
+    const arrRes = arr.filter(v => selecFlag.includes(v.label))
+    if (isTypeShow && !isEdit) {
+      setTypeCheck([arrRes[0].value])
+      // 默认就选中
+    }
+
+    return arrRes
+  }, [isEdit, isTypeShow, selecFlag])
+
+  // 上传附件的信息
+  const [fileList, setFileList] = useState({
+    model: {} as FileListType,
+    img: [] as FileListType[],
+    audio: {} as FileListType,
+    video: {} as FileListType
+  })
+
+  // 附件信息的校验,不满足返回 true
+  const fileCheckFu = useMemo(() => {
+    let flag = false
+    if (typeCheck.length === 0) flag = true
+    if (typeCheck.includes('model') && !fileList.model.id) flag = true
+    if (typeCheck.includes('img') && fileList.img.length === 0) flag = true
+    if (typeCheck.includes('audio') && !fileList.audio.id) flag = true
+    if (typeCheck.includes('video') && !fileList.video.id) flag = true
+    return flag
+  }, [fileList, typeCheck])
+
+  // 点击上传附件按钮
+  const myInput = useRef<HTMLInputElement>(null)
+
+  const [fileOneType, setFileOneType] = useState('')
+
+  useEffect(() => {
+    if (fileOneType) myInput.current?.click()
+  }, [fileOneType])
+
+  const upFileFu = useCallback((type: string) => {
+    setFileOneType('')
+    window.setTimeout(() => {
+      setFileOneType(type)
+    }, 100)
+  }, [])
+
+  // 上传附件的处理函数
+  const handeUpPhoto2 = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+
+        let anType = ['image/jpeg', 'image/png']
+        let anTit1 = '只支持png、jpg格式!'
+        let anTit2 = `最大支持${imgSize}M!`
+        let anSize = imgSize * 1024 * 1024
+
+        if (fileOneType === 'audio') {
+          anType = ['audio/mpeg']
+          anTit1 = '只支持mp3格式!'
+          anTit2 = `最大支持${audioSize}M!`
+          anSize = audioSize * 1024 * 1024
+        } else if (fileOneType === 'video') {
+          anType = ['video/mp4']
+          anTit1 = '只支持mp4格式!'
+          anTit2 = `最大支持${videoSize}M!`
+          anSize = videoSize * 1024 * 1024
+        } else if (fileOneType === 'model') {
+          anType = ['']
+          anTit1 = '只支持4dage格式!'
+          anTit2 = `最大支持${modelSize}M!`
+          anSize = modelSize * 1024 * 1024
+        }
+
+        // 校验格式
+        if (fileOneType !== 'model') {
+          if (!anType.includes(filesInfo.type)) {
+            e.target.value = ''
+            return MessageFu.warning(anTit1)
+          }
+        } else {
+          if (!filesInfo.name.includes('.4dage')) {
+            e.target.value = ''
+            return MessageFu.warning(anTit1)
+          }
+        }
+
+        // 校验大小
+        if (filesInfo.size > anSize) {
+          e.target.value = ''
+          return MessageFu.warning(anTit2)
+        }
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        fd.append('type', fileOneType)
+        fd.append('dirCode', dirCode)
+        fd.append('isDb', 'true')
+
+        //初始图片 fileName为:未命名
+        if (isUpName) {
+          fd.append('isDefaultName', 'false')
+        }
+
+        fd.append('file', filesInfo)
+
+        if (fileOneType === 'img') {
+          // 开启压缩图片
+          fd.append('isCompress', 'true')
+        }
+
+        if (fromData) {
+          for (const k in fromData) {
+            if (fromData[k]) fd.append(k, fromData[k])
+          }
+        }
+
+        e.target.value = ''
+
+        const res = await API_upFile(fd, myUrl)
+
+        try {
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            if (fileOneType === 'img')
+              setFileList({
+                ...fileList,
+                img: [...fileList.img, { ...res.data, imgName: '未命名' }]
+              })
+            else setFileList({ ...fileList, [fileOneType]: res.data })
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [audioSize, dirCode, fileList, fileOneType, fromData, isUpName, modelSize, myUrl, videoSize]
+  )
+
+  // 附件图片的拖动
+  const [dragImg, setDragImg] = useState<any>(null)
+
+  const handleDragOver = useCallback(
+    (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
+      if (isLook) return
+      e.dataTransfer.dropEffect = 'move'
+    },
+    [isLook]
+  )
+
+  const handleDragEnter = useCallback(
+    (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
+      if (isLook) return
+
+      e.dataTransfer.effectAllowed = 'move'
+      if (item === dragImg) return
+      const newItems = [...fileList.img] //拷贝一份数据进行交换操作。
+      const src = newItems.indexOf(dragImg) //获取数组下标
+      const dst = newItems.indexOf(item)
+      newItems.splice(dst, 0, ...newItems.splice(src, 1)) //交换位置
+      setFileList({ ...fileList, img: newItems })
+    },
+    [dragImg, fileList, isLook]
+  )
+
+  // 删除某一张图片
+  const delImgListFu = useCallback(
+    (id: number) => {
+      const newItems = fileList.img.filter(v => v.id !== id)
+      setFileList({ ...fileList, img: newItems })
+    },
+    [fileList]
+  )
+
+  // 模型 音频 视频 的 dom
+  const resOneDivDom = useCallback(
+    (type: 'model' | 'audio' | 'video') => {
+      const dom = (
+        <div className='ZTbox' hidden={!typeCheck.includes(type)}>
+          <div className='ZTbox1' hidden={isOneType}>
+            <span> </span>
+            {type === 'model' ? '模型' : type === 'audio' ? '音频' : '视频'}:
+          </div>
+          {fileList[type].id ? (
+            <div className='ZTbox2'>
+              <div className='ZTbox2Name'>{fileList[type].fileName}</div>
+
+              <div
+                className='ZTbox2Look'
+                onClick={() =>
+                  store.dispatch({
+                    type: 'layout/lookDom',
+                    payload: { src: fileList[type].filePath, type }
+                  })
+                }
+              >
+                <EyeOutlined rev={undefined} />
+              </div>
+
+              <a
+                href={baseURL + fileList[type].filePath}
+                download
+                target='_blank'
+                className='ZTbox2Down'
+                rel='noreferrer'
+              >
+                <DownloadOutlined rev={undefined} />
+              </a>
+
+              <MyPopconfirm
+                txtK='删除'
+                onConfirm={() => setFileList({ ...fileList, [type]: {} as FileListType })}
+                Dom={<CloseCircleOutlined className='ZTbox2X' rev={undefined} />}
+              />
+            </div>
+          ) : (
+            <>
+              <Button onClick={() => upFileFu(type)} icon={<UploadOutlined rev={undefined} />}>
+                上传
+              </Button>
+
+              <div className='ZTboxTit'>
+                {type === 'model'
+                  ? `仅支持4dage格式的模型文件,大小不能超过${modelSize}M。`
+                  : type === 'audio'
+                  ? `仅支持mp3格式的音频文件,大小不得超过${audioSize}MB。`
+                  : `仅支持mp4格式的视频文件,大小不得超过${videoSize}MB。${videoTit}`}
+              </div>
+            </>
+          )}
+        </div>
+      )
+      return dom
+    },
+    [audioSize, fileList, isOneType, modelSize, typeCheck, upFileFu, videoSize, videoTit]
+  )
+
+  // ------------让父组件调用的 回显
+  const setFileComFileFu = useCallback((info: any) => {
+    if (info.type) setTypeCheck(info.type.split(','))
+
+    if (info.fileList && info.fileList.length) {
+      const data: FileListType[] = info.fileList
+      const obj = {
+        model: {} as FileListType,
+        img: [] as FileListType[],
+        audio: {} as FileListType,
+        video: {} as FileListType
+      }
+
+      data.forEach(v => {
+        if (v.type === 'img') {
+          obj.img.push({ ...v, imgName: v.fileName })
+        } else obj[v.type!] = v
+      })
+      setFileList(obj)
+    }
+  }, [])
+
+  // --------------让父组件调用的返回 附件 信息
+  const fileComFileResFu = useCallback(() => {
+    let coverUrl = ''
+    let coverPcUrl = ''
+    const fileIds = []
+    if (fileList.model.id && typeCheck.includes('model')) fileIds.push(fileList.model.id)
+    if (fileList.audio.id && typeCheck.includes('audio')) fileIds.push(fileList.audio.id)
+    if (fileList.video.id && typeCheck.includes('video')) fileIds.push(fileList.video.id)
+    if (typeCheck.includes('img')) {
+      fileList.img.forEach((v, i) => {
+        if (v.id) {
+          fileIds.push(v.id)
+          if (oneIsCover && i === 0) {
+            // 返回 第一张图的url 作为封面
+            coverUrl = v.thumb || v.filePath
+            coverPcUrl = v.filePath
+          }
+        }
+      })
+    }
+    return {
+      sonType: typeCheck,
+      sonFileIds: fileIds,
+      sonIsOk: fileCheckFu,
+      coverUrl,
+      coverPcUrl
+    }
+  }, [
+    fileCheckFu,
+    fileList.audio.id,
+    fileList.img,
+    fileList.model.id,
+    fileList.video.id,
+    oneIsCover,
+    typeCheck
+  ])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    setFileComFileFu,
+    fileComFileResFu
+  }))
+
+  // 修改图片名称
+  const [isNameChange, setIsNameChange] = useState({
+    id: 0,
+    oldName: '',
+    newName: ''
+  })
+
+  // 关闭弹窗
+  const isNameChangeXFu = useCallback(() => {
+    setIsNameChange({ id: 0, oldName: '', newName: '' })
+  }, [])
+
+  // 点击图片名字-出来弹窗
+  const isNameChangeFu = useCallback(
+    (item: FileListType) => {
+      if (isLook) return
+      setIsNameChange({ id: item.id, oldName: item.imgName, newName: '' })
+    },
+    [isLook]
+  )
+
+  // 修改完这点击 确定修改
+  const isNameChangeOkFu = useCallback(async () => {
+    // if (!isNameChange.newName) return MessageFu.warning("图片名不能为空!");
+    // const res = await A2_APIchangeImgName({
+    //   id: isNameChange.id,
+    //   fileName: isNameChange.newName,
+    // });
+    // if (res.code === 0) {
+    //   MessageFu.success("修改图片名成功!");
+    //   setFileList({
+    //     ...fileList,
+    //     img: fileList.img.map((v) => ({
+    //       ...v,
+    //       imgName: v.id === isNameChange.id ? isNameChange.newName : v.imgName,
+    //     })),
+    //   });
+    //   isNameChangeXFu();
+    // }
+  }, [])
+  //
+
+  return (
+    <div className={classNames(styles.ZupTypes, isLook ? styles.ZupTypesLook : '')}>
+      <input
+        id='upInput'
+        type='file'
+        accept={
+          fileOneType === 'img'
+            ? '.png,.jpg,.jpeg'
+            : fileOneType === 'audio'
+            ? '.mp3'
+            : fileOneType === 'model'
+            ? '.4dage'
+            : '.mp4'
+        }
+        ref={myInput}
+        onChange={e => handeUpPhoto2(e)}
+      />
+      <div hidden={isOneType}>
+        {isOneType ? (
+          <>
+            {typeCheckArr.map(v => (
+              <Radio
+                key={v.label}
+                onClick={() => setTypeCheck([v.value])}
+                checked={v.value === typeCheck[0]}
+              >
+                {v.label}
+              </Radio>
+            ))}
+          </>
+        ) : (
+          <Checkbox.Group
+            options={typeCheckArr}
+            value={typeCheck}
+            onChange={e => setTypeCheck(e as string[])}
+          />
+        )}
+      </div>
+
+      {/* -----------模型 */}
+      {resOneDivDom('model')}
+
+      {/* -----------图片 */}
+      <div className='ZTboxImgMain' hidden={!typeCheck.includes('img')}>
+        <div className='ZTboxImgBox'>
+          <div className='ZTbox1' hidden={isOneType}>
+            <span> </span> 图片:
+          </div>
+
+          <div className='ZTbox1Img'>
+            <div
+              hidden={(!!fileList.img.length && fileList.img.length >= imgLength) || isLook}
+              className='ZTbox1ImgIcon'
+              onClick={() => upFileFu('img')}
+            >
+              <PlusOutlined rev={undefined} />
+            </div>
+            {fileList.img.map((v, i) => (
+              <div
+                className='ZTbox1ImgRow'
+                key={v.id}
+                draggable='true'
+                onDragStart={() => setDragImg(v)}
+                onDragOver={e => handleDragOver(e, v)}
+                onDragEnter={e => handleDragEnter(e, v)}
+                onDragEnd={() => setDragImg(null)}
+              >
+                {v.thumb || v.filePath ? (
+                  <ImageLazy noLook={true} width={100} height={100} src={v.thumb || v.filePath} />
+                ) : null}
+
+                {oneIsCover && i === 0 ? <div className='ZTbox1ImgRowCover'>封面</div> : null}
+
+                {/* 修改图片名字 */}
+                {isUpName ? (
+                  <div
+                    title={v.imgName}
+                    className='ZTbox1ImgRowName'
+                    onClick={() => isNameChangeFu(v)}
+                  >
+                    {v.imgName}
+                  </div>
+                ) : null}
+
+                <div className='ZTbox1ImgRowIcon'>
+                  <EyeOutlined
+                    onClick={() =>
+                      store.dispatch({
+                        type: 'layout/lookBigImg',
+                        payload: {
+                          url: baseURL + v.filePath,
+                          show: true
+                        }
+                      })
+                    }
+                    rev={undefined}
+                  />
+                  <a href={baseURL + v.filePath} download target='_blank' rel='noreferrer'>
+                    <DownloadOutlined rev={undefined} />
+                  </a>
+                </div>
+
+                <MyPopconfirm
+                  txtK='删除'
+                  onConfirm={() => delImgListFu(v.id!)}
+                  Dom={<CloseOutlined className='ZTbox1ImgRowX' rev={undefined} />}
+                />
+              </div>
+            ))}
+            {fileList.img.length === 0 && oneIsCover && isLook ? (
+              <span style={{ position: 'relative', top: -2 }}>(空)</span>
+            ) : null}
+          </div>
+        </div>
+
+        <div className='ZTboxTit' hidden={isLook}>
+          {fileList.img.length && fileList.img.length >= 2 ? (
+            <>
+              按住鼠标可拖动图片调整顺序。
+              <br />
+            </>
+          ) : null}
+          支持png、jpg的图片格式;最多支持{imgLength}张。
+          {lastImgTxt}
+        </div>
+      </div>
+
+      {/* -----------音频 */}
+      {resOneDivDom('audio')}
+
+      {/* -----------视频 */}
+      {resOneDivDom('video')}
+
+      {/* 最后的提示 */}
+      <div className={classNames('ZcheckTxt', fileCheck && fileCheckFu ? 'ZcheckTxtAc' : '')}>
+        请上传附件!
+      </div>
+
+      {/* 点击修改名字出来的弹窗 */}
+      {isNameChange.id ? (
+        <Modal
+          wrapClassName={styles.ZupTypesMo}
+          open={true}
+          title='修改展品图片名称'
+          footer={
+            [] // 设置footer为空,去掉 取消 确定默认按钮
+          }
+        >
+          <br />
+          <div className='ZupTypesMoRow'>
+            <strong>当前名:</strong>
+            {isNameChange.oldName}
+          </div>
+          <div className='ZupTypesMoRow'>
+            <br />
+            <strong>修改为:</strong>
+            <Input
+              style={{ width: 400 }}
+              placeholder='请输入图片名'
+              maxLength={50}
+              showCount
+              value={isNameChange.newName}
+              onChange={e => {
+                setIsNameChange({
+                  ...isNameChange,
+                  newName: e.target.value.replace(/\s+/g, '')
+                })
+              }}
+            />
+          </div>
+
+          <div className='ZupTypesMoBtn'>
+            <Button onClick={isNameChangeXFu}>取消</Button>
+            &emsp;
+            <Button type='primary' onClick={isNameChangeOkFu}>
+              修改
+            </Button>
+          </div>
+        </Modal>
+      ) : null}
+    </div>
+  )
+}
+
+export default forwardRef(ZupTypes)

+ 35 - 0
src/index.tsx

@@ -0,0 +1,35 @@
+// import 'default-passive-events';
+
+import App from './App'
+import store from './store/index'
+
+import { Provider } from 'react-redux'
+import { createRoot } from 'react-dom/client'
+
+import { ConfigProvider } from 'antd'
+
+// 兼容360浏览器
+import { StyleProvider, legacyLogicalPropertiesTransformer } from '@ant-design/cssinjs'
+
+import 'dayjs/locale/zh-cn'
+import locale from 'antd/locale/zh_CN'
+
+const container = document.getElementById('root') as HTMLElement
+const root = createRoot(container)
+
+root.render(
+  <ConfigProvider
+    locale={locale}
+    theme={{
+      token: {
+        colorPrimary: ' #9f1927'
+      }
+    }}
+  >
+    <Provider store={store}>
+      <StyleProvider hashPriority='high' transformers={[legacyLogicalPropertiesTransformer]}>
+        <App />
+      </StyleProvider>
+    </Provider>
+  </ConfigProvider>
+)

+ 4 - 0
src/pages/A2_query/A22antique/index.module.scss

@@ -0,0 +1,4 @@
+// .A22antique {
+//   :global {
+//   }
+// }

+ 9 - 0
src/pages/A2_query/A22antique/index.tsx

@@ -0,0 +1,9 @@
+import React from 'react'
+import C1ledger from '@/pages/A3_ledger/C1ledger'
+function A22antique() {
+  return <C1ledger />
+}
+
+const MemoA22antique = React.memo(A22antique)
+
+export default MemoA22antique

+ 4 - 0
src/pages/A2_query/A23media/index.module.scss

@@ -0,0 +1,4 @@
+// .A23media {
+//   :global {
+//   }
+// }

+ 9 - 0
src/pages/A2_query/A23media/index.tsx

@@ -0,0 +1,9 @@
+import C2files from '@/pages/C_goodsManage/C2files'
+import React from 'react'
+function A23media() {
+  return <C2files />
+}
+
+const MemoA23media = React.memo(A23media)
+
+export default MemoA23media

+ 50 - 0
src/pages/A3_ledger/A32Routing/A32set/index.module.scss

@@ -0,0 +1,50 @@
+.A32set {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ant-modal {
+      width: 800px !important;
+    }
+    .A32Smain {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+
+      .A32Sbox {
+        max-height: 500px;
+        overflow-y: auto;
+        .A32Srow {
+          margin-bottom: 20px;
+          .A32Srow1 {
+            position: relative;
+            font-size: 14px;
+            font-weight: 700;
+            padding-left: 20px;
+            margin-bottom: 20px;
+            &::before {
+              content: '';
+              position: absolute;
+              top: 0;
+              left: 0;
+              width: 5px;
+              height: 22px;
+              border-radius: 4px;
+              background-color: var(--themeColor);
+            }
+          }
+          .A32Srow2Check {
+            margin-right: 10px;
+            margin-bottom: 10px;
+          }
+        }
+      }
+
+      .A32Sbtn {
+        margin-top: 15px;
+        text-align: center;
+      }
+    }
+  }
+}

+ 94 - 0
src/pages/A3_ledger/A32Routing/A32set/index.tsx

@@ -0,0 +1,94 @@
+import React, { useCallback, useEffect, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Checkbox, Modal } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A32treeData } from '../data'
+import { A32_APIset } from '@/store/action/A32Routing'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  filterId: string[]
+  succFu: () => void
+  closeFu: () => void
+}
+
+function A32set({ filterId, succFu, closeFu }: Props) {
+  // 多选框变化
+  const [checkArr, setCheckArr] = useState<string[]>([])
+  useEffect(() => {
+    setCheckArr(filterId)
+  }, [filterId])
+
+  const checkFu = useCallback(
+    (id: string) => {
+      if (checkArr.includes(id)) setCheckArr(checkArr.filter(v => v !== id))
+      else setCheckArr([...checkArr, id])
+    },
+    [checkArr]
+  )
+
+  // 点击保存
+  const btnOk = useCallback(async () => {
+    const obj = {
+      id: '50000',
+      type: 'goodsAccount',
+      rtf: JSON.stringify(checkArr),
+      name: '分账设置',
+      ancestor: '0',
+      parentId: '0',
+      sort: 1
+    }
+    const res = await A32_APIset(obj)
+
+    if (res.code === 0) {
+      MessageFu.success('设置成功')
+      succFu()
+      closeFu()
+    }
+  }, [checkArr, closeFu, succFu])
+
+  return (
+    <Modal
+      wrapClassName={styles.A32set}
+      destroyOnClose
+      open={true}
+      title='分账设置'
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='A32Smain'>
+        <div className='A32Sbox'>
+          {A32treeData.map(item1 => (
+            <div className='A32Srow' key={item1.name}>
+              <div className='A32Srow1'>{item1.name}</div>
+              <div className='A32Srow2'></div>
+              {item1.son.map(item2 => (
+                <Checkbox
+                  className='A32Srow2Check'
+                  key={item2.id}
+                  checked={checkArr.includes(item2.id)}
+                  onChange={() => checkFu(item2.id)}
+                >
+                  {item2.name}
+                </Checkbox>
+              ))}
+            </div>
+          ))}
+        </div>
+
+        <div className='A32Sbtn'>
+          <Button type='primary' onClick={btnOk}>
+            保存
+          </Button>
+          &emsp;
+          <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+        </div>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoA32set = React.memo(A32set)
+
+export default MemoA32set

+ 32 - 0
src/pages/A3_ledger/A32Routing/A32table/index.module.scss

@@ -0,0 +1,32 @@
+.A32table {
+  :global {
+    .ant-table-body {
+      overflow-y: auto !important;
+      overflow-y: overlay !important;
+    }
+    .ant-table-cell {
+      padding: 6px !important;
+      min-width: 100px;
+    }
+    .NODATA {
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      color: #666666;
+      font-size: 16px;
+      // img {
+      //   width: 150px;
+      // }
+      & > p {
+        margin-bottom: 10px;
+      }
+      div {
+        width: 80%;
+        margin-top: 8%;
+        display: flex;
+        justify-content: space-around;
+      }
+    }
+  }
+}

+ 192 - 0
src/pages/A3_ledger/A32Routing/A32table/index.tsx

@@ -0,0 +1,192 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
+import { selectObj } from '@/utils/select'
+import { Button, Table } from 'antd'
+import classNames from 'classnames'
+import { openGoodsInfoFu, resJiLianFu } from '@/utils/history'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import ImageLazy from '@/components/ImageLazy'
+import { statusCollectObj, statusStorageObj } from '@/utils/tableData'
+import { FourTableType } from '@/pages/B_enterTibet/B1collect/type'
+import { baseURL } from '@/utils/http'
+
+type Props = {
+  ordrerId?: number
+  menuId: number
+  pageNum: number
+  pageSize: number
+  pageChangeFu: (pageNum: number, pageSize: number) => void
+  advanced: boolean
+}
+
+function A32table({ pageNum, pageSize, pageChangeFu, ordrerId, menuId, advanced }: Props) {
+  // 打开侧边栏
+  const [cathet, setCathet] = useState(0)
+
+  // 从仓库拿数据
+  const tableInfo = useSelector((state: RootState) => state.A32Routing.tableInfo)
+
+  const columns = useMemo(() => {
+    const arr: any = [
+      {
+        title: '藏品编号',
+        width: 100,
+        fixed: 'left',
+        render: (item: FourTableType) => {
+          return (
+            <span
+              onClick={() => setCathet(item.id)}
+              className={classNames('D1GtNum', item.id === cathet ? 'D1GtNumAc' : '')}
+            >
+              {item.num || '(空)'}
+            </span>
+          )
+        }
+      },
+      {
+        title: '封面图',
+        width: 60,
+        render: (item: FourTableType) => (
+          <div className='tableImgAuto'>
+            <ImageLazy
+              width={60}
+              height={60}
+              srcBig={item.thumbPc || item.filePath}
+              src={item.thumb}
+            />
+          </div>
+        )
+      },
+      {
+        title: '编号类型',
+        render: (item: FourTableType) => item.numName || '(空)'
+      },
+      {
+        title: '藏品名称',
+        render: (item: FourTableType) => item.name || '(空)'
+      },
+      {
+        title: '文物级别',
+        render: (item: FourTableType) => item.dictLevel || '(空)'
+      },
+      {
+        title: '文物类别',
+        render: (item: FourTableType) => resJiLianFu(item.dictType)
+      },
+      {
+        title: '年代',
+        render: (item: FourTableType) =>
+          item.dictAge === '其他' ? '其他' : resJiLianFu(item.dictAge)
+      },
+      {
+        title: '质地',
+        render: (item: FourTableType) => resJiLianFu(item.dictTexture3)
+      },
+      {
+        title: '完残程度',
+        render: (item: FourTableType) => resJiLianFu(item.dictTorn)
+      },
+      {
+        title: '来源',
+        render: (item: FourTableType) => resJiLianFu(item.source)
+      },
+      {
+        title: '',
+        render: (item: FourTableType) =>
+          Reflect.get(statusCollectObj, item.statusCollect) || '(空)'
+      },
+      {
+        title: '库存状态',
+        render: (item: FourTableType) =>
+          Reflect.get(statusStorageObj, item.statusStorage) || '(空)'
+      },
+      {
+        title: '展览状态',
+        render: (item: FourTableType) =>
+          (selectObj['展览状态'].find(v => v.value === item.accountIndoor) || {}).label || '(空)'
+      },
+      {
+        title: '创建日期',
+        render: (item: FourTableType) => item.createTime
+      },
+
+      {
+        title: '操作',
+        width: 80,
+        fixed: 'right',
+        render: (item: FourTableType) => {
+          return (
+            <Button
+              size='small'
+              type='text'
+              onClick={() => openGoodsInfoFu(item.id, 602, '', item.id)}
+            >
+              查看
+            </Button>
+          )
+        }
+      }
+    ]
+
+    return arr
+  }, [cathet])
+
+  useEffect(() => {
+    const dom = document.querySelector('.ant-table-body') as HTMLDivElement
+    if (dom) {
+      dom.style.height = (advanced ? 550 : 605) + 'px'
+    }
+  }, [advanced])
+
+  // 空数据列表会闪一下的问题
+  const timerrRef = useRef(-1)
+  const [isLoding, setIsLoding] = useState(false)
+  useEffect(() => {
+    timerrRef.current = window.setTimeout(() => {
+      setIsLoding(true)
+    }, 500)
+    return () => {
+      clearTimeout(timerrRef.current)
+    }
+  }, [])
+
+  return (
+    <div className={styles.A32table}>
+      <Table
+        locale={{
+          emptyText: (
+            <div
+              className='NODATA'
+              style={{ height: advanced ? 550 : 605, opacity: isLoding ? '1' : '0' }}
+            >
+              <img src={baseURL + `/baseData/staImg/build.png`} alt='' />
+              <p>暂无相关搜索结果,请更换关键字搜索</p>
+            </div>
+          )
+        }}
+        scroll={{ y: advanced ? 550 : 605, x: 'max-content' }}
+        dataSource={tableInfo.list}
+        columns={columns}
+        rowKey='id'
+        pagination={{
+          showQuickJumper: true,
+          position: ['bottomCenter'],
+          showSizeChanger: true,
+          current: pageNum,
+          pageSize: pageSize,
+          total: tableInfo.total,
+          onChange: (pageNum, pageSize) => pageChangeFu(pageNum, pageSize)
+        }}
+      />
+
+      {/* 打开侧边栏 */}
+      <Y1cathet sId={cathet} menuId={menuId} closeFu={() => setCathet(0)} />
+    </div>
+  )
+}
+
+const MemoA32table = React.memo(A32table)
+
+export default MemoA32table

+ 33 - 0
src/pages/A3_ledger/A32Routing/data.ts

@@ -0,0 +1,33 @@
+import { cascaderObjFu } from '@/utils/history'
+import { selectObj } from '@/utils/select'
+
+export type A32treeDataType = {
+  name: string
+  show: boolean
+  key: string
+  son: { id: string; name: string }[]
+}
+
+export const A32treeData: A32treeDataType[] = [
+  {
+    name: '馆藏与非馆藏',
+    show: true,
+    key: 'accountType',
+    son: selectObj['入藏去向'].map(v => ({ id: v.value, name: v.label }))
+  },
+  {
+    name: '类别分账',
+    show: true,
+    key: 'dictType',
+    son: cascaderObjFu()['文物类别']
+  },
+  {
+    name: '展品分账',
+    show: true,
+    key: 'accountIndoor',
+    son: [
+      { id: '6', name: '馆外' },
+      { id: '5', name: '馆内' }
+    ]
+  }
+]

+ 106 - 0
src/pages/A3_ledger/A32Routing/index.module.scss

@@ -0,0 +1,106 @@
+.A32Routing {
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 10px 24px 0;
+  position: relative;
+  :global {
+    .C4tit {
+      color: #999;
+      margin-left: 10px;
+      font-size: 14px;
+      font-weight: 400;
+    }
+    .A32top {
+      display: flex;
+      justify-content: space-between;
+    }
+
+    .A32main {
+      margin-top: 15px;
+      height: calc(100% - 47px);
+      display: flex;
+      .A32left {
+        width: 200px;
+        margin-right: 15px;
+        user-select: none;
+
+        & > div {
+          margin-top: 10px;
+          height: calc(100% - 45px);
+          overflow-y: auto;
+          font-size: 14px;
+          padding-bottom: 20px;
+          .A32llRow {
+            .A32llRow1 {
+              font-weight: 700;
+              height: 38px;
+              line-height: 38px;
+              cursor: pointer;
+              & > span {
+                display: inline-block;
+                transition: all 0.3s;
+              }
+            }
+            .A32llRow2 {
+              padding-left: 30px;
+              height: 0;
+              overflow: hidden;
+              word-wrap: break-word;
+              .A32llRow2Row {
+                margin-bottom: 2px;
+                & > span {
+                  cursor: pointer;
+                  display: inline-block;
+                  padding: 3px 4px;
+                  border-radius: 4px;
+                  &:hover {
+                    background-color: #f5f5f5;
+                  }
+                }
+                .A32llRow2RowAc {
+                  background-color: #b4bfbc !important;
+                }
+              }
+            }
+            .A32llRow2Show {
+              height: auto;
+              overflow: visible;
+            }
+          }
+        }
+      }
+      .A32right {
+        width: calc(100% - 215px);
+        height: 100%;
+        padding-top: 10px;
+        .A32rrTop {
+          display: flex;
+          flex-wrap: wrap;
+          & > div {
+            margin-right: 15px;
+            display: flex;
+            align-items: center;
+            position: relative;
+            width: 8.7%;
+            margin-bottom: 22px;
+            .ant-input {
+              width: 100%;
+            }
+            .ant-select {
+              width: 100%;
+            }
+            & > span {
+              position: absolute;
+              top: -18px;
+              left: 0;
+              pointer-events: none;
+            }
+            &:nth-of-type(10n) {
+              margin-right: 0;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 477 - 0
src/pages/A3_ledger/A32Routing/index.tsx

@@ -0,0 +1,477 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, Input, Select } from 'antd'
+import { MessageFu } from '@/utils/message'
+import { resJiLianFu } from '@/utils/history'
+import { statusCollectObj, statusStorageObj } from '@/utils/tableData'
+import { useDispatch } from 'react-redux'
+import { C1baseFormData, C1baseFormData2, C1InputKeyArr1, C1InputKeyArr2 } from '../C1ledger/data'
+import { A32_APIgetList } from '@/store/action/A32Routing'
+import { C1InputKeyType } from '../C1ledger/type'
+import C8recycleBin from '../ComPage/C8recycleBin'
+import C4import from '../ComPage/C4import'
+import { A32treeData, A32treeDataType } from './data'
+import { CaretDownOutlined } from '@ant-design/icons'
+import classNames from 'classnames'
+import { Z1_APIgetInfo } from '@/store/action/Z1dict'
+import A32set from './A32set'
+import store from '@/store'
+import A32table from './A32table'
+import { selectObj } from '@/utils/select'
+import { FourTableType } from '@/pages/B_enterTibet/B1collect/type'
+// import { EXPORT_WORD_ENUM, exportWordHandler } from '@/utils/exportTemplates'
+
+function A32Routing() {
+  const dispatch = useDispatch()
+
+  // -------------左侧树-----------------------
+  const [value, setValue] = useState('')
+  const [value2, setValue2] = useState('')
+
+  const timeRef = useRef(-1)
+
+  useEffect(() => {
+    clearTimeout(timeRef.current)
+    window.setTimeout(() => {
+      setValue2(value)
+    }, 500)
+  }, [value])
+
+  // 过滤的id
+  const [filterId, setFilterId] = useState<string[]>([])
+
+  const getFilterFu = useCallback(async () => {
+    const res = await Z1_APIgetInfo('50000')
+    if (res.code === 0) {
+      const data = res.data
+      if (data.rtf) {
+        const val = JSON.parse(data.rtf)
+        setFilterId([...val])
+      }
+    }
+  }, [])
+
+  useEffect(() => {
+    getFilterFu()
+  }, [getFilterFu])
+
+  const treeKey = useRef('')
+  const [treeAc, setTreeAc] = useState<string | null>('')
+  const treeAcRef = useRef('')
+  useEffect(() => {
+    if (treeAc) treeAcRef.current = treeAc
+  }, [treeAc])
+
+  const [treeArr, setTreeArr] = useState<A32treeDataType[]>([])
+
+  useEffect(() => {
+    let arr = A32treeData
+    // 顶部过滤
+    arr = arr.map(v => ({
+      ...v,
+      show: true,
+      son: (v.son || []).filter(c => c.name.includes(value2) && filterId.includes(c.id))
+    }))
+
+    const arrPu: { id: string; name: string; key: string }[] = []
+
+    arr.forEach(v => {
+      if (v.son && v.son.length) {
+        v.son.forEach(c => {
+          arrPu.push({ ...c, key: v.key })
+        })
+      }
+    })
+
+    if (flagRef.current) {
+      if (arrPu && arrPu.length) {
+        flagRef.current = false
+
+        // 是不是原来选中了 后面取消了
+        if (!filterId.includes(treeAcRef.current)) {
+          setTreeAc(arrPu[0].id)
+          treeKey.current = arrPu[0].key
+        }
+      } else {
+        setTreeAc(null)
+      }
+    }
+
+    setTreeArr(arr)
+  }, [filterId, value2])
+
+  const flagRef = useRef(true)
+
+  useEffect(() => {}, [treeArr])
+
+  // 点击展开的收起
+  const showTreeArrFu = useCallback(
+    (name: string) => {
+      setTreeArr(
+        treeArr.map(v => ({
+          ...v,
+          show: v.name === name ? !v.show : v.show
+        }))
+      )
+    },
+    [treeArr]
+  )
+  // -------------左侧树end-----------------------
+
+  const [formData, setFormData] = useState(C1baseFormData)
+  const formDataRef = useRef(C1baseFormData)
+  const formDataOldRef = useRef(C1baseFormData)
+
+  useEffect(() => {
+    formDataRef.current = formData
+  }, [formData])
+
+  // 点击搜索的 时间戳
+  const [timeKey, setTimeKey] = useState(0)
+
+  // 点击搜索
+  const clickSearch = useCallback(() => {
+    setFormData({ ...formData, pageNum: 1 })
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [formData])
+
+  // 封装发送请求的函数
+  const treeTimeRef = useRef(-1)
+
+  const getListFu = useCallback(() => {
+    clearTimeout(treeTimeRef.current)
+    treeTimeRef.current = window.setTimeout(() => {
+      if (treeAc) {
+        formDataOldRef.current = { ...formDataRef.current }
+
+        const obj = { ...formDataRef.current }
+        obj[treeKey.current as 'name'] = treeAc
+
+        dispatch(A32_APIgetList(obj))
+      } else if (treeAc === null) {
+        store.dispatch({ type: 'A32/getList', payload: { list: [], total: 0 } })
+      }
+    }, 100)
+  }, [dispatch, treeAc])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu, timeKey])
+
+  // 输入框的改变
+  const txtChangeFu = useCallback(
+    (txt: string, key: C1InputKeyType) => {
+      setFormData({
+        ...formData,
+        [key]: txt
+      })
+    },
+    [formData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setFormData(C1baseFormData)
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [])
+
+  // 页码变化
+  const paginationChange = useCallback(
+    (pageNum: number, pageSize: number) => {
+      setFormData({ ...formData, pageNum, pageSize })
+      setTimeout(() => {
+        setTimeKey(Date.now())
+      }, 50)
+    },
+    [formData]
+  )
+
+  // 顶部筛选
+  const searchDom = useCallback(
+    (arr: any[]) => {
+      return arr.map(item => {
+        return (
+          <div key={item.name}>
+            <span>{item.name}:</span>
+            {item.type === '输入框' ? (
+              <Input
+                placeholder='请输入'
+                maxLength={30}
+                value={formData[item.key as 'num']}
+                onChange={e => txtChangeFu(e.target.value, item.key)}
+              />
+            ) : item.type === '下拉框' ? (
+              <Select
+                options={item.data}
+                placeholder='全部'
+                allowClear={true}
+                value={formData[item.key as 'num'] ? formData[item.key as 'num'] : null}
+                onChange={e => setFormData({ ...formData, [item.key]: e })}
+              />
+            ) : (
+              <Cascader
+                options={item.data}
+                placeholder='全部'
+                fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+                allowClear={true}
+                value={
+                  formData[item.key as 'num']
+                    ? (formData[item.key as 'num'] as string).split(',')
+                    : []
+                }
+                onChange={e => setFormData({ ...formData, [item.key]: e ? e.join(',') : '' })}
+              />
+            )}
+          </div>
+        )
+      })
+    },
+    [formData, txtChangeFu]
+  )
+
+  // const deriveFu2 = useCallback(async () => {
+  //   const obj = {
+  //     ...formDataOldRef.current,
+  //     pageNum: 1,
+  //     pageSize: 99999
+  //   }
+  //   obj[treeKey.current as 'name'] = treeAc!
+
+  //   const res = await A32_APIgetList(obj, true)
+
+  //   if (res.code === 0) {
+  //     exportWordHandler(EXPORT_WORD_ENUM.COLLECTION_LEDGER, { collects: res.data.records })
+  //   }
+  // }, [treeAc])
+
+  // 点击导出
+  const deriveFu = useCallback(async () => {
+    const obj = {
+      ...formDataOldRef.current,
+      pageNum: 1,
+      pageSize: 99999
+    }
+    obj[treeKey.current as 'name'] = treeAc!
+
+    const res = await A32_APIgetList(obj, true)
+
+    if (res.code === 0) {
+      if (res.data.records.length <= 0) return MessageFu.warning('当前搜索条件没有数据!')
+
+      store.dispatch({
+        type: 'layout/exInfo',
+        payload: {
+          name: '藏品分账',
+          show: true,
+          arr: [
+            { key: 'num', txt: '藏品编号' },
+            { key: 'thumbPc', txt: '封面图地址' },
+            { key: 'numName', txt: '编号类型' },
+            { key: 'name', txt: '藏品名称' },
+            { key: 'dictLevel', txt: '文物级别' },
+            { key: 'dictType', txt: '文物类别' },
+            { key: 'dictAge', txt: '年代' },
+            { key: 'dictTexture3', txt: '质地' },
+            { key: 'dictTorn', txt: '完残程度' },
+            { key: 'source', txt: '来源' },
+            { key: 'statusCollect', txt: '入藏状态' },
+            { key: 'statusStorage', txt: '库存状态' },
+            { key: 'accountIndoor', txt: '展览状态' },
+            { key: 'createTime', txt: '创建日期' }
+          ],
+          data: res.data.records.map((v: FourTableType) => ({
+            ...v,
+            thumbPc: v.thumbPc ? window.location.origin + v.thumbPc : '(空)',
+            dictType: resJiLianFu(v.dictType),
+            dictAge: v.dictAge === '其他' ? '其他' : resJiLianFu(v.dictAge),
+            dictTexture3: resJiLianFu(v.dictTexture3),
+            dictTorn: resJiLianFu(v.dictTorn),
+            source: resJiLianFu(v.source),
+            statusCollect: Reflect.get(statusCollectObj, v.statusCollect) || '(空)',
+            statusStorage: Reflect.get(statusStorageObj, v.statusStorage) || '(空)',
+            accountIndoor:
+              (selectObj['展览状态'].find(c => c.value === v.accountIndoor) || {}).label || '(空)'
+          }))
+        }
+      })
+    }
+  }, [treeAc])
+
+  // 高级搜索的切换
+  const [advanced, setAdvanced] = useState(false)
+
+  // 点击收起高级搜索
+  const advancedFu = useCallback(
+    (flag: boolean) => {
+      setAdvanced(flag)
+      if (!flag) {
+        setFormData({ ...formData, ...C1baseFormData2 })
+      }
+    },
+    [formData]
+  )
+
+  // 点击回收站
+  const [recycleBin, setRecycleBin] = useState(false)
+
+  // 点击数据导入
+  const [importPage, setImportPage] = useState(false)
+
+  // 点击分账设置
+  const [setPage, setSetPage] = useState(false)
+
+  return (
+    <div className={styles.A32Routing}>
+      <div className='pageTitle'>
+        藏品分账{recycleBin ? '-回收站' : ''}
+        {importPage ? (
+          <>
+            -数据导入<span className='C4tit'>最多支持单次导入1000条</span>
+          </>
+        ) : (
+          ''
+        )}
+      </div>
+
+      <div className='A32top'>
+        <Button type='primary' onClick={() => setSetPage(true)}>
+          设置分账
+        </Button>
+        <div>
+          {/* <Button type='primary' onClick={() => setRecycleBin(true)}>
+            回收站
+          </Button>
+          &emsp; */}
+          <Button type='primary' onClick={() => setImportPage(true)}>
+            数据导入
+          </Button>
+          &emsp;
+          <Button type='primary' onClick={deriveFu} disabled={!treeAc}>
+            批量导出
+          </Button>
+          {/* &emsp;
+          <Button type='primary' onClick={deriveFu2} disabled={!treeAc}>
+            导出藏品总账
+          </Button> */}
+          &emsp;
+          <Button danger={advanced} onClick={() => advancedFu(!advanced)}>
+            {advanced ? '收起' : ''}高级搜索
+          </Button>
+          &emsp;
+          <Button type='primary' onClick={clickSearch} disabled={!treeAc}>
+            查询
+          </Button>
+          &emsp;
+          <Button onClick={resetSelectFu} disabled={!treeAc}>
+            重置
+          </Button>
+        </div>
+      </div>
+
+      <div className='A32main'>
+        <div className='A32left'>
+          <Input
+            placeholder='请输入分账名称'
+            value={value}
+            onChange={e => setValue(e.target.value.trim())}
+            allowClear
+          />
+          <div>
+            {treeArr.map(item1 => (
+              <div className='A32llRow' key={item1.name}>
+                <div className='A32llRow1' onClick={() => showTreeArrFu(item1.name)}>
+                  <span
+                    style={{
+                      transform: `rotate(${item1.show ? 0 : -90}deg)`,
+                      opacity: item1.son && item1.son.length ? '1' : '0'
+                    }}
+                  >
+                    <CaretDownOutlined />
+                  </span>
+                  &nbsp;
+                  {item1.name}
+                </div>
+
+                {/* 二级 */}
+                <div className={classNames('A32llRow2', item1.show ? 'A32llRow2Show' : '')}>
+                  {item1.son.map(item2 => (
+                    <div className='A32llRow2Row' key={item2.id}>
+                      <span
+                        onClick={() => {
+                          treeKey.current = item1.key
+                          setTreeAc(item2.id)
+                        }}
+                        className={classNames(treeAc === item2.id ? 'A32llRow2RowAc' : '')}
+                      >
+                        {item2.name}
+                      </span>
+                    </div>
+                  ))}
+                </div>
+              </div>
+            ))}
+          </div>
+        </div>
+        <div className='A32right'>
+          <div className='A32rrTop'>
+            {searchDom(C1InputKeyArr1.filter(v => v.name !== '文物类别'))}
+
+            {advanced
+              ? searchDom(C1InputKeyArr2.filter(v => !['入藏去向', '展览状态'].includes(v.name)))
+              : null}
+          </div>
+
+          {/* 表格 */}
+          <A32table
+            advanced={advanced}
+            menuId={602}
+            pageNum={formData.pageNum}
+            pageSize={formData.pageSize}
+            pageChangeFu={(pageNum, pageSize) => paginationChange(pageNum, pageSize)}
+          />
+        </div>
+      </div>
+
+      {/* 回收站 */}
+      {recycleBin ? (
+        <C8recycleBin
+          menuId={602}
+          closeFu={flag => {
+            if (flag) getListFu()
+            setRecycleBin(false)
+          }}
+        />
+      ) : null}
+
+      {/* 数据导入 */}
+      {importPage ? (
+        <C4import
+          colseFu={flag => {
+            if (flag) resetSelectFu()
+            setImportPage(false)
+          }}
+        />
+      ) : null}
+
+      {/* 设置分账 */}
+      {setPage ? (
+        <A32set
+          filterId={filterId}
+          succFu={() => {
+            flagRef.current = true
+            getFilterFu()
+          }}
+          closeFu={() => setSetPage(false)}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA32Routing = React.memo(A32Routing)
+
+export default MemoA32Routing

+ 73 - 0
src/pages/A3_ledger/C1ledger/data.ts

@@ -0,0 +1,73 @@
+import { selectObj } from '@/utils/select'
+import { C1DomArrType, TYpeC1Form, TYpeC1Form1, TYpeC1Form2 } from './type'
+import { cascaderObjFu } from '@/utils/history'
+
+export const C1baseFormData1: TYpeC1Form1 = {
+  numName: '',
+  num: '',
+  name: '',
+  dictLevel: '',
+  dictType: '',
+  dictAge: '',
+  dictTexture3: '',
+  dictTorn: '',
+  source: '',
+  statusCollect: '',
+  statusStorage: '',
+  accountType: '',
+  accountIndoor: ''
+}
+
+export const C1baseFormData2: TYpeC1Form2 = {
+  numType: '',
+  userName: '',
+  qualityDictScope: '',
+  inDictDateScope: '',
+  sourcePass: '',
+  sourcePreface: '',
+  sourceStamp: '',
+  historyWork: '',
+  historyUndergo: ''
+}
+
+export const C1baseFormData: TYpeC1Form = {
+  pageNum: 1,
+  pageSize: 10,
+
+  ...C1baseFormData1,
+
+  ...C1baseFormData2
+}
+
+export const C1InputKeyArr1: C1DomArrType = [
+  { name: '编号类型', key: 'numName', type: '下拉框', data: selectObj['藏品编号类型'] },
+  { name: '藏品编号', key: 'num', type: '输入框' },
+  { name: '藏品名称', key: 'name', type: '输入框' },
+  { name: '文物级别', key: 'dictLevel', type: '下拉框', data: selectObj['文物级别'] },
+  { name: '文物类别', key: 'dictType', type: '级联', data: cascaderObjFu()['文物类别'] },
+  { name: '年代', key: 'dictAge', type: '级联', data: cascaderObjFu()['年代'] },
+  { name: '质地', key: 'dictTexture3', type: '级联', data: cascaderObjFu()['单一质地'] },
+  { name: '完残程度', key: 'dictTorn', type: '级联', data: cascaderObjFu()['完残程度'] },
+  { name: '来源', key: 'source', type: '级联', data: cascaderObjFu()['来源'] },
+  { name: '入藏状态', key: 'statusCollect', type: '下拉框', data: selectObj['入藏状态'] },
+  { name: '库存状态', key: 'statusStorage', type: '下拉框', data: selectObj['库存状态'] }
+]
+
+export const C1InputKeyArr2: C1DomArrType = [
+  { name: '分类号', key: 'numType', type: '输入框' },
+  { name: '制档人', key: 'userName', type: '输入框' },
+  { name: '质量范围', key: 'qualityDictScope', type: '级联', data: cascaderObjFu()['质量范围'] },
+  {
+    name: '入藏日期范围',
+    key: 'inDictDateScope',
+    type: '级联',
+    data: cascaderObjFu()['入藏日期范围']
+  },
+  { name: '征集经过', key: 'sourcePass', type: '输入框' },
+  { name: '铭记题跋', key: 'sourcePreface', type: '输入框' },
+  { name: '鉴藏印记', key: 'sourceStamp', type: '输入框' },
+  { name: '著作书目', key: 'historyWork', type: '输入框' },
+  { name: '流转经历', key: 'historyUndergo', type: '输入框' },
+  { name: '入藏去向', key: 'accountType', type: '下拉框', data: selectObj['入藏去向'] },
+  { name: '展览状态', key: 'accountIndoor', type: '下拉框', data: selectObj['展览状态'] }
+]

+ 96 - 0
src/pages/A3_ledger/C1ledger/index.module.scss

@@ -0,0 +1,96 @@
+.C1ledger {
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 24px 24px 0;
+  position: relative;
+  :global {
+    .C4tit {
+      color: #999;
+      margin-left: 10px;
+      font-size: 14px;
+      font-weight: 400;
+    }
+    .C1top {
+      display: flex;
+      justify-content: space-between;
+      .C1topll {
+        display: flex;
+        & > div {
+          margin-right: 15px;
+          display: flex;
+          align-items: center;
+          position: relative;
+          & > span {
+            position: absolute;
+            top: -18px;
+            left: 0;
+            pointer-events: none;
+          }
+        }
+      }
+
+      .C1toprrSuo {
+        width: 100%;
+        margin-top: 15px;
+        text-align: right;
+        .ant-btn {
+          margin-right: 15px;
+        }
+      }
+
+      .C1toprrKai {
+        position: absolute;
+        top: 65px;
+        right: 30px;
+        width: 310px;
+        height: 114px;
+        display: flex;
+        flex-wrap: wrap;
+        justify-content: center;
+        .ant-btn {
+          margin-right: 15px;
+          width: 116px;
+        }
+      }
+
+      .C1topllAll {
+        width: 100%;
+        & > div {
+          width: 8%;
+          .ant-input {
+            width: 100%;
+          }
+          .ant-select {
+            width: 100%;
+          }
+        }
+      }
+    }
+
+    .C1top2 {
+      align-items: center;
+      margin-bottom: 15px;
+      .C1topll {
+        width: calc(100% - 320px);
+        flex-wrap: wrap;
+        & > div {
+          margin-top: 23px;
+
+          width: 15%;
+          .ant-input {
+            width: 100%;
+          }
+          .ant-select {
+            width: 100%;
+          }
+        }
+      }
+    }
+    .ant-select-selection-placeholder {
+      color: black !important;
+    }
+    .ant-table-cell {
+      padding: 8px !important;
+    }
+  }
+}

+ 345 - 0
src/pages/A3_ledger/C1ledger/index.tsx

@@ -0,0 +1,345 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, Input, Select } from 'antd'
+import { C1InputKeyArr1, C1InputKeyArr2, C1baseFormData, C1baseFormData2 } from './data'
+import { C1InputKeyType } from './type'
+import { openGoodsInfoFu, resJiLianFu } from '@/utils/history'
+import MyTable from '@/components/MyTable'
+import { C1tableC, statusCollectObj, statusStorageObj } from '@/utils/tableData'
+import classNames from 'classnames'
+import Y1cathet from '@/pages/Y_goodsDetails/Y1cathet'
+import { useDispatch, useSelector } from 'react-redux'
+import { C1_APIgetList } from '@/store/action/C1ledger'
+import store, { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import C8recycleBin from '../ComPage/C8recycleBin'
+import C4import from '../ComPage/C4import'
+import { selectObj } from '@/utils/select'
+import { FourTableType } from '@/pages/B_enterTibet/B1collect/type'
+import { EXPORT_WORD_ENUM } from '@/utils/exportTemplates'
+import { EXbtnFu } from '@/utils/EXBtn'
+
+function C1ledger() {
+  // 是否是藏品查询的藏品信息
+  const [antiqueSearch, setAntiqueSearch] = useState(window.location.href.includes('antiqueSearch'))
+
+  useEffect(() => {
+    if (1 + 1 !== 2) console.log(setAntiqueSearch)
+  }, [])
+
+  const dispatch = useDispatch()
+
+  const [formData, setFormData] = useState(C1baseFormData)
+  const formDataRef = useRef(C1baseFormData)
+  const formDataOldRef = useRef(C1baseFormData)
+
+  useEffect(() => {
+    formDataRef.current = formData
+  }, [formData])
+
+  // 点击搜索的 时间戳
+  const [timeKey, setTimeKey] = useState(0)
+
+  // 点击搜索
+  const clickSearch = useCallback(() => {
+    setFormData({ ...formData, pageNum: 1 })
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [formData])
+
+  // 封装发送请求的函数
+  const getListFu = useCallback(() => {
+    formDataOldRef.current = { ...formDataRef.current }
+    dispatch(C1_APIgetList(formDataRef.current))
+  }, [dispatch])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu, timeKey])
+
+  // 输入框的改变
+  const txtChangeFu = useCallback(
+    (txt: string, key: C1InputKeyType) => {
+      setFormData({
+        ...formData,
+        [key]: txt
+      })
+    },
+    [formData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setFormData(C1baseFormData)
+    setTimeout(() => {
+      setTimeKey(Date.now())
+    }, 50)
+  }, [])
+
+  // 页码变化
+  const paginationChange = useCallback(
+    (pageNum: number, pageSize: number) => {
+      setFormData({ ...formData, pageNum, pageSize })
+      setTimeout(() => {
+        setTimeKey(Date.now())
+      }, 50)
+    },
+    [formData]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '展览状态',
+        render: (item: FourTableType) =>
+          (selectObj['展览状态'].find(v => v.value === item.accountIndoor) || {}).label || '(空)'
+      },
+      {
+        title: '创建日期',
+        render: (item: FourTableType) => item.createTime
+      },
+
+      {
+        title: '操作',
+        render: (item: FourTableType) => {
+          return (
+            <Button size='small' type='text' onClick={() => openGoodsInfoFu(item.id, 601)}>
+              查看
+            </Button>
+          )
+        }
+      }
+    ]
+  }, [])
+
+  // 从仓库拿数据
+  const tableInfo = useSelector((state: RootState) => state.C1ledger.tableInfo)
+
+  // 高级搜索的切换
+  const [advanced, setAdvanced] = useState(false)
+
+  // 点击收起高级搜索
+  const advancedFu = useCallback(
+    (flag: boolean) => {
+      setAdvanced(flag)
+      if (!flag) {
+        setFormData({ ...formData, ...C1baseFormData2 })
+      }
+    },
+    [formData]
+  )
+
+  // 打开侧边栏
+  const [cathet, setCathet] = useState(0)
+
+  const startBtn = useMemo(() => {
+    return [
+      {
+        title: '藏品编号',
+        render: (item: FourTableType) => {
+          return (
+            <span
+              onClick={() => setCathet(item.id)}
+              className={classNames('D1GtNum', item.id === cathet ? 'D1GtNumAc' : '')}
+            >
+              {item.num || '(空)'}
+            </span>
+          )
+        }
+      }
+    ]
+  }, [cathet])
+
+  // 顶部筛选
+  const searchDom = useCallback(
+    (arr: any[]) => {
+      return arr.map(item => {
+        return (
+          <div key={item.name}>
+            <span>{item.name}:</span>
+            {item.type === '输入框' ? (
+              <Input
+                placeholder='请输入'
+                maxLength={30}
+                value={formData[item.key as 'num']}
+                onChange={e => txtChangeFu(e.target.value, item.key)}
+              />
+            ) : item.type === '下拉框' ? (
+              <Select
+                options={item.data}
+                placeholder='全部'
+                allowClear={true}
+                value={formData[item.key as 'num'] ? formData[item.key as 'num'] : null}
+                onChange={e => setFormData({ ...formData, [item.key]: e })}
+              />
+            ) : (
+              <Cascader
+                options={item.data}
+                placeholder='全部'
+                fieldNames={{ label: 'name', value: 'id', children: 'children' }}
+                allowClear={true}
+                value={
+                  formData[item.key as 'num']
+                    ? (formData[item.key as 'num'] as string).split(',')
+                    : []
+                }
+                onChange={e => setFormData({ ...formData, [item.key]: e ? e.join(',') : '' })}
+              />
+            )}
+          </div>
+        )
+      })
+    },
+    [formData, txtChangeFu]
+  )
+
+  // 点击导出
+  const deriveFu = useCallback(async () => {
+    const res = await C1_APIgetList(
+      {
+        ...formDataOldRef.current,
+        pageNum: 1,
+        pageSize: 99999
+      },
+      true
+    )
+
+    if (res.code === 0) {
+      if (res.data.records.length <= 0) return MessageFu.warning('当前搜索条件没有数据!')
+
+      store.dispatch({
+        type: 'layout/exInfo',
+        payload: {
+          name: antiqueSearch ? '藏品信息' : '藏品总账',
+          show: true,
+          arr: [
+            { key: 'num', txt: '藏品编号' },
+            { key: 'thumbPc', txt: '封面图地址' },
+            { key: 'numName', txt: '编号类型' },
+            { key: 'name', txt: '藏品名称' },
+            { key: 'dictLevel', txt: '文物级别' },
+            { key: 'dictType', txt: '文物类别' },
+            { key: 'dictAge', txt: '年代' },
+            { key: 'dictTexture3', txt: '质地' },
+            { key: 'dictTorn', txt: '完残程度' },
+            { key: 'source', txt: '来源' },
+            { key: 'statusCollect', txt: '入藏状态' },
+            { key: 'statusStorage', txt: '库存状态' },
+            { key: 'accountIndoor', txt: '展览状态' },
+            { key: 'createTime', txt: '创建日期' }
+          ],
+          data: res.data.records.map((v: FourTableType) => ({
+            ...v,
+            thumbPc: v.thumbPc ? window.location.origin + v.thumbPc : '(空)',
+            dictType: resJiLianFu(v.dictType),
+            dictAge: v.dictAge === '其他' ? '其他' : resJiLianFu(v.dictAge),
+            dictTexture3: resJiLianFu(v.dictTexture3),
+            dictTorn: resJiLianFu(v.dictTorn),
+            source: resJiLianFu(v.source),
+            statusCollect: Reflect.get(statusCollectObj, v.statusCollect) || '(空)',
+            statusStorage: Reflect.get(statusStorageObj, v.statusStorage) || '(空)',
+            accountIndoor:
+              (selectObj['展览状态'].find(c => c.value === v.accountIndoor) || {}).label || '(空)'
+          }))
+        }
+      })
+    }
+  }, [antiqueSearch])
+
+  // 点击回收站
+  const [recycleBin, setRecycleBin] = useState(false)
+
+  // 点击数据导入
+  const [importPage, setImportPage] = useState(false)
+
+  return (
+    <div className={styles.C1ledger}>
+      <div className='pageTitle'>
+        {antiqueSearch ? '藏品信息' : '藏品总账'}
+        {recycleBin ? '-回收站' : ''}
+        {importPage ? <>-数据导入</> : ''}
+      </div>
+      {/* 第一行 */}
+      <div className='C1top'>
+        <div className='C1topll C1topllAll'>{searchDom(C1InputKeyArr1)}</div>
+      </div>
+
+      {/* 第二行 */}
+      <div className='C1top C1top2'>
+        {advanced ? (
+          <div className='C1topll'>{searchDom(C1InputKeyArr2)}</div>
+        ) : (
+          <div className='C1topll'></div>
+        )}
+
+        <div className={classNames(advanced ? 'C1toprrKai' : 'C1toprrSuo')}>
+          {/* <Button type='primary' onClick={() => setRecycleBin(true)} hidden={antiqueSearch}>
+            回收站
+          </Button>
+          &emsp; */}
+          <Button type='primary' onClick={() => setImportPage(true)} hidden={antiqueSearch}>
+            数据导入
+          </Button>
+          <Button type='primary' onClick={deriveFu} hidden={antiqueSearch}>
+            批量导出
+          </Button>
+          {!antiqueSearch &&
+            EXbtnFu({ collects: tableInfo.list }, [
+              EXPORT_WORD_ENUM.COLLECTION_LEDGER,
+              EXPORT_WORD_ENUM.COLLECTION_LOG
+            ])}
+          <Button danger={advanced} onClick={() => advancedFu(!advanced)}>
+            {advanced ? '收起' : ''}高级搜索
+          </Button>
+          <Button type='primary' onClick={clickSearch}>
+            查询
+          </Button>
+          <Button onClick={resetSelectFu}>重置</Button>
+        </div>
+      </div>
+
+      {/* 表格 */}
+      <MyTable
+        emptyText={true}
+        yHeight={advanced ? 540 : 595}
+        list={tableInfo.list}
+        columnsTemp={C1tableC}
+        lastBtn={tableLastBtn}
+        startBtn={startBtn}
+        pageNum={formData.pageNum}
+        pageSize={formData.pageSize}
+        total={tableInfo.total}
+        onChange={(pageNum, pageSize) => paginationChange(pageNum, pageSize)}
+      />
+
+      {/* 打开侧边栏 */}
+      <Y1cathet sId={cathet} menuId={601} closeFu={() => setCathet(0)} />
+
+      {/* 回收站 */}
+      {recycleBin ? (
+        <C8recycleBin
+          menuId={601}
+          closeFu={flag => {
+            if (flag) getListFu()
+            setRecycleBin(false)
+          }}
+        />
+      ) : null}
+
+      {/* 数据导入 */}
+      {importPage ? (
+        <C4import
+          colseFu={flag => {
+            if (flag) resetSelectFu()
+            setImportPage(false)
+          }}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoC1ledger = React.memo(C1ledger)
+
+export default MemoC1ledger

+ 151 - 0
src/pages/A3_ledger/C1ledger/type.d.ts

@@ -0,0 +1,151 @@
+export type C1InputKeyType = keyof TYpeC1Form
+
+export interface TYpeC1Form extends TYpeC1Form1, TYpeC1Form2 {
+  pageSize: number
+  pageNum: number
+}
+
+export interface TYpeC1Form1 {
+  numName: string
+  num: string
+  name: string
+  dictLevel: string
+  dictType: string
+  dictAge: string
+  dictTexture3: string
+  dictTorn: string
+  source: string
+  statusCollect: string
+  statusStorage: string
+  accountType: string
+  accountIndoor: string
+}
+
+export interface TYpeC1Form2 {
+  numType: string
+  userName: string
+  qualityDictScope: string
+  inDictDateScope: string
+  sourcePass: string
+  sourcePreface: string
+  sourceStamp: string
+  historyWork: string
+  historyUndergo: string
+}
+
+export type C1DomArrType = {
+  name: string
+  key: C1InputKeyType
+  type: '输入框' | '下拉框' | '级联'
+  data?: any[]
+}[]
+
+export type C1GoodType = {
+  display: 0 | 1
+  ageInfo: string
+  author: string
+  authorDesc: string
+  color: string
+  createTime: string
+  creatorId: number
+  creatorName: string
+  dateMaking: string
+  dictAge: string
+  dictLevel: string
+  dictTexture1: string
+  dictTexture2: string
+  dictTexture3: string
+  dictTorn: string
+  dictType: string
+  // display?: any;
+  file: GoodFileType[]
+  fileIds: string
+  historyUndergo: string
+  historyWork: string
+  id: number
+  inDictDateScope: string
+  inGoodsDate: string
+  inGoodsNum: string
+  inHouseNum: string
+  // isEdit?: any;
+  name: string
+  namePrimitive: string
+  num: string
+  numName: string
+  numType: string
+  pcs: number
+  pcsActual: string
+  pcsUnit: string
+  preserveState: string
+  pressAuthor: string
+  pressFile: string
+  pressVersion: string
+  quality: string
+  qualityDictScope: string
+  qualityUnit: string
+  // regionId?: any;
+  rtf: string
+  shape: string
+  siteStr: string
+  sizeH: string
+  sizeInfo: string
+  sizeL: string
+  sizeUnit: string
+  sizeW: string
+  source: string
+  sourceInfo: string
+  sourcePass: string
+  sourcePreface: string
+  sourceStamp: string
+  // statusCollect?: any;
+  // statusStorage?: any;
+  // tempRegionId?: any;
+  tempSiteStr: string
+  thumb: string
+  thumbPc: string
+  filePath: string
+  torn: string
+  updateTime: string
+  uses: string
+  // 存放位置数组
+  siteId: number
+  // 库存状态
+  statusStorage: number
+  // 藏品关注
+  isFocus: 1 | 0
+
+  // 入藏去向
+  accountType: string
+
+  // 文物修复
+  txt1: string
+  txt2: string
+  txt3: string
+  id2: number
+
+  // 入馆-入藏
+  relatedOrderId: number
+  subNum: string
+
+  // 新加的字段 馆内藏品分类
+  dictHouse1: string
+  dictHouse2: string
+
+  // 藏品入库
+  siteNum: number | null
+
+  // 详情按钮的盘点
+  statusCollect: number
+
+  // 是否在编辑状态isEdit
+  isEdit: 0 | 1
+
+  // 出库
+  date: string
+  txt4: string
+  beiZhu: string
+  storageId: number
+
+  // 移库
+  siteNumNew: number | null
+}

+ 31 - 0
src/pages/A3_ledger/ComPage/C4import/C4imgModal/index.module.scss

@@ -0,0 +1,31 @@
+.C4imgModal {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 1000px !important;
+    }
+    .B1XLmain {
+      border-top: 1px solid #999999;
+      padding-top: 15px;
+      width: 100%;
+      .B1XLtop {
+        margin-bottom: 15px;
+        .ant-btn {
+          margin-right: 15px;
+        }
+      }
+
+      .ant-table-cell {
+        padding: 8px !important;
+        text-align: center !important;
+      }
+
+      .B1XLbtn {
+        margin-top: 20px;
+        text-align: center;
+      }
+    }
+  }
+}

+ 109 - 0
src/pages/A3_ledger/ComPage/C4import/C4imgModal/index.tsx

@@ -0,0 +1,109 @@
+import React, { useCallback, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Modal } from 'antd'
+import { C4zipType } from '../data'
+import MyTable from '@/components/MyTable'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A3m_APIaddZip, A3m_APIaddZip_2 } from '@/store/action/A3machineReg'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  info: C4zipType
+  closeFu: () => void
+  againUpFu: () => void
+  succFu: () => void
+}
+
+function C4imgModal({ info, closeFu, againUpFu, succFu }: Props) {
+  const [btnAc, setBtnAc] = useState('成功')
+
+  // 点击导入当前数据
+  const btnOk = useCallback(async () => {
+    const obj = {
+      fileName: info.file?.fileName,
+      filePath: info.file?.filePath,
+      pcsError: info.err.length,
+      pcsSuccess: info.succ.length,
+      pcsTotal: info.err.length + info.succ.length,
+      type: '3'
+    }
+
+    const res1 = await A3m_APIaddZip(obj)
+    if (res1.code === 0) {
+      const obj2 = [...info.succ, ...info.err].map(v => ({
+        ...v,
+        id: null,
+        importId: res1.data.id,
+        moduleName: 'goods',
+        importError: v.description === '成功' ? '0' : '1'
+      }))
+
+      const res2 = await A3m_APIaddZip_2(obj2)
+
+      if (res2.code === 0) {
+        MessageFu.success('导入成功')
+        succFu()
+        closeFu()
+      }
+    }
+  }, [closeFu, info, succFu])
+
+  return (
+    <Modal
+      wrapClassName={styles.C4imgModal}
+      destroyOnClose
+      open={true}
+      title={
+        <>
+          导入数据条数:{info.succ.length + info.err.length}&emsp;&emsp;校验成功条数:
+          {info.succ.length}&emsp;&emsp;校验失败条数:{info.err.length}
+        </>
+      }
+      footer={[]}
+    >
+      <div className='B1XLmain'>
+        <div className='B1XLtop'>
+          {['成功', '失败'].map(v => (
+            <Button key={v} type={v === btnAc ? 'primary' : 'default'} onClick={() => setBtnAc(v)}>
+              {v} {v === '成功' ? info.succ.length : info.err.length}
+            </Button>
+          ))}
+        </div>
+
+        <MyTable
+          classKey='C4imgModal'
+          yHeight={500}
+          list={btnAc === '成功' ? info.succ : info.err}
+          columnsTemp={[
+            ['txt', '图片名称', 'fileName'],
+            ['txt', '检验结果', 'description']
+          ]}
+          lastBtn={[]}
+          pagingInfo={false}
+        />
+
+        <div className='B1XLbtn'>
+          {info.type === '查看' ? (
+            <Button onClick={closeFu}>关闭</Button>
+          ) : (
+            <>
+              <Button type='primary' onClick={againUpFu}>
+                重新上传压缩包
+              </Button>
+              &emsp;
+              <Button type='primary' disabled={info.succ.length === 0} onClick={btnOk}>
+                导入当前数据
+              </Button>
+              &emsp;
+              <MyPopconfirm txtK='取消' onConfirm={closeFu} Dom={<Button>取消导入</Button>} />
+            </>
+          )}
+        </div>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoC4imgModal = React.memo(C4imgModal)
+
+export default MemoC4imgModal

+ 0 - 0
src/pages/A3_ledger/ComPage/C4import/C4impImg/index.module.scss


Some files were not shown because too many files changed in this diff