Commit cbce4f88 by wong.peiyi

日期及选择弹窗

parent 26e4e396
......@@ -12,6 +12,7 @@
"@babel/plugin-proposal-decorators": "^7.13.15",
"@react-native-community/async-storage": "^1.12.1",
"babel-plugin-import": "^1.13.3",
"dayjs": "^1.10.4",
"inversify": "^5.0.5",
"mobx": "^5.7.0",
"mobx-persist": "^0.4.1",
......
import React, { Component } from 'react'
import { View, Text, Image, TouchableOpacity, TextInput } from 'react-native'
import { IFormField } from 'bonehouse'
import { g, isRequired } from '../../utils/utils'
import styles from './index.styl'
export const Title = props => {
const { item } = props
const required = isRequired(item)
return (
<Text style={g(styles, 'form-label')}>
{!!required && <Text style={g(styles, 'red', 'pr5')}>*</Text>}
<Text>{item.label}</Text>
</Text>
)
}
@import '../../assets/styles/base.styl'
@import '../../assets/styles/variable.styl'
.modal
&-hd
height 50px
padding 15px 20px
&__btns
flex-direction row
justify-content space-between
&-btn
color btn_sub_color
font-size second_text_size
&-bd
height 100%
align-items center
/*
* @FilePath: /BoneHouse_Business_APP/src/components/form/date-modal.tsx
* @Author: peii
* @Date: 2021-04-27 21:53:02
* @Vision: 1.0
* @Description: 选择组件的弹出框
*
* @Revision:
*
*/
import React, { useEffect, useRef, useState, createRef } from 'react'
import { View, Text, TouchableOpacity, ScrollView, Animated } from 'react-native'
import DatePicker from 'react-native-date-picker'
import { IFormField } from 'bonehouse'
import * as R from 'ramda'
import dayjs from 'dayjs'
import { isBlank } from '../../utils/utils'
import { BottomModal } from '../modals/base/bottom'
import { FieldType } from '../../enums'
import { g } from '../../utils/utils'
import container from '../../inversify'
import { TYPES } from '../../inversify/types'
import styles from './date-modal.styl'
const store = container.get(TYPES.SysStore)
type IModalProps = {
value: string | number
mask?: boolean
title?: string
visible: boolean
onChange?: Function
onClose: Function
item?: IFormField
}
/**
* @description: 选择器弹窗
* @param {IModalProps} props
* @return {*}
*/
export function DateModal(props: IModalProps) {
const { item, value, mask, onChange, visible, onClose } = props
const [contentHeight, setContentHeight] = useState(300)
const [dateValue, setDateValue] = useState(value)
const [action, setAction] = useState(0)
return (
<BottomModal contentHeight={contentHeight} visible={visible} onClose={onClose} action={action}>
<View style={g(styles, ['modal-hd', 'modal-hd__btns'])}>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
animated(bottom, -contentHeight, opacity, 0, onClose)
}}
>
<Text style={g(styles, 'modal-hd__btns-btn')}>取消</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={() => {
const date = dateValue || value
onChange(dayjs(date).format('YYYY-MM-DD HH:mm'))
setAction(action + 1)
}}
>
<Text style={g(styles, 'modal-hd__btns-btn')}>确定</Text>
</TouchableOpacity>
</View>
<View style={g(styles, 'modal-bd')}>
<DatePicker
date={dayjs(dateValue).toDate() || dayjs().toDate()}
mode={item.dateMode || 'date'}
locale="zh"
onDateChange={date => {
setDateValue(date)
}}
/>
</View>
</BottomModal>
)
}
/*
* @FilePath: /BoneHouse_Business_APP/src/components/form/date.tsx
* @Author: peii
* @Date: 2021-04-25 23:36:10
* @Vision: 1.0
* @Description: 选择器组件
*
* @Revision:
*
*/
import React, { Component } from 'react'
import { View, Text, Image, TouchableOpacity } from 'react-native'
import { IFormField, IOption } from 'bonehouse'
import * as R from 'ramda'
import { isBlank, show, isNotBlank, g } from '../../utils/utils'
import { FieldType } from '../../enums'
import { Title } from './common'
import { DateModal } from './date-modal'
import styles from './index.styl'
type IProps = {
item: IFormField
value: any
onChange: Function
data: any
fields: any[]
modalCallback: Function
}
/**
* @description: 获取显示字符
* @param {*} item
* @param {*} val
* @return {*}
*/
const getText = (item, val) => {
if (!val) return null
return R.compose(R.find(R.propEq('value', val)), R.propOr([], 'options'))(item)
}
export default class DatePicker extends Component<IProps> {
state = {
visible: false,
}
constructor(props) {
super(props)
this.onPressHandler = this.onPressHandler.bind(this)
this.onChange = this.onChange.bind(this)
}
shouldComponentUpdate(nextProps: IProps) {
return true
}
componentWillReceiveProps(nextProps, nextState) {
const { item, value } = nextProps
const { visible } = this.state
}
/**
* @description: 点击显示行
*/
onPressHandler() {
const { item, data, fields, value } = this.props
if (item.disabled) return
// 需要先选择依赖的字段
for (const ref of item.refrence || []) {
if (isBlank(data[ref])) {
const field = R.find(R.propEq('field', ref))(fields)
if (isNotBlank(field)) {
return show(`请先选择${field.label}`)
}
}
}
this.setState({ visible: true }, () => {
const modal = (
<DateModal
item={item}
value={value}
visible={true}
mask={true}
onChange={this.onChange}
onClose={() => {
this.setState({ visible: false }, () => {
this.props.modalCallback()
})
}}
/>
)
this.props.modalCallback(modal)
})
}
/**
* @description: 点击弹出选中值
*/
onChange(val: any): void {
const { item, value } = this.props
if (val === value) return
this.props && this.props.onChange(item.field, val, item.callback)
}
render() {
const { visible } = this.state
const { item, value } = this.props
return (
<>
<TouchableOpacity
style={g(styles, 'form-item')}
activeOpacity={item.disabled ? 1 : 0.8}
onPress={this.onPressHandler}
>
<Title item={item} />
<View style={g(styles, 'form-input')}>
{/* 值 */}
{isBlank(value) ? (
<Text style={g(styles, 'form-input__placeholder')}>
{item.placeholder || '请选择'}
</Text>
) : (
<Text numberOfLines={2} style={g(styles, 'form-input__text')}>
{value}
</Text>
)}
{/* 箭头 */}
{item.arrow !== false && (
<Image
source={require('../../assets/images/arr_rig.png')}
style={g(styles, 'form-input__arrow')}
/>
)}
</View>
</TouchableOpacity>
</>
)
}
}
......@@ -5,26 +5,42 @@
font-size second_text_size
font-family font_family_regular
.container
flex 1
.form
flex 1
&-inner
padding 18px
width 100%
height 100%
&-item
width 378px
height 58px
background-color #fff
padding 18px
padding 0 18px
flex-direction row
justify-content space-between
align-items center
margin-bottom 18px
&-multi
height 160px
flex-direction column
align-items flex-start
&-label
@extend .text
color primary_text_color
width 100px
&-input
flex-direction row
align-items center
justify-content flex-end
flex 1
&__placeholder
@extend .text
......@@ -33,11 +49,21 @@
&__text
@extend .text
color second_text_color
line-height 18px
text-align right
&__arrow
width 8px
height 14px
margin-left 9px
&-text-input
@extend .text
text-align right
color second_text_color
&__multi
text-align left
.red
color #f00
......@@ -9,36 +9,81 @@
*
*/
import React, { Component } from 'react'
import { View, Text } from 'react-native'
import { View, Text, ScrollView, SafeAreaView } from 'react-native'
import { IFormField } from 'bonehouse'
import { FieldType } from '../../enums'
import Select from './select'
import Input from './input'
import DatePicker from './date'
import { g } from '../../utils/utils'
import styles from './index.styl'
type IProps = {
fields: IFromField[]
data: { [key: string]: any }
modalCallback: Function
}
const formComponents = {
[FieldType.SELECT]: Select,
[FieldType.TEXT]: Input,
[FieldType.DATE]: DatePicker,
}
export default class Form extends Component<IProps> {
state = {
value: '',
scrollable: true,
modal: null,
}
onChange() {}
constructor(props: IProps) {
super(props)
this.onChange = this.onChange.bind(this)
this.modalCallback = this.modalCallback.bind(this)
}
async onChange(key: string, value: any, callback?: Function) {
this.props.onChange && (await this.props.onChange(key, value))
callback && callback()
}
/**
* modal框开关回调
* @param modalOpen 是否有modal框打开
*/
modalCallback(modal?: Component) {
this.setState({ modal })
}
render() {
const { fields, data } = this.props
const { fields = [], data = {} } = this.props
const { scrollable, modal } = this.state
return (
<View style={g(styles, 'form')}>
<View style={g(styles, 'container')}>
<ScrollView style={g(styles, 'form')}>
<View style={g(styles, 'form-inner')}>
{fields.map(item => {
const FormComponent = formComponents[item.type] || Select
return <FormComponent key={item.field} item={item} value={data[item.field]} />
return (
<FormComponent
key={item.field}
item={item}
value={data[item.field]}
data={data}
fields={fields}
onChange={this.onChange}
modalCallback={this.modalCallback}
/>
)
})}
</View>
</ScrollView>
{/* 弹出窗 */}
{modal}
</View>
)
}
}
/*
* @FilePath: /BoneHouse_Business_APP/src/components/form/input.tsx
* @Author: peii
* @Date: 2021-05-06 13:05:38
* @Vision: 1.0
* @Description: 文本输入组件
*
*/
import React, { Component } from 'react'
import { View, Text, Image, TouchableOpacity, TextInput } from 'react-native'
import { IFormField } from 'bonehouse'
import * as R from 'ramda'
import { isBlank, show, isRequired, debounce } from '../../utils/utils'
import { placehold_text_color } from '../../assets/styles/base'
import { FieldType } from '../../enums'
import { g } from '../../utils/utils'
import { Title } from './common'
import styles from './index.styl'
type IProps = {
item: IFormField
onChange: Function
}
export default class Input extends Component<IProps> {
constructor(props) {
super(props)
this.onChangeText = debounce(this.onChangeText.bind(this), 500)
}
onChangeText(text: string) {
const { item, onChange } = this.props
onChange && onChange(item.field, text)
}
render() {
const { item, value } = this.props
return (
<View style={g(styles, { 'form-item': true, 'form-item-multi': item.multiline })}>
<Title item={item} />
<TextInput
style={g(styles, {
'form-input': true,
'form-text-input': true,
'form-text-input__multi': item.multiline,
})}
autoCapitalize="none"
placeholder={item.placeholder || '请输入'}
onChangeText={this.onChangeText}
multiline={item.multiline}
numberOfLines={4}
maxLength={140}
placeholderTextColor={placehold_text_color}
underlineColorAndroid="transparent"
/>
</View>
)
}
}
@import '../../assets/styles/base.styl'
@import '../../assets/styles/variable.styl'
.modal
position absolute
left 0
top 0
.option-text
width 100%
text-align center
color third_text_color
font-size second_text_size
&-mask
@extend .modal
.select
width 100%
height 100%
background-color rgba(0, 0, 0, 0.5)
&-content
position absolute
&-bd
width 100%
max-height 400px
background-color foundation_color
bottom 0
height 100%
padding 50px 30px
&-option
height 35px
justify-content center
&__text
@extend .option-text
&-active
@extend .option-text
color first_text_color
......@@ -8,19 +8,16 @@
* @Revision:
*
*/
import React, { useEffect, useRef, useState } from 'react'
import React, { useEffect, useRef, useState, createRef } from 'react'
import { View, Text, TouchableOpacity, ScrollView, Animated } from 'react-native'
import { Provider, Modal } from '@ant-design/react-native'
import { IOption } from 'bonehouse'
import * as R from 'ramda'
import { isBlank } from '../../utils/utils'
import Resolution from '../../components/common/Resolution'
import { BottomModal } from '../modals/base/bottom'
import { FieldType } from '../../enums'
import { g } from '../../utils/utils'
import styles from './select-modal.styl'
import container from '../../inversify'
import { TYPES } from '../../inversify/types'
const store = container.get(TYPES.SysStore)
type IModalProps = {
data: IOption[]
......@@ -32,29 +29,7 @@ type IModalProps = {
onClose: Function
}
/**
* @description: 动画函数
* @param {*} bottomAnim
* @param {*} bottom
* @param {*} opacityAnim
* @param {*} opacity
* @param {*} cb
* @return {*}
*/
function animated(bottomAnim, bottom, opacityAnim, opacity, cb) {
Animated.parallel([
Animated.timing(bottomAnim, {
toValue: bottom,
duration: 150,
}),
Animated.timing(opacityAnim, {
toValue: opacity,
duration: 150,
}),
]).start(() => {
cb && cb()
})
}
let scrollRef = null
/**
* @description: 选择器弹窗
......@@ -63,61 +38,49 @@ function animated(bottomAnim, bottom, opacityAnim, opacity, cb) {
*/
export function SelectModal(props: IModalProps) {
const { data = [], value, mask, onChange, visible, title, onClose } = props
const { width, height } = Resolution.get()
const [modalHeight, setModalHeight] = useState(height - store.headerHeight)
const [contentHeight, setContentHeight] = useState(400)
const bottom = useRef(new Animated.Value(-contentHeight)).current
const opacity = useRef(new Animated.Value(0)).current
const [contentHeight, setContentHeight] = useState(300)
const [action, setAction] = useState(0)
useEffect(() => {
if (visible) {
animated(bottom, 0, opacity, 1)
setContentHeight(35 * data.length + 100)
const idx = R.findIndex(R.propEq('value', value))(data)
if (idx > -1) {
scrollRef && scrollRef.scrollTo({ y: 35 * idx + 20 })
}
}, [visible])
if (!visible) return <></>
console.log(data)
}, [data.length])
return (
<Animated.View style={[g(styles, 'modal'), { width, height: modalHeight, opacity: opacity }]}>
{!!mask && (
<TouchableOpacity
style={g(styles, 'modal-mask')}
activeOpacity={1}
onPress={() => {
animated(bottom, -contentHeight, opacity, 0, onClose)
}}
></TouchableOpacity>
)}
<Animated.View style={[g(styles, 'modal-content'), { bottom, height: contentHeight }]}>
{!!title && (
<View style={g(styles, 'modal-hd')}>
<Text style={g(styles, 'modal-header__text')}>{title}</Text>
</View>
)}
<ScrollView style={g(styles, 'modal-bd')}>
<View style={g(styles, 'select')}>
<BottomModal contentHeight={contentHeight} visible={visible} onClose={onClose} action={action}>
<ScrollView style={g(styles, 'select')} ref={ref => (scrollRef = ref)}>
<View style={g(styles, 'select-bd')}>
{data.map(option => {
return (
<TouchableOpacity
key={option.value}
style={g(styles, 'select-option')}
activeOpacity={0.8}
onPress={() => {
if (R.propEq('value', value, option)) return
onChange && onChange(option)
onChange && onChange(option.value)
setAction(action + 1)
}}
>
<Text>{option.label}123123</Text>
<Text
numberOfLines={1}
style={g(
styles,
value === option.value
? ['select-option__text-active']
: ['select-option__text'],
)}
>
{option.label}
</Text>
</TouchableOpacity>
)
})}
</View>
</ScrollView>
</Animated.View>
</Animated.View>
</BottomModal>
)
}
......@@ -10,28 +10,31 @@
*/
import React, { Component } from 'react'
import { View, Text, Image, TouchableOpacity } from 'react-native'
import { IFormField } from 'bonehouse'
import { IFormField, IOption } from 'bonehouse'
import * as R from 'ramda'
import { isBlank } from '../../utils/utils'
import { isBlank, show, isRequired, isNotBlank, g } from '../../utils/utils'
import { FieldType } from '../../enums'
import { g } from '../../utils/utils'
import { Title } from './common'
import { SelectModal } from './select-modal'
import styles from './index.styl'
type IProps = {
item: IFormField
value: any
onChange: Function
data: any
fields: any[]
modalCallback: Function
}
/**
* @description: 是否必需
* @param {Object}
* @return {Boolean}
* @description: 获取显示字符
* @param {*} item
* @param {*} val
* @return {*}
*/
const isRequired = R.compose(R.find(R.propEq('required', true)), R.propOr([], 'rules'))
const getText = (item, val) => {
if (!val) return null
return R.compose(R.find(R.propEq('value', val)), R.propOr([], 'options'))(item)
}
......@@ -43,16 +46,71 @@ export default class Select extends Component<IProps> {
constructor(props) {
super(props)
this.onPressHandler = this.onPressHandler.bind(this)
this.onChange = this.onChange.bind(this)
}
shouldComponentUpdate(nextProps) {
shouldComponentUpdate(nextProps: IProps) {
return true
}
componentWillReceiveProps(nextProps, nextState) {
const { item, value } = nextProps
const { visible } = this.state
// 当前只有一个选项时,赋值第一个选项作为当前选项值
if (item && item.options && item.options.length === 1 && item.options[0].value !== value) {
this.onChange(item.options[0].value)
}
const val = getText(item, value)
if (isNotBlank(value) && isBlank(val)) {
this.onChange(null)
}
}
/**
* @description: 点击显示行
*/
onPressHandler() {
const { item } = this.props
const { item, data, fields, value } = this.props
if (item.disabled) return
this.setState({ visible: true })
// 需要先选择依赖的字段
for (const ref of item.refrence || []) {
if (isBlank(data[ref])) {
const field = R.find(R.propEq('field', ref))(fields)
if (isNotBlank(field)) {
return show(`请先选择${field.label}`)
}
}
}
if (isBlank(item.options)) return show(`没有可选择的${item.label}`)
this.setState({ visible: true }, () => {
// 弹出窗,要放在最外层
const modal = (
<SelectModal
data={item.options}
value={value}
visible={true}
mask={true}
onChange={this.onChange}
onClose={() => {
this.setState({ visible: false }, () => {
this.props.modalCallback()
})
}}
/>
)
this.props.modalCallback(modal)
})
}
/**
* @description: 点击弹出选中值
*/
onChange(val: any): void {
const { item, value } = this.props
if (val === value) return
this.props && this.props.onChange(item.field, val, item.callback)
}
render() {
......@@ -61,7 +119,6 @@ export default class Select extends Component<IProps> {
const required = isRequired(item)
const val = getText(item, value)
console.log(item)
return (
<>
<TouchableOpacity
......@@ -69,18 +126,21 @@ export default class Select extends Component<IProps> {
activeOpacity={item.disabled ? 1 : 0.8}
onPress={this.onPressHandler}
>
<Text style={g(styles, 'form-label')}>
{!!required && <Text style={g(styles, 'red', 'pr5')}>*</Text>}
<Text>{item.label}</Text>
</Text>
<Title item={item} />
<View style={g(styles, 'form-input')}>
{/* 值 */}
{isBlank(value) ? (
<Text style={g(styles, 'form-input__placeholder')}>
{item.placeholder || '请选择'}
</Text>
) : (
<Text style={g(styles, 'form-input__text')}>{val && val.label}</Text>
<Text numberOfLines={2} style={g(styles, 'form-input__text')}>
{val && val.label}
</Text>
)}
{/* 箭头 */}
{item.arrow !== false && (
<Image
source={require('../../assets/images/arr_rig.png')}
......@@ -89,16 +149,6 @@ export default class Select extends Component<IProps> {
)}
</View>
</TouchableOpacity>
<SelectModal
data={item.options}
value={value}
visible={visible}
mask={true}
onClose={() => {
this.setState({ visible: false })
}}
/>
</>
)
}
......
/*
* @FilePath: /BoneHouse_Business_APP/src/components/form/textarea.tsx
* @Author: peii
* @Date: 2021-05-06 13:05:38
* @Vision: 1.0
* @Description: 多行文本输入组件
*
*/
import React, { Component } from 'react'
import { View, Text, Image, TouchableOpacity, } from 'react-native'
import { IFormField } from 'bonehouse'
import * as R from 'ramda'
import { isBlank, show, isRequired, debounce } from '../../utils/utils'
import { FieldType } from '../../enums'
import { g } from '../../utils/utils'
import { SelectModal } from './select-modal'
import styles from './index.styl'
type IProps = {
item: IFormField
onChange: Function
}
export default class Input extends Component<IProps> {
constructor(props) {
super(props)
this.onChangeText = debounce(this.onChangeText.bind(this), 500)
}
onChangeText(text: string) {
const { item, onChange } = this.props
onChange && onChange(item.field, text)
}
render() {
const { item, value } = this.props
const required = isRequired(item)
return (
<View style={g(styles, 'form-item')}>
<Text style={g(styles, 'form-label')}>
{!!required && <Text style={g(styles, 'red', 'pr5')}>*</Text>}
<Text>{item.label}</Text>
</Text>
<TextInput
style={g(styles, 'form-input', 'form-text-input')}
autoCapitalize="none"
placeholder={item.placeholder || '请输入'}
onChangeText={this.onChangeText}
/>
</View>
)
}
}
@import '../../../assets/styles/base.styl'
@import '../../../assets/styles/variable.styl'
.modal
position absolute
left 0
top 0
z-index 99
&-mask
@extend .modal
width 100%
height 100%
background-color rgba(0, 0, 0, 0.5)
z-index 99
&-content
position absolute
width 100%
max-height 300px
background-color foundation_color
bottom 0
border-top-left-radius 5px
border-top-right-radius 5px
z-index 999
&__date
max-height 400px
&-hd
height 50px
padding 15px 20px
&__btns
flex-direction row
justify-content space-between
&-btn
color btn_sub_color
font-size second_text_size
&-bd
height 100%
/*
* @FilePath: /BoneHouse_Business_APP/src/components/modals/base/bottom.tsx
* @Author: peii
* @Date: 2021-04-27 21:53:02
* @Vision: 1.0
* @Description: 在下方的基础弹出框
*
* @Revision:
*
*/
import React, { useEffect, useRef, useState, createRef } from 'react'
import { View, Text, TouchableOpacity, ScrollView, Animated } from 'react-native'
import { Provider, Modal } from '@ant-design/react-native'
import { IOption } from 'bonehouse'
import * as R from 'ramda'
import Resolution from '../../../components/common/Resolution'
import { FieldType } from '../../../enums'
import { g, isBlank } from '../../../utils/utils'
import container from '../../../inversify'
import { TYPES } from '../../../inversify/types'
import styles from './bottom.styl'
const store = container.get(TYPES.SysStore)
type IModalProps = {
mask?: boolean
title?: string
visible: boolean
contentHeight: number
onClose: Function
action: number
}
/**
* @description: 动画函数
* @param {*} bottomAnim
* @param {*} bottom
* @param {*} opacityAnim
* @param {*} opacity
* @param {*} cb
* @return {*}
*/
export function animated(bottomAnim, bottom, opacityAnim, opacity, cb) {
Animated.parallel([
Animated.timing(bottomAnim, {
toValue: bottom,
duration: 150,
}),
Animated.timing(opacityAnim, {
toValue: opacity,
duration: 150,
}),
]).start(() => {
cb && cb()
})
}
let scrollRef = null
/**
* @description: 选择器弹窗
* @param {IModalProps} props
* @return {*}
*/
export function BottomModal(props: IModalProps) {
const { mask = true, onChange, visible, title, onClose, contentHeight = 300, action } = props
const { width, height } = Resolution.get()
const [modalHeight, setModalHeight] = useState(height - store.headerHeight)
const bottom = useRef(new Animated.Value(-contentHeight)).current
const opacity = useRef(new Animated.Value(0)).current
useEffect(() => {
if (visible) {
animated(bottom, 0, opacity, 1)
}
}, [visible])
useEffect(() => {
if (action) {
animated(bottom, -contentHeight, opacity, 0, onClose)
}
}, [action])
if (!visible) return <></>
return (
<Animated.View style={[g(styles, 'modal'), { width, height: modalHeight, opacity: opacity }]}>
{!!mask && (
<TouchableOpacity
style={g(styles, 'modal-mask')}
activeOpacity={1}
onPress={() => {
animated(bottom, -contentHeight, opacity, 0, onClose)
}}
></TouchableOpacity>
)}
<Animated.View style={[g(styles, 'modal-content'), { bottom, height: contentHeight }]}>
{!!title && (
<View style={g(styles, 'modal-hd')}>
<Text style={g(styles, 'modal-header__text')}>{title}</Text>
</View>
)}
<View style={g(styles, 'modal-bd')}>{props.children}</View>
</Animated.View>
</Animated.View>
)
}
......@@ -16,6 +16,7 @@ import Store from '../stores/store'
import System from '../stores/system'
import User from '../stores/user'
import Organization from '../stores/organization'
import Consume from '../stores/consume'
const container = new Container({ defaultScope: 'Singleton' })
......@@ -24,5 +25,6 @@ container.bind<Store>(TYPES.Store).to(Store)
container.bind<System>(TYPES.SysStore).to(System)
container.bind<User>(TYPES.UserStore).to(User)
container.bind<Organization>(TYPES.OrgStore).to(Organization)
container.bind<Consume>(TYPES.Consume).to(Consume)
export default container
......@@ -14,4 +14,5 @@ export const TYPES = {
SysStore: Symbol.for('system'),
UserStore: Symbol.for('user'),
OrgStore: Symbol.for('organization'),
Consume: Symbol.for('consume'),
}
......@@ -11,11 +11,11 @@
import React, { Component } from 'react'
import { View, Text, ScrollView } from 'react-native'
import { inject, observer } from 'mobx-react'
import { IOrganization } from 'bonehouse'
import { IOrganization, ISurgeryCollectHeader } from 'bonehouse'
import Form from '../../components/form'
import { FieldType } from '../../enums'
import Header from '../../components/header/header'
import { g } from '../../utils/utils'
import { g, getFormItem, isBlank } from '../../utils/utils'
import styles from './consume.styl'
type IProps = {
......@@ -28,15 +28,23 @@ type IProps = {
orgStore: {
organizations: IOrganization[]
orgs: Function
getCustomers: Function
customers: Function
}
consumeStore: {
orders: ISurgeryCollectHeader[]
getOrders: Function
}
}
class Consume extends Component<IProps> {
constructor(props) {
super(props)
this.setData = this.setData.bind(this)
}
state = {
scrollable: true,
data: {
sellerCode: this.props.userStore.userName,
},
......@@ -52,28 +60,162 @@ class Consume extends Component<IProps> {
},
],
placeholder: '请选择',
rules: [{ required: true, message: '请选择组织' }],
rules: [{ required: true, message: '请选择销售员' }],
disabled: true,
},
{
field: 'orgId',
field: 'orgCode',
label: '组织',
type: FieldType.SELECT,
options: this.props.orgStore.orgs(),
placeholder: '请选择',
rules: [{ required: true, message: '请选择组织' }],
callback: this.getCustomers.bind(this),
},
{
field: 'customerCode',
label: '客户',
type: FieldType.SELECT,
options: [],
placeholder: '请选择',
rules: [{ required: true, message: '请选择客户' }],
refrence: ['orgCode'],
callback: this.customerCallback.bind(this),
},
{
field: 'surgeryCollectNumber',
label: '订单信息',
type: FieldType.SELECT,
options: [],
placeholder: '请选择',
rules: [{ required: true, message: '请选择订单' }],
refrence: ['orgCode', 'customerCode'],
},
{
field: 'consumeDate',
label: '消耗时间',
type: FieldType.DATE,
dateMode: 'datetime',
options: [],
placeholder: '请选择',
rules: [{ required: true, message: '请选择消耗时间' }],
},
{
field: 'patientName',
label: '患者姓名',
type: FieldType.TEXT,
placeholder: '请输入患者姓名',
rules: [{ required: true, message: '请输入患者姓名' }],
},
{
field: 'patientGender',
label: '性别',
type: FieldType.SELECT,
options: [
{
label: '男',
value: '男',
},
{
label: '女',
value: '女',
},
],
placeholder: '请选择',
rules: [{ required: true, message: '请选择性别' }],
},
{
field: 'patientAge',
label: '年龄',
type: FieldType.TEXT,
placeholder: '请输入年龄',
rules: [{ required: true, message: '请输入年龄' }],
},
{
field: 'patientBed',
label: '床位',
type: FieldType.TEXT,
placeholder: '请输入床位',
rules: [{ required: true, message: '请输入床位' }],
},
{
field: 'patientId',
label: '病历号',
type: FieldType.TEXT,
placeholder: '请输入病历号',
rules: [{ required: true, message: '请输入病历' }],
},
{
field: 'remark',
label: '备注',
type: FieldType.TEXT,
placeholder: '请输入备注',
multiline: true,
rules: [],
},
],
}
componentDidMount() {
this.setSeller()
componentDidMount() {}
/**
* @description: 设置修改值
* @param {*} key
* @param {*} value
* @return {*}
*/
setData(key: string, value: any) {
return new Promise<any>((resolve, reject) => {
const { data } = this.state
data[key] = value
this.setState({ data }, () => {
console.log(data)
resolve()
})
})
}
/**
* 获取客户列表
*/
async getCustomers() {
const { data, formItems } = this.state
const { orgCode, sellerCode } = data
let customers = this.props.orgStore.customers(sellerCode, orgCode)
if (isBlank(customers)) {
await this.props.orgStore.getCustomers(sellerCode, orgCode)
customers = this.props.orgStore.customers(sellerCode, orgCode)
}
const item = getFormItem(formItems, 'customerCode')
item.options = customers
this.setState({ formItems })
}
setSeller() {}
/**
* @description: 客户选择完回调
* @param {*}
* @return {*}
*/
async customerCallback() {
const { data, formItems } = this.state
let orders = this.props.consumeStore.orders(data.sellerCode, data.orgCode, data.customerCode)
if (isBlank(orders)) {
await this.props.consumeStore.getOrders({
sellerCode: data.sellerCode,
orgCode: data.orgCode,
customerCode: data.customerCode,
collectHeaderStatus: 'RETURNED,COLLECTED',
})
orders = this.props.consumeStore.orders(data.sellerCode, data.orgCode, data.customerCode)
}
const item = getFormItem(formItems, 'surgeryCollectNumber')
item.options = orders
this.setState({ formItems })
}
render() {
const { formItems, data } = this.state
const { formItems, data, scrollable } = this.state
const { navigation } = this.props
const title = navigation.getParam('title', '骨科智慧仓')
......@@ -86,12 +228,11 @@ class Consume extends Component<IProps> {
}}
/>
<ScrollView style={g(styles, 'container')}>
<Form fields={formItems} data={data} />
</ScrollView>
{/* form表单 */}
<Form fields={formItems} data={data} onChange={this.setData} />
</View>
)
}
}
export default inject('sysStore', 'userStore', 'orgStore')(observer(Consume))
export default inject('sysStore', 'userStore', 'orgStore', 'consumeStore')(observer(Consume))
......@@ -26,9 +26,6 @@ type IProps = {
getBizFuns: Function
setNavigation: Function
}
sysStore: {
getSysProfile: Function
}
orgStore: {
getOrganizations: Function
}
......@@ -53,7 +50,6 @@ class Index extends Component<IProps> {
const { navigation, store, orgStore } = this.props
!store.token && navigation.navigate('Signin')
this.getSysProfile()
orgStore.getOrganizations()
}, 0)
}
......@@ -66,13 +62,14 @@ class Index extends Component<IProps> {
onRefresh() {
this.setState({ refreshing: true })
this.getSysProfile().finally(() => this.setState({ refreshing: false }))
}
async getSysProfile() {
this.props.sysStore.getSysProfile()
this.props.orgStore.getOrganizations().finally(() => this.setState({ refreshing: false }))
}
/**
* @description: 导航到功能页
* @param {*} fun
* @return {*}
*/
navigateToBizPage(fun: IFunction) {
const pages = {
MOBILE_BORROW_ORDER: '',
......@@ -81,7 +78,6 @@ class Index extends Component<IProps> {
MOBILE_DEVICE_INFORMATION: '',
MOBILE_TRANSFER_APPLICATION: '',
}
console.log(fun)
this.props.navigation.navigate(pages[fun.functionCode], { title: fun.functionName })
}
......@@ -122,4 +118,4 @@ class Index extends Component<IProps> {
}
}
export default inject('store', 'sysStore', 'orgStore')(observer(Index))
export default inject('store', 'orgStore')(observer(Index))
......@@ -42,4 +42,27 @@ export default class Service {
getOrganizations() {
return request({ url: `${ctx}/authorized_inventory/search` })
}
/**
* 获取客户
* @param data
* @returns
*/
getCustomers(data: { orgCode: string; sellerCode: string }): Promise<any> {
return request({ url: `${ctx}/sale/seller_customer/search`, data })
}
/**
* @description: 获取借货订单
* @param {*}
* @return {*}
*/
getOrders(data: {
orgCode: string
sellerCode: string
customerCode: string
collectHeaderStatus: string
}) {
return request({ url: `${ctx}/surgery/collect_order/search`, data })
}
}
// @ts-nocheck
/*
* @FilePath: /BoneHouse_Business_APP/src/stores/consume.ts
* @Author: peii
* @Date: 2021-05-10 11:57:51
* @Vision: 1.0
* @Description: 消耗确认
*
* @Revision:
*
*/
import { observable, flow, runInAction, action } from 'mobx'
import { ISurgeryCollectHeader } from 'bonehouse'
import { injectable, inject } from 'inversify'
import * as R from 'ramda'
import Service from '../services/service'
import { TYPES } from '../inversify/types'
import dayjs from 'dayjs'
@injectable()
export default class Consume {
@inject(TYPES.Service)
service!: Service
@observable _orders: { [key: string]: ISurgeryCollectHeader[] } = {}
/**
* @description: 借货订单列表
* @param {string} sellerCode
* @param {string} orgCode
* @param {string} customerCode
* @return {*}
*/
orders(sellerCode: string, orgCode: string, customerCode: string) {
const orders = this._orders[`${sellerCode}_${orgCode}_${customerCode}`] || []
function add() {
return R.reduce((prev: string, next: string) => {
return prev + next + '-'
}, '')(arguments)
}
return R.map(
R.applySpec({
label: R.compose(
R.init,
R.converge(add, [
R.prop('collectNumber'),
R.propOr('无', 'surgeryName'),
R.propOr('无', 'doctorName'),
R.compose(date => {
return date && dayjs(date).format('YYYY-MM-DD')
}, R.prop('createTime')),
]),
),
value: R.prop('collectNumber'),
}),
)(orders)
}
/**
* @description: 获取借货订单
* @param {*}
* @return {*}
*/
getOrders = flow(function* (this: Consume, params: any) {
const res = yield this.service.getOrders(params) as any
const { sellerCode, orgCode, customerCode } = params
this._orders[`${sellerCode}_${orgCode}_${customerCode}`] = res.data.surgeryCollectHeaders
})
}
......@@ -17,6 +17,7 @@ const store = container.get<any>(TYPES.Store)
const sysStore = container.get<any>(TYPES.SysStore)
const userStore = container.get<any>(TYPES.UserStore)
const orgStore = container.get<any>(TYPES.OrgStore)
const consumeStore = container.get<any>(TYPES.Consume)
const hydrate = create({
storage: AsyncStorage,
......@@ -29,4 +30,4 @@ hydrate('sysStore', sysStore)
hydrate('userStore', userStore)
hydrate('orgStore', orgStore)
export default { store, sysStore, userStore, orgStore }
export default { store, sysStore, userStore, orgStore, consumeStore }
......@@ -9,11 +9,12 @@
*
*/
// @ts-nocheck
import { observable, action, runInAction, toJS, flow } from 'mobx'
import { persist } from 'mobx-persist'
import { injectable, inject } from 'inversify'
import Service from '../services/service'
import { IOrganization } from 'bonehouse'
import { IOrganization, ICustomer } from 'bonehouse'
import * as R from 'ramda'
import { isBlank } from '../utils/utils'
import { TYPES } from '../inversify/types'
......@@ -22,9 +23,14 @@ import { TYPES } from '../inversify/types'
export default class Organization {
@inject(TYPES.Service) service!: Service
@persist @observable organizations: IOrganization[] = []
// 组织
@persist('list') @observable organizations: IOrganization[] = []
@persist @observable inventories = []
// 库存
@persist('list') @observable inventories = []
// 客户
@observable _customers: { [key: string]: ICustomer[] } = {}
orgs(): IOrganization[] {
return R.map(
......@@ -36,6 +42,27 @@ export default class Organization {
}
/**
* 客户选项
* @param sellerCode
* @param orgCode
* @returns
*/
customers(sellerCode: string, orgCode: string): ICustomer[] {
const customers = this._customers[`${sellerCode}_${orgCode}`] || []
return R.map(
R.applySpec({
label: R.prop('customerName'),
value: R.prop('customerCode'),
}) as any,
)(customers as any) as any
}
@action
resetCustomers() {
this._customers = {}
}
/**
* @description: 获取组织
* @return {*}
*/
......@@ -44,4 +71,14 @@ export default class Organization {
this.organizations = res.data.organizations
this.inventories = res.data.inventories
})
/**
* @description: 根据组织和销售员获取客户
* @param {*} function
* @return {*}
*/
getCustomers = flow(function* (this: Organization, sellerCode: string, orgCode: string) {
const res = yield this.service.getCustomers({ orgCode, sellerCode })
this._customers[`${sellerCode}_${orgCode}`] = res.data.customers
})
}
......@@ -25,7 +25,7 @@ export default class System {
@inject(TYPES.Service)
private service!: Service
@persist @observable sysProfiles = {
@persist('map') @observable sysProfiles = {
// APP借货仓库显隐及是否必填
OBS_MOBILE_BOR_WARE_REQUIRED: '00',
// APP消耗是否显示部门权限
......
/*
* @FilePath: /BoneHouse_Business_APP/src/stores/user.ts
* @Author: peii
* @Date: 2021-05-07 18:48:00
* @Vision: 1.0
* @Description: 用户仓库
*
* @Revision:
*
*/
// @ts-nocheck
import { observable, action, flow, runInAction } from 'mobx'
import { persist } from 'mobx-persist'
import { injectable, inject } from 'inversify'
import { TYPES } from '../inversify/types'
import Service from '../services/service'
import Store from './store'
import SysStore from './system'
import { IDepartment, IInventory } from 'bonehouse'
@injectable()
......@@ -14,6 +26,9 @@ export default class UserStore {
@inject(TYPES.Store)
store!: Store
@inject(TYPES.SysStore)
sysStore!: SysStore
// 登录信息
@persist @observable private username: string = ''
@persist @observable private password: string = ''
......@@ -66,5 +81,6 @@ export default class UserStore {
this.gender = res.gender
this.store.setToken(res.accessToken)
this.store.setFunctions(res.functions)
this.sysStore.getSysProfile()
})
}
......@@ -33,6 +33,13 @@ export const getStyles = (styles: any, ...cls: any[]) => {
if (R.type(key) === 'Array') {
return getStyles(styles, ...key)
}
if (R.type(key) === 'Object') {
const keys = R.compose(
R.filter(k => key[k]),
R.keys,
)(key)
return getStyles(styles, ...keys)
}
return [styles[key]]
}),
)(cls)
......@@ -137,3 +144,38 @@ export const toBinaryNumber = (n: number | string, len = 2): string => {
while (R.length(binN) < len) binN = '0' + binN
return binN
}
/**
* @description: form表单是否必需
* @param {Object}
* @return {Boolean}
*/
export const isRequired = R.compose(R.find(R.propEq('required', true)), R.propOr([], 'rules'))
/**
* @description: 防抖函数
* @param {Function} fn
* @param {*} wait
* @return {*}
*/
export const debounce = (fn: Function, wait = 300): Function => {
let timer = null
return function () {
const context = this
let args = arguments
clearTimeout(timer)
timer = setTimeout(() => {
fn.call(context, ...args)
}, wait)
}
}
/**
* 获取FormItem
* @param items
* @param fieldName
* @returns
*/
export const getFormItem = (items: IFormField[], fieldName: string) =>
R.find(R.propEq('field', fieldName), items)
......@@ -45,17 +45,106 @@ declare module 'bonehouse' {
export type IFormField = {
field: string
name: string
label: string
type: EnumType
dateMode: 'date' | 'datetime'
options?: IOption[]
arrow?: boolean
placeholder?: string
rules?: IRule[]
disabled?: boolean
refrence?: string[]
multiline?: boolean
}
export type IOrganization = {
orgName: string
orgCode: string
}
export type IShipToSite = {
shipToSiteCode: string
shipToSiteName: string
}
export type IBillToSite = {
billToSiteCode: string
billToSiteName: string
shipToSites: IShipToSite[]
}
export type ICustomer = {
customerCode: string
customerName: string
billToSites: IBillToSite[]
}
export type ISurgeryCollectHeader = {
address: string
approvedDate: string
billToSiteCode: string
billToSiteId: string
billToSiteName: string
businessManagerCode: string
businessManagerName: string
collectConfirmor: string
collectConfirmorCode: string
collectConfirmorName: string
collectNumber: string
collectedDate: string
collector: string
collectorCode: string
collectorName: string
combinedAddress: string
consumeOrderAutoShip: string
consumeOrderTypeCode: string
consumeOrderTypeName: string
createBy: string
createTime: string
customerCode: string
customerId: string
customerName: string
deliveryTerms: string
deliverymanCode: string
deliverymanName: string
departmentCode: string
departmentName: string
destinationCode: string
destinationName: string
doctorName: string
headerStatus: string
headerStatusName: string
lastProcess: string
orderCurrency: string
orderTypeCode: string
orderTypeId: string
orderTypeName: string
orgCode: string
orgId: string
orgName: string
paymentControl: string
paymentMethod: string
paymentTerms: string
salOrderTypeCode: string
salOrderTypeName: string
selfCollectRequiredConfirmation: string
selfReturnRequiredConfirmation: string
sellerCode: string
sellerId: string
sellerName: string
shipToSiteCode: string
shipToSiteId: string
shipToSiteName: string
shippingMethod: string
shippingMethodCode: string
shippingMethodName: string
sourceCode: string
surgeryCollectNumber: string
surgeryDate: string
surgeryFollowerCode: string
surgeryFollowerName: string
surgeryName: string
updateBy: string
updateTime: string
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment