ylj20011123 9fb0245d17 update
2025-08-22 19:39:48 +08:00

649 lines
23 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 { Drawer } from "antd";
import { Select } from "antd";
import { Button, message, Space, Spin, Tree } from "antd";
import useRequest from "@ahooksjs/use-request";
import { getFieldEnum, getMoney, getServerpartTree, handleCallLogs } from "@/services/options";
import type { ActionType } from "@ant-design/pro-table";
import ProTable from "@ant-design/pro-table";
import { handleGetExamineList } from "@/pages/examine/examineList/service";
import moment from "moment";
import session from "@/utils/session";
import {
handleGetGetTransactionCustomer,
handleGetShopShortNames, handleGetTransactionCustomerByDate
} from "@/pages/reports/BusinessAnalysis/transactionAnalysis/service";
import ReactHTMLTableToExcel from "react-html-table-to-excel";
import * as numeral from "numeral";
import {
handleGetShopShortNamesGet
} from "@/pages/reports/BusinessAnalysis/transactionAnalysis/service";
import { handleGetGetRevenueYOYQOQByDate } from "@/pages/reports/BusinessAnalysis/revenueYOYQOQReport/service";
import PageTitleBox from "@/components/PageTitleBox";
const transactionAnalysis: React.FC<{ currentUser: CurrentUser }> = (props) => {
const { currentUser } = props
const downloadBtnRef = useRef<any>()
const actionRef = useRef<ActionType>();
const actionByDateRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const formByDateRef = 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 getServerpartTree(currentUser?.ProvinceCode, currentUser?.CityAuthority, true, false, true)
setTreeView(data)
return data
})
// 树相关的属性和方法
const [selectedId, setSelectedId] = useState<string>()
const businessTypeList = session.get("BUSINESSTYPEList")
const businessTypeObj = session.get("BUSINESSTYPEObj")
const BUSINESSTYPEObj = session.get('BUSINESSTYPEObj')
// 判断是不是第一次进入页面
const [isFirst, setIsFirst] = useState<boolean>(true)
// 导出的加载效果
const [showLoading, setShowLoading] = useState<boolean>(false)
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 查询的条件
const [searchParams, setSearchParams] = useState<any>()
// 自营业态的选择列表
const [selfList, setSelfList] = useState<any>()
const [businessModel, setBusinessModel] = useState<any>()
// 经营模式
const [businessType, setBusinessType] = useState<any>()
// 展示自营业态
const [showSelfBusiness, setShowSelfBusiness] = useState<boolean>(true)
// 自营业态的选择列表
const [shopNamesList, setShopNamesList] = useState<any>()
const [printIndex, setPrintIndex] = useState<number>(new Date().getTime())
// 显示每日流水抽屉
const [showDailyDrawer, setShowDailyDrawer] = useState<boolean>(false)
const [currentRow, setCurrentRow] = useState<any>()
const { loading: selfLoading, data: self } = useRequest(async () => {
const data = await handleGetShopShortNames()
if (data && data.length > 0) {
const list: any = []
data.forEach((item: any) => {
list.push({ label: item, value: item })
})
return list
}
})
const [columnsStateMap, setColumnsStateMap] = useState<any>({
// Business_Type:{show:false},
ShopTrade: { show: false }
})
const [columnsDailyStateMap, setColumnsDailyStateMap] = useState<any>({
ShopTrade: { show: false }
})
const columns: any = [
{
title: '统计时间',
dataIndex: 'search_date',
valueType: 'dateRange',
hideInTable: true,
hideInDescriptions: true,
initialValue: [moment().add(-1, 'day'), moment().add(-1, 'day')],
search: {
transform: (value) => {
return {
StartDate: value[0],
EndDate: value[1],
};
},
},
fieldProps: {
disabledDate: (current: any) => current && current > moment().endOf('day')
}
},
{
title: '经营模式',
dataIndex: 'businessType',
valueType: 'select',
hideInTable: true,
valueEnum: BUSINESSTYPEObj,
fieldProps: {
mode: "multiple",
onChange: async (e) => {
console.log('e', e)
const flag = e.filter(item => item === '1000')?.toString() || ''
console.log('flag', flag)
if (flag) {
setShowSelfBusiness(false)
// handleGetShopNamesList(selectedId)
} else {
setShowSelfBusiness(true)
// setShopNamesList({})
}
}
}
},
{
title: '自营业态',
dataIndex: 'shopNames',
hideInTable: true,
hideInSearch: showSelfBusiness,
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: 'targetSystem',
valueType: 'select',
hideInTable: true,
valueEnum: {
"": '全部',
1: "是",
0: "否"
},
initialValue: ""
},
{
title: '',
dataIndex: 'SearchKeyValue',
hideInTable: true,
fieldProps: {
placeholder: "请输入商户/品牌/门店/服务区"
}
},
{
title: '服务区信息',
dataIndex: '',
hideInSearch: true,
children: [
// {
// title: '序号',
// width: 120,
// hideInSearch: true,
// dataIndex: 'index'
// },
{
title: '门店名称',
width: 200,
hideInSearch: true,
dataIndex: 'Name',
ellipsis: true,
render: (_, record) => {
return showDailyDrawer ? record?.Name : <a onClick={() => {
if (record?.Name === '合计') {
let id: string = ''
if (record?.children && record?.children.length > 0) {
record?.children.forEach((item: any) => {
if (item?.children && item?.children.length > 0) {
item?.children.forEach((subItem: any) => {
if (id) {
id += `,${subItem.Id}`
} else {
id = subItem.Id
}
})
}
})
}
setCurrentRow({
...record,
Id: id
})
}
else if (record?.Name && record?.Name.indexOf('管理中心') !== -1) {
let id: string = ''
if (record?.children && record?.children.length > 0) {
record?.children.forEach((item: any) => {
if (id) {
id += `,${item.Id}`
} else {
id = item.Id
}
})
}
setCurrentRow({
...record,
Id: id,
SpregiontypeName: record?.Name,
ServerpartId: id
})
}
else if (record?.Name && record?.Name.indexOf('服务区') !== -1) {
setCurrentRow({
...record,
ServerpartId: record?.Id,
ServerpartName: record?.Name,
})
} else {
setCurrentRow(record)
}
setShowDailyDrawer(true)
}}>
{record?.Name}
</a>
}
},
{
title: '门店数量',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'ShopCount'
},
{
title: '经营模式',
width: 150,
dataIndex: 'Business_Type',
valueType: 'select',
valueEnum: businessTypeObj,
render: (_, record) => {
return record.BusinessType ? businessTypeObj[record.BusinessType] : '-'
}
},
{
title: '经营商户',
width: 120,
ellipsis: true,
dataIndex: 'MERCHANTS_NAME',
hideInSearch: true,
},
{
dataIndex: 'ShopTrade',
title: '商品业态',
align: 'center',
width: 120,
valueType: 'select',
hideInSearch: true,
request: async () => {
// 这里要手动添加枚举字段(从下面这些字典中选择一个):商品业态[BUSINESSTYPE]
const options = await getFieldEnum({ FieldExplainField: 'BUSINESSTYPE' });
return options;
},
},
]
},
{
title: '经营数据',
dataIndex: '',
hideInSearch: true,
children: [
{
title: '客单数量',
hideInSearch: true,
dataIndex: 'TicketCount',
valueType: 'digit'
},
{
title: '销售数量',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'TotalCount'
},
{
title: '销售金额',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'TotalSellAmount'
},
{
title: '客单均量',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'AverageCount'
},
{
title: '客单均价',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'AverageAmount'
},
{
title: '商品均价',
hideInSearch: true,
valueType: 'digit',
dataIndex: 'AverageCommodity'
},
]
}
]
const exportTable = (e) => {
e.stopPropagation(); // 防止Collapse组件收起
const main = document.getElementsByClassName(`transactionAnalysisHideBox${printIndex}`)[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-transactionAnalysis'); // 给table添加id值与按钮上的table字段对应
container.appendChild(tempTable); // 把创建的节点添加到页面容器中
setShowLoading(false)
downloadBtnRef.current.handleDownload();
setShowExportTable(false)
tempTable.remove() // 防止重复打印一个内容
}
// 自营业态的选择的列表方法
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)
}
}
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={`transactionAnalysisHideBox${printIndex}`} 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 === 1)
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}
rowKey={(record) => {
return `${record?.Id}-${record?.Name}-${record?.ServerpartId}`
}}
bordered
headerTitle={<PageTitleBox props={props} />}
search={{ span: 6, defaultCollapsed: false, }}
scroll={{ y: 'calc(100vh - 470px)' }}
request={async (params) => {
if (isFirst) {
setIsFirst(false)
return
}
if (!selectedId) {
message.error('请选择服务区')
return
}
console.log('params', params)
handleCallLogs()
setSearchParams(params)
const req = {
ServerpartIds: selectedId,
startDate: params?.StartDate,
endDate: params?.EndDate,
businessType,
// shopNames: businessModel,
targetSystem: params.targetSystem || '',
shopNames: params.shopNames || '',
SearchKeyName: "MerchantName,Brand,Shop,Serverpart",
SearchKeyValue: params?.SearchKeyValue || ''
}
const data = await handleGetGetTransactionCustomer(req)
console.log('data', data)
let list: any = []
if (data && data.length > 0) {
list = JSON.parse(JSON.stringify(data))
// let ShopCountSum: number = 0
// let TicketCountSum: number = 0
// let TotalCountSum: number = 0
// let TotalSellAmountSum: number = 0
// let AverageCountSum: number = 0
// let AverageAmountSum: number = 0
// let AverageCommoditySum: number = 0
//
// list.forEach((item: any, index: number) => {
// item.index = index + 1
// ShopCountSum += item.ShopCount
// TicketCountSum += item.TicketCount
// TotalCountSum += item.TotalCount
// TotalSellAmountSum += item.TotalSellAmount
// AverageCountSum += item.AverageCount
// AverageAmountSum += item.AverageAmount
// AverageCommoditySum += item.AverageCommodity
//
// if (item.children && item.children.length > 0) {
// item.children.forEach((subItem: any, subIndex: number) => {
// subItem.index = `${index + 1}.${subIndex + 1}`
// if (subItem.children && subItem.children.length > 0) {
// subItem.children.forEach((thirdItem: any, thirdIndex: number) => {
// thirdItem.index = `${index + 1}.${subIndex + 1}.${thirdIndex + 1}`
// })
// }
// })
// }
// })
// list.unshift({
// Name: '合计',
// ShopCount: getMoney(ShopCountSum),
// TicketCount: getMoney(TicketCountSum),
// TotalCount: getMoney(TotalCountSum),
// TotalSellAmount: getMoney(TotalSellAmountSum),
// AverageCount: getMoney(AverageCountSum),
// AverageAmount: getMoney(AverageAmountSum),
// AverageCommodity:getMoney(AverageCommoditySum),
// })
}
console.log('list', list)
setReqDetailList(list)
return { data: list, success: true }
}}
toolbar={{
actions: [
<span style={{ visibility: 'hidden' }}>
<ReactHTMLTableToExcel
buttonText={'导出excel'}
ref={downloadBtnRef}
table="table-to-xls-transactionAnalysis"
filename={`客单交易分析${searchParams?.StartDate}-${searchParams?.EndDate}`}
sheet="sheet1"
/>
</span>,
<Button
key="new"
type="primary"
onClick={(e) => {
if (reqDetailList && reqDetailList.length > 0) {
setShowLoading(true)
setTimeout(() => {
setShowExportTable(true)
setTimeout(() => {
exportTable(e)
}, 100)
}, 100)
} else {
message.error('暂无数据可导出!')
}
}}
>
excel
</Button>
]
}}
columnsState={{
value: columnsStateMap,
onChange: setColumnsStateMap,
}}
/>
</div>
</div>
<Drawer
width="80%"
visible={showDailyDrawer}
onClose={() => {
setCurrentRow(undefined);
setShowDailyDrawer(false);
}}
bodyStyle={{ backgroundColor: "#f9f9f9", padding: 16 }}
destroyOnClose
closable={false}
>
<div style={{ width: '100%' }}>
<ProTable
actionRef={actionByDateRef}
formRef={formByDateRef}
rowKey={(record) => {
return `${record?.index}-${record?.Id}-${record?.Name}-${record?.SERVERPART_ID}`
}}
columns={columns}
bordered
search={false}
options={false}
pagination={false}
headerTitle={currentRow?.Name === '合计' ? '合计' : `${currentRow?.SpregiontypeName}${currentRow?.ServerpartName ? `-${currentRow?.ServerpartName}` : ''}${currentRow?.SERVERPARTSHOP_NAME ? `-${currentRow?.SERVERPARTSHOP_NAME}` : ''}`}
request={async (params) => {
console.log('currentRow', currentRow)
const req = {
ServerpartIds: currentRow?.Name === '合计' ? selectedId : currentRow?.ServerpartId ? currentRow?.ServerpartId : '',
ServerpartShopId: currentRow?.SERVERPARTSHOP_ID ? currentRow?.SERVERPARTSHOP_ID : '',
startDate: searchParams?.StartDate,
endDate: searchParams?.EndDate,
businessType,
shopNames: businessModel,
targetSystem: searchParams?.targetSystem || '',
}
const data = await handleGetTransactionCustomerByDate(req)
console.log('data22222', data)
if (data && data.length > 0) {
console.log('data', data)
data.forEach((item: any, index: number) => {
item.index = index + 1
if (item.children && item.children.length > 0) {
item.children.forEach((subItem: any, subIndex: number) => {
subItem.index = subIndex + 1
})
}
})
return { data, success: true }
}
return { data: [], success: true }
}}
scroll={{ x: 1700 }}
columnsState={{
value: columnsDailyStateMap,
onChange: setColumnsDailyStateMap,
}}
/>
</div>
</Drawer>
</div>
)
}
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(transactionAnalysis);