[TBID:ECX-8807] chore: merge master changes for release

This commit is contained in:
moyayu
2026-06-26 14:01:37 +08:00
parent 4f28ca9d31
commit 2579b62b1e
27 changed files with 554 additions and 343 deletions

2
.gitignore vendored
View File

@@ -18,4 +18,4 @@ openspec/
scripts/
.agent/
.codex/
.cursor/commit-summary
.cursor/

View File

@@ -23,6 +23,11 @@ export function cartFastBuyAdd(params) {
return req.post('/operator/cartfastbuyadd', params)
}
// 移动收银选择会员后同步会话POST bodycompany_id 由 req 拦截器注入,与 cartdataadd 一致)
export function memberReady(params) {
return req.post('/operator/member/ready', params)
}
// 扫码加入收银台
export function scanAddToCart(params) {
return req.post('/operator/scancodeAddcart', params)

View File

@@ -47,14 +47,6 @@ function WgtFilm(props) {
})}
style={styleNames(outerStyle)}
>
{base.title && (
<View className='wgt-head'>
<View className='wgt-hd'>
<Text className='wgt-title'>{base.title}</Text>
{base.subtitle && <Text className='wgt-subtitle'>{base.subtitle}</Text>}
</View>
</View>
)}
<View className={classNames('wgt-film__video-wrap', videoWrapClass)}>
<View className='wgt-film__video-inner'>
<Video

View File

@@ -31,12 +31,14 @@
video,
.flim-video {
width: 100%;
height: 100%;
display: block;
}
}
.flim-video {
display: block;
height: 100%;
width: 100%;
}
.taro-video-video{

View File

@@ -129,35 +129,34 @@ export function getGlobalBaseStyle(baseStyle) {
const bgType = baseStyle.bgType
const style = {}
if (baseStyle.paddedt !== undefined && baseStyle.paddedt !== null) {
const v = toDesignPx(baseStyle.paddedt)
if (v !== null) style.paddingTop = Taro.pxTransform(v)
}
if (baseStyle.paddedb !== undefined && baseStyle.paddedb !== null) {
const v = toDesignPx(baseStyle.paddedb)
if (v !== null) style.paddingBottom = Taro.pxTransform(v)
}
if (baseStyle.paddedl !== undefined && baseStyle.paddedl !== null) {
const v = toDesignPx(baseStyle.paddedl)
if (v !== null) style.paddingLeft = Taro.pxTransform(v)
}
if (baseStyle.paddedr !== undefined && baseStyle.paddedr !== null) {
const v = toDesignPx(baseStyle.paddedr)
if (v !== null) style.paddingRight = Taro.pxTransform(v)
}
style.padding = `${Taro.pxTransform(baseStyle.paddedt || 0)} ${Taro.pxTransform(baseStyle.paddedr || 0)} ${Taro.pxTransform(baseStyle.paddedb || 0)} ${Taro.pxTransform(baseStyle.paddedl || 0)}`
if (bgType === 'color' && baseStyle.bgColor) {
style.backgroundColor = baseStyle.bgColor
if(isWeb){
style['background-color'] = baseStyle.bgColor
} else {
style.backgroundColor = baseStyle.bgColor
}
} else if (bgType === 'pic' && baseStyle.bgPic) {
style.backgroundImage = `url(${baseStyle.bgPic})`
if(isWeb){
style['background-image'] = `url('${baseStyle.bgPic}')`
style['background-size'] = '100% 100%'
style['background-position'] = 'center'
style['background-repeat'] = 'no-repeat'
} else {
style.backgroundImage = `url('${baseStyle.bgPic}')`
style.backgroundSize = '100% 100%'
style.backgroundPosition = 'center'
style.backgroundRepeat = 'no-repeat'
}
} else if (bgType === 'gradient' && baseStyle.startColor) {
const endColor = baseStyle.endColor || baseStyle.startColor
style.backgroundImage = `linear-gradient(${baseStyle.startColor}, ${endColor})`
style.backgroundSize = 'cover'
if(isWeb){
style['background-image'] = `linear-gradient(${baseStyle.startColor}, ${endColor})`
style['background-size'] = 'cover'
} else {
style.backgroundImage = `linear-gradient(${baseStyle.startColor}, ${endColor})`
style.backgroundSize = 'cover'
}
}
return style
}

View File

@@ -4,7 +4,7 @@
*/
import React, { useMemo } from 'react'
import Taro from '@tarojs/taro'
import { Button, View } from '@tarojs/components'
import { Button, View, ScrollView } from '@tarojs/components'
import { SpImage, SpLogin } from '@/components'
import { linkPage, classNames, styleNames, isArray, getDistributorId } from '@/utils'
import { needLoginPage, needLoginPageType } from '@/consts'
@@ -102,6 +102,17 @@ function WgtImgHotZone(props) {
return <View {...zoneProps} onClick={clickHandler} />
}
const renderImgContent = () => (
<>
<SpImage
src={config.imgUrl}
className='wgt-imghot-zone__body-img'
mode={!isVertical ? 'widthFix' : 'heightFix'}
/>
{isArray(data) && data.length > 0 && data.map(renderHotZone)}
</>
)
if (!info || !config.imgUrl) {
return null
}
@@ -113,19 +124,20 @@ function WgtImgHotZone(props) {
style={styleNames(outerStyle)}
>
<View className='wgt-imghot-zone__body' style={styleNames(bodyStyle)}>
<View
className={classNames('wgt-imghot-zone__body-img-wrapper', {
'wgt-imghot-zone__body-img-wrapper__vertical': isVertical,
'wgt-imghot-zone__body-img-wrapper__horizontal': !isVertical
})}
>
<SpImage
src={config.imgUrl}
className='wgt-imghot-zone__body-img'
mode={!isVertical ? 'widthFix' : 'heightFix'}
/>
{isArray(data) && data.length > 0 && data.map(renderHotZone)}
</View>
{isVertical ? (
<ScrollView
scrollX
enhanced
showScrollbar={false}
className='wgt-imghot-zone__body-img-wrapper wgt-imghot-zone__body-img-wrapper__vertical'
>
<View className='wgt-imghot-zone__scroll-inner'>{renderImgContent()}</View>
</ScrollView>
) : (
<View className='wgt-imghot-zone__body-img-wrapper wgt-imghot-zone__body-img-wrapper__horizontal'>
{renderImgContent()}
</View>
)}
</View>
</View>
)

View File

@@ -22,22 +22,29 @@
.wgt-imghot-zone__body-img-wrapper__vertical {
width: 100%;
height: 100%;
overflow-x: scroll;
scroll-snap-type: x mandatory;
box-sizing: border-box;
white-space: nowrap;
.wgt-imghot-zone__scroll-inner {
position: relative;
display: inline-block;
height: 100%;
vertical-align: top;
}
.wgt-imghot-zone__body-img {
height: 100% !important;
width: min-content;
width: max-content;
display: block;
}
}
.wgt-imghot-zone__body_zone {
position: absolute;
z-index: 1;
}
.wgt-imghot-zone__body {
position: relative;
&_zone {
position: absolute;
z-index: 99;
}
}
}

View File

@@ -230,14 +230,15 @@ function PurchaseAuth() {
// 企业二维码扫码登录
const getQrcodeEid = async () => {
try {
const { id, enterprise_id, code } = await entryLaunch.getRouteParams()
const { id, activity_id, enterprise_id, code } = await entryLaunch.getRouteParams()
//亲友扫码
let _id = id || activity_id || ''
setState((draft) => {
draft.invite_code = code || ''
draft.activity_id = id || ''
draft.activity_id = _id || ''
draft.enterprise_id = enterprise_id || ''
})
return { id, enterprise_id, code }
return { id:_id, enterprise_id, code }
} catch (error) {
return {}
}

View File

@@ -22,6 +22,7 @@ import './detail.scss'
const initialState = {
itemId: '',
title: '',
summary: '',
articleFocusNum: 0,
content: [],
updated: '',
@@ -33,7 +34,7 @@ function GuideRecommendDetail(props) {
const { i18n } = useTranslation()
const $instance = getCurrentInstance() || {}
const [state, setState] = useImmer(initialState)
const { img, shareImageUrl, itemId, title, content, articleFocusNum, updated } = state
const { img, shareImageUrl, itemId, title, summary, content, articleFocusNum, updated } = state
const { userInfo } = useSelector((state) => state.guide)
const router = useRouter()
@@ -88,7 +89,7 @@ function GuideRecommendDetail(props) {
})
}
}
const { itemId, title, articleFocusNum, content, updated, isPraise, articlePraiseNum } = pickBy(
const { itemId, title, summary, articleFocusNum, content, updated, isPraise, articlePraiseNum } = pickBy(
res,
doc.article.ARTICLE_ITEM
)
@@ -96,6 +97,7 @@ function GuideRecommendDetail(props) {
setState((draft) => {
draft.itemId = itemId
draft.title = title
draft.summary = summary
draft.articleFocusNum = articleFocusNum
draft.content = content
draft.updated = updated
@@ -162,7 +164,10 @@ function GuideRecommendDetail(props) {
>
<ScrollView className='scrollview-container' scrollY>
<View className='article-hd'>
<View className='article-title'>{title}</View>
<View className='article-title-row'>
<View className='article-title'>{title}</View>
{summary ? <Text className='article-summary'>{summary}</Text> : null}
</View>
<View className='article-info'>
<Text className='update-time'>{updated}</Text>
</View>

View File

@@ -13,10 +13,19 @@
.article-bd {
background: #fff;
}
.article-title-row {
display: flex;
flex-direction: column;
gap: 8px;
}
.article-title {
font-size: 40px;
color: #222;
}
.article-summary {
font-size: 28px;
color: #666;
}
.article-info {
margin-top: 16px;
}

View File

@@ -24,7 +24,7 @@ import {
SpInput as AtInput
} from '@/components'
import { useDianWuLogin, useDebounce } from '@/hooks'
import { styleNames, pickBy, showToast, classNames, validate } from '@/utils'
import { styleNames, pickBy, showToast, classNames } from '@/utils'
import { useTranslation, $t, i18n, ti } from '@/i18n'
import { selectMember } from '@/store/slices/dianwu'
import CompGoods from './comps/comp-goods'
@@ -33,6 +33,7 @@ import CompGoodsPrice from './comps/comp-goods-price'
import CompTabbar from './comps/comp-tabbar'
import CompDianwuSelectMember from './comps/comp-dianwu-select-member'
import CompDianwuPlatformOrder from './comps/comp-dianwu-platform-order'
import { isDianwuGoodsDisabled, resolveDianwuGoodsActions } from './utils/dianwu-goods-action'
import './cashier.scss'
const initialState = {
@@ -43,7 +44,8 @@ const initialState = {
searchResultLayout: false,
addUserCurtain: false,
isCameraOpend: false,
platformOrderItem: null
platformOrderItem: null,
isPlatformStoreBuy: false
}
function DianWuCashier() {
@@ -57,7 +59,8 @@ function DianWuCashier() {
searchGoodsList,
isCameraOpend,
distributor_id,
platformOrderItem
platformOrderItem,
isPlatformStoreBuy
} = state
const pageRef = useRef()
const getCashierListRef = useRef(null)
@@ -113,7 +116,7 @@ function DianWuCashier() {
const handleSearchByKeyword = async (keywords) => {
Taro.showLoading({ title: '' })
const { list: goodsList } = await dianwuApi.goodsItems({
const { list: goodsList, is_platform_store_buy } = await dianwuApi.goodsItems({
page: 1,
pageSize: 100,
keywords,
@@ -123,6 +126,9 @@ function DianWuCashier() {
setState((draft) => {
draft.searchGoodsList = pickBy(goodsList, doc.dianwu.GOODS_ITEM)
if (is_platform_store_buy != null) {
draft.isPlatformStoreBuy = !!is_platform_store_buy
}
// draft.searchMemberList = pickBy(memberList, doc.dianwu.MEMBER_ITEM)
draft.searchResultLayout = true
})
@@ -268,43 +274,6 @@ function DianWuCashier() {
}
}
const handleCreateMember = async () => {
const res = await dianwuApi.createMember({ mobile })
const newUser = pickBy(res, doc.dianwu.CREATE_MEMBER_ITEM)
const userInfo = await dianwuApi.getMemberByUserId({ user_id: newUser.userId })
const { couponNum, point, vipDiscount } = pickBy(userInfo, doc.dianwu.MEMBER_INFO)
dispatch(
selectMember({
...newUser,
couponNum,
point,
vipDiscount
})
)
setState((draft) => {
draft.addUserCurtain = false
})
}
const onChangeMobile = (e) => {
setState((draft) => {
draft.mobile = e
})
}
const handleConfirm = async () => {
if (validate.isMobileNum(mobile)) {
const { list } = await dianwuApi.getMembers({
mobile
})
setState((draft) => {
draft.searchMemberResult = pickBy(list, doc.dianwu.MEMBER_ITEM)
})
} else {
showToast($t('7187dbd0.a32ab5'))
}
}
const onChangePlus = async (item, idx, index) => {
const _num = parseInt(item.num) + 1
if (_num > item.store) {
@@ -657,35 +626,43 @@ function DianWuCashier() {
}}
>
<ScrollView className='tab-scroll-list' scrollY>
{searchGoodsList.map((item, index) => (
<View className='goods-item-wrap' key={`goods-item-wrap__${index}`}>
<CompGoods info={item}>
{item.store > 0 && item.isTotalStore === true && (
<AtButton
circle
className='btn-add-cart'
onClick={handleAddToCart.bind(this, item)}
>
{$t('7187dbd0.cd2240')}
</AtButton>
)}
{searchGoodsList.map((item, index) => {
const actions = resolveDianwuGoodsActions(item, isPlatformStoreBuy)
return (
<View
className={classNames('goods-item-wrap', {
'item-disabled': isDianwuGoodsDisabled(item, isPlatformStoreBuy)
})}
key={`goods-item-wrap__${index}`}
>
<CompGoods info={item} isPlatformStoreBuy={isPlatformStoreBuy}>
{actions.addToCashier && (
<AtButton
circle
className='btn-add-cart'
onClick={handleAddToCart.bind(this, item)}
>
{$t('7187dbd0.cd2240')}
</AtButton>
)}
{item.store == 0 && item.platformStore > 0 && (
<AtButton
className='btn-add-cart btn-platform-order'
circle
onClick={() => {
setState((draft) => {
draft.platformOrderItem = item
})
}}
>
立即下单
</AtButton>
)}
</CompGoods>
</View>
))}
{actions.buyNow && (
<AtButton
className='btn-add-cart btn-platform-order'
circle
onClick={() => {
setState((draft) => {
draft.platformOrderItem = item
})
}}
>
{$t('8cac8565.887eb6')}
</AtButton>
)}
</CompGoods>
</View>
)
})}
</ScrollView>
</SpFloatLayout>

View File

@@ -542,6 +542,9 @@
&:not(:last-child) {
margin-bottom: 30px;
}
&.item-disabled {
opacity: 0.6;
}
.at-button {
display: inline-block;
min-width: 120px;

View File

@@ -54,6 +54,17 @@ function CompDianwuSelectMember({ open, onClose, onAfterSelect, distributor_id }
const handleSelectMember = async () => {
const [item] = searchMemberResult
const userInfo = await dianwuApi.getMemberByUserId({ user_id: item.userId })
try {
await dianwuApi.memberReady({
user_id: item.userId,
distributor_id,
showError: false
})
} catch (e) {
const msg =
e?.message || e?.res?.data?.data?.message || $t('09e29d60.g3q6mc')
showToast(msg)
}
const { couponNum, point, vipDiscount } = pickBy(userInfo, doc.dianwu.MEMBER_INFO)
dispatch(
selectMember({

View File

@@ -6,19 +6,19 @@ import React from 'react'
import { View, Text } from '@tarojs/components'
import { SpImage } from '@/components'
import { useTranslation, $t, ti } from '@/i18n'
import { resolveDianwuGoodsDisplay } from '../utils/dianwu-goods-action'
import CompGoodsPrice from './comp-goods-price'
import './comp-goods.scss'
function CompGoods(props) {
useTranslation()
const { children, info } = props
const { children, info, isPlatformStoreBuy = false } = props
if (!info) {
return null
}
const showStore = info.isTotalStore === true
const showPlatformStore = info.platformStore != null
const showInventory = showStore || showPlatformStore
const display = resolveDianwuGoodsDisplay(info, isPlatformStoreBuy)
const showInventory = display.showStore || display.showCloud
return (
<View className='comp-goods'>
@@ -34,26 +34,15 @@ function CompGoods(props) {
{info.name}
</View>
{info.itemSpecDesc && <View className='sku'>{info.itemSpecDesc}</View>}
{/* <View className='price-list'>
<SpPrice className='sale-price' value={999.99}></SpPrice>
<View className='price-wrap'>
<SpPrice className='vip-price' value={888.99}></SpPrice>
<SpVipLabel content='VIP' type='vip' />
</View>
<View className='price-wrap'>
<SpPrice className='svip-price' value={666.99}></SpPrice>
<SpVipLabel content='SVIP' type='svip' />
</View>
</View> */}
<CompGoodsPrice info={info} />
<View className='goods-info'>
<View className='kc-bn'>
{showInventory && (
<View className='kc'>
<Text className='label'>{$t('982aa174.b008bd')}</Text>
{showStore && <Text>{ti('982aa174.285600', [info.store])}</Text>}
{showStore && showPlatformStore && <Text> | </Text>}
{showPlatformStore && <Text>{ti('982aa174.f36d41', [info.platformStore])}</Text>}
{display.showStore && <Text>{ti('982aa174.285600', [display.storeValue])}</Text>}
{display.showStore && display.showCloud && <Text> | </Text>}
{display.showCloud && <Text>{ti('982aa174.f36d41', [display.cloudValue])}</Text>}
</View>
)}
{info.barcode && (
@@ -64,12 +53,6 @@ function CompGoods(props) {
)}
</View>
<View className='btn-actions'>{children}</View>
{/* <AtButton circle className={classNames({ 'active': true })}>
<Text className='iconfont icon-plus'></Text>
</AtButton> */}
{/* <AtButton circle disabled>
缺货
</AtButton> */}
</View>
</View>
</View>

View File

@@ -16,28 +16,21 @@ import { useNavigation } from '@/hooks'
import CompGoods from './comps/comp-goods'
import CompTabbar from './comps/comp-tabbar'
import CompDianwuPlatformOrder from './comps/comp-dianwu-platform-order'
import { isDianwuGoodsDisabled, resolveDianwuGoodsActions } from './utils/dianwu-goods-action'
import './list.scss'
/** 无库存不可加购:门店与云仓均为 0 才禁用;未返回 platform_store 时仅按门店库存判断(与历史行为一致) */
function isDianwuListGoodsDisabled(item) {
if (item.store != 0) return false
const ps = item.platformStore
if (ps == null || ps === '') return true
return ps == 0
}
const initialState = {
keywords: '',
list: [],
current: 0,
cartList: [],
platformOrderItem: null
platformOrderItem: null,
isPlatformStoreBuy: false
}
function DianWuList() {
useTranslation()
const [state, setState] = useImmer(initialState)
const { keywords, typeList, current, list, cartList, platformOrderItem } = state
const { keywords, list, cartList, platformOrderItem, isPlatformStoreBuy } = state
const goodsRef = useRef()
const $instance = getCurrentInstance() || {}
const { distributor_id } = $instance?.router?.params
@@ -72,13 +65,16 @@ function DianWuList() {
}
Taro.showLoading({ title: '' })
try {
const { list: _list, total_count } = await dianwuApi.goodsItems(params)
const { list: _list, total_count, is_platform_store_buy } = await dianwuApi.goodsItems(params)
setState((draft) => {
// 首屏请求需清空分页缓存,否则搜索无结果时仍保留上一关键词的多页数据
if (pageIndex === 1) {
draft.list = []
}
if (pageIndex === 1 && is_platform_store_buy != null) {
draft.isPlatformStoreBuy = !!is_platform_store_buy
}
draft.list[pageIndex - 1] = pickBy(_list, doc.dianwu.GOODS_ITEM)
})
@@ -153,40 +149,43 @@ function DianWuList() {
</View>
<SpScrollView className='item-list-scroll' auto={false} ref={goodsRef} fetch={fetch}>
{list.map((items, idx) => {
return items.map((item, sidx) => (
<View
className={classNames('item-wrap', {
'item-disabled': isDianwuListGoodsDisabled(item)
})}
key={`item-wrap__${idx}_${sidx}`}
>
<CompGoods info={item}>
{item.store > 0 && item.isTotalStore === true && (
<AtButton
className='btn-add-cart'
circle
onClick={handleAddToCart.bind(this, item)}
>
{$t('8cac8565.cd2240')}
</AtButton>
)}
return items.map((item, sidx) => {
const actions = resolveDianwuGoodsActions(item, isPlatformStoreBuy)
return (
<View
className={classNames('item-wrap', {
'item-disabled': isDianwuGoodsDisabled(item, isPlatformStoreBuy)
})}
key={`item-wrap__${idx}_${sidx}`}
>
<CompGoods info={item} isPlatformStoreBuy={isPlatformStoreBuy}>
{actions.addToCashier && (
<AtButton
className='btn-add-cart'
circle
onClick={handleAddToCart.bind(this, item)}
>
{$t('8cac8565.cd2240')}
</AtButton>
)}
{item.store == 0 && item.platformStore > 0 && (
<AtButton
className='btn-add-cart btn-platform-order'
circle
onClick={() => {
setState((draft) => {
draft.platformOrderItem = item
})
}}
>
{$t('8cac8565.887eb6')}
</AtButton>
)}
</CompGoods>
</View>
))
{actions.buyNow && (
<AtButton
className='btn-add-cart btn-platform-order'
circle
onClick={() => {
setState((draft) => {
draft.platformOrderItem = item
})
}}
>
{$t('8cac8565.887eb6')}
</AtButton>
)}
</CompGoods>
</View>
)
})
})}
</SpScrollView>

View File

@@ -0,0 +1,107 @@
/**
* Copyright © ShopeX http://www.shopex.cn. All rights reserved.
* See LICENSE file for license details.
*/
/** 商品开启店铺库存(店铺发货) */
export function isDianwuStoreInventoryEnabled(item) {
const v = item?.isTotalStore
return v === false || v === 0 || v === '0' || v === 'false'
}
function toStockNum(val) {
const n = Number(val)
return Number.isFinite(n) ? n : 0
}
function resolvePlatformStockNum(item) {
if (item?.platformStore == null || item.platformStore === '') {
return null
}
return toStockNum(item.platformStore)
}
/**
* @param {object} item pickBy(GOODS_ITEM)
* @param {boolean} isPlatformStoreBuy 门店 is_platform_store_buy
*/
export function resolveDianwuGoodsDisplay(item, isPlatformStoreBuy) {
const storeStock = toStockNum(item?.store)
const platformStock = resolvePlatformStockNum(item)
const storeInventoryEnabled = isDianwuStoreInventoryEnabled(item)
if (isPlatformStoreBuy) {
if (storeInventoryEnabled) {
return {
showStore: true,
showCloud: platformStock != null,
storeValue: storeStock,
cloudValue: platformStock != null ? platformStock : 0
}
}
return {
showStore: false,
showCloud: true,
storeValue: 0,
cloudValue: platformStock != null ? platformStock : storeStock
}
}
if (storeInventoryEnabled) {
return {
showStore: true,
showCloud: false,
storeValue: storeStock,
cloudValue: 0
}
}
return {
showStore: false,
showCloud: true,
storeValue: 0,
cloudValue: storeStock
}
}
/**
* @param {object} item
* @param {boolean} isPlatformStoreBuy
*/
export function resolveDianwuGoodsActions(item, isPlatformStoreBuy) {
const storeStock = toStockNum(item?.store)
const platformStock = resolvePlatformStockNum(item)
const storeInventoryEnabled = isDianwuStoreInventoryEnabled(item)
let addToCashier = false
let buyNow = false
if (isPlatformStoreBuy) {
if (storeInventoryEnabled) {
if (storeStock > 0) {
addToCashier = true
} else if (platformStock != null && platformStock > 0) {
buyNow = true
}
} else if (platformStock != null && platformStock > 0) {
buyNow = true
}
} else if (storeInventoryEnabled) {
if (storeStock > 0) {
addToCashier = true
}
} else if (storeStock > 0) {
buyNow = true
}
return { addToCashier, buyNow }
}
/**
* @param {object} item
* @param {boolean} isPlatformStoreBuy
*/
export function isDianwuGoodsDisabled(item, isPlatformStoreBuy) {
const { addToCashier, buyNow } = resolveDianwuGoodsActions(item, isPlatformStoreBuy)
return !addToCashier && !buyNow
}

View File

@@ -77,7 +77,7 @@ export default class WgtFilm extends Component {
const { width, height, objectFit } = this.resolveSize(config, screenWidth, base)
return (
<View className={`wgt ${base.padded ? 'wgt__padded' : null}`}>
<View className={`wgt wgt-guide-film ${base.padded ? 'wgt__padded' : null}`}>
{base.title && (
<View className='wgt__header'>
<View className='wgt__title'>{base.title}</View>

View File

@@ -2,34 +2,13 @@
* Copyright © ShopeX http://www.shopex.cn. All rights reserved.
* See LICENSE file for license details.
*/
.wgt-film {
.wgts {
&__header {
// padding: 45px 30px 30px;
padding: 15px 30px 30px;
}
&__title {
line-height: 18px;
letter-spacing: 1px;
color: #000000;
font-size: 32px;
}
&__subtitle {
font-size: 22px;
line-height: 18px;
margin-top: 22px;
color: #8a8a8a;
}
&__wrap {
position: relative;
}
}
}
.wgt-guide-film {
.flim-video {
width: 100%;
display: block;
.flim-video {
width: 100%;
display: block;
&.is-fullscreen {
position: absolute;
&.is-fullscreen {
position: absolute;
}
}
}
}

View File

@@ -2932,6 +2932,7 @@
"09e29d60.625fb2": "إلغاء",
"09e29d60.3a6fa4": "اختيار عضو",
"09e29d60.9fd000": "إنشاء الآن",
"09e29d60.g3q6mc": "فشل مزامنة جلسة العضو",
"eac57497.887eb6": "اطلب الآن",
"eac57497.0bf60b": "الكمية",
"eac57497.5591b7": "${0} متاح في المستودع السحابي",

View File

@@ -2932,6 +2932,7 @@
"09e29d60.625fb2": "Cancel",
"09e29d60.3a6fa4": "Select member",
"09e29d60.9fd000": "Create now",
"09e29d60.g3q6mc": "Failed to sync member session",
"eac57497.887eb6": "Order now",
"eac57497.0bf60b": "Quantity",
"eac57497.5591b7": "${0} available in cloud warehouse",

View File

@@ -2936,6 +2936,7 @@
"09e29d60.625fb2": "取消",
"09e29d60.3a6fa4": "选择会员",
"09e29d60.9fd000": "立即创建",
"09e29d60.g3q6mc": "会员就绪同步失败",
"eac57497.887eb6": "立即下单",
"eac57497.0bf60b": "数量",
"eac57497.5591b7": "云仓可售 ${0} 件",

View File

@@ -5,25 +5,24 @@
.page-point-detail {
overflow-x: hidden;
.point-detail-hd {
width: 850px;
box-sizing: border-box;
width: 100%;
height: 300px;
background: var(--color-primary);
border-radius: 0 0 150px 150px;
border-radius: 0 0 80px 80px;
position: relative;
left: 50%;
transform: translateX(-50%);
}
.point-info {
width: 710px;
box-sizing: border-box;
left: 20px;
right: 20px;
width: auto;
height: 190px;
background: #ffffff;
border-radius: 16px;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -80px;
padding: 30px 40px;
box-sizing: border-box;
&-hd {
display: flex;
justify-content: space-between;

View File

@@ -11,21 +11,34 @@
display: flex;
justify-content: flex-end;
align-items: center;
box-sizing: border-box;
width: 100%;
padding: 14px 16px 0;
}
.switch-tab {
display: flex;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
max-width: 100%;
background: #f5f5f5;
border-radius: 50px;
margin: 14px 16px 10px 20px;
margin: 0 0 16px;
padding: 4px;
font-size: 24px;
box-sizing: border-box;
.switch-item {
// padding: 0px 30px;
width: 160px;
width: auto;
min-width: 100px;
max-width: 100%;
padding: 0 24px;
text-align: center;
height: 65px;
line-height: 65px;
color: #666;
border-radius: 50px;
white-space: nowrap;
box-sizing: border-box;
&.active {
font-weight: bold;
color: #fff;
@@ -34,6 +47,11 @@
}
}
}
.address-picker {
.address {
padding-top: 0;
}
}
.address-module {
background: #fff;
padding: 12px 30px;

View File

@@ -25,7 +25,6 @@ import {
pickBy,
authSetting,
showToast,
isWeb,
isAPP,
isWxWeb,
log,
@@ -48,7 +47,7 @@ function PointShopEspierCheckout() {
const { i18n } = useTranslation()
const $instance = getCurrentInstance() || {}
const { updateAddress } = useLocation()
const { isLogin, isNewUser, getUserInfoAuth } = useLogin({
const { isLogin } = useLogin({
autoLogin: true,
loginSuccess: () => {
updateAddress()
@@ -125,12 +124,13 @@ function PointShopEspierCheckout() {
}, [isLogin])
useEffect(() => {
if (isNewUser) {
const token = S?.getAuthToken()
if (!token) {
Taro.redirectTo({
url: `/subpages/member/index`
})
}
}, [isNewUser])
}, [])
useEffect(() => {
console.log(
@@ -638,7 +638,12 @@ function PointShopEspierCheckout() {
}
return (
<SpPage ref={pageRef} className='page-pointshop-espiercheckout' renderFooter={renderFooter()}>
<SpPage
ref={pageRef}
className='page-pointshop-espiercheckout'
footerHeight={140}
renderFooter={renderFooter()}
>
<View className='cart-checkout__address'>
<CompDeliver
ref={deliverRef}

View File

@@ -13,15 +13,23 @@ $margin24: 24px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
background: #fff;
padding: 0 20px;
padding: 12px 20px;
height: 100%;
box-sizing: border-box;
&__total {
white-space: nowrap;
white-space: normal;
flex: 1;
min-width: 0;
margin-right: 0;
display: flex;
align-items: center;
flex-direction: column;
align-items: flex-start;
justify-content: center;
font-size: 22px;
line-height: 1.35;
color: #333;
.last_price {
display: inline-block;
.sp-price__append {
@@ -30,8 +38,23 @@ $margin24: 24px;
}
}
.checkout-total {
margin-left: 20px;
line-height: 32px;
margin-left: 0;
margin-top: 4px;
width: 100%;
line-height: 1.4;
font-size: 22px;
word-break: break-word;
}
.at-button {
flex-shrink: 0;
width: auto !important;
min-width: 180px;
max-width: 240px;
height: 80px;
line-height: 80px;
margin: 0;
padding: 0 24px;
font-size: 28px;
}
&__price {
display: inline-block;
@@ -211,11 +234,27 @@ $margin24: 24px;
padding: 16px 0px;
}
}
.trade-remark {
.sp-cell__bd {
flex: 1;
min-width: 0;
justify-content: flex-start;
}
}
.trade-remark__input,
.at-input {
width: 100%;
margin: 0;
font-size: 24px;
&:after {
border: none;
}
.at-input__input {
font-size: 24px;
}
.input-placeholder {
font-size: 22px;
}
}
}

View File

@@ -2,7 +2,7 @@
* Copyright © ShopeX http://www.shopex.cn. All rights reserved.
* See LICENSE file for license details.
*/
import React, { useEffect, useRef } from 'react'
import React, { useEffect, useRef, useState, memo, useCallback } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { View, Text, Input, Picker } from '@tarojs/components'
import Taro from '@tarojs/taro'
@@ -24,6 +24,13 @@ const PLACEHOLDER_CITY = '选择市'
const PLACEHOLDER_DISTRICT = '选择区'
const DISTRICT_UNLIMITED = '__DISTRICT_UNLIMITED__'
function getDistrictLabels(city, unlimitedLabel) {
if (city?.children?.length) {
return [unlimitedLabel, ...city.children.map((d) => d.label)]
}
return [unlimitedLabel]
}
function displayPickerLabel(value) {
if (value === PLACEHOLDER_PROVINCE) return $t('9e660622.19a4c5')
if (value === PLACEHOLDER_CITY) return $t('9e660622.0c1085')
@@ -32,12 +39,114 @@ function displayPickerLabel(value) {
return value
}
/** 独立子组件:避免列表刷新导致 Picker value 被重置、滚动回弹 */
const LocationRegionPicker = memo(function LocationRegionPicker({
provinceLabel,
cityLabel,
districtLabel,
onConfirm
}) {
useTranslation()
const [areaData, setAreaData] = useState([])
const [areaList, setAreaList] = useState([[], [], []])
const [multiIndex, setMultiIndex] = useState([0, 0, 0])
const loadArea = useCallback(async () => {
try {
const res = await api.member.areaList()
if (!res?.length) return
const unlimitedLabel = $t('9e660622.8441b3')
const provinces = res.map((item) => item.label)
const firstCity = res[0]?.children?.[0]
const cities = res[0]?.children?.map((c) => c.label) || ['']
const districts = getDistrictLabels(firstCity, unlimitedLabel)
setAreaData(res)
setAreaList([provinces, cities.length ? cities : [''], districts])
setMultiIndex([0, 0, 0])
} catch (e) {
console.error('loadArea error', e)
}
}, [])
useEffect(() => {
loadArea()
}, [loadArea])
useEffect(() => {
const onLanguageChanged = () => {
loadArea()
}
i18n.on('languageChanged', onLanguageChanged)
return () => i18n.off('languageChanged', onLanguageChanged)
}, [loadArea])
const onColumnChange = (e) => {
const { column, value } = e.detail
const unlimitedLabel = $t('9e660622.8441b3')
if (column === 0) {
const province = areaData[value]
const cities = province?.children?.map((c) => c.label) || ['']
const districts = getDistrictLabels(province?.children?.[0], unlimitedLabel)
setMultiIndex([value, 0, 0])
setAreaList([areaList[0], cities.length ? cities : [''], districts])
} else if (column === 1) {
const city = areaData[multiIndex[0]]?.children?.[value]
const districts = getDistrictLabels(city, unlimitedLabel)
setMultiIndex([multiIndex[0], value, 0])
setAreaList([areaList[0], areaList[1], districts])
} else {
setMultiIndex([multiIndex[0], multiIndex[1], value])
}
}
const onChange = (e) => {
const [provinceIndex, cityIndex, districtIndex] = e.detail.value || [0, 0, 0]
const province = areaData[provinceIndex]
const city = province?.children?.[cityIndex]
const district = districtIndex === 0 ? null : city?.children?.[districtIndex - 1]
setMultiIndex([provinceIndex, cityIndex, districtIndex])
onConfirm({
province: province?.label || PLACEHOLDER_PROVINCE,
city: city?.label || PLACEHOLDER_CITY,
district:
districtIndex === 0 ? DISTRICT_UNLIMITED : district?.label || PLACEHOLDER_DISTRICT
})
}
return (
<Picker
mode='multiSelector'
range={areaList}
value={multiIndex}
disabled={!areaList[0]?.length}
onColumnChange={onColumnChange}
onChange={onChange}
>
<View className='location-picker'>
<View className='picker-item'>
<Text className='picker-item-label'>{displayPickerLabel(provinceLabel)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
<View className='picker-item'>
<Text className='picker-item-label'>{displayPickerLabel(cityLabel)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
<View className='picker-item'>
<Text className='picker-item-label'>{displayPickerLabel(districtLabel)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
</View>
</Picker>
)
})
const initialState = {
shopList: [],
keyword: '',
locationList: [],
locationRange: [[], [], []],
locationValue: [0, 0, 0],
selectedProvince: PLACEHOLDER_PROVINCE,
selectedCity: PLACEHOLDER_CITY,
selectedDistrict: PLACEHOLDER_DISTRICT,
@@ -62,8 +171,6 @@ function NearbyList() {
const {
shopList,
keyword,
locationRange,
locationValue,
selectedProvince,
selectedCity,
selectedDistrict,
@@ -125,7 +232,6 @@ function NearbyList() {
useEffect(() => {
const onLanguageChanged = () => {
setNavigationBarTitle($t('678873b3.9f3102'))
initLocationPicker()
}
onLanguageChanged()
i18n.on('languageChanged', onLanguageChanged)
@@ -133,7 +239,6 @@ function NearbyList() {
}, [setNavigationBarTitle])
useEffect(() => {
initLocationPicker()
getLocationInfo()
fetchCategoryList()
}, [])
@@ -158,28 +263,6 @@ function NearbyList() {
}
}, [refresh])
const initLocationPicker = async () => {
try {
const res = await api.member.areaList()
if (!res || !Array.isArray(res)) return
const provinces = res.map((item) => item.label)
const cities = res[0]?.children ? res[0].children.map((c) => c.label) : []
const unlimitedLabel = $t('9e660622.8441b3')
const districts = res[0]?.children?.[0]?.children
? [unlimitedLabel, ...res[0].children[0].children.map((d) => d.label)]
: [unlimitedLabel]
setState((draft) => {
draft.locationList = res
draft.locationRange = [provinces, cities, districts]
draft.locationValue = [0, 0, 0]
})
} catch (e) {
console.error('initLocationPicker error', e)
}
}
const getLocationInfo = async () => {
await entryLaunch.isOpenPosition(async (res) => {
bumpListFetchGen()
@@ -361,49 +444,16 @@ function NearbyList() {
return category?.category_name || $t('9e660622.a48948')
}
const onColumnChange = (e) => {
const { column, value } = e.detail
const handleLocationConfirm = useCallback((selection) => {
bumpListFetchGen()
setState((draft) => {
if (column === 0) {
draft.locationValue = [value, 0, 0]
const currentProvince = draft.locationList[value]
const cities = currentProvince?.children ? currentProvince.children.map((c) => c.label) : []
const unlimitedLabel = $t('9e660622.8441b3')
const districts = currentProvince?.children?.[0]?.children
? [unlimitedLabel, ...currentProvince.children[0].children.map((d) => d.label)]
: [unlimitedLabel]
draft.locationRange = [draft.locationRange[0], cities, districts]
} else if (column === 1) {
draft.locationValue[1] = value
draft.locationValue[2] = 0
const currentProvince = draft.locationList[draft.locationValue[0]]
const currentCity = currentProvince?.children?.[value]
const unlimitedLabel = $t('9e660622.8441b3')
const districts = currentCity?.children
? [unlimitedLabel, ...currentCity.children.map((d) => d.label)]
: [unlimitedLabel]
draft.locationRange[2] = districts
} else if (column === 2) {
draft.locationValue[2] = value
}
draft.selectedProvince = selection.province
draft.selectedCity = selection.city
draft.selectedDistrict = selection.district
draft.shopList = []
draft.refresh = true
})
}
const onLocationChange = (e) => {
const [provinceIndex, cityIndex, districtIndex] = e.detail.value || [0, 0, 0]
setState((draft) => {
const province = draft.locationList[provinceIndex]
const city = province?.children?.[cityIndex]
// 区列表第一项是"不限"所以索引需要减1
const district = districtIndex === 0 ? null : city?.children?.[districtIndex - 1]
draft.selectedProvince = province?.label || PLACEHOLDER_PROVINCE
draft.selectedCity = city?.label || PLACEHOLDER_CITY
draft.selectedDistrict =
districtIndex === 0 ? DISTRICT_UNLIMITED : district?.label || PLACEHOLDER_DISTRICT
draft.locationValue = [provinceIndex, cityIndex, districtIndex]
})
refreshList()
}
}, [])
const handleNavigation = (e, info) => {
e.stopPropagation()
@@ -479,7 +529,7 @@ function NearbyList() {
/>
{/* 搜索筛选区域 */}
<View className='filter-section'>
<View className={classNames('filter-section', { 'is-store-type-open': showStoreTypePicker })}>
{/* 搜索框和门店类型 */}
<View className='search-row-wrapper'>
<View className='search-row'>
@@ -550,28 +600,12 @@ function NearbyList() {
{/* 省市区选择 */}
<View className='location-row'>
<Picker
mode='multiSelector'
range={locationRange}
value={locationValue}
onColumnChange={onColumnChange}
onChange={onLocationChange}
>
<View className='location-picker'>
<View className='picker-item'>
<Text>{displayPickerLabel(selectedProvince)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
<View className='picker-item'>
<Text>{displayPickerLabel(selectedCity)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
<View className='picker-item'>
<Text>{displayPickerLabel(selectedDistrict)}</Text>
<Text className='iconfont icon-xialajiantou arrow-icon'></Text>
</View>
</View>
</Picker>
<LocationRegionPicker
provinceLabel={selectedProvince}
cityLabel={selectedCity}
districtLabel={selectedDistrict}
onConfirm={handleLocationConfirm}
/>
</View>
</View>

View File

@@ -26,6 +26,7 @@
.filter-section {
background-color: #f3f3f3;
padding: 40px 36px 24px;
flex-shrink: 0;
border-radius: 16px 16px 0 0;
display: flex;
@@ -34,6 +35,7 @@
.search-row-wrapper {
position: relative;
z-index: 200;
}
.search-row {
@@ -97,6 +99,9 @@
}
.location-row {
position: relative;
flex-shrink: 0;
.location-picker {
display: flex;
justify-content: space-between;
@@ -104,19 +109,36 @@
padding: 0 16px;
.picker-item {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
font-size: 24px;
color: #000;
.picker-item-label {
flex: 1;
min-width: 0;
@include multi-ellipsis(1);
}
.arrow-icon {
flex-shrink: 0;
font-size: 10px;
color: #444;
}
}
}
}
&.is-store-type-open {
.location-row {
visibility: hidden;
pointer-events: none;
}
}
}
// 门店列表滚动区域
@@ -288,7 +310,7 @@
right: -36px;
width: calc(100% + 72px);
background-color: #fff;
z-index: 100;
z-index: 201;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
.dropdown-content {