first commit

This commit is contained in:
wtq
2025-11-28 09:19:55 +08:00
commit cc3972e719
163 changed files with 30371 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
import React from 'react';
import ReactSVG from 'react-svg'
import {NavLink} from 'react-router-dom';
/*
to
src
text
*/
export default (props) => (
<NavLink className="nav" to={props.to} activeClassName="nav-active" exact>
<ReactSVG src={props.src} className="svg"></ReactSVG>
<span className="nav-text">{props.text}</span>
</NavLink>
);

View File

@@ -0,0 +1,41 @@
@import '../../assets/style/colors.scss';
// 底部菜单
.bottom-bar {
display: flex;
// justify-content: space-between;
font-size: .2rem;
height: 1rem;
position: fixed;
z-index: 1000;
bottom: 0;
left: 0;
width: 100%;
background: white;
box-sizing: border-box;
border-top: 1px solid $gray;
.nav {
flex: 1;
color: $sliver;
text-decoration: none;
display: flex;
flex-flow: column;
align-items: center;
justify-content: center;
padding: 0 .2rem;
.svg {
svg {
fill: $sliver;
width: .5rem;
height: .5rem;
}
}
}
.nav-active {
color: $blue;
.svg {
svg {
fill: $blue;
}
}
}
}

View File

@@ -0,0 +1,21 @@
import React, { Component } from 'react';
import './bottom-bar.scss';
import NavLink from './NavLink';
import {withRouter} from 'react-router-dom';
import {IndexList} from '@/router';
class BottomBar extends Component {
render() {
return (
<div className="bottom-bar">
{
IndexList.map((link, index) => (
<NavLink key={index} {...link} to={link.to}></NavLink>
))
}
</div>
);
}
}
export default withRouter(BottomBar);

View File

@@ -0,0 +1,68 @@
@import '../../assets/style/colors.scss';
$fontSize: .28rem;
.btn {
font-size: $fontSize;
outline: none;
padding: .2rem;
border: 1px solid $gray;
border-radius: .1rem;
color: white;
cursor: pointer;
// box-shadow: 0 0 .05rem rgba($lightGray, .8);
// text-shadow: 1px 1px 1px rgba($black, 1);
box-sizing: border-box;
display: inline-flex;
align-items: center;
}
.btn-default {
background: white;
color: $black;
}
.btn-primary {
background: $primary;
}
.btn-success {
background: $success;
}
.btn-warning {
background: $warning;
}
.btn-danger {
background: $danger;
}
.btn-disable {
// opacity: .6;
color: $gray;
background: white;
cursor:not-allowed;
}
.btn-block {
display: flex;
align-items: center;
justify-content: center;
height: .9rem;
width: 100%;
}
.btn-loading {
display: inline-block;
width: $fontSize;
height: $fontSize;
margin-right: .1rem;
box-sizing: border-box;
border: .04rem solid $lightGray;
border-radius: 50%;
border-right-color: transparent;
animation: btnRotate .5s infinite ease-in-out;
}
@keyframes btnRotate {
from {
transform: rotateZ(0);
}
to {
transform: rotateZ(360deg);
}
}

View File

@@ -0,0 +1,55 @@
import React, {Component} from 'react';
import classNames from 'classnames';
import './button.scss';
/*
props:
type: null btn-default
primary
success
warning
danger
disabled boo
block boo
loading boo 是否是loading状态
extraClass str 额外的样式
nativeType 原生type
*/
class Button extends Component {
constructor (...args) {
super(...args);
}
onClick (e) {
if (!this.props.loading) {
this.props.onClick && this.props.onClick(e);
}
}
render () {
return (
<button
className={
classNames(
'btn',
this.props.type ? `btn-${this.props.type}`: 'btn-default',
{'btn-block': this.props.block},
{'btn-disable': this.props.disabled},
this.props.extraClass
)}
type={this.props.nativeType}
disabled={this.props.disabled}
onClick={this.onClick.bind(this)}
>
{
this.props.loading && (
<span className="btn-loading"></span>
)
}
{this.props.children}
</button>
)
}
}
export default Button;

View File

@@ -0,0 +1,139 @@
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './dialog.scss';
import BScroll from 'better-scroll';
import Animated from 'animated/lib/targets/react-dom'
/*
Dialog2.show('错误', desc, {
// showCancel: ,
// cancelText: '',
confirmText: '是的'
}, res => {
console.log(res);
});
*/
class Dialog extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
title: '',
message: '',
options: {
showCancel: true,
cancelText: '',
confirmText: ''
},
ani: new Animated.Value(0)
};
this.func = null;
this.scroll = null;
}
async componentDidMount () {
this.setState({
title: this.props.title,
message: this.props.message,
})
if (this.props.options) this.setState({
'options': {
...this.state.options,
...this.props.options
}
})
if (this.props.func) this.func = this.props.func;
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start(() => {
// console.log('入长了')
const code105Div = document.querySelector('.code105')
console.log(code105Div)
if (code105Div) {
this.scroll = new BScroll(code105Div, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
});
}
clickMask () {
this.fnClick(null);
}
fnClose () {
this.fnClick(false);
}
fnConfirm () {
this.fnClick(true);
}
fnClick (boo) {
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(async () => {
// await this.setState({show: false});
// 移除dom
try {
ReactDOM.unmountComponentAtNode(oDiv);
document.body.removeChild(oDiv);
this.func && this.func(boo);
} catch (e) {}
});
}
render () {
return (
<div className="dialog">
<Animated.div className="mask" onClick={this.clickMask.bind(this)} style={{opacity: this.state.ani}}>
<div className="dia-wrapper" onClick={e => e.stopPropagation()}>
<div className="dia-head">
{/* 标题 */}
{
this.state.title && (
<div className="title">{this.state.title || '标题'}</div>
)
}
</div>
<div className="dia-body">
{/* 内容 */}
<div className="message" dangerouslySetInnerHTML={{__html: this.state.message || '内容'}}></div>
</div>
<div className="dia-footer">
{/* 底部按钮 */}
{
this.state.options.showCancel && (
<div className="btn btn-cancel" onClick={this.fnClose.bind(this)}>{this.state.options.cancelText || '取消'}</div>
)
}
<div className="btn btn-confirm" onClick={this.fnConfirm.bind(this)}>{this.state.options.confirmText || '确定'}</div>
</div>
</div>
</Animated.div>
</div>
)
}
}
let oDiv = null;
Dialog.show = (title, message, options, fn) => {
let props = {
show: true,
title,
message,
options,
func: fn
}
oDiv = document.createElement('div');
document.body.appendChild(oDiv);
ReactDOM.render(React.createElement(Dialog, props), oDiv);
}
export default Dialog;

View File

@@ -0,0 +1,76 @@
@import '../../assets/style/colors.scss';
// dialog 对话框
.dialog {
font-size: .32rem;
.mask {
position: fixed;
z-index: 9000;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: rgba(black, .6);
}
.dia-wrapper {
position: fixed;
z-index: 9998;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: .1rem;
width: 4.5rem;
}
.dia-head {
text-align: center;
font-weight: 500;
padding: .2rem;
color: $extraLightBlack;
}
.dia-body {
padding: .2rem;
text-align: center;
font-size: .3rem;
color: $black;
word-break: break-all;
.message {
line-height: 1.5;
}
}
.dia-footer {
display: flex;
margin-top: .2rem;
border-top: 1px solid $gray;
align-items: center;
}
$btn-height: .8rem;
.btn {
flex: 1;
height: $btn-height;
line-height: $btn-height;
text-align: center;
}
.btn:nth-of-type(2) {
border-left: 1px solid $gray;
}
.btn-cancel {
color: $sliver;
}
.btn-confirm {
color: $blue;
font-weight: 500;
}
.success {
color: $success;
}
.address {
font-size: .26rem;
color: $sliver;
}
.error {
color: $danger;
}
.warning {
color: $warning;
}
}

View File

@@ -0,0 +1,26 @@
import Dialog from './Dialog.jsx';
/*
Dialog2.show('错误', desc, {
showCancel: true,
cancelText: '',
confirmText: '是的'
}, res => {
console.log(res); // true false
});
*/
export default {
show (title, message, options, fn) {
Dialog.show(title, message, options, fn);
},
showErr (e) {
let msg = e
if (Object.prototype.toString(e).indexOf('Error') > -1) {
msg = e.message
}
Dialog.show('错误', msg, {
showCancel: false
})
}
};

View File

@@ -0,0 +1,59 @@
@import '../../assets/style/colors.scss';
// dialog 对话框
.dialog {
font-size: .32rem;
.mask {
position: fixed;
z-index: 9000;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: rgba(black, .6);
}
.dia-wrapper {
position: fixed;
z-index: 9998;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: .1rem;
width: 4.5rem;
}
.dia-head {
text-align: center;
font-weight: 500;
padding: .2rem;
color: $extraLightBlack;
}
.dia-body {
padding: .2rem;
text-align: center;
font-size: .3rem;
color: $black;
}
.dia-footer {
display: flex;
margin-top: .2rem;
border-top: 1px solid $gray;
align-items: center;
}
$btn-height: .8rem;
.btn {
flex: 1;
height: $btn-height;
line-height: $btn-height;
text-align: center;
}
.btn:nth-of-type(2) {
border-left: 1px solid $gray;
}
.btn-cancel {
color: $sliver;
}
.btn-confirm {
color: $blue;
font-weight: 500;
}
}

View File

@@ -0,0 +1,107 @@
import React, {Component} from 'react';
import './dialog.scss';
import Animated from 'animated/lib/targets/react-dom'
/*
this.refs.dialog.show('', '成功注册!', {
// showCancel: false,
confirmText: '知道了'
}, confirm => {
console.log(confirm);
});
*/
class Dialog extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
title: '',
message: '',
options: {
showCancel: true,
cancelText: '',
confirmText: ''
},
ani: new Animated.Value(0)
}
}
async show (title, message, options, func) {
this.setState({
show: true,
title,
message
});
if (options) this.setState({
'options': {
...this.state.options,
...options
}
});
if (func) this.func = func;
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start();
}
clickMask () {
this.fnClose();
}
fnClose () {
this.func && this.func(false);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
fnConfirm () {
this.func && this.func(true);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
render () {
return (
<div className="dialog">
{
this.state.show && (
<Animated.div className="mask" onClick={this.clickMask.bind(this)} style={{opacity: this.state.ani}}>
<div className="dia-wrapper" onClick={e => e.stopPropagation()}>
<div className="dia-head">
{/* 标题 */}
{
this.state.title && (
<div className="title">{this.state.title || '标题'}</div>
)
}
</div>
<div className="dia-body">
{/* 内容 */}
<div className="message">{this.state.message || '内容'}</div>
</div>
<div className="dia-footer">
{/* 底部按钮 */}
{
this.state.options.showCancel && (
<div className="btn btn-cancel" onClick={this.fnClose.bind(this)}>{this.state.options.cancelText || '取消'}</div>
)
}
<div className="btn btn-confirm" onClick={this.fnConfirm.bind(this)}>{this.state.options.confirmText || '确定'}</div>
</div>
</div>
</Animated.div>
)
}
</div>
)
}
}
export default Dialog;

127
src/components/Form.js Normal file
View File

@@ -0,0 +1,127 @@
import React, { Component } from 'react';
import './form.scss';
import Toast from '@/components/Toast';
import fetchJson from '@/utils/fetch';
/*
getFormData
*/
class Form extends Component {
constructor (...args) {
super(...args);
this.state = {
list: {},
focus: '',
num: 0
}
}
async componentDidMount () {
for (let i = 0; i < this.props.fields.length; i++) {
await this.setState({
list: {
...this.state.list,
[this.props.fields[i].name]: this.props.fields[i].value
}
});
}
}
// input focus
fnFocus (name) {
console.log(name);
this.setState({focus: name});
}
fnBlur () {
console.log('失去焦点');
// this.setState({focus: ''});
}
// input 修改
async fnChange (name, e) {
// console.log(name, e.target.value);
// this[name] = e.target.value;
await this.setState({
list: {
...this.state.list,
[name]: e.target.value
}
})
// console.log(this.state);
}
// 清除
fnClear (name) {
// console.log('清除' + name);
this.refs[name].value = '';
this.setState({
list: {
...this.state.list,
[name]: ''
}
})
}
// 获得formData
getFormData () {
let form = new FormData(this.refs.form);
return form;
}
// 发送验证码
async fnSendCode (name) {
if (!/^\d{11}$/.test(this.state.list[name]) || this.state.num > 0) return false;
let tel = this.state.list[name];
console.log(tel);
let form = new FormData();
form.append('authID', tel);
form.append('authToken', this.props.token);
try {
await fetchJson('/v2/auth2/SendVerifyCode', {
method: 'POST',
body: form
});
this.refs.toast.show('短信发送成功');
// 开始倒计时
await this.setState({num: 59});
clearInterval(this.timer);
this.timer = setInterval(() => {
if (this.state.num === 0) {
clearInterval(this.timer);
} else {
this.setState({num: --this.state.num});
}
}, 1000);
} catch (e) {
this.refs.toast.show(e);
}
}
render() {
if (!this.props.fields) console.error('Form need fields');
return (
<form ref="form" className="form">
{
this.props.fields.length > 0 && (
this.props.fields.map((field, index) => (
<div className="form-item" key={index}>
<label className={'icon ' + field.icon} htmlFor={field.name}></label>
<input ref={field.name} type={field.name} placeholder={field.placeholder} name={field.name} id={field.name} defaultValue={field.value} onChange={this.fnChange.bind(this, field.name)} onFocus={this.fnFocus.bind(this, field.name)} onBlur={this.fnBlur.bind(this)}/>
{/* // 清空按钮 */}
{
(this.state.focus === field.name && this.state.list[field.name]) && (
<div className="clear" onClick={this.fnClear.bind(this, field.name)}></div>
)
}
{/* // 其他按钮 */}
{
field.btn && (
<div className={this.state.list[field.name] && this.state.list[field.name].length === 11 && this.state.num === 0 ? 'btn-getcode' : 'btn-getcode disabled'} onClick={this.fnSendCode.bind(this, field.name)}>{field.btn.name}{this.state.num > 0 && '(' + this.state.num + ')'}</div>
)
}
</div>
))
)
}
<Toast ref="toast"></Toast>
</form>
);
}
}
export default Form;

View File

@@ -0,0 +1,93 @@
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './loading.scss';
import Animated from 'animated/lib/targets/react-dom';
/*
用法:
// 显示
Loading.show();
// 隐藏
Loading.hide();
*/
class Loading extends Component {
constructor (...args) {
super(...args);
this.state = {
ani: new Animated.Value(1),
loading: false
}
}
componentDidMount () {
// this.disableScroll();
this.setState({loading: true})
}
// 锁定滚动
disableScroll () {
const documentBody = document.body;
if (documentBody) {
documentBody.style.setProperty('overflow', 'hidden');
}
}
// 允许滚动
enableScroll () {
const documentBody = document.body;
if (documentBody) {
documentBody.style.removeProperty('overlow');
}
}
// show () {
// this.setState({show: true});
// }
hide () {
// 出场动画
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
try {
this.setState({ani: new Animated.Value(1), loading: false});
ReactDOM.unmountComponentAtNode(oDiv);
document.body.removeChild(oDiv);
} catch (e) {}
// 销毁
});
}
render () {
return (
<div className="loading">
<Animated.div style={{opacity: this.state.ani}} className="loading-wrapper">
<div className="cmp-loading"></div>
</Animated.div>
</div>
)
}
}
let oDiv = null;
let loading = null;
Loading.show = () => {
let props = {};
let ele = document.querySelector('.zy-ui-loading');
if (ele) return false;
oDiv = document.createElement('div');
oDiv.className = 'zy-ui-loading';
document.body.appendChild(oDiv);
ReactDOM.render(React.createElement(Loading, props), oDiv, function () {
loading = this;
});
}
Loading.hide = () => {
try {
let ele = document.querySelector('.zy-ui-loading');
if (ele) loading.hide();
} catch (e) {
console.error(e)
}
}
export default Loading;

View File

@@ -0,0 +1,16 @@
import Loading from './Loading.jsx';
// Loading
// 显示
// Loading.show();
// 隐藏
// Loading.hide();
export default {
show: () => {
Loading.show();
},
hide: () => {
Loading.hide();
}
};

View File

@@ -0,0 +1,41 @@
@import '../../assets/style/colors.scss';
.loading {
.loading-wrapper {
position: fixed;
z-index: 9999;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(white, .4);
}
$size: .8rem;
.cmp-loading {
position: fixed;
left: 50%;
top: 50%;
// transform: translate(-50%, -50%);
margin-left: -($size / 2);
margin-top: -($size / 2);
border-radius: 50%;
width: $size;
height: $size;
border: .06rem solid $blue;
border-top-color: $lightGray;
border-bottom-color: $lightGray;
background: transparent;
box-sizing: border-box;
animation: dotRotate .7s infinite ease-in-out;
animation-fill-mode: both;
}
}
@keyframes dotRotate {
from {
transform: rotateZ(0);
}
to {
transform: rotateZ(360deg);
}
}

View File

@@ -0,0 +1,48 @@
import React, {Component} from 'react';
import Animated from 'animated/lib/targets/react-dom';
import './loading.scss';
/*
用法:
// 显示
this.refs.loading.show();
// 隐藏
this.refs.loading.hide();
*/
class Loading extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
ani: new Animated.Value(1)
}
}
show () {
this.setState({show: true});
}
hide () {
// 出场动画
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false, ani: new Animated.Value(1)});
});
}
render () {
return (
<div className="loading">
{
this.state.show && (
<Animated.div style={{opacity: this.state.ani}} className="loading-wrapper">
<div className="cmp-loading"></div>
</Animated.div>
)
}
</div>
)
}
}
export default Loading;

View File

@@ -0,0 +1,41 @@
@import '../../assets/style/colors.scss';
.loading {
.loading-wrapper {
position: fixed;
z-index: 9999;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(white, .4);
}
$size: .8rem;
.cmp-loading {
position: fixed;
left: 50%;
top: 50%;
// transform: translate(-50%, -50%);
margin-left: -($size / 2);
margin-top: -($size / 2);
border-radius: 50%;
width: $size;
height: $size;
border: .06rem solid $blue;
border-top-color: $lightGray;
border-bottom-color: $lightGray;
background: transparent;
box-sizing: border-box;
animation: dotRotate .7s infinite ease-in-out;
animation-fill-mode: both;
}
}
@keyframes dotRotate {
from {
transform: rotateZ(0);
}
to {
transform: rotateZ(360deg);
}
}

View File

@@ -0,0 +1,105 @@
import React, {Component} from 'react';
import './promopt.scss';
import Animated from 'animated/lib/targets/react-dom'
/*
this.refs.dialog.show('', '成功注册!', {
// showCancel: false,
confirmText: '知道了'
}, confirm => {
console.log(confirm);
});
*/
class Prompt extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
title: '',
options: {
showCancel: true,
cancelText: '',
confirmText: ''
},
ani: new Animated.Value(0)
}
}
async show (title, func) {
this.setState({
show: true,
title
});
// if (options) this.setState({
// 'options': {
// ...this.state.options,
// ...options
// }
// });
if (func) this.func = func;
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start();
}
clickMask () {
this.fnClose();
}
fnClose () {
this.func && this.func(false);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
fnConfirm () {
this.func && this.func(true);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
render () {
return (
<div className="promopt">
{
this.state.show && (
<Animated.div className="mask" onClick={this.clickMask.bind(this)} style={{opacity: this.state.ani}}>
<div className="dia-wrapper" onClick={e => e.stopPropagation()}>
<div className="dia-head">
{/* 标题 */}
{
this.state.title && (
<div className="title">{this.state.title || '标题'}</div>
)
}
</div>
<div className="dia-body">
{/* 内容 */}
{this.props.children}
</div>
<div className="dia-footer">
{/* 底部按钮 */}
{
this.state.options.showCancel && (
<div className="btn btn-cancel" onClick={this.fnClose.bind(this)}>{this.state.options.cancelText || '取消'}</div>
)
}
<div className="btn btn-confirm2" onClick={this.fnConfirm.bind(this)}>{this.state.options.confirmText || '确定'}</div>
</div>
</div>
</Animated.div>
)
}
</div>
)
}
}
export default Prompt;

View File

@@ -0,0 +1,60 @@
@import '../../assets/style/colors.scss';
// dialog 对话框
.promopt {
font-size: .32rem;
.mask {
position: fixed;
z-index: 9000;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: rgba(black, .6);
}
.dia-wrapper {
position: fixed;
z-index: 9998;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: .1rem;
width: 4.5rem;
}
.dia-head {
text-align: center;
font-weight: 500;
padding: .2rem;
color: $extraLightBlack;
}
.dia-body {
padding: .2rem;
text-align: center;
font-size: .3rem;
color: $black;
}
.dia-footer {
display: flex;
margin-top: .2rem;
border-top: 1px solid $gray;
align-items: center;
}
$btn-height: .8rem;
.btn {
flex: 1;
height: $btn-height;
line-height: $btn-height;
text-align: center;
// width: 50%;
}
.btn:nth-of-type(2) {
border-left: 1px solid $gray;
}
.btn-cancel {
color: $sliver;
}
.btn-confirm2 {
color: $blue;
font-weight: 500;
}
}

View File

@@ -0,0 +1,27 @@
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './loading.scss';
export default class Loading extends Component {
render () {
let {tip} = this.props;
console.log(tip);
return (
<div className="loading">
<div className="loading-mask">
<div className="wrapper">{tip}</div>
</div>
</div>
)
}
}
Loading.show = function showLoading (options) {
let props = options || {};
let oDiv = document.createElement('div');
document.body.appendChild(oDiv);
ReactDOM.render(React.createElement(Loading, props), oDiv);
}
// ReactDOM.unmountComponentAtNode(div);
// document.body.removeChild(div);

View File

@@ -0,0 +1,7 @@
import Loading from './Loading.jsx';
export default {
open (tip = '加载中') {
Loading.show({tip});
}
};

View File

@@ -0,0 +1,18 @@
.loading {
.loading-mask {
position: fixed;
left: 0;
top: 0;
bottom: 0;
right: 0;
background: rgba(black, .6);
z-index: 9999;
}
.wrapper {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
color: white;
}
}

View File

@@ -0,0 +1,75 @@
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import './toast.scss';
import Animated from 'animated/lib/targets/react-dom';
/*
用法:
Toast.show('aaa', [timer = 5s])
*/
class Toast extends Component {
constructor (...args) {
super(...args);
this.state = {
text: '',
ani: new Animated.Value(0)
};
this.timer = null;
}
componentDidMount () {
this.show(this.props.text, this.props.timer);
}
async show (text, timer = 4) {
// 清除定时器
clearTimeout(this.timer);
this.setState({
text
});
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start();
this.timer = await setTimeout(() => {
// 出场动画
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({
text: ''
});
ReactDOM.unmountComponentAtNode(oDiv[0]);
document.body.removeChild(oDiv[0]);
// console.log(oDiv);
oDiv.shift();
clearTimeout(this.timer);
});
}, timer * 1000);
}
render () {
return (
<div>
<Animated.div className="zy-toast" style={{opacity: this.state.ani}}>
{this.state.text}
</Animated.div>
</div>
)
}
}
let oDiv = [];
Toast.show = (text, timer) => {
let props = {
text, timer
}
let div = document.createElement('div');
div.className = 'zy-ui-toast';
document.body.appendChild(div);
ReactDOM.render(React.createElement(Toast, props), div);
oDiv.push(div);
}
export default Toast;

View File

@@ -0,0 +1,7 @@
import Toast from './Toast';
export default {
show (text, timer) {
Toast.show(text, timer);
}
};

View File

@@ -0,0 +1,14 @@
.zy-toast {
position: fixed;
z-index: 9998;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-width: 5rem;
font-size: .28rem;
background: rgba(black, .6);
padding: .2rem;
color: white;
border-radius: .1rem;
box-sizing: border-box;
}

View File

@@ -0,0 +1,107 @@
import React, {Component} from 'react';
import './dialog.scss';
import Animated from 'animated/lib/targets/react-dom'
/*
this.refs.dialog.show('', '成功注册!', {
// showCancel: false,
confirmText: '知道了'
}, confirm => {
console.log(confirm);
});
*/
class Dialog extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
title: '',
message: '',
options: {
showCancel: true,
cancelText: '',
confirmText: ''
},
ani: new Animated.Value(0)
}
}
async show (title, message, options, func) {
this.setState({
show: true,
title,
message
});
if (options) this.setState({
'options': {
...this.state.options,
...options
}
});
if (func) this.func = func;
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start();
}
clickMask () {
this.fnClose();
}
fnClose () {
this.func && this.func(false);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
fnConfirm () {
this.func && this.func(true);
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({show: false});
});
}
render () {
return (
<div className="dialog">
{
this.state.show && (
<Animated.div className="mask" onClick={this.clickMask.bind(this)} style={{opacity: this.state.ani}}>
<div className="dia-wrapper" onClick={e => e.stopPropagation()}>
<div className="dia-head">
{/* 标题 */}
{
this.state.title && (
<div className="title">{this.state.title || '标题'}</div>
)
}
</div>
<div className="dia-body">
{/* 内容 */}
<div className="message">{this.state.message || '内容'}</div>
</div>
<div className="dia-footer">
{/* 底部按钮 */}
{
this.state.options.showCancel && (
<div className="btn btn-cancel" onClick={this.fnClose.bind(this)}>{this.state.options.cancelText || '取消'}</div>
)
}
<div className="btn btn-confirm" onClick={this.fnConfirm.bind(this)}>{this.state.options.confirmText || '确定'}</div>
</div>
</div>
</Animated.div>
)
}
</div>
)
}
}
export default Dialog;

View File

@@ -0,0 +1,132 @@
import React, { Component } from 'react';
import './form.scss';
import Toast from './Toast';
import fetchJson from '@/utils/fetch';
/*
getFormData
*/
class Form extends Component {
constructor (...args) {
super(...args);
this.state = {
list: {},
focus: '',
num: 0
}
}
async componentDidMount () {
for (let i = 0; i < this.props.fields.length; i++) {
await this.setState({
list: {
...this.state.list,
[this.props.fields[i].name]: this.props.fields[i].value
}
});
}
}
// input focus
fnFocus (name) {
console.log(name);
this.setState({focus: name});
}
fnBlur () {
console.log('失去焦点');
// this.setState({focus: ''});
}
// input 修改
async fnChange (name, e) {
// console.log(name, e.target.value);
// this[name] = e.target.value;
await this.setState({
list: {
...this.state.list,
[name]: e.target.value
}
})
// console.log(this.state);
}
// 清除
fnClear (name) {
// console.log('清除' + name);
this.refs[name].value = '';
this.setState({
list: {
...this.state.list,
[name]: ''
}
})
}
// 获得formData
getFormData () {
// let form = new FormData(this.refs.form);
let json = {};
this.props.fields.forEach(field => {
json[field.name] = this.refs[field.name].value;
});
return json;
}
// 发送验证码
async fnSendCode (name) {
if (!/^\d{11}$/.test(this.state.list[name]) || this.state.num > 0) return false;
let tel = this.state.list[name];
console.log(tel);
let form = new FormData();
form.append('authID', tel);
form.append('authToken', this.props.token);
try {
await fetchJson('/v2/auth2/SendVerifyCode', {
method: 'POST',
body: form
});
this.refs.toast.show('短信发送成功');
// 开始倒计时
await this.setState({num: 59});
clearInterval(this.timer);
this.timer = setInterval(() => {
if (this.state.num === 0) {
clearInterval(this.timer);
} else {
this.setState({num: --this.state.num});
}
}, 1000);
} catch (e) {
this.refs.toast.show(e);
}
}
render() {
if (!this.props.fields) console.error('Form need fields');
return (
<form ref="form" className="form">
{
this.props.fields.length > 0 && (
this.props.fields.map((field, index) => (
<div className="form-item" key={index}>
<label className={'icon ' + field.icon} htmlFor={field.name}></label>
<input ref={field.name} type={field.name} placeholder={field.placeholder} name={field.name} id={field.name} defaultValue={field.value} onChange={this.fnChange.bind(this, field.name)} onFocus={this.fnFocus.bind(this, field.name)} onBlur={this.fnBlur.bind(this)}/>
{/* // 清空按钮 */}
{
(this.state.focus === field.name && this.state.list[field.name]) && (
<div className="clear" onClick={this.fnClear.bind(this, field.name)}></div>
)
}
{/* // 其他按钮 */}
{
field.btn && (
<div className={this.state.list[field.name] && this.state.list[field.name].length === 11 && this.state.num === 0 ? 'btn-getcode' : 'btn-getcode disabled'} onClick={this.fnSendCode.bind(this, field.name)}>{field.btn.name}{this.state.num > 0 && '(' + this.state.num + ')'}</div>
)
}
</div>
))
)
}
<Toast ref="toast"></Toast>
</form>
);
}
}
export default Form;

View File

@@ -0,0 +1,61 @@
import React, {Component} from 'react';
import './toast.scss';
import Animated from 'animated/lib/targets/react-dom';
/*
用法:
this.refs.xxx.show('aaa', [timer = 5s])
*/
class Toast extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
text: '',
ani: new Animated.Value(0)
};
this.timer = null;
}
async show (text, timer = 5) {
// 清除定时器
clearTimeout(this.timer);
this.setState({
show: true,
text
});
// 入场动画
Animated.timing(this.state.ani, {
toValue: 1,
duration: 400
}).start();
this.timer = await setTimeout(() => {
// 出场动画
Animated.timing(this.state.ani, {
toValue: 0,
duration: 400
}).start(() => {
this.setState({
show: false,
text: ''
});
clearTimeout(this.timer);
});
}, timer * 1000);
}
render () {
return (
<div>
{
this.state.show && (
<Animated.div className="zy-toast" style={{opacity: this.state.ani}}>
{this.state.text}
</Animated.div>
)
}
</div>
)
}
}
export default Toast;

View File

@@ -0,0 +1,60 @@
$maincolor: #409EFF;
// dialog 对话框
.dialog {
font-size: .32rem;
.mask {
position: fixed;
z-index: 9000;
left: 0;
right: 0;
bottom: 0;
top: 0;
background: rgba(black, .6);
}
.dia-wrapper {
position: fixed;
z-index: 9998;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: .1rem;
width: 4.5rem;
}
.dia-head {
// border-bottom: 1px solid $maincolor;
text-align: center;
font-weight: 500;
padding: .2rem;
color: #303133;
}
.dia-body {
padding: .2rem;
text-align: center;
font-size: .3rem;
color: #606266;
}
.dia-footer {
display: flex;
margin-top: .2rem;
border-top: 1px solid #ccc;
align-items: center;
}
$btn-height: .8rem;
.btn {
flex: 1;
height: $btn-height;
line-height: $btn-height;
text-align: center;
}
.btn:nth-of-type(2) {
border-left: 1px solid #ccc;
}
.btn-cancel {
color: #909399;
}
.btn-confirm {
color: $maincolor;
font-weight: 500;
}
}

View File

@@ -0,0 +1,71 @@
// 表单样式
$maincolor: #409EFF;
.form {
.form-item {
font-size: .3rem;
box-sizing: border-box;
height: 1rem;
padding: .2rem;
display: flex;
align-items: center;
background: white;
border-bottom: 1px solid #ccc;
position: relative;
}
.icon {
display: inline-block;
width: .36rem;
height: .36rem;
// border: 1px solid;
background-size: 100%;
background-position: center;
background-repeat: no-repeat;
margin-right: .2rem;
flex-shrink: 0;
}
.icon-username {
background-image: url(../../assets/imgs/icon-username.png);
}
.icon-nickname {
background-image: url(../../assets/imgs/icon-nickname2.png);
}
.icon-tel {
background-image: url(../../assets/imgs/icon-tel.png);
}
.icon-code {
background-image: url(../../assets/imgs/icon-code.png);
}
input {
color: #333;
// padding: .1rem;
height: .6rem;
outline: none;
border: none;
width: 100%;
}
input::-webkit-input-placeholder {
// color: black;
color: #ccc;
}
.clear {
// position: absolute;
background: url(../../assets/imgs/icon-clear.png) center center no-repeat;
width: .8rem;
height: .7rem;
// top: 50%;
// right: 0;
// transform: translateY(-50%);
background-size: .36rem;
}
.btn-getcode {
background: $maincolor;
color: white;
padding: .1rem .2rem;
border-radius: .1rem;
white-space: nowrap;
margin-left: .2rem;
}
.disabled {
opacity: .5;
}
}

View File

@@ -0,0 +1,14 @@
.zy-toast {
position: fixed;
z-index: 9999;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-width: 5rem;
font-size: .28rem;
background: rgba(black, .6);
padding: .2rem;
color: white;
border-radius: .1rem;
box-sizing: border-box;
}

71
src/components/form.scss Normal file
View File

@@ -0,0 +1,71 @@
// 表单样式
$maincolor: #409EFF;
.form {
.form-item {
font-size: .3rem;
box-sizing: border-box;
height: 1rem;
padding: .2rem;
display: flex;
align-items: center;
background: white;
border-bottom: 1px solid #ccc;
position: relative;
}
.icon {
display: inline-block;
width: .36rem;
height: .36rem;
// border: 1px solid;
background-size: 100%;
background-position: center;
background-repeat: no-repeat;
margin-right: .2rem;
flex-shrink: 0;
}
.icon-username {
background-image: url(../assets/imgs/icon-username.png);
}
.icon-nickname {
background-image: url(../assets/imgs/icon-nickname2.png);
}
.icon-tel {
background-image: url(../assets/imgs/icon-tel.png);
}
.icon-code {
background-image: url(../assets/imgs/icon-code.png);
}
input {
color: #333;
// padding: .1rem;
height: .6rem;
outline: none;
border: none;
width: 100%;
}
input::-webkit-input-placeholder {
// color: black;
color: #ccc;
}
.clear {
// position: absolute;
background: url(../assets/imgs/icon-clear.png) center center no-repeat;
width: .8rem;
height: .7rem;
// top: 50%;
// right: 0;
// transform: translateY(-50%);
background-size: .36rem;
}
.btn-getcode {
background: $maincolor;
color: white;
padding: .1rem .2rem;
border-radius: .1rem;
white-space: nowrap;
margin-left: .2rem;
}
.disabled {
opacity: .5;
}
}

View File

@@ -0,0 +1,40 @@
import React, { Component } from 'react';
import classNames from 'classnames';
import './main.scss';
/*
current={[]} [-1, 0, 1]
fields={[
{value: 1, text: '营业中'},
{value: 0, text: '休息中'},
{value: -1, text: '禁用中'}
]}
fnClick={this.changeStatuss.bind(this)} changeStatuss(val)
*/
class CheckBox extends Component {
fnClick (val) {
// console.log(val);
this.props.fnClick && this.props.fnClick(val);
}
render() {
let fields = this.props.fields || [];
let current = this.props.current || [];
return (
<div className="zy-ui-checkbox">
{
fields.map((field, index) => (
<div
className={classNames('zy-ui-checkbox-btn', {'zy-ui-checkbox-btn-active': current.indexOf(field.value) !== -1})}
key={index}
onClick={this.fnClick.bind(this, field.value)}
>{field.text}</div>
))
}
</div>
);
}
}
export default CheckBox;

View File

@@ -0,0 +1,37 @@
import React, { Component } from 'react';
import classNames from 'classnames';
import './main.scss';
/*
current={[]} [-1, 0, 1]
fields={[
{value: 1, text: '营业中'},
{value: 0, text: '休息中'},
{value: -1, text: '禁用中'}
]}
fnClick={this.changeStatuss.bind(this)} changeStatuss(val)
*/
class CheckBoxSelf extends Component {
fnClick (val) {
// console.log(val);
this.props.fnClick && this.props.fnClick(val);
}
render() {
let fields = this.props.fields || [];
let current = this.props.current || null;
return (
<div className="zy-ui-checkbox">
<div
className={classNames('zy-ui-checkbox-btn', {'zy-ui-checkbox-btn-active': current === fields[1]}, this.props.className)}
onClick={this.fnClick.bind(this, current === fields[1] ? fields[0] : fields[1])}
>
<span className={classNames({'zy-ui-checkbox-check-true': current === fields[1]}, {'zy-ui-checkbox-check-false': current !== fields[1]})}></span>{this.props.text}
</div>
</div>
);
}
}
export default CheckBoxSelf;

View File

@@ -0,0 +1,113 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Animated from 'animated/lib/targets/react-dom';
import classNames from 'classnames';
import BScroll from 'better-scroll';
import './main.scss';
const clientH = document.documentElement.clientHeight;
/*
ref="checklist"
datas={this.props.system.cityLevel2}
nullShow = false 是否显示空
nullLabel = '' null显示
map={{value: 'code', label: 'name'}}
current={storeSearch.placeID}
title="城市选择"
fnPick={this.cityPick.bind(this)}
// 打开
this.refs.xxx.show()
*/
class CheckList extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
ani: new Animated.Value(clientH)
};
this.scroll = null;
}
componentDidMount () {
}
componentDidUpdate () {
if (this.scroll) {
this.scroll.refresh();
this.scroll.scrollTo(0, 0);
} else {
if (this.refs.bodyWrapper) {
this.scroll = new BScroll(this.refs.bodyWrapper, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
// show
show () {
// console.log(this)
this.setState({show: true});
Animated.timing(this.state.ani, {
toValue: 0,
duration: 300
}).start(() => {
console.log('start')
});
}
hide () {
this.setState({show: false, ani: new Animated.Value(clientH)});
this.scroll = null;
}
// 返回
fnReturn () {
console.log('返回');
this.hide();
}
// 选取
fnClick (code) {
// console.log(code);
this.props.fnPick && this.props.fnPick(code);
this.hide()
}
render() {
return ReactDOM.createPortal((
<div className={classNames("zy-ui-checklist", {'pointerAll': this.state.show})}>
{
this.state.show && (
<div className="zy-ui-checklist-mask" onClick={this.hide.bind(this)}>
<Animated.div className="zy-ui-checklist-wrapper" style={{transform: [{translateY: this.state.ani}]}} onClick={e => e.stopPropagation()}>
<div className="zy-ui-checklist-header">
<div className="zy-ui-checklist-return" onClick={this.fnReturn.bind(this)}>返回</div>
<div className="zy-ui-checklist-title">{this.props.title}</div>
</div>
{/*onTouchMove={this.fnTouchMove.bind(this)} */}
<div className="zy-ui-checklist-body" ref="bodyWrapper">
<div>
{
this.props.nullShow && (
<div className={classNames('zy-ui-checklist-item', {'zy-ui-checklist-item-active': !this.props.current})} onClick={this.fnClick.bind(this, null)}>{this.props.nullLabel}</div>
)
}
{
this.props.datas.length > 0 && this.props.datas.map((data, index) => (
<div key={index} className={classNames('zy-ui-checklist-item', {'zy-ui-checklist-item-active': this.props.current === data[this.props.map.value]})} onClick={this.fnClick.bind(this, data[this.props.map.value])}>{data[this.props.map.label]}</div>
))
}
</div>
</div>
</Animated.div>
</div>
)
}
</div>
), document.getElementById('root'));
}
}
export default CheckList;

View File

@@ -0,0 +1,38 @@
import React, { Component } from 'react';
import './main.scss';
import classNames from 'classnames';
import SearchStore from './SearchStore';
class KeyWordSearch extends Component {
constructor (...args) {
super(...args);
this.state = {
name: '',
id: null
};
}
showPick = () => {
this.refs.storePick.show();
}
pickStore = async (val) => {
// console.log(val);
let {id, name} = val;
await this.setState({
name: `${name}-${id}`,
id
});
this.props.fnPick && this.props.fnPick(id);
}
render() {
return (
<div className="zy-ui-keywordsearch">
<div className={classNames('placeholder', {'name': this.state.name})} onClick={this.showPick}>{this.state.name ? this.state.name : this.props.placeholder}</div>
<SearchStore ref="storePick" fnPick={this.pickStore}></SearchStore>
</div>
);
}
}
export default KeyWordSearch;

View File

@@ -0,0 +1,60 @@
import React, {Component} from 'react';
import './main.scss';
import ReactSVG from 'react-svg';
import CheckList from '@/components/layout/CheckList';
import SVGDown from '@/assets/svg/icon-down.svg';
class PickInput extends Component {
constructor (...args) {
super(...args);
this.state = {};
}
// 选取后
fnPick (val) {
// console.log(val);
this.props.fnPick && this.props.fnPick(val);
}
// 弹出选择窗口
showList () {
this.refs.checklist.show();
}
// 输入框改变
fnChange = e => {
// console.log(e.target.value);
this.props.fnChange && this.props.fnChange(e.target.value);
}
// 选取输入框
render () {
// 显示文字
let text = this.props.datas.find(item => item[this.props.map['value']] === this.props.current);
return (
<div className="zy-ui-pick-input">
{/* 选择框 */}
<div className="zy-ui-pick-btn" onClick={this.showList.bind(this)}>
<span className="zy-ui-pick-text">{text[this.props.map['label']]}</span>
<ReactSVG className="zy-ui-pick-icon" src={SVGDown}></ReactSVG>
</div>
{/* 输入框 */}
<input className="zy-ui-pick-cmpinput" placeholder={this.props.placeholder} type={this.props.type || 'text'} onChange={this.fnChange}/>
{/* 操作按钮 */}
{
this.props.fnConfirm && (
<div className="zy-ui-pick-confirmbtn" onClick={() => {this.props.fnConfirm()}}>获取</div>
)
}
{/* 弹出窗口 */}
<CheckList
ref="checklist"
title={this.props.title}
datas={this.props.datas}
map={this.props.map}
current={this.props.current}
fnPick={this.fnPick.bind(this)}
></CheckList>
</div>
);
}
}
export default PickInput;

View File

@@ -0,0 +1,145 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import './main.scss';
import ReactSVG from 'react-svg';
import Animated from 'animated/lib/targets/react-dom';
import classNames from 'classnames';
import BScroll from 'better-scroll';
import SVGReturn from '@/assets/svg/icon-return.svg';
// import SVGConfirm from '@/assets/svg/icon-confirm.svg';
const screenW = document.body.clientWidth;
/*
title="更多检索条件"
fnReturn={this.props.fnReturn.bind(this)}
fnPrevConfirm={fn} ruturn true or false 确认前
fnConfirm={this.props.fnConfirm.bind(this)}
*/
class PopupPage extends Component {
constructor (...args) {
super(...args);
this.state = {
ani: new Animated.Value(screenW)
};
this.isMain = !this.props.fnReturn && !this.props.fnConfirm;
this.scroll = null;
}
componentDidMount () {
// 入场动画
Animated.timing(this.state.ani, {
toValue: 0,
duration: 300
}).start(() => {
// 增加滚动
// this.scroll = new BScroll(this.refs.toBottom, {
// click: true,
// stopPropagation: true,
// bounce: {
// top: false,
// bottom: false
// }
// })
});
}
componentDidUpdate () {
if (this.scroll) {
this.scroll.refresh();
} else {
if (this.refs.toBottom) {
this.scroll = new BScroll(this.refs.toBottom, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
// 返回按钮
fnReturn () {
// 出场动画
Animated.timing(this.state.ani, {
toValue: -screenW,
duration: 300
}).start(() => {
this.props.fnReturn();
});
}
// 确认按钮
async fnConfirm () {
if (this.props.fnPrevConfirm) {
let res = await this.props.fnPrevConfirm();
if (res) {
Animated.timing(this.state.ani, {
toValue: -screenW,
duration: 300
}).start(() => {
this.props.fnConfirm();
});
}
} else {
Animated.timing(this.state.ani, {
toValue: -screenW,
duration: 300
}).start(() => {
this.props.fnConfirm();
});
}
}
// 滚动到底部
toBottom () {
// console.log()
// this.refs.toBottom.scrollTop = 100;
}
fnTouchMove (e) {
console.log(e);
e.preventDefault();
e.stopPropagation();
return false;
}
render() {
return ReactDOM.createPortal((
<div className="popup-page">
<Animated.div className={classNames('popup-wrapper', {'popup-wrapper-main': this.isMain})} style={{transform: [{translateX: this.state.ani}]}}>
<div className={classNames('popup-menu', {'menu-center': this.isMain})}>
{/* 返回按钮 */}
{
this.props.fnReturn && (
<div className="btn-return" onClick={this.fnReturn.bind(this)}>
<ReactSVG className="icon" src={SVGReturn}></ReactSVG>
</div>
)
}
<div className="popup-title">
{
this.props.SVG && <ReactSVG src={this.props.SVG} className="svg"></ReactSVG>
}
{this.props.title}
</div>
{/* 确定按钮 */}
{
this.props.fnConfirm && (
<div className="btn-confirm" onClick={this.fnConfirm.bind(this)}>
{/* <ReactSVG className="icon" src={SVGConfirm}></ReactSVG> */}
<span className="btn-confirm-text">确定</span>
</div>
)
}
</div>
<div className={classNames('popup-content', {'popup-content-main': this.isMain}, this.props.className)} ref="toBottom">
<div>
{this.props.children}
</div>
</div>
</Animated.div>
</div>
), document.getElementById('root'));
}
}
export default PopupPage;

View File

@@ -0,0 +1,81 @@
import React, { Component } from 'react';
import './main.scss';
import ReactSVG from 'react-svg'
import classNames from 'classnames';
import SVGReturn from '@/assets/svg/icon-return.svg';
// import SVGConfirm from '@/assets/svg/icon-confirm.svg';
/*
title="更多检索条件"
fnReturn={this.props.fnReturn.bind(this)}
fnPrevConfirm={fn} ruturn true or false 确认前
fnConfirm={this.props.fnConfirm.bind(this)}
*/
class PopupPage extends Component {
constructor (...args) {
super(...args);
this.state = {};
this.isMain = !this.props.fnReturn && !this.props.fnConfirm;
}
// 返回按钮
fnReturn () {
this.props.fnReturn();
}
// 确认按钮
async fnConfirm () {
if (this.props.fnPrevConfirm) {
let res = await this.props.fnPrevConfirm();
if (res) {
this.props.fnConfirm();
}
} else {
this.props.fnConfirm();
}
}
// 滚动到底部
toBottom () {
// console.log()
// this.refs.toBottom.scrollTop = 100;
}
render() {
return (
<div className="popup-page2">
<div className={classNames('popup-wrapper', {'popup-wrapper-main': this.isMain})}>
<div className={classNames('popup-menu', {'menu-center': this.isMain})}>
{/* 返回按钮 */}
{
this.props.fnReturn && (
<div className="btn-return" onClick={this.fnReturn.bind(this)}>
<ReactSVG className="icon" src={SVGReturn}></ReactSVG>
</div>
)
}
<div className="popup-title">
{
this.props.SVG && <ReactSVG src={this.props.SVG} className="svg"></ReactSVG>
}
{this.props.title}
</div>
{/* 确定按钮 */}
{
this.props.fnConfirm && (
<div className="btn-confirm" onClick={this.fnConfirm.bind(this)}>
{/* <ReactSVG className="icon" src={SVGConfirm}></ReactSVG> */}
<span className="btn-confirm-text">确定</span>
</div>
)
}
</div>
<div className={classNames('popup-content', {'popup-content-main': this.isMain})} ref="toBottom">
{this.props.children}
</div>
</div>
</div>
);
}
}
export default PopupPage;

View File

@@ -0,0 +1,36 @@
import React, { Component } from 'react';
import classNames from 'classnames';
import './main.scss';
// 单选组件
/*
fields={[
{value: 0, text: '不限定'},
{value: 1, text: '已绑定'},
{value: -1, text: '未绑定'}
]}
id={}
current={storeSearch.vendorStoreConds['0']}
fnClick={} return val id
*/
class Radio extends Component {
fnClick (val) {
// console.log(val);
this.props.fnClick && this.props.fnClick(val, this.props.id);
}
render() {
return (
<div className="zy-ui-radio">
{
this.props.fields && this.props.fields.map((field, index) => (
<div key={index} className={classNames('zy-ui-radio-btn', {'zy-ui-radio-btn-active': field.value === this.props.current})} onClick={this.fnClick.bind(this, field.value)}>{field.text}</div>
))
}
</div>
);
}
}
export default Radio;

View File

@@ -0,0 +1,237 @@
import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';
import fetchJsonp from 'fetch-jsonp';
import fetchJson from '@/utils/fetch';
import './main.scss';
import classNames from 'classnames';
import BScroll from 'better-scroll';
import * as dd from 'dingtalk-jsapi';
import Loading from '@/components/Loading';
import ReactSVG from 'react-svg'
import SVGAddress from '@/assets/svg/icon-address.svg';
import SVGAddressList from '@/assets/svg/icon-addresslist.svg';
import SVGMYPOSITION from '@/assets/svg/icon-curposition.svg';
const clientH = document.documentElement.clientHeight;
/*
region 市名
ref="SearchAddress"
fnPick={this.fn.bind(this)}
// 打开
this.refs.xxx.show()
*/
class SearchAddress extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
ani: new Animated.Value(clientH),
keyword: '',
addressList: [],
lat: '',
lng: ''
};
this.scroll = null;
}
async componentDidMount () {
console.log('地图 mounted');
await this.getAddress();
}
componentDidUpdate () {
console.log('update');
if (this.scroll) {
this.scroll.refresh();
this.scroll.scrollTo(0, 0);
} else {
if (this.refs.scrollList) {
this.scroll = new BScroll(this.refs.scrollList, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
async posiAddress () {
let {result} = await (await fetchJsonp(`https://apis.map.qq.com/ws/geocoder/v1/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&location=${this.state.lat},${this.state.lng}&get_poi=1`)).json()
// console.log('datasdfdsfdsfd', result);
if (result) {
this.setState({addressList: result.pois});
}
}
// 腾讯地图查找地址关键字
async getAddress () {
// let {data} = await (await fetchJsonp(`https://apis.map.qq.com/ws/place/v1/suggestion/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&keyword=${this.state.keyword}&region=${this.props.region}&policy=1&region_fix=1`)).json()
let {data} = await (await fetchJsonp(`https://apis.map.qq.com/ws/place/v1/search/?output=jsonp&key=ZGGBZ-RUWLQ-XY25J-GD2AV-YPT2T-6EFA6&keyword=${encodeURI(this.state.keyword)}&boundary=region(${this.props.region},0)`)).json()
if (data) {
this.setState({addressList: data});
}
// address: "四川省成都市青羊区童子街50号"
// location: {lat, lng}
// title: '童子街'
}
// 输入关键词
async handleKeyword (e) {
await this.setState({keyword: e.target.value});
await this.getAddress(this.state.keyword);
}
// show
async show () {
await this.setState({show: true});
Animated.timing(this.state.ani, {
toValue: 0,
duration: 300
}).start();
this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this));
// if (dd.ios || dd.android) {
// dd.device.geolocation.get({
// targetAccuracy: 200,
// coordinate: 1,
// onSuccess: res => {
// let {longitude, latitude} = res;
// this.setState({
// lat: latitude,
// lng: longitude
// });
// },
// onFail: err => {
// this.setState({
// lat: JSON.stringify(err)
// });
// }
// });
// }
}
hide () {
this.setState({show: false, ani: new Animated.Value(clientH)});
this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this));
this.scroll = null;
}
// 滚动失去焦点
handleBlur () {
this.refs.keyword.blur();
}
// 选取地址
fnClick (address) {
// console.log(address);
this.props.fnPick && this.props.fnPick(address);
this.hide();
}
// 返回
fnReturn () {
console.log('返回');
this.hide();
}
// 获取当前定位
async getCurPosition () {
// 清除关键字,失去焦点
this.refs.keyword.value = '';
this.setState({keyword: ''});
this.refs.keyword.blur();
console.log('获取当前定位');
let config = {
agentId: process.env.NODE_ENV !== 'production' ? '241047291' : '239461075', // 241047291 239461075
corpId: 'ding7ab5687f3784a8db',
timeStamp: Math.round(new Date().getTime() / 1000),
nonceStr: 'rosy' + new Date().getTime(),
signature: '', // 接口获取
jsApiList: ['device.geolocation.get']
}
const url = window.location.origin + window.location.pathname;
console.log(config);
// 通过京西接口后去signature
let signature = await fetchJson(`/v2/ddapi/Sign?url=${url}&nonceStr=${config.nonceStr}&timeStamp=${config.timeStamp}`);
console.log('signature', signature);
config.signature = signature;
// 通过接口获取鉴权
if (dd.ios || dd.android) {
dd.ready(() => {
dd.config({
...config,
});
dd.ready(() => {
Loading.show();
dd.device.geolocation.get({
targetAccuracy: 200,
coordinate: 1,
onSuccess: async res => {
let {longitude, latitude} = res;
await this.setState({
lat: latitude,
lng: longitude
});
// 通过经纬度获取地址列表
await this.posiAddress();
Loading.hide();
},
onFail: err => {
console.log(JSON.stringify(err));
dd.device.notification.alert({
message: JSON.stringify(err),
title: '错误'
})
Loading.hide();
}
});
});
dd.error(err => {
console.log(JSON.stringify(err));
dd.device.notification.alert({
message: JSON.stringify(err),
title: '错误'
})
});
})
}
}
render() {
return (
<div className={classNames("zy-ui-checklist search-address", {'pointerAll': this.state.show})}>
{
this.state.show && (
<div className="zy-ui-checklist-mask" onClick={this.hide.bind(this)}>
<Animated.div className="zy-ui-checklist-wrapper" style={{transform: [{translateY: this.state.ani}]}} onClick={e => e.stopPropagation()}>
<div className="zy-ui-address-header">
<div className="zy-ui-checklist-return" onClick={this.fnReturn.bind(this)}>返回</div>
<div className="zy-ui-address-input">
<input ref="keyword" type="text" onChange={this.handleKeyword.bind(this)} placeholder="请输入详细地址进行检索"/>
<ReactSVG className="svg" src={SVGAddress}></ReactSVG>
</div>
<div className="zy-ui-checklist-position" onClick={this.getCurPosition.bind(this)}>
<ReactSVG className="my-position" src={SVGMYPOSITION}></ReactSVG>
</div>
</div>
<div className="zy-ui-address-body" ref="scrollList">
<div>
{
this.state.addressList.length > 0 && this.state.addressList.map(address => (
<div className="address-item" key={address.id} onClick={this.fnClick.bind(this, address)}>
<ReactSVG className="svg" src={SVGAddressList}></ReactSVG>
<div className="address-content">
<div className="content1">{address.title}</div>
<div className="content2">{address.address}</div>
</div>
<div className="pick-text">选择</div>
</div>
))
}
</div>
</div>
</Animated.div>
</div>
)
}
</div>
);
}
}
export default SearchAddress;

View File

@@ -0,0 +1,161 @@
import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';
import fetchJson from '@/utils/fetch';
import BScroll from 'better-scroll';
import './main.scss';
import classNames from 'classnames';
import {debounce} from '@/utils'
import ReactSVG from 'react-svg'
import SVGAddress from '@/assets/svg/icon-store.svg';
// import SVGAddressList from '@/assets/svg/icon-addresslist.svg';
const clientH = document.documentElement.clientHeight;
/*
region 市名
ref="SearchAddress"
fnPick={this.fn.bind(this)}
// 打开
this.refs.xxx.show()
*/
class SearchStore extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
ani: new Animated.Value(clientH),
storeList: [],
keyword: ''
};
this.doSearch = debounce(this.doSearch, 1000)
}
componentDidUpdate () {
if (this.scroll) {
this.scroll.refresh();
this.scroll.scrollTo(0, 0);
} else {
if (this.refs.scrollList) {
this.scroll = new BScroll(this.refs.scrollList, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
// 查询门店
async getAPI (keyword) {
try {
let {stores} = await fetchJson(`/v2/store/GetStores?keyword=${this.state.keyword}&pageSize=10`);
console.log(stores)
if (stores) {
this.setState({storeList: stores});
} else {
this.setState({storeList: []});
}
} catch (e) {
this.setState({storeList: []});
}
}
// 输入关键词
async handleKeyword (e) {
// let _this = this
// debounce(async (e) => {
// console.log(e.target.value)
// await _this.setState({keyword: e.target.value});
// await _this.getAPI(_this.state.keyword);
// }, 1000)
this.doSearch(e.target.value)
}
doSearch = async (value) => {
await this.setState({keyword: value});
await this.getAPI(this.state.keyword);
}
// show
async show () {
await this.setState({show: true, storeList: []});
Animated.timing(this.state.ani, {
toValue: 0,
duration: 300
}).start();
this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this));
// this.refs.keyword.focus();
await this.getAPI(this.state.keyword);
}
hide () {
this.setState({show: false, ani: new Animated.Value(clientH)});
this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this));
this.scroll = null;
}
// 滚动失去焦点
handleBlur () {
this.refs.keyword.blur();
}
// 选取地址
fnClick (item) {
// console.log(item);
this.props.fnPick && this.props.fnPick(item);
this.hide();
}
// 返回
fnReturn () {
console.log('返回');
this.hide();
}
// 清除
fnClear = () => {
this.props.fnClear && this.props.fnClear()
this.hide()
}
render() {
return (
<div className={classNames("zy-ui-checklist search-address", {'pointerAll': this.state.show})}>
{
this.state.show && (
<div className="zy-ui-checklist-mask" onClick={this.hide.bind(this)}>
<Animated.div className="zy-ui-checklist-wrapper" style={{transform: [{translateY: this.state.ani}]}} onClick={e => e.stopPropagation()}>
<div className="zy-ui-address-header store-pick">
<div className="zy-ui-checklist-return" onClick={this.fnReturn.bind(this)}>返回</div>
<div className="zy-ui-address-input">
<input ref="keyword" type="text" onChange={this.handleKeyword.bind(this)} placeholder="请输入门店关键字"/>
<ReactSVG className="svg" src={SVGAddress}></ReactSVG>
</div>
{
this.props.fnClear && (
<div className="zy-ui-checklist-clear" onClick={this.fnClear}>清除选择</div>
)
}
</div>
<div className="zy-ui-address-body" ref="scrollList">
<div>
{
this.state.storeList.length > 0 && this.state.storeList.map(store => (
<div className="address-item" key={store.id} onClick={this.fnClick.bind(this, store)}>
<ReactSVG className="svg" src={SVGAddress}></ReactSVG>
<div className="address-content">
<div className="content1">{store.name}</div>
<div className="content2">ID:{store.id} {store.cityName}-{store.districtName}</div>
</div>
<div className="pick-text">选择</div>
</div>
// <div>111</div>
))
}
</div>
</div>
</Animated.div>
</div>
)
}
</div>
);
}
}
export default SearchStore;

View File

@@ -0,0 +1,140 @@
import React, { Component } from 'react';
import Animated from 'animated/lib/targets/react-dom';
import fetchJson from '@/utils/fetch';
import BScroll from 'better-scroll';
import './main.scss';
import classNames from 'classnames';
import ReactSVG from 'react-svg'
import SVGAddress from '@/assets/svg/icon-user.svg';
// import SVGAddressList from '@/assets/svg/icon-addresslist.svg';
const clientH = document.documentElement.clientHeight;
/*
region 市名
ref="SearchAddress"
fnPick={this.fn.bind(this)}
// 打开
this.refs.xxx.show()
*/
class SearchUser extends Component {
constructor (...args) {
super(...args);
this.state = {
show: false,
ani: new Animated.Value(clientH),
userList: [],
keyword: ''
};
}
componentDidUpdate () {
if (this.scroll) {
this.scroll.refresh();
this.scroll.scrollTo(0, 0);
} else {
if (this.refs.scrollList) {
this.scroll = new BScroll(this.refs.scrollList, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
// 查询门店
async getAPI (keyword) {
try {
let {data} = await fetchJson(`/v2/user2/GetUsers?keyword=${keyword}&userType=14&offset=0&pageSize=20`);
console.log('用户列表', data)
if (data) {
this.setState({userList: data});
} else {
this.setState({userList: []});
}
} catch (e) {
this.setState({userList: []});
}
}
// 输入关键词
async handleKeyword (e) {
await this.setState({keyword: e.target.value});
await this.getAPI(this.state.keyword);
}
// show
async show () {
await this.setState({show: true, userList: []});
Animated.timing(this.state.ani, {
toValue: 0,
duration: 300
}).start();
this.refs.scrollList.addEventListener('scroll', this.handleBlur.bind(this));
// this.refs.keyword.focus();
await this.getAPI(this.state.keyword);
}
hide () {
this.setState({show: false, ani: new Animated.Value(clientH)});
this.refs.scrollList.removeEventListener('scroll', this.handleBlur.bind(this));
this.scroll = null;
}
// 滚动失去焦点
handleBlur () {
this.refs.keyword.blur();
}
// 选取地址
fnClick (item) {
// console.log(item);
this.props.fnPick && this.props.fnPick(item);
this.hide();
}
// 返回
fnReturn () {
console.log('返回');
this.hide();
}
render() {
return (
<div className={classNames("zy-ui-checklist search-address", {'pointerAll': this.state.show})}>
{
this.state.show && (
<div className="zy-ui-checklist-mask" onClick={this.hide.bind(this)}>
<Animated.div className="zy-ui-checklist-wrapper" style={{transform: [{translateY: this.state.ani}]}} onClick={e => e.stopPropagation()}>
<div className="zy-ui-address-header">
<div className="zy-ui-checklist-return" onClick={this.fnReturn.bind(this)}>返回</div>
<div className="zy-ui-address-input">
<input ref="keyword" type="text" onChange={this.handleKeyword.bind(this)} placeholder="请输入关键字进行检索"/>
<ReactSVG className="svg" src={SVGAddress}></ReactSVG>
</div>
</div>
<div className="zy-ui-address-body" ref="scrollList">
<div>
{
this.state.userList.length > 0 && this.state.userList.map(user => (
<div className="address-item" key={user.id} onClick={this.fnClick.bind(this, user)}>
<ReactSVG className="svg" src={SVGAddress}></ReactSVG>
<div className="address-content">
<div className="content1">{user.name}</div>
<div className="content2">tel:{user.mobile}</div>
</div>
<div className="pick-text">选择</div>
</div>
// <div>111</div>
))
}
</div>
</div>
</Animated.div>
</div>
)
}
</div>
);
}
}
export default SearchUser;

View File

@@ -0,0 +1,21 @@
import React, { Component } from 'react';
import classNames from 'classnames';
class Tabs extends Component {
render() {
return (
<ul className="zy-ui-tabs">
{
this.props.fields && this.props.fields.map(field => (
<li className={classNames('zy-ui-tabs-item', {'zy-ui-tabs-item-active': this.props.current === field.index})} key={field.index} onClick={() => {
this.props.fnClick(field.index)
}}>{field.text}</li>
))
}
</ul>
);
}
}
export default Tabs;

View File

@@ -0,0 +1,436 @@
// 弹出窗口
@import '../../assets/style/colors.scss';
.popup-page, .popup-page2 {
.popup-wrapper {
position: fixed;
z-index: 2000;
left: 0;
top: 0;
bottom: 0;
right: 0;
background: white;
}
.popup-wrapper-main {
bottom: 1rem;
}
.popup-menu {
height: 1rem;
background: $blue;
}
.popup-content {
// position: absolute;
box-sizing: border-box;
width: 100%;
// top: 1rem;
height: calc(100vh - 1rem);
// overflow-y: auto;
overflow: hidden;
// -webkit-overflow-scrolling: touch;
background: #fafafa;
// touch-action: none;
// pointer-events: none;
}
.popup-content-main {
height: calc(100% - 1rem);
}
.popup-menu {
display: flex;
justify-content: space-between;
align-items: center;
// padding: 0 .2rem;
}
.menu-center {
justify-content: center;
}
.popup-title {
color: white;
font-size: .32rem;
padding: 0 .2rem;
.svg {
display: inline-block;
margin-right: .1rem;
vertical-align: -0.06rem;
svg {
fill: white;
width: .34rem;
height: .34rem;
}
}
}
.btn-return, .btn-confirm {
color: $gray;
font-size: .28rem;
// padding: .1rem .4rem;
height: 100%;
width: 1.6rem;
// border: 1px solid white;
border-radius: .1rem;
display: flex;
.icon {
margin: auto 0;
svg {
width: .5rem;
height: .5rem;
fill: white;
}
}
}
.btn-return {
.icon {
margin-left: .1rem;
}
}
.btn-confirm {
justify-content: flex-end;
.icon {
margin-right: .1rem;
}
.btn-confirm-text {
margin: auto 0;
margin-right: .2rem;
color: white;
font-size: .32rem;
// text-decoration: underline;
// font-weight: bold;
background: $success;
padding: .14rem .3rem;
border-radius: .1rem;
border: 1px solid white;
// box-shadow: 0 0 .05rem rgba($gray, 1);
}
}
}
.popup-page2 {
.popup-wrapper {
position: static;
}
.popup-content-main {
height: calc(100% - 2rem);
}
}
// 单选 多选
.zy-ui-radio, .zy-ui-checkbox {
display: flex;
font-size: .28rem;
// border-radius: .1rem;
flex-flow: row wrap;
.zy-ui-radio-btn, .zy-ui-checkbox-btn {
background: $gray;
color: white;
padding: .18rem;
transition: all .3s ease-in-out;
// &:first-of-type {
// border-top-left-radius: .1rem;
// border-bottom-left-radius: .1rem;
// }
// &:last-of-type {
// border-top-right-radius: .1rem;
// border-bottom-right-radius: .1rem;
// }
}
.zy-ui-radio-btn-active {
background: $blue;
}
.zy-ui-checkbox-btn-active {
background: $success;
}
.zy-ui-checkbox-check-true {
display: inline-block;
width: .4rem;
text-align: center;
color: white;
&:before {
content: "✔\fe0e";
color: white;
}
}
.zy-ui-checkbox-check-false {
display: inline-block;
width: .4rem;
text-align: center;
color: white;
&:before {
content: "";
color: white;
}
}
}
// checkList 列表单选
.zy-ui-checklist {
.zy-ui-checklist-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: rgba(black, .4);
z-index: 3000;
}
.zy-ui-checklist-wrapper {
background: white;
position: absolute;
bottom: 0;
width: 100%;
height: 80%;
border-top-left-radius: .2rem;
border-top-right-radius: .2rem;
padding: .2rem;
box-sizing: border-box;
}
.zy-ui-checklist-header {
font-size: .32rem;
height: .8rem;
box-sizing: border-box;
border-bottom: 1px solid $blue;
}
.zy-ui-checklist-return {
position: absolute;
line-height: .8rem;
// background: red;
width: 1.5rem;
color: $sliver;
font-size: .3rem;
z-index: 1;
}
.zy-ui-checklist-title {
line-height: .8rem;
text-align: center;
color: $blue;
}
.zy-ui-checklist-body {
font-size: .3rem;
height: calc(100% - .8rem);
overflow: hidden;
// -webkit-overflow-scrolling: touch;
& > div {
display: flex;
flex-flow: row wrap;
}
.zy-ui-checklist-item {
text-align: center;
padding: .2rem;
border-radius: .5rem;
width: 50%;
box-sizing: border-box;
}
.zy-ui-checklist-item-active {
background: $lightBlue;
color: white;
}
}
.zy-ui-address-header {
font-size: .32rem;
height: .8rem;
box-sizing: border-box;
border-bottom: 1px solid $sliver;
// width: 100%;
.zy-ui-checklist-return {
width: 1.2rem;
// background: red;
}
.zy-ui-checklist-position {
position: absolute;
// background: red;
width: 1rem;
color: $sliver;
font-size: .3rem;
z-index: 1;
top: .38rem;
right: .2rem;
text-align: center;
.my-position {
svg {
fill: $sliver;
width: .36rem;
height: .36rem;
}
}
}
.zy-ui-address-input {
text-align: center;
// background: green;
margin-left: 1.2rem;
height: 100%;
// width: 5rem;
position: relative;
}
input {
font-size: .32rem;
position: absolute;
left: 2.24rem;
top: 50%;
transform: translate(-50%, -50%);
padding: .1rem .2rem;
padding-left: .8rem;
outline: none;
border-radius: .1rem;
border: 1px solid $sliver;
width: 3.6rem;
}
.svg {
position: absolute;
left: .2rem;
top: 50%;
transform: translateY(-50%);
svg {
fill: $sliver;
width: .36rem;
height: .36rem;
}
}
}
.store-pick {
display: flex;
align-items: center;
.zy-ui-address-header {
position: static;
}
.zy-ui-address-input {
flex: 1;
margin-left: 0;
}
.zy-ui-checklist-return {
position: static;
flex:none;
}
.zy-ui-checklist-clear {
flex: none;
line-height: .8rem;
// background: red;
width: 1.5rem;
color: $sliver;
font-size: .3rem;
text-align: right;
}
}
.zy-ui-address-body {
height: calc(100% - .8rem);
overflow: hidden;
// -webkit-overflow-scrolling: touch;
}
.address-item {
font-size: .32rem;
display: flex;
align-items: center;
border-bottom: 1px solid $gray;
padding: .2rem;
.svg {
width: .6rem;
flex-shrink: 0;
svg {
fill: $success;
width: .4rem;
height: .4rem;
}
}
.address-content {
flex: 1;
.content1 {}
.content2 {
margin-top: .1rem;
color: $sliver;
font-size: .24rem;
}
}
.pick-text {
flex-shrink: 0;
color: $blue;
width: .8rem;
text-align: right;
}
}
}
// 地址列表
.search-address {
}
// Tabs
.zy-ui-tabs {
margin: 0;
padding: 0;
font-size: .32rem;
display: flex;
justify-content: space-around;
background: white;
box-shadow: 0 0 .05rem $gray;
position: relative;
z-index: 1;
.zy-ui-tabs-item {
list-style: none;
height: .8rem;
line-height: .8rem;
padding: 0 .2rem;
box-sizing: border-box;
color: $black;
transition: all .3s;
}
.zy-ui-tabs-item-active {
border-bottom: .04rem solid $blue;
color: $blue;
font-weight: bold;
}
}
// 选择输入框
.zy-ui-pick-input {
display: flex;
align-items: center;
box-sizing: border-box;
width: 100%;
.zy-ui-pick-btn {
display: flex;
color: $sliver;
width: 2rem;
flex-shrink: 0;
.zy-ui-pick-icon {
svg {
width: .32rem;
height: .32rem;
fill: $sliver;
}
}
}
.zy-ui-pick-cmpinput {
color: $black;
border: 1px solid $gray;
padding: .1rem;
border-radius: .1rem;
outline: none;
width: 100%;
flex: 1;
}
.zy-ui-pick-confirmbtn {
flex-shrink: 0;
margin-left: .2rem;
padding: .14rem .3rem;
border-radius: .1rem;
background: $primary;
color: white;
}
}
.zy-ui-keywordsearch {
border: 1px solid $gray;
padding: .1rem .2rem;
border-radius: .1rem;
width: 100%;
box-sizing: border-box;
.placeholder {
color: $sliver;
// box-sizing: border-box;
width: 100%;
// white-space:nowrap;
// overflow:hidden;
// text-overflow:ellipsis;
}
.name {
color: $black;
}
}

View File

@@ -0,0 +1,525 @@
import React, { Component } from 'react';
import './cmp-modify.scss';
import {connect} from 'react-redux';
import fetchJson from '@/utils/fetch';
import Loading from '@/components/Loading';
import {isNum, isEmpty, isTel, isTel2} from '@/utils/regExp';
import {businessHours, dealOpenTime} from '@/utils/projectTools';
import Dialog from '@/components/Dialog';
import CheckList from '@/components/layout/CheckList';
import {mapPlaces} from '@/utils/mapData';
import ReactSVG from 'react-svg'
import SVGGo from '@/assets/svg/icon-go.svg';
import Toast from '@/components/Toast';
import BScroll from 'better-scroll';
import {formatDate} from '@/utils/tools'
// import PopupPage from '@/components/layout/PopupPage';
import Input from './Input';
import Radio from '@/components/layout/Radio';
import SearchAddress from '@/components/layout/SearchAddress';
class CmpModifyBase extends Component {
constructor (...args) {
super(...args);
this.state = {
districtData: [],
keyword: '',
// printerType: null,
printerInfo: [],
printerVendorInfoArr: [],
autoEnableAt: 0,
autoTime: [
{
label: '将在明天自动营业',
value: 0
},
{
label: '将在后天自动营业',
value: 1
}
]
};
this.scroll = null;
this.timeList = businessHours();
this.uniArr = ['status', 'openTime1', 'closeTime1', 'openTime2', 'closeTime2', 'autoEnableAt'];
this.showTestPrint = this.props.shop.printerVendorID !== 0;
}
async componentDidMount () {
await this.setState(prev => {
return {
...prev,
...this.props.shop
};
});
// await this.getStore();
console.log(this.state)
await this.getDistrict();
// 打印厂商
let {printerVendorInfo} = this.props.system.cms;
// [
// {value: 0, label: '无'},
// {value: 200, label: '飞鹅'},
// {value: 201, label: '外卖管家'}
// ]
let printerVendorInfoArr = [
{value: 0, label: '无'}
];
for (let attr in printerVendorInfo) {
printerVendorInfoArr.push({
value: parseInt(attr, 10),
label: printerVendorInfo[attr][0]
});
}
this.setState({printerVendorInfoArr});
// 设置打印机信息
this.setState({
printerInfo: printerVendorInfo[this.state.printerVendorID] || []
});
this.scroll = new BScroll(this.refs.baseWrapper, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
// 获取区信息
async getDistrict () {
Loading.show();
try {
// 请求城市列表 level = 2
let res = await fetchJson('/v2/cms/GetPlaces?parentCode=' + this.state.cityCode);
console.log('区信息', mapPlaces(res));
await this.setState({districtData: mapPlaces(res)});
} catch (e) {} finally {
Loading.hide();
}
}
// 修改属性
async handleModify (key, val) {
console.log(key, val);
if (this.uniArr.some(item => item.indexOf(key) !== -1)) {
// 单独请求接口
await this.apiTimeAndStatus(key, val);
}
if (key === 'autoEnableAt') {
let autoEnableAt = val ? formatDate(+new Date() + 2 * 24 * 3600 * 1000, 'YYYY-MM-DD') : formatDate(+new Date() + 1 * 24 * 3600 * 1000, 'YYYY-MM-DD')
this.setState({status: 0, autoEnableAt})
} else {
await this.setState({[key]: val});
if (key === 'status' && val !== 0) {
this.setState({autoEnableAt: null})
}
}
// 网络打印机
if (key === 'printerVendorID') {
console.log('key == printerVendorID');
// 清空sn和key
this.setState({
printerSN: '',
printerKey: ''
});
this.showTestPrint = false // 切换打印机,隐藏测试打印按钮
this.refs.printerSN && this.refs.printerSN.fnClear();
this.refs.printerKey && this.refs.printerKey.fnClear();
if (val === 0) {
this.setState({
printerInfo: [
{value: 0, label: '无'}
]
});
} else {
let {printerVendorInfo} = this.props.system.cms;
this.setState({
printerInfo: printerVendorInfo[val]
});
}
}
if (key === 'cityCode') {
await this.getDistrict();
await this.setState({districtCode: this.state.districtData[0].code});
}
if (key === 'openTime1' && val === 0) this.setState({closeTime1: 0});
if (key === 'openTime2' && val === 0) this.setState({closeTime2: 0});
if (key === 'closeTime1' && val === 0) this.setState({openTime1: 0});
if (key === 'closeTime2' && val === 0) this.setState({openTime2: 0});
}
// 单独请求接口
async apiTimeAndStatus (key, val) {
try {
Loading.show();
let json = {};
if (this.uniArr.some(item => item.indexOf(key) !== -1)) {
if (key === 'openTime1' && val === 0) json['closeTime1'] = 0
if (key === 'openTime2' && val === 0) json['closeTime2'] = 0
if (key === 'closeTime1' && val === 0) json['openTime1'] = 0
if (key === 'closeTime2' && val === 0) json['openTime2'] = 0
}
if (key === 'autoEnableAt') {
json.status = 0
json[key] = val ? formatDate(+new Date() + 2 * 24 * 3600 * 1000, 'YYYY-MM-DD') : formatDate(+new Date() + 1 * 24 * 3600 * 1000, 'YYYY-MM-DD')
} else {
json[key] = val;
}
let form = new FormData();
form.append('storeID', this.state.id);
form.append('payload', JSON.stringify(json));
let res = await fetchJson('/v2/store/UpdateStore', {
method: 'PUT',
body: form
});
console.log(res);
Toast.show('修改成功');
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 点击开始时间
fnOpenTime (type) {
if (type === 1) {
this.refs.openTime1.show();
} else {
this.refs.openTime2.show();
}
}
// 点击结束时间
fnCloseTime (type) {
if (type === 1) {
this.refs.closeTime1.show();
} else {
this.refs.closeTime2.show();
}
}
// 点击打印机类型
fnPrinterVendorID () {
this.refs.printerVendorID.show();
}
// 处理打印机类型
dealPrinterVendorID (type) {
if (type === 0) {
return '无';
} else {
let {printerVendorInfo} = this.props.system.cms;
return printerVendorInfo[type][0];
}
}
// 点击市选择
async fnCityPick () {
this.refs.cityCode.show();
}
// 点击区选择
fnDistrictPick () {
this.refs.districtCode.show();
}
// 接入地图
showMap () {
this.refs.SearchAddress.show();
}
// 选取地址
addressPick (address) {
console.log(address);
this.setState({address: address.address || address.title, lat: address.location.lat, lng: address.location.lng});
}
// 删除按钮
fnDelete () {
Dialog.show('确认', `是否删除 ${this.state.name}`, {}, res => {
// console.log(res);
if (res) {
// 开始删除
}
})
}
// 测试打印按钮
async handleTestPrint () {
console.log('测试打印');
try {
let form = new FormData();
form.append('vendorOrderID', 'test');
form.append('vendorID', this.state.id);
let res = await fetchJson('/v2/order/PrintOrder', {
method: 'PUT',
body: form
});
console.log('测试打印', res);
if (res.printerStatus === 2) {
Toast.show('测试打印发送成功');
} else {
Toast.show('打印机状态错误');
}
} catch (e) {
console.log(e);
} finally {}
}
// 确认前
async fnSubmit () {
console.log(this.state);
// 存放错误消息
let arr = [];
if (!isNum(this.state.id)) arr.push('京西门店ID必须是纯数字');
if (!isEmpty(this.state.name)) arr.push('店铺名称不能为空');
if (!isTel(this.state.tel1)) arr.push('店长手机不合法');
if (!this.state.address) arr.push('请输入门店详细地址');
if (this.state.deliveryRangeType === 3 && !isNum(this.state.deliveryRange)) arr.push('配送半径不合法');
if (this.state.printerVendorID !== 0 && !isEmpty(this.state.printerSN)) arr.push('请填写' + this.state.printerInfo[1])
if ((this.state.printerVendorID !== 0 && this.state.printerVendorID !== 202) && !isEmpty(this.state.printerKey)) arr.push('请填写' + this.state.printerInfo[2])
if (arr.length > 0) {
Dialog.show('错误', `<span class="error">${arr.join('<br />')}</span>`, {
showCancel: false,
confirmText: '好的'
})
return false;
} else {
// 开始修改门店
try {
Loading.show();
let form = new FormData();
let json = JSON.parse(JSON.stringify(this.state));
json.id = Number(json.id);
// 清除无用数据
delete json.districtData;
delete json.keyword;
delete json.createdAt;
delete json.updatedAt;
delete json.lastOperator;
delete json.deletedAt;
delete json.openTime2;
delete json.closeTime2;
delete json.idCardFront;
delete json.idCardBack;
delete json.idCardHand;
delete json.licence;
delete json.licenceCode;
delete json.StoreMaps;
delete json.CourierMaps;
delete json.getVendorStore;
// 单独请求接口
delete json.status;
delete json.openTime1;
delete json.closeTime1;
delete json.openTime2;
delete json.closeTime2;
form.append('storeID', json.id);
form.append('payload', JSON.stringify(json));
let res = await fetchJson('/v2/store/UpdateStore', {
method: 'PUT',
body: form
});
console.log(res);
Dialog.show('成功', `<span class="success">${json.name} 修改成功</span>`, {showCancel: false, confirmText: '知道了'});
return true;
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
return false;
} finally {
Loading.hide();
}
}
}
// 临时休息
fnAutoOpen () {
this.refs.pickAutoOpen.show()
}
render() {
let cityData = this.props.system.cityLevel2.find(item => item.code === this.state.cityCode);
// 城市名称
let cityName = cityData ? cityData.name : '未知'
let districtData = this.state.districtData.find(item => item.code === this.state.districtCode);
// 区名称
let districtName = districtData ? districtData.name : '未知'
return (
<div className="cmp-modify-base">
<div className="modify-base-wrapper" ref="baseWrapper">
{
this.state.id && (
<div>
<div className="header-title">基本信息</div>
{/* 京西门店ID */}
<div className="create-cell">
<div className="cell-label">京西门店ID:</div>
<div className="cell-text">{this.state.id}</div>
</div>
{/* 店铺名称 */}
<div className="create-cell">
<div className="cell-label">店铺名称:</div>
<Input check={isEmpty} errText="店铺名称不能为空" fnBlur={this.handleModify.bind(this, 'name')} value={this.state.name} placeholder="请输入店铺名称"></Input>
</div>
{/* 店长手机 */}
<div className="create-cell">
<div className="cell-label">店长手机:</div>
<Input check={isTel} errText="店长手机不合法" fnBlur={this.handleModify.bind(this, 'tel1')} value={this.state.tel1} placeholder="请输入店长手机号" type="number"></Input>
</div>
{/* tel2 */}
<div className="create-cell">
<div className="cell-label">tel2:</div>
<Input check={isTel2} errText="tel2手机不合法" fnBlur={this.handleModify.bind(this, 'tel2')} value={this.state.tel2} placeholder="请输入tel2(选填)" type="number"></Input>
</div>
{/* 营业时间 */}
<div className="create-cell">
<div className="cell-label">营业时间段1:</div>
<div className="cell-time1">
<div className="time" onClick={this.fnOpenTime.bind(this, 1)}>{dealOpenTime(this.state.openTime1)}</div>
<span></span>
<div className="time" onClick={this.fnCloseTime.bind(this, 1)}>{dealOpenTime(this.state.closeTime1)}</div>
</div>
</div>
<div className="create-cell">
<div className="cell-label">营业时间段2:</div>
<div className="cell-time1">
<div className="time" onClick={this.fnOpenTime.bind(this, 2)}>{dealOpenTime(this.state.openTime2)}</div>
<span></span>
<div className="time" onClick={this.fnCloseTime.bind(this, 2)}>{dealOpenTime(this.state.closeTime2)}</div>
</div>
</div>
{/* 营业状态 */}
<div className="create-cell">
<div className="cell-label">营业状态:</div>
<div className="cell-value">
<Radio fields={[
{value: 1, text: '营业'},
// {value: 0, text: '临时休息'},
{value: -1, text: '长期休息'},
{value: -2, text: '禁用'}
]} id={'status'} current={this.state.status} fnClick={this.handleModify.bind(this, 'status')}></Radio>
</div>
</div>
{/* 临时休息 */}
<div className="create-cell auto-open">
<div className="cell-label">临时休息:</div>
{
this.state.autoEnableAt ? (
<div className="auto-open-text">
将在 {formatDate(this.state.autoEnableAt, 'YYYY-MM-DD')} 自动营业
</div>
) : (
<div className="auto-open-button" onClick={this.fnAutoOpen.bind(this)}>
选择自动营业时间
</div>
)
}
</div>
{/* 价格审核 */}
{/* <div className="create-cell">
<div className="cell-label">价格审核:</div>
<div className="cell-value">
<Radio fields={[
{value: 0, text: '关闭'},
{value: 1, text: '开启'}
]} id={'changePriceType'} current={this.state.changePriceType} fnClick={this.handleModify.bind(this, 'changePriceType')}></Radio>
</div>
</div> */}
{/* 禁用网络打印 */}
<div className="create-cell">
<div className="cell-label">禁用网络打印机:</div>
<div className="cell-value">
<Radio fields={[
{value: 0, text: '否'},
{value: 1, text: '是'}
]} id={'printerDisabled'} current={this.state.printerDisabled} fnClick={this.handleModify.bind(this, 'printerDisabled')}></Radio>
</div>
</div>
{/* 打印机类型 */}
<div className="create-cell printer-cell">
<div className="cell-label">打印机品牌:</div>
<div className="printer" onClick={this.fnPrinterVendorID.bind(this)}>{this.dealPrinterVendorID(this.state.printerVendorID)}</div>
{
this.showTestPrint && (
<div className="test-print" onClick={this.handleTestPrint.bind(this)}>测试打印</div>
)
}
</div>
{/* 网络打印机SN */}
{
(this.state.printerVendorID !== 0 && this.state.printerVendorInfoArr.length > 0) && (
<div className="create-cell">
<div className="cell-label">{this.state.printerInfo[1]}</div>
<Input ref="printerSN" fnBlur={this.handleModify.bind(this, 'printerSN')} value={this.state.printerSN} placeholder={'请填写' + this.state.printerInfo[1]}></Input>
</div>
)
}
{/* 网络打印机KEY */}
{
(this.state.printerVendorID !== 0 && this.state.printerInfo[2] !== '不填' && this.state.printerVendorInfoArr.length > 0) && (
<div className="create-cell">
<div className="cell-label">{this.state.printerInfo[2]}</div>
<Input ref="printerKey" fnBlur={this.handleModify.bind(this, 'printerKey')} value={this.state.printerKey} placeholder={'请填写' + this.state.printerInfo[2]}></Input>
</div>
)
}
<div className="header-title">地址及配送范围</div>
{/* 所在城市 */}
<div className="create-cell">
<div className="cell-label">所在城市:</div>
<div className="cell-city">
<div className="area" onClick={this.fnCityPick.bind(this)}>{cityName}</div>
<div className="area" onClick={this.fnDistrictPick.bind(this)}>{districtName}</div>
</div>
</div>
{/* 详细地址 */}
<div className="create-cell" onClick={this.showMap.bind(this)}>
<div className="cell-label">详细地址:</div>
<div className="cell-value">{this.state.address || '请填写详细地址'}</div>
<ReactSVG src={SVGGo} className="icon"></ReactSVG>
</div>
{/* 配送范围 */}
<div className="create-cell">
<div className="cell-label">配送范围:</div>
{
this.state.deliveryRangeType === 3 ? (
<div className="cell-value">
<Radio fields={[
{value: 3, text: '半径服务'}
]} id={'changePriceType'} current={this.state.deliveryRangeType} fnClick={this.handleModify.bind(this, 'deliveryRangeType')}></Radio>
<Input className="rangeInput" check={isNum} errText="半径不合法" fnBlur={this.handleModify.bind(this, 'deliveryRange')} value={this.state.deliveryRange} placeholder="请输入半径" type="number"></Input>
<div className="suffix"></div>
</div>
) : (
<div className="cell-value">
规划范围
{/* <Radio fields={[
{value: 2, text: '规划范围'}
]} id={'changePriceType'} current={this.state.deliveryRangeType} fnClick={this.handleModify.bind(this, 'deliveryRangeType')}></Radio> */}
</div>
)
}
</div>
{/* 配送类型 */}
<div className="create-cell">
<div className="cell-label">配送类型:</div>
<div className="cell-text">{this.props.system.cms.storeDeliveryType && this.props.system.cms.storeDeliveryType[this.state.deliveryType]}</div>
</div>
<div className="btn-group">
{/* <div className="delete-btn" onClick={this.fnDelete.bind(this)}>删除门店</div> */}
<div className="modify-btn" onClick={this.fnSubmit.bind(this)}>修改信息</div>
</div>
{/* <div className="padding-bottom-1rem"></div> */}
</div>
)
}
</div>
{/* 时间checkList */}
<CheckList ref="openTime1" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.openTime1} title="开始时间" fnPick={this.handleModify.bind(this, 'openTime1')}></CheckList>
<CheckList ref="closeTime1" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.closeTime1} title="结束时间" fnPick={this.handleModify.bind(this, 'closeTime1')}></CheckList>
<CheckList ref="openTime2" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.openTime2} title="开始时间" fnPick={this.handleModify.bind(this, 'openTime2')}></CheckList>
<CheckList ref="closeTime2" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.closeTime2} title="结束时间" fnPick={this.handleModify.bind(this, 'closeTime2')}></CheckList>
<CheckList ref="printerVendorID" datas={this.state.printerVendorInfoArr} map={{value: 'value', label: 'label'}} current={this.state.printerVendorID} title="网络打印机品牌" fnPick={this.handleModify.bind(this, 'printerVendorID')}></CheckList>
{/* 自动营业时间选择 */}
<CheckList ref="pickAutoOpen" datas={this.state.autoTime} map={{value: 'value', label: 'label'}} current={this.state.autoEnableAt} title="选择自动营业时间" fnPick={this.handleModify.bind(this, 'autoEnableAt')}></CheckList>
{/* 市、区选择 */}
<CheckList ref="cityCode" datas={this.props.system.cityLevel2} map={{value: 'code', label: 'name'}} current={this.state.cityCode} title="选择市" fnPick={this.handleModify.bind(this, 'cityCode')}></CheckList>
<CheckList ref="districtCode" datas={this.state.districtData} map={{value: 'code', label: 'name'}} current={this.state.districtCode} title="选择区" fnPick={this.handleModify.bind(this, 'districtCode')}></CheckList>
{/* 详细地址 */}
<SearchAddress ref="SearchAddress" region={cityData ? cityData.name : ''} fnPick={this.addressPick.bind(this)}></SearchAddress>
</div>
);
}
}
export default connect((state, props) => Object.assign({}, props, state), {})(CmpModifyBase);

View File

@@ -0,0 +1,324 @@
import React, { Component } from 'react';
import './cmp-modify.scss';
// import {isTel} from '@/utils/regExp';
// import classNames from 'classnames';
import ReactSVG from 'react-svg';
import fetchJson from '@/utils/fetch';
import BScroll from 'better-scroll';
// import SVGModify from '@/assets/svg/icon-modify.svg';
import SVGDelete from '@/assets/svg/icon-delete.svg';
// import SVGPublic from '@/assets/svg/icon-public.svg';
// import SVGMini from '@/assets/svg/icon-mini.svg';
import SVGNoGroup from '@/assets/svg/no-group.svg';
import Dialog from '@/components/Dialog';
import Loading from '@/components/Loading';
import Promopt from '@/components/Promopt';
// import Input from './Input';
import Toast from '@/components/Toast';
import SearchUser from '@/components/layout/SearchUser';
class CmpModifyGroup extends Component {
constructor (...args) {
super(...args);
this.state = {
tel: ''
};
this.scroll = null;
}
componentDidMount () {
this.scroll = new BScroll(this.refs.groupWrapper, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
// 打开对话框
// showPromopt (options) {
// let text = '';
// if (options.type === 'addGroup') text = '添加分组';
// if (options.type === 'addMembers') text = '添加组员';
// this.refs.promopt.show(text, async res => {
// if (res) {
// if (!isTel(this.state.tel)) {
// Toast.show('请输入正确的11位手机号', 2);
// } else {
// // 开始执行
// if (options.type === 'addGroup') {
// // 添加分组
// await this.reqBindGroup(this.state.tel, options.storeID);
// } else if (options.type === 'addMembers') {
// // 添加组员
// await this.reqaddMembers(options.parentMobile, this.state.tel);
// } else if (options.type === 'changeMobile') {
// // 修改号码
// await this.reqChangeMobile(options.curMobile, this.state.tel);
// }
// }
// }
// });
// }
// 手机号变化
// async handleModify (key, val) {
// // this.setState
// console.log(key, val);
// await this.setState({[key]: val.trim()});
// }
// 绑定分组
// async reqBindGroup (mobile, storeID) {
// try {
// Loading.show();
// let form = new FormData();
// form.append('mobile', mobile);
// form.append('storeID', storeID);
// await fetchJson('/v2/user/TmpBindMobile2Store', {
// method: 'PUT',
// body: form
// });
// Toast.show('添加分组成功');
// this.props.update && this.props.update();
// } catch (e) {
// Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
// } finally {
// Loading.hide();
// }
// }
// // 添加组员
// async reqaddMembers (parentMobile, mobile) {
// try {
// Loading.show();
// let form = new FormData();
// form.append('mobile', mobile);
// form.append('parentMobile', parentMobile);
// await fetchJson('/v2/user/TmpAddMobile2Mobile', {
// method: 'PUT',
// body: form
// });
// Toast.show('添加组员成功');
// this.props.update && this.props.update();
// } catch (e) {
// Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
// } finally {
// Loading.hide();
// }
// }
// // 修改号码
// async reqChangeMobile (curMobile, expectedMobile) {
// try {
// Loading.show();
// let form = new FormData();
// form.append('curMobile', curMobile);
// form.append('expectedMobile', expectedMobile);
// await fetchJson('/v2/user/TmpChangeMobile', {
// method: 'PUT',
// body: form
// });
// Toast.show('修改号码成功');
// this.props.update && this.props.update();
// } catch (e) {
// Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
// } finally {
// Loading.hide();
// }
// }
// 添加组员
addUser = () => {
this.refs.searchUser.show();
}
// 解绑操作
async fnUnBind (user) {
console.log(user);
Dialog.show('警告', `<span class="error">是否删除 ${user.name}</span>`, {}, async res => {
if (res) {
try {
console.log(this.props.storeID);
// let form = new FormData();
// form.append('mobile', tel);
// Loading.show();
// await fetchJson('/v2/user/TmpUnbindMobile', {
// method: 'PUT',
// body: form
// });
await fetchJson(`/v2/user2/DeleteUsers4Role?roleName=StoreBoss&storeID=${this.props.storeID}&userIDs=${JSON.stringify([user.userID])}`, {
method: 'DELETE'
});
Toast.show('删除成功');
this.props.update && this.props.update();
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
});
}
// 选择用户
handlePick = async (user) => {
console.log('选择的用户', user);
try {
Loading.show();
let form = new FormData();
form.append('roleName', 'StoreBoss');
form.append('storeID', this.props.storeID);
form.append('userIDs', JSON.stringify([user.userID]));
await fetchJson('/v2/user2/AddUsers4Role', {
method: 'POST',
body: form
})
Toast.show('添加成功');
this.props.update && this.props.update();
} catch (e) {
console.error(e);
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 添加分组
// bindGroup () {
// // console.log(this.props.storeID);
// this.showPromopt({
// type: 'addGroup',
// storeID: this.props.storeID
// });
// }
// 添加组员
// bindMembers (parentMobile) {
// this.showPromopt({
// type: 'addMembers',
// parentMobile
// });
// }
// 修改号码
// modifyTel (curTel) {
// console.log(curTel);
// this.showPromopt({
// type: 'changeMobile',
// curMobile: curTel
// });
// }
// 查询用户
async getOneUser (mobile) {
try {
let {totalCount, data} = await fetchJson(`/v2/user2/GetUsers?userType=0&keyword=${mobile}`)
return totalCount ? data[0] : null
} catch (e) {
throw e
}
}
// 注册
async handleRegister (mobile) {
try {
let res = fetchJson(`/v2/user2/RegisterUser`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `payload=${JSON.stringify({
userID2: mobile,
mobile,
name: mobile
})}`
})
return res
} catch (e) {
throw e
}
}
// 注册形式添加用户
registerUser = () => {
this.refs.promopt.show('请输入手机号', async res => {
if (res) {
const mobile = this.refs.telInput.value.trim()
if (/^\d{11}$/.test(mobile)) {
console.log(mobile)
try {
Loading.show()
let user = await this.getOneUser(mobile)
if (user) {
// 用户存在,直接绑定
console.log(user)
await this.handlePick(user)
} else {
// 用户不存在,注册
await this.handleRegister(mobile)
let newUser = await this.getOneUser(mobile)
await this.handlePick(newUser)
}
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide()
}
} else {
Toast.show('手机格式错误')
}
}
})
}
render() {
return (
<div className="cmp-modify-group">
{/* 添加分组按钮 */}
<div className="btn-group">
<div className="btn-addGroup" onClick={this.addUser}>添加用户</div>
<div className="btn-reg" onClick={this.registerUser}>注册用户</div>
</div>
{/* 分组显示 */}
<div className="card-wrapper" ref="groupWrapper">
<div>
{
this.props.users.length > 0 && this.props.users.map(user => (
<div className="card" key={user.id}>
<div className="card-group">
<div className="card-left">
{/* 昵称 */}
<div className="nick-name"><b>{user.name || '无昵称'}</b></div>
{/* 手机号 */}
<div className="tel"><b>tel:{user.mobile}</b></div>
{/* 绑定标识 */}
{/* <div className="bind-id">
<ReactSVG className={classNames('no-bind', {'is-bind': user.openID})} src={SVGPublic}></ReactSVG>
<ReactSVG className={classNames('no-bind', {'is-bind': user.openIDMini})} src={SVGMini}></ReactSVG>
</div> */}
</div>
<div className="card-right">
{/* 操作 */}
<div className="btn-delete" onClick={this.fnUnBind.bind(this, user)}>
<ReactSVG className="svg" src={SVGDelete}></ReactSVG>
</div>
{/* <div className="btn-editor" onClick={this.modifyTel.bind(this, user.tel)}>
<ReactSVG className="svg" src={SVGModify}></ReactSVG>
</div> */}
</div>
</div>
</div>
))
}
{/* 没有分组 */}
</div>
{
this.props.users.length === 0 && (
<div className="no-thing">
<ReactSVG src={SVGNoGroup} className="no-store"/>
<div className="text">该门店还没有分组</div>
</div>
)
}
</div>
<SearchUser ref="searchUser" fnPick={this.handlePick}></SearchUser>
<Promopt ref="promopt">
<input ref="telInput" className="tel-input" type="text"/>
</Promopt>
</div>
);
}
}
export default CmpModifyGroup;

View File

@@ -0,0 +1,90 @@
import React, { Component } from 'react';
import './cmp-modify.scss';
// import fetchJson from '@/utils/fetch';
// import Loading from '@/components/Loading';
// import Dialog from '@/components/Dialog';
import VendorCell from './VendorCell';
class CmpModifyMaps extends Component {
constructor (...args) {
super(...args);
this.state = {
// StoreMaps: '',
// CourierMaps: ''
};
}
async componentDidMount () {
await this.setState({...this.props.shop});
console.log(this.state);
// await this.getVendorMaps();
}
componentWillReceiveProps (newProps) {
console.log('new Props', newProps);
this.setState({...newProps.shop});
}
// 获取平台门店绑定情况
// async getVendorMaps () {
// try {
// Loading.show();
// let res = await fetchJson('/v2/store/GetStoreVendorMaps?storeID=' + this.props.shop.id);
// console.log('平台门店绑定情况', res);
// } catch (e) {
// Dialog.show('错误', e, {showCancel: false});
// } finally {
// Loading.hide();
// }
// }
render() {
return (
<div className="cmp-modify-maps">
{
this.state.id && (
<div>
<div className="header-title">平台门店绑定情况</div>
{/* 京东 */}
<VendorCell
shop={this.state}
plant={{name: 'StoreMaps', id: 0}}
update={this.props.update}
></VendorCell>
{/* 美团 */}
<VendorCell
shop={this.state}
plant={{name: 'StoreMaps', id: 1}}
update={this.props.update}
></VendorCell>
{/* 饿百 */}
<VendorCell
shop={this.state}
plant={{name: 'StoreMaps', id: 3}}
update={this.props.update}
></VendorCell>
{/* 微盟 */}
{/* <VendorCell
shop={this.state}
plant={{name: 'StoreMaps', id: 11}}
update={this.props.update}
></VendorCell> */}
<div className="header-title">专送门店绑定情况</div>
{/* 达达 */}
<VendorCell
shop={this.state}
plant={{name: 'CourierMaps', id: 101}}
update={this.props.update}
></VendorCell>
{/* 美团 */}
<VendorCell
shop={this.state}
plant={{name: 'CourierMaps', id: 102}}
update={this.props.update}
></VendorCell>
</div>
)
}
</div>
);
}
}
export default CmpModifyMaps;

View File

@@ -0,0 +1,530 @@
import React, { Component } from 'react';
import {connect} from 'react-redux';
import fetchJson from '@/utils/fetch';
import Loading from '@/components/Loading';
import './create-store.scss';
import {isNum, isEmpty, isTel, isTel2} from '@/utils/regExp';
import {dealOpenTime, businessHours} from '@/utils/projectTools';
import Dialog from '@/components/Dialog';
import CheckList from '@/components/layout/CheckList';
import {mapPlaces} from '@/utils/mapData';
import ReactSVG from 'react-svg'
import SVGGo from '@/assets/svg/icon-go.svg';
import classNames from 'classnames';
import BScroll from 'better-scroll';
import PopupPage from '@/components/layout/PopupPage';
import Input from './Input';
import Radio from '@/components/layout/Radio';
import SearchAddress from '@/components/layout/SearchAddress';
import PickInput from '@/components/layout/PickInput';
class CreateStore extends Component {
constructor (...args) {
super(...args);
this.state = {
id: 0,
name: '',
tel1: '',
tel2: '',
openTime1: 800,
closeTime1: 1900,
openTime2: 0,
closeTime2: 0,
status: -1,
changePriceType: 0,
cityCode: 510100,
districtCode: null,
address: '',
deliveryRangeType: 3,
deliveryRange: '3000',
lng: '',
lat: '',
brandID:9,//品牌默认无 9
payPercentage:20,//结算比例
// 区数据
districtData: [],
keyword: '',
// 获取平台数据
vendorID: `0,${this.props.system.vendorOrgCode[0][0]}`,
vendorStoreID: '',
printerVendorID: 0,
printerSN: '',
printerKey: '',
printerDisabled: 0,
printerInfo: [],
printerVendorInfoArr: []
}
this.scroll = null;
this.timeList = businessHours();
}
async componentDidMount () {
await this.getDistrict();
// 打印厂商
let {printerVendorInfo} = this.props.system.cms;
console.log('printerVendorInfo', printerVendorInfo);
// [
// {value: 0, label: '无'},
// {value: 200, label: '飞鹅'},
// {value: 201, label: '外卖管家'}
// ]
let printerVendorInfoArr = [
{value: 0, label: '无'}
];
for (let attr in printerVendorInfo) {
printerVendorInfoArr.push({
value: parseInt(attr, 10),
label: printerVendorInfo[attr][0]
});
}
this.setState({printerVendorInfoArr});
}
componentDidUpdate () {
if (this.scroll) {
this.scroll.refresh();
} else {
if (this.refs.createStoreWrapper) {
this.scroll = new BScroll(this.refs.createStoreWrapper, {
click: true,
stopPropagation: true,
bounce: {
top: false,
bottom: false
}
});
}
}
}
// 请求次级数据
async getDistrict () {
Loading.show();
try {
// 请求城市列表 level = 2
let res = await fetchJson('/v2/cms/GetPlaces?parentCode=' + this.state.cityCode);
console.log('区信息', mapPlaces(res));
await this.setState({districtData: mapPlaces(res)});
await this.setState({districtCode: this.state.districtData[0].code});
} catch (e) {} finally {
Loading.hide();
}
}
// async getBrandList () {
// Loading.show();
// try {
// // 请求城市列表 level = 2
// let res = await fetchJson('/v2/store/GetBrands');
// console.log('品牌', res);
// } catch (e) {} finally {
// Loading.hide();
// }
// }
// 修改属性
async handleModify (key, val) {
console.log(key, val);
console.log('this.state.printerInfo', this.state.printerInfo);
await this.setState({[key]: val});
if (key === 'printerVendorID') {
this.setState({
printerSN: '',
printerKey: ''
});
this.refs.printerSN && this.refs.printerSN.fnClear();
this.refs.printerKey && this.refs.printerKey.fnClear();
if (val === 0) {
this.setState({
printerInfo: [
{value: 0, label: '无'}
]
});
} else {
let {printerVendorInfo} = this.props.system.cms;
this.setState({
printerInfo: printerVendorInfo[val]
});
}
}
if (key === 'cityCode') {
await this.getDistrict();
}
if (key === 'openTime1' && val === 0) this.setState({closeTime1: 0});
if (key === 'openTime2' && val === 0) this.setState({closeTime2: 0});
if (key === 'closeTime1' && val === 0) this.setState({openTime1: 0});
if (key === 'closeTime2' && val === 0) this.setState({openTime2: 0});
}
// 点击开始时间
fnOpenTime (type) {
if (type === 1) {
this.refs.openTime1.show();
} else {
this.refs.openTime2.show();
}
}
fnBrandChoose(){
this.refs.openTime2.show();
}
// 点击结束时间
fnCloseTime (type) {
if (type === 1) {
this.refs.closeTime1.show();
} else {
this.refs.closeTime2.show();
}
}
// 点击打印机类型
fnPrinterVendorID () {
this.refs.printerVendorID.show();
}
// 处理打印机类型
dealPrinterVendorID (type) {
// console.log(type);
if (type === 0) {
return '无';
} else {
let {printerVendorInfo} = this.props.system.cms;
return printerVendorInfo[type][0];
}
}
// 点击市选择
async fnCityPick () {
this.refs.cityCode.show();
}
// 点击区选择
fnDistrictPick () {
this.refs.districtCode.show();
}
// 接入地图
showMap () {
this.refs.SearchAddress.show();
}
// 选取地址
addressPick (address) {
console.log(address);
this.setState({address: address.address || address.title, lat: address.location.lat, lng: address.location.lng});
}
// 点击半径
rClick () {
// console.log('点击半径', this.refs.PopupPage.toBottom())
}
// 校验
fnCheckData () {
let arr = [];
if (!isNum(this.state.id)) arr.push('京西门店ID必须是纯数字');
if (!isEmpty(this.state.name)) arr.push('店铺名称不能为空');
if (!isTel(this.state.tel1)) arr.push('店长手机不合法');
if (!this.state.address) arr.push('请输入门店详细地址');
if (this.state.deliveryRangeType === 3 && !isNum(this.state.deliveryRange)) arr.push('配送半径不合法');
if (this.state.printerVendorID !== 0 && !isEmpty(this.state.printerSN)) arr.push('请填写' + this.state.printerInfo[1])
if ((this.state.printerVendorID !== 0 && this.state.printerVendorID !== 202) && !isEmpty(this.state.printerKey)) arr.push('请填写' + this.state.printerInfo[2])
if (arr.length > 0) {
Dialog.show('错误', `<span class="error">${arr.join('<br />')}</span>`, {
showCancel: false,
confirmText: '好的'
})
return false;
} else {
return true;
}
}
// 确认前
async prevConfirm () {
console.log(this.state);
if (this.fnCheckData()) {
// 开始创建门店
if (Number(this.state.id) !== 0) {
Dialog.show('注意', `<span class="warning">京西门店ID正常都应该自动生成京西门店ID为0时您确认必须要设置成${this.state.id}</span>`, {}, async (res) => {
if (res) {
let res = await this.apiCreateStore();
return res;
}
})
} else {
let res = await this.apiCreateStore();
return res;
}
} else {}
}
// 创建门店接口
async apiCreateStore () {
try {
Loading.show();
let form = new FormData();
let json = JSON.parse(JSON.stringify(this.state));
json.id = Number(json.id);
delete json.districtData;
delete json.keyword;
form.append('payload', JSON.stringify(json));
let res = await fetchJson('/v2/store/CreateStore', {
method: 'POST',
body: form
});
console.log(res);
Dialog.show('成功', `${json.name} 创建成功`, {showCancel: false, confirmText: '知道了'});
return true;
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
return false;
} finally {
Loading.hide();
}
}
// vendorID 修改
handleVendorID = val => {
console.log(val)
this.setState({vendorID: val});
}
// vendorStoreID 修改
handleVendorStoreID = val => {
this.setState({vendorStoreID: val});
}
// 获取远端门店数据
handleGetVendorStore = async () => {
console.log(this.state.vendorID, this.state.vendorStoreID);
let store = await this.getVendorStore();
console.log(store);
if (!store) return false;
await this.setState({
// id: store.id,
name: store.name,
tel1: store.tel1,
tel2: store.tel2,
openTime1: store.openTime1,
closeTime1: store.closeTime1,
status: store.status,
changePriceType: store.changePriceType,
cityCode: store.cityCode,
districtCode: store.districtCode,
address: store.address,
// deliveryRangeType: 3,
// deliveryRange: store.deliveryRangeType === 3,
lng: store.lng,
lat: store.lat,
});
// 单独处理配送范围
if (store.deliveryRangeType === 3) this.setState({deliveryRange: store.deliveryRange});
console.log(this.state)
this.refs.name.checkData();
this.refs.deliveryRange.checkData();
this.refs.tel1.checkData();
this.refs.tel2.checkData();
}
// 获取远端门店信息
async getVendorStore () {
try {
Loading.show();
let res = await fetchJson(`/v2/store/GetVendorStore?vendorStoreID=${this.state.vendorStoreID}&vendorID=${this.state.vendorID.split(',')[0]}&vendorOrgCode=${this.state.vendorID.split(',')[1]}`);
// console.log(res);
// 重新拉取区信息
let district = await fetchJson('/v2/cms/GetPlaces?parentCode=' + res.cityCode);
console.log('区信息', mapPlaces(district));
await this.setState({districtData: mapPlaces(district)});
// await this.setState({districtCode: this.state.districtData[0].code});
return res;
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {
showCancel: false,
confirmText: '好的'
});
} finally {
Loading.hide();
}
}
dealVendorOrgCode = () => {
let vendorName = this.props.system.cms.vendorName || {};
const {vendorOrgCode} = this.props.system
console.log(vendorOrgCode)
let arr = []
for (let key in vendorOrgCode) {
(vendorOrgCode[key] || []).forEach(item => {
arr.push({
id: `${key},${item}`,
name: vendorName[key].substring(0, 1) + item
})
})
}
return arr
// [
// {id: 0, name: vendorName['0']},
// {id: 1, name: vendorName['1']},
// {id: 3, name: vendorName['3']}
// ]
}
render () {
let cityData = this.props.system.cityLevel2.find(item => item.code === this.state.cityCode);
// 城市名称
let cityName = cityData ? cityData.name : '未知'
let districtData = this.state.districtData.find(item => item.code === this.state.districtCode);
// 区名称
let districtName = districtData ? districtData.name : '未知';
return (
<div className={classNames("create-store")}>
<PopupPage
className="create-store"
ref="PopupPage"
title="创建门店"
fnReturn={this.props.fnReturn}
fnPrevConfirm={this.prevConfirm.bind(this)}
fnConfirm={this.props.fnConfirm}
>
<div className="create-store-wrapper" ref="createStoreWrapper">
<div>
{/* 从平台拉取数据 */}
<div className="header-title">从平台获取数据</div>
<div className="create-cell">
<PickInput
title="选择平台"
placeholder="请输入平台门店ID"
datas={this.dealVendorOrgCode()}
map={{value: 'id', label: 'name'}}
current={this.state.vendorID}
fnPick={this.handleVendorID}
fnChange={this.handleVendorStoreID}
fnConfirm={this.handleGetVendorStore}
type="number"
></PickInput>
</div>
<div className="header-title">基本信息</div>
{/* 京西门店ID */}
<div className="create-cell has-suffix">
<div className="cell-label">京西门店ID:</div>
<Input check={isNum} errText="京西门店ID必须是纯数字" fnBlur={this.handleModify.bind(this, 'id')} value={this.state.id} placeholder="0 自动生成" type="number"></Input>
<div className="suffix2">0表示自动生成缺省ID, 特殊情况可手动设置</div>
</div>
{/* 店铺名称 */}
<div className="create-cell">
<div className="cell-label">店铺名称:</div>
<Input ref="name" check={isEmpty} errText="店铺名称不能为空" fnBlur={this.handleModify.bind(this, 'name')} value={this.state.name} placeholder="请输入店铺名称"></Input>
</div>
{/* 店长手机 */}
<div className="create-cell">
<div className="cell-label">店长手机:</div>
<Input ref="tel1" check={isTel} errText="店长手机不合法" fnBlur={this.handleModify.bind(this, 'tel1')} value={this.state.tel1} placeholder="请输入店长手机号" type="number"></Input>
</div>
{/* tel2 */}
<div className="create-cell">
<div className="cell-label">tel2:</div>
<Input ref="tel2" check={isTel2} errText="tel2手机不合法" fnBlur={this.handleModify.bind(this, 'tel2')} value={this.state.tel2} placeholder="请输入tel2(选填)" type="number"></Input>
</div>
{/* 营业时间 */}
<div className="create-cell">
<div className="cell-label">营业时间段1:</div>
<div className="cell-time1">
<div className="time" onClick={this.fnOpenTime.bind(this, 1)}>{dealOpenTime(this.state.openTime1)}</div>
<span></span>
<div className="time" onClick={this.fnCloseTime.bind(this, 1)}>{dealOpenTime(this.state.closeTime1)}</div>
</div>
</div>
<div className="create-cell">
<div className="cell-label">营业时间段2:</div>
<div className="cell-time1">
<div className="time" onClick={this.fnOpenTime.bind(this, 2)}>{dealOpenTime(this.state.openTime2)}</div>
<span></span>
<div className="time" onClick={this.fnCloseTime.bind(this, 2)}>{dealOpenTime(this.state.closeTime2)}</div>
</div>
</div>
{/* 营业状态 */}
<div className="create-cell">
<div className="cell-label">营业状态:</div>
<div className="cell-value">
<Radio fields={[
{value: 1, text: '营业'},
{value: -1, text: '长期休息'},
{value: -2, text: '禁用'}
]} id={'status'} current={this.state.status} fnClick={this.handleModify.bind(this, 'status')}></Radio>
</div>
</div>
{/* 价格审核 */}
{/* <div className="create-cell">
<div className="cell-label">价格审核:</div>
<div className="cell-value">
<Radio fields={[
{value: 0, text: '关闭'},
{value: 1, text: '开启'}
]} id={'changePriceType'} current={this.state.changePriceType} fnClick={this.handleModify.bind(this, 'changePriceType')}></Radio>
</div>
</div> */}
{/* 禁用网络打印 */}
<div className="create-cell">
<div className="cell-label">禁用网络打印机:</div>
<div className="cell-value">
<Radio fields={[
{value: 0, text: '否'},
{value: 1, text: '是'}
]} id={'printerDisabled'} current={this.state.printerDisabled} fnClick={this.handleModify.bind(this, 'printerDisabled')}></Radio>
</div>
</div>
{/* 打印机类型 */}
<div className="create-cell printer-cell">
<div className="cell-label">打印机品牌:</div>
<div className="printer" onClick={this.fnPrinterVendorID.bind(this)}>{this.dealPrinterVendorID(this.state.printerVendorID)}</div>
<div></div>
</div>
{/* 网络打印机SN */}
{
this.state.printerVendorID !== 0 && (
<div className="create-cell">
<div className="cell-label">{this.state.printerInfo[1]}</div>
<Input ref="printerSN" fnBlur={this.handleModify.bind(this, 'printerSN')} value={this.state.printerSN} placeholder={'请填写' + this.state.printerInfo[1]}></Input>
</div>
)
}
{/* 网络打印机KEY */}
{
(this.state.printerVendorID !== 0 && this.state.printerInfo[2] !== '不填') && (
<div className="create-cell">
<div className="cell-label">{this.state.printerInfo[2]}</div>
<Input ref="printerKey" fnBlur={this.handleModify.bind(this, 'printerKey')} value={this.state.printerKey} placeholder={'请填写' + this.state.printerInfo[2]}></Input>
</div>
)
}
<div className="header-title">地址及配送范围</div>
{/* 所在城市 */}
<div className="create-cell">
<div className="cell-label">所在城市:</div>
<div className="cell-city">
<div className="area" onClick={this.fnCityPick.bind(this)}>{cityName}</div>
<div className="area" onClick={this.fnDistrictPick.bind(this)}>{districtName}</div>
</div>
</div>
{/* 详细地址 */}
<div className="create-cell" onClick={this.showMap.bind(this)}>
<div className="cell-label">详细地址:</div>
<div className="cell-value">{this.state.address || '请填写详细地址'}</div>
<ReactSVG src={SVGGo} className="icon"></ReactSVG>
</div>
{/* 配送范围 */}
<div className="create-cell">
<div className="cell-label">配送范围:</div>
<div className="cell-value">
<Radio fields={[
// {value: 2, text: '规划范围'},
{value: 3, text: '半径服务'}
]} id={'changePriceType'} current={this.state.deliveryRangeType} fnClick={this.handleModify.bind(this, 'deliveryRangeType')}></Radio>
<Input ref="deliveryRange" className="rangeInput" check={isNum} errText="半径不合法" fnBlur={this.handleModify.bind(this, 'deliveryRange')} value={this.state.deliveryRange} placeholder="请输入半径" type="number"></Input>
<div className="suffix"></div>
</div>
</div>
<div className="padding-bottom-1rem"></div>
</div>
</div>
{/* 时间checkList */}
<CheckList ref="openTime1" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.openTime1} title="开始时间" fnPick={this.handleModify.bind(this, 'openTime1')}></CheckList>
<CheckList ref="closeTime1" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.closeTime1} title="结束时间" fnPick={this.handleModify.bind(this, 'closeTime1')}></CheckList>
<CheckList ref="openTime2" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.openTime2} title="开始时间" fnPick={this.handleModify.bind(this, 'openTime2')}></CheckList>
<CheckList ref="closeTime2" datas={this.timeList} map={{value: 'value', label: 'label'}} current={this.state.closeTime2} title="结束时间" fnPick={this.handleModify.bind(this, 'closeTime2')}></CheckList>
<CheckList ref="printerVendorID" datas={this.state.printerVendorInfoArr} map={{value: 'value', label: 'label'}} current={this.state.printerVendorID} title="网络打印机品牌" fnPick={this.handleModify.bind(this, 'printerVendorID')}></CheckList>
{/* 市、区选择 */}
<CheckList ref="cityCode" datas={this.props.system.cityLevel2} map={{value: 'code', label: 'name'}} current={this.state.cityCode} title="选择市" fnPick={this.handleModify.bind(this, 'cityCode')}></CheckList>
<CheckList ref="districtCode" datas={this.state.districtData} map={{value: 'code', label: 'name'}} current={this.state.districtCode} title="选择区" fnPick={this.handleModify.bind(this, 'districtCode')}></CheckList>
{/* 详细地址 */}
<SearchAddress ref="SearchAddress" region={cityData ? cityData.name : ''} fnPick={this.addressPick.bind(this)}></SearchAddress>
</PopupPage>
</div>
);
}
}
export default connect((state, props) => Object.assign({}, props, state), {})(CreateStore);

View File

@@ -0,0 +1,94 @@
import React, { Component } from 'react';
import classNames from 'classnames';
// import Toast from '@/components/Toast';
/*
check={isNum} 校验函数
value={this.state.id} 默认值
placeholder=""
errText="京西门店ID必须是纯数字" 报错提示
fnBlur={this.idBlur.bind(this, key)} 失去焦点处理函数 可以为空
*/
class Input extends Component {
constructor (...args) {
super(...args);
this.state = {
wrongShow: false
};
}
componentDidMount () {
// console.log('校······验', this.props.value)
// 校验一次defaultValue
this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)});
}
// 当props或者state变化时才更新
// shouldComponentUpdate (nextProps, nextState) {
// // console.log(JSON.stringify(nextProps) !== JSON.stringify(this.props) || JSON.stringify(nextState) !== JSON.stringify(this.state))
// return JSON.stringify(nextProps) !== JSON.stringify(this.props) || JSON.stringify(nextState) !== JSON.stringify(this.state);
// }
// 更新触发校验
// componentDidUpdate () {
// this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)});
// }
// id变化
storeIDChange (e) {
// console.log(e.target.value);
this.props.check && this.setState({wrongShow: !this.props.check(e.target.value.trim())});
if (this.props.check) {
this.setState({wrongShow: !this.props.check(e.target.value.trim())});
if (this.props.check(e.target.value.trim())) {
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
} else {
this.setState({wrongShow: true});
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
}
}
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
}
// id失去焦点
storeIDBlue (e) {
// console.log(e.target.value)
// !isNum(e.target.value) ? Toast.show('京西门店ID需要纯数字') : this.
if (this.props.check) {
if (this.props.check(e.target.value.trim())) {
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
} else {
this.setState({wrongShow: true});
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
// Toast.show(this.props.errText);
}
} else {
this.props.fnBlur && this.props.fnBlur(e.target.value.trim());
}
}
// 校验
checkData () {
// console.log('校验');
this.refs.input.value = this.props.value;
this.props.check && this.setState({wrongShow: !this.props.check(this.props.value)});
}
// 清除
fnClear () {
this.refs.input.value = '';
}
render() {
return (
<input
ref="input"
type={this.props.type || 'text'}
className={classNames('cell-input', {'danger': this.state.wrongShow}, this.props.className)}
placeholder={this.props.placeholder}
defaultValue={this.props.value}
onChange={this.storeIDChange.bind(this)}
onBlur={this.storeIDBlue.bind(this)}
onClick={this.props.onClick}
/>
);
}
}
export default Input;

View File

@@ -0,0 +1,141 @@
import React, { Component } from 'react';
import './modify-store.scss';
import fetchJson from '@/utils/fetch';
import SVGStore from '@/assets/svg/icon-store.svg';
import PopupPage from '@/components/layout/PopupPage';
import Dialog from '@/components/Dialog';
import Loading from '@/components/Loading';
import Tabs from '@/components/layout/Tabs';
// import BScroll from 'better-scroll';
import CmpModifyBase from '@/components/pageCmp/CmpModifyBase'
import CmpModifyMaps from '@/components/pageCmp/CmpModifyMaps'
import CmpModifyGroup from '@/components/pageCmp/CmpModifyGroup'
class ModifyStore extends Component {
constructor (...args) {
super(...args);
this.state = {
shop: {},
users: [],
current: 0
};
// this.scroll = null;
}
async componentDidMount () {
await this.getStore();
await this.getStoreUsers();
// 增加滚动
// this.scroll = new BScroll(this.refs.modifyWrapper, {
// // click: true,
// bounce: {
// top: false,
// bottom: false
// }
// });
}
// 获取门店数据
async getStore () {
try {
Loading.show();
let {stores} = await fetchJson('/v2/store/GetStores?storeID=' + this.props.shop.id);
console.log('当前门店', stores[0]);
await this.setState({shop: stores[0]});
} catch (e) {
Dialog.show('错误', `<span calss="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 获取分组数据
async getStoreUsers () {
try {
Loading.show();
let res = await fetchJson('/v2/user2/GetRoleUserList?roleName=StoreBoss&storeID=' + this.props.shop.id);
if (res) {
let users = await this.apiGetUserList(res);
this.setState({users});
} else {
this.setState({users: []});
}
} catch (e) {
Dialog.show('错误', `<span calss="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 获取所有用户列表
apiGetUserList = async (userIDs = [], pageSize = 50) => {
try {
let {data} = await fetchJson(`/v2/user2/GetUsers?userIDs=${JSON.stringify(userIDs)}&userType=14&offset=0&pageSize=50`)
// console.log('获取的用户', totalCount, data)
console.log('用户信息', data);
return data || []
} catch (e) {
console.error(e)
Dialog.show('错误', `<span calss="error">${e}</span>`, {showCancel: false});
}
}
// tabs切换
async fnSwitch (index) {
// console.log(index);
this.setState({current: index});
if (index !== 2) {
await this.getStore();
} else {
await this.getStoreUsers();
}
}
componentDidUpdate () {
console.log('updated')
// this.scroll = new BScroll(this.refs.modifyWrapper, {
// // click: true,
// // stopPropagation: true,
// bounce: {
// top: false,
// bottom: false
// }
// });
// this.scroll && this.scroll.refresh();
}
render() {
return (
<div className="modify-store">
<PopupPage
title={this.props.shop.name}
fnReturn={this.props.fnReturn}
showConfirm={false}
SVG={SVGStore}
>
<Tabs
current={this.state.current}
fields={[
{index: 0, text: '基础信息'},
{index: 1, text: '平台绑定'},
{index: 2, text: '分组管理'}
]}
fnClick={this.fnSwitch.bind(this)}
></Tabs>
{
this.state.current === 0 && this.state.shop.id && <div className="modify-body">
<CmpModifyBase shop={this.state.shop}></CmpModifyBase>
</div>
}
{
this.state.current === 1 && this.state.shop.id && <div className="modify-body">
<CmpModifyMaps shop={this.state.shop} update={this.getStore.bind(this)}></CmpModifyMaps>
</div>
}
{
this.state.current === 2 && this.state.shop.id && <div className="modify-body">
<CmpModifyGroup storeID={this.state.shop.id} users={this.state.users} update={this.getStoreUsers.bind(this)}></CmpModifyGroup>
</div>
}
</PopupPage>
</div>
);
}
}
export default ModifyStore;

View File

@@ -0,0 +1,206 @@
import React, { Component } from 'react';
import PopupPage from '@/components/layout/PopupPage';
import {connect} from 'react-redux';
import {setStoreSearch} from '@/store/actions';
import './more-search.scss';
import ReactSVG from 'react-svg'
import SVGGo from '@/assets/svg/icon-go.svg';
import Radio from '@/components/layout/Radio';
import CheckBox from '@/components/layout/CheckBox';
import CheckList from '@/components/layout/CheckList';
class MoreSearch extends Component {
constructor (...args) {
super(...args);
this.state = {};
this.vendorList = [
{id: 0, name: '京东'},
{id: 1, name: '美团'},
{id: 3, name: '饿百'},
{id: 9, name: '京西'},
// {id: 11, name: '微盟'}
];
this.courierList = [
{id: 101, name: '达达'},
{id: 102, name: '美团'}
];
this.fields = [
{value: 0, text: '不限定'},
{value: 1, text: '已绑定'},
{value: -1, text: '未绑定'}
];
this.fieldsStore = [
{value: '0', text: '不限定'},
{value: '1', text: '已绑定'},
{value: '-1', text: '未绑定'}
];
}
async componentDidMount () {
await this.setState({
...this.props.search.storeSearch
});
// console.log(this.props.system.vendorOrgCode)
}
// 平台门店绑定情况切换
vendorStoreCondsClick (val, id) {
console.log(val, id);
this.setState({
vendorStoreConds: {
...this.state.vendorStoreConds,
[id]: val
}
});
}
// 平台门店绑定情况切换
courierStoreCondsClick (val, id) {
// console.log(val, id);
this.setState({
courierStoreConds: {
...this.state.courierStoreConds,
[id]: val
}
});
}
// 点击确定
async fnConfirm () {
console.log(this.state);
await this.props.setStoreSearch(this.state)
this.props.fnConfirm && this.props.fnConfirm();
}
// 营业状态修改
changeStatuss (val) {
// console.log(val);
let arr = Object.assign([], this.state.statuss);
// 查找元素
let n = arr.indexOf(val);
if (n === -1) {
// 没找到,添加
arr.push(val);
} else {
// 找到了,去除
if (arr.length !== 1) arr.splice(n, 1);
}
this.setState({
statuss: arr
});
}
// 城市改变
changeCity () {
console.log('城市改变');
this.refs.checklist.show();
}
// 选择城市
cityPick (val) {
console.log(val);
this.setState({placeID: Number(val)});
}
dealVendorOrgCode = (list, vendor) => {
let arr = JSON.parse(JSON.stringify(list))
const {vendorOrgCode} = this.props.system
// console.log(arr, vendor, vendorOrgCode, )
let account = vendorOrgCode[vendor.id]
if (account && account.length > 1) {
account.forEach(item => {
arr.push({
value: item,
text: item
})
})
}
return arr
}
// 渲染
render() {
let storeSearch = this.state;
let placeName = storeSearch.placeID ? this.props.system.cityLevel2.find(item => item.code === storeSearch.placeID).name : '全国';
return (
<div className="more-search">
{
// 存在才渲染
storeSearch.vendorStoreConds && (
<PopupPage
className="more-search"
title="更多检索条件"
fnReturn={this.props.fnReturn.bind(this)}
fnConfirm={this.fnConfirm.bind(this)}
>
{/* 城市 */}
<div className="search-cell" onClick={this.changeCity.bind(this)}>
<div className="cell-label">城市:</div>
<div className="cell-value">{placeName}</div>
<ReactSVG src={SVGGo} className="icon"></ReactSVG>
</div>
{/* 营业状态 */}
<div className="search-cell">
<div className="cell-label">状态:</div>
<div className="cell-value">
<CheckBox
current={storeSearch.statuss}
fields={[
{value: 1, text: '营业'},
{value: 0, text: '临时休息'},
{value: -1, text: '长期休息'},
{value: -2, text: '禁用'}
]}
fnClick={this.changeStatuss.bind(this)}
></CheckBox>
</div>
</div>
{/* 平台门店绑定情况 */}
<div className="header-title">平台门店绑定情况</div>
{/* cell */}
{
this.vendorList.map((item, index) => (
<div className="search-cell" key={index}>
<div className="cell-label">{item.name}:</div>
<div className="cell-value">
<Radio
fields={this.dealVendorOrgCode(this.fieldsStore, item)}
current={storeSearch.vendorStoreConds[item.id]}
id={item.id}
fnClick={this.vendorStoreCondsClick.bind(this)}
></Radio>
</div>
</div>
))
}
{/* cell */}
{/* 专送门店绑定情况 */}
<div className="header-title">专送门店绑定情况</div>
{
this.courierList.map((item, index) => (
<div className="search-cell" key={index}>
<div className="cell-label">{item.name}:</div>
<div className="cell-value">
<Radio
fields={this.fields}
current={storeSearch.courierStoreConds[item.id]}
id={item.id}
fnClick={this.courierStoreCondsClick.bind(this)}
></Radio>
</div>
</div>
))
}
</PopupPage>
)
}
{/* checkList */}
<CheckList
ref="checklist"
datas={this.props.system.cityLevel2}
map={{value: 'code', label: 'name'}}
nullShow={true}
nullLabel='全国'
current={storeSearch.placeID}
title="城市选择"
fnPick={this.cityPick.bind(this)}
></CheckList>
</div>
);
}
}
export default connect((state, props) => Object.assign({}, props, state), {setStoreSearch})(MoreSearch);

View File

@@ -0,0 +1,39 @@
import React, { Component } from 'react';
import classNames from 'classnames'
import './store-cell.scss';
import {connect} from 'react-redux';
class StoreCell extends Component {
constructor (...args) {
super(...args);
this.storeStatus = {
'-1': '长期休息',
'0': '临时休息',
'1': '营业',
'-2': '禁用'
}
}
render() {
let store = this.props.shop;
// 门店所在城市
let cityData = this.props.system.cityLevel2.find(item => item.code === store.cityCode);
let cityName = cityData ? cityData.name : '未知'
return (
<div className="store-item" onClick={this.props.onClick}>
<div className="store-cell">
{/* <div className="store-id">{store.id}</div> */}
<div className="store-name">
{store.name}
<span className="city">({cityName})
</span>
</div>
{/* 营业状态 */}
<div className={classNames('store-status', {'status-jy': store.status < 0}, {'status-xx': store.status === 0}, {'status-yy': store.status === 1})}>{this.storeStatus[store.status]}</div>
</div>
</div>
);
}
}
export default connect((state, props) => Object.assign({}, props, state), {})(StoreCell);

View File

@@ -0,0 +1,462 @@
import React, {Component} from 'react';
import classNames from 'classnames';
import {connect} from 'react-redux';
import ReactSVG from 'react-svg';
import {isNum} from '@/utils/regExp';
import Loading from '@/components/Loading';
import fetchJson from '@/utils/fetch';
import SVGInsert from '@/assets/svg/icon-insert.svg';
import SVGDelete from '@/assets/svg/icon-delete.svg';
import SVGUpdate from '@/assets/svg/icon-update.svg';
import PopupPage from '@/components/layout/PopupPage';
import CheckBoxSelf from '@/components/layout/CheckBoxSelf';
import Radio from '@/components/layout/Radio';
import Input from '@/components/pageCmp/Input';
import Dialog from '@/components/Dialog';
import CheckList from '@/components/layout/CheckList';
/*
shop={this.state}
plant={{name: 'StoreMaps', id: 1}}
*/
class VendorCell extends Component {
constructor (...args) {
super(...args);
let vendorOrgCodeArr = this.props.system.vendorOrgCode[this.props.plant.id]
this.reset = {
vendorStoreID: '',
status: 1, // 开关店状态
vendorID: this.props.plant.id,
autoPickup: 1, // 是否自动拣货
deliveryCompetition: 1, // 是否配送竞争
pricePercentage: 100,
isSync: 1, // 是否同步
popupShow: false,
isEditor: false,
vendorOrgCode: this.props.plant.id < 100 ? vendorOrgCodeArr[0] : ''
};
this.state = {
vendorStoreID: '',
status: 1, // 开关店状态
vendorID: this.props.plant.id,
autoPickup: 1, // 是否自动拣货
deliveryCompetition: 1, // 是否配送竞争
pricePercentage: 100,
isSync: 1, // 是否同步
popupShow: false,
isEditor: false,
vendorOrgCode: this.props.plant.id < 100 ? vendorOrgCodeArr[0] : ''
};
this.vendorOrgCodeList = []
if (vendorOrgCodeArr) {
vendorOrgCodeArr.forEach(item => {
this.vendorOrgCodeList.push({
code: item,
name: item
})
})
}
}
componentDidMount () {
console.log('vendorOrgCode', this.props.system.vendorOrgCode, this.props.plant.id)
}
// 远程查询平台门店信息
async searchVendorStore () {
try {
Loading.show();
let res = await fetchJson(`/v2/store/GetVendorStore?vendorStoreID=${this.state.vendorStoreID}&vendorID=${this.state.vendorID}&vendorOrgCode=${this.state.vendorOrgCode}`);
console.log(res);
Dialog.show('平台门店查询成功', `<p class="success">${res.cityName}${res.districtName}${res.name}</p><p class="address">${res.address}</p>`, {showCancel: false});
} catch (e) {
// console.error(e);
Dialog.show('查询错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 获取平台门店绑定情况
async getVendorMaps () {
try {
Loading.show();
let res = await fetchJson('/v2/store/GetStoreVendorMaps?storeID=' + this.props.shop.id + '&vendorID=' + this.state.vendorID);
await this.setState({
...this.state,
...res[0]
});
console.log('平台门店绑定情况', res[0]);
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 获取专送门店绑定情况
async getCourierMaps () {
try {
Loading.show();
let res = await fetchJson('/v2/store/GetStoreCourierMaps?storeID=' + this.props.shop.id + '&vendorID=' + this.state.vendorID);
await this.setState({
...this.state,
...res[0]
});
console.log('平台门店绑定情况', res[0]);
} catch (e) {
Dialog.show('错误', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
// 绑定
fnBind () {
console.log('绑定');
this.setState({popupShow: true});
}
// 删除绑定
async fnDelete () {
console.log('删除');
Dialog.show('警告', `是否解除${this.props.shop.name}-${this.props.system.cms.vendorName[this.props.plant.id]}的绑定`, {}, async (res) => {
if (res) {
try {
Loading.show();
if (this.state.vendorID < 100) {
await fetchJson(`/v2/store/DeleteStoreVendorMap?storeID=${this.props.shop.id}&vendorID=${this.state.vendorID}`, {method: 'DELETE'});
} else {
await fetchJson(`/v2/store/DeleteStoreCourierMap?storeID=${this.props.shop.id}&vendorID=${this.state.vendorID}`, {method: 'DELETE'});
}
Dialog.show('成功', `<span class="success">解绑成功</span>`, {showCancel: false});
// 更新数据
this.props.update && this.props.update();
} catch (e) {
Dialog.show('解绑失败', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
})
}
// 点击编辑
async fnUpdate () {
console.log('编辑');
if (this.state.vendorID < 100) {
// 平台门店绑定
await this.getVendorMaps();
} else {
// 专送门店绑定
await this.getCourierMaps();
}
console.log(this.state);
this.setState({popupShow: true, isEditor: true});
}
// 弹出框返回
fnReturn () {
// this.setState({popupShow: false, isEditor: false});
console.log('重置数据');
this.setState({...this.reset});
}
// 弹出框验证 新增、编辑
async fnPrevConfirm () {
console.log(this.state);
// 存放错误消息
let arr = [];
if (!isNum(this.state.vendorStoreID)) arr.push('平台门店ID必须是纯数字');
if (!isNum(this.state.pricePercentage)) arr.push('平台调价必须是纯数字');
if (arr.length > 0) {
Dialog.show('错误', `<span class="error">${arr.join('<br />')}</span>`, {
showCancel: false,
confirmText: '好的'
})
return false;
} else {
// 请求接口绑定门店
console.log('开始保存');
try {
Loading.show();
let form = new FormData();
form.append('storeID', this.props.shop.id);
form.append('vendorID', this.state.vendorID);
if (this.state.isEditor) {
// ----- 编辑 -----
if (this.state.vendorID < 100) {
form.append('vendorOrgCode', this.state.vendorOrgCode)
// 平台编辑
let json = {
autoPickup: this.state.autoPickup,
deliveryCompetition: this.state.deliveryCompetition,
isSync: this.state.isSync,
pricePercentage: this.state.pricePercentage,
status: this.state.status,
syncStatus: this.state.syncStatus,
vendorStoreID: this.state.vendorStoreID,
vendorOrgCode: this.state.vendorOrgCode
}
form.append('payload', JSON.stringify(json));
await fetchJson('/v2/store/UpdateStoreVendorMap', {
method: 'PUT',
body: form
});
} else {
// 专送编辑
let json = {
vendorStoreID: this.state.vendorStoreID,
status: this.state.status
}
form.append('payload', JSON.stringify(json));
await fetchJson('/v2/store/UpdateStoreCourierMap', {
method: 'PUT',
body: form
});
}
// console.log(res);
Dialog.show('成功', `<span class="success">修改成功</span>`, {showCancel: false});
return true;
} else {
// ----- 新增 -----
if (this.state.vendorID < 100) {
form.append('vendorOrgCode', this.state.vendorOrgCode)
// 新增平台
let json = {
vendorStoreID: this.state.vendorStoreID,
status: this.state.status,
autoPickup: this.state.Pickup,
deliveryCompetition: this.state.deliveryCompetition,
pricePercentage: Number(this.state.pricePercentage),
isSync: this.state.isSync,
vendorOrgCode: this.state.vendorOrgCode
}
form.append('payload', JSON.stringify(json));
await fetchJson('/v2/store/AddStoreVendorMap', {
method: 'POST',
body: form
});
} else {
// 新增专送
let json = {
vendorStoreID: this.state.vendorStoreID,
status: this.state.status
}
form.append('payload', JSON.stringify(json));
await fetchJson('/v2/store/AddStoreCourierMap', {
method: 'POST',
body: form
});
}
Dialog.show('成功', `<span class="success">绑定成功</span>`, {showCancel: false});
return true;
}
} catch (e) {
Dialog.show('绑定失败', `<span class="error">${e}</span>`, {showCancel: false});
} finally {
Loading.hide();
}
}
}
// 弹出框确定
fnConfirm () {
// this.setState({popupShow: false, isEditor: false});
this.setState({...this.reset});
// 更新数据
this.props.update && this.props.update();
}
// 修改
handleModify (key, val) {
console.log(key, val);
this.setState({[key]: val});
}
// 切换账号
fnOrgCodePick = () => {
this.refs.orgCode.show();
}
// 修改账号
render () {
let plantData = null;
if (this.props.shop[this.props.plant.name]) {
plantData = this.props.shop[this.props.plant.name].find(item => item.vendorID === this.props.plant.id);
}
let venderName = this.props.system.cms.vendorName ? this.props.system.cms.vendorName[this.props.plant.id] : '';
return (
<div className="create-cell">
<div className="cell-label">{venderName}:</div>
<div className={classNames(
'cell-text',
{'no-bind': !plantData},
{'status-0': plantData && plantData.status === 0},
{'status-1': plantData && plantData.status === 1}
)}>{plantData ? plantData.vendorStoreID : '未绑定'}</div>
{/* 添加绑定 */}
{
!plantData && (
<div className="btn-insert" onClick={this.fnBind.bind(this)}>
<ReactSVG className="svg" src={SVGInsert}></ReactSVG>
</div>
)
}
{/* 删除绑定 */}
{
plantData && (
<div className="btn-delete" onClick={this.fnDelete.bind(this)}>
<ReactSVG className="svg" src={SVGDelete}></ReactSVG>
</div>
)
}
{/* 修改绑定 */}
{
plantData && (
<div className="btn-update" onClick={this.fnUpdate.bind(this)}>
<ReactSVG className="svg" src={SVGUpdate}></ReactSVG>
</div>
)
}
{/* 添加编辑弹出框 */}
{
this.state.popupShow && (
<PopupPage
className="cmp-modify-maps"
title={this.state.isEditor ? '编辑' + venderName : '绑定' + venderName}
fnReturn={this.fnReturn.bind(this)}
fnPrevConfirm={this.fnPrevConfirm.bind(this)}
showConfirm={true}
fnConfirm={this.fnConfirm.bind(this)}
>
<div className="header-title">正在操作 {this.props.shop.name} {this.state.isEditor ? '编辑' : '绑定'} {venderName}</div>
{/* 平台status */}
{
this.state.isEditor && this.state.vendorID < 100 && (
<div className="create-cell">
<div className="cell-label">平台状态:</div>
<div className="cell-value-checkbox">
<Radio fields={[
{value: 1, text: '开店'},
{value: 0, text: '关店'}
]} id={'status'} current={this.state.status} fnClick={this.handleModify.bind(this, 'status')}></Radio>
</div>
</div>
)
}
{/* 平台账号 */}
{
!this.state.isEditor && this.state.vendorID < 100 && (
<div className="create-cell">
<div className="cell-label">平台账号:</div>
<div className="btn-wrapper">
<div className="orgCode" onClick={this.fnOrgCodePick}>{this.state.vendorOrgCode}</div>
</div>
</div>
)
}
{/* 平台门店ID */}
<div className="create-cell">
<div className="cell-label">平台门店ID:</div>
{
this.state.isEditor ? (
<div className="cell-vendorStoreID-text">{this.state.vendorStoreID}</div>
) : (
<Input className="rangeInput" check={isNum} errText="平台门店ID不合法" fnBlur={this.handleModify.bind(this, 'vendorStoreID')} value={this.state.vendorStoreID} placeholder="请输入平台门店ID"></Input>
)
}
{
this.state.vendorID < 100 ? (
<div className="suffix-btn" onClick={this.searchVendorStore.bind(this)}>查询</div>
) : ''
}
</div>
{
this.state.vendorID < 100 ? (
<div>
{/* 基本设置 */}
<div className="create-cell">
<div className="cell-label">基本设置:</div>
<div className="cell-value">
<CheckBoxSelf
className="base-setting"
current={this.state.autoPickup}
text="自动拣货"
fields={[0, 1]}
fnClick={this.handleModify.bind(this, 'autoPickup')}
></CheckBoxSelf>
<CheckBoxSelf
className="base-setting"
current={this.state.deliveryCompetition}
text="配送竞争"
fields={[0, 1]}
fnClick={this.handleModify.bind(this, 'deliveryCompetition')}
></CheckBoxSelf>
</div>
</div>
{/* 平台调价 */}
<div className="create-cell">
<div className="cell-label">平台调价:</div>
<Input className="rangeInput" check={isNum} errText="请输入纯数字的平台调价" fnBlur={this.handleModify.bind(this, 'pricePercentage')} value={this.state.pricePercentage} placeholder="百分数"></Input>
<div className="suffix-text">%</div>
</div>
{/* 同步状态 */}
{/* {
!this.state.isEditor && (
)
} */}
<div className="create-cell">
<div className="cell-label">同步状态:</div>
<div className="cell-value-checkbox">
{
!this.state.isEditor && (
<Radio fields={[
{value: 1, text: '同步'},
{value: 0, text: '不同步'}
]} id={'isSync'} current={this.state.isSync} fnClick={this.handleModify.bind(this, 'isSync')}></Radio>
)
}
{
this.state.isEditor && this.state.isSync ? (
<span>同步</span>
) : (
<Radio fields={[
{value: 1, text: '同步'},
{value: 0, text: '不同步'}
]} id={'isSync'} current={this.state.isSync} fnClick={this.handleModify.bind(this, 'isSync')}></Radio>
)
}
</div>
</div>
{/* 配送类型 */}
{
this.state.isEditor && (
<div className="create-cell">
<div className="cell-label">快递类型:</div>
<div className="cell-vendorStoreID-text">
{this.props.system.cms.storeDeliveryType[this.state.deliveryType]}
</div>
</div>
)
}
</div>
) : (
<div className="create-cell">
{/* 专送status */}
<div className="cell-label">状态:</div>
<div className="cell-value-checkbox">
<Radio fields={[
{value: 1, text: '开启'},
{value: 0, text: '关闭'}
]} id={'status'} current={this.state.status} fnClick={this.handleModify.bind(this, 'status')}></Radio>
</div>
</div>
)
}
{/* 账号选择 */}
<CheckList ref="orgCode" datas={this.vendorOrgCodeList} map={{value: 'code', label: 'name'}} current={this.state.vendorOrgCode} title="选择账号" fnPick={this.handleModify.bind(this, 'vendorOrgCode')}></CheckList>
</PopupPage>
)
}
</div>
);
}
};
export default connect((state, props) => Object.assign({}, props, state), {})(VendorCell);

View File

@@ -0,0 +1,498 @@
@import '../../assets/style/colors.scss'; // 修改门店基础信息
.cmp-modify-base {
.modify-base-wrapper {
height: calc(100vh - 1.8rem);
overflow: hidden;
}
.create-cell {
font-size: 0.32rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.1rem 0.2rem;
background: white;
border-bottom: 1px solid $gray;
min-height: 0.9rem;
box-sizing: border-box;
.icon {
flex-shrink: 0;
svg {
width: 0.3rem;
height: 0.3rem;
fill: $sliver;
}
}
.cell-text {
width: 100%;
color: $sliver;
}
.cell-label {
width: 2rem;
color: $sliver;
flex-shrink: 0;
}
.cell-value {
color: $black;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
& > * {
flex-shrink: 0;
}
}
.rangeInput {
width: 100%;
margin: 0 0.1rem;
}
.suffix {
color: $sliver;
}
.cell-input {
flex: 1;
color: $black;
// border: 1px solid $danger;
border: none;
padding: 0.1rem;
border-radius: 0.1rem;
outline: none;
}
.danger {
border: 0.04rem dotted $danger;
}
.cell-time1 {
flex: 1;
display: flex;
justify-content: space-around;
align-items: center;
color: $sliver;
.time {
background: $lightGray;
color: $black;
padding: 0.1rem;
border-radius: 0.1rem;
width: 1.4rem;
text-align: center;
}
}
.cell-city {
flex: 1;
display: flex;
justify-content: space-around;
align-items: center;
.area {
color: white;
padding: 0.14rem 0.4rem;
border-radius: 0.1rem;
&:nth-of-type(1) {
background: $blue;
}
&:nth-of-type(2) {
background: $success;
}
}
}
}
.auto-open {
justify-content: flex-start;
.auto-open-text {
color: $lightBlue;
}
.auto-open-button {
background: $success;
margin-left: .1rem;
padding: 0.14rem 0.4rem;
color: white;
border-radius: .1rem;
}
}
.printer-cell {
justify-content: flex-start;
.printer {
// flex: 1;
background: $success;
margin-left: .1rem;
padding: 0.14rem 0.4rem;
color: white;
border-radius: .1rem;
}
.test-print {
margin-left: .4rem;
color: $blue;
// font-size: .28rem;
border: 1px solid $blue;
padding: 0.14rem 0.2rem;
border-radius: .1rem;
box-sizing: border-box;
}
}
.header-title {
font-size: 0.3rem;
padding: 0.2rem 0.4rem;
color: $lightBlack;
border-bottom: 1px solid $gray;
}
.btn-group {
display: flex;
margin-top: 0.5rem;
box-shadow: 0 0 0.05rem $extraLightBlack;
}
.delete-btn,
.modify-btn {
flex: 1;
background: $blue;
color: white;
font-size: 0.32rem;
height: 1rem;
line-height: 1rem;
text-align: center;
// border-top: 1px solid $extraLightBlack;
}
.delete-btn {
background: $danger;
}
}
// 修改平台绑定
.cmp-modify-maps {
.create-cell {
font-size: 0.32rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.1rem 0.2rem;
background: white;
border-bottom: 1px solid $gray;
min-height: 0.9rem;
box-sizing: border-box;
.cell-input {
flex: 1;
color: $black;
// border: 1px solid $danger;
border: none;
padding: 0.1rem;
border-radius: 0.1rem;
outline: none;
}
.danger {
border: 0.04rem dotted $danger;
}
.suffix-btn {
color: white;
background: $blue;
height: .6rem;
line-height: .6rem;
padding: 0 .4rem;
border-radius: .1rem;
flex-shrink: 0;
}
.suffix-text {
color: $sliver;
display: inline-block;
width: 3rem;
}
.rangeInput {
width: 100%;
margin: 0 0.1rem;
}
.cell-text {
width: 100%;
color: $black;
}
.cell-vendorStoreID-text {
color: $sliver;
width: 100%;
padding-left: .1rem;
}
.cell-label {
width: 2rem;
color: $sliver;
flex-shrink: 0;
}
.cell-value, .cell-value-checkbox {
color: $black;
flex: 1;
display: flex;
align-items: center;
// justify-content: center;
& > * {
flex-shrink: 0;
}
.base-setting {
margin: 0 .1rem;
}
}
.cell-value-checkbox {
padding-left: .1rem;
}
.no-bind {
color: $extraLightBlack;
}
// 关店
.status-0 {
color: $danger;
}
// 开店
.status-1 {
color: $primary;
}
}
.btn-insert, .btn-delete, .btn-update {
flex-shrink: 0;
background: #ccc;
margin: 0 .1rem;
width: .64rem;
height: .64rem;
display: flex;
border-radius: .1rem;
box-shadow: 0 0 .05rem $sliver;
.svg {
margin: auto;
svg {
width: .32rem;
height: .32rem;
fill: white;
}
}
}
.btn-insert {
background: $success;
}
.btn-delete {
background: $danger;
}
.btn-update {
background: $primary;
}
.success {
color: $success;
}
.btn-wrapper {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.orgCode {
// flex: 1;
color: $black;
// border: 1px solid $danger;
border: none;
padding: 0.12rem;
border-radius: 0.1rem;
outline: none;
background: $primary;
width: 2rem;
text-align: center;
color: white;
}
.header-title {
font-size: 0.3rem;
padding: 0.2rem 0.4rem;
color: $lightBlack;
border-bottom: 1px solid $gray;
}
}
// 分组管理
.cmp-modify-group {
.btn-group {
display: flex;
}
.btn-addGroup, .btn-reg {
flex: 1;
font-size: .32rem;
height: .9rem;
line-height: .9rem;
background: $primary;
color: white;
text-align: center;
}
.btn-reg {
background: $success;
}
.tel-input {
width: 100%;
font-size: .32rem;
margin: .1rem 0;
outline: none;
border: 1px solid $sliver;
padding: .1rem .2rem;
box-sizing: border-box;
border-radius: .1rem;
}
.tel-wrong {
border-color: $danger;
}
.cell-input {
// flex: 1;
color: $black;
border: 2px solid $success !important;
border: none;
padding: 0.1rem;
border-radius: 0.1rem;
outline: none;
width: 100%;
box-sizing: border-box;
}
.danger {
border: 2px dashed $danger !important;
}
// 组员卡片
.card-wrapper {
height: calc(100vh - 2.7rem);
overflow: hidden;
// overflow-y: auto;
// -webkit-overflow-scrolling: touch;
// padding: .4rem 0 ;
// padding-bottom: 1rem;
// padding: .2rem;
& > div {
padding: .2rem;
}
}
.card {
margin-bottom: .4rem;
box-shadow: 0 1px 1px rgba(black, .2),
0 8px 0 -3px white,
0 9px 1px -3px rgba(black, .2),
0 16px 0 -6px white,
0 17px 2px -6px rgba(black, .2);
border-radius: .1rem;
overflow: hidden;
border: 1px solid rgba($blue, .4);
// border-bottom: none;
}
.card-group, .card-group-member {
display: flex;
align-items: center;
padding: .2rem;
background: linear-gradient(to right, rgba($blue, .4), white);
justify-content: space-between;
.card-left {
font-size: .32rem;
color: $black;
.tel {
margin-top: .05rem;
}
.bind-id {
display: flex;
margin-top: .1rem;
.no-bind {
margin-right: .1rem;
svg {
width: .36rem;
height: .36rem;
fill: white;
padding: .05rem;
background: $gray;
}
}
.is-bind {
svg {
background: $success;
border-radius: .05rem;
}
}
}
}
.card-right {
margin-left: .2rem;
display: flex;
align-items: center;
.btn-editor, .btn-delete {
width: .64rem;
height: .64rem;
border-radius: .1rem;
box-shadow: 0 0 .05rem rgba($sliver, 1);
box-sizing: border-box;
border: .02rem solid white;
display: flex;
align-items: center;
justify-content: center;
.svg {
font-size: .32rem;
svg {
width: .32rem;
height: .32rem;
fill: white;
}
}
}
.btn-editor {
background: $blue;
}
.btn-delete {
margin-right: .1rem;
background: $danger;
}
}
}
.card-member-info {
font-size: .28rem;
background: white;
text-align: center;
height: .8rem;
display: flex;
align-items: center;
justify-content: center;
span {
color: $lightSliver;
margin-right: .2rem;
}
.btn-add-members {
color: white;
background: $success;
padding: .14rem .2rem;
border-radius: .1rem;
}
}
.card-group-member {
background: white;
margin-bottom: 0;
padding: .2rem .2rem .2rem .8rem;
border-top: 1px solid rgba($blue, .4);
.card-left {
color: $extraLightBlack;
font-size: .28rem;
}
}
.no-thing {
text-align: center;
margin-top: 1rem;
.no-store {
svg {
width: 4rem;
height: 4rem;
}
}
.text {
color: #ccc;
font-size: .32rem;
}
}
}

View File

@@ -0,0 +1,128 @@
// 创建门店
@import '../../assets/style/colors.scss';
.create-store {
padding-bottom: 1rem;
.create-store-wrapper {
height: calc(100vh - 1rem);
overflow: hidden;
}
.create-cell {
font-size: .32rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: .1rem .2rem;
background: white;
border-bottom: 1px solid $gray;
min-height: .9rem;
box-sizing: border-box;
.icon {
flex-shrink: 0;
svg {
width: .3rem;
height: .3rem;
fill: $sliver;
}
}
.cell-label {
width: 2rem;
color: $sliver;
flex-shrink: 0;
}
.cell-value {
color: $black;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
& > * {
flex-shrink: 0;
}
}
.cell-input {
flex: 1;
color: $black;
// border: 1px solid $danger;
border: none;
padding: .1rem;
border-radius: .1rem;
outline: none;
}
.danger {
border: .04rem dotted $danger;
}
.cell-time1 {
flex: 1;
display: flex;
justify-content: space-around;
align-items: center;
color: $sliver;
.time {
background: $lightGray;
color: $black;
padding: .1rem;
border-radius: .1rem;
width: 1.4rem;
text-align: center;
}
}
.cell-city {
flex: 1;
display: flex;
justify-content: space-around;
align-items: center;
.area {
color: white;
padding: .14rem .4rem;
border-radius: .1rem;
&:nth-of-type(1) {
background: $blue;
}
&:nth-of-type(2) {
background: $success;
}
}
}
.rangeInput {
width: 100%;
margin: 0 .1rem;
}
.suffix {
color: $sliver;
}
}
.printer-cell {
justify-content: flex-start;
.printer {
// flex: 1;
background: $success;
margin-left: .1rem;
padding: 0.14rem 0.4rem;
color: white;
border-radius: .1rem;
}
}
.has-suffix {
.cell-input {
// background: red;
flex: 1;
width: 2.6rem;
}
}
.suffix2 {
flex: 1;
// flex-shrink: 0;
// width: 4rem;
font-size: .18rem;
line-height: 1.5;
color: $primary;
}
.header-title {
font-size: .3rem;
padding: .2rem .4rem;
color: $lightBlack;
border-bottom: 1px solid $gray;
}
}

View File

@@ -0,0 +1,13 @@
// 修改门店
@import '../../assets/style/colors.scss';
// 基础信息修改
.modify-store {
.modify-wrapper {
// margin-top: .05rem;
height: calc(100vh - 1.8rem);
// overflow-y: auto;
overflow: hidden;
// -webkit-overflow-scrolling: touch;
}
}

View File

@@ -0,0 +1,40 @@
// 更多检索条件
@import '../../assets/style/colors.scss';
.more-search {
.search-cell {
font-size: .32rem;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 .4rem;
background: white;
border-bottom: 1px solid $gray;
min-height: 1rem;
.icon {
flex-shrink: 0;
svg {
width: .3rem;
height: .3rem;
fill: $sliver;
}
}
}
.search-cell + .search-cell {
}
.cell-label {
width: 1.8rem;
color: $sliver;
flex-shrink: 0;
}
.cell-value {
color: $black;
flex: 1;
}
.header-title {
font-size: .3rem;
padding: .2rem .4rem;
color: $lightBlack;
border-bottom: 1px solid $gray;
}
}

View File

@@ -0,0 +1,49 @@
// 门店单元
@import '../../assets/style/colors.scss';
.store-item {
// padding: 0 .2rem;
.store-cell {
padding: .4rem .2rem;
display: flex;
justify-content: space-between;
// height: .6rem;
align-items: center;
background: white;
line-height: 1;
}
.store-id {
font-size: .28rem;
color: $sliver;
margin-right: .2rem;
}
.store-name {
font-size: .32rem;
color: $black;
flex: 1;
}
.store-status {
font-size: .24rem;
padding: .08rem .12rem;
border-radius: .3rem;
color: white;
margin-left: .2rem;
flex-shrink: 0;
}
.status-jy {
background: $danger;
color: white;
}
.status-xx {
background: $gray;
}
.status-yy {
background: $success;
color: white;
}
.city {
font-size: .24rem;
margin-left: .1rem;
color: $sliver;
}
}

View File

@@ -0,0 +1,109 @@
/*
移动端组件使用方法
*/
// ----- Dialog -----
Dialog.show('标题', '内容', {
showCancel: '是否显示取消按钮 默认是',
cancelText: '取消按钮名称',
confirmText: '确定按钮名称'
}, res => {
console.log(res); // true false
});
// ----- Loading -----
// 显示
Loading.show();
// 隐藏
Loading.hide();
// ----- Toast -----
Toast.show('aaa', [timer = 5])
// ----- 弹出框PopupPage PopupPage2无动画-----
title="更多检索条件"
fnReturn={this.props.fnReturn.bind(this)}
fnPrevConfirm={fn} ruturn true or false 确认前
fnConfirm={this.props.fnConfirm.bind(this)}
SVG
// ----- 单选组件 -----
/*
fields={[
{value: 0, text: '不限定'},
{value: 1, text: '已绑定'},
{value: -1, text: '未绑定'}
]}
id={} 对象名
current={storeSearch.vendorStoreConds['0']}
fnClick={} return val id
*/
// ----- 多选组件 -----
current={[]} [-1, 0, 1]
fields={[
{value: 1, text: '营业中'},
{value: 0, text: '休息中'},
{value: -1, text: '禁用中'}
]}
fnClick={this.changeStatuss.bind(this)} changeStatuss(val)
// ----- 自选组件CheckBoxSelf -----
current={this.state.autoPickup}
text="自动拣货"
fields={[0, 1]} 假,真
fnClick={this.changeStatuss.bind(this)} changeStatuss(val)
// ----- 列表 -----
ref="checklist"
datas={this.props.system.cityLevel2}
map={{value: 'code', label: 'name'}}
nullShow = false 是否显示空
nullLabel = '' null显示
current={storeSearch.placeID}
title="城市选择"
fnPick={this.cityPick.bind(this)}
this.refs.xxx.show()
// ----- 创建门店input -----
check={isNum} 校验函数
value={this.state.id} 默认值
placeholder=""
errText="京西门店ID必须是纯数字" 报错提示
fnBlur={this.idBlur.bind(this)} 失去焦点处理函数 可以为空
// ----- 地址选择组件 -----
region 市名
ref="SearchAddress"
fnPick={this.fn.bind(this)}
// 打开
this.refs.xxx.show()
// ----- 选择输入框 -----
pick input button
title="选择平台" // 选择面板标题
placeholder="请输入平台门店ID"
datas={[
{id: 0, name: vendorName['0']},
{id: 1, name: vendorName['1']},
{id: 3, name: vendorName['3']}
]}
map={{value: 'id', label: 'name'}}
current={this.state.vendorID} // 当前选择
fnPick={this.handleVendorID} // 选择后处理函数val
fnChange={this.handleVendorStoreID} // 输入框改变处理函数val
fnConfirm={this.handleGetVendorStore} // 是否有确认按钮,且执行何种函数
// ----- 输入筛选 -----
placeholder="请选择门店" fnPick={this.pickSrcStore}
// ----- 门店选择组件 -----
ref="SearchAddress"
fnPick={this.fn.bind(this)}
// 打开
this.refs.xxx.show()