import { useCallback, useRef, useState } from 'react' import Taro, { useDidShow, useReachBottom } from '@tarojs/taro' import { View, Text, ScrollView } from '@tarojs/components' import { Empty, Icon, Popup } from '@antmjs/vantui' import useAuthStore from '@/stores/auth/useAuthStore' import { getBillExportUrl, getBillListApi } from '@/services/bill' import { getCategoriesApi } from '@/services/product' import { downloadExportFile } from '@/utils/download' import type { Bill, BillSummary } from '@/services/bill' import './index.less' const PAGE_SIZE = 10 /** 单次导出上限(后端限制 1~100 张) */ const EXPORT_MAX = 100 /** 状态筛选(接口 status 为原始支付状态:0 未支付含审核中 / 1 已支付) */ const STATUS_FILTERS: Array<{ value: 0 | 1 | undefined; label: string }> = [ { value: undefined, label: '全部' }, { value: 0, label: '未支付' }, { value: 1, label: '已支付' }, ] /** 导出分类选项(id=0 全部分类) */ interface CategoryOption { id: number name: string } /** * 账单列表页 * 采购单完成后由后台按门店生成(只读);底部汇总栏为门店口径待支付汇总(含审核中,不受筛选影响) * 支持多选账单合并导出 Excel(可按一级分类过滤商品明细) */ export default function BillListPage() { const token = useAuthStore(s => s.token) const [status, setStatus] = useState<0 | 1 | undefined>(undefined) const [bills, setBills] = useState([]) const [summary, setSummary] = useState(null) const [page, setPage] = useState(1) const [loading, setLoading] = useState(false) const [finished, setFinished] = useState(false) const loadingRef = useRef(false) /** 导出:多选模式 + 已选账单 + 分类弹层 */ const [selectMode, setSelectMode] = useState(false) const [selectedIds, setSelectedIds] = useState([]) const [showCategory, setShowCategory] = useState(false) const [categories, setCategories] = useState([]) const loggedIn = !!token /** 拉取账单列表(summary 每次随响应刷新) */ const loadList = useCallback( async (pageNum: number, reset: boolean, statusParam?: 0 | 1) => { if (!loggedIn || loadingRef.current) return loadingRef.current = true setLoading(true) try { const res = await getBillListApi({ status: statusParam, page: pageNum, pageSize: PAGE_SIZE }) const { data, total, summary: sum } = res.data setBills(prev => (reset ? data : [...prev, ...data])) setSummary(sum) setPage(pageNum) setFinished(pageNum * PAGE_SIZE >= total) } catch { // 错误已由 request 层 toast } finally { loadingRef.current = false setLoading(false) } }, [loggedIn], ) useDidShow(() => { loadList(1, true, status) }) useReachBottom(() => { if (!finished && !loadingRef.current && loggedIn) { loadList(page + 1, false, status) } }) /** 切换状态筛选 */ const handleStatusTap = useCallback( (value?: 0 | 1) => { setStatus(value) setFinished(false) loadList(1, true, value) }, [loadList], ) const goLogin = useCallback(() => { Taro.navigateTo({ url: '/pages/login/index' }) }, []) const goDetail = useCallback((id: number) => { Taro.navigateTo({ url: `/pages/bill-detail/index?id=${id}` }) }, []) /** 合并付款 → 发起付款页 */ const goPay = useCallback(() => { Taro.navigateTo({ url: '/pages/payment/index' }) }, []) /** 进入/退出多选导出模式 */ const toggleSelectMode = useCallback(() => { setSelectMode(prev => !prev) setSelectedIds([]) }, []) /** 账单点击:多选模式切换勾选,否则进详情 */ const handleItemTap = useCallback( (bill: Bill) => { if (!selectMode) { goDetail(bill.id) return } 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] }) }, [selectMode, goDetail], ) /** 全选当前已加载账单(受导出上限约束) */ 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( async (categoryId: number) => { const isH5 = process.env.TARO_ENV === 'h5' Taro.showLoading({ title: '导出中...', mask: true }) try { await downloadExportFile(getBillExportUrl(selectedIds, categoryId), '门店账单.xlsx') setShowCategory(false) toggleSelectMode() if (isH5) { Taro.showToast({ title: '导出成功', icon: 'success' }) } } catch (e: any) { Taro.showToast({ title: e?.message || '导出失败,请稍后重试', icon: 'none' }) } finally { Taro.hideLoading() } }, [selectedIds, toggleSelectMode], ) /** 底部待支付汇总栏是否可见(多选导出时让位给导出栏) */ const showPayBar = loggedIn && !selectMode && !!summary && summary.unpaid_count > 0 return ( {/* ========== 状态筛选 + 导出入口 ========== */} {STATUS_FILTERS.map(item => ( handleStatusTap(item.value)} > {item.label} ))} {loggedIn && bills.length > 0 && ( {selectMode ? '取消' : '导出'} )} {/* ========== 账单列表 ========== */} {!loggedIn ? ( 去登录 ) : bills.length === 0 ? ( loading ? ( 加载中... ) : ( ) ) : ( bills.map(bill => { const checked = selectMode && selectedIds.includes(bill.id) return ( handleItemTap(bill)} > {selectMode && ( {checked && } )} {bill.bill_no} {bill.pay_state_name} 账单日期 {bill.bill_date} {bill.purchase && ( 采购单 {bill.purchase.purchase_no} )} ¥{bill.total_amount} 应结算 {bill.settlement_date} {bill.pay_state === 2 && bill.paid_at && ( 已于 {bill.paid_at} 支付 )} ) }) )} {loggedIn && finished && bills.length > 0 && ( 没有更多了 )} {/* ========== 底部待支付汇总栏(门店口径,含审核中) ========== */} {showPayBar && summary && ( 待支付账单(含审核中){summary.unpaid_count} 笔 ¥{summary.unpaid_amount} 去付款 )} {/* ========== 导出操作栏 ========== */} {selectMode && ( 已选 {selectedIds.length} 张 {selectedIds.length === bills.length && bills.length > 0 ? '取消全选' : '全选'} 导出 Excel )} {/* ========== 分类选择弹层 ========== */} setShowCategory(false)} > 选择商品分类 仅过滤商品明细行,配送费与附加金额仍全额汇总 {categories.map(c => ( handleExport(c.id)}> {c.name} ))} ) }