Merge branch 'feature/ECX-7316' into 'master'

Feature/ecx 7316

See merge request huiqi/ecshopx-vshop!82
This commit is contained in:
马倩
2025-09-29 18:32:25 +08:00
11 changed files with 218 additions and 43 deletions

View File

@@ -15,7 +15,8 @@ function SpCheckboxNew(props) {
const [isChecked, setChecked] = useState(checked)
const onChangeCheckbox = () => {
const onChangeCheckbox = (e) => {
e.stopPropagation()
if (disabled) return
setChecked(!isChecked)
onChange && onChange(!isChecked)
@@ -23,7 +24,7 @@ function SpCheckboxNew(props) {
useEffect(() => {
setChecked(props.checked)
})
}, [])
return (
<View

View File

@@ -14,4 +14,4 @@ export const SG_ROUTER_PARAMS = 'routerParams' // 路由参数缓存
export const SG_GUIDE_PARAMS = 'guideParams' // 导购参数缓存
export const SG_GUIDE_PARAMS_UPDATETIME = 'guideParams_updatetime' // 导购参数缓存时间戳
export const SG_GUIDE_PARAMS_EXPRESSTIME = 'guideParams_expresstime' // 店铺参数保存时间
export const SG_CHECK_STORE_RULE = 'check_store_rule' // 检查店铺进店规则
export const SG_CHECK_STORE_RULE = 'check_store_rule' // 检查店铺进店规则

View File

@@ -120,6 +120,7 @@ export const SHOP_INFO = {
}
export const AFTER_TRADE = {
aftersalesAddress: 'aftersales_address',
aftersalesBn: 'aftersales_bn',
aftersalesStatus: 'aftersales_status',
createdTime: ({ create_time }) => formatDateTime(create_time * 1000),
@@ -141,6 +142,7 @@ export const AFTER_TRADE = {
refundFee: ({ refund_fee }) => refund_fee / 100,
userId: 'user_id',
freight: ({ freight }) => freight / 100,
progress: 'progress',
}
export const TRADE_AFTER_SALES_ITEM = {
@@ -156,8 +158,8 @@ export const TRADE_AFTER_SALES_ITEM = {
pic: ({ orderItem }) => orderItem.pic,
itemName: ({ orderItem }) => orderItem.item_name,
itemSpecDesc: ({ orderItem }) => orderItem.item_spec_desc,
num: ({ orderItem }) => orderItem.refundNum,
price: ({ orderItem }) => orderItem.total_fee / 100,
num: 'num',
price: ({ refund_fee, num }) => (refund_fee / num) / 100,
isPrescription: ({ orderItem }) => orderItem.is_prescription,
})
},
@@ -170,8 +172,9 @@ export const TRADE_AFTER_SALES_ITEM = {
refundFee: ({ refund_fee }) => refund_fee / 100,
refund_info: ({ refund_info }) => {
return pickBy(refund_info, {
refundFee: ({ refunded_fee }) => refunded_fee / 100,
refundFee: ({ refund_fee }) => refund_fee / 100,
refundPoint: 'refund_point',
freight: ({ freight }) => freight / 100
})
},
refundPoint: 'refund_point',

View File

@@ -195,7 +195,7 @@ function TradeAfterSaleDetail(props) {
</View>
<View className='goods-info-bd'>
<View>{item.itemSpecDesc && <Text className='sku-info'>{`${item.itemSpecDesc}`}</Text>}</View>
<View><SpPrice size={28} value={item.price / item.num} /> x <Text className='num'>{item.num}</Text></View>
<View><SpPrice size={28} value={item.price} /> x <Text className='num'>{item.num}</Text></View>
</View>
</View>
</View>

View File

@@ -206,7 +206,7 @@ function TradeAfterSaleDetail(props) {
</View>
<View className='goods-info-bd'>
<View>{item.itemSpecDesc && <Text className='sku-info'>{`${item.itemSpecDesc}`}</Text>}</View>
<View><SpPrice size={28} value={item.price / item.num} /> x <Text className='num'>{item.num}</Text></View>
<View><SpPrice size={28} value={item.price} /> x <Text className='num'>{item.num}</Text></View>
</View>
</View>
</View>
@@ -224,9 +224,9 @@ function TradeAfterSaleDetail(props) {
<SpCell title='退积分' value={<SpPrice value={info?.refund_info?.refundPoint || info?.refundPoint} />}></SpCell>
</View>
{
info?.refund_freight_amount > 0 &&
info?.refund_info?.freight > 0 &&
<View className='refund-point'>
<SpCell title='退运费' value={info?.refund_freight_amount}></SpCell>
<SpCell title='退运费' value={info?.refund_info?.freight}></SpCell>
</View>
}
</View>

View File

@@ -4,7 +4,7 @@ import Taro, { useRouter } from '@tarojs/taro'
import { View, ScrollView } from '@tarojs/components'
import { AtButton } from 'taro-ui'
import { useImmer } from 'use-immer'
import { SpPage, SpTagBar, SpScrollView } from '@/components'
import { SpPage, SpTagBar, SpScrollView, SpSearchBar } from '@/components'
import api from '@/api'
import doc from '@/doc'
import { pickBy } from '@/utils'
@@ -23,13 +23,22 @@ const initialState = {
status: '0',
typeVal:'0',
tradeList: [],
refresherTriggered: false
refresherTriggered: false,
order_id: '',
selectAftersn: []
}
function TradeAfterSaleList(props) {
const [state, setState] = useImmer(initialState)
const { tradeStatus, status, tradeList, refresherTriggered, typeVal } = state
const {
tradeStatus,
status,
tradeList,
refresherTriggered,
typeVal,
order_id,
selectAftersn
} = state
const tradeRef = useRef()
const router = useRouter()
useEffect(() => {
// 撤销售后事件
@@ -57,7 +66,8 @@ function TradeAfterSaleList(props) {
page: pageIndex,
pageSize,
order_type: 'normal',
aftersales_status: status
aftersales_status: status,
order_id
}
// params.order_class = typeVal == '1' ? 'employee_purchase' : 'normal'
const { list, total_count } = await api.aftersales.list(params)
@@ -72,6 +82,7 @@ function TradeAfterSaleList(props) {
const onChangeTradeState = (e) => {
setState((draft) => {
draft.status = tradeStatus[e].value
draft.selectAftersn = []
})
}
@@ -90,8 +101,78 @@ function TradeAfterSaleList(props) {
})
}
const handleOnChange = (val) => {
setState((v) => {
v.order_id = val
})
}
const handleOnClear = () => {
setState((v) => {
v.order_id = ''
v.tradeList = []
})
tradeRef.current.reset()
}
const handleConfirm = async (val) => {
setState((v) => {
v.tradeList = []
v.order_id = val
})
await tradeRef.current.reset()
}
const onSelect = (sn) => {
let list = [...selectAftersn]
const index = list.indexOf(sn)
if (index > -1) {
list.splice(index, 1)
} else {
list.push(sn)
}
setState((draft) => {
draft.selectAftersn = list
})
}
const onSubmit = () => {
console.log(tradeList,selectAftersn)
const fliterList = tradeList?.filter((item) => selectAftersn.includes(item.aftersalesBn) && item.aftersalesAddress)
debugger
const address = fliterList[0].aftersalesAddress
Taro.setStorageSync('moreAftersalesBn', {
aftersalesBn: selectAftersn,
address
})
Taro.navigateTo({ url: `/subpages/trade/logistics-info?type=more` })
}
const renderFooter = () => {
if (status != '1') return null
return (
<View className='btn-wrap'>
<AtButton circle type='primary' disabled={selectAftersn.length === 0} onClick={onSubmit}>批量处理</AtButton>
</View>
)
}
return (
<SpPage className='page-trade-aftersale-list'>
<SpPage
className='page-trade-aftersale-list'
renderFooter={renderFooter()}
>
<View className='search-bar-container'>
<SpSearchBar
keyword={order_id}
placeholder='请输入订单号'
showDailog={false}
onChange={handleOnChange}
onClear={handleOnClear}
onCancel={handleOnClear}
onConfirm={handleConfirm}
/>
</View>
<CompTrackType value={typeVal} onChange={onChangeTradeType} />
<SpTagBar list={tradeStatus} value={status} onChange={onChangeTradeState} />
<ScrollView
@@ -111,7 +192,12 @@ function TradeAfterSaleList(props) {
>
{tradeList.map((item, index) => (
<View className='trade-item-wrap' key={index}>
<CompAfterTradeItem info={item} />
<CompAfterTradeItem
info={item}
selectAftersn={selectAftersn}
isShowChecked={status == '1' && item.progress == 1}
onSelect={onSelect}
/>
</View>
))}
</SpScrollView>

View File

@@ -1,6 +1,10 @@
$tag-bar-height: 90px;
.page-trade-aftersale-list {
.search-bar-container {
padding: 20px;
background: #fff;
}
.sp-tag-bar {
background: #f5f5f5;
height: $tag-bar-height;
@@ -23,5 +27,11 @@ $tag-bar-height: 90px;
}
.comp-tradeitem .shop-info .shop-name {
width: 400px;
}
}
.btn-wrap {
width: 80%;
// display: flex;
// justify-content: end;
margin: 20px auto;
}
}

View File

@@ -3,17 +3,17 @@ import { useSelector } from 'react-redux'
import Taro from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { AtButton } from 'taro-ui'
import { SpImage, SpPrice, SpTradeItem } from '@/components'
import { SpImage, SpPrice, SpTradeItem, SpCheckboxNew } from '@/components'
import { VERSION_STANDARD } from '@/utils'
import { AFTER_SALE_STATUS } from '@/consts'
import './comp-aftertrade-item.scss'
function CompTradeItem(props) {
const { info } = props
const { info, isShowChecked = false, selectAftersn = [], onSelect = () => {} } = props
if (!info) {
return null
}
const { aftersalesBn, distributorInfo, orderId, createdTime, aftersalesStatus, items, orderStatus, refundFee, orderClass = 'normal', point, distributorId ,freight} = info
const { aftersalesBn, distributorInfo, createdTime, aftersalesStatus, items, orderStatus, refundFee, orderClass = 'normal', point, distributorId ,freight} = info
// const { pointName } = useSelector((state) => state.sys)
@@ -40,22 +40,20 @@ function CompTradeItem(props) {
<View className='comp-tradeitem'>
<View className='trade-item-hd' onClick={onViewTradeDetail}>
<View>
{/* <View className='shop-info' onClick={onViewStorePage}>
<SpImage src={distributorInfo?.logo} width={100} height={100} />
<View className='shop-name'>{distributorInfo?.name}{!VERSION_STANDARD && <Text className='iconfont icon-qianwang-01'></Text>}</View>
</View> */}
<View className='shop-info' onClick={onViewStorePage}>
{/* <SpImage src={distributorInfo?.logo} width={100} height={100} /> */}
{isShowChecked && <SpCheckboxNew checked={selectAftersn.includes(aftersalesBn)} style={{ marginRight: 24 }} onChange={() => onSelect(aftersalesBn)} />}
<View className={`shop-name ${isShowChecked ? '' : 'nochecked'}`}>{distributorInfo?.name}{!VERSION_STANDARD && <Text className='iconfont icon-qianwang-01'></Text>}</View>
</View>
<View className='trade-no'>{`退款单号: ${aftersalesBn}`}</View>
<View className='trade-time'>{`申请时间: ${createdTime}`}</View>
</View>
<View className='trade-state'>{AFTER_SALE_STATUS[aftersalesStatus]}</View>
</View>
<View className='trade-item-bd' onClick={onViewTradeDetail}>
{items.map((good) => (
<SpTradeItem info={{
...good,
}} />
{items.map((good, goodIns) => (
<SpTradeItem key={goodIns} info={{ ...good }} />
))}
<View className='trade-total'>
<View className='delivery'></View>
{/* {
@@ -74,10 +72,6 @@ function CompTradeItem(props) {
}
</View>
</View>
<View className='trade-item-ft'>
</View>
</View>
)
}

View File

@@ -68,18 +68,20 @@
.shop-info {
display: flex;
align-items: flex-start;
align-items: center;
.sp-image {
border-radius: 16px;
overflow: hidden;
}
.nochecked {
margin-left: 0px !important;
}
.shop-name {
font-size: 30px;
color: #111;
font-weight: bold;
margin-left: 24px;
display: flex;
align-items: center;

View File

@@ -4,23 +4,26 @@ import Taro, { getCurrentInstance } from '@tarojs/taro'
import { AtButton } from 'taro-ui'
import api from '@/api'
import S from '@/spx'
import { View, Picker } from '@tarojs/components'
import { View, Picker, Text } from '@tarojs/components'
import { LOGISTICS_CODE } from '@/consts'
import { SpPage, SpCell, SpInput as AtInput, SpToast } from '@/components'
import { showToast } from '@/utils'
import { showToast, copyText } from '@/utils'
import './logistics-info.scss'
const initialState = {
logi_no: '',
corpIndex: 0,
expressList: []
expressList: [],
afterInfo: null
}
function TradeLogisticsInfo(props) {
const $instance = getCurrentInstance()
const [state, setState] = useImmer(initialState)
const { logi_no, expressList, corpIndex } = state
const { logi_no, expressList, corpIndex, afterInfo } = state
const { item_id, order_id, aftersales_bn, type = 'single' } = $instance.router.params
useEffect(() => {
const aftersInfo = Taro.getStorageSync('moreAftersalesBn') || null
const _expressList = Object.keys(LOGISTICS_CODE).map((key) => {
return {
name: LOGISTICS_CODE[key],
@@ -29,11 +32,15 @@ function TradeLogisticsInfo(props) {
})
setState((draft) => {
draft.expressList = _expressList
draft.afterInfo = aftersInfo
})
return () => {
Taro.removeStorageSync('moreAftersalesBn')
}
}, [])
const onSubmit = async () => {
const { item_id, order_id, aftersales_bn } = $instance.router.params
const aftersInfo = Taro.getStorageSync('moreAftersalesBn') || null
const corp_code = expressList[corpIndex]?.code
if (!corp_code) {
showToast('请填写物流公司')
@@ -47,10 +54,11 @@ function TradeLogisticsInfo(props) {
await api.aftersales.sendback({
item_id,
order_id,
aftersales_bn,
aftersales_bn: aftersales_bn,
logi_no,
corp_code,
showError:false
showError:false,
aftersales_data: aftersInfo?.aftersalesBn
})
showToast('操作成功')
setTimeout(() => {
@@ -87,6 +95,21 @@ function TradeLogisticsInfo(props) {
</View>
}
>
{type == 'more' && <View className='after-address'>
<SpCell title='回寄信息:'>
<>
<View className='contact-mobile'>
<Text className='contact'>{afterInfo?.address?.aftersales_contact}</Text>
<Text className='mobile'>{afterInfo?.address?.aftersales_mobile}</Text>
</View>
<View className='btn-copy' circle size='small' onClick={() => {
copyText(`${afterInfo?.address?.aftersales_contact} ${afterInfo?.address?.aftersales_mobile}\n${afterInfo?.address?.aftersales_address}`)
}}>复制</View>
</>
</SpCell>
<View className='address-detail'>{afterInfo?.address?.aftersales_address}</View>
</View>
}
<SpCell
className='logistics-company'
title='物流公司'

View File

@@ -1,4 +1,55 @@
.page-trade-logistics-info {
.after-address {
margin: 24px 16px 0;
padding: 24px 0;
border-radius: 16px;
background: #fff;
overflow: hidden;
.sp-cell {
align-items: flex-start;
padding: 0 30px;
}
.sp-cell__title {
font-size: 30px;
font-weight: 600;
color: #222;
}
.sp-cell__bd {
display: flex;
justify-content: space-between;
align-items: flex-start;
.contact {
font-size: 30px;
font-weight: 600;
color: #222;
word-break: break-all;
}
.mobile {
font-size: 30px;
font-weight: 600;
color: #999;
// margin-left: 20px;
}
}
.sp-cell__ft,
.sp-cell__link {
display: none;
}
.address-detail {
color: #222222;
padding: 0 30px;
margin-top: 10px;
}
.btn-container {
display: flex;
justify-content: flex-end;
margin-top: 32px;
padding: 0 30px;
}
.btn-logistics {
width: 210px;
}
}
.btn-wrap {
padding: 20px;
}
@@ -28,4 +79,9 @@
.sp-input{
text-align: right;
}
.address-detail {
padding: 24px 32px;
padding-top: 0;
background: #fff;
}
}