浏览代码

烈士档案 列表和新增/编辑

shaogen1995 1 周之前
父节点
当前提交
1a1019f6c8

文件差异内容过多而无法显示
+ 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>

+ 1 - 0
后台管理/public/sss.js

@@ -0,0 +1 @@
+const aa = 

+ 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)

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

@@ -0,0 +1,95 @@
+.A1add {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 100;
+  width: 100%;
+  height: 100%;
+  border-radius: 10px;
+  background-color: rgba(0, 0, 0, 0.6);
+  padding: 50px 100px;
+  overflow: hidden;
+  :global {
+    .A1addMain {
+      width: 100%;
+      height: 100%;
+      background-color: #fff;
+      border-radius: 10px;
+      position: relative;
+      padding: 15px;
+
+      .A1aTit {
+        font-weight: 700;
+        font-size: 16px;
+        color: var(--themeColor);
+        margin-bottom: 15px;
+      }
+
+      .A1accc {
+        width: calc(100% - 100px);
+        height: calc(100% - 30px);
+        overflow: auto;
+
+        .ant-form-item-label {
+          width: 81px;
+        }
+
+        .A1aRow {
+          display: flex;
+          justify-content: space-between;
+          .ant-form-item {
+            width: 48%;
+          }
+
+          .A1aRowSon {
+            width: 48%;
+            display: flex;
+            .ant-btn {
+              margin-left: 10px;
+            }
+            .ant-form-item {
+              width: calc(100% - 74px);
+            }
+          }
+        }
+        .A1aRowAll {
+          width: 100%;
+          .ant-form-item {
+            width: 100%;
+          }
+        }
+
+        .formRow {
+          display: flex;
+
+          .formLeft {
+            position: relative;
+            top: 3px;
+            width: 81px;
+            text-align: right;
+
+            & > span {
+              color: #ff4d4f;
+            }
+          }
+
+          .formRight {
+            width: calc(100% - 81px);
+            position: relative;
+            top: -15px;
+          }
+        }
+        .formRow2 {
+          margin-bottom: -20px;
+        }
+
+        .A1aBtn {
+          position: absolute;
+          top: 50%;
+          right: 20px;
+          transform: translateY(-50%);
+        }
+      }
+    }
+  }
+}

+ 336 - 0
后台管理/src/pages/A1record/A1add/index.tsx

@@ -0,0 +1,336 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Cascader, DatePicker, Form, FormInstance, Input, Radio, Select } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { nationSelect } from '../data'
+import { useDispatch, useSelector } from 'react-redux'
+import { A1_APIgetInfo, A1_APIgetNumList, A1_APIsave } from '@/store/action/A1record'
+import { RootState } from '@/store'
+import A1num from '../A1num'
+import { myCity } from '@/utils/history'
+import TextArea from 'antd/es/input/TextArea'
+import ZupTypes from '@/components/ZupTypes'
+import ZupVideos from '@/components/ZupVideos'
+import dayjs from 'dayjs'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  sId: number
+  closeFu: () => void
+  addTableFu: () => void
+  upTableFu: () => void
+}
+
+function A1add({ sId, closeFu, addTableFu, upTableFu }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 多张图片的ref
+  const ZupImgsRef = useRef<any>(null)
+
+  // 多个视频的ref
+  const ZupVideosRef = useRef<any>(null)
+
+  // 编辑进来获取详情
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await A1_APIgetInfo(id)
+    if (res.code === 0) {
+      const obj = res.data
+
+      if (obj.dictPanId) fanHaoRef.current = obj.dictPanId
+
+      // 生卒日期
+      if (obj.dateStart) obj.dateStart = dayjs(obj.dateStart)
+      if (obj.dateEnd) obj.dateEnd = dayjs(obj.dateEnd)
+
+      // 籍贯
+      if (obj.nativeProvince) obj.myNative = [obj.nativeProvince]
+      if (obj.nativeCity) obj.myNative.push(obj.nativeCity)
+      if (obj.nativeRegion) obj.myNative.push(obj.nativeRegion)
+
+      // 牺牲地
+      if (obj.lossProvince) obj.myLoss = [obj.lossProvince]
+      if (obj.lossCity) obj.myLoss.push(obj.lossCity)
+      if (obj.lossRegion) obj.myLoss.push(obj.lossRegion)
+
+      FormBoxRef.current?.setFieldsValue(obj)
+    }
+
+    const file = res.data.img || []
+    // 传给 附件 组件的
+    const sonInfo = {
+      type: 'img',
+      fileList: file
+    }
+    ZupImgsRef.current?.setFileComFileFu(sonInfo)
+    ZupVideosRef.current?.setFileComFileFu(res.data.video || [])
+  }, [])
+
+  useEffect(() => {
+    if (sId > 0) getInfoFu(sId)
+    else FormBoxRef.current?.setFieldsValue({ nation: '汉族' })
+  }, [getInfoFu, sId])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {}, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      // 多张图片
+      const { sonFileIds, sonIsOk, coverUrl, coverUrlBig } = ZupImgsRef.current?.fileComFileResFu()
+
+      // 多个视频
+      const list = ZupVideosRef.current?.fileComFileResFu() || []
+
+      // 生卒日期
+      let dateStart = ''
+      if (values.dateStart) dateStart = dayjs(values.dateStart).format('YYYY-MM-DD')
+      let dateEnd = ''
+      if (values.dateEnd) dateEnd = dayjs(values.dateEnd).format('YYYY-MM-DD')
+
+      // 籍贯
+      let nativeProvince = ''
+      let nativeCity = ''
+      let nativeRegion = ''
+
+      if (values.myNative && values.myNative.length) {
+        nativeProvince = values.myNative[0] || ''
+        nativeCity = values.myNative[1] || ''
+        nativeRegion = values.myNative[2] || ''
+      }
+
+      // 牺牲地
+      let lossProvince = ''
+      let lossCity = ''
+      let lossRegion = ''
+
+      if (values.myLoss && values.myLoss.length) {
+        lossProvince = values.myLoss[0] || ''
+        lossCity = values.myLoss[1] || ''
+        lossRegion = values.myLoss[2] || ''
+      }
+
+      const obj = {
+        ...values,
+        id: sId > 0 ? sId : null,
+        dateStart,
+        dateEnd,
+        nativeProvince,
+        nativeCity,
+        nativeRegion,
+        lossProvince,
+        lossCity,
+        lossRegion,
+        thumb: coverUrl || '',
+        thumbPc: coverUrlBig || '',
+        imgIds: sonIsOk ? '' : sonFileIds.join(','),
+        videoIds: list.map((v: any) => v.id).join(',')
+      }
+
+      const res = await A1_APIsave(obj)
+      if (res.code === 0) {
+        MessageFu.success(sId > 0 ? '编辑成功' : '新增成功')
+        sId > 0 ? upTableFu() : addTableFu()
+        closeFu()
+      }
+
+      // if (1 + 1 === 2) {
+      //   console.log('------222', obj)
+      //   return
+      // }
+    },
+    [addTableFu, closeFu, sId, upTableFu]
+  )
+
+  const formDomRef = useRef<HTMLDivElement>(null)
+
+  // -------------获取番号列表--------------
+  const dispatch = useDispatch()
+  useEffect(() => {
+    dispatch(A1_APIgetNumList())
+  }, [dispatch])
+
+  const numList = useSelector((state: RootState) => state.A1record.numList)
+
+  const [numShow, setNumShow] = useState(false)
+
+  const fanHaoRef = useRef(0)
+
+  return (
+    <div className={styles.A1add}>
+      <div className='A1addMain'>
+        <div className='A1aTit'>{sId > 0 ? '编辑' : '新增'}烈士</div>
+
+        <div className='A1accc' ref={formDomRef}>
+          <div>
+            <Form
+              ref={FormBoxRef}
+              name='basic'
+              onFinish={onFinish}
+              onFinishFailed={onFinishFailed}
+              autoComplete='off'
+              scrollToFirstError
+            >
+              <div className='A1aRow'>
+                <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={10} showCount placeholder='请输入内容' />
+                </Form.Item>
+                <Form.Item
+                  label='性别'
+                  name='gender'
+                  rules={[{ required: true, message: '请选择性别' }]}
+                >
+                  <Radio.Group
+                    options={[
+                      { value: 1, label: '男' },
+                      { value: 2, label: '女' }
+                    ]}
+                  />
+                </Form.Item>
+              </div>
+
+              <div className='A1aRow'>
+                <Form.Item label='名族' name='nation'>
+                  <Select
+                    getPopupContainer={() => formDomRef.current!}
+                    allowClear
+                    placeholder='请搜索并选择'
+                    showSearch
+                    options={nationSelect}
+                  />
+                </Form.Item>
+                <div className='A1aRowSon'>
+                  <Form.Item label='番号' name='dictPanId'>
+                    <Select
+                      onChange={e => (fanHaoRef.current = e)}
+                      getPopupContainer={() => formDomRef.current!}
+                      filterOption={(input, option) =>
+                        (option?.label ?? '').toLowerCase().includes(input.toLowerCase())
+                      }
+                      allowClear
+                      placeholder='请搜索并选择'
+                      showSearch
+                      options={numList.map(v => ({ value: v.id, label: v.name }))}
+                    />
+                  </Form.Item>
+                  <Button type='primary' onClick={() => setNumShow(true)}>
+                    管理
+                  </Button>
+                </div>
+              </div>
+
+              <div className='A1aRow'>
+                <Form.Item label='籍贯' name='myNative'>
+                  <Cascader options={myCity} placeholder='请选择' allowClear changeOnSelect />
+                </Form.Item>
+                <Form.Item label='籍贯详址' name='nativeAddress'>
+                  <Input maxLength={50} showCount placeholder='请输入内容' />
+                </Form.Item>
+              </div>
+
+              <div className='A1aRow'>
+                <Form.Item label='牺牲地' name='myLoss'>
+                  <Cascader options={myCity} placeholder='请选择' allowClear changeOnSelect />
+                </Form.Item>
+                <Form.Item label='牺牲详址' name='lossAddress'>
+                  <Input maxLength={50} showCount placeholder='请输入内容' />
+                </Form.Item>
+              </div>
+
+              <div className='A1aRow'>
+                <Form.Item label='出生年份' name='dateStart'>
+                  <DatePicker />
+                </Form.Item>
+                <Form.Item label='死亡年份' name='dateEnd'>
+                  <DatePicker />
+                </Form.Item>
+              </div>
+
+              <div className='A1aRowAll'>
+                <Form.Item label='个人介绍' name='intro'>
+                  <TextArea maxLength={500} showCount placeholder='请输入内容' />
+                </Form.Item>
+              </div>
+
+              {/* 多个图片 */}
+              <div className='formRow'>
+                <div className='formLeft'>图片:</div>
+                <div className='formRight'>
+                  <ZupTypes
+                    ref={ZupImgsRef}
+                    isLook={false}
+                    fileCheck={false}
+                    selecFlag='图片'
+                    imgSize={5}
+                    imgLength={50}
+                    dirCode='A1record'
+                    myUrl='cms/martyr/upload'
+                    isTypeShow={true}
+                    oneIsCover={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/martyr/upload'
+                    upTxt=';数量不超过50个。'
+                    ref={ZupVideosRef}
+                  />
+                </div>
+              </div>
+
+              <div className='A1aRowAll'>
+                <Form.Item label='场景链接' name='link'>
+                  <TextArea maxLength={500} showCount placeholder='请输入内容' />
+                </Form.Item>
+              </div>
+              <div className='A1aRowAll'>
+                <Form.Item label='备注' name='remark'>
+                  <TextArea maxLength={500} showCount placeholder='请输入内容' />
+                </Form.Item>
+              </div>
+
+              {/* 确定和取消按钮 */}
+              <Form.Item className='A1aBtn'>
+                <Button type='primary' htmlType='submit'>
+                  提交
+                </Button>
+                <br />
+                <br />
+                <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+              </Form.Item>
+            </Form>
+          </div>
+        </div>
+      </div>
+
+      {/* 番号管理 */}
+      {numShow ? (
+        <A1num
+          AcId={fanHaoRef.current}
+          closeFu={() => setNumShow(false)}
+          upNumList={() => dispatch(A1_APIgetNumList())}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA1add = React.memo(A1add)
+
+export default MemoA1add

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

@@ -0,0 +1,58 @@
+.A1num {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 800px !important;
+    }
+    .A1Ntit {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      border-bottom: 1px solid #ccc;
+      padding-bottom: 10px;
+      margin-bottom: 15px;
+    }
+    .ant-table-cell {
+      padding: 8px !important;
+      text-align: center !important;
+    }
+    .A1Nbtn {
+      margin-top: 10px;
+      text-align: center;
+    }
+  }
+}
+
+.A1num2 {
+  :global {
+    .ant-modal-close {
+      display: none;
+    }
+    .ant-modal {
+      width: 600px !important;
+    }
+    .ant-modal-header {
+      margin-bottom: 24px !important;
+    }
+    .A1Ninput {
+      display: flex;
+      align-items: center;
+      & > div {
+        width: 90px;
+        text-align: right;
+        & > span {
+          color: #ff4d4f;
+        }
+      }
+      .ant-input-affix-wrapper {
+        width: calc(100% - 90px);
+      }
+    }
+    .A1Nbtn {
+      margin-top: 20px;
+      text-align: center;
+    }
+  }
+}

+ 138 - 0
后台管理/src/pages/A1record/A1num/index.tsx

@@ -0,0 +1,138 @@
+import React, { useCallback, useMemo, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Input, Modal } from 'antd'
+import MyTable from '@/components/MyTable'
+import { NumListType } from '../data'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A1_APINumdel, A1_APINumSave } from '@/store/action/A1record'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  closeFu: () => void
+  upNumList: () => void
+  AcId: number | undefined
+}
+
+function A1num({ closeFu, upNumList, AcId }: Props) {
+  const numList = useSelector((state: RootState) => state.A1record.numList)
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A1_APINumdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        upNumList()
+      }
+    },
+    [upNumList]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: NumListType) => (
+          <>
+            <Button size='small' type='text' onClick={() => setAddInfo(item)}>
+              编辑
+            </Button>
+            {AcId === item.id ? null : (
+              <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+            )}
+          </>
+        )
+      }
+    ]
+  }, [AcId, delTableFu])
+
+  // 新增和编辑
+  const [addInfo, setAddInfo] = useState({ id: 0, name: '' })
+
+  // 点击确定
+  const addBtn = useCallback(async () => {
+    if (!addInfo.name) return MessageFu.warning('请输入番号名称')
+
+    const obj = {
+      id: addInfo.id > 0 ? addInfo.id : null,
+      name: addInfo.name,
+      type: 'pan'
+    }
+
+    const res = await A1_APINumSave(obj)
+
+    if (res.code === 0) {
+      MessageFu.success(addInfo.id > 0 ? '编辑成功' : '新增成功')
+      upNumList()
+      setAddInfo({ id: 0, name: '' })
+    }
+  }, [addInfo.id, addInfo.name, upNumList])
+
+  return (
+    <Modal
+      wrapClassName={styles.A1num}
+      open={true}
+      title={
+        <div className='A1Ntit'>
+          <div>番号管理</div>
+          <Button type='primary' onClick={() => setAddInfo({ id: -1, name: '' })}>
+            新增
+          </Button>
+        </div>
+      }
+      footer={
+        [] // 设置footer为空,去掉 取消 确定默认按钮
+      }
+    >
+      <MyTable
+        classKey='A1num'
+        yHeight={500}
+        list={numList}
+        columnsTemp={[['txt', '番号名称', 'name']]}
+        lastBtn={tableLastBtn}
+        pagingInfo={false}
+      />
+
+      <div className='A1Nbtn'>
+        <Button onClick={closeFu}>关闭</Button>
+      </div>
+
+      {addInfo.id ? (
+        <Modal
+          wrapClassName={styles.A1num2}
+          open={true}
+          title={addInfo.id > 0 ? '番号编辑' : '番号新增'}
+          footer={
+            [] // 设置footer为空,去掉 取消 确定默认按钮
+          }
+        >
+          <div className='A1Ninput'>
+            <div>
+              <span>*</span> 番号名称:
+            </div>
+            <Input
+              value={addInfo.name}
+              maxLength={20}
+              showCount
+              onChange={e => setAddInfo({ id: addInfo.id, name: e.target.value.trim() })}
+            />
+          </div>
+
+          <div className='A1Nbtn'>
+            <Button type='primary' onClick={addBtn}>
+              提交
+            </Button>
+            &emsp;
+            <MyPopconfirm txtK='取消' onConfirm={() => setAddInfo({ id: 0, name: '' })} />
+          </div>
+        </Modal>
+      ) : null}
+    </Modal>
+  )
+}
+
+const MemoA1num = React.memo(A1num)
+
+export default MemoA1num

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

@@ -0,0 +1,272 @@
+export type A1ListType = {
+  clue?: any
+  createTime: string
+  creatorId: number
+  creatorName: string
+  dateEnd: string
+  dateStart: string
+  dictPanId: number
+  dictPanName: string
+  gender: number
+  id: number
+  img?: any
+  imgIds: string
+  intro: string
+  kinship?: any
+  life?: any
+  link: string
+  lossAddress: string
+  lossCity: string
+  lossProvince: string
+  lossRegion: string
+  name: string
+  nation: string
+  nativeAddress: string
+  nativeCity: string
+  nativeProvince: string
+  nativeRegion: string
+  num: string
+  relic?: any
+  remark: string
+  thumb: string
+  thumbPc: string
+  updateTime: string
+  video?: any
+  videoIds: string
+}
+
+export type LookInfoType = {
+  id: number
+  txt: '' | '新增' | '编辑' | '查看'
+}
+
+// 56个名族
+export const nationSelect = [
+  {
+    value: '汉族',
+    label: '汉族'
+  },
+  {
+    value: '蒙古族',
+    label: '蒙古族'
+  },
+  {
+    value: '回族',
+    label: '回族'
+  },
+  {
+    value: '藏族',
+    label: '藏族'
+  },
+  {
+    value: '维吾尔族',
+    label: '维吾尔族'
+  },
+  {
+    value: '苗族',
+    label: '苗族'
+  },
+  {
+    value: '彝族',
+    label: '彝族'
+  },
+  {
+    value: '壮族',
+    label: '壮族'
+  },
+  {
+    value: '布依族',
+    label: '布依族'
+  },
+  {
+    value: '朝鲜族',
+    label: '朝鲜族'
+  },
+  {
+    value: '满族',
+    label: '满族'
+  },
+  {
+    value: '侗族',
+    label: '侗族'
+  },
+  {
+    value: '瑶族',
+    label: '瑶族'
+  },
+  {
+    value: '白族',
+    label: '白族'
+  },
+  {
+    value: '土家族',
+    label: '土家族'
+  },
+  {
+    value: '哈尼族',
+    label: '哈尼族'
+  },
+  {
+    value: '哈萨克族',
+    label: '哈萨克族'
+  },
+  {
+    value: '傣族',
+    label: '傣族'
+  },
+  {
+    value: '黎族',
+    label: '黎族'
+  },
+  {
+    value: '僳僳族',
+    label: '僳僳族'
+  },
+  {
+    value: '佤族',
+    label: '佤族'
+  },
+  {
+    value: '畲族',
+    label: '畲族'
+  },
+  {
+    value: '高山族',
+    label: '高山族'
+  },
+  {
+    value: '拉祜族',
+    label: '拉祜族'
+  },
+  {
+    value: '水族',
+    label: '水族'
+  },
+  {
+    value: '东乡族',
+    label: '东乡族'
+  },
+  {
+    value: '纳西族',
+    label: '纳西族'
+  },
+  {
+    value: '景颇族',
+    label: '景颇族'
+  },
+  {
+    value: '柯尔克孜族',
+    label: '柯尔克孜族'
+  },
+  {
+    value: '土族',
+    label: '土族'
+  },
+  {
+    value: '达斡尔族',
+    label: '达斡尔族'
+  },
+  {
+    value: '仫佬族',
+    label: '仫佬族'
+  },
+  {
+    value: '羌族',
+    label: '羌族'
+  },
+  {
+    value: '布朗族',
+    label: '布朗族'
+  },
+  {
+    value: '撒拉族',
+    label: '撒拉族'
+  },
+  {
+    value: '毛南族',
+    label: '毛南族'
+  },
+  {
+    value: '仡佬族',
+    label: '仡佬族'
+  },
+  {
+    value: '锡伯族',
+    label: '锡伯族'
+  },
+  {
+    value: '阿昌族',
+    label: '阿昌族'
+  },
+  {
+    value: '普米族',
+    label: '普米族'
+  },
+  {
+    value: '塔吉克族',
+    label: '塔吉克族'
+  },
+  {
+    value: '怒族',
+    label: '怒族'
+  },
+  {
+    value: '乌孜别克族',
+    label: '乌孜别克族'
+  },
+  {
+    value: '俄罗斯族',
+    label: '俄罗斯族'
+  },
+  {
+    value: '鄂温克族',
+    label: '鄂温克族'
+  },
+  {
+    value: '德昂族',
+    label: '德昂族'
+  },
+  {
+    value: '保安族',
+    label: '保安族'
+  },
+  {
+    value: '裕固族',
+    label: '裕固族'
+  },
+  {
+    value: '京族',
+    label: '京族'
+  },
+  {
+    value: '塔塔尔族',
+    label: '塔塔尔族'
+  },
+  {
+    value: '独龙族',
+    label: '独龙族'
+  },
+  {
+    value: '鄂伦春族',
+    label: '鄂伦春族'
+  },
+  {
+    value: '赫哲族',
+    label: '赫哲族'
+  },
+  {
+    value: '门巴族',
+    label: '门巴族'
+  },
+  {
+    value: '珞巴族',
+    label: '珞巴族'
+  },
+  {
+    value: '基诺族',
+    label: '基诺族'
+  }
+]
+
+// 番号相关
+export type NumListType = { id: number; name: string }

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

@@ -1,5 +1,54 @@
 .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;
+      }
+    }
   }
 }

+ 229 - 3
后台管理/src/pages/A1record/index.tsx

@@ -1,12 +1,238 @@
-import React from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
 import A1look from './A1look'
+import { useDispatch, useSelector } from 'react-redux'
+import { A1_APIdel, A1_APIgetList } from '@/store/action/A1record'
+import { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import { Button, Input, Table } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A1ListType, LookInfoType } from './data'
+import MyTable from '@/components/MyTable'
+import { A1tableC } from '@/utils/tableData'
+import A1add from './A1add'
+import ImageLazy from '@/components/ImageLazy'
+
+const baseFromData = {
+  pageNum: 1,
+  pageSize: 10,
+  searchKey: ''
+}
+
 function A1record() {
+  const dispatch = useDispatch()
+
+  const [fromData, setFromData] = useState(baseFromData)
+
+  const getListFu = useCallback(() => {
+    dispatch(A1_APIgetList(fromData))
+  }, [dispatch, fromData])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  // 作品名称的输入
+  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 tableInfo = useSelector((state: RootState) => state.A1record.tableInfo)
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...baseFromData })
+  }, [])
+
+  // 点击查看
+  const [lookInfo, setLookInfo] = useState<LookInfoType>({
+    id: 0,
+    txt: ''
+  })
+
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A1_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  // 表格相关
+  const columns = useMemo(() => {
+    const arr: any = [
+      { title: 'ID', dataIndex: 'num', width: 100 },
+      {
+        title: '封面图',
+        width: 80,
+        render: (item: A1ListType) => (
+          <div className='tableImgAuto'>
+            <ImageLazy
+              width={60}
+              height={60}
+              src={item.thumb || item.thumbPc}
+              srcBig={item.thumbPc || item.thumb}
+            />
+          </div>
+        )
+      },
+      { title: '姓名', render: (item: A1ListType) => item.name, width: 100 },
+      {
+        title: '性别',
+        width: 100,
+        render: (item: A1ListType) => (item.gender === 1 ? '男' : '女')
+      },
+      { title: '民族', width: 100, render: (item: A1ListType) => item.nation || '(空)' },
+      { title: '番号', render: (item: A1ListType) => item.dictPanName || '(空)' },
+      {
+        title: '籍贯',
+
+        render: (item: A1ListType) => {
+          let res = '(空)'
+          if (item.nativeProvince) res = item.nativeProvince
+          if (item.nativeCity) res += ` / ${item.nativeCity}`
+          if (item.nativeRegion) res += ` / ${item.nativeRegion}`
+          if (item.nativeAddress) res += ` - ${item.nativeAddress}`
+          return res
+        }
+      },
+      {
+        title: '出生年份',
+        width: 100,
+        render: (item: A1ListType) => item.dateStart || '(空)'
+      },
+      {
+        title: '死亡年份',
+        width: 100,
+        render: (item: A1ListType) => item.dateEnd || '(空)'
+      },
+
+      {
+        title: '备注',
+        render: (item: A1ListType) =>
+          item.remark ? (
+            item.remark.length >= 50 ? (
+              <span style={{ cursor: 'pointer' }} title={item.remark}>
+                {item.remark.substring(0, 50) + '...'}
+              </span>
+            ) : (
+              item.remark
+            )
+          ) : (
+            '(空)'
+          )
+      },
+      { title: '编辑时间', width: 150, render: (item: A1ListType) => item.updateTime },
+      { title: '编辑人', width: 100, render: (item: A1ListType) => item.creatorName },
+
+      {
+        title: '操作',
+        fixed: 'right',
+        width: 150,
+        render: (item: A1ListType) => (
+          <>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setLookInfo({ id: item.id, txt: '查看' })}
+            >
+              查看
+            </Button>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setLookInfo({ id: item.id, txt: '编辑' })}
+            >
+              编辑
+            </Button>
+            <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+          </>
+        )
+      }
+    ]
+    return arr
+  }, [delTableFu])
+
   return (
     <div className={styles.A1record}>
-      <div className='pageTitle'>烈士档案</div>
+      <div className='pageTitle'>烈士档案{lookInfo.txt ? ` - ${lookInfo.txt}` : ''}</div>
+
+      {/* 顶部筛选 */}
+      <div className='A1top'>
+        <div className='A1topLeft'>
+          <div>
+            <Input
+              key={inputKey}
+              maxLength={30}
+              showCount
+              style={{ width: 300 }}
+              placeholder='请输入ID/姓名/番号/籍贯'
+              allowClear
+              onChange={e => fromKeyChangeFu(e, 'searchKey')}
+            />
+          </div>
+        </div>
+
+        <div>
+          <Button onClick={resetSelectFu}>重置</Button>
+          &emsp;
+          <Button type='primary' onClick={() => setLookInfo({ id: -1, txt: '新增' })}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className='A1tableBox'>
+        <Table
+          scroll={{ x: 'max-content', y: 645 }}
+          columns={columns}
+          dataSource={tableInfo.list}
+          rowKey='id'
+          pagination={{
+            showQuickJumper: true,
+            position: ['bottomCenter'],
+            showSizeChanger: true,
+            current: fromData.pageNum,
+            pageSize: fromData.pageSize,
+            total: tableInfo.total,
+            onChange: (pageNum, pageSize) => setFromData({ ...fromData, pageNum, pageSize })
+          }}
+        />
+
+        {/* <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>
 
-      <A1look />
+      {['新增', '编辑'].includes(lookInfo.txt) ? (
+        <A1add
+          sId={lookInfo.id}
+          closeFu={() => setLookInfo({ id: 0, txt: '' })}
+          addTableFu={resetSelectFu}
+          upTableFu={getListFu}
+        />
+      ) : null}
+      {/* <A1look /> */}
     </div>
   )
 }

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

@@ -149,9 +149,6 @@
           & > div {
             width: 100%;
             height: 100%;
-            background-color: #fff;
-            border-radius: 10px;
-            padding: 20px;
           }
         }
       }

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

@@ -0,0 +1,57 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+/**
+ * 烈士档案-获取列表
+ */
+export const A1_APIgetList = (data: any): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post('cms/martyr/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/martyr/remove/${id}`)
+}
+
+/**
+ * 烈士档案-新增
+ */
+export const A1_APIsave = (data: any) => {
+  return http.post('cms/martyr/save', data)
+}
+
+/**
+ * 烈士档案-新增
+ */
+export const A1_APIgetInfo = (id: number) => {
+  return http.get(`cms/martyr/detail/${id}`)
+}
+
+// --------------番号管理--------------------
+export const A1_APIgetNumList = (type = 'pan'): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get(`cms/martyr/dict/getDict/${type}`)
+    if (res.code === 0) {
+      dispatch({ type: 'A1/getNumList', payload: res.data || [] })
+    }
+  }
+}
+
+export const A1_APINumdel = (id: number) => {
+  return http.get(`cms/dict/remove/${id}`)
+}
+
+export const A1_APINumSave = (data: any) => {
+  return http.post('cms/dict/save', data)
+}

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

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

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

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

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

@@ -14,14 +14,19 @@
 //     ["text", "创建日期",'description', 50,A],
 //   ];
 
-// export const A1tableC = [
-//   ['img', '图片', 'thumb'],
-//   ['txt', '作品名称', 'name'],
-//   ['txt', '创作时间', 'time'],
-//   ['txt', '作品材质', 'texture'],
-//   ['txt', '排序值', 'sort']
-//   // ['text', '简介', 'intro', 100]
-// ]
+// 待完善
+export const A1tableC = [
+  ['txt', 'ID', 'num'],
+  ['txt', '姓名', 'name'],
+  ['txtChange', '性别', 'gender', { 2: '女', 1: '男' }],
+  ['txt', '民族', 'nation'],
+  ['txt', '番号', 'dictPanName'],
+  ['txt', '籍贯', 'name'],
+  ['txt', '生卒', 'name'],
+  ['text', '备注', 'name', 50],
+  ['txt', '编辑时间', 'name'],
+  ['txt', '编辑人', 'name']
+]
 
 export const Z1tableC = [
   ['txt', '用户名', 'userName'],