ylj20011123 cee81d5037 update
2025-09-23 19:14:35 +08:00

519 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 收银员统计表
import { connect } from "umi";
import type { CurrentUser } from "umi";
import type { ConnectState } from "@/models/connect";
import React, { useRef, useState } from "react";
import ProCard from "@ant-design/pro-card";
import { MenuFoldOutlined } from "@ant-design/icons";
import type { FormInstance } from "antd";
import { Button, message, Space, Spin, Tree } from "antd";
import useRequest from "@ahooksjs/use-request";
import { getServerpartTree, handleGetServiceShopTreeList } from "@/services/options";
import type { ActionType } from "@ant-design/pro-table";
import ProTable from "@ant-design/pro-table";
import ReactHTMLTableToExcel from "react-html-table-to-excel";
import { handleGetPERSONSELLList } from "@/pages/reports/BusinessAnalysis/personSellReport/service";
import moment from "moment";
import ProDescriptions from "@ant-design/pro-descriptions";
import numeral from "numeral";
import { contractType } from "@/pages/contract/emun";
import { handleGetShopShortNamesGet } from "@/pages/reports/BusinessAnalysis/transactionAnalysis/service";
import PageTitleBox from "@/components/PageTitleBox";
import { exportXlsxFromProColumnsExcelJS } from "@/utils/exportExcelFun";
import { formatTreeData } from "@/utils/format";
const personSellReport: React.FC<{ currentUser: CurrentUser }> = (props) => {
const { currentUser } = props
const downloadBtnRef = useRef<any>()
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const [reqDetailList, setReqDetailList] = useState<any>(); // 合计项数据源
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const [collapsible, setCollapsible] = useState<boolean>(false)
const [treeView, setTreeView] = useState<any>()
// 加载服务区树
const { loading: treeLoading, data: treeViews } = useRequest(async () => {
const data = await handleGetServiceShopTreeList({
ProvinceCode: currentUser?.ProvinceCode,
BusinessState: "1000,2000,3000",
ShowState: true,
SortStr: 'BUSINESS_STATE,SHOPSHORTNAME'
});
setTreeView(data)
return data
})
// 树相关的属性和方法
const [selectedId, setSelectedId] = useState<string>()
// 导出的加载效果
const [showLoading, setShowLoading] = useState<boolean>(false)
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 自营业态的选择列表
const [shopNamesList, setShopNamesList] = useState<any>()
// 表格数据
const [tableData, setTableData] = useState<any>()
// 自营业态的选择的列表方法
const handleGetShopNamesList = async (id: any) => {
const req: any = {
ProvinceCode: currentUser?.USER_PROVINCE,
BusinessType: 1000,
ServerpartId: id || selectedId
}
const data = await handleGetShopShortNamesGet(req)
console.log('data', data)
if (data && data.length > 0) {
const obj: any = {}
if (data && data.length > 0) {
data.forEach((item: any) => {
obj[item] = item
})
}
setShopNamesList(obj)
}
}
const columns: any = [
{
title: '统计时间',
dataIndex: 'search_date',
valueType: 'dateTimeRange',
hideInTable: true,
hideInDescriptions: true,
colSize: 1,
initialValue: [moment(`${moment().add(-1, 'day').format('YYYY-MM-DD')} 12:00:00`).format('YYYY-MM-DD hh:mm:ss'), moment(`${moment().format('YYYY-MM-DD')} 12:00:00`).format('YYYY-MM-DD hh:mm:ss')],
search: {
transform: (value) => {
return {
SELL_ENDDATE_Start: value[0],
SELL_ENDDATE_End: value[1],
};
},
},
fieldProps: {
disabledDate: (current: any) => current && current < moment()
}
},
{
title: '经营模式',
key: 'BusinessType',
dataIndex: 'BusinessType',
valueType: 'select',
valueEnum: contractType,
hideInTable: true,
},
{
title: '自营业态',
dataIndex: 'shopNames',
hideInTable: true,
valueType: 'select',
valueEnum: shopNamesList,
// request: async () => {
// return await getFieldEnum({ FieldExplainField: 'BUSINESSTYPE' })
// },
fieldProps: {
multiple: true,
showSearch: true,
filterOption: (input, option) => (option?.label ?? '').toLowerCase().includes(input.toLowerCase()),
}
},
{
title: '序号',
dataIndex: 'index',
align: 'center',
width: 90,
hideInSearch: true,
},
{
title: '服务区名称',
dataIndex: 'SERVERPART_NAME',
align: 'center',
width: 150,
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'SHOPNAME',
align: 'left',
width: 200,
ellipsis: true,
hideInSearch: true,
},
{
title: '交班时间',
dataIndex: 'SELL_ENDDATE',
width: 150,
align: 'center',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'TICKETCOUNT',
width: 120,
align: 'right',
valueType: 'digit',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'TOTALCOUNT',
width: 120,
align: 'right',
valueType: 'digit',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'TOTALOFFAMOUNT',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'RealActualCashpay',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'TOTALSELLAMOUNT',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'CASHPAY',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'CASH',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'DIFFERENT_PRICE',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: <div style={{ textAlign: 'center' }}></div>,
dataIndex: 'MobilyPay',
width: 120,
valueType: 'digit',
align: 'right',
hideInSearch: true,
},
{
title: '机器号码',
dataIndex: 'MACHINECODE',
align: 'center',
width: 120,
hideInSearch: true,
},
{
title: '收银员工号',
dataIndex: 'CASHWORKER_CODE',
align: 'center',
width: 140,
hideInSearch: true,
},
{
title: '收银员名称',
dataIndex: 'CASHIER_NAME',
width: 140,
align: 'center',
hideInSearch: true,
},
]
const exportTable = (e) => {
e.stopPropagation(); // 防止Collapse组件收起
const main = document.getElementsByClassName('saleReportHideBox')[0]
const thead = main.querySelector('thead').cloneNode(true); // 深克隆DOM节点
const tbody = main.querySelector('tbody').cloneNode(true); // 深克隆DOM节点
const container = document.querySelector('#hiddenBox');
const tempTable = document.createElement('table');
tempTable.appendChild(thead);
tempTable.appendChild(tbody);
tempTable.setAttribute('id', 'table-to-xls-personSellReport'); // 给table添加id值与按钮上的table字段对应
container.appendChild(tempTable); // 把创建的节点添加到页面容器中
setShowLoading(false)
downloadBtnRef.current.handleDownload();
setShowExportTable(false)
tempTable.remove() // 防止重复打印一个内容
}
// 查询的条件
const [searchParams, setSearchParams] = useState<any>()
return (
<div ref={(el) => {
// 打印报表
if (!reqDetailList || reqDetailList.length === 0) return;
setPrintOut(el);
}} >
{
showLoading ?
<div
style={{
width: '100%',
height: '100%',
background: 'rgba(0,0,0,0.1)',
position: 'fixed',
zIndex: 5,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '15px 20px 10px',
background: '#fff',
borderRadius: '8px',
width: '200px'
}}>
<Spin />
<span style={{ marginLeft: '5px' }}>...</span>
</div>
</div> : ''
}
<div className={'saleReportHideBox'} style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }}>
{
showExportTable && reqDetailList && reqDetailList.length > 0 ?
<ProTable
columns={columns}
dataSource={reqDetailList}
pagination={false}
expandable={{
defaultExpandAllRows: true
}}
/> : ''
}
</div>
<div id='hiddenBox' style={{ position: 'fixed', zIndex: -1, top: 0, left: 0 }} />
<div style={{ backgroundColor: '#fff', display: 'flex' }}>
<ProCard
style={{ width: !collapsible ? "300px" : "60px" }}
className="pageTable-leftnav"
bodyStyle={{ padding: 0, paddingTop: 20, paddingLeft: 20, width: !collapsible ? "300px" : "60px" }}
extra={<MenuFoldOutlined onClick={() => {
setCollapsible(!collapsible)
}} />}
colSpan={!collapsible ? "300px" : "60px"}
title={!collapsible ? "请选择服务区" : ""}
headerBordered
collapsed={collapsible}
>
{treeView && treeView.length > 0 ? <Tree
checkable
treeData={[{
label: '全部',
value: 0,
key: '0-0',
children: treeView
}]}
fieldNames={{
title: "label",
key: "key"
}}
blockNode
defaultExpandedKeys={['0-0']}
onCheck={(checkedKeys: React.Key[] | any, info) => {
const selectedIds = info.checkedNodes.filter(n => n?.type === 2)
setSelectedId(selectedIds.map(n => n?.value)?.toString() || '')
handleGetShopNamesList(selectedIds.map(n => n?.value)?.toString() || '')
// actionRef?.current?.reload()
// getData(selectedIds.map(n => n?.value)?.toString() || '')
}}
// switcherIcon={<PlusOutlined />}
/> : ''}
</ProCard>
<div style={{
width: !collapsible ? 'calc(100% - 300px)' : 'calc(100% - 60px)',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable
actionRef={actionRef}
formRef={formRef}
columns={columns}
bordered
scroll={{ x: '100%', y: 'calc(100vh - 560px)' }}
// pagination={false}
headerTitle={<PageTitleBox props={props} />}
search={{ span: 8, defaultCollapsed: false }}
request={async (params) => {
if (!selectedId) {
return
}
const req = {
SearchParameter: {
...params,
SHOPCODES: selectedId
},
PageIndex: 1,
PageSize: 999999,
SortStr: 'SELL_ENDDATE desc'
}
setSearchParams(params)
console.log('req', req)
const data = await handleGetPERSONSELLList(req)
if (data.List && data.List.length > 0) {
const list: any = []
data.List.forEach((item: any, index: number) => {
item.index = index + 1
list.push(item)
})
list.unshift({
...data.OtherData,
SERVERPART_NAME: '合计'
})
console.log('list', list)
let fieldData: any = [
"TICKETCOUNT",
"TOTALCOUNT",
"TOTALOFFAMOUNT",
"RealActualCashpay",
"TOTALSELLAMOUNT",
"CASHPAY",
"CASH",
"DIFFERENT_PRICE",
"MobilyPay",
]
let enumList: any = []
let newPrintData: any = formatTreeData(JSON.parse(JSON.stringify(list)), fieldData, enumList, [], [])
setReqDetailList(newPrintData)
setTableData(list)
return { data: list, success: true }
}
setReqDetailList([])
setTableData([])
return { data: [], success: true }
}}
toolbar={{
actions: [
// <span style={{ visibility: 'hidden' }}>
// <ReactHTMLTableToExcel
// buttonText={'导出excel'}
// ref={downloadBtnRef}
// table="table-to-xls-personSellReport"
// filename={`服务区收银员报表${searchParams?.SELL_ENDDATE_Start}-${searchParams?.SELL_ENDDATE_End}`}
// sheet="sheet1"
// />
// </span>,
<Button
key="new"
type="primary"
onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) {
// 尝试一下 导出新方法
exportXlsxFromProColumnsExcelJS(columns,
reqDetailList,
`服务区收银员报表${searchParams?.SELL_ENDDATE_Start}-${searchParams?.SELL_ENDDATE_End}`,
// {
// topTitle: `单品销售排行统计`, // 顶部大标题
// }
)
} else {
message.error('暂无数据可导出!')
}
// if (reqDetailList && reqDetailList.length > 0) {
// setShowLoading(true)
// setTimeout(() => {
// setShowExportTable(true)
// setTimeout(() => {
// exportTable(e)
// }, 100)
// }, 100)
// } else {
// message.error('暂无数据可导出!')
// }
}}
>
excel
</Button>
]
}}
tableExtraRender={() => {
if (tableData && tableData.length > 0) {
let TICKETCOUNTSum: number = 0
let TOTALCOUNTSum: number = 0
let TOTALOFFAMOUNTSum: number = 0
let RealActualCashpaySum: number = 0
let TOTALSELLAMOUNTSum: number = 0
let CASHPAYSum: number = 0
let CASHSum: number = 0
let DIFFERENT_PRICESum: number = 0
let MobilyPaySum: number = 0
tableData.forEach((item: any) => {
TICKETCOUNTSum += item.TICKETCOUNT
TOTALCOUNTSum += item.TOTALCOUNT
TOTALOFFAMOUNTSum += item.TOTALOFFAMOUNT
RealActualCashpaySum += item.RealActualCashpay
TOTALSELLAMOUNTSum += item.TOTALSELLAMOUNT
CASHPAYSum += item.CASHPAY
CASHSum += item.CASH
DIFFERENT_PRICESum += item.DIFFERENT_PRICE
MobilyPaySum += item.MobilyPay
})
return <div style={{ paddingLeft: 30, paddingBottom: 0 }}>
<ProDescriptions column={6} className="commity-sale-description" labelStyle={{ color: "#00000073" }}>
<ProDescriptions.Item span={1} label="客单数量" contentStyle={{ color: 'orangered' }}>{TICKETCOUNTSum ? numeral(TICKETCOUNTSum).format('0,0') : '0'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="销售数量" contentStyle={{ color: 'orangered' }}>{TOTALCOUNTSum ? numeral(TOTALCOUNTSum).format('0,0') : '0'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="优惠金额" valueType="money" contentStyle={{ color: 'orangered' }}>{TOTALOFFAMOUNTSum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="实收金额" valueType="money" contentStyle={{ color: 'orangered' }}>{RealActualCashpaySum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={2} label="对客营收" valueType="money" contentStyle={{ color: 'orangered' }}>{TOTALSELLAMOUNTSum || '0.00'}</ProDescriptions.Item>
</ProDescriptions>
<ProDescriptions column={6} className="commity-sale-description" labelStyle={{ color: "#00000073" }}>
<ProDescriptions.Item span={1} label="缴款金额" valueType="money" contentStyle={{ color: 'orangered' }}>{CASHPAYSum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="现金支付" valueType="money" contentStyle={{ color: 'orangered' }}>{CASHSum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="长短款额:" valueType="money" contentStyle={{ color: 'orangered' }}>{DIFFERENT_PRICESum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={1} label="移动支付" valueType="money" contentStyle={{ color: 'orangered' }}>{MobilyPaySum || '0.00'}</ProDescriptions.Item>
<ProDescriptions.Item span={2} label="统计时间">{`${moment(searchParams?.SELL_ENDDATE_Start).format('YYYY-MM-DD HH时')}${moment(searchParams?.SELL_ENDDATE_End).format('YYYY-MM-DD HH时')}`}</ProDescriptions.Item>
</ProDescriptions>
</div>
}
}}
/>
</div>
</div>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(personSellReport);