index.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import React, { useCallback, useEffect, useRef, useState } from 'react'
  2. import { CaretRightOutlined } from '@ant-design/icons'
  3. import styles from './index.module.scss'
  4. import { Route, Switch, useLocation } from 'react-router-dom'
  5. import AuthRoute from '@/components/AuthRoute'
  6. import history from '@/utils/history'
  7. import { Button, Form, Input, Modal } from 'antd'
  8. import { Base64 } from 'js-base64'
  9. import encodeStr from '@/utils/pass'
  10. import { passWordEditAPI } from '@/store/action/layout'
  11. import { changSetFu, getTokenInfo, removeTokenInfo } from '@/utils/storage'
  12. import { MessageFu } from '@/utils/message'
  13. import NotFound from '@/components/NotFound'
  14. import classNames from 'classnames'
  15. import tabLeftArr, { routerSon, RouterType, RouterTypeRow } from './data'
  16. import MyPopconfirm from '@/components/MyPopconfirm'
  17. import store from '@/store'
  18. import SpinLodingSon from '@/components/SpinLodingSon'
  19. import { DownOutlined, RightOutlined } from '@ant-design/icons'
  20. import { I6_APIgetInfo } from '@/store/action/Isystem/I6role'
  21. import { TypeI6Role } from '../Isystem/I6role/data'
  22. import { useDispatch } from 'react-redux'
  23. import { I2_APIgetDict } from '@/store/action/Isystem/I2dict'
  24. import { E1_APIgetTree } from '@/store/action/Eculture/E1tag'
  25. function Layout() {
  26. const [loding, setLoding] = useState(false)
  27. // 获取字典值 // 获取标签
  28. const dispatch = useDispatch()
  29. const getListFu = useCallback(() => {
  30. dispatch(
  31. I2_APIgetDict(() => {
  32. setLoding(true)
  33. })
  34. )
  35. dispatch(E1_APIgetTree())
  36. }, [dispatch])
  37. useEffect(() => {
  38. getListFu()
  39. }, [getListFu])
  40. // 当前路径选中的左侧菜单
  41. const location = useLocation()
  42. const [path, setPath] = useState('')
  43. const sroolRef = useRef<HTMLDivElement>(null)
  44. const sroolTimeRef = useRef(-1)
  45. useEffect(() => {
  46. if (loding) {
  47. const arr = location.pathname.split('/')
  48. let pathTemp = '/'
  49. if (arr[1]) pathTemp = '/' + arr[1]
  50. // 滚动到中间
  51. clearTimeout(sroolTimeRef.current)
  52. if (pathTemp !== '/goodsLook') {
  53. sroolTimeRef.current = window.setTimeout(() => {
  54. if (sroolRef.current) {
  55. const dom = document.querySelector('.layoutLeftMain') as HTMLDivElement
  56. if (dom) {
  57. dom.scrollTo({
  58. top: sroolRef.current.offsetTop - 350,
  59. behavior: 'smooth'
  60. })
  61. }
  62. }
  63. }, 200)
  64. }
  65. setPath(pathTemp)
  66. }
  67. }, [location, loding])
  68. const [routerSonRes, setRouterSonRes] = useState<RouterTypeRow[]>([])
  69. // 获取用户权限信息
  70. const getUserAuthFu = useCallback(async () => {
  71. const userInfo = getTokenInfo().user
  72. const res = await I6_APIgetInfo(userInfo.roleId)
  73. if (res.code === 0) {
  74. let isOkIdArr: number[] = [9901]
  75. const roleArr: TypeI6Role[] = res.data.permission
  76. roleArr.forEach(v1 => {
  77. if (v1.children) {
  78. v1.children.forEach(v2 => {
  79. if (v2.authority) isOkIdArr.push(v2.id)
  80. if (v2.children) {
  81. v2.children.forEach(v3 => {
  82. if (v3.authority) isOkIdArr.push(v3.id)
  83. })
  84. }
  85. })
  86. }
  87. })
  88. // 详情页,也需要看下有没有页面权限
  89. setRouterSonRes(routerSon.filter(v => isOkIdArr.includes(v.id)))
  90. // 是管理员
  91. if (userInfo.isAdmin === 1) {
  92. ;[750].forEach(v => {
  93. isOkIdArr.push(v)
  94. })
  95. } else if (isOkIdArr.includes(750)) isOkIdArr = isOkIdArr.filter(v => v !== 750)
  96. // 页面权限id,存到仓库
  97. store.dispatch({ type: 'layout/authorityIds', payload: isOkIdArr })
  98. const tempArr: RouterTypeRow[] = []
  99. // 权限数据存到仓库
  100. const roleArrStoreArr: RouterTypeRow[] = []
  101. tabLeftArr.forEach(v1 => {
  102. if (v1.son && v1.son[0]) {
  103. v1.son.forEach(v2 => {
  104. if (isOkIdArr.includes(v2.id)) {
  105. tempArr.push(v2)
  106. // 过滤掉 藏品详情 页
  107. if (v2.id < 9901) {
  108. roleArrStoreArr.push({ ...v2, authority: true })
  109. }
  110. }
  111. })
  112. }
  113. })
  114. // 权限数据存到仓库
  115. store.dispatch({ type: 'layout/userRolePermissions', payload: roleArrStoreArr })
  116. setRouterCom(tempArr)
  117. // 如果当前页面没有权限了,跳转有权限的第一个页面
  118. const urlAll = window.location.hash
  119. const isNowPath = urlAll.replace('#', '')
  120. const pathArr = tempArr.map(v => v.pathLast || v.path)
  121. const routerSonArr = routerSon.map(v => v.path)
  122. const lastArr = [...pathArr, ...routerSonArr]
  123. let flagPush = true
  124. lastArr.forEach(v => {
  125. if (v === '/') {
  126. if (isNowPath === '/') flagPush = false
  127. } else {
  128. if (isNowPath.includes(v)) flagPush = false
  129. }
  130. })
  131. if (flagPush) history.push(pathArr[0])
  132. const resList = tabLeftArr.map(v => ({
  133. ...v,
  134. son: v.son.filter(c => isOkIdArr.includes(c.id))
  135. }))
  136. setList(resList)
  137. }
  138. }, [])
  139. useEffect(() => {
  140. getUserAuthFu()
  141. }, [getUserAuthFu])
  142. // 左侧菜单 信息
  143. const [list, setList] = useState([] as RouterType)
  144. // 路由信息(过滤之后的)
  145. const [RouterCom, setRouterCom] = useState<RouterTypeRow[]>([])
  146. // useEffect(() => {
  147. // console.log(123, list);
  148. // }, [list]);
  149. // 点击跳转
  150. const pathCutFu = useCallback((item: RouterTypeRow) => {
  151. history.push(item.path)
  152. if (item.name !== '工作台') changSetFu(item)
  153. }, [])
  154. // 修改密码相关
  155. const [open, setOpen] = useState(false)
  156. // 拿到新密码的输入框的值
  157. const oldPasswordValue = useRef('')
  158. const checkPassWord = (rule: any, value: any = '') => {
  159. if (value !== oldPasswordValue.current) return Promise.reject('新密码不一致!')
  160. else return Promise.resolve(value)
  161. }
  162. const onFinish = async (values: any) => {
  163. // 通过校验之后发送请求
  164. if (values.oldPassword === values.newPassword) return MessageFu.warning('新旧密码不能相同!')
  165. const obj = {
  166. oldPassword: encodeStr(Base64.encode(values.oldPassword)),
  167. newPassword: encodeStr(Base64.encode(values.newPassword))
  168. }
  169. const res: any = await passWordEditAPI(obj)
  170. if (res.code === 0) {
  171. MessageFu.success('修改成功!')
  172. loginExit()
  173. }
  174. }
  175. // 点击退出登录
  176. const loginExit = () => {
  177. removeTokenInfo()
  178. history.push('/login')
  179. }
  180. // 点击用户 出来 退出登录 修改密码
  181. const [isUserBtnShow, setIsUserBtnShow] = useState(false)
  182. // path的高亮判断
  183. const pathAcFu = useCallback(
  184. (url: string) => {
  185. let flag = false
  186. if (url === '/') {
  187. if (path.includes('/_') || path === '/') flag = true
  188. } else {
  189. if (path.includes(url)) flag = true
  190. }
  191. return flag
  192. },
  193. [path]
  194. )
  195. return (
  196. <div className={styles.Layout}>
  197. {/* 左边 */}
  198. <div className='layoutLeft'>
  199. <div className='layoutLeftTop'>
  200. <img className='logo' src={require('@/assets/img/logo.png')} alt='' />
  201. </div>
  202. {/* 左边主体 */}
  203. <div className='layoutLeftMain mySorrl'>
  204. {list.map(v => (
  205. <div
  206. className={classNames('layoutLRowBox')}
  207. key={v.id}
  208. hidden={!v.son.length || (v.son.length === 1 && v.son[0].name === '藏品详情')}
  209. >
  210. <div
  211. className={classNames('layoutLRowBoxTxt', v.show ? 'layoutLRowBoxTxtShow' : '')}
  212. onClick={() => {
  213. setList(
  214. list.map(c => ({
  215. ...c,
  216. show: c.id === v.id ? !c.show : c.show
  217. }))
  218. )
  219. }}
  220. >
  221. {v.name}
  222. <span className={classNames(v.show ? 'layoutLRowBoxTxtIcon' : '')}>
  223. <DownOutlined />
  224. </span>
  225. </div>
  226. {v.son.map(v2 => (
  227. <div
  228. key={v2.id}
  229. hidden={v2.id >= 9901}
  230. className={classNames(
  231. 'layoutLRowBoxRow',
  232. pathAcFu(v2.path) ? 'active' : '',
  233. v.show ? '' : 'layoutLRowBoxRowHide'
  234. )}
  235. ref={pathAcFu(v2.path) ? sroolRef : null}
  236. onClick={() => pathCutFu(v2)}
  237. >
  238. {v2.name}
  239. <span>
  240. <RightOutlined />
  241. </span>
  242. </div>
  243. ))}
  244. </div>
  245. ))}
  246. </div>
  247. </div>
  248. {/* 右边 */}
  249. <div className='layoutRight'>
  250. <div className='layoutRightTop'>
  251. {/* 用户相关 */}
  252. <div
  253. className={classNames('user', isUserBtnShow ? 'userShow' : '')}
  254. onMouseLeave={() => setIsUserBtnShow(false)}
  255. >
  256. <div className='userNameBox' onClick={() => setIsUserBtnShow(true)}>
  257. {getTokenInfo().user.realName || getTokenInfo().user.userName || '匿名'}
  258. <div className='userInco userInco2'>
  259. <CaretRightOutlined />
  260. </div>
  261. </div>
  262. <div className='userSet'>
  263. <div>
  264. <span onClick={() => setOpen(true)}>修改密码</span>
  265. <MyPopconfirm txtK='退出登录' onConfirm={loginExit} Dom='退出登录' loc='bottom' />
  266. </div>
  267. </div>
  268. </div>
  269. </div>
  270. {/* 右边主体 */}
  271. <div className='layoutRightMain'>
  272. {/* 二级路由页面 */}
  273. {loding ? (
  274. <div className='mainBoxR'>
  275. <React.Suspense fallback={<SpinLodingSon />}>
  276. <Switch>
  277. {RouterCom.map(v => (
  278. <AuthRoute key={v.id} exact path={v.path} component={v.Com} />
  279. ))}
  280. {/* 非tab栏页面 */}
  281. {routerSonRes.map(v => (
  282. <AuthRoute key={v.id} exact path={v.path} component={v.Com} />
  283. ))}
  284. <Route path='*' component={NotFound} />
  285. </Switch>
  286. </React.Suspense>
  287. </div>
  288. ) : null}
  289. </div>
  290. </div>
  291. {/* 点击修改密码打开的对话框 */}
  292. <Modal
  293. destroyOnClose
  294. open={open}
  295. title='修改密码'
  296. onCancel={() => setOpen(false)}
  297. footer={
  298. [] // 设置footer为空,去掉 取消 确定默认按钮
  299. }
  300. >
  301. <Form
  302. scrollToFirstError={true}
  303. name='basic'
  304. labelCol={{ span: 5 }}
  305. wrapperCol={{ span: 16 }}
  306. onFinish={onFinish}
  307. autoComplete='off'
  308. >
  309. <Form.Item
  310. label='旧密码'
  311. name='oldPassword'
  312. rules={[{ required: true, message: '不能为空!' }]}
  313. getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
  314. >
  315. <Input.Password maxLength={20} />
  316. </Form.Item>
  317. <Form.Item
  318. label='新密码'
  319. name='newPassword'
  320. rules={[
  321. { required: true, message: '不能为空!' },
  322. { min: 6, max: 15, message: '密码长度为6-15个字符!' }
  323. ]}
  324. getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
  325. >
  326. <Input.Password
  327. maxLength={15}
  328. onChange={e => (oldPasswordValue.current = e.target.value)}
  329. />
  330. </Form.Item>
  331. <Form.Item
  332. label='确定新密码'
  333. name='checkPass'
  334. rules={[{ validator: checkPassWord }]}
  335. getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
  336. >
  337. <Input.Password maxLength={15} />
  338. </Form.Item>
  339. <Form.Item wrapperCol={{ offset: 14, span: 16 }}>
  340. <Button onClick={() => setOpen(false)}>取消</Button>
  341. &emsp;
  342. <Button type='primary' htmlType='submit'>
  343. 确定
  344. </Button>
  345. </Form.Item>
  346. </Form>
  347. </Modal>
  348. </div>
  349. )
  350. }
  351. // 使用 React.memo 来优化组件,避免组件的无效更新,类似 类组件里面的PureComponent
  352. const MemoLayout = React.memo(Layout)
  353. export default MemoLayout