Commit d092fa77 authored by 前端-钟卫鹏's avatar 前端-钟卫鹏
parents b3f99b6f 613ace49
......@@ -45,6 +45,19 @@ const AuthConfigRoute = {
path: '/memberCenter/systemSetting/collection',
name: 'collection',
component: '@/pages/systemSetting/collection',
},
// 账户安全设置
{
path: '/memberCenter/systemSetting/accountSetting',
name: 'accountSetting',
component: '@/pages/accountSetting'
},
{
path: '/memberCenter/systemSetting/editAccount',
name: 'editAccount',
component: '@/pages/accountSetting/editAccount',
hideInMenu: true,
hidePageHeader: true
}
],
......
......@@ -254,4 +254,6 @@ export default {
'menu.systemSetting.authConfig.userDetail': '用户详情',
'menu.systemSetting.collection': '收藏管理',
'menu.systemSetting.accountSetting': '账号安全设置',
'menu.systemSetting.editAccount': '编辑账号信息'
};
\ No newline at end of file
.passwordContainer {
position: relative;
.erros {
position: absolute;
top: 0;
right: 0;
}
}
\ No newline at end of file
import React from 'react';
// import { SchemaForm } from '@formily/antd';
import { Form, Input, Row, Col, Button, Select } from 'antd';
import styles from './index.less';
import { StepForwardOutlined } from '@ant-design/icons'
const Option = Select.Option;
const EditDataComponent = ({type}) => {
const renderPwd = () => {
return (
<>
<Form.Item
label="登录密码"
className={styles.passwordContainer}
>
<Row gutter={10}>
<Col span={18}>
<Form.Item
name="password"
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input.Password />
</Form.Item>
</Col>
<div className={styles.erros}>
<p>密码长度8-20个字符</p>
<p>密码长度8-20个字符</p>
<p>密码长度8-20个字符</p>
</div>
</Row>
</Form.Item>
<Form.Item
label="确认密码"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Row gutter={10}>
<Col span={18}>
<Form.Item
name="captcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input.Password />
</Form.Item>
</Col>
</Row>
</Form.Item>
</>
)
}
const renderPhone = () => {
return (
<>
<Form.Item label="新的手机号码">
<Row gutter={10}>
<Col span={4}>
<Select>
<Option value="jack"> <StepForwardOutlined />jack</Option>
</Select>
</Col>
<Col span={14}>
<Form.Item
name="captcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
</Row>
</Form.Item>
<Form.Item label="验证码">
<Row gutter={10}>
<Col span={14}>
<Form.Item
name="phoneCaptcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={6}>
<Button>获取验证码</Button>
</Col>
</Row>
</Form.Item>
</>
)
}
const renderEmail = () => {
return (
<>
<Form.Item label="邮箱地址">
<Row gutter={10}>
<Col span={14}>
<Form.Item
name="email"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={6}>
<Button>获取验证码</Button>
</Col>
</Row>
</Form.Item>
<Form.Item label="邮箱验证码">
<Row gutter={10}>
<Col span={18}>
<Form.Item
name="emailCaptcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
</Row>
</Form.Item>
</>
)
}
return (
<div>
{
renderEmail()
}
</div>
)
}
export default EditDataComponent
\ No newline at end of file
.box {
}
.container {
height: 32px;
line-height: 32px;
font-size: 14px;
background: #EBF7F2;
border: 1px solid #00B37A;
margin: 8px 0 32px 0;
width: 495px;
cursor: pointer;
position: relative;
.btn {
color: #00B37A;
text-align: center;
}
}
.sliderVerify {
position: absolute;
top: 50%;
left: 50%;
// background-color: #fff;
z-index: 99;
// transform: translateX(-130px);
margin-left: -130px;
margin-top: -136px;
}
\ No newline at end of file
import React, { useState } from 'react';
import styles from './index.less';
import { Row, Col } from 'antd';
import SliderVerify from '../SliderVerify';
const imageUrl = 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1689053532,4230915864&fm=26&gp=0.jpg'
const SafeVerification = () => {
const [visible, setVisible] = useState(false)
const handleVisible = () => {
setVisible(true)
}
const cancel = () => {
setVisible(false)
}
// console.log(visible)
const handleSuccess = () => {
setVisible(false);
}
return (
<div className={styles.box}>
<Row>
<Col span={16} offset={3}>
<div className={styles.container}>
<div className={styles.btn} onClick={handleVisible}>
点击进行验证
</div>
<div className={styles.sliderVerify}>
<SliderVerify
visible={visible}
onCancel={cancel}
onSuccess={handleSuccess}
imageUrl={imageUrl}
/>
</div>
</div>
</Col>
</Row>
</div>
)
}
export default SafeVerification
\ No newline at end of file
.sliderContainer {
display: flex;
flex-direction: row;
justify-content: center;
border: 1px solid #e6dede;
position: relative;
width: 260px;
box-shadow: 2px 2px 2px #e6dede;
border-radius: 4px;
background-color: #fff;
}
.loading {
display: flex;
flex-direction: row;
justify-content: center;
font-weight: 600;
font-size: 16px;
}
.container {
display: flex;
flex-direction: column;
// align-items: center;
padding: 5px 5px;
justify-content: center;
user-select: none;
.imageContainer {
margin-bottom: 15px;
background-size:100% 100%;
position: relative;
.canvas {
position: absolute;
left: 25px;
top: 45px;
}
.target {
position: absolute;
left: 175px;
top: 45px;
}
}
.slider {
width: 250px;
height: 45px;
position: relative;
border-radius: 45px;
.sliderHandle {
width: 45px;
height: 45px;
border-radius: 50%;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
background-color: #fff;
z-index: 99;
position: absolute;
left: 0;
top: 0;
right: auto;
// transform: translateX(-50%);
box-shadow: 1px 5px 2px #eee;
cursor: pointer;
}
.sliderRail {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
border-radius: 45px;
background-color: #f5f5f5;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.sliderTrack {
position: absolute;
top: 0;
left: 0;
z-index: 89;
right: 0;
bottom: 0;
border-top-left-radius: 45px;
border-bottom-left-radius: 45px ;
background-color: #91d5ff;
}
}
.footer {
display: flex;
flex-direction: row;
margin-top: 15px;
font-size: 20px;
color: #999;
padding: 5px 0 0 15px;
border-top: 1px solid #e6dede;
.cancel {
margin-right: 15px;
cursor: pointer;
}
.reset {
cursor: pointer;
}
}
}
\ No newline at end of file
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'
import styles from './index.less';
import { DoubleRightOutlined, CloseCircleOutlined, RedoOutlined } from '@ant-design/icons'
import { createClipPath } from './utils';
import { Spin } from 'antd';
const fragmentSize = 37;
const imageWidth = 250;
const imageHeight = 150;
// const imageUrl = 'https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1689053532,4230915864&fm=26&gp=0.jpg'
const differ = 5;
const READY = 0;
const SUCCESS = 1;
const ERROR = 2
interface SliderVerifyProps {
visible: boolean,
onCancel: any,
onSuccess?: any,
imageUrl: string
}
const SliderVerify: React.FC<SliderVerifyProps> = (props) => {
const [dragging, setDragging] = useState(false);
const [origin, setOrigin] = useState({ x: 0, y: 0 });
const [offset, setOffset] = useState({ x: 0, y: 0});
const [loading, setLoading] = useState(false);
const [clipImagePosition, setClipImagePosition] = useState({x: 0, y: 0});
const [status, setStatus] = useState(READY);
// const [visible, setVisible] = useState(props.visible || false)
const { visible, imageUrl } = props;
const shadowCanvas = useRef(null)
const fragmentCanvas = useRef(null);
const mouseDown = ({clientX, clientY}) => {
console.log("mouseDown", clientX);
setOrigin((state) => ({ x: clientX, y: clientY}))
setDragging(true);
};
const mouseMove = ({clientX, clientY}) => {
if(!dragging || status !== READY) {
return ;
}
let x = clientX - origin.x; // 计算拖动的距离
const min = 0;
const max = imageWidth - fragmentSize;
let currX = x;
currX = currX < min ? 0 : currX > max ? max : currX
const transition = {x: currX, y: origin.y };
setOffset((state) => {return {...state, ...transition}});
};
const mouseUp = () => {
if(!dragging || status !== READY) {
return ;
}
console.log("up")
setDragging(false)
const targetX = clipImagePosition.x;
const currentX = offset.x;
const match = Math.abs(targetX - currentX) <= 5;
if(match) {
onSuccess();
} else {
onError()
}
};
useEffect(() => {
if(visible) {
reRender();
}
}, [visible])
const sliderHandleStyle = useMemo(() => ({
left: (offset.x),
transition: (!dragging ? 'left 500ms' : 'none' )
}), [offset, dragging])
const trackStyle = useMemo(() => ({
width: !dragging && status == READY ? 0 : (offset.x + 22.5) + 'px',
transition: (!dragging ? 'left 500ms' : 'none' )
}), [offset, dragging])
const onSuccess = () => {
console.log("success");
setStatus(SUCCESS);
props.onSuccess && props.onSuccess();
setOffset({x: 0, y: 0})
}
const onError = () => {
console.log("error")
// setOffset({x: 0, y: 0})
reRender()
}
const onReset = () => {
reRender();
}
const reRender = () => {
setLoading(true);
setStatus(READY);
setOffset({x: 0, y: 0})
const objImage = new Image()
console.log(imageUrl)
objImage.addEventListener("load", () => {
console.log(objImage.width);
// 先获取两个ctx
const ctxShadow = shadowCanvas.current.getContext("2d")
const ctxFragment = fragmentCanvas.current.getContext("2d")
// 让两个ctx拥有同样的裁剪路径(可滑动小块的轮廓)
const styleIndex = Math.floor(Math.random() * 16)
createClipPath(ctxShadow, fragmentSize, styleIndex)
createClipPath(ctxFragment, fragmentSize, styleIndex)
// 随机生成裁剪图片的开始坐标
const clipX = Math.floor(fragmentSize + (imageWidth - 2 * fragmentSize) * Math.random())
const clipY = Math.floor((imageHeight - fragmentSize) * Math.random())
console.log(clipX, clipY);
const scaleWidth = objImage.width / imageWidth;
const scaleHeight = objImage.height / imageHeight;
// 让小块绘制出被裁剪的部分
// 175 是小图的, 还原成原图 应该是 x / 175 = objImage.width / imageWidth;
ctxFragment.drawImage(objImage, clipX * scaleWidth, clipY * scaleHeight,
fragmentSize * scaleWidth, fragmentSize * scaleHeight, 0, 0, fragmentSize, fragmentSize)
// 让阴影canvas带上阴影效果
ctxShadow.fillStyle = "rgba(0, 0, 0, 0.5)"
ctxShadow.fill()
// 恢复画布状态
ctxShadow.restore()
ctxFragment.restore()
// 设置裁剪小块的位置
setClipImagePosition({x: clipX, y: clipY});
// 修改状态
setLoading(false);
})
objImage.src = imageUrl
}
const handleClose = () => {
props.onCancel && props.onCancel()
}
return (
<>
{
!visible
? null
: (
<div>
{
loading
? <div className={styles.loading}><Spin /></div>
: null
}
<div className={styles.sliderContainer} style={{visibility: !loading ? 'visible' : 'hidden'}}>
<div className={styles.container}>
<div className={styles.imageContainer} style={{width: imageWidth, height: '150px', backgroundImage: `url("${imageUrl}")` }}>
<canvas
className={styles.canvas}
width={37}
height={37}
ref={shadowCanvas}
style={{ left: clipImagePosition.x + "px", top: clipImagePosition.y + "px" }}
/>
<canvas
className={styles.target}
width={37}
height={37}
ref={fragmentCanvas}
style={{ left: offset.x + "px", top: clipImagePosition.y + "px" }}
></canvas>
</div>
<div className={styles.slider} onMouseMove={mouseMove} onMouseLeave={mouseUp}>
<div className={styles.sliderHandle} onMouseDown={mouseDown} onMouseUp={mouseUp} style={sliderHandleStyle} ><DoubleRightOutlined /></div>
<div className={styles.sliderRail}>
<div className={styles.text}>向右滑动验证</div>
</div>
<div className={styles.sliderTrack} style={trackStyle}></div>
</div>
<div className={styles.footer}>
<div className={styles.cancel} onClick={handleClose}>
<CloseCircleOutlined />
</div>
<div className={styles.reset} onClick={onReset}>
<RedoOutlined />
</div>
</div>
</div>
</div>
</div>
)
}
</>
);
}
export default SliderVerify;
\ No newline at end of file
// 生成裁剪路径
export function createClipPath(ctx, size = 100, styleIndex = 0) {
// 这里对应的是左, 下。 右, 上 四种位置,顺时针或者逆时针向内 凹凸
const styles = [
[0, 0, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0],
[0, 0, 1, 1],
[0, 1, 0, 0],
[0, 1, 0, 1],
[0, 1, 1, 0],
[0, 1, 1, 1],
[1, 0, 0, 0],
[1, 0, 0, 1],
[1, 0, 1, 0],
[1, 0, 1, 1],
[1, 1, 0, 0],
[1, 1, 0, 1],
[1, 1, 1, 0],
[1, 1, 1, 1]
]
const style = styles[styleIndex]
const r = 0.1 * size
ctx.save()
ctx.beginPath()
// left
ctx.moveTo(r, r)
ctx.lineTo(r, 0.5 * size - r)
// r 园中心x, 0.5 * size 圆中心Y, r 半径, 1.5*Math.PI 起始角, 0.5*Math.PI结束角, 顺时针/逆时针
ctx.arc(r, 0.5 * size, r, 1.5 * Math.PI, 0.5 * Math.PI, style[0])
ctx.lineTo(r, size - r)
// bottom
ctx.lineTo(0.5 * size - r, size - r)
ctx.arc(0.5 * size, size - r, r, Math.PI, 0, style[1])
ctx.lineTo(size - r, size - r)
// right
ctx.lineTo(size - r, 0.5 * size + r)
ctx.arc(size - r, 0.5 * size, r, 0.5 * Math.PI, 1.5 * Math.PI, style[2])
ctx.lineTo(size - r, r)
// top
ctx.lineTo(0.5 * size + r, r)
ctx.arc(0.5 * size, r, r, 0, Math.PI, style[3])
ctx.lineTo(r, r)
ctx.clip()
ctx.closePath()
}
\ No newline at end of file
.header {
margin: 12px 0 32px 0;
.title {
width: 112px;
height: 22px;
font-size: 14px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #6B778C;
line-height: 22px;
margin-bottom: 16px;
}
.value {
height: 32px;
font-size: 24px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #172B4D;
line-height: 32px;
}
}
\ No newline at end of file
import React from 'react';
import styles from './index.less'
const TypeForHeader = (props) => {
const {type = 'phone'} = props;
const phoneRender = () => {
return (
<>
<div className={styles.title}>当前绑定手机号码</div>
<div className={styles.value}>+86 185 2929 6758</div>
</>
)
}
const emailRender = () => {
return (
<>
<div className={styles.title}>当前已认证邮箱</div>
<div className={styles.value}>735051883@qq.com</div>
</>
)
}
const payCodeRender = () => {
return (
null
)
}
const selectTypeRender = () => {
const { type = 'phone' } = props;
if(type == 'phone') {
return phoneRender();
} else if(type == 'email') {
return emailRender();
} else {
return payCodeRender()
}
}
return (
<div className={styles.header}>
{
selectTypeRender()
}
</div>
)
}
export default TypeForHeader
\ No newline at end of file
.container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
background-color: #fff;
padding: 36px 24px;
width: 610px;
.infos {
display: flex;
flex-direction: row;
.image {
margin-right: 22px;
width: 72px;
height: 72px;
}
.details {
.title {
font-size: 16px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 600;
color: #172B4D;
line-height: 24px;
margin-bottom: 16px;
}
.tips {
font-size: 13px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #42526E;
line-height: 22px;
margin-bottom: 0;
}
}
}
.controls {
width: 90px;
text-align: right;
// cursor: pointer;
}
}
\ No newline at end of file
import React from 'react';
import styles from './index.less';
import account_tips from '@/assets/imgs/account_tips.png';
import { Link } from 'umi';
const TITLE_NAP = {
'loginPwd': {
title: '登录密码',
desc: "互联网账号存在被盗风险,建议您定期更改密码以保护账号安全"
},
'email': {
title: '邮箱验证',
desc: "您验证的邮箱:{{email}}"
},
'phone': {
title: '手机验证',
desc: "您验证的手机:{{phone}}, 若丢失或已停用,请立刻更换,避免账号被盗"
} ,
'paycode': {
title: '支付密码',
desc: "您的支付密码已开启,建议您定期更换新的支付密码,提高安全性"
}
}
const TypeVerify = (props) => {
const { type, email, phone } = props;
const titleRender = () => {
return TITLE_NAP[type].title
}
const descRender = () => {
if(type == 'email' && email == '') {
return "你还没有绑定邮箱"
} else if(type == 'paycode') {
return "请前往设置支付密码"
}
return TITLE_NAP[type].desc.replace(/\{\{(.*?)\}\}/, (match, key) => { return props[key] });
}
const renderLink = () => {
if(type == 'email' && email == '') {
return <a>设置邮箱</a>
} else if(type == 'paycode') {
return <a>设置支付密码</a>
}
return <Link to={`/memberCenter/systemSetting/editAccount?type=${type}`}>修改</Link>
}
return (
<div className={styles.container}>
<div className={styles.infos}>
<div className={styles.image}>
<img src={account_tips} />
</div>
<div className={styles.details}>
<p className={styles.title}>{titleRender()}</p>
<p className={styles.tips}>{descRender()}</p>
</div>
</div>
<div className={styles.controls}>
{
renderLink()
}
</div>
</div>
)
}
export default TypeVerify;
\ No newline at end of file
import React from 'react';
// import { SchemaForm } from '@formily/antd';
import { Form, Input, Button, Row, Col } from 'antd';
import SafeVerification from '../../SafeVerification';
import TypeForHeader from '../../TypeForHeader';
import EditDataComponent from '../../EditDataComponent';
const layout = {
labelCol: { span: 3 },
wrapperCol: { span: 13 },
labelAlign: "left"
};
const tailLayout = {
wrapperCol: { offset: 3, span: 13 },
};
const EmailVerifyPanel = () => {
return (
<div>
<TypeForHeader type="email" />
<Form
{...layout}
name="basic"
>
<Form.Item label="验证码">
<Row gutter={10}>
<Col span={14}>
<Form.Item
name="captcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={6}>
<Button>获取验证码</Button>
</Col>
</Row>
</Form.Item>
<SafeVerification />
<EditDataComponent type="password" />
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Form.Item>
</Form>
</div>
)
}
export default EmailVerifyPanel;
\ No newline at end of file
import React from 'react';
// import { SchemaForm } from '@formily/antd';
import { Form, Input, Button, Row, Col } from 'antd';
import SafeVerification from '../../SafeVerification';
import TypeForHeader from '../../TypeForHeader';
import EditDataComponent from '../../EditDataComponent';
const layout = {
labelCol: { span: 3 },
wrapperCol: { span: 13 },
labelAlign: "left"
};
const tailLayout = {
wrapperCol: { offset: 3, span: 13 },
};
const PaycodeVerifyPanel = () => {
return (
<div>
<TypeForHeader type="payCode" />
<Form
{...layout}
name="basic"
>
<Form.Item label="验证码">
<Row gutter={10}>
<Col span={14}>
<Form.Item
name="captcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={6}>
<Button>获取验证码</Button>
</Col>
</Row>
</Form.Item>
<SafeVerification />
<EditDataComponent type="password" />
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Form.Item>
</Form>
</div>
)
}
export default PaycodeVerifyPanel;
\ No newline at end of file
import React from 'react';
// import { SchemaForm } from '@formily/antd';
import { Form, Input, Button, Row, Col } from 'antd';
import SafeVerification from '../../SafeVerification';
import TypeForHeader from '../../TypeForHeader';
import EditDataComponent from '../../EditDataComponent';
const layout = {
labelCol: { span: 3 },
wrapperCol: { span: 13 },
labelAlign: "left"
};
const tailLayout = {
wrapperCol: { offset: 3, span: 13 },
};
const PhoneVerifyPanel = () => {
return (
<div>
<TypeForHeader type="phone"/>
<Form
{...layout}
name="basic"
>
<Form.Item label="验证码">
<Row gutter={10}>
<Col span={14}>
<Form.Item
name="captcha"
noStyle
rules={[{ required: true, message: 'Please input the captcha you got!' }]}
>
<Input />
</Form.Item>
</Col>
<Col span={6}>
<Button>获取验证码</Button>
</Col>
</Row>
</Form.Item>
<SafeVerification />
<EditDataComponent type="password" />
<Form.Item {...tailLayout}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Form.Item>
</Form>
</div>
)
}
export default PhoneVerifyPanel;
\ No newline at end of file
import React from 'react';
import { Tabs } from 'antd';
import PhoneVerifyPanel from './PhoneVerifyPanel';
import PaycodeVerifyPanel from './PaycodeVerifyPanel';
import EmailVerifyPanel from './EmailVerifyPanel';
const { TabPane } = Tabs;
const VerifyPanel = () => {
return (
<Tabs defaultActiveKey="1" >
<TabPane tab="手机校验码验证" key="1">
<PhoneVerifyPanel />
</TabPane>
<TabPane tab="邮箱验证" key="2">
<EmailVerifyPanel />
</TabPane>
<TabPane tab="支付密码验证" key="3">
<PaycodeVerifyPanel />
</TabPane>
</Tabs>
)
}
export default VerifyPanel;
\ No newline at end of file
import Panel from './Panel';
export default Panel
\ No newline at end of file
import React from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { Card } from 'antd';
import ReutrnEle from '@/components/ReturnEle';
import { history } from 'umi';
import VerifyPanel from './components/VerifyPanel';
const MAP = {
'loginPwd': '修改登录密码',
'email': '修改邮箱',
'phone': '修改手机',
'paycode': '重置支付密码'
}
const EditAccount = (props) => {
const { type = 'loginPwd' } = props.location.query
// const { type } = props;
console.log(props);
return (
<PageHeaderWrapper
onBack={() => history.goBack()}
backIcon={<ReutrnEle description="返回" />}
title={MAP[type]}
>
<Card>
<VerifyPanel />
</Card>
</PageHeaderWrapper>
)
}
export default EditAccount;
\ No newline at end of file
.page {
display: flex;
flex-direction: row;
flex-wrap: wrap;
.item {
margin: 0 24px 24px 0;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react';
import { Card } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import TypeVerify from './components/TypeVerify';
import styles from './index.less';
import { PublicApi } from '@/services/api';
const getData = async () => {
///member/security/get
const res = await PublicApi.getMemberSecurityGet();
return res.data
}
const AccountSetting = () => {
const [account, setAccount] = useState({})
useEffect(() => {
async function init() {
const res = await getData();
console.log(res);
setAccount(res);
}
init()
}, [])
const TYPES = ['loginPwd', 'email', 'phone', 'paycode']
return (
<PageHeaderWrapper>
<div className={styles.page}>
{
TYPES.map((item) => {
return (
<div className={styles.item} key={item}>
<TypeVerify type={item} phone={account.phone} email={account.email} />
</div>
)
})
}
</div>
</PageHeaderWrapper>
)
}
export default AccountSetting;
\ No newline at end of file
.billInfo {
:global {
.ant-tabs-top >
.ant-tabs-nav {
.ant-tabs-ink-bar {
top: 0;
}
&::before {
border-bottom: none;
}
}
}
}
.dateWrap {
padding: 10px 16px 22px;
.time {
margin-top: 18px;
line-height: 22px;
font-weight: 400;
color: #172B4D;
}
}
.content {
display: flex;
&-left {
flex-shrink: 0;
width: 300px;
}
&-right {
flex: 1;
}
}
\ No newline at end of file
import React from 'react';
import {
Tabs,
Descriptions,
Row,
Col,
} from 'antd';
import MellowCard from '@/components/MellowCard';
import StatusTag from '@/components/StatusTag';
import TradeWrap from '../TradeWrap';
import styles from './index.less';
const { TabPane } = Tabs;
const BillInfo: React.FC = () => {
return (
<MellowCard
style={{
marginTop: 24,
}}
bodyStyle={{
padding: '0 24px 24px',
}}
>
<div className={styles.billInfo}>
<Tabs defaultActiveKey="1" onChange={() => {}}>
<TabPane
tab={(
<div className={styles.dateWrap}>
<StatusTag type="danger" title="逾期 9 天" />
<div className={styles.time}>2020/06/11 ~ 2020/07/11</div>
</div>
)}
key="1"
>
<div className={styles.content}>
<div className={styles['content-left']}>
<Descriptions column={1}>
<Descriptions.Item label="账单金额(元)">30,000.00</Descriptions.Item>
<Descriptions.Item label="账单最后还款日期">2020-08-20</Descriptions.Item>
<Descriptions.Item label="账单最后还款金额(元)">20,000.00</Descriptions.Item>
<Descriptions.Item label="还清日期">2020-08-21</Descriptions.Item>
</Descriptions>
</div>
<div className={styles['content-right']}>
<TradeWrap>
<TradeWrap.TradeItem width="33.33%">
<Descriptions column={1}>
<Descriptions.Item label="交易流水号">
<Row justify="space-between">
<Col span={12}>
<a>20200820000010</a>
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<StatusTag type="danger" title="待确认还款结果" />
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易项目">
<Row justify="space-between">
<Col span={12}>
还款
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<strong>+30,000.00元</strong>
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易时间">2020-08-25 08:58</Descriptions.Item>
</Descriptions>
</TradeWrap.TradeItem>
</TradeWrap>
</div>
</div>
</TabPane>
</Tabs>
</div>
</MellowCard>
);
};
export default BillInfo;
\ No newline at end of file
import React from 'react';
import MellowCard from '@/components/MellowCard';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import EyePreview from '@/components/EyePreview';
const HistoryList: React.FC = () => {
const columns: EditableColumns[] = [
{
title: '申请单号',
dataIndex: 'order',
render: text => (
<EyePreview
url={`/memberCenter/payandSettle/creditApplication/quotaMenage/detail`}
>
{text}
</EyePreview>
),
},
{
title: '调整前额度(元)',
dataIndex: 'content',
align: 'center',
},
{
title: '申请调整额度(元)',
dataIndex: 'byMemberName',
align: 'center',
},
{
title: '审批额度(元)',
dataIndex: 'reason',
align: 'center',
},
{
title: '申请时间',
dataIndex: 'createTime',
align: 'center',
},
];
const handlePaginationChange = () => {
};
return (
<MellowCard
title="历史授信申请"
style={{
marginTop: 24,
}}
>
<PolymericTable
rowKey="remark"
dataSource={[]}
columns={columns}
loading={false}
pagination={{
pageSize: 10,
total: 100,
}}
onPaginationChange={handlePaginationChange}
/>
</MellowCard>
);
};
export default HistoryList;
\ No newline at end of file
......@@ -8,10 +8,13 @@ import {
Select,
Space,
Button,
Descriptions,
Pagination,
} from 'antd';
import MellowCard from '@/components/MellowCard';
import { Pie } from '@/components/Charts';
import StatusTag from '@/components/StatusTag';
import TradeRecord from '../TradeRecord';
import styles from './index.less';
const { Option } = Select;
......@@ -35,7 +38,7 @@ const IntroduceRow: React.FC<IntroduceRowProps> = ({
return (
<Row gutter={23}>
<Col span={10}>
<MellowCard title="授信额度">
<MellowCard title="授信额度" fullHeight>
<Row gutter={20} align="middle">
<Col span={14}>
<Pie
......@@ -91,6 +94,7 @@ const IntroduceRow: React.FC<IntroduceRowProps> = ({
</Space>
</div>
)}
fullHeight
>
{!visibleRecord ? (
<>
......@@ -145,9 +149,7 @@ const IntroduceRow: React.FC<IntroduceRowProps> = ({
</div>
</>
) : (
<div>
2
</div>
<TradeRecord />
)}
</MellowCard>
</Col>
......
@import '../../../../../../global/styles/utils.less';
.record {
.list {
height: 290px;
overflow-y: auto;
overflow-x: hidden;
.silkyScrollbar();
}
.pagination {
margin-top: 10px;
text-align: right;
}
}
\ No newline at end of file
import React from 'react';
import {
Row,
Col,
Descriptions,
Pagination,
} from 'antd';
import StatusTag from '@/components/StatusTag';
import TradeWrap from '../TradeWrap';
import styles from './index.less';
const TradeRecord: React.FC = () => {
return (
<div className={styles.record}>
<div className={styles.list}>
<TradeWrap>
<TradeWrap.TradeItem>
<Descriptions column={1}>
<Descriptions.Item label="交易流水号">
<Row justify="space-between">
<Col span={12}>
<a>20200820000010</a>
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<StatusTag type="primary" title="待确认还款结果" />
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易项目">
<Row justify="space-between">
<Col span={12}>
还款
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<strong>+30,000.00元</strong>
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易时间">2020-08-25 08:58</Descriptions.Item>
<Descriptions.Item label="备注">订单号:DTR980</Descriptions.Item>
</Descriptions>
</TradeWrap.TradeItem>
<TradeWrap.TradeItem>
<Descriptions column={1}>
<Descriptions.Item label="交易流水号">
<Row justify="space-between">
<Col span={12}>
<a>20200820000010</a>
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<StatusTag type="primary" title="待确认还款结果" />
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易项目">
<Row justify="space-between">
<Col span={12}>
还款
</Col>
<Col
span={10}
style={{
textAlign: 'right',
}}
>
<strong>+30,000.00元</strong>
</Col>
</Row>
</Descriptions.Item>
<Descriptions.Item label="交易时间">2020-08-25 08:58</Descriptions.Item>
<Descriptions.Item label="备注">订单号:DTR980</Descriptions.Item>
</Descriptions>
</TradeWrap.TradeItem>
</TradeWrap>
</div>
<div className={styles.pagination}>
<Pagination size="small" total={50} />
</div>
</div>
);
};
export default TradeRecord;
\ No newline at end of file
.tradeWrap {
padding: 0;
margin: 0 -12px;
&-item {
padding: 0 12px 16px;
display: inline-block;
list-style: none;
&-content {
padding: 12px 16px;
background: #FAFBFC;
border-radius: 4px;
border: 1px solid #EBECF0;
:global {
.ant-descriptions-row {
&:last-child {
.ant-descriptions-item {
padding-bottom: 0;
}
}
}
}
}
}
}
\ No newline at end of file
import React, { ReactNode } from 'react';
import styles from './index.less';
interface TradeItemProps {
width?: number | string;
children?: ReactNode;
};
const TradeItem: React.FC<TradeItemProps> = ({
width = '50%',
children,
}) => {
return (
<li
className={styles['tradeWrap-item']}
style={{
width,
}}
>
<div className={styles['tradeWrap-item-content']}>
{children}
</div>
</li>
);
};
class TradeWrap extends React.Component {
static TradeItem = TradeItem;
render() {
const { children } = this.props;
return (
<ul className={styles.tradeWrap}>
{children}
</ul>
);
};
}
export default TradeWrap;
\ No newline at end of file
......@@ -16,6 +16,8 @@ import StatusTag from '@/components/StatusTag';
import styles from './index.less';
const IntroduceRow = React.lazy(() => import('./components/IntroduceRow'));
const BillInfo = React.lazy(() => import('./components/BillInfo'));
const HistoryList = React.lazy(() => import('./components/HistoryList'));
const QuotaMenageDetail: React.FC = () => {
......@@ -74,6 +76,14 @@ const QuotaMenageDetail: React.FC = () => {
<Suspense fallback={null}>
<IntroduceRow quotaData={quotaData} />
</Suspense>
<Suspense fallback={null}>
<BillInfo />
</Suspense>
<Suspense fallback={null}>
<HistoryList />
</Suspense>
</PageHeaderWrapper>
);
};
......
......@@ -495,8 +495,10 @@ const PositionSetting:React.FC<PositionSettingProps> = (props) => {
return parentState.value
});
// 商城类型修改的时候,就清空商品
addSchemaAction.setFieldValue('productId', "");
addSchemaAction.setFieldValue('productName', "");
if(!id) {
addSchemaAction.setFieldValue('productId', "");
addSchemaAction.setFieldValue('productName', "");
}
}
})
// FormEffectHooks.
......@@ -507,7 +509,7 @@ const PositionSetting:React.FC<PositionSettingProps> = (props) => {
schema={schema}
/>
<ModalTable
modalTitle='选择渠道会员'
modalTitle='选择会员'
confirm={handleOkAddMember}
cancel={handleCancelAddMember}
visible={visibleChannelMember}
......
import React from 'react';
import { FilePdfOutlined, FileWordOutlined, FileOutlined } from '@ant-design/icons';
import { FilePdfFilled, FileWordFilled, FileFilled } from '@ant-design/icons';
import { PublicApi } from '@/services/api'
import styles from './index.less';
......@@ -13,9 +13,9 @@ interface ContractList {
};
const IconMap = {
'.pdf': <FilePdfOutlined />,
'.doc': <FileWordOutlined />,
'.doxc': <FileWordOutlined />,
'.pdf': <FilePdfFilled />,
'.doc': <FileWordFilled />,
'.doxc': <FileWordFilled />,
};
const ContractItem: React.FC<ContractItem> = ({
......@@ -36,7 +36,7 @@ const ContractItem: React.FC<ContractItem> = ({
<li className={styles['contractList-item']} onClick={() => handleDownload(fileName, electronicContractUrl)}>
<a>
<div className={styles['contractList-item-icon']}>
{IconMap[suffix] || <FileOutlined />}
{IconMap[suffix] || <FileFilled />}
</div>
<div
className={styles['contractList-item-name']}
......
@import '../../../../../../global/styles/utils.less';
.contractList {
padding: 0;
margin: 0;
&-item {
padding: 6px 8px;
background: #F4F5F7;
border-radius: 4px;
list-style: none;
> a {
display: flex;
align-items: center;
}
&-icon {
flex-shrink: 0;
margin-right: 4px;
color: #7178EA;
}
&-name {
flex: 1;
.textOverflow();
}
&:not(:last-child) {
margin-bottom: 8px;
}
}
}
.noData {
color: #909399;
}
\ No newline at end of file
import React from 'react';
import { FilePdfFilled, FileWordFilled, FileFilled } from '@ant-design/icons';
import { PublicApi } from '@/services/api'
import styles from './index.less';
interface ContractItem {
electronicContractUrl?: string;
electronicContractName?: string;
};
interface ContractList {
dataSource: ContractItem[];
};
const IconMap = {
'.pdf': <FilePdfFilled />,
'.doc': <FileWordFilled />,
'.doxc': <FileWordFilled />,
};
const ContractItem: React.FC<ContractItem> = ({
electronicContractUrl,
electronicContractName,
}) => {
const index1 = electronicContractUrl.lastIndexOf('.');
const suffix = electronicContractUrl.slice(index1);
const index2 = electronicContractUrl.lastIndexOf('/');
// 如果没有文件名,但是有链接就从链接截取文件名
const fileName = electronicContractName ? electronicContractName : electronicContractUrl.slice(index2 + 1);
const handleDownload = (name, url) => {
window.location.href = `/api/order/contractTemplate/downloadContract?contractName=${name}&contractUrl=${url}`;
};
return (
<li className={styles['contractList-item']} onClick={() => handleDownload(fileName, electronicContractUrl)}>
<a>
<div className={styles['contractList-item-icon']}>
{IconMap[suffix] || <FileFilled />}
</div>
<div
className={styles['contractList-item-name']}
title={fileName}
>
{fileName}
</div>
</a>
</li>
);
};
const ContractList: React.FC<ContractList> = ({ dataSource }) => {
if (!Array.isArray(dataSource)) {
return <div className={styles.noData}>没有相关数据~</div>;
}
return (
<ul className={styles.contractList}>
{dataSource.map((item, index) => (
<ContractItem
key={index}
electronicContractUrl={item.electronicContractUrl}
electronicContractName={item.electronicContractName}
/>
))}
</ul>
);
};
export default ContractList;
\ No newline at end of file
......@@ -6,6 +6,7 @@ import { formatTimeString } from '@/utils'
import { DELIVERY_TYPE } from '@/constants'
import style from './index.less'
import { PublicApi } from '@/services/api'
import ContractList from '../ContractList'
export interface OrderMergeInfoProps { }
const payInfo = [
......@@ -64,20 +65,31 @@ const OrderMergeInfo: React.FC<OrderMergeInfoProps> = (props) => {
}
return (
<Row style={{ marginTop: 24 }}>
<Row style={{ marginTop: 24 }} gutter={24}>
<Col span={12}>
<MellowCard title='交易信息' blockClassName={style.fullHeight} className={style.fullHeight}>
<MellowCard title='交易信息' fullHeight>
<RenderCard infoList={payInfo} dataSource={data} />
</MellowCard>
</Col>
<Col span={6} push={1}>
<MellowCard title='其他信息' blockClassName={style.fullHeight} className={style.fullHeight}>
<Col span={6}>
<MellowCard title='其他信息' fullHeight>
<RenderCard infoList={otherInfo} dataSource={data} />
</MellowCard>
</Col>
<Col span={4} push={2}>
<MellowCard title='电子合同' blockClassName={style.fullHeight} className={style.fullHeight}>
<RenderCard infoList={electronInfo} dataSource={data} />
<Col span={6}>
<MellowCard title='电子合同' fullHeight>
<ContractList
dataSource={
data.electronicContractUrl ?
[
{
electronicContractUrl: data.electronicContractUrl,
electronicContractName: data.electronicContractName,
},
] :
null
}
/>
</MellowCard>
</Col>
</Row>
......
......@@ -2,7 +2,7 @@
* @Author: XieZhiXiong
* @Date: 2020-09-16 15:16:47
* @LastEditors: XieZhiXiong
* @LastEditTime: 2020-09-25 18:28:47
* @LastEditTime: 2020-09-28 20:19:46
* @Description: 联动逻辑相关
*/
import { FormEffectHooks, FormPath } from '@formily/antd';
......@@ -433,7 +433,7 @@ export const useBusinessEffects = (context, actions) => {
return `invoicesDetailsRequests.${$1}.amount`
}),
state => {
state.value = '';
state.value = `¥${(+value * current.price).toFixed(2)}`;
}
);
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment