| | |
| | | path: '/user/login', |
| | | component: './User/Login/index', |
| | | }, |
| | | { |
| | | name: 'setting', |
| | | path: '/user/setting', |
| | | component: './User/Setting/index', |
| | | }, |
| | | ], |
| | | }, |
| | | { |
| | |
| | | // }, |
| | | // ] |
| | | // : []), |
| | | { |
| | | key: 'setting', |
| | | icon: <SettingOutlined />, |
| | | label: '个人设置', |
| | | }, |
| | | { |
| | | type: 'divider' |
| | | }, |
| | | // { |
| | | // type: 'divider' |
| | | // }, |
| | | { |
| | | key: 'logout', |
| | | icon: <LogoutOutlined />, |
New file |
| | |
| | | import { UploadOutlined } from '@ant-design/icons'; |
| | | import { |
| | | ProForm, |
| | | ProFormDependency, |
| | | ProFormFieldSet, |
| | | ProFormSelect, |
| | | ProFormText, |
| | | ProFormTextArea, |
| | | } from '@ant-design/pro-components'; |
| | | import { useRequest } from '@umijs/max'; |
| | | import { Button, Input, message, Upload } from 'antd'; |
| | | import React from 'react'; |
| | | import useStyles from './index.style'; |
| | | |
| | | const queryCurrent = () => { |
| | | |
| | | } |
| | | |
| | | const validatorPhone = (rule, value, callback) => { |
| | | if (!value[0]) { |
| | | callback('Please input your area code!'); |
| | | } |
| | | if (!value[1]) { |
| | | callback('Please input your phone number!'); |
| | | } |
| | | callback(); |
| | | }; |
| | | |
| | | const BaseView = () => { |
| | | const { styles } = useStyles(); |
| | | // 头像组件 方便以后独立,增加裁剪之类的功能 |
| | | const AvatarView = ({ avatar }) => ( |
| | | <> |
| | | <div className={styles.avatar_title}>头像</div> |
| | | <div className={styles.avatar}> |
| | | <img src={avatar} alt="avatar" /> |
| | | </div> |
| | | <Upload showUploadList={false}> |
| | | <div className={styles.button_view}> |
| | | <Button> |
| | | <UploadOutlined /> |
| | | 更换头像 |
| | | </Button> |
| | | </div> |
| | | </Upload> |
| | | </> |
| | | ); |
| | | const { data: currentUser, loading } = useRequest(() => { |
| | | return queryCurrent(); |
| | | }); |
| | | const getAvatarURL = () => { |
| | | if (currentUser) { |
| | | if (currentUser.avatar) { |
| | | return currentUser.avatar; |
| | | } |
| | | const url = 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png'; |
| | | return url; |
| | | } |
| | | return ''; |
| | | }; |
| | | const handleFinish = async () => { |
| | | message.success('更新基本信息成功'); |
| | | }; |
| | | return ( |
| | | <div className={styles.baseView}> |
| | | {loading ? null : ( |
| | | <> |
| | | <div className={styles.left}> |
| | | <ProForm |
| | | layout="vertical" |
| | | onFinish={handleFinish} |
| | | submitter={{ |
| | | searchConfig: { |
| | | submitText: '更新基本信息', |
| | | }, |
| | | render: (_, dom) => dom[1], |
| | | }} |
| | | initialValues={{ |
| | | ...currentUser, |
| | | phone: currentUser?.phone.split('-'), |
| | | }} |
| | | hideRequiredMark |
| | | > |
| | | <ProFormText |
| | | width="md" |
| | | name="email" |
| | | label="邮箱" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: '请输入您的邮箱!', |
| | | }, |
| | | ]} |
| | | /> |
| | | <ProFormText |
| | | width="md" |
| | | name="name" |
| | | label="昵称" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: '请输入您的昵称!', |
| | | }, |
| | | ]} |
| | | /> |
| | | <ProFormTextArea |
| | | name="profile" |
| | | label="个人简介" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: '请输入个人简介!', |
| | | }, |
| | | ]} |
| | | placeholder="个人简介" |
| | | /> |
| | | |
| | | <ProFormText |
| | | width="md" |
| | | name="address" |
| | | label="地址" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: '请输入您的地址!', |
| | | }, |
| | | ]} |
| | | /> |
| | | <ProFormFieldSet |
| | | name="phone" |
| | | label="联系电话" |
| | | rules={[ |
| | | { |
| | | required: true, |
| | | message: '请输入您的联系电话!', |
| | | }, |
| | | { |
| | | validator: validatorPhone, |
| | | }, |
| | | ]} |
| | | > |
| | | <Input className={styles.area_code} /> |
| | | <Input className={styles.phone_number} /> |
| | | </ProFormFieldSet> |
| | | </ProForm> |
| | | </div> |
| | | <div className={styles.right}> |
| | | <AvatarView avatar={getAvatarURL()} /> |
| | | </div> |
| | | </> |
| | | )} |
| | | </div> |
| | | ); |
| | | }; |
| | | export default BaseView; |
New file |
| | |
| | | import { createStyles } from 'antd-style'; |
| | | |
| | | const useStyles = createStyles(({ token }) => { |
| | | return { |
| | | baseView: { |
| | | display: 'flex', |
| | | paddingTop: '12px', |
| | | '.ant-legacy-form-item .ant-legacy-form-item-control-wrapper': { |
| | | width: '100%', |
| | | }, |
| | | [`@media screen and (max-width: ${token.screenXL}px)`]: { |
| | | flexDirection: 'column-reverse', |
| | | }, |
| | | }, |
| | | left: { |
| | | minWidth: '224px', |
| | | maxWidth: '448px', |
| | | }, |
| | | right: { |
| | | flex: '1', |
| | | paddingLeft: '104px', |
| | | [`@media screen and (max-width: ${token.screenXL}px)`]: { |
| | | display: 'flex', |
| | | flexDirection: 'column', |
| | | alignItems: 'center', |
| | | maxWidth: '448px', |
| | | padding: '20px', |
| | | }, |
| | | }, |
| | | avatar_title: { |
| | | height: '22px', |
| | | marginBottom: '8px', |
| | | color: token.colorTextHeading, |
| | | fontSize: token.fontSize, |
| | | lineHeight: '22px', |
| | | [`@media screen and (max-width: ${token.screenXL}px)`]: { |
| | | display: 'none', |
| | | }, |
| | | }, |
| | | avatar: { |
| | | width: '144px', |
| | | height: '144px', |
| | | marginBottom: '12px', |
| | | overflow: 'hidden', |
| | | img: { width: '100%' }, |
| | | }, |
| | | button_view: { |
| | | width: '144px', |
| | | textAlign: 'center', |
| | | }, |
| | | area_code: { |
| | | width: '72px', |
| | | }, |
| | | phone_number: { |
| | | width: '214px', |
| | | }, |
| | | }; |
| | | }); |
| | | |
| | | export default useStyles; |
New file |
| | |
| | | import { List } from 'antd'; |
| | | import React from 'react'; |
| | | |
| | | |
| | | const passwordStrength = { |
| | | strong: <span className="strong">强</span>, |
| | | medium: <span className="medium">中</span>, |
| | | weak: <span className="weak">弱 Weak</span>, |
| | | }; |
| | | |
| | | const SecurityView = () => { |
| | | const getData = () => [ |
| | | { |
| | | title: '账户密码', |
| | | description: ( |
| | | <> |
| | | 当前密码强度: |
| | | {passwordStrength.strong} |
| | | </> |
| | | ), |
| | | actions: [<a key="Modify">修改</a>], |
| | | }, |
| | | { |
| | | title: '密保手机', |
| | | description: `已绑定手机:138****8293`, |
| | | actions: [<a key="Modify">修改</a>], |
| | | }, |
| | | { |
| | | title: '密保问题', |
| | | description: '未设置密保问题,密保问题可有效保护账户安全', |
| | | actions: [<a key="Set">设置</a>], |
| | | }, |
| | | { |
| | | title: '备用邮箱', |
| | | description: `已绑定邮箱:ant***sign.com`, |
| | | actions: [<a key="Modify">修改</a>], |
| | | }, |
| | | { |
| | | title: 'MFA 设备', |
| | | description: '未绑定 MFA 设备,绑定后,可以进行二次确认', |
| | | actions: [<a key="bind">绑定</a>], |
| | | }, |
| | | ]; |
| | | |
| | | const data = getData(); |
| | | return ( |
| | | <> |
| | | <List |
| | | itemLayout="horizontal" |
| | | dataSource={data} |
| | | renderItem={(item) => ( |
| | | <List.Item actions={item.actions}> |
| | | <List.Item.Meta title={item.title} description={item.description} /> |
| | | </List.Item> |
| | | )} |
| | | /> |
| | | </> |
| | | ); |
| | | }; |
| | | |
| | | export default SecurityView; |
New file |
| | |
| | | import { GridContent } from '@ant-design/pro-components'; |
| | | import { Menu } from 'antd'; |
| | | import React, { useLayoutEffect, useRef, useState } from 'react'; |
| | | import BaseView from './components/base'; |
| | | import SecurityView from './components/security'; |
| | | import useStyles from './style.style'; |
| | | |
| | | const Settings = () => { |
| | | const { styles } = useStyles(); |
| | | const menuMap = { |
| | | base: '基本设置', |
| | | security: '安全设置', |
| | | binding: '账号绑定', |
| | | notification: '新消息通知', |
| | | }; |
| | | const [initConfig, setInitConfig] = useState({ |
| | | mode: 'inline', |
| | | selectKey: 'base', |
| | | }); |
| | | const dom = useRef(); |
| | | const resize = () => { |
| | | requestAnimationFrame(() => { |
| | | if (!dom.current) { |
| | | return; |
| | | } |
| | | let mode = 'inline'; |
| | | const { offsetWidth } = dom.current; |
| | | if (dom.current.offsetWidth < 641 && offsetWidth > 400) { |
| | | mode = 'horizontal'; |
| | | } |
| | | if (window.innerWidth < 768 && offsetWidth > 400) { |
| | | mode = 'horizontal'; |
| | | } |
| | | setInitConfig({ |
| | | ...initConfig, |
| | | mode: mode, |
| | | }); |
| | | }); |
| | | }; |
| | | useLayoutEffect(() => { |
| | | if (dom.current) { |
| | | window.addEventListener('resize', resize); |
| | | resize(); |
| | | } |
| | | return () => { |
| | | window.removeEventListener('resize', resize); |
| | | }; |
| | | }, [dom.current]); |
| | | const getMenu = () => { |
| | | return Object.keys(menuMap).map((item) => ({ key: item, label: menuMap[item] })); |
| | | }; |
| | | const renderChildren = () => { |
| | | const { selectKey } = initConfig; |
| | | switch (selectKey) { |
| | | case 'base': |
| | | return <BaseView />; |
| | | case 'security': |
| | | return <SecurityView />; |
| | | default: |
| | | return null; |
| | | } |
| | | }; |
| | | return ( |
| | | <GridContent> |
| | | <div |
| | | className={styles.main} |
| | | ref={(ref) => { |
| | | if (ref) { |
| | | dom.current = ref; |
| | | } |
| | | }} |
| | | > |
| | | <div className={styles.leftMenu}> |
| | | <Menu |
| | | mode={initConfig.mode} |
| | | selectedKeys={[initConfig.selectKey]} |
| | | onClick={({ key }) => { |
| | | setInitConfig({ |
| | | ...initConfig, |
| | | selectKey: key, |
| | | }); |
| | | }} |
| | | items={getMenu()} |
| | | /> |
| | | </div> |
| | | <div className={styles.right}> |
| | | <div className={styles.title}>{menuMap[initConfig.selectKey]}</div> |
| | | {renderChildren()} |
| | | </div> |
| | | </div> |
| | | </GridContent> |
| | | ); |
| | | }; |
| | | export default Settings; |
New file |
| | |
| | | import { createStyles } from 'antd-style'; |
| | | |
| | | const useStyles = createStyles(({ token }) => { |
| | | return { |
| | | main: { |
| | | display: 'flex', |
| | | width: '100%', |
| | | height: '100%', |
| | | paddingTop: '16px', |
| | | paddingBottom: '16px', |
| | | backgroundColor: token.colorBgContainer, |
| | | '.ant-list-split .ant-list-item:last-child': { |
| | | borderBottom: `1px solid ${token.colorSplit}`, |
| | | }, |
| | | '.ant-list-item': { paddingTop: '14px', paddingBottom: '14px' }, |
| | | [`@media screen and (max-width: ${token.screenMD}px)`]: { |
| | | flexDirection: 'column', |
| | | }, |
| | | }, |
| | | leftMenu: { |
| | | width: '224px', |
| | | borderRight: `${token.lineWidth}px solid ${token.colorSplit}`, |
| | | '.ant-menu-inline': { border: 'none' }, |
| | | '.ant-menu-horizontal': { fontWeight: 'bold' }, |
| | | [`@media screen and (max-width: ${token.screenMD}px)`]: { |
| | | width: '100%', |
| | | border: 'none', |
| | | }, |
| | | }, |
| | | right: { |
| | | flex: '1', |
| | | padding: '8px 40px', |
| | | [`@media screen and (max-width: ${token.screenMD}px)`]: { |
| | | padding: '40px', |
| | | }, |
| | | }, |
| | | title: { |
| | | marginBottom: '12px', |
| | | color: token.colorTextHeading, |
| | | fontWeight: '500', |
| | | fontSize: '20px', |
| | | lineHeight: '28px', |
| | | }, |
| | | taobao: { |
| | | display: 'block', |
| | | color: '#ff4000', |
| | | fontSize: '48px', |
| | | lineHeight: '48px', |
| | | borderRadius: token.borderRadius, |
| | | }, |
| | | dingding: { |
| | | margin: '2px', |
| | | padding: '6px', |
| | | color: '#fff', |
| | | fontSize: '32px', |
| | | lineHeight: '32px', |
| | | backgroundColor: '#2eabff', |
| | | borderRadius: token.borderRadius, |
| | | }, |
| | | alipay: { |
| | | color: '#2eabff', |
| | | fontSize: '48px', |
| | | lineHeight: '48px', |
| | | borderRadius: token.borderRadius, |
| | | }, |
| | | ':global': { |
| | | 'font.strong': { color: token.colorSuccess }, |
| | | 'font.medium': { color: token.colorWarning }, |
| | | 'font.weak': { color: token.colorError }, |
| | | }, |
| | | }; |
| | | }); |
| | | |
| | | export default useStyles; |
| | |
| | | break; |
| | | } |
| | | } |
| | | addUserSettingMenu(remoteMenu); |
| | | patchRouteItems(proLayout, remoteMenu); |
| | | } |
| | | |
| | | function patchRouteItems(parent, children) { |
| | | for (const menu of children) { |
| | | if (menu.component !== null) { |
| | | if (menu.component !== null && menu.component !== undefined) { |
| | | // children |
| | | const Component = require(`@/pages${menu.path}/index.jsx`).default |
| | | const newRoute = { |
| | |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | function addUserSettingMenu(remoteMenu) { |
| | | const settingRoute = { |
| | | name: "个人设置", |
| | | path: "/account/setting", |
| | | component: "/account/setting" |
| | | } |
| | | remoteMenu.push({ |
| | | name: "个人中心", |
| | | path: "/account", |
| | | component: null, |
| | | routes: [settingRoute], |
| | | icon: createIcon('UserOutlined') |
| | | }) |
| | | } |
| | |
| | | "@@test/*": ["./src/.umi-test/*"] |
| | | } |
| | | }, |
| | | "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/app.tsx", "src/utils/icon-util.js", "src/pages/User/Login/index1.jsx", "src/components/Footer/index.jsx", "src/components/HeaderDropdown/index.jsx", "src/pages/system/host/components/UpdateForm.jsx", "src/pages/system/host/index.jsx", "src/utils/tree-util.js", "src/components/RightContent/index.jsx", "src/components/RightContent/AvatarDropdown.jsx"] |
| | | "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx", "src/app.tsx", "src/utils/icon-util.js", "src/pages/User/Login/index1.jsx", "src/components/Footer/index.jsx", "src/components/HeaderDropdown/index.jsx", "src/pages/system/host/components/UpdateForm.jsx", "src/pages/system/host/index.jsx", "src/utils/tree-util.js", "src/components/RightContent/index.jsx", "src/components/RightContent/AvatarDropdown.jsx", "src/pages/account/setting/style.style.js", "src/pages/account/setting/components/index.style.js", "src/pages/account/setting/components/security.jsx", "src/pages/account/setting/components/base.jsx"] |
| | | } |