Merge branch 'feature/ECX-3832' into 'develop'

【小程序】收货地址改造

See merge request ecshopx/ecshopx-vshop!54
This commit is contained in:
陆凯杰
2022-12-08 15:35:39 +08:00
4 changed files with 409 additions and 330 deletions

View File

@@ -1,8 +1,9 @@
import React, { Component } from 'react'
import React, { useEffect } from 'react'
import { useImmer } from 'use-immer'
import Taro, { getCurrentInstance, getCurrentPages } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
// import AddressList from '@/components/new-address/address'
import { connect } from 'react-redux'
import { AtButton } from 'taro-ui'
import { useDispatch, useSelector } from 'react-redux'
import { SpToast, SpCell, SpNavBar, SpPage } from '@/components'
import S from '@/spx'
import api from '@/api'
@@ -11,37 +12,32 @@ import './address.scss'
const ADDRESS_ID = 'address_id'
@connect(
({ user, sys }) => ({
address: user.address,
colors: sys
}),
(dispatch) => ({
updateChooseAddress: (address) =>
dispatch({ type: 'user/updateChooseAddress', payload: address })
})
)
export default class AddressIndex extends Component {
$instance = getCurrentInstance()
constructor(props) {
super(props)
const initialState = {
list: [],
isPicker: false,
selectedId: null
}
this.state = {
list: [],
isPicker: false,
selectedId: null
}
function AddressIndex(props) {
const $instance = getCurrentInstance()
const [state, setState] = useImmer(initialState)
const colors = useSelector((state) => state.sys)
const { address } = useSelector((state) => state.user)
const dispatch = useDispatch()
useEffect(() => {
fetch()
}, [address])
const updateChooseAddress = (address) => {
dispatch({ type: 'user/updateChooseAddress', payload: address })
}
componentDidShow() {
this.fetch()
}
async fetch(isDelete = false) {
const { isPicker, receipt_type = '', city = '' } = this.$instance.router.params
const fetch = async (isDelete = false) => {
const { isPicker, receipt_type = '', city = '' } = $instance.router.params
if (isPicker) {
this.setState({
isPicker: true
setState(draft=>{
draft.isPicker = true
})
}
Taro.showLoading({ title: '' })
@@ -57,45 +53,48 @@ export default class AddressIndex extends Component {
.sort((first) => (first.disabled ? 1 : -1))
}
let selectedId = null
if (this.props.address) {
selectedId = this.props.address[ADDRESS_ID]
if (address) {
selectedId = address[ADDRESS_ID]
} else {
selectedId = list.find((addr) => addr.is_def > 0) || null
}
this.setState({
list: newList,
selectedId
setState(draft=>{
draft.list = newList,
draft.selectedId = selectedId
})
}
handleClickChecked = (item) => {
this.setState({
selectedId: item[ADDRESS_ID]
const handleClickChecked = (e, item) => {
setState(draft=>{
draft.selectedId = item[ADDRESS_ID]
})
this.props.updateChooseAddress(item)
updateChooseAddress(item)
setTimeout(() => {
Taro.navigateBack()
}, 700)
}
handleChangeDefault = async (item) => {
item.is_def = 1
const handleChangeDefault = async (e, item) => {
const nItem = JSON.parse(JSON.stringify(item))
nItem.is_def = 1
try {
await api.member.addressCreateOrUpdate(item)
if (item.address_id) {
await api.member.addressCreateOrUpdate(nItem)
if (item?.address_id) {
S.toast('修改成功')
}
setTimeout(() => {
this.fetch()
fetch()
}, 700)
} catch (error) {
return false
}
}
handleClickToEdit = (item) => {
if (item.address_id) {
const handleClickToEdit = (e, item) => {
if (item?.address_id) {
Taro.navigateTo({
url: `/marketing/pages/member/edit-address?address_id=${item.address_id}`
})
@@ -106,30 +105,44 @@ export default class AddressIndex extends Component {
}
}
handleDelete = async (item) => {
const { selectedId } = this.state
const handleDelete = async (e, item) => {
const res = await Taro.showModal({
title: '提示',
content: `确定要删除该地址吗?`,
showCancel: true,
cancel: '取消',
cancelText: '拒绝',
confirmText: '同意',
confirmColor: colors.colorPrimary
})
if (!res.confirm) return
const { selectedId } = state
await api.member.addressDelete(item.address_id)
S.toast('删除成功')
if (selectedId === item.address_id) {
this.props.updateChooseAddress(null)
updateChooseAddress(null)
}
setTimeout(() => {
this.fetch(true)
fetch(true)
}, 700)
}
wxAddress = () => {
const wxAddress = () => {
Taro.navigateTo({
url: `/marketing/pages/member/edit-address?isWechatAddress=true`
})
}
crmAddress = () => {
const crmAddress = () => {
Taro.navigateTo({
url: `/marketing/pages/member/crm-address-list?isCrmAddress=true`
})
}
handleClickLeftIcon = () => {
const handleClickLeftIcon = () => {
let CHECKOUT_PAGE = '/pages/cart/espier-checkout'
const pages = getCurrentPages()
if (pages.length > 1) {
@@ -141,114 +154,127 @@ export default class AddressIndex extends Component {
Taro.navigateBack()
}
render() {
const { colors } = this.props
const { selectedId, isPicker, list, is_open_crmAddress } = this.state
return (
<SpPage className='page-address-index'>
<View>
{process.env.TARO_ENV === 'weapp' ? (
<SpCell
isLink
iconPrefix='sp-icon'
icon='weixin'
title='获取微信收货地址'
onClick={this.wxAddress.bind(this)}
/>
) : (
<SpNavBar
title='收货地址'
leftIconType='chevron-left'
fixed='true'
onClickLeftIcon={this.handleClickLeftIcon}
/>
)}
const { selectedId, isPicker, list } = state
<View className='member-address-list'>
{list.map((item) => {
return (
<View
key={item[ADDRESS_ID]}
className={`address-item ${item.disabled ? 'disabled' : ''}`}
>
{isPicker && !item.disabled && (
<View
className='address-item__check'
onClick={this.handleClickChecked.bind(this, item)}
>
{item[ADDRESS_ID] === selectedId ? (
<Text
className='iconfont icon-check address-item__checked'
style={{ color: colors.colorPrimary }}
></Text>
) : (
<Text
className='address-item__unchecked'
style={{ borderColor: colors.colorPrimary }}
>
{' '}
</Text>
)}
</View>
)}
return (
<SpPage
className='page-address-index'
renderFooter={
<View className='btn-wrap'>
<AtButton circle type='primary' onClick={handleClickToEdit}>
+新增地址
</AtButton>
</View>
}
>
<View>
{process.env.TARO_ENV === 'weapp' ? (
<SpCell
isLink
iconPrefix='sp-icon'
icon='weixin'
title='获取微信收货地址'
onClick={wxAddress}
/>
) : (
<SpNavBar
title='收货地址'
leftIconType='chevron-left'
fixed='true'
onClickLeftIcon={handleClickLeftIcon}
/>
)}
<View className='address-item__content'>
<View className='address-item__title'>
<Text className='address-item__info'>{item.username}</Text>
<Text className='address-item__info'>{item.telephone}</Text>
</View>
<View className='member-address-list'>
{list?.map((item) => {
return (
<View
key={item[ADDRESS_ID]}
className={`address-item ${item.disabled ? 'disabled' : ''}`}
>
<View className='address-item__content'>
<View className='address-item__title'>
<Text className='address-item__info'>{item.username}</Text>
<Text className='address-item__info_tel'>{item.telephone}</Text>
</View>
<View className='address-item__detail_box'>
<View className='address-item__detail'>
{item.province}
{item.city}
{item.county}
{item.adrdetail}
</View>
<View className='address-item__footer'>
<View
className='address-item__footer_default'
onClick={this.handleChangeDefault.bind(this, item)}
>
{item.is_def ? (
{isPicker && !item.disabled && (
<View className='address-item__check' onClick={e=>handleClickChecked(e,item)}>
{item[ADDRESS_ID] === selectedId ? (
<Text
className='iconfont icon-check address-item__checked'
style={{ color: colors.colorPrimary }}
></Text>
) : (
<Text
className='address-item__unchecked'
style={{ borderColor: colors.colorPrimary }}
>
{' '}
</Text>
)}
</View>
)}
</View>
<View className='address-item__footer'>
<View
className='address-item__footer_default'
onClick={(e) => handleChangeDefault(e, item)}
>
{item.is_def ? (
<>
<Text
className='iconfont icon-check default__icon default__checked'
style={{ color: colors.colorPrimary }}
>
{' '}
</Text>
) : (
<Text className='iconfont icon-check default__icon'> </Text>
)}
<Text className='default-text'>设为默认</Text>
<Text className='default-text'>已设为默认</Text>
</>
) : (
<>
<Text
className='address-item__unchecked'
style={{ borderColor: colors.colorPrimary }}
>
{' '}
</Text>
<Text className='default-text'>设为默认</Text>
</>
)}
</View>
<View className='address-item__footer_edit'>
<View className='footer-text' onClick={(e) => handleDelete(e, item)}>
<Text className='iconfont icon-trashCan footer-icon'> </Text>
<Text>删除</Text>
</View>
<View className='address-item__footer_edit'>
<View
className='footer-text'
onClick={this.handleClickToEdit.bind(this, item)}
>
<Text className='iconfont icon-edit footer-icon'> </Text>
<Text>编辑</Text>
</View>
<View className='footer-text' onClick={this.handleDelete.bind(this, item)}>
<Text className='iconfont icon-trashCan footer-icon'> </Text>
<Text>删除</Text>
</View>
<View className='footer-text' onClick={(e) => handleClickToEdit(e, item)}>
<Text className='iconfont icon-edit footer-icon'> </Text>
<Text>编辑</Text>
</View>
</View>
</View>
</View>
)
})}
</View>
<View
className='member-address-add'
style={'background: ' + colors.colorPrimary}
onClick={this.handleClickToEdit.bind(this)}
>
添加新地址
</View>
<SpToast />
</View>
)
})}
</View>
</SpPage>
)
}
<SpToast />
</View>
</SpPage>
)
}
AddressIndex.options = {
addGlobalClass: true
}
export default AddressIndex

View File

@@ -2,16 +2,33 @@
.page-address-index {
background: #f5f5f5;
padding: 20rpx 20rpx 0 20rpx;
.btn-wrap{
padding: 20rpx 40rpx;
}
.member-address-list {
padding: 53px 30px 120px 40px;
margin-top: 20rpx;
// padding: 53px 30px 120px 40px;
/* #ifdef h5 */
padding: 48px 30px;
/* #endif */
.address-item__content{
.address-item__detail_box{
display: flex;
justify-content: space-between;
border-bottom: 1px solid #e6e6e6;
.address-item__unchecked{
margin-top: 0;
}
}
}
}
.address-item {
display: flex;
justify-content: space-between;
margin-bottom: 48px;
margin-bottom: 30px;
&.disabled {
opacity: 0.6;
}
@@ -45,11 +62,16 @@
}
&__info {
margin-right: 20px;
&_tel{
font-size: 19px;
color: #9d9d9d;
}
}
&__detail {
@include multi-ellipsis(2);
margin: 0 40rpx 17rpx 0;
color: #0d0d0d;
padding-bottom: 14px;
border-bottom: 1px solid #e6e6e6;
}
}
.address-item__footer {
@@ -60,6 +82,9 @@
&_default {
display: flex;
font-size: 23px;
.address-item__unchecked{
margin: 0 14rpx 0 0;
}
}
.default__icon {
margin-right: 14px;

View File

@@ -1,97 +1,86 @@
import React, { Component } from 'react'
import React, { useEffect } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { useImmer } from 'use-immer'
import Taro, { getCurrentInstance } from '@tarojs/taro'
// import EditAddress from '@/components/new-address/edit-address'
import { View, Switch, Text, Picker, Button } from '@tarojs/components'
import { AtForm, AtInput } from 'taro-ui'
import { connect } from 'react-redux'
import { SpCell, SpToast, SpNavBar, SpAddress } from '@/components'
import { View, Switch, Text, Button } from '@tarojs/components'
import { AtInput, AtButton } from 'taro-ui'
import { SpCell, SpNavBar, SpAddress } from '@/components'
import api from '@/api'
import { isWxWeb } from '@/utils'
import S from '@/spx'
import { useNavigation } from '@/hooks'
import './edit-address.scss'
//转换属性
const traverseData = (data) => {
let item = []
data.forEach((d) => {
let newData = {}
newData.name = d.label
if (d.children) {
newData.subList = traverseData(d.children)
}
item.push(newData)
})
return item
const initialState = {
info: {},
listLength: 0,
areaArray: [[], [], []],
areaIndexArray: [0, 0, 0],
areaData: [],
chooseValue: ['', '', ''],
isOpened: false
}
@connect(({ colors }) => ({
colors: colors.current
}),(dispatch) => ({
updateChooseAddress: (address) =>
function AddressIndex(props) {
const $instance = getCurrentInstance()
const [state, setState] = useImmer(initialState)
const colors = useSelector((state) => state.colors.current)
const dispatch = useDispatch()
const { setNavigationBarTitle } = useNavigation()
const updateChooseAddress = (address) => {
dispatch({ type: 'user/updateChooseAddress', payload: address })
}))
export default class AddressIndex extends Component {
$instance = getCurrentInstance()
constructor(props) {
super(props)
this.state = {
info: {},
listLength: 0,
areaArray: [[], [], []],
areaIndexArray: [0, 0, 0],
areaData: [],
chooseValue: ['北京市', '北京市', '昌平区'],
isOpened: false
// ubmitLoading: false,
}
}
componentDidMount() {
this.fetchAddressList()
this.fetch()
useEffect(() => {
fetchAddressList()
fetch()
setNavigationBarTitle(initNavigationBarTitle())
},[])
const initNavigationBarTitle = () => {
return $instance.router?.params?.address_id ? '编辑地址' : '新增地址'
}
fetchAddressList = async () => {
const fetchAddressList = async () => {
const areaData = await api.member.areaList()
this.setState({
areaData
setState((draft) => {
draft.areaData = areaData
})
}
async fetch() {
const fetch = async () => {
Taro.showLoading({ title: '' })
const { list } = await api.member.addressList()
this.setState({
listLength: list.length
setState((draft) => {
draft.listLength = list?.length
})
list.map((a_item) => {
if (a_item.address_id === this.$instance.router.params.address_id) {
this.setState({
info: a_item,
chooseValue: [a_item.province, a_item.city, a_item.county]
if (a_item.address_id === $instance.router?.params?.address_id) {
setState((draft) => {
;(draft.info = a_item),
(draft.chooseValue = [a_item.province, a_item.city, a_item.county])
})
}
})
if (this.$instance.router.params.isWechatAddress) {
if ($instance.router?.params?.isWechatAddress) {
try {
const resAddress = await Taro.chooseAddress()
const query = {
province: resAddress.provinceName,
city: resAddress.cityName,
county: resAddress.countyName,
adrdetail: resAddress.detailInfo,
province: resAddress?.provinceName,
city: resAddress?.cityName,
county: resAddress?.countyName,
adrdetail: resAddress?.detailInfo,
is_def: 0,
postalCode: resAddress.postalCode,
telephone: resAddress.telNumber,
username: resAddress.userName
postalCode: resAddress?.postalCode,
telephone: resAddress?.telNumber,
username: resAddress?.userName
}
this.setState({
info: query,
chooseValue: [query.province, query.city, query.county]
setState((draft) => {
;(draft.info = query), (draft.chooseValue = [query.province, query.city, query.county])
})
} catch (err) {
console.error(err)
@@ -102,56 +91,49 @@ export default class AddressIndex extends Component {
Taro.hideLoading()
}
onPickerClick = () => {
this.setState(
{
isOpened: true
}
)
}
handleClickClose = () => {
this.setState(
{
isOpened: false
}
)
}
onPickerChange = (selectValue) => {
// console.log(selectValue,'selectValue1111');
const chooseValue = [selectValue[0].label,selectValue[1].label,selectValue[2].label]
this.setState({
chooseValue
const onPickerClick = () => {
setState((draft) => {
draft.isOpened = true
})
}
handleChange = (name, val) => {
const { info } = this.state
info[name] = val
this.setState({
info
const handleClickClose = () => {
setState((draft) => {
draft.isOpened = false
})
}
handleDefChange = (e) => {
const onPickerChange = (selectValue) => {
const chooseValue = [selectValue[0]?.label, selectValue[1]?.label, selectValue[2]?.label]
setState((draft) => {
draft.chooseValue = chooseValue
})
}
const handleChange = (name, val) => {
const nInfo = JSON.parse(JSON.stringify(state.info || {}))
nInfo[name] = val
setState((draft) => {
draft.info = nInfo
})
}
const handleDefChange = (e) => {
const info = {
...this.state.info,
...state.info,
is_def: e.detail.value ? 1 : 0
}
this.setState({
info
setState((draft) => {
draft.info = info
})
}
handleSubmit = async (e) => {
const handleSubmit = async (e) => {
const { value } = e.detail || {}
const { chooseValue } = this.state
const { chooseValue } = state
const data = {
...this.state.info,
...state.info,
...value
}
@@ -160,7 +142,7 @@ export default class AddressIndex extends Component {
} else {
data.is_def = '1'
}
if (this.state.listLength === 0) {
if (state.listLength === 0) {
data.is_def = '1'
}
@@ -172,11 +154,9 @@ export default class AddressIndex extends Component {
return S.toast('请输入手机号')
}
// if (!data.province) {
data.province = chooseValue[0]
data.city = chooseValue[1]
data.county = chooseValue[2]
// }
if (!data.adrdetail) {
return S.toast('请输入详细地址')
@@ -191,7 +171,7 @@ export default class AddressIndex extends Component {
} else {
S.toast('创建成功')
}
this.props.updateChooseAddress(data)
updateChooseAddress(data)
setTimeout(() => {
Taro.navigateBack()
}, 700)
@@ -202,94 +182,119 @@ export default class AddressIndex extends Component {
Taro.hideLoading()
}
render() {
const { colors } = this.props
const { info, areaIndexArray, areaArray, chooseValue, isOpened } = this.state
return (
<View className='page-address-edit' style={isWxWeb && { paddingTop: 0 }}>
{/*<EditAddress*/}
{/*address={getCurrentInstance().params.address}*/}
{/*addressID={getCurrentInstance().params.address_id}*/}
{/*/>*/}
<SpNavBar title='编辑地址' leftIconType='chevron-left' fixed='true' />
<AtForm onSubmit={this.handleSubmit}>
<View className='page-address-edit__form'>
const { info, chooseValue, isOpened } = state
return (
<View className='page-address-edit' style={isWxWeb && { paddingTop: 0 }}>
<SpNavBar title={setNavigationBarTitle} leftIconType='chevron-left' fixed='true' />
<View className='page-address-edit__form'>
<SpCell
className='logistics-no'
title='收件人'
value={
<AtInput
title='收件人姓名'
name='username'
value={info.username}
onChange={this.handleChange.bind(this, 'username')}
value={info?.username}
placeholder='收件人姓名'
onChange={(e) => handleChange('username', e)}
/>
}
></SpCell>
<SpCell
className='logistics-no'
title='手机号码'
value={
<AtInput
title='手机号码'
name='telephone'
maxLength={11}
value={info.telephone}
onChange={this.handleChange.bind(this, 'telephone')}
value={info?.telephone}
placeholder='收件人手机号码'
onChange={(e) => handleChange('telephone', e)}
/>
{/* <Picker
mode='multiSelector'
onClick={this.onPickerClick}
onChange={this.onPickerChange}
onColumnChange={this.onColumnChange}
value={areaIndexArray}
range={areaArray}
>
<View className='picker' onClick={this.onPickerClick}>
<View className='picker__title'>所在区域</View>
<Text>{chooseValue.join('') || '选择地区'}</Text>
</View>
</Picker> */}
<View className='picker' onClick={this.onPickerClick}>
<View className='picker__title'>所在区域</View>
<Text>{chooseValue.join('') || '选择地区'}</Text>
}
></SpCell>
<SpCell
className='logistics-no province'
title='所在区域'
value={
<View className='picker' onClick={onPickerClick}>
{chooseValue?.join('') === '' ? (
<Text>选择省//</Text>
) : (
<Text style={{ color: '#000' }}>{chooseValue?.join('/')}</Text>
)}
</View>
<SpAddress isOpened={isOpened} onClose={this.handleClickClose} onChange={this.onPickerChange}/>
}
></SpCell>
<SpAddress isOpened={isOpened} onClose={handleClickClose} onChange={onPickerChange} />
<SpCell
className='logistics-no'
title='详细地址'
value={
<AtInput
title='详细地址'
name='adrdetail'
value={info.adrdetail}
onChange={this.handleChange.bind(this, 'adrdetail')}
value={info?.adrdetail}
placeholder='请填写详细地址(街道、门牌)'
onChange={(e) => handleChange('adrdetail', e)}
/>
<AtInput
title='邮政编码'
name='postalCode'
value={info.postalCode}
onChange={this.handleChange.bind(this, 'postalCode')}
/>
</View>
}
></SpCell>
<View className='sec'>
<SpCell title='设为默认地址'>
<Switch checked={info.is_def} onChange={this.handleDefChange.bind(this)} />
</SpCell>
</View>
<View className='btns'>
{process.env.TARO_ENV === 'weapp' ? (
<Button
type='primary'
// onClick={this.handleSubmit}
formType='submit'
style={`background: ${colors}; border-color: ${colors}`}
>
提交
</Button>
) : (
<Button
type='primary'
// onClick={this.handleSubmit}
formType='submit'
style={`background: ${colors}; border-color: ${colors}`}
>
提交
</Button>
)}
</View>
</AtForm>
<SpToast />
{/* <SpCell
className='logistics-no'
title='邮政编码'
value={
<AtInput
name='postalCode'
value={info.postalCode}
onChange={this.handleChange.bind(this, 'postalCode')}
/>
}
></SpCell> */}
</View>
)
}
<View className='sec'>
<SpCell
title='设为默认地址'
iisLink
value={
<Switch
checked={info?.is_def}
className='def-switch'
onChange={handleDefChange}
color={colors.data[0].primary}
/>
}
></SpCell>
</View>
<View className='btns'>
{/* <AtButton
circle
type='primary'
className='save-btn'
onClick={handleSubmit}
style={`background: ${colors}; border-color: ${colors};border-radius: 25px;`}
>
保存并使用
</AtButton> */}
<Button
type='primary'
onClick={handleSubmit}
className='submit-btn'
style={`background: ${colors.data[0].primary}; border-color: ${colors.data[0].primary};border-radius: 25px;`}
>
保存并使用
</Button>
</View>
</View>
)
}
AddressIndex.options = {
addGlobalClass: true
}
export default AddressIndex

View File

@@ -3,20 +3,24 @@
.page-address-edit {
width: 100%;
overflow-x: hidden;
padding: 24px $edge-size;
box-sizing: border-box;
/* #ifdef h5 */
padding-top: $navigate-height-h5;
/* #endif */
.page-address-edit__form{
padding-top: 0;
}
.sp-cell__ft {
color: #ccc;
}
&__form {
background: #fff;
padding: $edge-size;
margin: 0 0 $edge-margin;
}
.picker {
padding: 16px 0;
margin: 0 0 $edge-size;
display: flex;
justify-content: flex-start;
border-bottom: 1px solid rgba($color-brand-primary, 0.2);
width: 350px;
&__title {
font-size: $font-size;
margin-right: 16px;
@@ -32,4 +36,23 @@
color: #808080;
}
}
.submit-btn{
border-radius: 5px;
font-size: 30px;
}
.logistics-no {
border-bottom:1rpx solid #e6e6e6;
.at-input {
margin-bottom: 0;
width: 350px;
padding: 0;
&:after {
border-color: transparent;
}
&__input {
padding-right: 0;
}
}
}
}