2025-06-13 19:18:28 +08:00

634 lines
26 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 moment from 'moment';
import numeral from 'numeral';
import React, { useRef, useState } from 'react';
import ReactToPrint from 'react-to-print';
import useRequest from '@ahooksjs/use-request'; // 请求数据的引用
import SubMenu from 'antd/lib/menu/SubMenu';
import ProCard from '@ant-design/pro-card'; // 卡片组件
import ProTable from '@ant-design/pro-table';
import { connect } from 'umi';
import { MenuFoldOutlined } from '@ant-design/icons';
import { Button, message, Avatar, Menu, Typography } from 'antd';
import type { CurrentUser } from "umi";
import type { FormInstance } from 'antd';
import type { ConnectState } from '@/models/connect';
import type { ActionType, ProColumns } from '@ant-design/pro-table';
import type { SHOPEXPENSEListModel } from '../ShopExpenses/data';
import { getSPRegionShopTree } from "@/services/options"; // 枚举和树相关的引用,没有使用可以删除
import { getList } from './service'; // 接口相关对象的引用
import { getDetail, getShopImg } from '@/pages/basicManage/ServerpartShop/service';
import ProDescriptions from '@ant-design/pro-descriptions';
import { exportExcel, printOutBody } from '@/utils/utils';
import defaultImg from "@/assets/brand/defaultIcon.png";
import ReactHTMLTableToExcel from "react-html-table-to-excel";
import './style.less'
import PageTitleBox from '@/components/PageTitleBox';
const { Text } = Typography;
// 标题金额拆分小数点前后显示大小
const amountDom = (value: any) => {
const stringValue = `${value}`
const [intValue, floatValue] = stringValue.split(".")
return floatValue ?
<><Text type="warning" style={{
fontSize: 30, lineHeight: 0,
fontFamily: "Bahnschrift Regular"
}}>{numeral(intValue || 0).format("0,0")}</Text><Text type="warning" style={{
fontSize: 18,
fontFamily: "Bahnschrift Regular"
}}>.{floatValue}</Text>
<Text type="warning" style={{ fontSize: 14 }}></Text></>
:
<><Text type="warning" style={{
fontSize: 30, lineHeight: 0,
fontFamily: "Bahnschrift Regular"
}}>{numeral(intValue || 0).format("0,0")}</Text><Text type="warning" style={{ fontSize: 14 }}></Text></>
}
type prop = {
isComponent: boolean;
serviceShopId: any;
time: any;
}
const SHOPEXPENSETable: React.FC<{ currentUser: CurrentUser | prop }> = (props) => {
const { currentUser } = props
const actionRef = useRef<ActionType>();
const formRef = useRef<FormInstance>();
const [curShop, setShopData] = useState<any>(); // 选择的门店数据源
const [reqDetailList, setReqDetailList] = useState<SHOPEXPENSEListModel[]>(); // 合计项数据源
const [shopImg, setShopImg] = useState<string>()// 门店图片
const [printOut, setPrintOut] = useState<any>(); // 打印数据的内容
const downloadBtnRef = useRef<any>()
const [isFirst, setIsFirst] = useState<boolean>(true)
// 树相关的属性和方法
const [currenRegion, setCurrenRegion] = useState<any>(); // 当前选中左侧菜单的片区节点
const [currenMenu, setCurrenMenu] = useState<any>(); // 当前选中左侧菜单的服务区节点
const [selectedId, setSelectedId] = useState<any>(); // 当前选中左侧菜单的门店节点
const [collapsible, setCollapsible] = useState<boolean>(false)
// 加载服务区树
const { loading: treeLoading, data: treeView = [] } = useRequest(() => {
// 1000,2000,3000 第五个的入参 先去掉
return getSPRegionShopTree(currentUser?.ProvinceCode, '', '', '', '',
'2000,3000,4000', true, true, true, 'SERVERPARTSHOP_INDEX,BUSINESS_STATE,SHOPSHORTNAME', true)
})
// 是否显示打印的表格
const [showExportTable, setShowExportTable] = useState<boolean>(false)
// 根据左侧选中的菜单加载右侧数据
const loadSelectedId = (item?: any) => {
// 选中的子菜单key
const [type, value] = item.key.split('-')
if (type === '1') {
setCurrenMenu(value)
setSelectedId('')
setCurrenRegion('')
}
else if (type === '2') {
setSelectedId(value)
setCurrenMenu('')
setCurrenRegion('')
actionRef?.current?.reload()
}
else {
setCurrenRegion(value)
setSelectedId('')
setCurrenMenu('')
}
}
// 生成左侧菜单
const getMenuDom = (data: any[], callback: (item: any) => void) => {
return (data.map((element: any) => {
// 绑定嵌套树的子节点
if (element.children && element.children.length > 0) {
return (
<SubMenu title={(element.desc ? <><span>{element.label}</span>
<span style={{ position: 'absolute', right: '10', top: '9px', lineHeight: '20px', padding: '2px 3px', color: 'red' }}>
[{element.desc}]</span></> : element.label)}
icon={element.ico ? <Avatar src={element.ico} size={16} style={{ marginRight: 4 }} shape="square" /> : null}
key={`${element.key || element.value}`}
onTitleClick={(item) => {
// 选中一级菜单
if (!currenMenu || item.key !== `${currenMenu?.key}`) {
callback.call(callback, item)
}
item.domEvent.stopPropagation();
}}
>
{element.children && element.children.length > 0 && getMenuDom(element.children, callback)}
</SubMenu>
)
}
return (<Menu.Item icon={element.ico ? <Avatar src={element.ico} size={16} shape="square" /> : null}
key={`${element.key || element.value}`}>{element.label}</Menu.Item>)
}))
}
// 定义列表字段内容
const columns: any = [
{
dataIndex: 'STATISTICS_MONTH',
title: '月份',
align: 'center',
width: 200,
hideInSearch: true,
sorter: (a, b) => a.STATISTICS_MONTH.localeCompare(b.STATISTICS_MONTH),
defaultSortOrder: 'descend',
render: (_, record) => {
return record?.STATISTICS_MONTH ? moment(record?.STATISTICS_MONTH).format('YYYYMM') : '-'
}
},
{
title: '物业费',
hideInSearch: true,
width: 300,
children: [
{
title: '面积',
dataIndex: 'mianji',
align: 'right',
hideInSearch: true,
},
{
title: '单价',
dataIndex: 'danjia',
align: 'right',
hideInSearch: true,
},
{
title: '金额',
dataIndex: 'SHOPEXPENSE_AMOUNT_5000',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_5000 || 0 - (b.SHOPEXPENSE_AMOUNT_5000 || 0),
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['5000'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_5000).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['5000'] > 0 ? '' : ''
}
</span>
</div>
}
},
]
},
{
title: '住宿费',
hideInSearch: true,
width: 300,
children: [
{
title: '间数',
dataIndex: 'jianshu',
align: 'right',
hideInSearch: true,
},
{
title: '单价',
dataIndex: 'danjia2',
align: 'right',
hideInSearch: true,
},
{
title: '金额',
dataIndex: 'SHOPEXPENSE_AMOUNT_3000',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_3000 - b.SHOPEXPENSE_AMOUNT_3000,
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['3000'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_3000).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['3000'] > 0 ? '' : ''
}
</span>
</div>
}
},
]
},
{
title: '就餐费',
dataIndex: 'SHOPEXPENSE_AMOUNT_4000',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_4000 || 0 - (b.SHOPEXPENSE_AMOUNT_4000 || 0),
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['4000'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_4000).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['4000'] > 0 ? '' : ''
}
</span>
</div>
}
},
{
title: '电费',
dataIndex: 'SHOPEXPENSE_AMOUNT_2000',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_2000 || 0 - (b.SHOPEXPENSE_AMOUNT_2000 || 0),
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['2000'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_2000).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['2000'] > 0 ? '' : ''
}
</span>
</div>
}
},
{
title: '水费',
dataIndex: 'SHOPEXPENSE_AMOUNT_1000',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_1000 || 0 - (b.SHOPEXPENSE_AMOUNT_1000 || 0),
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['1000'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_1000).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['1000'] > 0 ? '' : ''
}
</span>
</div>
}
},
{
title: '其他费用',
dataIndex: 'SHOPEXPENSE_AMOUNT_9999',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT_9999 || 0 - (b.SHOPEXPENSE_AMOUNT_9999 || 0),
render: (_, record) => {
return <div>
<span style={{ color: record?.thirdFeeObj && record?.thirdFeeObj['9999'] > 0 ? '' : '#00000073' }}>
{numeral(record?.SHOPEXPENSE_AMOUNT_9999).format('0,0.00')}
</span>
<span style={{ color: 'red', marginLeft: '8px' }}>
{
record?.thirdTypeObj && record?.thirdTypeObj['9999'] > 0 ? '' : ''
}
</span>
</div>
}
},
{
title: '合计',
dataIndex: 'SHOPEXPENSE_AMOUNT',
valueType: 'digit',
align: 'right',
hideInSearch: true,
sorter: (a, b) => a.SHOPEXPENSE_AMOUNT || 0 - (b.SHOPEXPENSE_AMOUNT || 0),
},
{
title: '查询月份',
dataIndex: 'search_months',
valueType: 'dateRange',
hideInTable: true,
hideInDescriptions: true,
search: {
transform: (value) => {
return {
// format中的格式决定入参内容
STATISTICS_MONTH_Start: moment(value[0]).startOf("month").format('YYYY-MM-DD'),
STATISTICS_MONTH_End: moment(value[1]).endOf("month").format('YYYY-MM-DD'),
};
},
},
initialValue: [moment('2023-01-01').endOf("month"), moment().subtract(1, 'month').endOf("month")],
fieldProps: {
picker: "month",
format: 'YYYY-MM',
},
},
];
// 导出excel方法
const exportTable = (e) => {
e.stopPropagation(); // 防止Collapse组件收起
const main = document.getElementsByClassName('ShopExpenseDetailHideBox')[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-ShopExpenseDetail'); // 给table添加id值与按钮上的table字段对应
container.appendChild(tempTable); // 把创建的节点添加到页面容器中
downloadBtnRef.current.handleDownload();
setShowExportTable(false)
}
return (
<div ref={(el) => {
// 打印报表
if (!reqDetailList || reqDetailList.length === 0) return;
setPrintOut(el);
}}>
<div className={'ShopExpenseDetailHideBox'} 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' }}>
{
props.isComponent ?
'' : <ProCard
style={{ width: !collapsible ? "300px" : "60px" }}
className="pageTable-leftnav"
bodyStyle={{ padding: 0, paddingTop: 20, paddingLeft: 20 }}
extra={<MenuFoldOutlined onClick={() => {
setCollapsible(!collapsible)
}} />}
colSpan={!collapsible ? "300px" : "60px"}
title={!collapsible ? "请选择门店" : ""}
headerBordered
collapsed={collapsible}
>
{!treeLoading && <Menu
mode="inline"
style={{ height: 'calc(100vh - 220px)', overflowY: 'auto', overflowX: 'hidden' }}
selectedKeys={[`2-${selectedId}` || `1-${currenMenu}` || `0-${currenRegion}`]}
onSelect={(item) => {
loadSelectedId(item)
}}
>
{getMenuDom(treeView, loadSelectedId)}
</Menu>}
</ProCard>
}
<div style={{
width: !collapsible ? 'calc(100% - 300px)' : 'calc(100% - 60px)',
paddingTop: 0,
paddingBottom: 0,
paddingRight: 0
}}>
<ProTable<SHOPEXPENSEListModel>
rowKey={(record) => {
return `${record?.STATISTICS_MONTH}`
}}
// tableStyle={{ height: 'calc(100vh - 400px)' }}
headerTitle={<PageTitleBox props={props} />}// 列表表头
actionRef={actionRef}
formRef={formRef}
search={{ span: 6, labelWidth: 'auto' }}
// 请求数据
request={async (params) => {
let start: any = ''
let end: any = ''
if (isFirst) {
console.log('props.time', props.time)
if (props.time) {
formRef.current?.setFieldsValue({ search_months: props.time })
start = props.time[0]
end = props.time[1]
}
setIsFirst(false)
} else {
start = params.STATISTICS_MONTH_Start
end = params.STATISTICS_MONTH_End
}
const data = await getList(props.serviceShopId || selectedId || 0, start, end);
// 从返回的接口中获取到合计项数据
if (data && data?.data && selectedId || data && data?.data && props.serviceShopId) {
setReqDetailList(data?.data)
const shopData = await getDetail(props.serviceShopId || selectedId);
console.log('shopDatadas', shopData);
setShopData(shopData)
const shopMseeage = await getShopImg({
ServerpartShopIds: shopData.SERVERPARTSHOP_ID,
ShowWholePower: true
})
if (shopMseeage.List && shopMseeage.List.length > 0) {
setShopImg(shopMseeage.List[0].BRAND_INTRO)
}
}
setPrintOut(undefined);
console.log('tableData', data);
if (data.data && data.data.length > 0) {
data.data.forEach((item: any) => {
const thirdFeeObj: any = {}
// 门店层的修改未修改
const thirdTypeObj: any = {}
if (item.ExpenseChangeList && item.ExpenseChangeList.length > 0) {
item.ExpenseChangeList.forEach((item: any) => {
thirdFeeObj[item.key] = item.value
thirdTypeObj[item.key] = item.type
})
}
item.thirdFeeObj = thirdFeeObj
item.thirdTypeObj = thirdTypeObj
})
}
return data;
}}
columns={columns}
// 显示合计项内容:水费、电费、住宿费、就餐费、其他费用
tableExtraRender={() => {
if (reqDetailList && reqDetailList.length > 0) {
const reduceData = reqDetailList.reduce((p: {
Expense_Amount: number, Expense_Amount_1000: number, Expense_Amount_2000: number,
Expense_Amount_3000: number, Expense_Amount_4000: number,
Expense_Amount_5000: number, Expense_Amount_9999: number
}, currentValue: SHOPEXPENSEListModel) => {
const previousValue = { ...p }
// 费用类型分别进行合计
previousValue.Expense_Amount = numeral(numeral(
(previousValue.Expense_Amount + currentValue?.SHOPEXPENSE_AMOUNT || 0)).format('0.00')).value();
// 水费
previousValue.Expense_Amount_1000 = numeral(numeral(
(previousValue.Expense_Amount_1000 + currentValue?.SHOPEXPENSE_AMOUNT_1000 || 0)).format('0.00')).value();
// 电费
previousValue.Expense_Amount_2000 = numeral(numeral(
(previousValue.Expense_Amount_2000 + currentValue?.SHOPEXPENSE_AMOUNT_2000 || 0)).format('0.00')).value();
// 住宿费
previousValue.Expense_Amount_3000 = numeral(numeral(
(previousValue.Expense_Amount_3000 + currentValue?.SHOPEXPENSE_AMOUNT_3000 || 0)).format('0.00')).value();
// 就餐费
previousValue.Expense_Amount_4000 = numeral(numeral(
(previousValue.Expense_Amount_4000 + currentValue?.SHOPEXPENSE_AMOUNT_4000 || 0)).format('0.00')).value();
// 物业费
previousValue.Expense_Amount_5000 = numeral(numeral(
(previousValue.Expense_Amount_5000 + currentValue?.SHOPEXPENSE_AMOUNT_5000 || 0)).format('0.00')).value();
// 其他费用
previousValue.Expense_Amount_9999 = numeral(numeral(
(previousValue.Expense_Amount_9999 + currentValue?.SHOPEXPENSE_AMOUNT_9999 || 0)).format('0.00')).value();
return previousValue
}, {
Expense_Amount_1000: 0, Expense_Amount_2000: 0, Expense_Amount_3000: 0, Expense_Amount_4000: 0,
Expense_Amount_5000: 0, Expense_Amount_9999: 0, Expense_Amount: 0
});
// 显示在列表上方是的类型合计
return <div style={{ paddingLeft: 24 }}>
<ProDescriptions column={4} className="commity-sale-description"
labelStyle={{ color: "#00000073" }}>
{/* 服务区 */}
<ProDescriptions.Item label="">
{/* {curShop?.SERVERPART_NAME} */}
<div style={{ display: 'flex', alignItems: 'flex-end' }}>
<img style={{ width: '78px', height: '78px', borderRadius: '8px', marginRight: '4px' }}
src={shopImg || defaultImg} />
<span>{curShop?.SERVERPART_NAME}</span>
</div>
</ProDescriptions.Item>
{/* 门店名称 */}
<ProDescriptions.Item label="">
<div style={{ height: '78px', display: 'flex', alignItems: 'flex-end' }}>
{curShop?.SHOPSHORTNAME}
</div>
</ProDescriptions.Item>
{/* 单位名称 */}
<ProDescriptions.Item label="">
<div style={{ height: '78px', display: 'flex', alignItems: 'flex-end' }}>
{/* {curShop?.BUSINESS_UNIT} */}
{curShop?.SELLER_NAME}
</div>
</ProDescriptions.Item>
</ProDescriptions>
<ProDescriptions column={4} className="commity-sale-description"
labelStyle={{ color: "#00000073" }}
title={<Text type="success"
style={{ color: "#1890ff", fontSize: 14 }}></Text>}>
<ProDescriptions.Item
label="合计费用">¥{amountDom(reduceData.Expense_Amount)}</ProDescriptions.Item>
<ProDescriptions.Item
label="物业费">¥{amountDom(reduceData.Expense_Amount_5000)}</ProDescriptions.Item>
<ProDescriptions.Item
label="宿舍费">¥{amountDom(reduceData.Expense_Amount_3000)}</ProDescriptions.Item>
<ProDescriptions.Item
label="就餐费">¥{amountDom(reduceData.Expense_Amount_4000)}</ProDescriptions.Item>
<ProDescriptions.Item
label="水费">¥{amountDom(reduceData.Expense_Amount_1000)}</ProDescriptions.Item>
<ProDescriptions.Item
label="电费">¥{amountDom(reduceData.Expense_Amount_2000)}</ProDescriptions.Item>
<ProDescriptions.Item
label="其他费用">¥{amountDom(reduceData.Expense_Amount_9999)}</ProDescriptions.Item>
</ProDescriptions>
</div>
}
return <></>
}}
toolbar={{
actions: [
<span style={{ visibility: 'hidden' }}>
<ReactHTMLTableToExcel
buttonText={'导出excel'}
ref={downloadBtnRef}
table="table-to-xls-ShopExpenseDetail"
filename={`商户月度应收款项明细表_${moment().format('YYYY_MM_DD')}`}
sheet="sheet1"
/>
</span>,
<Typography.Text type="secondary"></Typography.Text>,
<ReactToPrint
trigger={() => (
<Button key="printout" type="default" onClick={() => {
}}>
</Button>
)}
content={() => {
if (printOut) {
const content: HTMLElement | Node | undefined = printOut?.getElementsByClassName('ant-spin-container')[0]
const extra: HTMLElement | Node | undefined = printOut?.getElementsByClassName('ant-pro-table-extra')[0]
const innerText = `统计时间:${moment().format('YYYY/MM/DD')}`;
const ele = printOutBody([extra ? extra.cloneNode(true) : '', content ? content.cloneNode(true) : ''], innerText);
return ele
}
return ''
}}
/>,
<Button
key="new"
type="primary"
onClick={async (e) => {
if (reqDetailList && reqDetailList.length > 0) {
setShowExportTable(true)
setTimeout(() => {
exportTable(e)
}, 100)
} else {
message.error('暂无数据可导出!')
}
// const nowColumns = columns.filter(n => !n.hideInTable)
// const fat = nowColumns.map((n: any, index: number) => {
// if (n?.children) {
// return n?.children.map((m: { title: any; }) => {
// const title = index === 0 ? m.title : n.title + m.title
// return {...m, title}
// })
// }
// return n
// })
// const success = await exportExcel(
// fat.flat(),
// reqDetailList || [],
// `商户月度应收款项明细表_${moment().format('YYYY/MM/DD')}`,
// );
// if (success.message !== 'ok') {
// message.info({content: success.message});
// }
}}
>
excel
</Button>,
],
}}
bordered
options={false} // 隐藏刷新组件
scroll={{ y: 'calc(100vh - 470px)' }}
pagination={false}
/>
</div>
</div>
</div>
);
};
export default connect(({ user }: ConnectState) => ({
currentUser: user.currentUser
}))(SHOPEXPENSETable);