Compare commits
9 Commits
2317c9d973
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f9b54ce76 | |||
| a85da7a5d1 | |||
| 1b9e505feb | |||
| 74aee003e7 | |||
| e2926b0c70 | |||
| 1701169c9d | |||
| e97cc128ec | |||
| 9d893b4ed4 | |||
| 130ef2c949 |
@@ -1,7 +1,7 @@
|
|||||||
// 购物车悬浮球:右下角,底部避让自定义 tabBar(110rpx + 安全区)
|
// 购物车悬浮球:右下角,底部避让自定义 tabBar(110rpx + 安全区)
|
||||||
.cart-ball {
|
.cart-ball {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 24rpx;
|
left: 24rpx;
|
||||||
bottom: calc(150rpx + env(safe-area-inset-bottom));
|
bottom: calc(150rpx + env(safe-area-inset-bottom));
|
||||||
z-index: 998;
|
z-index: 998;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
&__btn {
|
&__btn {
|
||||||
width: 52rpx;
|
width: 42rpx;
|
||||||
height: 52rpx;
|
height: 42rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import {useEffect, useState} from 'react'
|
import {useEffect, useState} from 'react'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {View, Text, Image} from '@tarojs/components'
|
import {View, Text, Image} from '@tarojs/components'
|
||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
|
||||||
import useCartStore from '@/stores/cart/useCartStore'
|
|
||||||
import IndexImage from '@/static/images/nav/index.png';
|
import IndexImage from '@/static/images/nav/index.png';
|
||||||
import IndexActiveImage from '@/static/images/nav/index_active.png';
|
import IndexActiveImage from '@/static/images/nav/index_active.png';
|
||||||
import CartImage from '@/static/images/nav/cart.png';
|
import CartImage from '@/static/images/nav/cart.png';
|
||||||
@@ -15,13 +15,15 @@ interface PriceTextProps {
|
|||||||
* - block 独占一行(窄卡片:首页推荐、商品列表、购物车)
|
* - block 独占一行(窄卡片:首页推荐、商品列表、购物车)
|
||||||
*/
|
*/
|
||||||
mode?: 'inline' | 'block'
|
mode?: 'inline' | 'block'
|
||||||
|
/** 单位 */
|
||||||
|
price_unit?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 商品价格:售价 + 零售价标注(小字)
|
* 商品价格:售价 + 零售价标注(小字)
|
||||||
* price=30、spec=15 → ¥30 零售价:¥2
|
* price=30、spec=15 → ¥30 零售价:¥2
|
||||||
*/
|
*/
|
||||||
export default function PriceText({ price, spec, className, mode = 'inline' }: PriceTextProps) {
|
export default function PriceText({ price, spec, className, mode = 'inline', price_unit }: PriceTextProps) {
|
||||||
const retail = formatRetailPrice(price, spec)
|
const retail = formatRetailPrice(price, spec)
|
||||||
|
|
||||||
// 窄卡片:大价格 / 零售价上下两行,避免与右侧按钮(+/步进器)挤压换行
|
// 窄卡片:大价格 / 零售价上下两行,避免与右侧按钮(+/步进器)挤压换行
|
||||||
@@ -30,7 +32,7 @@ export default function PriceText({ price, spec, className, mode = 'inline' }: P
|
|||||||
<View className={`price-text ${className ?? ''}`}>
|
<View className={`price-text ${className ?? ''}`}>
|
||||||
<Text className='price-text__main'>¥{price}</Text>
|
<Text className='price-text__main'>¥{price}</Text>
|
||||||
{retail !== null && (
|
{retail !== null && (
|
||||||
<Text className='price-text__retail'>单价:¥{retail}</Text>
|
<Text className='price-text__retail'>单价:¥{retail} {price_unit}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
@@ -40,7 +42,7 @@ export default function PriceText({ price, spec, className, mode = 'inline' }: P
|
|||||||
<Text className={className}>
|
<Text className={className}>
|
||||||
¥{price}
|
¥{price}
|
||||||
{retail !== null && (
|
{retail !== null && (
|
||||||
<Text className='price-text__retail price-text__retail--inline'>单价:¥{retail}</Text>
|
<Text className='price-text__retail price-text__retail--inline'>单价:¥{retail} {price_unit}</Text>
|
||||||
)}
|
)}
|
||||||
</Text>
|
</Text>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" >
|
||||||
<title>订货采购</title>
|
<title>订货采购</title>
|
||||||
<script crossorigin="anonymous" src="https://polyfill.alicdn.com/polyfill.min.js?features=es2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022"></script>
|
<script crossorigin="anonymous" src="https://polyfill.alicdn.com/polyfill.min.js?features=es2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019%2Ces2020%2Ces2021%2Ces2022"></script>
|
||||||
|
<script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
|
||||||
<script><%= htmlWebpackPlugin.options.script %></script>
|
<script><%= htmlWebpackPlugin.options.script %></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { View, Text, ScrollView } from '@tarojs/components'
|
import { View, Text, ScrollView } from '@tarojs/components'
|
||||||
import CustomNavBar from '@/components/NavBar'
|
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +8,6 @@ import './index.less'
|
|||||||
export default function AgreementPage() {
|
export default function AgreementPage() {
|
||||||
return (
|
return (
|
||||||
<View className='agreement-page'>
|
<View className='agreement-page'>
|
||||||
<CustomNavBar title='用户服务协议' />
|
|
||||||
<ScrollView scrollY className='agreement-scroll'>
|
<ScrollView scrollY className='agreement-scroll'>
|
||||||
<View className='agreement-content'>
|
<View className='agreement-content'>
|
||||||
<Text className='doc-title'>用户服务协议</Text>
|
<Text className='doc-title'>用户服务协议</Text>
|
||||||
|
|||||||
@@ -115,8 +115,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__img {
|
&__img {
|
||||||
width: 72rpx;
|
width: 120rpx;
|
||||||
height: 72rpx;
|
height: 120rpx;
|
||||||
border-radius: 8rpx;
|
border-radius: 8rpx;
|
||||||
background: #f2f3f5;
|
background: #f2f3f5;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -147,14 +147,16 @@
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__qty {
|
|
||||||
font-size: 24rpx;
|
|
||||||
color: #969799;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__amount {
|
&__amount {
|
||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
|
color: #ee0a24;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
margin-top: 4rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__price {
|
||||||
|
font-size: 24rpx;
|
||||||
color: #323233;
|
color: #323233;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { Empty, Popup } from '@antmjs/vantui'
|
|||||||
import { getBillDetailApi } from '@/services/bill'
|
import { getBillDetailApi } from '@/services/bill'
|
||||||
import { getOrderDetailApi } from '@/services/order'
|
import { getOrderDetailApi } from '@/services/order'
|
||||||
import { ORDER_STATUS_TEXT } from '@/types/order'
|
import { ORDER_STATUS_TEXT } from '@/types/order'
|
||||||
import { formatSpec, resolveFileUrl } from '@/utils/format'
|
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
|
||||||
import PriceText from '@/components/PriceText'
|
|
||||||
import type { OrderDetail } from '@/types/order'
|
import type { OrderDetail } from '@/types/order'
|
||||||
import type { BillDetail } from '@/services/bill'
|
import type { BillDetail } from '@/services/bill'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
@@ -60,18 +59,9 @@ export default function BillDetailPage() {
|
|||||||
|
|
||||||
const { bill, items, orders } = detail
|
const { bill, items, orders } = detail
|
||||||
|
|
||||||
/** 附加金额:正=压筐附加,负=回筐抵扣(0 不带头符号) */
|
|
||||||
const addedNum = Number(bill.added_amount)
|
|
||||||
const addedLabel = addedNum < 0 ? '回筐抵扣' : '压筐附加'
|
|
||||||
const addedText =
|
|
||||||
addedNum < 0
|
|
||||||
? `-¥${Math.abs(addedNum).toFixed(2)}`
|
|
||||||
: addedNum > 0
|
|
||||||
? `+¥${addedNum.toFixed(2)}`
|
|
||||||
: '¥0.00'
|
|
||||||
/** 筐/托盘明细:正压负回,数量取绝对值(单价为出账时快照) */
|
/** 筐/托盘明细:正压负回,数量取绝对值(单价为出账时快照) */
|
||||||
const boxPart = `${bill.box_num < 0 ? '回筐' : '压筐'} ${Math.abs(bill.box_num)}×¥${bill.box_price}`
|
const boxTotalPrice = (Number(bill.box_price) * bill.box_num).toFixed(2)
|
||||||
const trayPart = `${bill.tray_num < 0 ? '回托盘' : '压托盘'} ${Math.abs(bill.tray_num)}×¥${bill.tray_price}`
|
const trayTotalPrice = (Number(bill.tray_price) * bill.tray_num).toFixed(2)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={`bill-detail ${bill.can_pay ? 'bill-detail--pay' : ''}`}>
|
<View className={`bill-detail ${bill.can_pay ? 'bill-detail--pay' : ''}`}>
|
||||||
@@ -114,17 +104,11 @@ export default function BillDetailPage() {
|
|||||||
<Text className='bill-card__value'>{bill.pay_remark}</Text>
|
<Text className='bill-card__value'>{bill.pay_remark}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
{bill.remark && (
|
|
||||||
<View className='bill-card__row'>
|
|
||||||
<Text className='bill-card__label'>账单备注</Text>
|
|
||||||
<Text className='bill-card__value'>{bill.remark}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ===== 金额构成 ===== */}
|
{/* ===== 金额构成 ===== */}
|
||||||
<View className='bill-card'>
|
<View className='bill-card'>
|
||||||
<Text className='bill-section__title'>金额构成</Text>
|
<Text className='bill-section__title'>金额明细</Text>
|
||||||
<View className='bill-card__row'>
|
<View className='bill-card__row'>
|
||||||
<Text className='bill-card__label'>商品金额</Text>
|
<Text className='bill-card__label'>商品金额</Text>
|
||||||
<Text className='bill-card__value'>¥{bill.product_amount}</Text>
|
<Text className='bill-card__value'>¥{bill.product_amount}</Text>
|
||||||
@@ -134,11 +118,27 @@ export default function BillDetailPage() {
|
|||||||
<Text className='bill-card__value'>¥{bill.delivery_fee}</Text>
|
<Text className='bill-card__value'>¥{bill.delivery_fee}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='bill-card__row'>
|
<View className='bill-card__row'>
|
||||||
<Text className='bill-card__label'>{addedLabel}({boxPart},{trayPart})</Text>
|
<Text className='bill-card__label'>周转筐({bill.box_price} × {bill.box_num})</Text>
|
||||||
<Text className={`bill-card__value ${addedNum < 0 ? 'bill-card__value--return' : ''}`}>
|
<Text className={`bill-card__value ${Number(boxTotalPrice) < 0 ? 'bill-card__value--return' : ''}`}>
|
||||||
{addedText}
|
{Number(boxTotalPrice) < 0 ? `- ¥${boxTotalPrice}` : `¥${boxTotalPrice}`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
<View className='bill-card__row'>
|
||||||
|
<Text className='bill-card__label'>周转托盘({bill.tray_price} × {bill.tray_num})</Text>
|
||||||
|
<Text className={`bill-card__value ${Number(trayTotalPrice) < 0 ? 'bill-card__value--return' : ''}`}>
|
||||||
|
{Number(trayTotalPrice) < 0 ? `- ¥${trayTotalPrice}` : `¥${trayTotalPrice}`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className='bill-card__row'>
|
||||||
|
<Text className='bill-card__label'>售后</Text>
|
||||||
|
<Text className={`bill-card__value ${Number(bill.after_sale) < 0 ? 'bill-card__value--return' : ''}`}>
|
||||||
|
{Number(bill.after_sale) < 0 ? `- ¥${bill.after_sale}` : `¥${bill.after_sale}`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className='bill-card__row'>
|
||||||
|
<Text className='bill-card__label'>账单备注</Text>
|
||||||
|
<Text className='bill-card__value'>{bill.remark || '暂无备注'}</Text>
|
||||||
|
</View>
|
||||||
<View className='bill-card__row bill-card__row--total'>
|
<View className='bill-card__row bill-card__row--total'>
|
||||||
<Text className='bill-card__label'>账单总额</Text>
|
<Text className='bill-card__label'>账单总额</Text>
|
||||||
<Text className='bill-card__total'>¥{bill.total_amount}</Text>
|
<Text className='bill-card__total'>¥{bill.total_amount}</Text>
|
||||||
@@ -148,7 +148,6 @@ export default function BillDetailPage() {
|
|||||||
{/* ===== 商品明细(跨订单按商品合并) ===== */}
|
{/* ===== 商品明细(跨订单按商品合并) ===== */}
|
||||||
<View className='bill-card'>
|
<View className='bill-card'>
|
||||||
<Text className='bill-section__title'>商品明细({items?.length ?? 0})</Text>
|
<Text className='bill-section__title'>商品明细({items?.length ?? 0})</Text>
|
||||||
<Text className='bill-section__desc'>同一商品跨订单合并,单价为加权平均价</Text>
|
|
||||||
{(items ?? []).map(item => (
|
{(items ?? []).map(item => (
|
||||||
<View key={item.product_id} className='bill-goods'>
|
<View key={item.product_id} className='bill-goods'>
|
||||||
{!!item.image && (
|
{!!item.image && (
|
||||||
@@ -160,15 +159,17 @@ export default function BillDetailPage() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<View className='bill-goods__main'>
|
<View className='bill-goods__main'>
|
||||||
<Text className='bill-goods__name'>{item.product_name}</Text>
|
<View className='bill-goods__name'>{item.product_name}</View>
|
||||||
<Text className='bill-goods__spec'>
|
<View className='bill-goods__spec'>
|
||||||
{formatSpec(item.product_spec, item.unit)}{' '}
|
{formatSpec(item.product_spec, item.unit)}
|
||||||
<PriceText price={item.price} spec={item.product_spec} />
|
</View>
|
||||||
</Text>
|
<View className='bill-goods__spec'>
|
||||||
|
单价:{formatRetailPrice(item.price, item.spec)} {item.price_unit}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<View className='bill-goods__side'>
|
<View className='bill-goods__side'>
|
||||||
<Text className='bill-goods__qty'>×{item.quantity}</Text>
|
<Text className='bill-goods__price'>¥{item.price} × {item.quantity}</Text>
|
||||||
<Text className='bill-goods__amount'>¥{item.amount}</Text>
|
<View className='bill-goods__amount'>¥{item.amount}</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
@@ -217,10 +218,10 @@ export default function BillDetailPage() {
|
|||||||
<Text className='order-popup__item-name'>{item.product_name}</Text>
|
<Text className='order-popup__item-name'>{item.product_name}</Text>
|
||||||
<Text className='order-popup__item-spec'>
|
<Text className='order-popup__item-spec'>
|
||||||
{formatSpec(item.product_spec, item.unit)}{' '}
|
{formatSpec(item.product_spec, item.unit)}{' '}
|
||||||
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
|
单价:{formatRetailPrice(item.price, item.product_spec)} {item.price_unit}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='order-popup__item-amount'>¥{item.amount}</Text>
|
<Text className='order-popup__item-amount'>¥{item.price} × {item.quantity}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
+26
-29
@@ -9,16 +9,24 @@
|
|||||||
padding-bottom: 160rpx;
|
padding-bottom: 160rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 待支付汇总 =====
|
// 底部待支付汇总栏留出空间
|
||||||
.bill-summary {
|
&--pay {
|
||||||
|
padding-bottom: 180rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 底部待支付汇总栏 =====
|
||||||
|
.bill-paybar {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 99;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: linear-gradient(135deg, #ee0a24, #ff4d4f);
|
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
||||||
border-radius: 16rpx;
|
background: #fff;
|
||||||
padding: 28rpx;
|
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
margin-bottom: 20rpx;
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
&__info {
|
&__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -26,35 +34,24 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__label {
|
&__label {
|
||||||
font-size: 24rpx;
|
font-size: 22rpx;
|
||||||
opacity: 0.85;
|
color: #969799;
|
||||||
}
|
|
||||||
|
|
||||||
&__count {
|
|
||||||
margin-top: 8rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__amount {
|
&__amount {
|
||||||
font-size: 40rpx;
|
margin-top: 4rpx;
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #ee0a24;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__right {
|
&__btn {
|
||||||
display: flex;
|
padding: 16rpx 56rpx;
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__pay {
|
|
||||||
margin-top: 12rpx;
|
|
||||||
padding: 8rpx 32rpx;
|
|
||||||
background: #fff;
|
|
||||||
color: #ee0a24;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
border-radius: 999rpx;
|
border-radius: 999rpx;
|
||||||
|
background: linear-gradient(135deg, #ee0a24, #ff6034);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+16
-16
@@ -29,7 +29,7 @@ interface CategoryOption {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 账单列表页
|
* 账单列表页
|
||||||
* 采购单完成后由后台按门店生成(只读);头部 summary 为门店口径待支付汇总(含审核中,不受筛选影响)
|
* 采购单完成后由后台按门店生成(只读);底部汇总栏为门店口径待支付汇总(含审核中,不受筛选影响)
|
||||||
* 支持多选账单合并导出 Excel(可按一级分类过滤商品明细)
|
* 支持多选账单合并导出 Excel(可按一级分类过滤商品明细)
|
||||||
*/
|
*/
|
||||||
export default function BillListPage() {
|
export default function BillListPage() {
|
||||||
@@ -185,22 +185,11 @@ export default function BillListPage() {
|
|||||||
[selectedIds, toggleSelectMode],
|
[selectedIds, toggleSelectMode],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
/** 底部待支付汇总栏是否可见(多选导出时让位给导出栏) */
|
||||||
<View className={`bill-page ${selectMode ? 'bill-page--select' : ''}`}>
|
const showPayBar = loggedIn && !selectMode && !!summary && summary.unpaid_count > 0
|
||||||
{/* ========== 待支付汇总(门店口径,含审核中) ========== */}
|
|
||||||
{loggedIn && summary && summary.unpaid_count > 0 && (
|
|
||||||
<View className='bill-summary'>
|
|
||||||
<View className='bill-summary__info'>
|
|
||||||
<Text className='bill-summary__label'>待支付账单(含审核中)</Text>
|
|
||||||
<Text className='bill-summary__count'>{summary.unpaid_count} 笔</Text>
|
|
||||||
</View>
|
|
||||||
<View className='bill-summary__right'>
|
|
||||||
<Text className='bill-summary__amount'>¥{summary.unpaid_amount}</Text>
|
|
||||||
<View className='bill-summary__pay' onClick={goPay}>去付款</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className={`bill-page ${selectMode ? 'bill-page--select' : ''} ${showPayBar ? 'bill-page--pay' : ''}`}>
|
||||||
{/* ========== 状态筛选 + 导出入口 ========== */}
|
{/* ========== 状态筛选 + 导出入口 ========== */}
|
||||||
<View className='bill-toolbar'>
|
<View className='bill-toolbar'>
|
||||||
<ScrollView scrollX className='status-scroll'>
|
<ScrollView scrollX className='status-scroll'>
|
||||||
@@ -278,6 +267,17 @@ export default function BillListPage() {
|
|||||||
<View className='bill-loading'><Text>没有更多了</Text></View>
|
<View className='bill-loading'><Text>没有更多了</Text></View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ========== 底部待支付汇总栏(门店口径,含审核中) ========== */}
|
||||||
|
{showPayBar && summary && (
|
||||||
|
<View className='bill-paybar'>
|
||||||
|
<View className='bill-paybar__info'>
|
||||||
|
<Text className='bill-paybar__label'>待支付账单(含审核中){summary.unpaid_count} 笔</Text>
|
||||||
|
<Text className='bill-paybar__amount'>¥{summary.unpaid_amount}</Text>
|
||||||
|
</View>
|
||||||
|
<View className='bill-paybar__btn' onClick={goPay}>去付款</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* ========== 导出操作栏 ========== */}
|
{/* ========== 导出操作栏 ========== */}
|
||||||
{selectMode && (
|
{selectMode && (
|
||||||
<View className='export-bar'>
|
<View className='export-bar'>
|
||||||
|
|||||||
@@ -59,8 +59,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__image {
|
&__image {
|
||||||
width: 150rpx;
|
width: 180rpx;
|
||||||
height: 150rpx;
|
height: 180rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
background: #f2f3f5;
|
background: #f2f3f5;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -88,6 +88,15 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__spec-tag {
|
||||||
|
margin-left: 12rpx;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #969799;
|
||||||
|
border-radius: 6rpx;
|
||||||
|
padding: 2rpx 8rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&__invalid-tag {
|
&__invalid-tag {
|
||||||
margin-left: 12rpx;
|
margin-left: 12rpx;
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
@@ -99,7 +108,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__spec {
|
&__spec {
|
||||||
margin-top: 10rpx;
|
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
}
|
}
|
||||||
@@ -167,6 +175,7 @@
|
|||||||
padding: 16rpx 24rpx;
|
padding: 16rpx 24rpx;
|
||||||
border-top: 1rpx solid #ebedf0;
|
border-top: 1rpx solid #ebedf0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
z-index: 99;
|
||||||
|
|
||||||
&__total {
|
&__total {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
+13
-10
@@ -6,11 +6,11 @@ import useAuthStore from '@/stores/auth/useAuthStore'
|
|||||||
import useCartStore from '@/stores/cart/useCartStore'
|
import useCartStore from '@/stores/cart/useCartStore'
|
||||||
import { createOrderApi } from '@/services/order'
|
import { createOrderApi } from '@/services/order'
|
||||||
import { getStoreInfoApi } from '@/services/store'
|
import { getStoreInfoApi } from '@/services/store'
|
||||||
import PriceText from '@/components/PriceText'
|
import {formatRetailPrice, formatSpec} from '@/utils/format'
|
||||||
import { formatSpec } from '@/utils/format'
|
|
||||||
import type { CartItem } from '@/types/cart'
|
import type { CartItem } from '@/types/cart'
|
||||||
import type { StoreDetail } from '@/types/store'
|
import type { StoreDetail } from '@/types/store'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
import CustomTabBar from "@/components/CustomTabBar";
|
||||||
|
|
||||||
export default function CartPage() {
|
export default function CartPage() {
|
||||||
const token = useAuthStore(s => s.token)
|
const token = useAuthStore(s => s.token)
|
||||||
@@ -159,7 +159,7 @@ export default function CartPage() {
|
|||||||
if (submitting) return
|
if (submitting) return
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
try {
|
try {
|
||||||
const res = await createOrderApi({
|
await createOrderApi({
|
||||||
items: purchasable.map(item => ({
|
items: purchasable.map(item => ({
|
||||||
product_id: item.product_id,
|
product_id: item.product_id,
|
||||||
quantity: Number(qtyMap[item.id] ?? item.quantity),
|
quantity: Number(qtyMap[item.id] ?? item.quantity),
|
||||||
@@ -221,13 +221,12 @@ export default function CartPage() {
|
|||||||
<Text className='cart-item__name'>{item.name}</Text>
|
<Text className='cart-item__name'>{item.name}</Text>
|
||||||
{item.status === 0 && <Text className='cart-item__invalid-tag'>已失效</Text>}
|
{item.status === 0 && <Text className='cart-item__invalid-tag'>已失效</Text>}
|
||||||
</View>
|
</View>
|
||||||
<Text className='cart-item__spec'>{formatSpec(item.spec, item.unit)}</Text>
|
<Text className='cart-item__spec'>
|
||||||
|
{formatSpec(item.spec, item.unit)}{' '}
|
||||||
|
<View>单价:{formatRetailPrice(item.price, item.spec)} {item.price_unit}</View>
|
||||||
|
</Text>
|
||||||
<View className='cart-item__bottom'>
|
<View className='cart-item__bottom'>
|
||||||
{item.price !== null ? (
|
<Text className='cart-item__price'>¥{item.price}</Text>
|
||||||
<PriceText className='cart-item__price' price={item.price} spec={item.spec} mode='block' />
|
|
||||||
) : (
|
|
||||||
<Text className='cart-item__price cart-item__price--none'>价格待定</Text>
|
|
||||||
)}
|
|
||||||
{item.status === 1 ? (
|
{item.status === 1 ? (
|
||||||
<Stepper
|
<Stepper
|
||||||
value={displayQty(item)}
|
value={displayQty(item)}
|
||||||
@@ -252,6 +251,8 @@ export default function CartPage() {
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<View style={{ height: 100 }}></View>
|
||||||
|
|
||||||
{loggedIn && hasInvalid && (
|
{loggedIn && hasInvalid && (
|
||||||
<View className='cart-invalid-hint'>
|
<View className='cart-invalid-hint'>
|
||||||
<Text>部分商品已下架或未设置您所在等级的价格,不可下单</Text>
|
<Text>部分商品已下架或未设置您所在等级的价格,不可下单</Text>
|
||||||
@@ -262,7 +263,7 @@ export default function CartPage() {
|
|||||||
{loggedIn && items.length > 0 && (
|
{loggedIn && items.length > 0 && (
|
||||||
<View className='cart-footer'>
|
<View className='cart-footer'>
|
||||||
<View className='cart-footer__total'>
|
<View className='cart-footer__total'>
|
||||||
<Text className='cart-footer__label'>合计(可购{totalQuantity}件)</Text>
|
<Text className='cart-footer__label'>合计{totalQuantity}件</Text>
|
||||||
<Text className='cart-footer__amount'>¥{totalAmount}</Text>
|
<Text className='cart-footer__amount'>¥{totalAmount}</Text>
|
||||||
</View>
|
</View>
|
||||||
<Button type='danger' className='cart-footer__submit' onClick={handleOrderTap}>
|
<Button type='danger' className='cart-footer__submit' onClick={handleOrderTap}>
|
||||||
@@ -351,6 +352,8 @@ export default function CartPage() {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</Popup>
|
</Popup>
|
||||||
|
|
||||||
|
{process.env.TARO_ENV === 'h5' && <CustomTabBar />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,6 +235,17 @@
|
|||||||
color: #969799;
|
color: #969799;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20rpx 0 8rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading-text {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #969799;
|
||||||
|
}
|
||||||
|
|
||||||
&__login-btn {
|
&__login-btn {
|
||||||
margin-top: 24rpx;
|
margin-top: 24rpx;
|
||||||
padding: 14rpx 60rpx;
|
padding: 14rpx 60rpx;
|
||||||
@@ -274,8 +285,8 @@
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #323233;
|
color: #323233;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
margin-right: 20rpx;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -284,14 +295,13 @@
|
|||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
font-size: 22rpx;
|
font-size: 22rpx;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__bottom {
|
&__bottom {
|
||||||
margin-top: 16rpx;
|
margin-top: 8rpx;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -310,8 +320,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__add {
|
&__add {
|
||||||
width: 52rpx;
|
width: 42rpx;
|
||||||
height: 52rpx;
|
height: 42rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, #ee0a24, #ff6034);
|
background: linear-gradient(135deg, #ee0a24, #ff6034);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
+100
-24
@@ -1,24 +1,27 @@
|
|||||||
import { useCallback, useMemo, useState } from 'react'
|
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import Taro, { useDidShow } from '@tarojs/taro'
|
import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
|
||||||
import { View, Text, Image } from '@tarojs/components'
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
import { Grid, GridItem, Icon, Search, Swiper, SwiperItem } from '@antmjs/vantui'
|
import { Grid, GridItem, Icon, Search, Swiper, SwiperItem } from '@antmjs/vantui'
|
||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
|
||||||
import useCartStore from '@/stores/cart/useCartStore'
|
import useCartStore from '@/stores/cart/useCartStore'
|
||||||
import { getHomeConfigApi } from '@/services/home'
|
import { getHomeConfigApi } from '@/services/home'
|
||||||
import type { HomeConfig } from '@/services/home'
|
import type { HomeConfig } from '@/services/home'
|
||||||
import { getProductListApi } from '@/services/product'
|
import { getSpecialListApi, normalizeSpecialCart } from '@/services/special'
|
||||||
import { getProductCover } from '@/types/product'
|
import { getProductCover } from '@/types/product'
|
||||||
import type { Product, ProductCartPatch } from '@/types/product'
|
import type { Product, ProductCartPatch } from '@/types/product'
|
||||||
import PriceText from '@/components/PriceText'
|
import { getToken } from '@/utils/request'
|
||||||
import CartBall from '@/components/CartBall'
|
import CartBall from '@/components/CartBall'
|
||||||
import CartStepper from '@/components/CartStepper'
|
import CartStepper from '@/components/CartStepper'
|
||||||
import { formatSpec } from '@/utils/format'
|
import {formatRetailPrice, formatSpec} from '@/utils/format'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
import CustomTabBar from "@/components/CustomTabBar";
|
||||||
|
|
||||||
/** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */
|
/** 首页 → 商品页的本地存储传参 key(switchTab 无法带参) */
|
||||||
const PENDING_CATEGORY_KEY = 'product_category_id'
|
const PENDING_CATEGORY_KEY = 'product_category_id'
|
||||||
const PENDING_KEYWORD_KEY = 'product_keyword'
|
const PENDING_KEYWORD_KEY = 'product_keyword'
|
||||||
|
|
||||||
|
/** 特价推荐每页条数 */
|
||||||
|
const SPECIAL_PAGE_SIZE = 10
|
||||||
|
|
||||||
/** tabBar 页面路径(link 跳转需改用 switchTab) */
|
/** tabBar 页面路径(link 跳转需改用 switchTab) */
|
||||||
const TAB_PATHS = [
|
const TAB_PATHS = [
|
||||||
'pages/index/index',
|
'pages/index/index',
|
||||||
@@ -47,8 +50,17 @@ export default function IndexPage() {
|
|||||||
|
|
||||||
/** 首页配置(轮播图 / 宫格导航 / 促销卡片) */
|
/** 首页配置(轮播图 / 宫格导航 / 促销卡片) */
|
||||||
const [config, setConfig] = useState<HomeConfig>({ banners: [], navs: [], promos: [] })
|
const [config, setConfig] = useState<HomeConfig>({ banners: [], navs: [], promos: [] })
|
||||||
/** 推荐商品 */
|
/** 特价推荐商品(后台「客户端配置 → 特价推荐」标记,价格为登录门店的等级价) */
|
||||||
const [products, setProducts] = useState<Product[]>([])
|
const [specials, setSpecials] = useState<Product[]>([])
|
||||||
|
/** 特价推荐分页 */
|
||||||
|
const [specialPage, setSpecialPage] = useState(1)
|
||||||
|
const [specialHasMore, setSpecialHasMore] = useState(false)
|
||||||
|
/** 加载更多中(首屏重置不展示,避免已渲染列表下方闪烁) */
|
||||||
|
const [specialLoading, setSpecialLoading] = useState(false)
|
||||||
|
/** 特价推荐请求序号(返回 tab 重置与上拉加载并发时,仅采用最后一次响应) */
|
||||||
|
const specialSeqRef = useRef(0)
|
||||||
|
/** 是否有「加载更多」请求进行中 */
|
||||||
|
const specialLoadingRef = useRef(false)
|
||||||
/** 搜索框输入 */
|
/** 搜索框输入 */
|
||||||
const [keyword, setKeyword] = useState('')
|
const [keyword, setKeyword] = useState('')
|
||||||
|
|
||||||
@@ -56,7 +68,13 @@ export default function IndexPage() {
|
|||||||
|
|
||||||
useDidShow(() => {
|
useDidShow(() => {
|
||||||
loadHomeConfig()
|
loadHomeConfig()
|
||||||
loadRecommend()
|
loadSpecials(1, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 上拉加载更多特价推荐 */
|
||||||
|
useReachBottom(() => {
|
||||||
|
if (!specialHasMore) return
|
||||||
|
loadSpecials(specialPage + 1, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 首页配置聚合数据(响应附带悬浮球汇总) */
|
/** 首页配置聚合数据(响应附带悬浮球汇总) */
|
||||||
@@ -71,15 +89,37 @@ export default function IndexPage() {
|
|||||||
}
|
}
|
||||||
}, [setSummary])
|
}, [setSummary])
|
||||||
|
|
||||||
/** 推荐商品 */
|
/**
|
||||||
const loadRecommend = useCallback(async () => {
|
* 特价推荐商品(reset 时回到第一页整体替换)。
|
||||||
|
* 行结构与 /mini/product/list 一致;响应附带悬浮球汇总
|
||||||
|
*/
|
||||||
|
const loadSpecials = useCallback(
|
||||||
|
async (pageNum: number, reset: boolean) => {
|
||||||
|
if (!reset && specialLoadingRef.current) return
|
||||||
|
const seq = ++specialSeqRef.current
|
||||||
|
specialLoadingRef.current = true
|
||||||
|
if (!reset) setSpecialLoading(true)
|
||||||
try {
|
try {
|
||||||
const res = await getProductListApi({ page: 1, pageSize: 10 })
|
const res = await getSpecialListApi({ page: pageNum, pageSize: SPECIAL_PAGE_SIZE })
|
||||||
setProducts(res.data.data)
|
if (seq !== specialSeqRef.current) return // 已有更新的请求,丢弃本次响应
|
||||||
|
const { data, total, cart } = res.data
|
||||||
|
setSpecials(prev => (reset ? data : [...prev, ...data]))
|
||||||
|
setSpecialPage(pageNum)
|
||||||
|
setSpecialHasMore(pageNum * SPECIAL_PAGE_SIZE < total)
|
||||||
|
// 列表响应附带悬浮球汇总(旧版本后端可能未返回)
|
||||||
|
const summary = normalizeSpecialCart(cart)
|
||||||
|
if (summary) setSummary(summary)
|
||||||
} catch {
|
} catch {
|
||||||
// 错误已由 request 层 toast
|
// 错误已由 request 层 toast
|
||||||
|
} finally {
|
||||||
|
if (seq === specialSeqRef.current) {
|
||||||
|
specialLoadingRef.current = false
|
||||||
|
setSpecialLoading(false)
|
||||||
}
|
}
|
||||||
}, [])
|
}
|
||||||
|
},
|
||||||
|
[setSummary],
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 后台配置的 link 统一跳转:
|
* 后台配置的 link 统一跳转:
|
||||||
@@ -117,9 +157,9 @@ export default function IndexPage() {
|
|||||||
goProduct(keyword.trim())
|
goProduct(keyword.trim())
|
||||||
}, [goProduct, keyword])
|
}, [goProduct, keyword])
|
||||||
|
|
||||||
/** 行内加减购确认后回写推荐商品项的购物车字段 */
|
/** 行内加减购确认后回写特价商品项的购物车字段 */
|
||||||
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
|
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
|
||||||
setProducts(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
|
setSpecials(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 快捷加购(用返回的购物车行回写,卡片随即展示加减器) */
|
/** 快捷加购(用返回的购物车行回写,卡片随即展示加减器) */
|
||||||
@@ -137,6 +177,16 @@ export default function IndexPage() {
|
|||||||
[addItem, handleRowSync],
|
[addItem, handleRowSync],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/** 无价格时点击:未登录引导登录,已登录但未设等级价提示原因 */
|
||||||
|
const handlePriceGuide = useCallback((e: any) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
if (getToken()) {
|
||||||
|
Taro.showToast({ title: '该商品暂未设置等级价', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
Taro.navigateTo({ url: '/pages/login/index' })
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='home-page'>
|
<View className='home-page'>
|
||||||
{/* ========== 自定义顶部导航栏 ========== */}
|
{/* ========== 自定义顶部导航栏 ========== */}
|
||||||
@@ -239,23 +289,24 @@ export default function IndexPage() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ========== 推荐商品 ========== */}
|
{/* ========== 特价推荐 ========== */}
|
||||||
<View className='home-recommend'>
|
<View className='home-recommend'>
|
||||||
<View className='home-recommend__header'>
|
<View className='home-recommend__header'>
|
||||||
<View className='home-recommend__title-wrap'>
|
<View className='home-recommend__title-wrap'>
|
||||||
<View className='home-recommend__title-bar' />
|
<View className='home-recommend__title-bar' />
|
||||||
<Text className='home-recommend__title'>推荐商品</Text>
|
<Text className='home-recommend__title'>特价推荐</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='home-recommend__more' onClick={() => goProduct()}>查看更多 ›</Text>
|
<Text className='home-recommend__more' onClick={() => goProduct()}>查看更多 ›</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{ products.length === 0 ? (
|
{ specials.length === 0 ? (
|
||||||
<View className='home-recommend__empty'>
|
<View className='home-recommend__empty'>
|
||||||
<Text className='home-recommend__empty-text'>暂无商品</Text>
|
<Text className='home-recommend__empty-text'>暂无特价商品</Text>
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
|
<>
|
||||||
<View className='product-grid'>
|
<View className='product-grid'>
|
||||||
{products.map(product => (
|
{specials.map(product => (
|
||||||
<View key={product.id} className='product-card' onClick={() => goDetail(product.id)}>
|
<View key={product.id} className='product-card' onClick={() => goDetail(product.id)}>
|
||||||
<Image
|
<Image
|
||||||
className='product-card__image'
|
className='product-card__image'
|
||||||
@@ -265,12 +316,24 @@ export default function IndexPage() {
|
|||||||
/>
|
/>
|
||||||
<View className='product-card__info'>
|
<View className='product-card__info'>
|
||||||
<Text className='product-card__name'>{product.name}</Text>
|
<Text className='product-card__name'>{product.name}</Text>
|
||||||
<Text className='product-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
|
<View className='product-card__spec'>
|
||||||
|
{formatSpec(product.spec, product.unit)}{' '}
|
||||||
|
<View>
|
||||||
|
{product.price !== null && <>
|
||||||
|
单价:{formatRetailPrice(product.price, product.spec)} {product.price_unit}
|
||||||
|
</>}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
<View className='product-card__bottom'>
|
<View className='product-card__bottom'>
|
||||||
{product.price !== null ? (
|
{product.price !== null ? (
|
||||||
<PriceText className='product-card__price' price={product.price} spec={product.spec} mode='block' />
|
<Text className='product-card__price'>¥{product.price}</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text className='product-card__price product-card__price--none'>登陆后查看价格</Text>
|
<Text
|
||||||
|
className='product-card__price product-card__price--none'
|
||||||
|
onClick={handlePriceGuide}
|
||||||
|
>
|
||||||
|
登录后查看价格
|
||||||
|
</Text>
|
||||||
)}
|
)}
|
||||||
{/* 已加购展示行内加减器,否则展示快捷加购按钮 */}
|
{/* 已加购展示行内加减器,否则展示快捷加购按钮 */}
|
||||||
{Number(product.cart_quantity ?? 0) > 0 ? (
|
{Number(product.cart_quantity ?? 0) > 0 ? (
|
||||||
@@ -285,11 +348,24 @@ export default function IndexPage() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
{/* 加载更多状态 */}
|
||||||
|
{specialLoading && (
|
||||||
|
<View className='home-recommend__loading'>
|
||||||
|
<Text className='home-recommend__loading-text'>加载中…</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{!specialHasMore && specialPage > 1 && (
|
||||||
|
<View className='home-recommend__loading'>
|
||||||
|
<Text className='home-recommend__loading-text'>已加载全部特价商品</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ========== 购物车悬浮球 ========== */}
|
{/* ========== 购物车悬浮球 ========== */}
|
||||||
<CartBall />
|
<CartBall />
|
||||||
|
{process.env.TARO_ENV === 'h5' && <CustomTabBar />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,8 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 30px;
|
font-size: 30px;
|
||||||
color: #323233;
|
color: #323233;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-input-placeholder {
|
.form-input-placeholder {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { View, Text, Button, Input } from '@tarojs/components'
|
import { View, Text, Button, Input } from '@tarojs/components'
|
||||||
import CustomNavBar from '@/components/NavBar'
|
|
||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
@@ -76,8 +75,6 @@ export default function LoginPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='login-page'>
|
<View className='login-page'>
|
||||||
{/* ========== 导航栏 ========== */}
|
|
||||||
<CustomNavBar title='登录' />
|
|
||||||
|
|
||||||
{/* ========== 内容区域 ========== */}
|
{/* ========== 内容区域 ========== */}
|
||||||
<View className='login-content'>
|
<View className='login-content'>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { formatTime } from '@/utils/format'
|
|||||||
import { NOTICE_TYPE_MAP } from '@/types/notice'
|
import { NOTICE_TYPE_MAP } from '@/types/notice'
|
||||||
import type { Notice, NoticeType } from '@/types/notice'
|
import type { Notice, NoticeType } from '@/types/notice'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
import CustomTabBar from "@/components/CustomTabBar";
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
@@ -123,6 +124,8 @@ export default function MessagePage() {
|
|||||||
{loggedIn && finished && notices.length > 0 && (
|
{loggedIn && finished && notices.length > 0 && (
|
||||||
<View className='message-loading'><Text>没有更多了</Text></View>
|
<View className='message-loading'><Text>没有更多了</Text></View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{process.env.TARO_ENV === 'h5' && <CustomTabBar />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Empty, Popup } from '@antmjs/vantui'
|
|||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||||
import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order'
|
import { cancelOrderApi, getOrderDetailApi, getOrderListApi } from '@/services/order'
|
||||||
import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order'
|
import { ORDER_STATUS_FILTERS, ORDER_STATUS_TEXT } from '@/types/order'
|
||||||
import { formatSpec, resolveFileUrl } from '@/utils/format'
|
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
|
||||||
import PriceText from '@/components/PriceText'
|
import PriceText from '@/components/PriceText'
|
||||||
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
|
import type { OrderDetail, OrderListItem, OrderStatus } from '@/types/order'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
@@ -264,10 +264,10 @@ export default function OrderListPage() {
|
|||||||
<Text className='detail-popup__item-name'>{item.product_name}</Text>
|
<Text className='detail-popup__item-name'>{item.product_name}</Text>
|
||||||
<Text className='detail-popup__item-spec'>
|
<Text className='detail-popup__item-spec'>
|
||||||
{formatSpec(item.product_spec, item.unit)}{' '}
|
{formatSpec(item.product_spec, item.unit)}{' '}
|
||||||
<PriceText price={item.price} spec={item.product_spec} /> × {item.quantity}
|
单价:{formatRetailPrice(item.price, item.product_spec)} {item.price_unit}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='detail-popup__item-amount'>¥{item.amount}</Text>
|
<Text className='detail-popup__item-amount'>¥{item.price} × {item.quantity}</Text>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
+174
-24
@@ -20,10 +20,68 @@ const MAX_VOUCHERS = 3
|
|||||||
/** 在线支付(调起 wx.requestPayment)仅微信小程序支持 */
|
/** 在线支付(调起 wx.requestPayment)仅微信小程序支持 */
|
||||||
const IS_WEAPP = process.env.TARO_ENV === 'weapp'
|
const IS_WEAPP = process.env.TARO_ENV === 'weapp'
|
||||||
|
|
||||||
/** 支付方式选项(在线支付仅小程序端展示,排在最前) */
|
/** H5 端处于微信内置浏览器时,可走公众号网页授权 + JSAPI 在线支付 */
|
||||||
|
const IS_H5_WECHAT =
|
||||||
|
process.env.TARO_ENV === 'h5' &&
|
||||||
|
typeof navigator !== 'undefined' &&
|
||||||
|
/micromessenger/i.test(navigator.userAgent)
|
||||||
|
|
||||||
|
/** 在线支付(旺铺网关 JSAPI)是否可用:小程序 / 微信内 H5 */
|
||||||
|
const ONLINE_PAY_AVAILABLE = IS_WEAPP || IS_H5_WECHAT
|
||||||
|
|
||||||
|
/** H5 公众号支付草稿存储 key(授权跳转前暂存账单选择,回跳后恢复) */
|
||||||
|
const H5_PAY_DRAFT_KEY = 'h5_online_pay_draft'
|
||||||
|
|
||||||
|
/** H5 公众号支付草稿(授权回跳页面重载,勾选状态经 sessionStorage 恢复) */
|
||||||
|
interface H5PayDraft {
|
||||||
|
bill_ids: number[]
|
||||||
|
remark?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调起公众号 JSAPI 支付(WeixinJSBridge 未注入时等待 WeixinJSBridgeReady 事件)
|
||||||
|
* resolve: 'ok' 支付成功 / 'cancel' 用户取消 / 'fail' 调起失败
|
||||||
|
*/
|
||||||
|
function invokeWechatJsapiPay(payParams: Record<string, any>): Promise<'ok' | 'cancel' | 'fail'> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const invoke = () => {
|
||||||
|
;(window as any).WeixinJSBridge.invoke(
|
||||||
|
'getBrandWCPayRequest',
|
||||||
|
{
|
||||||
|
appId: String(payParams.appId || ''),
|
||||||
|
timeStamp: String(payParams.timeStamp || ''),
|
||||||
|
nonceStr: String(payParams.nonceStr || ''),
|
||||||
|
package: String(payParams.package || ''),
|
||||||
|
signType: String(payParams.signType || 'RSA'),
|
||||||
|
paySign: String(payParams.paySign || ''),
|
||||||
|
},
|
||||||
|
(res: any) => {
|
||||||
|
const msg: string = res?.err_msg || ''
|
||||||
|
if (msg === 'get_brand_wcpay_request:ok') resolve('ok')
|
||||||
|
else if (msg === 'get_brand_wcpay_request:cancel') resolve('cancel')
|
||||||
|
else resolve('fail')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ((window as any).WeixinJSBridge) {
|
||||||
|
invoke()
|
||||||
|
} else {
|
||||||
|
document.addEventListener('WeixinJSBridgeReady', invoke, { once: true })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 支付方式选项(在线支付仅小程序/微信内 H5 展示,排在最前) */
|
||||||
const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc: string }> = [
|
const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc: string }> = [
|
||||||
...(IS_WEAPP
|
...(ONLINE_PAY_AVAILABLE
|
||||||
? [{ value: 4 as PayMethod, label: '微信在线支付', icon: 'wechat', desc: '小程序内直接付款,免上传凭证' }]
|
? [
|
||||||
|
{
|
||||||
|
value: 4 as PayMethod,
|
||||||
|
label: '微信在线支付',
|
||||||
|
icon: 'wechat',
|
||||||
|
desc: IS_WEAPP ? '小程序内直接付款,免上传凭证' : '微信内直接付款,免上传凭证',
|
||||||
|
},
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
{ value: 1, label: '微信支付', icon: 'wechat', desc: '扫码完成转账' },
|
{ value: 1, label: '微信支付', icon: 'wechat', desc: '扫码完成转账' },
|
||||||
{ value: 2, label: '支付宝', icon: 'alipay', desc: '扫码完成转账' },
|
{ value: 2, label: '支付宝', icon: 'alipay', desc: '扫码完成转账' },
|
||||||
@@ -32,24 +90,11 @@ const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起付款页(合并付款)
|
* 发起付款页(合并付款)
|
||||||
* 选择本店可付款账单(?payable=1)→ 选择支付方式 → 提交:
|
|
||||||
* - 在线支付(仅小程序):wx.login 取 code → 后端经旺铺网关下单 → 调起微信支付 → 主动查询同步结果(回调兜底)
|
|
||||||
* - 线下凭证:展示收款码 / 对公账户 → 上传汇款凭证 → 提交,后台审核
|
|
||||||
* 支持 ?ids=1,2 预选账单(账单详情页"去付款"跳转)
|
|
||||||
*/
|
*/
|
||||||
export default function PaymentPage() {
|
export default function PaymentPage() {
|
||||||
const router = useRouter()
|
|
||||||
const token = useAuthStore(s => s.token)
|
const token = useAuthStore(s => s.token)
|
||||||
const loggedIn = !!token
|
const loggedIn = !!token
|
||||||
|
|
||||||
/** 预选账单(路由参数,仅在首次加载时消费一次) */
|
|
||||||
const presetRef = useRef<number[] | null>(
|
|
||||||
(router.params.ids || '')
|
|
||||||
.split(',')
|
|
||||||
.map(Number)
|
|
||||||
.filter(n => n > 0),
|
|
||||||
)
|
|
||||||
|
|
||||||
const [bills, setBills] = useState<Bill[]>([])
|
const [bills, setBills] = useState<Bill[]>([])
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([])
|
const [selectedIds, setSelectedIds] = useState<number[]>([])
|
||||||
const [page, setPage] = useState(1)
|
const [page, setPage] = useState(1)
|
||||||
@@ -58,7 +103,7 @@ export default function PaymentPage() {
|
|||||||
const loadingRef = useRef(false)
|
const loadingRef = useRef(false)
|
||||||
|
|
||||||
const [config, setConfig] = useState<PaymentConfig | null>(null)
|
const [config, setConfig] = useState<PaymentConfig | null>(null)
|
||||||
const [payMethod, setPayMethod] = useState<PayMethod>(IS_WEAPP ? 4 : 1)
|
const [payMethod, setPayMethod] = useState<PayMethod>(ONLINE_PAY_AVAILABLE ? 4 : 1)
|
||||||
const [vouchers, setVouchers] = useState<UploadedFile[]>([])
|
const [vouchers, setVouchers] = useState<UploadedFile[]>([])
|
||||||
const [remark, setRemark] = useState('')
|
const [remark, setRemark] = useState('')
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
@@ -79,11 +124,7 @@ export default function PaymentPage() {
|
|||||||
setBills(prev => (reset ? data : [...prev, ...data]))
|
setBills(prev => (reset ? data : [...prev, ...data]))
|
||||||
setPage(pageNum)
|
setPage(pageNum)
|
||||||
setFinished(pageNum * PAGE_SIZE >= total)
|
setFinished(pageNum * PAGE_SIZE >= total)
|
||||||
if (presetRef.current) {
|
setSelectedIds(prev => Array.from(new Set([...prev, ...data.map(i => i.id)])))
|
||||||
const preset = presetRef.current
|
|
||||||
presetRef.current = null
|
|
||||||
setSelectedIds(prev => Array.from(new Set([...prev, ...preset])))
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// 错误已由 request 层 toast
|
// 错误已由 request 层 toast
|
||||||
} finally {
|
} finally {
|
||||||
@@ -264,14 +305,123 @@ export default function PaymentPage() {
|
|||||||
}
|
}
|
||||||
}, [submitting, selectedIds, remark, loadBills])
|
}, [submitting, selectedIds, remark, loadBills])
|
||||||
|
|
||||||
/** 提交入口:按支付方式分发 */
|
/**
|
||||||
|
* H5 公众号支付主流程(授权回跳后执行):
|
||||||
|
* 授权 code 下单(scene=mp,后端换付款人 openid)→ WeixinJSBridge 调起支付 → 主动查询同步结果
|
||||||
|
* 与小程序端一致:无论成功/取消都跳转支付详情(待支付单可在详情页刷新同步结果)
|
||||||
|
*/
|
||||||
|
const runH5OnlinePay = useCallback(
|
||||||
|
async (billIds: number[], code: string, remarkText?: string) => {
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
const res = await createOnlinePaymentApi({
|
||||||
|
bill_ids: billIds,
|
||||||
|
code,
|
||||||
|
scene: 'mp',
|
||||||
|
remark: remarkText,
|
||||||
|
})
|
||||||
|
const { id, payment_no, pay_params } = res.data
|
||||||
|
const result = await invokeWechatJsapiPay(pay_params)
|
||||||
|
if (result !== 'ok') {
|
||||||
|
// 用户取消或调起失败:账单仍锁定在支付单中,进详情页可刷新同步/稍后处理
|
||||||
|
Taro.showToast({
|
||||||
|
title: result === 'cancel' ? '已取消支付' : '支付调起失败,请稍后重试',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.redirectTo({ url: `/pages/payment-detail/index?id=${id}` })
|
||||||
|
}, 800)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 主动查询同步结果(网关后台通知延迟/丢失时的兜底结账)
|
||||||
|
let paid = false
|
||||||
|
try {
|
||||||
|
const q = await queryOnlinePaymentApi(payment_no)
|
||||||
|
paid = q.data.status === 1
|
||||||
|
} catch {
|
||||||
|
// 查询失败不阻断,进详情页可手动刷新
|
||||||
|
}
|
||||||
|
Taro.showToast({ title: paid ? '支付成功' : '支付结果确认中', icon: paid ? 'success' : 'none' })
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.redirectTo({ url: `/pages/payment-detail/index?id=${id}` })
|
||||||
|
}, 800)
|
||||||
|
} catch {
|
||||||
|
// 下单失败:账单可能已被其他端付款/锁定,刷新列表
|
||||||
|
loadBills(1, true)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[loadBills],
|
||||||
|
)
|
||||||
|
|
||||||
|
/** H5 公众号支付:处理微信授权回跳(URL 携带 code 且本地存在支付草稿时自动继续支付) */
|
||||||
|
const h5CallbackRef = useRef(false)
|
||||||
|
useEffect(() => {
|
||||||
|
if (!IS_H5_WECHAT || h5CallbackRef.current || !loggedIn) return
|
||||||
|
const code = new URLSearchParams(window.location.search).get('code')
|
||||||
|
if (!code) return
|
||||||
|
// 清理地址栏授权参数,避免刷新/分享带出已失效的 code
|
||||||
|
window.history.replaceState(null, '', window.location.pathname)
|
||||||
|
let draft: H5PayDraft | null = null
|
||||||
|
try {
|
||||||
|
draft = JSON.parse(window.sessionStorage.getItem(H5_PAY_DRAFT_KEY) || 'null')
|
||||||
|
window.sessionStorage.removeItem(H5_PAY_DRAFT_KEY)
|
||||||
|
} catch {
|
||||||
|
draft = null
|
||||||
|
}
|
||||||
|
if (!draft || !Array.isArray(draft.bill_ids) || draft.bill_ids.length === 0) return
|
||||||
|
h5CallbackRef.current = true
|
||||||
|
setSelectedIds(draft.bill_ids)
|
||||||
|
if (draft.remark) setRemark(draft.remark)
|
||||||
|
runH5OnlinePay(draft.bill_ids, code, draft.remark)
|
||||||
|
}, [loggedIn, runH5OnlinePay])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* H5 公众号支付入口:暂存支付草稿 → 跳转微信网页授权(snsapi_base 静默授权)
|
||||||
|
* 授权后回跳本页携带 code,由上方 effect 恢复草稿并继续支付
|
||||||
|
*/
|
||||||
|
const handleH5OnlinePay = useCallback(async () => {
|
||||||
|
if (submitting) return
|
||||||
|
if (selectedIds.length === 0) {
|
||||||
|
Taro.showToast({ title: '请选择要付款的账单', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
// mp_appid 可能尚未加载完成,兜底重新拉取
|
||||||
|
let appid = config?.mp_appid
|
||||||
|
if (!appid) {
|
||||||
|
const res = await getPaymentConfigApi()
|
||||||
|
setConfig(res.data)
|
||||||
|
appid = res.data.mp_appid
|
||||||
|
}
|
||||||
|
if (!appid) {
|
||||||
|
Taro.showToast({ title: '公众号支付暂未开通,请选择其他支付方式', icon: 'none' })
|
||||||
|
setSubmitting(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const draft: H5PayDraft = { bill_ids: selectedIds, remark: remark.trim() || undefined }
|
||||||
|
window.sessionStorage.setItem(H5_PAY_DRAFT_KEY, JSON.stringify(draft))
|
||||||
|
const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname)
|
||||||
|
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base#wechat_redirect`
|
||||||
|
} catch {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}, [submitting, selectedIds, remark, config])
|
||||||
|
|
||||||
|
/** 提交入口:按支付方式与端分发(小程序 wx.requestPayment / H5 公众号 JSAPI / 线下凭证) */
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
if (isOnline) {
|
if (isOnline) {
|
||||||
|
if (IS_H5_WECHAT) {
|
||||||
|
handleH5OnlinePay()
|
||||||
|
} else {
|
||||||
handleOnlinePay()
|
handleOnlinePay()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
handleVoucherSubmit()
|
handleVoucherSubmit()
|
||||||
}
|
}
|
||||||
}, [isOnline, handleOnlinePay, handleVoucherSubmit])
|
}, [isOnline, handleOnlinePay, handleH5OnlinePay, handleVoucherSubmit])
|
||||||
|
|
||||||
/** 当前支付方式的收款展示 */
|
/** 当前支付方式的收款展示 */
|
||||||
const renderMethodContent = () => {
|
const renderMethodContent = () => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { View, Text, ScrollView } from '@tarojs/components'
|
import { View, Text, ScrollView } from '@tarojs/components'
|
||||||
import CustomNavBar from '@/components/NavBar'
|
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +8,6 @@ import './index.less'
|
|||||||
export default function PrivacyPage() {
|
export default function PrivacyPage() {
|
||||||
return (
|
return (
|
||||||
<View className='privacy-page'>
|
<View className='privacy-page'>
|
||||||
<CustomNavBar title='隐私政策' />
|
|
||||||
<ScrollView scrollY className='privacy-scroll'>
|
<ScrollView scrollY className='privacy-scroll'>
|
||||||
<View className='privacy-content'>
|
<View className='privacy-content'>
|
||||||
<Text className='doc-title'>隐私政策</Text>
|
<Text className='doc-title'>隐私政策</Text>
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { Empty, Stepper, Swiper, SwiperItem } from '@antmjs/vantui'
|
|||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||||
import useCartStore from '@/stores/cart/useCartStore'
|
import useCartStore from '@/stores/cart/useCartStore'
|
||||||
import { getProductDetailApi } from '@/services/product'
|
import { getProductDetailApi } from '@/services/product'
|
||||||
import { formatSpec, resolveFileUrl } from '@/utils/format'
|
import {formatRetailPrice, formatSpec, resolveFileUrl} from '@/utils/format'
|
||||||
import PriceText from '@/components/PriceText'
|
|
||||||
import type { Product } from '@/types/product'
|
import type { Product } from '@/types/product'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
@@ -116,7 +115,7 @@ export default function ProductDetailPage() {
|
|||||||
<View className='goods-card'>
|
<View className='goods-card'>
|
||||||
<View className='goods-card__price-row'>
|
<View className='goods-card__price-row'>
|
||||||
{product.price !== null ? (
|
{product.price !== null ? (
|
||||||
<PriceText className='goods-card__price' price={product.price} spec={product.spec} />
|
<Text className='goods-card__price'>¥{product.price}</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text className='goods-card__price goods-card__price--none'>
|
<Text className='goods-card__price goods-card__price--none'>
|
||||||
{loggedIn ? '价格待定' : '登录后查看价格'}
|
{loggedIn ? '价格待定' : '登录后查看价格'}
|
||||||
@@ -124,7 +123,12 @@ export default function ProductDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
<Text className='goods-card__name'>{product.name}</Text>
|
<Text className='goods-card__name'>{product.name}</Text>
|
||||||
<Text className='goods-card__spec'>{formatSpec(product.spec, product.unit)}</Text>
|
<Text className='goods-card__spec'>
|
||||||
|
{formatSpec(product.spec, product.unit)}{' '}
|
||||||
|
{product.price !== null && <>
|
||||||
|
单价:{formatRetailPrice(product.price, product.spec)} {product.price_unit}
|
||||||
|
</>}
|
||||||
|
</Text>
|
||||||
<View className='goods-card__meta'>
|
<View className='goods-card__meta'>
|
||||||
{!!product.shelf_life && product.shelf_life > 0 && (
|
{!!product.shelf_life && product.shelf_life > 0 && (
|
||||||
<Text className='goods-card__tag'>保质期 {product.shelf_life} 天</Text>
|
<Text className='goods-card__tag'>保质期 {product.shelf_life} 天</Text>
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
// 底部预留给自定义 tabBar(110rpx + 安全区),避免列表被遮挡
|
|
||||||
padding-bottom: calc(110rpx + env(safe-area-inset-bottom));
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.product-search {
|
.product-search {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -114,8 +111,8 @@
|
|||||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
&__image {
|
&__image {
|
||||||
width: 160rpx;
|
width: 180rpx;
|
||||||
height: 160rpx;
|
height: 180rpx;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
background: #f2f3f5;
|
background: #f2f3f5;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@@ -139,7 +136,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__spec {
|
&__spec {
|
||||||
margin-top: 10rpx;
|
|
||||||
font-size: 24rpx;
|
font-size: 24rpx;
|
||||||
color: #969799;
|
color: #969799;
|
||||||
}
|
}
|
||||||
@@ -164,8 +160,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__add {
|
&__add {
|
||||||
width: 56rpx;
|
width: 42rpx;
|
||||||
height: 56rpx;
|
height: 42rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ee0a24;
|
background: #ee0a24;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
+20
-81
@@ -1,17 +1,17 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import Taro, { useDidShow } from '@tarojs/taro'
|
import Taro, { useDidShow } from '@tarojs/taro'
|
||||||
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
import { View, Text, Image, ScrollView } from '@tarojs/components'
|
||||||
import { Button, Empty, Popup, Search, Stepper } from '@antmjs/vantui'
|
import { Empty, Search } from '@antmjs/vantui'
|
||||||
import useCartStore from '@/stores/cart/useCartStore'
|
import useCartStore from '@/stores/cart/useCartStore'
|
||||||
import { getCategoriesApi, getProductListApi } from '@/services/product'
|
import { getCategoriesApi, getProductListApi } from '@/services/product'
|
||||||
import type { ProductListParams } from '@/services/product'
|
import type { ProductListParams } from '@/services/product'
|
||||||
import { getProductCover } from '@/types/product'
|
import { getProductCover } from '@/types/product'
|
||||||
import type { Category, Product, ProductCartPatch } from '@/types/product'
|
import type { Category, Product, ProductCartPatch } from '@/types/product'
|
||||||
import PriceText from '@/components/PriceText'
|
|
||||||
import CartBall from '@/components/CartBall'
|
import CartBall from '@/components/CartBall'
|
||||||
import CartStepper from '@/components/CartStepper'
|
import CartStepper from '@/components/CartStepper'
|
||||||
import { formatSpec } from '@/utils/format'
|
import {formatRetailPrice, formatSpec} from '@/utils/format'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
import CustomTabBar from "@/components/CustomTabBar";
|
||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
/** 存储 key:首页点击分类/搜索跳转时经本地存储传参(switchTab 无法带参) */
|
/** 存储 key:首页点击分类/搜索跳转时经本地存储传参(switchTab 无法带参) */
|
||||||
@@ -43,12 +43,6 @@ export default function ProductPage() {
|
|||||||
/** 是否有请求进行中(仅用于避免"加载更多"并发) */
|
/** 是否有请求进行中(仅用于避免"加载更多"并发) */
|
||||||
const loadingRef = useRef(false)
|
const loadingRef = useRef(false)
|
||||||
|
|
||||||
/** 加购弹层 */
|
|
||||||
const [showPopup, setShowPopup] = useState(false)
|
|
||||||
const [current, setCurrent] = useState<Product | null>(null)
|
|
||||||
const [qty, setQty] = useState(1)
|
|
||||||
const addingRef = useRef(false)
|
|
||||||
|
|
||||||
/** 当前选中二级分类所属的一级分类ID(用于父级高亮) */
|
/** 当前选中二级分类所属的一级分类ID(用于父级高亮) */
|
||||||
const activeParentId = useMemo(() => {
|
const activeParentId = useMemo(() => {
|
||||||
if (activeId == null) return null
|
if (activeId == null) return null
|
||||||
@@ -191,13 +185,6 @@ export default function ProductPage() {
|
|||||||
setSearchKey('')
|
setSearchKey('')
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 打开加购弹层 */
|
|
||||||
const handleAddTap = useCallback((product: Product) => {
|
|
||||||
setCurrent(product)
|
|
||||||
setQty(1)
|
|
||||||
setShowPopup(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
/** 行内加减购确认后回写列表项的购物车字段 */
|
/** 行内加减购确认后回写列表项的购物车字段 */
|
||||||
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
|
const handleRowSync = useCallback((productId: number, patch: ProductCartPatch) => {
|
||||||
setProducts(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
|
setProducts(prev => prev.map(p => (p.id === productId ? { ...p, ...patch } : p)))
|
||||||
@@ -209,20 +196,15 @@ export default function ProductPage() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 确认加购(用返回的购物车行回写列表项,行内随即展示加减器) */
|
/** 确认加购(用返回的购物车行回写列表项,行内随即展示加减器) */
|
||||||
const handleConfirmAdd = useCallback(async () => {
|
const handleConfirmAdd = useCallback(async (product: Product) => {
|
||||||
if (!current || addingRef.current) return
|
|
||||||
addingRef.current = true
|
|
||||||
try {
|
try {
|
||||||
const res = await addItem(current.id, qty)
|
const res = await addItem(product.id, 1)
|
||||||
handleRowSync(current.id, { cart_id: res.id, cart_quantity: res.quantity })
|
handleRowSync(product.id, { cart_id: res.id, cart_quantity: res.quantity })
|
||||||
Taro.showToast({ title: '已加入购物车', icon: 'success' })
|
// Taro.showToast({ title: '已加入购物车', icon: 'success' })
|
||||||
setShowPopup(false)
|
|
||||||
} catch {
|
} catch {
|
||||||
// 错误(未设等级价/数量上限)已由 request 层 toast
|
|
||||||
} finally {
|
|
||||||
addingRef.current = false
|
|
||||||
}
|
}
|
||||||
}, [current, qty, addItem, handleRowSync])
|
}, [addItem, handleRowSync])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className='product-page'>
|
<View className='product-page'>
|
||||||
@@ -307,10 +289,17 @@ export default function ProductPage() {
|
|||||||
/>
|
/>
|
||||||
<View className='product-item__info'>
|
<View className='product-item__info'>
|
||||||
<Text className='product-item__name'>{product.name}</Text>
|
<Text className='product-item__name'>{product.name}</Text>
|
||||||
<Text className='product-item__spec'>{formatSpec(product.spec, product.unit)}</Text>
|
<Text className='product-item__spec'>
|
||||||
|
{formatSpec(product.spec, product.unit)}{' '}
|
||||||
|
<View>
|
||||||
|
{product.price !== null && <>
|
||||||
|
单价:{formatRetailPrice(product.price, product.spec)} {product.price_unit}
|
||||||
|
</>}
|
||||||
|
</View>
|
||||||
|
</Text>
|
||||||
<View className='product-item__bottom'>
|
<View className='product-item__bottom'>
|
||||||
{product.price !== null ? (
|
{product.price !== null ? (
|
||||||
<PriceText className='product-item__price' price={product.price} spec={product.spec} mode='block' />
|
<Text className='product-item__price'>¥{product.price}</Text>
|
||||||
) : (
|
) : (
|
||||||
<Text className='product-item__price product-item__price--none'>价格待定</Text>
|
<Text className='product-item__price product-item__price--none'>价格待定</Text>
|
||||||
)}
|
)}
|
||||||
@@ -322,7 +311,7 @@ export default function ProductPage() {
|
|||||||
className='product-item__add'
|
className='product-item__add'
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
handleAddTap(product)
|
handleConfirmAdd(product)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Text className='product-item__add-icon'>+</Text>
|
<Text className='product-item__add-icon'>+</Text>
|
||||||
@@ -343,59 +332,9 @@ export default function ProductPage() {
|
|||||||
</ScrollView>
|
</ScrollView>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ========== 加购弹层 ========== */}
|
|
||||||
<Popup
|
|
||||||
show={showPopup}
|
|
||||||
position='bottom'
|
|
||||||
round
|
|
||||||
closeable
|
|
||||||
closeOnClickOverlay
|
|
||||||
safeAreaInsetBottom
|
|
||||||
style={{ paddingBottom: '110px' }}
|
|
||||||
onClose={() => setShowPopup(false)}
|
|
||||||
>
|
|
||||||
{current && (
|
|
||||||
<View className='add-popup'>
|
|
||||||
<View className='add-popup__product'>
|
|
||||||
<Image
|
|
||||||
className='add-popup__image'
|
|
||||||
src={getProductCover(current) || 'https://img.yzcdn.cn/vant/cat.jpeg'}
|
|
||||||
mode='aspectFill'
|
|
||||||
/>
|
|
||||||
<View className='add-popup__info'>
|
|
||||||
<Text className='add-popup__name'>{current.name}</Text>
|
|
||||||
<Text className='add-popup__spec'>{formatSpec(current.spec, current.unit)}</Text>
|
|
||||||
{current.price !== null ? (
|
|
||||||
<PriceText className='add-popup__price' price={current.price} spec={current.spec} />
|
|
||||||
) : (
|
|
||||||
<Text className='add-popup__price add-popup__price--none'>价格待定</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View className='add-popup__row'>
|
|
||||||
<Text className='add-popup__label'>购买数量</Text>
|
|
||||||
<Stepper
|
|
||||||
value={qty}
|
|
||||||
min={1}
|
|
||||||
max={99999999.99}
|
|
||||||
onChange={e => setQty(Number(e.detail))}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<Button
|
|
||||||
type='danger'
|
|
||||||
block
|
|
||||||
round
|
|
||||||
className='add-popup__submit'
|
|
||||||
onClick={handleConfirmAdd}
|
|
||||||
>
|
|
||||||
加入购物车
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Popup>
|
|
||||||
|
|
||||||
{/* ========== 购物车悬浮球 ========== */}
|
{/* ========== 购物车悬浮球 ========== */}
|
||||||
<CartBall />
|
<CartBall />
|
||||||
|
{process.env.TARO_ENV === 'h5' && <CustomTabBar />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type { BillSummary } from '@/services/bill'
|
|||||||
import { ORDER_NAV_ITEMS } from '@/types/order'
|
import { ORDER_NAV_ITEMS } from '@/types/order'
|
||||||
import { resolveAvatarUrl } from '@/utils/format'
|
import { resolveAvatarUrl } from '@/utils/format'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
import CustomTabBar from "@/components/CustomTabBar";
|
||||||
|
|
||||||
/** 菜单项(订单/账单入口已由上方专区承载,后续单独页面开发时在此追加) */
|
/** 菜单项(订单/账单入口已由上方专区承载,后续单独页面开发时在此追加) */
|
||||||
const MENU_ITEMS = [
|
const MENU_ITEMS = [
|
||||||
@@ -229,6 +230,8 @@ export default function ProfilePage() {
|
|||||||
<Text>退出登录</Text>
|
<Text>退出登录</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{process.env.TARO_ENV === 'h5' && <CustomTabBar />}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,9 +173,13 @@ export default function ReportPage() {
|
|||||||
</View>
|
</View>
|
||||||
<View className='report-item__row'>
|
<View className='report-item__row'>
|
||||||
<Text className='report-item__spec'>
|
<Text className='report-item__spec'>
|
||||||
{item.product_spec ? `${item.product_spec} · ` : ''}
|
{item.product_spec ? `${item.product_spec}` : ''}{item.unit}
|
||||||
累计 {item.quantity}{item.unit}
|
</Text>
|
||||||
{parseFloat(item.weight) > 0 ? ` · 称重 ${item.weight}` : ''}
|
</View>
|
||||||
|
<View className='report-item__row'>
|
||||||
|
<Text className='report-item__spec'>
|
||||||
|
累计 {item.quantity}
|
||||||
|
{parseFloat(item.weight) > 0 ? ` · 重量 ${item.weight}` : ''}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className='report-item__percent'>{item.percent}%</Text>
|
<Text className='report-item__percent'>{item.percent}%</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export interface Bill {
|
|||||||
settlement_date: string
|
settlement_date: string
|
||||||
/** 付款时间(已支付时非空) */
|
/** 付款时间(已支付时非空) */
|
||||||
paid_at: string | null
|
paid_at: string | null
|
||||||
|
/** 售后金额 */
|
||||||
|
after_sale: string
|
||||||
/** 付款备注 */
|
/** 付款备注 */
|
||||||
pay_remark: string
|
pay_remark: string
|
||||||
/** 账单备注 */
|
/** 账单备注 */
|
||||||
@@ -72,6 +74,8 @@ export interface BillItem {
|
|||||||
amount: string
|
amount: string
|
||||||
/** 商品首图 URL(无图为空字符串) */
|
/** 商品首图 URL(无图为空字符串) */
|
||||||
image: string
|
image: string
|
||||||
|
price_unit: string
|
||||||
|
spec: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 账单关联订单 */
|
/** 账单关联订单 */
|
||||||
|
|||||||
+12
-3
@@ -42,6 +42,8 @@ export interface PaymentConfig {
|
|||||||
wechat_qrcode: string
|
wechat_qrcode: string
|
||||||
alipay_qrcode: string
|
alipay_qrcode: string
|
||||||
bank_info: string
|
bank_info: string
|
||||||
|
/** 公众号 appid(H5 网页授权取 code 拼授权链接用,配置了公众号支付才返回) */
|
||||||
|
mp_appid?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 支付记录(列表行与详情的 payment 字段一致) */
|
/** 支付记录(列表行与详情的 payment 字段一致) */
|
||||||
@@ -123,7 +125,7 @@ export function getPaymentDetailApi(id: number) {
|
|||||||
return get<PaymentDetail>(`/mini/payment/${id}`)
|
return get<PaymentDetail>(`/mini/payment/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 在线支付下单返回(pay_params 为旺铺网关透传的 wx.requestPayment 调起参数,以网关实际返回为准) */
|
/** 在线支付下单返回(pay_params 为旺铺网关透传的调起参数:小程序给 wx.requestPayment,H5 公众号给 getBrandWCPayRequest,以网关实际返回为准) */
|
||||||
export interface OnlinePaymentCreateResult {
|
export interface OnlinePaymentCreateResult {
|
||||||
id: number
|
id: number
|
||||||
/** 支付单号(ZF 前缀,= 上送网关的商户订单号 mer_order_id),查询/对账用 */
|
/** 支付单号(ZF 前缀,= 上送网关的商户订单号 mer_order_id),查询/对账用 */
|
||||||
@@ -131,6 +133,7 @@ export interface OnlinePaymentCreateResult {
|
|||||||
/** 应付金额(= 所选账单总额合计,元) */
|
/** 应付金额(= 所选账单总额合计,元) */
|
||||||
amount: string
|
amount: string
|
||||||
pay_params: {
|
pay_params: {
|
||||||
|
appId?: string
|
||||||
timeStamp?: string
|
timeStamp?: string
|
||||||
nonceStr?: string
|
nonceStr?: string
|
||||||
package?: string
|
package?: string
|
||||||
@@ -152,9 +155,15 @@ export interface OnlinePaymentQueryResult {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起在线支付(合并账单下单):POST /mini/payment/online
|
* 发起在线支付(合并账单下单):POST /mini/payment/online
|
||||||
* code 为 wx.login() 返回的登录凭证(后端换付款人 openid)
|
* code:小程序传 wx.login() 登录凭证(不传 scene);
|
||||||
|
* H5 公众号传网页授权回调 code,需带 scene: 'mp'(后端按场景换付款人 openid)
|
||||||
*/
|
*/
|
||||||
export function createOnlinePaymentApi(data: { bill_ids: number[]; code: string; remark?: string }) {
|
export function createOnlinePaymentApi(data: {
|
||||||
|
bill_ids: number[]
|
||||||
|
code: string
|
||||||
|
scene?: 'mp'
|
||||||
|
remark?: string
|
||||||
|
}) {
|
||||||
return post<OnlinePaymentCreateResult>('/mini/payment/online', data)
|
return post<OnlinePaymentCreateResult>('/mini/payment/online', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { get } from '@/utils/request'
|
||||||
|
import type { PaginatedData } from '@/types/api'
|
||||||
|
import type { CartSummary } from '@/types/cart'
|
||||||
|
import type { Product } from '@/types/product'
|
||||||
|
|
||||||
|
/** 特价推荐列表参数 */
|
||||||
|
export interface SpecialListParams {
|
||||||
|
page?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 特价推荐列表响应(分页 + 购物车悬浮球汇总)。
|
||||||
|
* 行结构与 /mini/product/list 一致:商品基础字段 + price + cart_id/cart_quantity
|
||||||
|
*/
|
||||||
|
export interface SpecialListData extends PaginatedData<Product> {
|
||||||
|
/**
|
||||||
|
* 购物车悬浮球汇总(未登录返回零值结构;旧版本后端可能未返回,调用方判空)。
|
||||||
|
* 接口文档示例为 count/quantity/amount 简写,与 /mini/home 等接口的 total_* 命名不一致,
|
||||||
|
* 统一经 normalizeSpecialCart 转换后再写入 store
|
||||||
|
*/
|
||||||
|
cart?: CartSummary | { count: number; quantity: string; amount: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 特价推荐商品列表(免登录;携带门店 token 时返回等级价与购物车字段):GET /mini/special/list */
|
||||||
|
export function getSpecialListApi(params: SpecialListParams = {}) {
|
||||||
|
return get<SpecialListData>('/mini/special/list', { data: params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 特价推荐响应附带的悬浮球汇总归一化(兼容 count/quantity/amount 与 total_* 两种命名) */
|
||||||
|
export function normalizeSpecialCart(cart: SpecialListData['cart']): CartSummary | null {
|
||||||
|
if (!cart) return null
|
||||||
|
const raw = cart as Record<string, unknown>
|
||||||
|
const count = raw.total_count ?? raw.count
|
||||||
|
const quantity = raw.total_quantity ?? raw.quantity
|
||||||
|
const amount = raw.total_amount ?? raw.amount
|
||||||
|
if (count == null || quantity == null || amount == null) return null
|
||||||
|
return {
|
||||||
|
total_count: Number(count),
|
||||||
|
total_quantity: String(quantity),
|
||||||
|
total_amount: String(amount),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ export interface CartItem {
|
|||||||
amount: string | null
|
amount: string | null
|
||||||
/** 1 可购 / 0 商品下架、缺失或未设等级价 */
|
/** 1 可购 / 0 商品下架、缺失或未设等级价 */
|
||||||
status: number
|
status: number
|
||||||
|
price_unit: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 购物车列表数据 */
|
/** 购物车列表数据 */
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ export interface OrderItem {
|
|||||||
product_name: string
|
product_name: string
|
||||||
product_spec: string
|
product_spec: string
|
||||||
unit: string
|
unit: string
|
||||||
|
price_unit: string
|
||||||
/** 下单时门店等级实际价快照 */
|
/** 下单时门店等级实际价快照 */
|
||||||
price: string
|
price: string
|
||||||
quantity: number
|
quantity: number
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface Product {
|
|||||||
content: string
|
content: string
|
||||||
/** 当前门店等级的实际销售价(未登录/未绑店/未设等级为 null) */
|
/** 当前门店等级的实际销售价(未登录/未绑店/未设等级为 null) */
|
||||||
price: string | null
|
price: string | null
|
||||||
|
price_unit: string | null
|
||||||
images_arr: ProductImage[]
|
images_arr: ProductImage[]
|
||||||
/** 所属分类(详情接口 with 返回) */
|
/** 所属分类(详情接口 with 返回) */
|
||||||
category?: { id: number; name: string } | null
|
category?: { id: number; name: string } | null
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const LOGIN_PATH = '/pages/login/index'
|
|||||||
/** 默认请求超时(ms) */
|
/** 默认请求超时(ms) */
|
||||||
const DEFAULT_TIMEOUT = 15000
|
const DEFAULT_TIMEOUT = 15000
|
||||||
/** 接口根地址(uploadFile 等原生请求同样使用) */
|
/** 接口根地址(uploadFile 等原生请求同样使用) */
|
||||||
// export const BASE_URL = "http://localhost:8000/index.php"
|
// export const BASE_URL = "http://localhost:8000"
|
||||||
export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user