Merge remote-tracking branch 'origin/main'

main
白封羽 2023-01-05 17:35:33 +08:00
commit 2b3b45835a
7 changed files with 205 additions and 532 deletions

View File

@ -26,30 +26,30 @@ import React, {
useEffect, useEffect,
ReactNode ReactNode
} from "react"; } from "react";
import {InvoiceDetail, InvoiceSearchOption} from "../../../models/Invoice" import {InvoiceDetail, InvoiceSearchOption} from "../../models/Invoice"
import {Space, Typography} from 'antd'; import {Space, Typography} from 'antd';
import {SizeType} from "antd/es/config-provider/SizeContext"; import {SizeType} from "antd/es/config-provider/SizeContext";
import axios from "axios"; import axios from "axios";
import axiosInstance, {baseUrl} from "../../../utils/axiosInstance"; import axiosInstance, {baseUrl} from "../../utils/axiosInstance";
import {Simulate} from "react-dom/test-utils"; import {Simulate} from "react-dom/test-utils";
import change = Simulate.change; import change = Simulate.change;
import {useNavigate, useSearchParams} from "react-router-dom"; import {useNavigate, useSearchParams} from "react-router-dom";
import {useAppDispatch} from "../../../models/hooks";
import InvoiceUploadView from "./InvoiceUploadView";
import { import {
Invoice, Invoice,
invoiceItemsMap, invoiceItemsMap,
invoiceTypeExtraItemsMap, invoiceTypeExtraItemsMap,
invoiceTypeItemsMap, invoiceTypeItemsMap,
invoiceTypeNameMap invoiceTypeNameMap
} from "../../../models/Invoice"; } from "../../models/Invoice";
import {RadioButtonProps} from "antd/es/radio/radioButton"; import {RadioButtonProps} from "antd/es/radio/radioButton";
import qs from "qs"; import qs from "qs";
import InvoiceDetailModal from "../InvoiceDetailModal"; import InvoiceDetailModal from "./InvoiceDetailModal";
import dayjs from "dayjs"; import dayjs from "dayjs";
import {store} from "../../../models/store"; import {store} from "../../models/store";
import InvoiceUploadView from "./mine/InvoiceUploadView";
const {Text, Paragraph} = Typography;
const {Meta} = Card; const {Meta} = Card;
const {Search} = Input; const {Search} = Input;
const {Option} = Select const {Option} = Select
@ -63,7 +63,7 @@ const rangeConfig = {
rules: [{type: 'array' as const, required: false, message: 'Please select time!'}], rules: [{type: 'array' as const, required: false, message: 'Please select time!'}],
}; };
function InvoiceSearch(props: { handleSearchData: any; }) { function InvoiceSearch(props: { isManagement: boolean, handleSearchData: any }) {
const { const {
token: {colorBgContainer, colorPrimary}, token: {colorBgContainer, colorPrimary},
} = theme.useToken(); } = theme.useToken();
@ -75,7 +75,7 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
{ {
key: '1', key: '1',
label: (<a className="simpleSearchOption" onClick={() => { label: (<a onClick={() => {
invoiceSearchOption.clear(); invoiceSearchOption.clear();
setActivatedOption("发票代码") setActivatedOption("发票代码")
}}> }}>
@ -84,13 +84,33 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
}, },
{ {
key: '2', key: '2',
label: (<a className="simpleSearchOption" onClick={() => { label: (<a onClick={() => {
invoiceSearchOption.clear(); invoiceSearchOption.clear();
setActivatedOption("发票编号") setActivatedOption("发票编号")
}}> }}>
</a>) </a>)
},
{
key: '3',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("上传者姓名")
}}>
</a>)
},
{
key: '4',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("上传者工号")
}}>
</a>)
} }
]; ];
const onValuesChange = (changedValues: any, allValues: any) => { const onValuesChange = (changedValues: any, allValues: any) => {
@ -121,12 +141,23 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
if (!complexEnabled) if (!complexEnabled)
invoiceSearchOption.clear() invoiceSearchOption.clear()
console.log(invoiceSearchOption) console.log(invoiceSearchOption)
if (activatedOption === "发票代码") { invoiceSearchOption.invoiceCode = null
invoiceSearchOption.invoiceCode = value; invoiceSearchOption.invoiceNo = null
invoiceSearchOption.invoiceNo = null; invoiceSearchOption.invoiceUploaderNameFuzzy = null
} else { invoiceSearchOption.invoiceUploaderIdFuzzy = null
invoiceSearchOption.invoiceNo = value; switch (activatedOption) {
invoiceSearchOption.invoiceCode = null; case "发票代码":
invoiceSearchOption.invoiceCode = value
break;
case "发票编号":
invoiceSearchOption.invoiceNo = value
break;
case "上传者姓名":
invoiceSearchOption.invoiceUploaderNameFuzzy = value
break;
case "上传者工号":
invoiceSearchOption.invoiceUploaderIdFuzzy = value
break;
} }
props.handleSearchData(invoiceSearchOption) props.handleSearchData(invoiceSearchOption)
@ -154,7 +185,7 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
<Search className="simpleSearch" <Search className="simpleSearch"
addonBefore={<Dropdown addonBefore={<Dropdown
menu={{ menu={{
items, items: props.isManagement ? items : items.slice(0, 2),
selectable: true, selectable: true,
defaultSelectedKeys: ['1'], defaultSelectedKeys: ['1'],
}} }}
@ -175,7 +206,9 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
<Checkbox onChange={(e) => { <Checkbox onChange={(e) => {
setComplexEnabled(e.target.checked) setComplexEnabled(e.target.checked)
}}></Checkbox> }}></Checkbox>
<InvoiceUploadView needRefresh={()=>{props.handleSearchData(invoiceSearchOption)}} /> {!props.isManagement && <InvoiceUploadView needRefresh={() => {
props.handleSearchData(invoiceSearchOption)
}}/>}
</div> </div>
{complexEnabled && {complexEnabled &&
<div className="complexSearchBar" style={{ <div className="complexSearchBar" style={{
@ -225,7 +258,7 @@ function InvoiceSearch(props: { handleSearchData: any; }) {
) )
} }
function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void }) { function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void, showUploader?: boolean }) {
const { const {
token: {colorBgContainer, colorPrimary, colorSuccess, colorWarning}, token: {colorBgContainer, colorPrimary, colorSuccess, colorWarning},
} = theme.useToken(); } = theme.useToken();
@ -237,7 +270,8 @@ function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void
<div style={{ <div style={{
margin: 30 margin: 30
}}> }}>
<Badge.Ribbon color={['#00000000', colorPrimary, colorSuccess][props.invoice.invoiceState]} text={['', '报销中', '已报销'][props.invoice.invoiceState]}> <Badge.Ribbon color={['#00000000', colorPrimary, colorSuccess][props.invoice.invoiceState]}
text={['', '报销中', '已报销'][props.invoice.invoiceState]}>
<Card <Card
onClick={onClick} onClick={onClick}
hoverable hoverable
@ -253,11 +287,32 @@ function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void
} }
> >
<div style={{marginTop: -20}}> <div style={{marginTop: -20}}>
<li style={{ <div style={{
height: 32,
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: 'center'
}}>
<Text style={{
fontWeight: "bold", fontWeight: "bold",
fontSize: 20 fontSize: 20
}}>¥{(props.invoice.invoiceAmount / 100.0).toFixed(2)}</li> }}>¥{(props.invoice.invoiceAmount / 100.0).toFixed(2)}</Text>
<li>{invoiceTypeNameMap.get(props.invoice.invoiceKind)}</li> <Text style={{
fontWeight: "bold",
fontSize: 16
}}>{props.showUploader ? props.invoice.invoiceUploader.staffName : ''}</Text>
</div>
<div style={{
height: 22,
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: 'center'
}}>
<Text>{invoiceTypeNameMap.get(props.invoice.invoiceKind)}</Text>
<Text>{props.showUploader ? props.invoice.invoiceUploader.staffId : ''}</Text>
</div>
<li>{props.invoice.invoiceNo}</li> <li>{props.invoice.invoiceNo}</li>
<li>{props.invoice.invoiceDate}</li> <li>{props.invoice.invoiceDate}</li>
</div> </div>
@ -271,7 +326,7 @@ function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void
} }
function InvoiceListView(props: {}) { function InvoiceListView(props: { isManagement: boolean }) {
const [totalNum, setTotalNum] = useState(0) const [totalNum, setTotalNum] = useState(0)
const [invoices, setInvoices] = useState([] as any[]) const [invoices, setInvoices] = useState([] as any[])
const [search, setSearch] = useSearchParams() const [search, setSearch] = useSearchParams()
@ -300,9 +355,8 @@ function InvoiceListView(props: {}) {
params.pageSize = Number(search.get('pageSize')) params.pageSize = Number(search.get('pageSize'))
else else
params.pageSize = 10 params.pageSize = 10
if (!props.isManagement)
params.invoiceUploaderId = store.getState().token.staffId params.invoiceUploaderId = store.getState().token.staffId
axiosInstance({ axiosInstance({
url: 'common/invoice?' + qs.stringify(params, {skipNulls: true, arrayFormat: 'indices'}), url: 'common/invoice?' + qs.stringify(params, {skipNulls: true, arrayFormat: 'indices'}),
method: 'get', method: 'get',
@ -316,7 +370,7 @@ function InvoiceListView(props: {}) {
} }
const onChange = (pageCurrentNum: Number, pageCurrentSize: Number) => { const onChange = (pageCurrentNum: Number, pageCurrentSize: Number) => {
console.log(pageCurrentNum, pageCurrentSize) console.log(pageCurrentNum, pageCurrentSize)
navigate('/invoice/mine?currentPage=' + pageCurrentNum + '&pageSize=' + pageCurrentSize) navigate('?currentPage=' + pageCurrentNum + '&pageSize=' + pageCurrentSize)
} }
const onItemClick = (invoice: Invoice) => { const onItemClick = (invoice: Invoice) => {
@ -333,7 +387,6 @@ function InvoiceListView(props: {}) {
}).catch(function (error) { }).catch(function (error) {
console.log(error) console.log(error)
}) })
} }
useEffect(() => { useEffect(() => {
@ -345,11 +398,12 @@ function InvoiceListView(props: {}) {
console.log(invoices) console.log(invoices)
return ( return (
<div style={{}}> <div style={{}}>
<InvoiceSearch handleSearchData={handleInvoiceSearchInfo}/> <InvoiceSearch isManagement={props.isManagement} handleSearchData={handleInvoiceSearchInfo}/>
<div style={{padding: 26, display: "flex", flexDirection: "column", alignItems: "flex-end"}}> <div style={{padding: '10px 30px 30px 30px', display: "flex", flexDirection: "column", alignItems: "flex-end"}}>
<div style={{display: "flex", flexWrap: "wrap", justifyContent: 'center', width: '100%'}}> <div style={{display: "flex", flexWrap: "wrap", justifyContent: 'center', width: '100%'}}>
{invoices.map((item: Invoice, index: number) => {invoices.map((item: Invoice, index: number) =>
<InvoiceItem onClick={onItemClick} invoice={item} key={index}/> <InvoiceItem showUploader={props.isManagement} onClick={onItemClick} invoice={item}
key={index}/>
)} )}
</div> </div>
<Pagination showSizeChanger <Pagination showSizeChanger
@ -359,9 +413,11 @@ function InvoiceListView(props: {}) {
pageSize={Number(search.get('pageSize') ? search.get('pageSize') : 10)} pageSize={Number(search.get('pageSize') ? search.get('pageSize') : 10)}
total={totalNum} onChange={onChange}/> total={totalNum} onChange={onChange}/>
</div> </div>
<InvoiceDetailModal needRefresh={()=>{searchInvoiceContent(invoiceSearchOption)}} invoiceDetail={invoiceDetail} open={detailModalOpen} onClose={() => { <InvoiceDetailModal needRefresh={() => {
searchInvoiceContent(invoiceSearchOption)
}} invoiceDetail={invoiceDetail} open={detailModalOpen} onClose={() => {
setDetailModalOpen(false) setDetailModalOpen(false)
}}/> }} showFooter={true}/>
</div> </div>
) )

View File

@ -1,404 +1,7 @@
import { import InvoiceListView from "../InvoiceListView";
Card,
Dropdown,
List,
Row,
Input,
Button,
Radio,
Col,
Divider,
DatePicker,
Form,
TimePicker,
Select,
Checkbox,
Pagination, theme, Tag, Badge
} from "antd";
import type {MenuProps, PaginationProps} from 'antd';
import {DownOutlined, UploadOutlined} from '@ant-design/icons';
import React, {
ReactElement,
JSXElementConstructor,
ReactFragment,
ReactPortal,
useState,
useEffect,
ReactNode
} from "react";
import {InvoiceDetail, InvoiceSearchOption} from "../../../models/Invoice"
import {Space, Typography} from 'antd';
import {SizeType} from "antd/es/config-provider/SizeContext";
import axios from "axios";
import axiosInstance, {baseUrl} from "../../../utils/axiosInstance";
import {Simulate} from "react-dom/test-utils";
import change = Simulate.change;
import {useNavigate, useSearchParams} from "react-router-dom";
import {useAppDispatch} from "../../../models/hooks";
import {
Invoice,
invoiceItemsMap,
invoiceTypeExtraItemsMap,
invoiceTypeItemsMap,
invoiceTypeNameMap
} from "../../../models/Invoice";
import {RadioButtonProps} from "antd/es/radio/radioButton";
import qs from "qs";
import InvoiceDetailModal from "../InvoiceDetailModal";
import dayjs from "dayjs";
const {Text, Paragraph} = Typography;
const {Meta} = Card;
const {Search} = Input;
const {Option} = Select
let invoices: Array<Invoice>
const {RangePicker} = DatePicker;
const config = {
rules: [{type: 'object' as const, required: false, message: 'Please select time!'}],
};
const rangeConfig = {
rules: [{type: 'array' as const, required: false, message: 'Please select time!'}],
};
function InvoiceSearch(props: { handleSearchData: any; }) {
const {
token: {colorBgContainer, colorPrimary},
} = theme.useToken();
const [activatedOption, setActivatedOption] = useState('发票代码')
const [complexEnabled, setComplexEnabled] = useState(false)
const [invoiceSearchOption, setInvoiceSearchOption] = useState(new InvoiceSearchOption())
const items: MenuProps['items'] = [
{
key: '1',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("发票代码")
}}>
</a>)
},
{
key: '2',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("发票编号")
}}>
</a>)
},
{
key: '3',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("上传者姓名")
}}>
</a>)
},
{
key: '4',
label: (<a onClick={() => {
invoiceSearchOption.clear();
setActivatedOption("上传者工号")
}}>
</a>)
}
];
const onValuesChange = (changedValues: any, allValues: any) => {
invoiceSearchOption.clear()
console.log(allValues)
if (allValues['upload-time-picker'] !== null && allValues['upload-time-picker'] !== undefined) {
invoiceSearchOption.invoiceUploadTimeStart = allValues['upload-time-picker'][0].format('YYYY-MM-DDtHH:mm:ss')
invoiceSearchOption.invoiceUploadTimeEnd = allValues['upload-time-picker'][1].format('YYYY-MM-DDtHH:mm:ss')
}
if (allValues['invoice-time-picker'] !== null && allValues['invoice-time-picker'] !== undefined) {
invoiceSearchOption.invoiceDateStart = allValues['invoice-time-picker'][0].format('YYYY-MM-DD')
invoiceSearchOption.invoiceDateEnd = allValues['invoice-time-picker'][1].format('YYYY-MM-DD')
}
if (allValues['invoice-state'] !== "全部") {
invoiceSearchOption.invoiceStates = [allValues['invoice-state']]
}
if (allValues['invoice-kind'] !== "全部") {
invoiceSearchOption.invoiceKinds = [allValues['invoice-kind']]
}
if (allValues['invoice-uploader'] !== null && allValues['invoice-uploader'] !== undefined && allValues['invoice-uploader'].trim() !== "") {
invoiceSearchOption.invoiceUploaderId = allValues['invoice-uploader'].trim()
}
props.handleSearchData(invoiceSearchOption)
}
const onSearch = (value: string) => {
if (!complexEnabled)
invoiceSearchOption.clear()
console.log(invoiceSearchOption)
invoiceSearchOption.invoiceCode = null
invoiceSearchOption.invoiceNo = null
invoiceSearchOption.invoiceUploaderNameFuzzy = null
invoiceSearchOption.invoiceUploaderIdFuzzy = null
switch (activatedOption) {
case "发票代码":
invoiceSearchOption.invoiceCode = value
break;
case "发票编号":
invoiceSearchOption.invoiceNo = value
break;
case "上传者姓名":
invoiceSearchOption.invoiceUploaderNameFuzzy = value
break;
case "上传者工号":
invoiceSearchOption.invoiceUploaderIdFuzzy = value
break;
}
props.handleSearchData(invoiceSearchOption)
}
const getInvoiceKindsRadioButtons: any = () => {
let options: any[] = []
invoiceTypeNameMap.forEach(function (value, key, map) {
options.push(<Radio.Button style={{marginBottom: 10}} value={key}>{value}</Radio.Button>)
})
return options
}
return (
<div className="headBar">
<div className="simpleSearchBar" style={{
height: '72px',
padding: '30px',
display: "flex",
alignItems: 'center',
justifyContent: "space-between",
backgroundColor: colorBgContainer
}}>
<Search className="simpleSearch"
addonBefore={<Dropdown
menu={{
items,
selectable: true,
defaultSelectedKeys: ['1'],
}}
>
<Typography.Link>
<Space>
{activatedOption}
<DownOutlined/>
</Space>
</Typography.Link>
</Dropdown>}
placeholder={'请在此输入'}
allowClear
onSearch={(value) => onSearch(value)}
style={{width: 404}}
enterButton
/>
<Checkbox onChange={(e) => {
setComplexEnabled(e.target.checked)
}}></Checkbox>
</div>
{complexEnabled &&
<div className="complexSearchBar" style={{
margin: '20px',
padding: '20px 20px 0px 20px',
display: "flex",
backgroundColor: colorBgContainer,
borderRadius: '20px'
}}>
<Form name="complexOption" onValuesChange={onValuesChange} style={{width: '100%'}}>
<Form.Item
name={`invoice-kind`}
label={`发票类型`}
>
<Radio.Group defaultValue="全部">
<Radio.Button value="全部"></Radio.Button>
{getInvoiceKindsRadioButtons()}
</Radio.Group>
</Form.Item>
<div style={{marginTop: -10, display: "flex", flexDirection: "row", flexWrap: "wrap"}}>
<Form.Item name="upload-time-picker" label="上传时间" {...rangeConfig}
style={{marginRight: 100}}>
<RangePicker/>
</Form.Item>
<Form.Item name="invoice-time-picker" label="开票日期" {...rangeConfig}>
<RangePicker/>
</Form.Item>
</div>
<Form.Item
name={`invoice-state`}
label={`发票状态`}
>
<Radio.Group defaultValue="全部">
<Radio.Button value="全部"></Radio.Button>
<Radio.Button value="0">使</Radio.Button>
<Radio.Button value="1"></Radio.Button>
<Radio.Button value="2"></Radio.Button>
</Radio.Group>
</Form.Item>
</Form>
</div>}
</div>
)
}
function InvoiceItem(props: { invoice: Invoice, onClick(invoice: Invoice): void }) {
const {
token: {colorBgContainer, colorPrimary, colorSuccess, colorWarning},
} = theme.useToken();
const onClick = () => {
props.onClick(props.invoice)
}
return (
<div style={{
margin: 30
}}>
<Badge.Ribbon color={['#00000000', colorPrimary, colorSuccess][props.invoice.invoiceState]} text={['', '报销中', '已报销'][props.invoice.invoiceState]}>
<Card
onClick={onClick}
hoverable
style={{
width: 250,
height: 300,
}}
cover={
<img alt="thumbnail"
src={baseUrl + props.invoice.invoiceThumbnailUri}
width="220" height="180"/>
}
>
<div style={{marginTop: -20}}>
<div style={{height: 32,display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: 'center'}}>
<Text style={{
fontWeight: "bold",
fontSize: 20
}}>¥{(props.invoice.invoiceAmount / 100.0).toFixed(2)}</Text>
<Text style={{
fontWeight: "bold",
fontSize: 16
}}>{props.invoice.invoiceUploader.staffName }</Text>
</div>
<div style={{height: 22,display: "flex", flexDirection: "row", justifyContent: "space-between", alignItems: 'center'}}>
<Text>{invoiceTypeNameMap.get(props.invoice.invoiceKind)}</Text>
<Text>{props.invoice.invoiceUploader.staffId }</Text>
</div>
<li>{props.invoice.invoiceNo}</li>
<li>{props.invoice.invoiceDate}</li>
</div>
</Card>
</Badge.Ribbon>
</div>
)
}
function InvoiceManagement(props: {}) { function InvoiceManagement(props: {}) {
const [totalNum, setTotalNum] = useState(0) return <InvoiceListView isManagement={true}/>
const [invoices, setInvoices] = useState([] as any[])
const [search, setSearch] = useSearchParams()
const [invoiceSearchOption, setInvoiceSearchOption] = useState(new InvoiceSearchOption())
const [detailModalOpen, setDetailModalOpen] = useState(false)
const [invoiceDetail, setInvoiceDetail] = useState(null as InvoiceDetail | null)
const navigate = useNavigate()
const handleInvoiceSearchInfo = (invoiceSearch: InvoiceSearchOption) => {
//setInvoiceSearchOption(invoiceSearch)
searchInvoiceContent(invoiceSearch)
}
const searchInvoiceContent = (invoiceSearch: InvoiceSearchOption) => {
setInvoices([])
let params: any = invoiceSearch
Object.keys(params).forEach(key => {
if (params[key] != null && params[key] !== undefined && params[key] === "") {
params[key] = null
}
})
if (search.get('currentPage'))
params.pageNum = Number(search.get('currentPage')) - 1
else
params.pageNum = 0
if (search.get('pageSize'))
params.pageSize = Number(search.get('pageSize'))
else
params.pageSize = 10
axiosInstance({
url: 'common/invoice?' + qs.stringify(params, {skipNulls: true, arrayFormat: 'indices'}),
method: 'get',
}).then(response => {
console.log(response.data)
setTotalNum(response.data.total)
setInvoices([...response.data.records])
}).catch(function (error) {
console.log(error)
})
}
const onChange = (pageCurrentNum: Number, pageCurrentSize: Number) => {
console.log(pageCurrentNum, pageCurrentSize)
navigate('/invoice/management?currentPage=' + pageCurrentNum + '&pageSize=' + pageCurrentSize)
}
const onItemClick = (invoice: Invoice) => {
console.log(invoice)
axiosInstance({
url: 'common/invoice/' + invoice.invoiceId,
method: 'get',
}).then(response => {
console.log(response.data)
let detail: InvoiceDetail = response.data
detail.invoiceAmount /= 100.
setInvoiceDetail(detail)
setDetailModalOpen(true)
}).catch(function (error) {
console.log(error)
})
}
useEffect(() => {
console.log(search.get('currentPage'))
console.log(search.get('pageSize'))
searchInvoiceContent(invoiceSearchOption)
}, [search]);
console.log(invoices)
return (
<div style={{}}>
<InvoiceSearch handleSearchData={handleInvoiceSearchInfo}/>
<div style={{padding: 26, display: "flex", flexDirection: "column", alignItems: "flex-end"}}>
<div style={{display: "flex", flexWrap: "wrap", justifyContent: 'center', width: '100%'}}>
{invoices.map((item: Invoice, index: number) =>
<InvoiceItem onClick={onItemClick} invoice={item} key={index}/>
)}
</div>
<Pagination showSizeChanger
showQuickJumper
showTotal={(total) => `${total}`}
current={Number(search.get('currentPage') ? search.get('currentPage') : 1)}
pageSize={Number(search.get('pageSize') ? search.get('pageSize') : 10)}
total={totalNum} onChange={onChange}/>
</div>
<InvoiceDetailModal needRefresh={()=>{ searchInvoiceContent(invoiceSearchOption)}} invoiceDetail={invoiceDetail} open={detailModalOpen} onClose={() => {
setDetailModalOpen(false)
}} showFooter={true}/>
</div>
)
} }
export default InvoiceManagement export default InvoiceManagement

View File

@ -0,0 +1,6 @@
import InvoiceListView from "../InvoiceListView";
function MyInvoice() {
return <InvoiceListView isManagement={false}/>
}
export default MyInvoice

View File

@ -1,5 +1,5 @@
import React from "react"; import React, {useRef, useState} from "react";
import {Button, Popover, Tabs} from "antd"; import {Button, Popover, Tabs, TabsProps, theme} from "antd";
import {ActionType, ProColumns, ProTable} from "@ant-design/pro-components"; import {ActionType, ProColumns, ProTable} from "@ant-design/pro-components";
import Search from "antd/es/input/Search"; import Search from "antd/es/input/Search";
import {statusEnum} from "../mine/MyReimbursement"; import {statusEnum} from "../mine/MyReimbursement";
@ -278,7 +278,7 @@ class ReimbursementTab extends React.Component<any, any> {
} }
render() { render() {
return <> return <div style={{padding: 30}}>
<ReimbursementDetail reimbursement={this.state.detailedReimbursement} <ReimbursementDetail reimbursement={this.state.detailedReimbursement}
closeDetail={this.closeDetail} closeDetail={this.closeDetail}
accessLevel={this.props.accessLevel} accessLevel={this.props.accessLevel}
@ -308,55 +308,62 @@ class ReimbursementTab extends React.Component<any, any> {
// </Button>, // </Button>,
// ]} // ]}
/> />
</>; </div>;
} }
} }
class ReimbursementApproval extends React.Component<any, any> { function ReimbursementApproval() {
ref = [React.createRef<ReimbursementTab>(), React.createRef<ReimbursementTab>(), React.createRef<ReimbursementTab>()] const [activatedTab, setActivatedTab] = useState(0)
const {
constructor(props: any) { token: {colorBgContainer},
super(props); } = theme.useToken();
this.state = { const tabItems = () => {
activatedTab: 0
}
}
tabItems = () => {
return [{ return [{
label: "等待审批", label: "等待审批",
key: "0", key: "0",
children: <ReimbursementTab ref={this.ref[0]} mode={0} activate={this.state.activatedTab === 0} children: <ReimbursementTab key={0} mode={0} activate={activatedTab === 0}
accessLevel={1}/> accessLevel={1}/>
}, },
{ {
label: "审批历史", label: "审批历史",
key: "1", key: "1",
children: <ReimbursementTab ref={this.ref[1]} mode={1} activate={this.state.activatedTab === 1} children: <ReimbursementTab key={1} mode={1} activate={activatedTab === 1}
accessLevel={-1}/> accessLevel={-1}/>
}, },
{ {
label: "全部记录", label: "全部记录",
key: "2", key: "2",
children: <ReimbursementTab ref={this.ref[2]} mode={2} activate={this.state.activatedTab === 2} children: <ReimbursementTab key={2} mode={2} activate={activatedTab === 2}
accessLevel={-1}/> accessLevel={-1}/>
}] }]
} }
tabChange = (key: string) => { const tabChange = (key: string) => {
this.setState({activatedTab: Number(key)}) setActivatedTab(Number(key))
} }
render() { const renderTabBar: TabsProps['renderTabBar'] = (props, DefaultTabBar) => (
<div style={{
paddingLeft: 30,
height: 72,
background: colorBgContainer,
display: "flex",
alignItems: 'flex-end'
}}>
<DefaultTabBar {...props} style={{marginBottom: 0, height: 64}}/>
</div>
)
return ( return (
<Tabs <Tabs
style={{backgroundColor: "white",paddingLeft:10}} activeKey={activatedTab.toString()}
activeKey={this.state.activatedTab.toString()} onChange={tabChange}
onChange={this.tabChange} renderTabBar={renderTabBar}
type="line" type="line"
items={this.tabItems()} items={tabItems()}
/> />
); );
}
} }
export default ReimbursementApproval export default ReimbursementApproval

View File

@ -373,13 +373,19 @@ class Subpage extends React.Component<any, any> {
// }, // },
// }, // },
// }} // }}
tableRender={(_, dom) => (
<div style={{margin: 30}}>
{dom}
</div>
)}
search={{ search={{
defaultCollapsed: false, defaultCollapsed: false,
labelWidth: 'auto', labelWidth: 'auto',
optionRender: ({searchText}, {form}, dom) => { optionRender: ({searchText}, {form}, dom) => {
// console.log(searchConfig, formProps, dom) // console.log(searchConfig, formProps, dom)
return [ return [
<div style={{flexWrap: "nowrap", display: "flex", justifyContent: "flex-end"}}> <div style={{flexWrap: "nowrap", display: "flex", justifyContent: "flex-end", marginRight:6}}>
<Space>
<Search className="searchBar" <Search className="searchBar"
addonBefore={"报销单号:"} addonBefore={"报销单号:"}
placeholder={""} placeholder={""}
@ -394,6 +400,7 @@ class Subpage extends React.Component<any, any> {
style={{width: 100}}> style={{width: 100}}>
</Button> </Button>
</Space>
</div> </div>
] ]
} }

View File

@ -84,7 +84,6 @@ function HorizontalBarChart(props: { title: string, values: number[], labels: st
}, },
series: [ series: [
{ {
name: 'Direct',
type: 'bar', type: 'bar',
colorBy: 'data', colorBy: 'data',
emphasis: { emphasis: {
@ -96,46 +95,9 @@ function HorizontalBarChart(props: { title: string, values: number[], labels: st
}} notMerge={true} lazyUpdate={true}/> }} notMerge={true} lazyUpdate={true}/>
} }
var mMapData =
[
{from: '青海省', to: '青海省', value: 90},
{from: '青海省', to: '安徽省', value: 80},
{from: '青海省', to: '甘肃省', value: 70},
{from: '青海省', to: '宁夏省', value: 60},
{from: '青海省', to: '山西省', value: 50},
{from: '青海省', to: '陕西省', value: 40},
{from: '青海省', to: '广东省', value: 30},
{from: '青海省', to: '重庆省', value: 20},
{from: '青海省', to: '西藏省', value: 10}
];
var convertLineData = function (data: { from: string, to: string, value: number }[]) {
var res = [];
for (var i = 0; i < data.length; i++) {
var dataItem = data[i];
// @ts-ignore
var fromCoord = GeoCoordMap[dataItem.from];
// @ts-ignore
var toCoord = GeoCoordMap[dataItem.to];
if (fromCoord && toCoord) {
res.push(
{
fromName: dataItem.from,
toName: dataItem.to,
coords: [fromCoord, toCoord],
value: dataItem.value,
});
}
}
return res;
};
function StatView() { function StatView() {
const { const {
token: {colorBgContainer, colorPrimary, colorSuccess}, token: {colorBgContainer, colorPrimary, colorSuccess, colorBorder},
} = theme.useToken(); } = theme.useToken();
const [type, setType] = useState<StatType>('month'); const [type, setType] = useState<StatType>('month');
@ -400,7 +362,11 @@ function StatView() {
return geoCoordMap.find(([name,]) => name.includes(placeName)) return geoCoordMap.find(([name,]) => name.includes(placeName))
} }
let mapDataTemp: { fromName: string, toName: string, coords: any[], value: number }[] = [] let maxValue = 0
for (const item of statResponse.reimbursementPlaceStats) {
maxValue = Math.max(maxValue, item.reimbursementLaunchCount)
}
let mapDataTemp: any[] = []
let departures = new Map<string, number>() let departures = new Map<string, number>()
let destinations = new Map<string, number>() let destinations = new Map<string, number>()
for (const item of statResponse.reimbursementPlaceStats) { for (const item of statResponse.reimbursementPlaceStats) {
@ -421,10 +387,13 @@ function StatView() {
if (fromCoords && toCoords) { if (fromCoords && toCoords) {
mapDataTemp.push({ mapDataTemp.push({
"fromName": fromCoords[0], fromName: fromCoords[0],
"toName": toCoords[0], toName: toCoords[0],
"coords": [fromCoords[1], toCoords[1]], coords: [fromCoords[1], toCoords[1]],
"value": item.reimbursementLaunchCount, value: item.reimbursementLaunchCount,
lineStyle: {
width: item.reimbursementLaunchCount / maxValue * 10
}
}) })
} }
} }
@ -676,6 +645,19 @@ function StatView() {
text: '差旅去向', text: '差旅去向',
left: 'center', left: 'center',
}, },
tooltip: {
trigger: 'item',
formatter: function (params: any) {
let returnStr = '';
if (params.componentSubType == 'lines') {
returnStr += params.marker;
returnStr += params.data.fromName + ' → ' + params.data.toName;
returnStr += '' + params.data.value;
}
return returnStr;
}
},
geo: { geo: {
map: 'china', map: 'china',
emphasis: { emphasis: {
@ -685,8 +667,8 @@ function StatView() {
disabled: true disabled: true
}, },
itemStyle: { itemStyle: {
areaColor: '#d4e2fd', areaColor: colorBorder,
borderColor: '#8c8c8c' borderColor: colorBgContainer
} }
}, },
series: [ series: [
@ -695,10 +677,21 @@ function StatView() {
zlevel: 2, zlevel: 2,
lineStyle: { lineStyle: {
normal: { normal: {
color: '#', color: {
width: 3, type: 'linear',
opacity: 0.2, x: 0,
curveness: .3 y: 0,
x2: 1,
y2: 1,
colorStops: [{
offset: 0, color: '#6395f9'
}, {
offset: 1, color: '#62daab'
}],
},
opacity: 0.5,
curveness: .3,
cap: 'round'
} }
}, },
data: mapData data: mapData

View File

@ -2,13 +2,14 @@ import {Routes, Route} from "react-router-dom";
import {createBrowserHistory} from "history"; import {createBrowserHistory} from "history";
import LoginView from "../pages/login/LoginView"; import LoginView from "../pages/login/LoginView";
import HomeView from "../pages/HomeView"; import HomeView from "../pages/HomeView";
import InvoiceListView from "../pages/Invoice/mine/InvoiceListView"; import InvoiceListView from "../pages/Invoice/InvoiceListView";
import MyReimbursement from "../pages/reimbursement/mine/MyReimbursement"; import MyReimbursement from "../pages/reimbursement/mine/MyReimbursement";
import {BrowserRouter} from "./BrowserRouter" import {BrowserRouter} from "./BrowserRouter"
import InvoiceManagement from "../pages/Invoice/management/InvoiceManagement"; import InvoiceManagement from "../pages/Invoice/management/InvoiceManagement";
import ReimbursementApproval from "../pages/reimbursement/approval/ReimbursementApproval"; import ReimbursementApproval from "../pages/reimbursement/approval/ReimbursementApproval";
import StatView from "../pages/stat/StatView"; import StatView from "../pages/stat/StatView";
import Configuration from "../pages/configuration/Configuration"; import Configuration from "../pages/configuration/Configuration";
import MyInvoice from "../pages/Invoice/mine/MyInvoice";
export const history = createBrowserHistory() export const history = createBrowserHistory()
@ -17,7 +18,7 @@ function CustomRouter() {
<BrowserRouter history={history}> <BrowserRouter history={history}>
<Routes> <Routes>
<Route path="/" element={<HomeView/>}> <Route path="/" element={<HomeView/>}>
<Route path="invoice/mine" element={<InvoiceListView/>}></Route> <Route path="invoice/mine" element={<MyInvoice/>}></Route>
<Route path="reimbursement/mine" element={<MyReimbursement/>}></Route> <Route path="reimbursement/mine" element={<MyReimbursement/>}></Route>
<Route path="invoice/management" element={<InvoiceManagement/>}></Route> <Route path="invoice/management" element={<InvoiceManagement/>}></Route>
<Route path="reimbursement/approval" element={<ReimbursementApproval/>}></Route> <Route path="reimbursement/approval" element={<ReimbursementApproval/>}></Route>