商品列表状态保存
This commit is contained in:
+18
-89
@@ -4,11 +4,6 @@
|
|||||||
padding: 20rpx 24rpx 60rpx;
|
padding: 20rpx 24rpx 60rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
// 多选模式为底部导出栏留出空间
|
|
||||||
&--select {
|
|
||||||
padding-bottom: 160rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部待支付汇总栏留出空间
|
// 底部待支付汇总栏留出空间
|
||||||
&--pay {
|
&--pay {
|
||||||
padding-bottom: 180rpx;
|
padding-bottom: 180rpx;
|
||||||
@@ -55,7 +50,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 状态筛选 + 导出入口 =====
|
// ===== 状态筛选 =====
|
||||||
.bill-toolbar {
|
.bill-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -67,22 +62,6 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__export {
|
|
||||||
flex-shrink: 0;
|
|
||||||
margin-left: 16rpx;
|
|
||||||
padding: 12rpx 28rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
background: #fff;
|
|
||||||
border: 1rpx solid #ee0a24;
|
|
||||||
color: #ee0a24;
|
|
||||||
font-size: 26rpx;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: #ee0a24;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-scroll {
|
.status-scroll {
|
||||||
@@ -132,11 +111,6 @@
|
|||||||
padding: 24rpx;
|
padding: 24rpx;
|
||||||
margin-bottom: 16rpx;
|
margin-bottom: 16rpx;
|
||||||
|
|
||||||
&--select {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -192,7 +166,24 @@
|
|||||||
font-size: 32rpx;
|
font-size: 32rpx;
|
||||||
color: #323233;
|
color: #323233;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧栏:金额 + 导出按钮
|
||||||
|
&__side {
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__export {
|
||||||
|
margin-top: 12rpx;
|
||||||
|
padding: 6rpx 24rpx;
|
||||||
|
border-radius: 999rpx;
|
||||||
|
border: 1rpx solid #ee0a24;
|
||||||
|
color: #ee0a24;
|
||||||
|
font-size: 22rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
@@ -213,68 +204,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== 勾选圆圈 =====
|
|
||||||
.bill-check {
|
|
||||||
width: 36rpx;
|
|
||||||
height: 36rpx;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2rpx solid #c8c9cc;
|
|
||||||
margin-right: 20rpx;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
&.on {
|
|
||||||
background: #ee0a24;
|
|
||||||
border-color: #ee0a24;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 导出操作栏 =====
|
|
||||||
.export-bar {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 99;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06);
|
|
||||||
|
|
||||||
&__count {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #646566;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__all {
|
|
||||||
padding: 12rpx 24rpx;
|
|
||||||
margin-right: 16rpx;
|
|
||||||
border: 1rpx solid #dcdee0;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
font-size: 26rpx;
|
|
||||||
color: #323233;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__btn {
|
|
||||||
padding: 14rpx 40rpx;
|
|
||||||
border-radius: 999rpx;
|
|
||||||
background: #ee0a24;
|
|
||||||
color: #fff;
|
|
||||||
font-size: 28rpx;
|
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===== 分类选择弹层 =====
|
// ===== 分类选择弹层 =====
|
||||||
.category-popup {
|
.category-popup {
|
||||||
padding: 32rpx 32rpx 24rpx;
|
padding: 32rpx 32rpx 24rpx;
|
||||||
|
|||||||
+61
-124
@@ -11,9 +11,6 @@ import './index.less'
|
|||||||
|
|
||||||
const PAGE_SIZE = 10
|
const PAGE_SIZE = 10
|
||||||
|
|
||||||
/** 单次导出上限(后端限制 1~100 张) */
|
|
||||||
const EXPORT_MAX = 100
|
|
||||||
|
|
||||||
/** 状态筛选(接口 status 为原始支付状态:0 未支付含审核中 / 1 已支付) */
|
/** 状态筛选(接口 status 为原始支付状态:0 未支付含审核中 / 1 已支付) */
|
||||||
const STATUS_FILTERS: Array<{ value: 0 | 1 | undefined; label: string }> = [
|
const STATUS_FILTERS: Array<{ value: 0 | 1 | undefined; label: string }> = [
|
||||||
{ value: undefined, label: '全部' },
|
{ value: undefined, label: '全部' },
|
||||||
@@ -30,7 +27,7 @@ interface CategoryOption {
|
|||||||
/**
|
/**
|
||||||
* 账单列表页
|
* 账单列表页
|
||||||
* 采购单完成后由后台按门店生成(只读);底部汇总栏为门店口径待支付汇总(含审核中,不受筛选影响)
|
* 采购单完成后由后台按门店生成(只读);底部汇总栏为门店口径待支付汇总(含审核中,不受筛选影响)
|
||||||
* 支持多选账单合并导出 Excel(可按一级分类过滤商品明细)
|
* 每个账单可单独导出 Excel(可按一级分类过滤商品明细),不支持合并导出
|
||||||
*/
|
*/
|
||||||
export default function BillListPage() {
|
export default function BillListPage() {
|
||||||
const token = useAuthStore(s => s.token)
|
const token = useAuthStore(s => s.token)
|
||||||
@@ -43,9 +40,8 @@ export default function BillListPage() {
|
|||||||
const [finished, setFinished] = useState(false)
|
const [finished, setFinished] = useState(false)
|
||||||
const loadingRef = useRef(false)
|
const loadingRef = useRef(false)
|
||||||
|
|
||||||
/** 导出:多选模式 + 已选账单 + 分类弹层 */
|
/** 导出:待导出账单 + 分类弹层 */
|
||||||
const [selectMode, setSelectMode] = useState(false)
|
const [exportBill, setExportBill] = useState<Bill | null>(null)
|
||||||
const [selectedIds, setSelectedIds] = useState<number[]>([])
|
|
||||||
const [showCategory, setShowCategory] = useState(false)
|
const [showCategory, setShowCategory] = useState(false)
|
||||||
const [categories, setCategories] = useState<CategoryOption[]>([])
|
const [categories, setCategories] = useState<CategoryOption[]>([])
|
||||||
|
|
||||||
@@ -107,72 +103,35 @@ export default function BillListPage() {
|
|||||||
Taro.navigateTo({ url: '/pages/payment/index' })
|
Taro.navigateTo({ url: '/pages/payment/index' })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 进入/退出多选导出模式 */
|
/** 打开分类选择弹层(首次加载分类树根节点) */
|
||||||
const toggleSelectMode = useCallback(() => {
|
const handleExportTap = useCallback(
|
||||||
setSelectMode(prev => !prev)
|
async (bill: Bill) => {
|
||||||
setSelectedIds([])
|
if (categories.length === 0) {
|
||||||
}, [])
|
try {
|
||||||
|
const res = await getCategoriesApi()
|
||||||
/** 账单点击:多选模式切换勾选,否则进详情 */
|
setCategories([
|
||||||
const handleItemTap = useCallback(
|
{ id: 0, name: '全部分类' },
|
||||||
(bill: Bill) => {
|
...res.data.map(c => ({ id: c.id, name: c.name })),
|
||||||
if (!selectMode) {
|
])
|
||||||
goDetail(bill.id)
|
} catch {
|
||||||
return
|
return // 错误已由 request 层 toast
|
||||||
}
|
|
||||||
setSelectedIds(prev => {
|
|
||||||
if (prev.includes(bill.id)) return prev.filter(i => i !== bill.id)
|
|
||||||
if (prev.length >= EXPORT_MAX) {
|
|
||||||
Taro.showToast({ title: `最多导出 ${EXPORT_MAX} 张`, icon: 'none' })
|
|
||||||
return prev
|
|
||||||
}
|
}
|
||||||
return [...prev, bill.id]
|
}
|
||||||
})
|
setExportBill(bill)
|
||||||
|
setShowCategory(true)
|
||||||
},
|
},
|
||||||
[selectMode, goDetail],
|
[categories],
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 全选当前已加载账单(受导出上限约束) */
|
/** 按所选分类导出单个账单 Excel */
|
||||||
const handleSelectAll = useCallback(() => {
|
|
||||||
setSelectedIds(prev => {
|
|
||||||
if (prev.length === bills.length) return []
|
|
||||||
if (bills.length > EXPORT_MAX) {
|
|
||||||
Taro.showToast({ title: `最多导出 ${EXPORT_MAX} 张,已选前 ${EXPORT_MAX} 张`, icon: 'none' })
|
|
||||||
return bills.slice(0, EXPORT_MAX).map(b => b.id)
|
|
||||||
}
|
|
||||||
return bills.map(b => b.id)
|
|
||||||
})
|
|
||||||
}, [bills])
|
|
||||||
|
|
||||||
/** 打开分类选择弹层(首次加载分类树根节点) */
|
|
||||||
const handleExportTap = useCallback(async () => {
|
|
||||||
if (selectedIds.length === 0) {
|
|
||||||
Taro.showToast({ title: '请先勾选要导出的账单', icon: 'none' })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (categories.length === 0) {
|
|
||||||
try {
|
|
||||||
const res = await getCategoriesApi()
|
|
||||||
setCategories([
|
|
||||||
{ id: 0, name: '全部分类' },
|
|
||||||
...res.data.map(c => ({ id: c.id, name: c.name })),
|
|
||||||
])
|
|
||||||
} catch {
|
|
||||||
return // 错误已由 request 层 toast
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setShowCategory(true)
|
|
||||||
}, [selectedIds, categories])
|
|
||||||
|
|
||||||
/** 按所选分类导出合并 Excel */
|
|
||||||
const handleExport = useCallback(
|
const handleExport = useCallback(
|
||||||
async (categoryId: number) => {
|
async (categoryId: number) => {
|
||||||
|
if (!exportBill) return
|
||||||
const isH5 = process.env.TARO_ENV === 'h5'
|
const isH5 = process.env.TARO_ENV === 'h5'
|
||||||
Taro.showLoading({ title: '导出中...', mask: true })
|
Taro.showLoading({ title: '导出中...', mask: true })
|
||||||
try {
|
try {
|
||||||
await downloadExportFile(getBillExportUrl(selectedIds, categoryId), '门店账单.xlsx')
|
await downloadExportFile(getBillExportUrl(exportBill.id, categoryId), `账单-${exportBill.bill_no}.xlsx`)
|
||||||
setShowCategory(false)
|
setShowCategory(false)
|
||||||
toggleSelectMode()
|
|
||||||
if (isH5) {
|
if (isH5) {
|
||||||
Taro.showToast({ title: '导出成功', icon: 'success' })
|
Taro.showToast({ title: '导出成功', icon: 'success' })
|
||||||
}
|
}
|
||||||
@@ -182,15 +141,15 @@ export default function BillListPage() {
|
|||||||
Taro.hideLoading()
|
Taro.hideLoading()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[selectedIds, toggleSelectMode],
|
[exportBill],
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 底部待支付汇总栏是否可见(多选导出时让位给导出栏) */
|
/** 底部待支付汇总栏是否可见 */
|
||||||
const showPayBar = loggedIn && !selectMode && !!summary && summary.unpaid_count > 0
|
const showPayBar = loggedIn && !!summary && summary.unpaid_count > 0
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={`bill-page ${selectMode ? 'bill-page--select' : ''} ${showPayBar ? 'bill-page--pay' : ''}`}>
|
<View className={`bill-page ${showPayBar ? 'bill-page--pay' : ''}`}>
|
||||||
{/* ========== 状态筛选 + 导出入口 ========== */}
|
{/* ========== 状态筛选 ========== */}
|
||||||
<View className='bill-toolbar'>
|
<View className='bill-toolbar'>
|
||||||
<ScrollView scrollX className='status-scroll'>
|
<ScrollView scrollX className='status-scroll'>
|
||||||
{STATUS_FILTERS.map(item => (
|
{STATUS_FILTERS.map(item => (
|
||||||
@@ -203,11 +162,6 @@ export default function BillListPage() {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
{loggedIn && bills.length > 0 && (
|
|
||||||
<View className={`bill-toolbar__export ${selectMode ? 'active' : ''}`} onClick={toggleSelectMode}>
|
|
||||||
<Text>{selectMode ? '取消' : '导出'}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ========== 账单列表 ========== */}
|
{/* ========== 账单列表 ========== */}
|
||||||
@@ -222,45 +176,44 @@ export default function BillListPage() {
|
|||||||
<Empty description='暂无账单' className='bill-empty' />
|
<Empty description='暂无账单' className='bill-empty' />
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
bills.map(bill => {
|
bills.map(bill => (
|
||||||
const checked = selectMode && selectedIds.includes(bill.id)
|
<View key={bill.id} className='bill-item' onClick={() => goDetail(bill.id)}>
|
||||||
return (
|
<View className='bill-item__content'>
|
||||||
<View
|
<View className='bill-item__header'>
|
||||||
key={bill.id}
|
<Text className='bill-item__no'>{bill.bill_no}</Text>
|
||||||
className={`bill-item ${selectMode ? 'bill-item--select' : ''}`}
|
<Text className={`bill-item__status bill-item__status--${bill.pay_state}`}>
|
||||||
onClick={() => handleItemTap(bill)}
|
{bill.pay_state_name}
|
||||||
>
|
</Text>
|
||||||
{selectMode && (
|
</View>
|
||||||
<View className={`bill-check ${checked ? 'on' : ''}`}>
|
<View className='bill-item__body'>
|
||||||
{checked && <Icon name='success' size={14} color='#fff' />}
|
<View className='bill-item__meta'>
|
||||||
</View>
|
<Text className='bill-item__date'>账单日期 {bill.bill_date}</Text>
|
||||||
)}
|
{bill.purchase && (
|
||||||
<View className='bill-item__content'>
|
<Text className='bill-item__purchase'>采购单 {bill.purchase.purchase_no}</Text>
|
||||||
<View className='bill-item__header'>
|
|
||||||
<Text className='bill-item__no'>{bill.bill_no}</Text>
|
|
||||||
<Text className={`bill-item__status bill-item__status--${bill.pay_state}`}>
|
|
||||||
{bill.pay_state_name}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View className='bill-item__body'>
|
|
||||||
<View className='bill-item__meta'>
|
|
||||||
<Text className='bill-item__date'>账单日期 {bill.bill_date}</Text>
|
|
||||||
{bill.purchase && (
|
|
||||||
<Text className='bill-item__purchase'>采购单 {bill.purchase.purchase_no}</Text>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
<Text className='bill-item__amount'>¥{bill.total_amount}</Text>
|
|
||||||
</View>
|
|
||||||
<View className='bill-item__footer'>
|
|
||||||
<Text className='bill-item__settle'>应结算 {bill.settlement_date}</Text>
|
|
||||||
{bill.pay_state === 2 && bill.paid_at && (
|
|
||||||
<Text className='bill-item__paid'>已于 {bill.paid_at} 支付</Text>
|
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
<View className='bill-item__side'>
|
||||||
|
<Text className='bill-item__amount'>¥{bill.total_amount}</Text>
|
||||||
|
<View
|
||||||
|
className='bill-item__export'
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation()
|
||||||
|
handleExportTap(bill)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
导出
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className='bill-item__footer'>
|
||||||
|
<Text className='bill-item__settle'>应结算 {bill.settlement_date}</Text>
|
||||||
|
{bill.pay_state === 2 && bill.paid_at && (
|
||||||
|
<Text className='bill-item__paid'>已于 {bill.paid_at} 支付</Text>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
</View>
|
||||||
})
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loggedIn && finished && bills.length > 0 && (
|
{loggedIn && finished && bills.length > 0 && (
|
||||||
@@ -278,22 +231,6 @@ export default function BillListPage() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ========== 导出操作栏 ========== */}
|
|
||||||
{selectMode && (
|
|
||||||
<View className='export-bar'>
|
|
||||||
<Text className='export-bar__count'>已选 {selectedIds.length} 张</Text>
|
|
||||||
<View className='export-bar__all' onClick={handleSelectAll}>
|
|
||||||
{selectedIds.length === bills.length && bills.length > 0 ? '取消全选' : '全选'}
|
|
||||||
</View>
|
|
||||||
<View
|
|
||||||
className={`export-bar__btn ${selectedIds.length === 0 ? 'disabled' : ''}`}
|
|
||||||
onClick={handleExportTap}
|
|
||||||
>
|
|
||||||
导出 Excel
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* ========== 分类选择弹层 ========== */}
|
{/* ========== 分类选择弹层 ========== */}
|
||||||
<Popup
|
<Popup
|
||||||
show={showCategory}
|
show={showCategory}
|
||||||
|
|||||||
@@ -123,9 +123,9 @@ export function getBillDetailApi(id: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账单合并导出文件地址:GET /mini/bill/export
|
* 单个账单导出文件地址:GET /mini/bill/{id}/export
|
||||||
* 返回 xlsx 文件流(非 JSON,需带 token 下载);categoryId 仅过滤商品明细,费用仍全额汇总
|
* 返回 xlsx 文件流(非 JSON,需带 token 下载);categoryId 仅过滤商品明细,费用仍全额汇总
|
||||||
*/
|
*/
|
||||||
export function getBillExportUrl(ids: number[], categoryId = 0): string {
|
export function getBillExportUrl(id: number, categoryId = 0): string {
|
||||||
return `${BASE_URL}/mini/bill/export?ids=${ids.join(',')}&category_id=${categoryId}`
|
return `${BASE_URL}/mini/bill/${id}/export?category_id=${categoryId}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ 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"
|
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"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP 状态码 → 错误提示映射
|
* HTTP 状态码 → 错误提示映射
|
||||||
|
|||||||
Reference in New Issue
Block a user