增加发票管理

This commit is contained in:
WenC 2024-04-02 18:45:46 +08:00
parent 44eca6fbdf
commit 0ac3163e79
18 changed files with 592 additions and 101 deletions

View File

@ -6,11 +6,12 @@ import {
GlobalData, GlobalData,
GlobalDataAction, GlobalDataAction,
GlobalDataActionType, GlobalDataActionType,
GlobalDispatchContext GlobalDispatchContext,
} from "./context/GlobalContext.ts"; } from "./context/GlobalContext.ts";
import {Reducer, useReducer} from "react"; import {Reducer, useReducer} from "react";
const App: React.FC = () => { const App: React.FC = () => {
const globalDataReducer = (state: GlobalData, action: GlobalDataAction) => { const globalDataReducer = (state: GlobalData, action: GlobalDataAction) => {
if (action.type == GlobalDataActionType.REFRESH_SELF) { if (action.type == GlobalDataActionType.REFRESH_SELF) {
if(action.user) { if(action.user) {

View File

@ -3,7 +3,7 @@ import {
CheckOutlined, CheckOutlined,
ClockCircleOutlined, ClockCircleOutlined,
CloseCircleOutlined, CloseCircleOutlined,
CreditCardOutlined, CreditCardOutlined, DeleteOutlined,
DownloadOutlined, DownOutlined, DownloadOutlined, DownOutlined,
EditOutlined, EditOutlined,
EyeOutlined, EyeOutlined,
@ -24,10 +24,10 @@ export default function CardProj(props: { item: IProj }) {
const nav = useNavigate(); const nav = useNavigate();
const data = props.item; const data = props.item;
const [messageApi, messageContext] = useMessage(); const [messageApi, messageContext] = useMessage();
const [projCategoryId, setProjCategoryId] = useState(data.projCategoryId);
const [projCategoryName, setProjCategoryName] = useState(data.projCategoryName); const [projCategoryName, setProjCategoryName] = useState(data.projCategoryName);
const indexListContext = useContext(IndexListContext); const indexListContext = useContext(IndexListContext);
/** /**
* *
*/ */
@ -110,7 +110,6 @@ export default function CardProj(props: { item: IProj }) {
return ( return (
<> <>
{messageContext}
<div className="card-proj"> <div className="card-proj">
<div className="title"> <div className="title">
<div className="left"> <div className="left">
@ -181,26 +180,32 @@ export default function CardProj(props: { item: IProj }) {
url: `/api/proj/update-category/${data.projId}/${e.key}`, url: `/api/proj/update-category/${data.projId}/${e.key}`,
onSuccess() { onSuccess() {
messageApi.success('修改成功'); messageApi.success('修改成功');
setProjCategoryId(e.key);
setProjCategoryName(span.innerText); setProjCategoryName(span.innerText);
} }
}); });
} }
}}> }}>
<span onClick={() => { <span>
put<any>({ <a href="/#">{projCategoryId ? projCategoryName : '无目录'}</a>
messageApi,
url: `/api/proj/cancel-category/${data.projId}`,
onSuccess() {
messageApi.success('取消成功');
setProjCategoryName('');
}
});
}}>
<a href="/#">{projCategoryName ? projCategoryName : '无目录'}</a>
<DownOutlined/> <DownOutlined/>
</span> </span>
</Dropdown> </Dropdown>
{
projCategoryId ? (
<DeleteOutlined title="移除目录" onClick={() => {
put<any>({
messageApi,
url: `/api/proj/cancel-category/${data.projId}`,
onSuccess() {
messageApi.success('取消成功');
setProjCategoryId('');
setProjCategoryName('');
}
});
}}/>
) : <></>
}
</div> </div>
</div> </div>
</div> </div>
@ -217,6 +222,7 @@ export default function CardProj(props: { item: IProj }) {
</ConfigProvider> </ConfigProvider>
</div> </div>
</div> </div>
{messageContext}
</> </>
) )
} }

View File

@ -0,0 +1,194 @@
import {Button, Dropdown, MenuProps, Modal, Table, TableProps, Tag} from "antd";
import {PlusOutlined} from "@ant-design/icons";
import {useEffect, useState} from "react";
import {get} from "../../util/AjaxUtils.ts";
import {IListPage} from "../../interfaces/listpage/IListPage.ts";
import useMessage from "antd/es/message/useMessage";
import InvoiceSave from "./InvoiceSave.tsx";
enum InvoiceStatusEnum {
PENDING = 'PENDING',
COMPLETE = 'COMPLETE',
FAILED = 'FAILED',
CANCEL = 'CANCEL',
}
type DataType = {
examineOpinion: string;
examineUserId: string;
examineUserUsername: string;
gmtCreate: string;
gmtExamine: string;
invoiceAccount: string;
invoiceAddress: string;
invoiceAmount: number;
invoiceBank: string;
invoiceFileList: string[];
invoiceFiles: string;
invoiceId: string;
invoiceNo: string;
invoiceNote: string;
invoicePhone: string;
invoiceStatus: InvoiceStatusEnum;
invoiceTitle: string;
orderIds: string[];
}
export default function InvoiceList() {
const [messageApi, messageContext] = useMessage();
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [dataArray, setDataArray] = useState<DataType[]>([]);
const columns: TableProps<DataType>['columns'] = [
{
title: '名称',
dataIndex: 'invoiceTitle',
align: 'center',
width: 180
},
{
title: '纳税人识别号',
dataIndex: 'invoiceNo',
align: 'center',
width: 180
},
{
title: '地址',
dataIndex: 'invoiceAddress',
align: 'center',
width: 180
},
{
title: '电话',
dataIndex: 'invoicePhone',
align: 'center',
width: 180
},
{
title: '开户行',
dataIndex: 'invoiceBank',
align: 'center',
width: 180
},
{
title: '开户行账号',
dataIndex: 'invoiceAccount',
align: 'center',
width: 180
},
{
title: '金额',
dataIndex: 'invoiceAmount',
align: 'center',
width: 160,
render: (value) => {
return (value / 100).toFixed(2)
}
},
{
title: '备注',
dataIndex: 'invoiceNote',
align: 'center',
width: 180
},
{
title: '创建时间',
dataIndex: 'gmtCreate',
align: 'center',
width: 180
},
{
title: '状态',
dataIndex: 'invoiceStatus',
align: 'center',
width: 100,
fixed: 'right',
render: (value) => {
if(value === InvoiceStatusEnum.PENDING) {
return <Tag color="magenta"></Tag>
}
if(value === InvoiceStatusEnum.COMPLETE) {
return <Tag color="green"></Tag>
}
if(value === InvoiceStatusEnum.FAILED) {
return <Tag color="red"></Tag>
}
if(value === InvoiceStatusEnum.CANCEL) {
return <Tag color="cyan"></Tag>
}
}
},
{
title: '操作',
dataIndex: 'operate',
align: 'center',
width: 120,
fixed: 'right',
render: (value, record) => {
if(record.invoiceStatus == InvoiceStatusEnum.COMPLETE) {
const items: MenuProps['items'] = [];
record.invoiceFileList.forEach((item, index) => {
items.push({
key: index,
label: (
<a href={item} download target="_blank">{index + 1}</a>
)
});
})
return (
<Dropdown menu={{ items }} placement="bottom" arrow>
<Button type="link"></Button>
</Dropdown>
)
}
}
},
]
useEffect(() => {
get<IListPage<DataType>>({
messageApi,
url: '/api/invoice/listpage/self',
config: {
params: {
page: page,
rows: 20
}
},
onSuccess({data}) {
setPage(data.page);
setTotal(data.total);
setDataArray(data.rows);
}
})
}, [page]);
return (
<>
<div className="invoice-list-container">
<div className="mod-list">
<div className="table-btn-group" style={{marginBottom: '15px'}}>
<Button value="small" onClick={() => {
}}><PlusOutlined/> </Button>
</div>
<Table columns={columns} dataSource={dataArray} pagination={
{
pageSize: 20,
onChange: (currentPage) => {
setPage(currentPage);
}
}
} scroll={{y: 500}} bordered key="dataTable" rowKey="invoiceId"/>
</div>
</div>
<Modal open={true}
title="申请开票"
>
<InvoiceSave />
</Modal>
{messageContext}
</>
)
}

View File

@ -0,0 +1,207 @@
import {Button, Divider, Form, Input, Radio, Space} from "antd";
import {useState} from "react";
type FormFieldType = {
invoiceTitle: string;
invoiceNo: number;
invoiceAddress: string;
invoicePhone: string;
invoiceAccount: string;
invoiceBank: string;
content: string;
rate: string;
type: string;
}
export default function InvoiceSave() {
const [form] = Form.useForm<FormFieldType>();
const [isSaveInvoiceInfo, setIsSaveInvoiceInfo] = useState(0);
return (
<>
<Form
name="basic"
initialValues={{remember: true}}
form={form}
>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceTitle"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入公司名称'}]}
>
<Input placeholder="请输入公司名称"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceNo"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入纳税人识别号'}]}
>
<Input placeholder="请输入纳税人识别号"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceAddress"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入公司地址'}]}
>
<Input placeholder="请输入公司地址"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoicePhone"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入联系电话'}]}
>
<Input placeholder="请输入联系电话"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceAccount"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开户行'}]}
>
<Input placeholder="请输入开户行"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="invoiceBank"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开户行账号'}]}
>
<Input placeholder="请输入开户行账号"/>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="content"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票内容'}]}
>
<Radio.Group>
<Space direction="vertical">
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Space>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="rate"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择开票税率'}]}
>
<Radio.Group>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="type"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请选择发票类型'}]}
>
<Radio.Group>
<Radio value={1}>A</Radio>
<Radio value={2}>B</Radio>
<Radio value={3}>C</Radio>
<Radio value={4}>D</Radio>
</Radio.Group>
</Form.Item>
</td>
</tr>
<tr>
<td className="table-label"> *</td>
<td>
<Radio.Group defaultValue={isSaveInvoiceInfo} onChange={(e) => {
setIsSaveInvoiceInfo(e.target.value)
}}>
<Radio value={0}></Radio>
<Radio value={1}></Radio>
</Radio.Group>
</td>
</tr>
</tbody>
</table>
<Divider orientation="left" plain></Divider>
<table className="pay-table">
<colgroup>
<col width="120"/>
<col/>
</colgroup>
<tbody>
<tr>
<td className="table-label"> *</td>
<td>
<div>
<span></span>
<span style={{fontSize: '20px', fontWeight: 'bold'}}>300</span>
<Button type="link" size="small"></Button>
</div>
</td>
</tr>
<tr>
<td className="table-label"></td>
<td>
<Form.Item
name="invoiceBank"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入开票备注'}]}
>
<Input.TextArea placeholder="请输入开票备注" rows={4}/>
</Form.Item>
</td>
</tr>
</tbody>
</table>
</Form>
</>
);
}

View File

@ -68,8 +68,8 @@ export default function ListProj() {
</div> </div>
) )
} }
return projs.map((item, index) => { return projs.map((item) => {
return <CardProj item={item} key={`proj${index}`}/>; return <CardProj item={item} key={new Date().getTime() + ':' + item.projId}/>;
}); });
} }
@ -77,9 +77,11 @@ export default function ListProj() {
} }
useEffect(() => { useEffect(() => {
reqData(page); if (indexListContext.categorys) {
renderCategory(); reqData(page);
}, [indexListContext.status, indexListContext.categorys, indexListContext.category, keywords, page]) renderCategory();
}
}, [indexListContext.status, indexListContext.categoryChangeCount, indexListContext.category, keywords, page])
const renderStatus = () => { const renderStatus = () => {
if (indexListContext.status == 'ALL') { if (indexListContext.status == 'ALL') {

View File

@ -25,10 +25,12 @@ export default function MenuTree(props: IMenuTree) {
if (item.isEdit) { if (item.isEdit) {
return ( return (
<> <>
<CheckOutlined className="icon" onClick={() => { <CheckOutlined className="icon" onClick={(e) => {
e.stopPropagation();
props.handleEditSaveClick(item); props.handleEditSaveClick(item);
}}/> }}/>
<CloseOutlined className="icon" onClick={() => { <CloseOutlined className="icon" onClick={(e) => {
e.stopPropagation();
props.handleEditCancelClick(item); props.handleEditCancelClick(item);
}}/> }}/>
</> </>
@ -36,13 +38,16 @@ export default function MenuTree(props: IMenuTree) {
} }
return ( return (
<> <>
<EditOutlined className="icon" onClick={() => { <EditOutlined className="icon" onClick={(e) => {
e.stopPropagation();
props.handleEditClick(item); props.handleEditClick(item);
}}/> }}/>
<PlusOutlined className="icon" onClick={() => { <PlusOutlined className="icon" onClick={(e) => {
e.stopPropagation();
props.handleAddClick(item); props.handleAddClick(item);
}}/> }}/>
<CloseOutlined className="icon" onClick={() => { <CloseOutlined className="icon" onClick={(e) => {
e.stopPropagation();
props.handleRemoveClick(item, index, parent); props.handleRemoveClick(item, index, parent);
}}/> }}/>
</> </>
@ -85,7 +90,8 @@ export default function MenuTree(props: IMenuTree) {
const lis = children.map((item, index) => { const lis = children.map((item, index) => {
const renderChildrenMenu = renderMenu(item.children, item); const renderChildrenMenu = renderMenu(item.children, item);
return ( return (
<li className={item.active ? 'active' : ''} key={item.id} onClick={() => { <li className={item.active ? 'active' : ''} key={item.id} onClick={(e) => {
e.stopPropagation();
props.handleClick(item); props.handleClick(item);
}}> }}>
<div className="menu-title"> <div className="menu-title">

View File

@ -194,7 +194,7 @@ export default function MenuTreeWithTopButton() {
...menuTreeArray ...menuTreeArray
]) ])
indexListDispatchContext({ indexListDispatchContext({
type: IndexListDataType.CATEGORY, type: IndexListDataType.CATEGORY_DELETE,
value: menus2Dropdowns(menuTreeArray) value: menus2Dropdowns(menuTreeArray)
}) })
} }

View File

@ -10,16 +10,16 @@ import {
message, message,
Radio, Radio,
Spin, Spin,
Upload, UploadFile, UploadProps Upload, UploadProps
} from "antd"; } from "antd";
import { import {
ReloadOutlined ReloadOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
import {useEffect, useState} from "react"; import {useEffect, useRef, useState} from "react";
import locale from 'antd/locale/zh_CN'; import locale from 'antd/locale/zh_CN';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import 'dayjs/locale/zh-cn'; import 'dayjs/locale/zh-cn';
import {DevUserId, downloadUrl, get, post, uploadImageUrl} from "../../util/AjaxUtils.ts"; import {DevUserId, get, post, uploadImageUrl} from "../../util/AjaxUtils.ts";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import useMessage from "antd/es/message/useMessage"; import useMessage from "antd/es/message/useMessage";
import {errorImage} from "../../util/CommonUtil.ts"; import {errorImage} from "../../util/CommonUtil.ts";
@ -70,7 +70,7 @@ export default function Payment(props: IPaymentProps) {
const [isRechargeMoneyEdit, setIsRechargeMoneyEdit] = useState(false); const [isRechargeMoneyEdit, setIsRechargeMoneyEdit] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [rechargeVoucherArray, setRechargeVoucherArray] = useState<string[]>([]); const [rechargeVoucherArray, setRechargeVoucherArray] = useState<string[]>([]);
const [thirdParty, setThirdParty] = useState(''); const [thirdParty, setThirdParty] = useState<ThirdPartyEnum | null>();
const [accountRechargeId, setAccountRechargeId] = useState(''); const [accountRechargeId, setAccountRechargeId] = useState('');
const [thirdPartyPayUrl, setThirdPartyPayUrl] = useState(''); const [thirdPartyPayUrl, setThirdPartyPayUrl] = useState('');
const [paySystemBank, setPaySystemBank] = useState<PaySystemBank>({ const [paySystemBank, setPaySystemBank] = useState<PaySystemBank>({
@ -83,14 +83,18 @@ export default function Payment(props: IPaymentProps) {
const [countdownTime, setCountdownTime] = useState(''); const [countdownTime, setCountdownTime] = useState('');
const [isCountdownTimeout, setIsCountdownTimeout] = useState(false); const [isCountdownTimeout, setIsCountdownTimeout] = useState(false);
const moneyRange: number[] = [0.01, 2000]; const moneyRange: number[] = [0.01, 2000];
let countdownInterval: number = -1; const countdownIntervalRef = useRef<number | undefined>();
const [refreshQrCodeCount, setRefreshQrCodeCount] = useState(0);
/** /**
* *
*/ */
const countdown = () => { const countdown = () => {
if (countdownInterval > -1) { if (countdownIntervalRef.current) {
clearInterval(countdownInterval); clearInterval(countdownIntervalRef.current);
}
if (thirdParty == ThirdPartyEnum.DGZZ) {
return;
} }
const Time = new Date().getTime(); const Time = new Date().getTime();
// 设定计时器的时间为60秒 // 设定计时器的时间为60秒
@ -108,11 +112,11 @@ export default function Payment(props: IPaymentProps) {
if (distance <= 0) { if (distance <= 0) {
setCountdownTime('已失效'); setCountdownTime('已失效');
setIsCountdownTimeout(true); setIsCountdownTimeout(true);
clearInterval(countdownInterval); clearInterval(countdownIntervalRef.current);
} }
} }
// 每秒更新一次计时器 // 每秒更新一次计时器
countdownInterval = setInterval(() => { countdownIntervalRef.current = setInterval(() => {
updateCountdown(); updateCountdown();
}, 1000); }, 1000);
} }
@ -131,10 +135,7 @@ export default function Payment(props: IPaymentProps) {
onSuccess({data}) { onSuccess({data}) {
setAccountRechargeId(data.accountRechargeId); setAccountRechargeId(data.accountRechargeId);
setThirdPartyPayUrl(data.thirdPartyPayUrl); setThirdPartyPayUrl(data.thirdPartyPayUrl);
clearInterval(countdownInterval); countdown();
if (thirdParty != ThirdPartyEnum.DGZZ) {
countdown();
}
}, },
onFinally() { onFinally() {
setIsLoading(false); setIsLoading(false);
@ -153,14 +154,16 @@ export default function Payment(props: IPaymentProps) {
} }
useEffect(() => { useEffect(() => {
form.setFieldsValue({ if (!thirdParty) {
thirdParty: ThirdPartyEnum.DGZZ, getPaySystemBank();
rechargeMoney: 300 form.setFieldsValue({
}) thirdParty: ThirdPartyEnum.DGZZ,
setThirdParty(ThirdPartyEnum.DGZZ); rechargeMoney: 300
getPaySystemBank(); })
setThirdParty(ThirdPartyEnum.DGZZ)
}
getPay(); getPay();
}, []); }, [thirdParty, isRechargeMoneyEdit, refreshQrCodeCount]);
const renderMoney = () => { const renderMoney = () => {
@ -178,7 +181,6 @@ export default function Payment(props: IPaymentProps) {
messageApi.error(`金额最大为${moneyRange[1]}`) messageApi.error(`金额最大为${moneyRange[1]}`)
} }
setIsRechargeMoneyEdit(false); setIsRechargeMoneyEdit(false);
getPay();
}}></Button> }}></Button>
</div> </div>
) )
@ -277,6 +279,18 @@ export default function Payment(props: IPaymentProps) {
</Form.Item> </Form.Item>
</td> </td>
</tr> </tr>
<tr>
<td className="table-label"> *</td>
<td>
<Form.Item
name="orgNumber"
style={{marginBottom: '0'}}
rules={[{required: true, message: '请输入银行账号'}]}
>
<Input placeholder="请输入银行账号"/>
</Form.Item>
</td>
</tr>
<tr> <tr>
<td className="table-label"> *</td> <td className="table-label"> *</td>
<td> <td>
@ -286,9 +300,10 @@ export default function Payment(props: IPaymentProps) {
rules={[{required: true, message: '请选择打款时间'}]} rules={[{required: true, message: '请选择打款时间'}]}
> >
<ConfigProvider locale={locale}> <ConfigProvider locale={locale}>
<DatePicker showTime placeholder="请选择打款时间" onChange={(_date, dateString) => { <DatePicker showTime placeholder="请选择打款时间"
form.setFieldValue('rechargeFinalTime', dateString); onChange={(_date, dateString) => {
}}/> form.setFieldValue('rechargeFinalTime', dateString);
}}/>
</ConfigProvider> </ConfigProvider>
</Form.Item> </Form.Item>
</td> </td>
@ -351,7 +366,7 @@ export default function Payment(props: IPaymentProps) {
{ {
isCountdownTimeout ? ( isCountdownTimeout ? (
<div className="qr-timeout" onClick={() => { <div className="qr-timeout" onClick={() => {
getPay() setRefreshQrCodeCount(refreshQrCodeCount + 1);
}}> }}>
<ReloadOutlined/> <ReloadOutlined/>
<span className="label"></span> <span className="label"></span>
@ -372,7 +387,6 @@ export default function Payment(props: IPaymentProps) {
initialValues={{remember: true}} initialValues={{remember: true}}
form={form} form={form}
onFinish={() => { onFinish={() => {
post<any>({ post<any>({
messageApi, messageApi,
url: `/api/pay/pay-account-recharge/${accountRechargeId}`, url: `/api/pay/pay-account-recharge/${accountRechargeId}`,
@ -406,7 +420,6 @@ export default function Payment(props: IPaymentProps) {
<Radio.Group onChange={(e) => { <Radio.Group onChange={(e) => {
form.setFieldValue('thirdParty', e.target.value); form.setFieldValue('thirdParty', e.target.value);
setThirdParty(e.target.value); setThirdParty(e.target.value);
getPay();
}} defaultValue="a"> }} defaultValue="a">
<Radio value="微信"></Radio> <Radio value="微信"></Radio>
<Radio value="支付宝"></Radio> <Radio value="支付宝"></Radio>

View File

@ -1,14 +1,36 @@
import './recharge-head.css'; import './recharge-head.css';
import Payment from "../payment/Payment.tsx";
import {Modal} from "antd";
import {useState} from "react";
export default function RechargeHead() { export default function RechargeHead() {
const handleRecharge = () => { const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
console.log('充值');
}
return ( return (
<div className="head-item recharge-head"> <>
<span onClick={handleRecharge}></span> <div className="head-item recharge-head">
</div> <span onClick={() => {
setIsPaymentModalOpen(true);
}}></span>
</div>
<Modal open={isPaymentModalOpen}
title="充值"
onCancel={() => {
setIsPaymentModalOpen(false);
}}
footer={false}
>
<Payment
handleConfirm={() => {
setIsPaymentModalOpen(false);
}}
handleCancel={() => {
setIsPaymentModalOpen(false);
}}
/>
</Modal>
</>
) )
} }

View File

@ -1,4 +1,6 @@
import {createContext, Dispatch} from "react"; import {createContext, Dispatch} from "react";
import {get} from "../util/AjaxUtils.ts";
import {MessageInstance} from "antd/es/message/interface";
export enum GlobalDataActionType { export enum GlobalDataActionType {
REFRESH_SELF REFRESH_SELF
@ -11,6 +13,28 @@ export interface User {
hasUserInfo: boolean; hasUserInfo: boolean;
} }
export function reloadUser(messageApi: MessageInstance, globalDispatchContext: Dispatch<GlobalDataAction>) {
return new Promise<any>(resolve => {
get<any>({
messageApi,
url: '/api/user-info/get-user-self',
onSuccess({data}) {
globalDispatchContext({
type: GlobalDataActionType.REFRESH_SELF,
user: {
balance: (Math.floor(data.accountMoney) / 100).toFixed(2),
nickname: data.nickname,
username: data.username,
hasUserInfo: data.hasUserInfo,
}
})
resolve(data);
}
})
})
}
export interface GlobalData { export interface GlobalData {
user: User; user: User;
} }
@ -28,4 +52,5 @@ export const GlobalContext = createContext<GlobalData>({
hasUserInfo: false hasUserInfo: false
} }
}); });
export const GlobalDispatchContext = createContext<Dispatch<GlobalDataAction>>(() => {}); export const GlobalDispatchContext = createContext<Dispatch<GlobalDataAction>>(() => {
});

View File

@ -5,7 +5,8 @@ export enum IndexListDataType {
PROJ, PROJ,
AGENT, AGENT,
CATEGORY, CATEGORY,
CATEGORY_CHANGE CATEGORY_CHANGE,
CATEGORY_DELETE
} }
export interface ListData { export interface ListData {
@ -13,6 +14,7 @@ export interface ListData {
status?: string; status?: string;
category?: string; category?: string;
categorys?: MenuProps['items']; categorys?: MenuProps['items'];
categoryChangeCount: number;
} }
export interface ListAction { export interface ListAction {
@ -22,7 +24,8 @@ export interface ListAction {
export const IndexListContext = createContext<ListData>({ export const IndexListContext = createContext<ListData>({
type: IndexListDataType.PROJ type: IndexListDataType.PROJ,
categoryChangeCount: 0
}) })
export const IndexListDispatchContext = createContext<Dispatch<ListAction>>(() => { export const IndexListDispatchContext = createContext<Dispatch<ListAction>>(() => {

View File

@ -2,14 +2,14 @@ import './head.css'
import BalanceHead from '../../components/balance/BalanceHead.tsx'; import BalanceHead from '../../components/balance/BalanceHead.tsx';
import RechargeHead from '../../components/recharge/RechargeHead.tsx'; import RechargeHead from '../../components/recharge/RechargeHead.tsx';
import {Divider, Dropdown, MenuProps, message, Modal, Space, Spin} from "antd"; import {Divider, Dropdown, MenuProps, message, Modal, Space, Spin} from "antd";
import {DownOutlined, UserOutlined, KeyOutlined, LogoutOutlined} from "@ant-design/icons"; import {DownOutlined, UserOutlined, KeyOutlined, LogoutOutlined, AccountBookOutlined} from "@ant-design/icons";
import {useContext, useEffect, useState} from "react"; import {useContext, useEffect, useState} from "react";
import {get, put} from "../../util/AjaxUtils.ts"; import {put} from "../../util/AjaxUtils.ts";
import {GlobalContext, GlobalDataActionType, GlobalDispatchContext} from "../../context/GlobalContext.ts"; import {GlobalContext, GlobalDispatchContext, reloadUser} from "../../context/GlobalContext.ts";
import UserEdit from "../../route/user/UserEdit.tsx"; import UserEdit from "../../components/user/UserEdit.tsx";
import PasswordChange from "../../route/password/PasswordChange.tsx"; import PasswordChange from "../../components/password/PasswordChange.tsx";
import headRightBg from '../../assets/head-right-bg.png'; import headRightBg from '../../assets/head-right-bg.png';
import Payment from "../../route/payment/Payment.tsx"; import InvoiceList from "../../components/invoice/InvoiceList.tsx";
export default function Head() { export default function Head() {
@ -22,24 +22,11 @@ export default function Head() {
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false); const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(false);
useEffect(() => { useEffect(() => {
get<any>({ reloadUser(messageApi, globalDispatchContext).then((data) => {
messageApi, if (!data.hasUserInfo) {
url: '/api/user-info/get-user-self', setIsSelfModalOpen(true);
onSuccess({data}) {
globalDispatchContext({
type: GlobalDataActionType.REFRESH_SELF,
user: {
balance: (Math.floor(data.accountMoney) / 100).toFixed(2),
nickname: data.nickname,
username: data.username,
hasUserInfo: data.hasUserInfo,
}
})
if(!data.hasUserInfo) {
setIsSelfModalOpen(true);
}
} }
}) });
}, [globalContext.user]); }, [globalContext.user]);
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
@ -47,7 +34,7 @@ export default function Head() {
key: 'userinfo', key: 'userinfo',
label: ( label: (
<div className="dropdown-item"> <div className="dropdown-item">
<UserOutlined /> <UserOutlined/>
<span className="title"></span> <span className="title"></span>
</div> </div>
), ),
@ -59,7 +46,7 @@ export default function Head() {
key: 'changePass', key: 'changePass',
label: ( label: (
<div className="dropdown-item"> <div className="dropdown-item">
<KeyOutlined /> <KeyOutlined/>
<span className="title"></span> <span className="title"></span>
</div> </div>
), ),
@ -67,11 +54,23 @@ export default function Head() {
setIsPasswordModalOpen(true); setIsPasswordModalOpen(true);
} }
}, },
{
key: 'invoice',
label: (
<div className="dropdown-item">
<AccountBookOutlined />
<span className="title"></span>
</div>
),
onClick: () => {
// nav('/invoice-list');
}
},
{ {
key: 'logout', key: 'logout',
label: ( label: (
<div className="dropdown-item"> <div className="dropdown-item">
<LogoutOutlined /> <LogoutOutlined/>
<span className="title">退</span> <span className="title">退</span>
</div> </div>
), ),
@ -108,12 +107,12 @@ export default function Head() {
title="个人信息" title="个人信息"
footer={false} footer={false}
onCancel={() => { onCancel={() => {
if(!globalContext.user.hasUserInfo) { if (!globalContext.user.hasUserInfo) {
messageApi.info('请完善个人信息'); messageApi.info('请完善个人信息');
return; return;
} }
setIsSelfModalOpen(false) setIsSelfModalOpen(false)
}} > }}>
<UserEdit handleConfirm={(data) => { <UserEdit handleConfirm={(data) => {
modal.confirm({ modal.confirm({
title: '提示', title: '提示',
@ -150,7 +149,7 @@ export default function Head() {
footer={false} footer={false}
onCancel={() => { onCancel={() => {
setIsPasswordModalOpen(false) setIsPasswordModalOpen(false)
}} > }}>
<PasswordChange handleConfirm={(data) => { <PasswordChange handleConfirm={(data) => {
modal.confirm({ modal.confirm({
title: '提示', title: '提示',
@ -183,10 +182,11 @@ export default function Head() {
}}/> }}/>
</Modal> </Modal>
<Modal open={true} <Modal open={true}
title="充值" title="发票管理"
width={1100}
footer={false} footer={false}
> >
<Payment/> <InvoiceList/>
</Modal> </Modal>
<Spin tip="正在提交..." spinning={loading} fullscreen/> <Spin tip="正在提交..." spinning={loading} fullscreen/>
{contextHolder} {contextHolder}

View File

@ -6,7 +6,7 @@ import MenuWithTopButton from "../../components/menu/MenuWithTopButton.tsx";
import MenuTreeWithTopButton from "../../components/menu/MenuTreeWithTopButton.tsx"; import MenuTreeWithTopButton from "../../components/menu/MenuTreeWithTopButton.tsx";
import ListProj from "../../components/list/ListProj.tsx"; import ListProj from "../../components/list/ListProj.tsx";
import ListProjAgent from "../../components/list/ListProjAgent.tsx"; import ListProjAgent from "../../components/list/ListProjAgent.tsx";
import {Breadcrumb, MenuProps} from 'antd'; import {Breadcrumb, Button, MenuProps} from 'antd';
import { import {
IndexListContext, IndexListContext,
IndexListDataType, IndexListDataType,
@ -28,8 +28,14 @@ export default function Index() {
state.status = action.value as string; state.status = action.value as string;
} else if (action.type == IndexListDataType.CATEGORY) { } else if (action.type == IndexListDataType.CATEGORY) {
state.categorys = action.value as MenuProps['items']; state.categorys = action.value as MenuProps['items'];
state.categoryChangeCount++;
} else if (action.type == IndexListDataType.CATEGORY_CHANGE) { } else if (action.type == IndexListDataType.CATEGORY_CHANGE) {
state.category = action.value as string state.category = action.value as string;
state.categoryChangeCount++;
} else if (action.type == IndexListDataType.CATEGORY_DELETE) {
state.categorys = action.value as MenuProps['items'];
state.category = '';
state.categoryChangeCount++;
} }
return { return {
...state ...state
@ -37,7 +43,8 @@ export default function Index() {
} }
const [listData, dispatch] = useReducer<Reducer<ListData, ListAction>>(listReducer, { const [listData, dispatch] = useReducer<Reducer<ListData, ListAction>>(listReducer, {
type: IndexListDataType.PROJ type: IndexListDataType.PROJ,
categoryChangeCount: 0
}); });
const [projMenu, setProjMenu] = useState<IMenuWithTopButton>({ const [projMenu, setProjMenu] = useState<IMenuWithTopButton>({
@ -126,6 +133,7 @@ export default function Index() {
list={agentMenu.list} list={agentMenu.list}
handleListItem={agentMenu.handleListItem} handleListItem={agentMenu.handleListItem}
/> />
<Button></Button>
</div> </div>
<div className="right"> <div className="right">
{ {

View File

@ -1,9 +1,10 @@
import './proj-new.css'; import './proj-new.css';
import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom"; import {Link, useNavigate, useParams, useSearchParams} from "react-router-dom";
import {Breadcrumb, Button, Flex, Form, Input, message, Modal, Spin} from "antd"; import {Breadcrumb, Button, Flex, Form, Input, message, Modal, Spin} from "antd";
import {useEffect, useState} from "react"; import {useContext, useEffect, useState} from "react";
import {get, post} from "../../util/AjaxUtils.ts"; import {get, post} from "../../util/AjaxUtils.ts";
import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts"; import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts";
import {GlobalDispatchContext, reloadUser} from "../../context/GlobalContext.ts";
const {TextArea} = Input; const {TextArea} = Input;
@ -13,6 +14,7 @@ type ProjInfo = {
}; };
export default function ProjNew() { export default function ProjNew() {
const globalDispatchContext = useContext(GlobalDispatchContext);
const nav = useNavigate(); const nav = useNavigate();
const pathParams = useParams(); const pathParams = useParams();
const [queryParams] = useSearchParams(); const [queryParams] = useSearchParams();
@ -156,6 +158,9 @@ export default function ProjNew() {
onSuccess({data}) { onSuccess({data}) {
setIsEditModalOpen(true); setIsEditModalOpen(true);
setCreateProjId(data.data); setCreateProjId(data.data);
reloadUser(messageApi, globalDispatchContext).then(() => {
messageApi.success('扣款成功');
});
}, },
onFinally() { onFinally() {
setLoading(false); setLoading(false);

View File

@ -145,6 +145,5 @@ export const router = createHashRouter([
{ {
path: '/agent-result/:orderId', path: '/agent-result/:orderId',
element: <AgentResult /> element: <AgentResult />
} },
// ], {basename: import.meta.env.BASE_URL})
]) ])