shaogen1995 hai 20 horas
pai
achega
e92a19c92b
Modificáronse 39 ficheiros con 16379 adicións e 47 borrados
  1. 1 1
      后台管理/README.md
  2. 14416 0
      后台管理/public/city.js
  3. 1 0
      后台管理/public/index.html
  4. BIN=BIN
      后台管理/src/assets/img/bg.jpg
  5. BIN=BIN
      后台管理/src/assets/img/logo.png
  6. 9 0
      后台管理/src/components/MyTable/index.tsx
  7. 2 1
      后台管理/src/components/ZupTypes/index.tsx
  8. 95 0
      后台管理/src/components/ZupVideos/index.module.scss
  9. 193 0
      后台管理/src/components/ZupVideos/index.tsx
  10. 91 0
      后台管理/src/pages/A1record/A1edit/index.module.scss
  11. 245 0
      后台管理/src/pages/A1record/A1edit/index.tsx
  12. 103 0
      后台管理/src/pages/A1record/A1look/index.module.scss
  13. 160 0
      后台管理/src/pages/A1record/A1look/index.tsx
  14. 53 0
      后台管理/src/pages/A1record/A1tab1introduce/A1T1add/index.module.scss
  15. 134 0
      后台管理/src/pages/A1record/A1tab1introduce/A1T1add/index.tsx
  16. 35 0
      后台管理/src/pages/A1record/A1tab1introduce/index.module.scss
  17. 98 0
      后台管理/src/pages/A1record/A1tab1introduce/index.tsx
  18. 70 0
      后台管理/src/pages/A1record/data.ts
  19. 49 0
      后台管理/src/pages/A1record/index.module.scss
  20. 135 2
      后台管理/src/pages/A1record/index.tsx
  21. 26 0
      后台管理/src/pages/A3dict/A3add/index.module.scss
  22. 113 0
      后台管理/src/pages/A3dict/A3add/index.tsx
  23. 13 0
      后台管理/src/pages/A3dict/data.ts
  24. 24 0
      后台管理/src/pages/A3dict/index.module.scss
  25. 106 1
      后台管理/src/pages/A3dict/index.tsx
  26. 3 11
      后台管理/src/pages/Layout/index.module.scss
  27. 12 2
      后台管理/src/pages/Layout/index.tsx
  28. 6 3
      后台管理/src/pages/Login/index.module.scss
  29. 1 0
      后台管理/src/pages/Z1user/Z1auth.tsx
  30. 5 7
      后台管理/src/pages/Z1user/index.tsx
  31. 42 0
      后台管理/src/store/action/A1record.ts
  32. 41 0
      后台管理/src/store/action/A3dict.ts
  33. 28 0
      后台管理/src/store/reducer/A1record.ts
  34. 25 0
      后台管理/src/store/reducer/A3dict.ts
  35. 4 0
      后台管理/src/store/reducer/index.ts
  36. 9 8
      后台管理/src/types/declaration.d.ts
  37. 9 1
      后台管理/src/utils/history.ts
  38. 2 2
      后台管理/src/utils/http.ts
  39. 20 8
      后台管理/src/utils/tableData.ts

+ 1 - 1
后台管理/README.md

@@ -1,6 +1,6 @@
 1.使用 yarn。 npm 有问题
 
-2.测试后端文档地址:
+2.测试后端文档地址:待完善
 
 3.测试堡垒机位置:
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 14416 - 0
后台管理/public/city.js


+ 1 - 0
后台管理/public/index.html

@@ -7,6 +7,7 @@
     <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"> -->
+    <script src="./city.js"></script>
 
     <title>云南沘江古桥一张图-后台管理</title>
   </head>

BIN=BIN
后台管理/src/assets/img/bg.jpg


BIN=BIN
后台管理/src/assets/img/logo.png


+ 9 - 0
后台管理/src/components/MyTable/index.tsx

@@ -82,6 +82,15 @@ function MyTable({
       const obj = {
         index: (_: any, __: any, index: number) => index + 1 + (pageNum - 1) * pageSize,
         txt: (item: any) => item[v[2]] || isNull,
+        // 城市拼接
+        cityAll: (item: any) => {
+          let res = '(空)'
+          if (item[v[2]]) res = item[v[2]]
+          if (item[v[3]]) res += ` / ${item[v[3]]}`
+          if (item[v[4]]) res += ` / ${item[v[4]]}`
+          if (item[v[5]]) res += ` - ${item[v[5]]}`
+          return res
+        },
         img: (item: any) => (
           <div className='tableImgAuto'>
             <ImageLazy

+ 2 - 1
后台管理/src/components/ZupTypes/index.tsx

@@ -525,7 +525,8 @@ function ZupTypes(
               <br />
             </>
           ) : null}
-          第一张为封面图;支持png、jpg的图片格式;最大支持{imgSize}M;最多支持{imgLength}张。
+          {oneIsCover ? '第一张为封面图;' : ''} 支持png、jpg的图片格式;最大支持{imgSize}
+          M;最多支持{imgLength}张。
           {lastImgTxt}
         </div>
       </div>

+ 95 - 0
后台管理/src/components/ZupVideos/index.module.scss

@@ -0,0 +1,95 @@
+.ZupVideos {
+  width: 100%;
+
+  :global {
+    .zVmain {
+      width: 100%;
+      display: flex;
+      flex-wrap: wrap;
+
+      .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;
+        margin-right: 20px;
+      }
+
+      .zVRow {
+        margin-right: 20px;
+        margin-bottom: 10px;
+
+        .zVRow1 {
+          display: flex;
+          position: relative;
+
+          .zVRow1V {
+            width: 100px;
+            height: 100px;
+            border-radius: 3px;
+
+            video {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+          }
+
+          .zVRow1I {
+            position: absolute;
+            top: 0;
+            right: 0px;
+            height: 100%;
+            display: flex;
+            flex-direction: column;
+            justify-content: space-around;
+            align-items: center;
+            background-color: rgba(0, 0, 0, 0.6);
+            color: #fff;
+            padding: 0 5px;
+            a {
+              color: #fff !important;
+            }
+          }
+        }
+
+        .zVRow2 {
+          margin-top: 3px;
+          font-size: 14px;
+          text-align: center;
+          width: 100px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+      }
+    }
+
+    .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;
+    }
+  }
+}

+ 193 - 0
后台管理/src/components/ZupVideos/index.tsx

@@ -0,0 +1,193 @@
+import React, { useCallback, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { forwardRef, useImperativeHandle } from 'react'
+import { FileListType } from '../ZupTypes'
+import { MessageFu } from '@/utils/message'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { PlusOutlined, EyeOutlined, CloseOutlined, DownloadOutlined } from '@ant-design/icons'
+import classNames from 'classnames'
+import { baseURL } from '@/utils/http'
+import store from '@/store'
+import MyPopconfirm from '../MyPopconfirm'
+
+type Props = {
+  size: number //视频大小
+  fileNum: number //数量限制
+  dirCode: string //文件的code码
+  myUrl: string //请求地址
+  fileCheck?: boolean //是否检验
+  typeArr?: string[] //上传格式 ['mp4','mov','avi','wmv']
+  checkTxt?: string
+  upTxt?: string
+  isLook?: boolean //是不是查看
+  fromData?: any
+  ref: any //当前自己的ref,给父组件调用
+}
+
+function ZupVideos(
+  {
+    size,
+    fileNum,
+    dirCode,
+    myUrl,
+    fileCheck = false,
+    typeArr = ['mp4', 'mov', 'avi', 'wmv'],
+    checkTxt = '请上传视频!',
+    upTxt = '',
+    isLook = false,
+    fromData
+  }: Props,
+  ref: any
+) {
+  const [fileList, setFileList] = useState<FileListType[]>([])
+
+  // 上传
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+        // console.log('-----', filesInfo.name)
+
+        const fileNameArr = filesInfo.name.split('.')
+        const fileNameLast = fileNameArr[fileNameArr.length - 1]
+
+        // 校验格式
+        if (!typeArr.includes(fileNameLast.toLowerCase())) {
+          e.target.value = ''
+          return MessageFu.warning(`只支持${typeArr.join('/')}格式`)
+        }
+
+        // 校验大小
+        if (filesInfo.size > size * 1024 * 1024) {
+          e.target.value = ''
+          return MessageFu.warning(`最大支持${size}M!`)
+        }
+        // 创建FormData对象
+        const fd = new FormData()
+
+        fd.append('type', 'video')
+        fd.append('isDb', 'true')
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
+
+        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])
+            // console.log(res);
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [dirCode, fileList, fromData, myUrl, size, typeArr]
+  )
+
+  // 让父组件调用的 回显 附件 地址
+  const setFileComFileFu = useCallback((valList: FileListType[]) => {
+    setFileList(valList)
+  }, [])
+
+  // 让父组件调用的返回 附件 名字和路径
+  const fileComFileResFu = useCallback(() => {
+    return fileList
+  }, [fileList])
+
+  // 可以让父组件调用子组件的方法
+  useImperativeHandle(ref, () => ({
+    setFileComFileFu,
+    fileComFileResFu
+  }))
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 点击预览
+  const lookFileFu = useCallback((file: string) => {
+    store.dispatch({
+      type: 'layout/lookDom',
+      payload: { src: file, type: 'video' }
+    })
+  }, [])
+
+  return (
+    <div className={styles.ZupVideos}>
+      <input
+        id='upInput'
+        type='file'
+        accept={typeArr.map(v => `.${v}`).join(',')}
+        ref={myInput}
+        onChange={e => handeUpPhoto(e)}
+      />
+
+      <div hidden={!(isLook && fileList.length <= 0)}>(空)</div>
+
+      <div className='zVmain'>
+        <div
+          hidden={fileList.length >= fileNum || isLook}
+          className='file_upIcon'
+          onClick={() => myInput.current?.click()}
+        >
+          <PlusOutlined />
+        </div>
+
+        {fileList.map(v => (
+          <div className='zVRow' key={v.id}>
+            <div className='zVRow1'>
+              <div className='zVRow1V' title={v.fileName}>
+                <video src={baseURL + v.filePath}></video>
+              </div>
+              <div className='zVRow1I'>
+                {/* 视频预览 */}
+                <EyeOutlined onClick={() => lookFileFu(v.filePath)} />
+                {/* 视频下载 */}
+                <a href={baseURL + v.filePath} download target='_blank' rel='noreferrer'>
+                  <DownloadOutlined />
+                </a>
+                {/* 视频删除 */}
+
+                {isLook ? null : (
+                  <MyPopconfirm
+                    txtK='删除'
+                    onConfirm={() => setFileList(fileList.filter(c => c.id !== v.id))}
+                    Dom={<CloseOutlined />}
+                  />
+                )}
+              </div>
+            </div>
+            <div className='zVRow2' title={v.fileName}>
+              {v.fileName}
+            </div>
+          </div>
+        ))}
+      </div>
+
+      <div className='fileBoxRow_r_tit' hidden={isLook}>
+        格式要求:支持{typeArr.join('/')}格式,最大支持{size}M{upTxt}
+        <br />
+        <div
+          className={classNames(
+            'noUpThumb',
+            fileList.length <= 0 && fileCheck ? 'noUpThumbAc' : ''
+          )}
+        >
+          {checkTxt}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+export default forwardRef(ZupVideos)

+ 91 - 0
后台管理/src/pages/A1record/A1edit/index.module.scss

@@ -0,0 +1,91 @@
+.A1edit {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ant-modal {
+      width: 1500px !important;
+      min-width: 1500px !important;
+    }
+    .ant-modal-header {
+      border-bottom: 1px solid #ccc !important;
+      padding-bottom: 15px !important;
+    }
+
+    .A1Emain {
+      width: calc(100% - 100px);
+      height: 620px;
+      overflow: auto;
+      padding-top: 10px;
+      .ant-form-item-label {
+        width: 81px;
+      }
+      .A1ERow {
+        display: flex;
+        justify-content: space-between;
+        .ant-form-item {
+          width: 48%;
+        }
+
+        .A1ERowSon {
+          width: 48%;
+          display: flex;
+          .ant-btn {
+            margin-left: 10px;
+          }
+          .ant-form-item {
+            width: calc(100% - 74px);
+          }
+        }
+      }
+      .A1ERowAll {
+        width: 100%;
+        .ant-form-item {
+          width: 100%;
+        }
+      }
+
+      .A3fromRow {
+        width: 48%;
+        position: relative;
+        .A3_6Frow {
+          position: absolute;
+          left: 80px;
+          font-size: 14px;
+          color: rgb(126, 124, 124);
+        }
+      }
+
+      .formRow {
+        display: flex;
+
+        .formLeft {
+          position: relative;
+          top: 3px;
+          width: 81px;
+          text-align: right;
+
+          & > span {
+            color: #ff4d4f;
+          }
+        }
+
+        .formRight {
+          width: calc(100% - 81px);
+          position: relative;
+        }
+      }
+      .formRow2 {
+        margin-bottom: -20px;
+      }
+
+      .A1EBtn {
+        position: absolute;
+        top: 50%;
+        right: 20px;
+        transform: translateY(-50%);
+      }
+    }
+  }
+}

+ 245 - 0
后台管理/src/pages/A1record/A1edit/index.tsx

@@ -0,0 +1,245 @@
+import React, { useCallback, useEffect, useRef } from 'react'
+import styles from './index.module.scss'
+import {
+  Button,
+  Cascader,
+  Form,
+  FormInstance,
+  Input,
+  InputNumber,
+  Modal,
+  Radio,
+  Select
+} from 'antd'
+import { A1ListType, A1select1 } from '../data'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { MessageFu } from '@/utils/message'
+import { A1_APIsave } from '@/store/action/A1record'
+import ZupOne from '@/components/ZupOne'
+import { myCity } from '@/utils/history'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import TextArea from 'antd/es/input/TextArea'
+
+type Props = {
+  sInfo: A1ListType
+  closeFu: () => void
+  upTableFu: () => void
+}
+
+function A1edit({ sInfo, closeFu, upTableFu }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 封面图的ref
+  const ZupThumbRef = useRef<any>(null)
+
+  // 编辑进来获取详情
+  const getInfoFu = useCallback(() => {
+    const obj: any = sInfo
+
+    // 线索地址
+    if (obj.province) obj.myCity = [obj.province]
+    if (obj.city) obj.myCity.push(obj.city)
+    if (obj.region) obj.myCity.push(obj.region)
+    obj.dictVillageId = obj.dictVillageId || null
+
+    FormBoxRef.current?.setFieldsValue(obj)
+
+    // 设置封面图
+    ZupThumbRef.current?.setFileComFileFu({
+      fileName: '',
+      filePath: sInfo.thumbPc,
+      thumb: sInfo.thumb
+    })
+  }, [sInfo])
+
+  useEffect(() => {
+    getInfoFu()
+  }, [getInfoFu])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {}, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      const coverUrl1 = ZupThumbRef.current?.fileComFileResFu()
+      // 没有传 封面图
+      // if (!coverUrl1.filePath) return MessageFu.warning('请上传封面图')
+
+      // 线索地址
+      let province = ''
+      let city = ''
+      let region = ''
+
+      if (values.myCity && values.myCity.length) {
+        province = values.myCity[0] || ''
+        city = values.myCity[1] || ''
+        region = values.myCity[2] || ''
+      }
+
+      const obj = {
+        ...values,
+        id: sInfo.id > 0 ? sInfo.id : null,
+        province,
+        city,
+        region,
+        thumb: coverUrl1.thumb || '',
+        thumbPc: coverUrl1.filePath || ''
+      }
+
+      // if (1 + 1 === 2) {
+      //   console.log('------222', obj)
+      //   return
+      // }
+
+      const res = await A1_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success('编辑成功')
+        upTableFu()
+        closeFu()
+      }
+    },
+    [closeFu, sInfo.id, upTableFu]
+  )
+
+  // 获取字典
+  const listObj = useSelector((state: RootState) => state.A3dict.listObj)
+
+  return (
+    <Modal
+      wrapClassName={styles.A1edit}
+      open={true}
+      title='编辑档案'
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='A1Emain'>
+        <Form
+          ref={FormBoxRef}
+          name='basic'
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete='off'
+          scrollToFirstError
+        >
+          <div className='A1ERow A1ERowAll'>
+            <Form.Item label='古桥名称' name='name'>
+              <Input disabled />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <Form.Item
+              label='结构形式'
+              name='type'
+              rules={[{ required: true, message: '请选择结构形式' }]}
+            >
+              <Radio.Group options={A1select1} />
+            </Form.Item>
+
+            <Form.Item label='建造年代' name='age'>
+              <Input maxLength={30} showCount placeholder='请输入内容' />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <Form.Item label='所在位置' name='myCity'>
+              <Cascader options={myCity} placeholder='请选择' allowClear changeOnSelect />
+            </Form.Item>
+            <Form.Item label='详细地址' name='address'>
+              <Input maxLength={50} showCount placeholder='请输入内容' />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <Form.Item label='经度' name='lat'>
+              <InputNumber stringMode placeholder='请输入' style={{ width: 568 }} />
+            </Form.Item>
+            <Form.Item label='纬度' name='lng'>
+              <InputNumber stringMode placeholder='请输入' style={{ width: 568 }} />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <Form.Item label='所属乡镇' name='dictVillageId'>
+              <Select
+                allowClear
+                placeholder='请选择'
+                options={listObj.village.map(v => ({ value: v.id, label: v.name }))}
+              />
+            </Form.Item>
+            <Form.Item label='所属村委会' name='village'>
+              <Input maxLength={30} showCount placeholder='请输入内容' />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <Form.Item label='场景链接' name='sceneLink'>
+              <TextArea placeholder='请输入' maxLength={200} showCount />
+            </Form.Item>
+            <Form.Item label='模型链接' name='moduleLink'>
+              <TextArea placeholder='请输入' maxLength={200} showCount />
+            </Form.Item>
+          </div>
+
+          <div className='A1ERow'>
+            <div className='formRow'>
+              <div className='formLeft'>封面图:</div>
+              <div className='formRight'>
+                <ZupOne
+                  ref={ZupThumbRef}
+                  isLook={false}
+                  fileCheck={false}
+                  size={5}
+                  dirCode='A1record'
+                  myUrl='cms/bridge/upload'
+                  format={['image/jpeg', 'image/png']}
+                  formatTxt='png、jpg和jpeg'
+                  checkTxt='请上传封面图'
+                  upTxt='最多1张'
+                  myType='thumb'
+                />
+              </div>
+            </div>
+            <div className='A3fromRow'>
+              <Form.Item
+                label='排序值'
+                name='sort'
+                rules={[{ required: true, message: '请输入排序值' }]}
+              >
+                <InputNumber
+                  style={{ width: 568 }}
+                  min={1}
+                  max={999}
+                  precision={0}
+                  placeholder='请输入'
+                />
+              </Form.Item>
+              <div className='A3_6Frow'>
+                请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+              </div>
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <Form.Item className='A1EBtn'>
+            <Button type='primary' htmlType='submit'>
+              提交
+            </Button>
+            <br />
+            <br />
+            <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+          </Form.Item>
+        </Form>
+      </div>
+    </Modal>
+  )
+}
+
+const MemoA1edit = React.memo(A1edit)
+
+export default MemoA1edit

+ 103 - 0
后台管理/src/pages/A1record/A1look/index.module.scss

@@ -0,0 +1,103 @@
+.A1look {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 10;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 20px;
+  :global {
+    // 顶部
+    .A1l1 {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      .A1l1Txt {
+        margin-left: 20px;
+        font-size: 20px;
+        font-weight: 700;
+      }
+    }
+
+    // 信息和底部
+    .A1Lmain {
+      height: calc(100% - 50px);
+      margin-top: 25px;
+      overflow-y: auto;
+      .A1Ltop {
+        min-height: 200px;
+      }
+      .A1lTopInfo {
+        display: flex;
+        font-size: 16px;
+        .A1LrowImg {
+          width: 220px;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+        }
+        .A1LrowBox {
+          width: calc(100% - 240px);
+          margin-right: 20px;
+          display: flex;
+          flex-wrap: wrap;
+          justify-content: space-between;
+          align-items: flex-start;
+          .A1Lrow {
+            width: calc(50% - 10px);
+            display: flex;
+            margin-bottom: 15px;
+
+            & > div {
+              &:nth-of-type(1) {
+                font-weight: 700;
+                width: 100px;
+              }
+              &:nth-of-type(2) {
+                width: calc(100% - 100px);
+              }
+            }
+          }
+          .A1LrowAll {
+            width: 100%;
+            & > div {
+              margin-right: 0 !important;
+            }
+          }
+          .A1Lrow2 {
+            margin-bottom: 0;
+            & > div {
+              &:nth-of-type(2) {
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+                a {
+                  color: var(--themeColor) !important;
+                }
+              }
+            }
+          }
+        }
+      }
+
+      // 底部
+      .A1LbtnBox {
+        height: calc(100% - 253px);
+        margin-top: 15px;
+        border-top: 1px solid #ccc;
+        padding-top: 15px;
+
+        .A1Lbtn1 {
+          .ant-btn {
+            margin-right: 20px;
+          }
+        }
+        .A1Lbtn2 {
+          margin-top: 15px;
+        }
+      }
+    }
+  }
+}

+ 160 - 0
后台管理/src/pages/A1record/A1look/index.tsx

@@ -0,0 +1,160 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { A1_APIgetInfo } from '@/store/action/A1record'
+import { Button } from 'antd'
+import { A1ListType, cityResFu } from '../data'
+import ImageLazy from '@/components/ImageLazy'
+import A1edit from '../A1edit'
+import A1tab1introduce from '../A1tab1introduce'
+
+type Props = {
+  sId: number
+  closeFu: (flag: boolean) => void
+}
+
+function A1look({ sId, closeFu }: Props) {
+  const [info, setInfo] = useState({} as A1ListType)
+
+  const getInfoFu = useCallback(async () => {
+    const res = await A1_APIgetInfo(sId)
+    if (res.code === 0) {
+      setInfo(res.data)
+    }
+  }, [sId])
+
+  useEffect(() => {
+    getInfoFu()
+  }, [getInfoFu])
+
+  // 返回的时候是否更新列表
+  const upTableRef = useRef(false)
+
+  const [btnAc, setBtnAc] = useState('古桥介绍')
+
+  // 新增 编辑
+  const [sInfo, setSInfo] = useState({} as A1ListType)
+
+  return (
+    <div className={styles.A1look}>
+      {/* 顶部 */}
+      <div className='A1l1'>
+        <div>
+          <Button onClick={() => closeFu(upTableRef.current)}>返回</Button>{' '}
+          <span className='A1l1Txt'>{info.name}</span>
+        </div>
+        <Button type='primary' onClick={() => setSInfo(info)}>
+          编辑
+        </Button>
+      </div>
+
+      {/* 信息和底部 */}
+      <div className='A1Lmain'>
+        <div className='A1Ltop'>
+          {info.id ? (
+            <div className='A1lTopInfo'>
+              <div className='A1LrowBox'>
+                <div className='A1Lrow'>
+                  <div>建造年代:</div>
+                  <div>{info.age || '(空)'}</div>
+                </div>
+                <div className='A1Lrow'>
+                  <div>结构形式:</div>
+                  <div>{info.type || '(空)'}</div>
+                </div>
+
+                <div className='A1Lrow A1LrowAll'>
+                  <div>所在位置:</div>
+                  <div>
+                    {cityResFu(info)}&emsp;{info.lat || '(空)'}° ,{info.lng || '(空)'}°&emsp;
+                    {info.address}
+                  </div>
+                </div>
+
+                <div className='A1Lrow'>
+                  <div>所属村委会:</div>
+                  <div>{info.village || '(空)'}</div>
+                </div>
+                <div className='A1Lrow'>
+                  <div>所属乡镇:</div>
+                  <div>{info.villageName || '(空)'}</div>
+                </div>
+
+                <div className='A1Lrow A1Lrow2'>
+                  <div>场景链接:</div>
+
+                  <div>
+                    {info.sceneLink ? (
+                      <a target='_blank' href={info.sceneLink} rel='noreferrer'>
+                        {info.sceneLink}
+                      </a>
+                    ) : (
+                      '(空)'
+                    )}
+                  </div>
+                </div>
+                <div className='A1Lrow A1Lrow2'>
+                  <div>模型链接:</div>
+                  <div>
+                    {info.moduleLink ? (
+                      <a target='_blank' href={info.moduleLink} rel='noreferrer'>
+                        {info.moduleLink}
+                      </a>
+                    ) : (
+                      '(空)'
+                    )}
+                  </div>
+                </div>
+              </div>
+
+              <div className='A1LrowImg'>
+                <ImageLazy
+                  width={200}
+                  height={200}
+                  src={info.thumb || info.thumbPc}
+                  srcBig={info.thumbPc || info.thumb}
+                />
+              </div>
+            </div>
+          ) : null}
+        </div>
+
+        {/* 底部 */}
+        <div className='A1LbtnBox'>
+          <div className='A1Lbtn1'>
+            {['古桥介绍', '附件管理', '维护记录'].map(v => (
+              <Button
+                key={v}
+                type={btnAc === v ? 'primary' : 'default'}
+                size='large'
+                onClick={() => setBtnAc(v)}
+              >
+                {v}
+              </Button>
+            ))}
+            <span>附件管理/维护记录中的内容仅供内部使用,不对用户端公开展示。</span>
+          </div>
+
+          <div className='A1Lbtn2'>
+            {btnAc === '古桥介绍' ? <A1tab1introduce sInfo={info} upInfoFu={getInfoFu} /> : null}
+          </div>
+        </div>
+      </div>
+
+      {/* 编辑 */}
+      {sInfo.id ? (
+        <A1edit
+          sInfo={sInfo}
+          closeFu={() => setSInfo({} as A1ListType)}
+          upTableFu={() => {
+            upTableRef.current = true
+            getInfoFu()
+          }}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA1look = React.memo(A1look)
+
+export default MemoA1look

+ 53 - 0
后台管理/src/pages/A1record/A1tab1introduce/A1T1add/index.module.scss

@@ -0,0 +1,53 @@
+.A1T1add {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+
+    .ant-modal {
+      width: 1200px !important;
+      min-width: 1200px !important;
+    }
+    .ant-modal-header {
+      border-bottom: 1px solid #ccc !important;
+      padding-bottom: 15px !important;
+    }
+    .A1T1aMain {
+      padding-top: 15px;
+      height: 500px;
+      overflow-y: auto;
+      position: relative;
+    }
+
+    .formRow {
+      display: flex;
+
+      .formLeft {
+        position: relative;
+        top: 3px;
+        width: 81px;
+        text-align: right;
+
+        & > span {
+          color: #ff4d4f;
+        }
+      }
+
+      .formRight {
+        width: calc(100% - 81px);
+        position: relative;
+      }
+      .formRight2 {
+        margin-top: -15px;
+      }
+    }
+    .formRow2 {
+      margin-bottom: -20px;
+    }
+
+    .A1T1aBtn {
+      margin-left: 81px;
+      margin-top: 24px;
+    }
+  }
+}

+ 134 - 0
后台管理/src/pages/A1record/A1tab1introduce/A1T1add/index.tsx

@@ -0,0 +1,134 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Modal } from 'antd'
+import { A1ListType } from '../../data'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import ZupTypes from '@/components/ZupTypes'
+import ZupVideos from '@/components/ZupVideos'
+import { A1_APIsave } from '@/store/action/A1record'
+import { MessageFu } from '@/utils/message'
+import TextArea from 'antd/es/input/TextArea'
+
+type Props = {
+  sInfo: A1ListType
+  upSonFu: () => void
+  closeFu: () => void
+}
+
+function A1T1add({ sInfo, closeFu, upSonFu }: Props) {
+  // 多张图片的ref
+  const ZupImgsRef = useRef<any>(null)
+
+  // 多个视频的ref
+  const ZupVideosRef = useRef<any>(null)
+
+  const [intro, setIntro] = useState('')
+
+  useEffect(() => {
+    setIntro(sInfo.intro)
+
+    const file = sInfo.img || []
+    // 传给 附件 组件的
+    const sonInfo = {
+      type: 'img',
+      fileList: file
+    }
+    ZupImgsRef.current?.setFileComFileFu(sonInfo)
+
+    ZupVideosRef.current?.setFileComFileFu(sInfo.video || [])
+  }, [sInfo])
+
+  const btnOk = useCallback(async () => {
+    // 多张图片
+    const { sonFileIds, sonIsOk } = ZupImgsRef.current?.fileComFileResFu()
+
+    // 多个视频
+    const list = ZupVideosRef.current?.fileComFileResFu() || []
+
+    const obj = {
+      ...sInfo,
+      intro,
+      imgIds: sonIsOk ? '' : sonFileIds.join(','),
+      videoIds: list.map((v: any) => v.id).join(',')
+    }
+    const res = await A1_APIsave(obj)
+    if (res.code === 0) {
+      MessageFu.success('编辑成功')
+      upSonFu()
+      closeFu()
+    }
+  }, [closeFu, intro, sInfo, upSonFu])
+
+  return (
+    <Modal
+      wrapClassName={styles.A1T1add}
+      open={true}
+      title='编辑介绍'
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <div className='A1T1aMain'>
+        <div className='formRow'>
+          <div className='formLeft'>古桥简介:</div>
+          <div className='formRight'>
+            <TextArea
+              maxLength={2000}
+              showCount
+              placeholder='请输入'
+              value={intro}
+              onChange={e => setIntro(e.target.value)}
+            />
+          </div>
+        </div>
+        <br />
+
+        {/* 多个图片 */}
+        <div className='formRow'>
+          <div className='formLeft'>图片:</div>
+          <div className='formRight formRight2'>
+            <ZupTypes
+              ref={ZupImgsRef}
+              isLook={false}
+              fileCheck={false}
+              selecFlag='图片'
+              imgSize={5}
+              imgLength={50}
+              dirCode='A1record'
+              myUrl='cms/bridge/upload'
+              isTypeShow={true}
+            />
+          </div>
+        </div>
+
+        {/* 多个视频 */}
+        <div className='formRow formRow2'>
+          <div className='formLeft'>视频:</div>
+          <div className='formRight'>
+            <ZupVideos
+              isLook={false}
+              size={500}
+              fileNum={50}
+              dirCode='A1recordVideo'
+              myUrl='cms/bridge/upload'
+              upTxt=';数量不超过50个。'
+              ref={ZupVideosRef}
+            />
+          </div>
+        </div>
+      </div>
+
+      <div className='A1T1aBtn'>
+        <Button type='primary' onClick={btnOk}>
+          提交
+        </Button>
+        &emsp;
+        <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+      </div>
+    </Modal>
+  )
+}
+
+const MemoA1T1add = React.memo(A1T1add)
+
+export default MemoA1T1add

+ 35 - 0
后台管理/src/pages/A1record/A1tab1introduce/index.module.scss

@@ -0,0 +1,35 @@
+.A1tab1introduce {
+  position: relative;
+  padding-top: 10px;
+  :global {
+    .A1T1btn {
+      position: absolute;
+      top: 0px;
+      right: 0px;
+    }
+    .A1T1box {
+      width: calc(100% - 100px);
+      font-size: 16px;
+      margin-bottom: 20px;
+      display: flex;
+      .A1T1box1 {
+        width: 100px;
+        font-weight: 700;
+      }
+      .A1T1box2 {
+        width: calc(100% - 100px);
+      }
+      .A1T1box2_2 {
+        word-wrap: break-word;
+        white-space: pre-wrap;
+        // 图片查看调整
+        .ZTboxImgMain {
+          margin-top: 0;
+        }
+        .ZcheckTxt {
+          display: none;
+        }
+      }
+    }
+  }
+}

+ 98 - 0
后台管理/src/pages/A1record/A1tab1introduce/index.tsx

@@ -0,0 +1,98 @@
+import React, { useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { A1ListType } from '../data'
+import { Button } from 'antd'
+import ZupTypes from '@/components/ZupTypes'
+import ZupVideos from '@/components/ZupVideos'
+import A1T1add from './A1T1add'
+
+type Props = {
+  sInfo: A1ListType
+  upInfoFu: () => void
+}
+
+function A1tab1introduce({ sInfo, upInfoFu }: Props) {
+  // 多张图片的ref
+  const ZupImgsRef = useRef<any>(null)
+
+  // 多个视频的ref
+  const ZupVideosRef = useRef<any>(null)
+
+  useEffect(() => {
+    const file = sInfo.img || []
+    // 传给 附件 组件的
+    const sonInfo = {
+      type: 'img',
+      fileList: file
+    }
+    ZupImgsRef.current?.setFileComFileFu(sonInfo)
+    ZupVideosRef.current?.setFileComFileFu(sInfo.video || [])
+  }, [sInfo])
+
+  const [editShow, setEditShow] = useState(false)
+
+  return (
+    <div className={styles.A1tab1introduce}>
+      <div className='A1T1btn'>
+        <Button type='primary' onClick={() => setEditShow(true)}>
+          编辑
+        </Button>
+      </div>
+
+      {sInfo.id ? (
+        <>
+          <div className='A1T1box'>
+            <div className='A1T1box1'>简介:</div>
+            <div className='A1T1box2 A1T1box2_2'>{sInfo.intro || '(空)'}</div>
+          </div>
+
+          <div className='A1T1box'>
+            <div className='A1T1box1'>图片:</div>
+            <div className='A1T1box2 A1T1box2_2'>
+              {sInfo.img.length === 0 ? (
+                '(空)'
+              ) : (
+                <ZupTypes
+                  ref={ZupImgsRef}
+                  isLook={true}
+                  fileCheck={false}
+                  selecFlag='图片'
+                  imgSize={5}
+                  imgLength={50}
+                  dirCode='A1record'
+                  myUrl='cms/martyr/upload'
+                  isTypeShow={true}
+                />
+              )}
+            </div>
+          </div>
+
+          <div className='A1T1box'>
+            <div className='A1T1box1'>视频:</div>
+            <div className='A1T1box2'>
+              {' '}
+              <ZupVideos
+                isLook={true}
+                size={500}
+                fileNum={50}
+                dirCode='A1recordVideo'
+                myUrl='cms/martyr/upload'
+                upTxt=';数量不超过50个。'
+                ref={ZupVideosRef}
+              />
+            </div>
+          </div>
+        </>
+      ) : null}
+
+      {/* 点击编辑 */}
+      {editShow ? (
+        <A1T1add sInfo={sInfo} upSonFu={upInfoFu} closeFu={() => setEditShow(false)} />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA1tab1introduce = React.memo(A1tab1introduce)
+
+export default MemoA1tab1introduce

+ 70 - 0
后台管理/src/pages/A1record/data.ts

@@ -0,0 +1,70 @@
+export type A1ListType = {
+  address: string
+  age: string
+  city: string
+  createTime: string
+  creatorName: string
+  dictVillageId: string
+  id: number
+  imgIds: string
+  intro: string
+  moduleLink: string
+  name: string
+  province: string
+  region: string
+  sceneLink: string
+  sort: number
+  thumb: string
+  thumbPc: string
+  type: string
+  updateTime: string
+  videoIds: string
+  village: string
+  villageName: string
+  lat: string
+  lng: string
+
+  img: []
+  video: []
+}
+
+export const A1select1 = [
+  {
+    value: '木梁桥',
+    label: '木梁桥'
+  },
+  {
+    value: '铁链吊桥',
+    label: '铁链吊桥'
+  },
+  {
+    value: '石拱桥',
+    label: '石拱桥'
+  },
+  {
+    value: '刚桁桥',
+    label: '刚桁桥'
+  },
+  {
+    value: '藤桥',
+    label: '藤桥'
+  }
+]
+
+// 处理所在位置
+export const cityResFu = (item: A1ListType) => {
+  let res = '(空)'
+
+  const val1 = 'province'
+  const val2 = 'city'
+  const val3 = 'region'
+  // const val4 = 'lat'
+  // const val5 = 'lng'
+  // const val6 = 'address'
+
+  if (item[val1]) res = item[val1]
+  if (item[val2]) res += ` / ${item[val2]}`
+  if (item[val3]) res += ` / ${item[val3]}`
+
+  return res
+}

+ 49 - 0
后台管理/src/pages/A1record/index.module.scss

@@ -1,4 +1,53 @@
 .A1record {
+  position: relative;
   :global {
+    .A1top {
+      padding: 15px 24px;
+      border-radius: 10px;
+      background-color: #fff;
+      display: flex;
+      justify-content: space-between;
+
+      .A1topLeft {
+        display: flex;
+
+        & > div {
+          margin-right: 24px;
+          .ant-btn {
+            margin-right: 10px;
+          }
+        }
+      }
+    }
+
+    .A1tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 77px);
+      background-color: #fff;
+
+      .ant-table-body {
+        height: 645px;
+        overflow: auto !important;
+      }
+
+      .ant-table-cell {
+        padding: 8px !important;
+      }
+
+      .A1tableFen {
+        & > p {
+          margin-top: 5px;
+          text-decoration: underline;
+          color: var(--themeColor);
+          cursor: pointer;
+        }
+      }
+
+      .A1Tit {
+        cursor: pointer;
+      }
+    }
   }
 }

+ 135 - 2
后台管理/src/pages/A1record/index.tsx

@@ -1,9 +1,142 @@
-import React from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
+import { useDispatch, useSelector } from 'react-redux'
+import { A1_APIgetList } from '@/store/action/A1record'
+import { RootState } from '@/store'
+import { Button, Input, Select } from 'antd'
+import { A1select1 } from './data'
+import MyTable from '@/components/MyTable'
+import { A1tableC } from '@/utils/tableData'
+import A1look from './A1look'
+
+const baseFromData = {
+  pageNum: 1,
+  pageSize: 10,
+  searchKey: '',
+  type: ''
+}
+
 function A1record() {
+  const dispatch = useDispatch()
+
+  const [fromData, setFromData] = useState(baseFromData)
+
+  const getListFu = useCallback(() => {
+    dispatch(A1_APIgetList(fromData))
+  }, [dispatch, fromData])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  const tableInfo = useSelector((state: RootState) => state.A1record.tableInfo)
+
+  // 作品名称的输入
+  const timeRef = useRef(-1)
+  const [inputKey, setInputKey] = useState(0)
+  const fromKeyChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value, pageNum: 1 })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 下拉框的改变
+  const selectChangeFu = useCallback(
+    (value: any, key: 'type') => {
+      setFromData({ ...fromData, [key]: value, pageNum: 1 })
+    },
+    [fromData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...baseFromData })
+  }, [])
+
+  // 点击查看
+  const [lookId, setLookId] = useState(0)
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: any) => {
+          return (
+            <>
+              <Button size='small' type='text' onClick={() => setLookId(item.id)}>
+                查看
+              </Button>
+            </>
+          )
+        }
+      }
+    ]
+  }, [])
+
   return (
     <div className={styles.A1record}>
-      <div className='pageTitle'>古桥档案</div>
+      <div className='pageTitle'>古桥档案{lookId ? ' - 查看' : ''}</div>
+
+      {/* 顶部筛选 */}
+      <div className='A1top'>
+        <div className='A1topLeft'>
+          <div>
+            <Input
+              key={inputKey}
+              maxLength={30}
+              showCount
+              style={{ width: 400 }}
+              placeholder='请输入古桥名称/建造年代/详细地址'
+              allowClear
+              onChange={e => fromKeyChangeFu(e, 'searchKey')}
+            />
+          </div>
+
+          <div>
+            <Select
+              allowClear
+              style={{ width: 150 }}
+              placeholder='结构类型'
+              value={fromData.type || null}
+              onChange={e => selectChangeFu(e, 'type')}
+              options={A1select1}
+            />
+          </div>
+        </div>
+
+        <div>
+          <Button onClick={resetSelectFu}>重置</Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className='A1tableBox'>
+        <MyTable
+          yHeight={645}
+          list={tableInfo.list}
+          columnsTemp={A1tableC}
+          lastBtn={tableLastBtn}
+          pageNum={fromData.pageNum}
+          pageSize={fromData.pageSize}
+          total={tableInfo.total}
+          onChange={(pageNum, pageSize) => setFromData({ ...fromData, pageNum, pageSize })}
+        />
+      </div>
+
+      {lookId ? (
+        <A1look
+          sId={lookId}
+          closeFu={flag => {
+            setLookId(0)
+            if (flag) getListFu()
+          }}
+        />
+      ) : null}
     </div>
   )
 }

+ 26 - 0
后台管理/src/pages/A3dict/A3add/index.module.scss

@@ -0,0 +1,26 @@
+.A3add {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 700px !important;
+      .A3fromRow {
+        position: relative;
+        width: 700px;
+
+        .A3_6Frow {
+          position: absolute;
+          left: 170px;
+          top: 5px;
+          color: #999;
+          font-size: 12px;
+        }
+      }
+      .A3Abtn {
+        position: relative;
+        left: 67px;
+      }
+    }
+  }
+}

+ 113 - 0
后台管理/src/pages/A3dict/A3add/index.tsx

@@ -0,0 +1,113 @@
+import React, { useCallback, useEffect, useRef } from 'react'
+import styles from './index.module.scss'
+import { Button, Form, FormInstance, Input, InputNumber, Modal } from 'antd'
+import { A3DictType } from '../data'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A3_APIsave } from '@/store/action/A3dict'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  sInfo: A3DictType
+  closeFu: () => void
+  upTableFu: () => void
+}
+
+function A3add({ sInfo, closeFu, upTableFu }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 编辑进来获取详情
+  const getInfoFu = useCallback(async (info: A3DictType) => {
+    FormBoxRef.current?.setFieldsValue(info)
+  }, [])
+
+  useEffect(() => {
+    if (sInfo.id > 0) getInfoFu(sInfo)
+    else FormBoxRef.current?.setFieldsValue({ sort: 999 })
+  }, [getInfoFu, sInfo])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {}, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      const obj = {
+        ...sInfo,
+        id: sInfo.id > 0 ? sInfo.id : null,
+        name: values.name,
+        sort: values.sort
+      }
+
+      // if (1 + 1 === 2) {
+      //   console.log('------222', obj)
+      //   return
+      // }
+
+      const res = await A3_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success(`${sInfo.id > 0 ? '编辑' : '新增'}成功`)
+        upTableFu()
+        closeFu()
+      }
+    },
+    [closeFu, sInfo, upTableFu]
+  )
+
+  return (
+    <Modal
+      wrapClassName={styles.A3add}
+      open={true}
+      title={sInfo.id > 0 ? '编辑字典' : '新增字典'}
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <Form
+        ref={FormBoxRef}
+        name='basic'
+        onFinish={onFinish}
+        onFinishFailed={onFinishFailed}
+        autoComplete='off'
+        scrollToFirstError
+      >
+        <Form.Item
+          label='字典值'
+          name='name'
+          rules={[{ required: true, message: '请输入字典值' }]}
+          // getValueFromEvent={(e) => e.target.value.replace(/\s+/g, "")}
+          getValueFromEvent={e => e.target.value.trim()}
+        >
+          <Input maxLength={30} showCount placeholder='请输入内容' />
+        </Form.Item>
+
+        <div className='A3fromRow'>
+          <Form.Item
+            label='排序值'
+            name='sort'
+            rules={[{ required: true, message: '请输入排序值' }]}
+          >
+            <InputNumber min={1} max={999} precision={0} placeholder='请输入' />
+          </Form.Item>
+          <div className='A3_6Frow'>
+            请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+          </div>
+        </div>
+
+        {/* 确定和取消按钮 */}
+        <Form.Item className='A3Abtn'>
+          <Button type='primary' htmlType='submit'>
+            提交
+          </Button>
+          &emsp;
+          <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+        </Form.Item>
+      </Form>
+    </Modal>
+  )
+}
+
+const MemoA3add = React.memo(A3add)
+
+export default MemoA3add

+ 13 - 0
后台管理/src/pages/A3dict/data.ts

@@ -0,0 +1,13 @@
+export type A3DictRowType = 'effect' | 'maintain' | 'village'
+
+export type A3DictType = {
+  id: number
+  name: string
+  type: A3DictRowType
+}
+
+export type A3ObjType = {
+  effect: A3DictType[]
+  maintain: A3DictType[]
+  village: A3DictType[]
+}

+ 24 - 0
后台管理/src/pages/A3dict/index.module.scss

@@ -1,4 +1,28 @@
 .A3dict {
+  background-color: #fff;
+  border-radius: 10px;
+  display: flex;
+  justify-content: space-between;
+
+  overflow-y: auto;
+  padding: 20px;
   :global {
+    .A3box {
+      border: 1px solid #ccc;
+      border-radius: 8px;
+      padding: 10px;
+      width: 32.5%;
+      .A3Tit {
+        display: flex;
+        justify-content: space-between;
+        & > h3 {
+          font-size: 18px;
+        }
+        margin-bottom: 15px;
+      }
+      .ant-table-cell {
+        padding: 8px !important;
+      }
+    }
   }
 }

+ 106 - 1
后台管理/src/pages/A3dict/index.tsx

@@ -1,9 +1,114 @@
-import React from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
 import styles from './index.module.scss'
+import MyTable from '@/components/MyTable'
+import { A3DictRowType, A3DictType } from './data'
+import { useDispatch, useSelector } from 'react-redux'
+import { A3_APIdel, A3_APIgetList } from '@/store/action/A3dict'
+import { A3tableC } from '@/utils/tableData'
+import { Button } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { MessageFu } from '@/utils/message'
+import { RootState } from '@/store'
+import A3add from './A3add'
+
+const titArr: { name: string; type: A3DictRowType }[] = [
+  {
+    name: '附件用途',
+    type: 'effect'
+  },
+  {
+    name: '维护类型',
+    type: 'maintain'
+  },
+  {
+    name: '乡镇',
+    type: 'village'
+  }
+]
+
 function A3dict() {
+  const dispatch = useDispatch()
+
+  const getListFu = useCallback(async () => {
+    dispatch(A3_APIgetList())
+  }, [dispatch])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  // 表格相关---------------
+
+  const listObj = useSelector((state: RootState) => state.A3dict.listObj)
+
+  // 新增编辑
+  const [addInfo, setAddInfo] = useState({} as A3DictType)
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A3_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  const tableLastBtn = useCallback(
+    (type: A3DictRowType) => {
+      return [
+        {
+          title: '操作',
+          render: (item: A3DictType) =>
+            item.name === '其他' ? (
+              '-'
+            ) : (
+              <>
+                <Button size='small' type='text' onClick={() => setAddInfo(item)}>
+                  编辑
+                </Button>
+                <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+              </>
+            )
+        }
+      ]
+    },
+    [delTableFu]
+  )
+
   return (
     <div className={styles.A3dict}>
       <div className='pageTitle'>字典管理</div>
+
+      {titArr.map(item => (
+        <div className='A3box' key={item.name}>
+          <div className='A3Tit'>
+            <h3>{item.name}</h3>
+            <Button
+              type='primary'
+              onClick={() => setAddInfo({ type: item.type, id: -1 } as A3DictType)}
+            >
+              新增
+            </Button>
+          </div>
+
+          <MyTable
+            classKey={`A3box${item.type}`}
+            yHeight={672}
+            list={listObj[item.type]}
+            columnsTemp={A3tableC}
+            lastBtn={tableLastBtn(item.type)}
+            pagingInfo={false}
+          />
+        </div>
+      ))}
+
+      {/* 新增和编辑 */}
+      {addInfo.id ? (
+        <A3add sInfo={addInfo} closeFu={() => setAddInfo({} as A3DictType)} upTableFu={getListFu} />
+      ) : null}
     </div>
   )
 }

+ 3 - 11
后台管理/src/pages/Layout/index.module.scss

@@ -13,14 +13,6 @@
       .layoutLeftTop {
         text-align: center;
         padding: 20px 20px 20px;
-        color: #fff;
-        & > h3 {
-          font-size: 18px;
-          margin-bottom: 10px;
-        }
-        & > p {
-          font-size: 16px;
-        }
       }
 
       .layoutLeftMain {
@@ -153,9 +145,9 @@
           & > div {
             width: 100%;
             height: 100%;
-            background-color: #fff;
-            border-radius: 10px;
-            padding: 20px;
+            // background-color: #fff;
+            // border-radius: 10px;
+            // padding: 20px;
           }
         }
       }

+ 12 - 2
后台管理/src/pages/Layout/index.tsx

@@ -17,9 +17,19 @@ import NotFound from '@/components/NotFound'
 import { RouterType, RouterTypeRow } from '@/types'
 import tabLeftArr from './data'
 import MyPopconfirm from '@/components/MyPopconfirm'
+import { useDispatch } from 'react-redux'
+import { A3_APIgetList } from '@/store/action/A3dict'
 // import { Z1_APIgetAuthByUserId } from "@/store/action/Z1user";
 
 function Layout() {
+  // 获取字典值----------------
+  const dispatch = useDispatch()
+
+  useEffect(() => {
+    dispatch(A3_APIgetList())
+  }, [dispatch])
+  // 获取字典值----------------end
+
   // 当前路径选中的左侧菜单
   const location = useLocation()
   const [path, setPath] = useState('')
@@ -47,6 +57,7 @@ function Layout() {
     //   },
     // ];
 
+    // 待完善
     const isOkIdArr: number[] = [101, 102, 103, 104, 105]
     // const tempList: UserListType[] = res.data || [];
     // console.log(123, tempList);
@@ -152,8 +163,7 @@ function Layout() {
       {/* 左边 */}
       <div className='layoutLeft'>
         <div className='layoutLeftTop'>
-          <h3>沘江古桥一张图</h3>
-          <p>信息管理后台</p>
+          <img src={require('@/assets/img/logo.png')} alt='' />
         </div>
         {/* 左边主体 */}
         <div className='layoutLeftMain'>

+ 6 - 3
后台管理/src/pages/Login/index.module.scss

@@ -4,6 +4,8 @@
   display: flex;
   justify-content: space-between;
   padding: 8%;
+  background-image: url('../../assets/img/bg.jpg');
+  background-size: 100% 100%;
 
   :global {
     .mainLeft {
@@ -19,18 +21,19 @@
     }
 
     .mainRight {
-      border: 2px solid var(--themeColor);
       border-radius: 6px;
       width: 530px;
       display: flex;
       flex-direction: column;
       justify-content: center;
       align-items: center;
+      background-color: rgba(255, 255, 255, 0.8);
+      backdrop-filter: blur(4px);
 
       .LogoImg {
         padding-top: 20px;
-        color: var(--themeColor);
-        font-size: 30px;
+        color: var(--themeColor2);
+        font-size: 26px;
         font-weight: 700;
         letter-spacing: 4px;
         line-height: 50px;

+ 1 - 0
后台管理/src/pages/Z1user/Z1auth.tsx

@@ -22,6 +22,7 @@ type Props = {
 }
 
 function Z1auth({ authInfo, closeFu }: Props) {
+  // 待完善
   const getAuthByUserIdFu = useCallback(async () => {
     // const res = await Z1_APIgetAuthByUserId(authInfo.id);
     // if (res.code === 0) {

+ 5 - 7
后台管理/src/pages/Z1user/index.tsx

@@ -113,15 +113,13 @@ function Z1user() {
                   </Button>
                 }
               />
-              {/* <Button
-                size="small"
-                type="text"
-                onClick={() =>
-                  setAuthInfo({ id: item.id, name: item.userName })
-                }
+              <Button
+                size='small'
+                type='text'
+                onClick={() => setAuthInfo({ id: item.id, name: item.userName })}
               >
                 权限管理
-              </Button> */}
+              </Button>
               <Button size='small' type='text' onClick={() => openEditPageFu(item.id)}>
                 编辑
               </Button>

+ 42 - 0
后台管理/src/store/action/A1record.ts

@@ -0,0 +1,42 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+/**
+ * 古桥档案-获取列表
+ */
+export const A1_APIgetList = (data: any, flag = false): any => {
+  if (flag) return http.post('cms/bridge/pageList', data)
+  else {
+    return async (dispatch: AppDispatch) => {
+      const res = await http.post('cms/bridge/pageList', data)
+      if (res.code === 0) {
+        const obj = {
+          list: res.data.records,
+          total: res.data.total
+        }
+
+        dispatch({ type: 'A1/getList', payload: obj })
+      }
+    }
+  }
+}
+
+/**
+ * 古桥档案-删除
+ */
+export const A1_APIdel = (id: number) => {
+  return http.get(`cms/bridge/remove/${id}`)
+}
+
+/**
+ * 古桥档案-新增
+ */
+export const A1_APIsave = (data: any) => {
+  return http.post('cms/bridge/save', data)
+}
+
+/**
+ * 古桥档案-获取详情
+ */
+export const A1_APIgetInfo = (id: number) => {
+  return http.get(`cms/bridge/detail/${id}`)
+}

+ 41 - 0
后台管理/src/store/action/A3dict.ts

@@ -0,0 +1,41 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+import { A3DictType, A3ObjType } from '@/pages/A3dict/data'
+/**
+ * 字典管理-获取列表
+ */
+export const A3_APIgetList = (): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get('cms/dict/getList')
+    if (res.code === 0) {
+      // const obj = {
+      //   list: res.data.records,
+      //   total: res.data.total
+      // }
+
+      const list: A3DictType[] = res.data || []
+
+      const obj = {} as A3ObjType
+
+      list.forEach(v => {
+        if (obj[v.type]) obj[v.type].push(v)
+        else obj[v.type] = [v]
+      })
+      dispatch({ type: 'A3/getList', payload: obj })
+    }
+  }
+}
+
+/**
+ * 字典管理-删除
+ */
+export const A3_APIdel = (id: number) => {
+  return http.get(`cms/dict/remove/${id}`)
+}
+
+/**
+ * 字典管理-新增
+ */
+export const A3_APIsave = (data: any) => {
+  return http.post('cms/dict/save', data)
+}

+ 28 - 0
后台管理/src/store/reducer/A1record.ts

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

+ 25 - 0
后台管理/src/store/reducer/A3dict.ts

@@ -0,0 +1,25 @@
+import { A3ObjType } from '@/pages/A3dict/data'
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  listObj: {} as A3ObjType
+}
+
+// 定义 action 类型
+type Props = {
+  type: 'A3/getList'
+  payload: A3ObjType
+}
+
+// reducer
+export default function userReducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case 'A3/getList':
+      return { ...state, listObj: action.payload }
+
+    default:
+      return state
+  }
+}

+ 4 - 0
后台管理/src/store/reducer/index.ts

@@ -3,6 +3,8 @@ import { combineReducers } from 'redux'
 
 // 导入 登录 模块的 reducer
 import A0Layout from './layout'
+import A1record from './A1record'
+import A3dict from './A3dict'
 
 import Z1user from './Z1user'
 import Z2log from './Z2log'
@@ -10,6 +12,8 @@ import Z2log from './Z2log'
 // 合并 reducer
 const rootReducer = combineReducers({
   A0Layout,
+  A1record,
+  A3dict,
   Z1user,
   Z2log
 })

+ 9 - 8
后台管理/src/types/declaration.d.ts

@@ -1,8 +1,9 @@
-declare module "history";
-declare module "*.scss";
-declare module "*.png";
-declare module "*.jpg";
-declare module "*.gif";
-declare module "*.svg";
-declare module "js-export-excel";
-declare module 'braft-utils';
+declare module 'history'
+declare module '*.scss'
+declare module '*.png'
+declare module '*.jpg'
+declare module '*.gif'
+declare module '*.svg'
+declare module 'js-export-excel'
+declare module 'braft-utils'
+declare const myCityTemp

+ 9 - 1
后台管理/src/utils/history.ts

@@ -1,3 +1,11 @@
-import { createHashHistory  } from 'history'
+import { createHashHistory } from 'history'
 const history = createHashHistory()
 export default history
+
+type Option = {
+  value: string
+  label: string
+  children?: Option[]
+}
+
+export const myCity: Option[] = myCityTemp

+ 2 - 2
后台管理/src/utils/http.ts

@@ -7,8 +7,8 @@ import { domShowFu } from './domShow'
 
 const envFlag = process.env.NODE_ENV === 'development'
 
-const baseUrlTemp = 'https://sit-hefeibwg.4dage.com' // 测试环境
-// const baseUrlTemp = 'http://192.168.20.61:8104' // 线下环境
+// const baseUrlTemp = 'xxxxxxxxxxxx' // 测试环境
+const baseUrlTemp = 'http://192.168.20.61:8105' // 线下环境
 
 const baseFlag = baseUrlTemp.includes('https://')
 

+ 20 - 8
后台管理/src/utils/tableData.ts

@@ -14,14 +14,26 @@
 //     ["text", "创建日期",'description', 50,A],
 //   ];
 
-// export const A1tableC = [
-//   ['img', '图片', 'thumb'],
-//   ['txt', '作品名称', 'name'],
-//   ['txt', '创作时间', 'time'],
-//   ['txt', '作品材质', 'texture'],
-//   ['txt', '排序值', 'sort']
-//   // ['text', '简介', 'intro', 100]
-// ]
+export const A1tableC = [
+  ['txt', '古桥名称', 'name'],
+  ['img', '封面图', 'thumb'],
+  ['txt', '建造年代', 'age'],
+  ['txt', '结构形式', 'type'],
+  ['txt', '所属乡镇', 'villageName'],
+  ['txt', '所属村委会', 'village'],
+  ['text', '场景链接', 'sceneLink', 30, 'A'],
+  ['text', '模型链接', 'moduleLink', 30, 'A'],
+  ['txt', '编辑时间', 'updateTime'],
+  ['txt', '编辑人', 'creatorName']
+  // ['cityAll', '所属乡镇', 'province', 'city', 'region', 'address'],
+
+  //
+]
+
+export const A3tableC = [
+  ['txt', '字典值', 'name'],
+  ['txt', '排序值', 'sort']
+]
 
 export const Z1tableC = [
   ['txt', '用户名', 'userName'],