Compare commits
3 Commits
e778553600
...
8590d577bd
Author | SHA1 | Date | |
---|---|---|---|
8590d577bd | |||
1411360614 | |||
960f08d92e |
@ -34,7 +34,8 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.1",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.23.1",
|
||||
"read-excel-file": "^5.8.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
|
@ -201,9 +201,98 @@ body #root{
|
||||
}
|
||||
}
|
||||
}
|
||||
.semi-popconfirm{
|
||||
.semi-popconfirm-inner{
|
||||
padding: 15px 15px;
|
||||
min-width: 260px;
|
||||
}
|
||||
.semi-popconfirm-header{
|
||||
//align-items: center;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.semi-popconfirm-header-icon{
|
||||
width: 18px;
|
||||
}
|
||||
.semi-popconfirm-header-title{
|
||||
line-height: 16px;
|
||||
}
|
||||
.semi-icon-extra-large{
|
||||
font-size: 20px;
|
||||
}
|
||||
.semi-popconfirm-body-withIcon{
|
||||
margin-left: 30px;
|
||||
}
|
||||
.semi-popconfirm-footer{
|
||||
margin-top: 15px;
|
||||
.semi-button{
|
||||
//padding: 2px 10px;
|
||||
//line-height: 16px;
|
||||
height: 28px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upload-wrapper{
|
||||
position: relative;
|
||||
.semi-upload-file-list-title{
|
||||
display: none;
|
||||
}
|
||||
.semi-upload-file-list{
|
||||
//flex-basis: auto;
|
||||
margin: 0;
|
||||
left:170px;
|
||||
top:0px;
|
||||
//top:40px;
|
||||
position: absolute;
|
||||
}
|
||||
.semi-upload-file-card{
|
||||
height: auto;
|
||||
border-radius: var(--semi-border-radius-small);
|
||||
--semi-color-fill-0: rgba(0,0,0,0.05)
|
||||
}
|
||||
.semi-upload-file-card-preview-placeholder{
|
||||
width: 24px;
|
||||
height: 22px;
|
||||
margin: 5px;
|
||||
}
|
||||
.semi-icon-file{
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
/************ end overrides ****************/
|
||||
|
||||
.bill-pdf-previewer{
|
||||
border-radius: 5px;
|
||||
overflow: hidden;
|
||||
.bill-pdf-container{
|
||||
height: 550px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
border:none;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.import-record-wrapper{
|
||||
margin: 15px 0;
|
||||
.table-list{
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
max-height: 350px;
|
||||
}
|
||||
table{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
tr{
|
||||
&:first-child td{
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
td{
|
||||
padding: 6px 10px;
|
||||
border:solid 1px #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dashboard-menu-container {
|
||||
padding: var(--dashboard-layout-padding, 15px);
|
||||
position: sticky;
|
||||
|
18
src/components/alert/index.tsx
Normal file
18
src/components/alert/index.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import {IconAlertCircle} from "@douyinfe/semi-icons";
|
||||
import React from "react";
|
||||
|
||||
import './style.less'
|
||||
|
||||
type AlertProps = {
|
||||
message: React.ReactNode;
|
||||
}
|
||||
|
||||
function Alert(props: AlertProps) {
|
||||
if(!props.message) return null;
|
||||
return <div className={'alert-container'}>
|
||||
<IconAlertCircle/>
|
||||
<span className={'alert-text'}>{props.message}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Alert
|
8
src/components/alert/style.less
Normal file
8
src/components/alert/style.less
Normal file
@ -0,0 +1,8 @@
|
||||
.alert-container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color:red;
|
||||
.alert-text{
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import {useTranslation} from "react-i18next";
|
||||
import {Card} from "@/components/card";
|
||||
import {BillQueryParams} from "@/service/api/bill.ts";
|
||||
import {useBillTypes} from "@/hooks/useBillTypes.ts";
|
||||
import {usePaymentChannels} from "@/hooks/usePaymentChannels.ts";
|
||||
|
||||
type SearchFormProps = {
|
||||
onSearch?: (params: BillQueryParams) => void;
|
||||
@ -29,6 +30,7 @@ type SearchFormFields = {
|
||||
}
|
||||
const SearchForm: React.FC<SearchFormProps> = (props) => {
|
||||
const BillTypes = useBillTypes()
|
||||
const { paymentChannelList } = usePaymentChannels()
|
||||
const formSubmit = (value: SearchFormFields) => {
|
||||
const params: BillQueryParams = {}
|
||||
|
||||
@ -146,12 +148,11 @@ const SearchForm: React.FC<SearchFormProps> = (props) => {
|
||||
</Form.Select>
|
||||
</Col>
|
||||
<Col xxl={4} xl={6} md={8}>
|
||||
<Form.Select showClear field="payment_channel" label={t('bill.title_pay_channel')}
|
||||
placeholder={t('base.please_select')} style={{width: '100%'}}>
|
||||
<Form.Select.Option value="FLYWIRE">FLYWIRE</Form.Select.Option>
|
||||
<Form.Select.Option value="CBP">CBP</Form.Select.Option>
|
||||
<Form.Select.Option value="PPS">PPS</Form.Select.Option>
|
||||
</Form.Select>
|
||||
<Form.Select
|
||||
showClear field="payment_channel" label={t('bill.title_pay_channel')}
|
||||
placeholder={t('base.please_select')} style={{width: '100%'}}
|
||||
optionList={paymentChannelList}
|
||||
/>
|
||||
</Col>
|
||||
<Col xxl={4} xl={6} md={8}>
|
||||
<Form.Select showClear field="is_delivered" label={t('bill.delivered_status')}
|
||||
|
42
src/hooks/usePaymentChannels.ts
Normal file
42
src/hooks/usePaymentChannels.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import {useState} from "react";
|
||||
|
||||
|
||||
|
||||
|
||||
export function usePaymentChannels(){
|
||||
//_setPaymentChannelList
|
||||
const [paymentChannelList] = useState<OptionValue[]>([
|
||||
{value: 'FLYWIRE', label: 'FlyWire'},
|
||||
{value: 'PPS', label: 'PPS'},
|
||||
{value: 'CBP', label: 'CBP'},
|
||||
// {value:'CASH', label:'Cash'},
|
||||
// {value: 'ASIAPAY', label: 'AsiaPay'},
|
||||
// {value: 'OTHER', label: 'Other'},
|
||||
])
|
||||
//_setPaymentChannelList
|
||||
const [paymentMethodList] = useState<OptionValue[]>([
|
||||
{value: 'card', label: 'Card(VISA,MasterCard,UnionPay,JCB...)'},
|
||||
{value: 'wechat', label: 'Wechat'},
|
||||
{value: 'alipay', label: 'Alipay'},
|
||||
{value:'cash', label:'Cash'},
|
||||
{value: 'other', label: 'Other'},
|
||||
])
|
||||
|
||||
// useEffect(()=>{
|
||||
// if(BillTypes.length == 0){
|
||||
// selectBillTypeList().then(ret => {
|
||||
// const types = ret.filter(it=>!it.description.toUpperCase().startsWith('ADJUSTMENT'))
|
||||
// .map(it=>({value: it.type, label: it.description}))
|
||||
// setBillTypes(types)
|
||||
// // 避免出现多次
|
||||
// BillTypesCache.length = 0;
|
||||
// BillTypesCache.push(...types)
|
||||
// })
|
||||
// }
|
||||
// },[])
|
||||
|
||||
return {
|
||||
paymentChannelList,
|
||||
paymentMethodList
|
||||
}
|
||||
}
|
@ -7,12 +7,13 @@
|
||||
"close": "Close",
|
||||
"confirm": "Confirm",
|
||||
"confirm_delete": "Please confirm delete record",
|
||||
"confirm_next_operation": "Please confirm this operation",
|
||||
"confirm_import": "Confirm Import",
|
||||
"confirm_next_operation": "Are you sure to process this action",
|
||||
"confirm_paid": "Confirm paid",
|
||||
"copy-pay-url": "Copy payment link",
|
||||
"delete": "Delete",
|
||||
"operate_fail": "Operation failed",
|
||||
"operate_success": "Operation success",
|
||||
"operate_success": "Process success",
|
||||
"please_enter": "Please Enter",
|
||||
"please_select": "Please Select",
|
||||
"please_select_bill_type": "Please Select Bill Type",
|
||||
@ -20,12 +21,20 @@
|
||||
"query_bill": "Failed to query bill:",
|
||||
"remove": "Remove",
|
||||
"save": "Save",
|
||||
"select_excel_file": "Select File",
|
||||
"select_upload_file": "Select File",
|
||||
"student_number": "Student Number",
|
||||
"validate": {
|
||||
"email": "Email format is incorrect",
|
||||
"error_details_message": "Please set bill details"
|
||||
},
|
||||
"warning": "Warning"
|
||||
},
|
||||
"bill": {
|
||||
"add_bill_record": "Add Transaction Record",
|
||||
"bill_date": "In",
|
||||
"bill_number": "Bill Number",
|
||||
"btn_cancel_confirm": "Cancel Confirm",
|
||||
"cancel": "Cancel",
|
||||
"cancel_confirm": "Please make sure to cancel the bill",
|
||||
"cancel_confirm_bills": "Confirm the check check bill?",
|
||||
@ -44,13 +53,21 @@
|
||||
"confirm_student_number": "Confirm Student Number",
|
||||
"confirm_success": "Confirm success!",
|
||||
"confirmed": "Confirmed",
|
||||
"create": {
|
||||
"confirm": "Confirm Add",
|
||||
"pay_area": "Payment Area"
|
||||
},
|
||||
"delivered_status": "Delivered Status",
|
||||
"delivered_status_no": "Undivided",
|
||||
"delivered_status_yes": "Delivered",
|
||||
"download-qr-code": "Download QR Code",
|
||||
"download_receipt": "Download receipt",
|
||||
"export_excel": "Export Transaction Excel",
|
||||
"import_bill": "Add Transaction Record",
|
||||
"import": {
|
||||
"error_require_file": "Please select the transaction record file to import",
|
||||
"error_require_payment_channel": "Please select a payment channel"
|
||||
},
|
||||
"import_bill": "Import Transaction Record",
|
||||
"import_excel": "Import Transaction Excel",
|
||||
"paid": "Paid",
|
||||
"paid_confirm": "Please confirm the order status is set to paid",
|
||||
@ -143,6 +160,10 @@
|
||||
"total_amount": "Total Amount HKD"
|
||||
},
|
||||
"permission": {
|
||||
"message": {
|
||||
"empty_tips": "No data, please create new",
|
||||
"error_require": "Please set your username or role"
|
||||
},
|
||||
"title": {
|
||||
"add": "New",
|
||||
"bill.btn.check": "Reconciliation button",
|
||||
|
@ -7,6 +7,7 @@
|
||||
"close": "关闭",
|
||||
"confirm": "确定",
|
||||
"confirm_delete": "请确认是否删除此数据",
|
||||
"confirm_import": "确认导入",
|
||||
"confirm_next_operation": "请确认是否进行此操作",
|
||||
"confirm_paid": "确认已支付",
|
||||
"copy-pay-url": "复制支付链接",
|
||||
@ -20,12 +21,20 @@
|
||||
"query_bill": "查询账单失败:",
|
||||
"remove": "删除",
|
||||
"save": "保存",
|
||||
"select_excel_file": "选择文件",
|
||||
"select_upload_file": "选择文件",
|
||||
"student_number": "学号",
|
||||
"validate": {
|
||||
"email": "Email格式不正确",
|
||||
"error_details_message": "请设置账单详情"
|
||||
},
|
||||
"warning": "警告"
|
||||
},
|
||||
"bill": {
|
||||
"add_bill_record": "添加交易记录",
|
||||
"bill_date": "开始支付时间",
|
||||
"bill_number": "账单编号",
|
||||
"btn_cancel_confirm": "取消确认",
|
||||
"cancel": "作废",
|
||||
"cancel_confirm": "确定作废此账单",
|
||||
"cancel_confirm_bills": "确认对账选中账单?",
|
||||
@ -44,13 +53,21 @@
|
||||
"confirm_student_number": "确认学号",
|
||||
"confirm_success": "对账成功!",
|
||||
"confirmed": "已对账",
|
||||
"create": {
|
||||
"confirm": "确认添加",
|
||||
"pay_area": "支付区域"
|
||||
},
|
||||
"delivered_status": "分账状态",
|
||||
"delivered_status_no": "未分账",
|
||||
"delivered_status_yes": "已分账",
|
||||
"download-qr-code": "下载二维码",
|
||||
"download_receipt": "下载收据",
|
||||
"export_excel": "导出交易记录",
|
||||
"import_bill": "添加交易记录",
|
||||
"import": {
|
||||
"error_require_file": "请选择要导入的交易记录文件",
|
||||
"error_require_payment_channel": "请选择支付渠道"
|
||||
},
|
||||
"import_bill": "导入交易记录",
|
||||
"import_excel": "导入交易记录",
|
||||
"paid": "已支付",
|
||||
"paid_confirm": "是否将此订单状态设为已支付",
|
||||
@ -143,6 +160,10 @@
|
||||
"total_amount": "应付总金额"
|
||||
},
|
||||
"permission": {
|
||||
"message": {
|
||||
"empty_tips": "暂无数据,请新增",
|
||||
"error_require": "请设置用户名或角色"
|
||||
},
|
||||
"title": {
|
||||
"add": "新增",
|
||||
"bill.btn.check": "对账按钮",
|
||||
|
@ -7,6 +7,7 @@
|
||||
"close": "關閉",
|
||||
"confirm": "確定",
|
||||
"confirm_delete": "請確認是否刪除此數據",
|
||||
"confirm_import": "確認導入",
|
||||
"confirm_next_operation": "請確認是否進行此操作",
|
||||
"confirm_paid": "確認已支付",
|
||||
"copy-pay-url": "複製付款連結",
|
||||
@ -20,12 +21,20 @@
|
||||
"query_bill": "查詢帳單失敗:",
|
||||
"remove": "刪除",
|
||||
"save": "儲存",
|
||||
"select_excel_file": "選擇文件",
|
||||
"select_upload_file": "選擇文件",
|
||||
"student_number": "學號",
|
||||
"validate": {
|
||||
"email": "Email格式不正確",
|
||||
"error_details_message": "請設定帳單詳情"
|
||||
},
|
||||
"warning": "警告"
|
||||
},
|
||||
"bill": {
|
||||
"add_bill_record": "新增交易記錄",
|
||||
"bill_date": "開始支付時間",
|
||||
"bill_number": "帳單編號",
|
||||
"btn_cancel_confirm": "取消確認",
|
||||
"cancel": "作廢",
|
||||
"cancel_confirm": "確定作廢此帳單",
|
||||
"cancel_confirm_bills": "確認對帳選取帳單?",
|
||||
@ -44,13 +53,21 @@
|
||||
"confirm_student_number": "確認學號",
|
||||
"confirm_success": "對帳成功!",
|
||||
"confirmed": "已對帳",
|
||||
"create": {
|
||||
"confirm": "確認新增",
|
||||
"pay_area": "支付區域"
|
||||
},
|
||||
"delivered_status": "分帳狀態",
|
||||
"delivered_status_no": "未分帳",
|
||||
"delivered_status_yes": "已分賬",
|
||||
"download-qr-code": "下載二維碼",
|
||||
"download_receipt": "下載收據",
|
||||
"export_excel": "導出交易记录",
|
||||
"import_bill": "新增交易记录",
|
||||
"import": {
|
||||
"error_require_file": "請選擇要匯入的交易記錄文件",
|
||||
"error_require_payment_channel": "請選擇支付管道"
|
||||
},
|
||||
"import_bill": "導入交易記錄",
|
||||
"import_excel": "導入交易记录",
|
||||
"paid": "已支付",
|
||||
"paid_confirm": "是否將此訂單狀態設為已支付",
|
||||
@ -143,6 +160,10 @@
|
||||
"total_amount": "應付總金額"
|
||||
},
|
||||
"permission": {
|
||||
"message": {
|
||||
"empty_tips": "暫無數據,請新增",
|
||||
"error_require": "請設定使用者名稱或角色"
|
||||
},
|
||||
"title": {
|
||||
"add": "新增",
|
||||
"bill.btn.check": "對帳按鈕",
|
||||
|
@ -1,29 +1,124 @@
|
||||
import {useSetState} from "ahooks";
|
||||
import {Button, Checkbox, Popconfirm, Select, Space} from "@douyinfe/semi-ui";
|
||||
import {Button, Checkbox, Empty, Popconfirm, Select, Space, Toast} from "@douyinfe/semi-ui";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useMemo} from "react";
|
||||
import {useEffect, useMemo} from "react";
|
||||
|
||||
import './permission.less';
|
||||
|
||||
import {Card} from "@/components/card";
|
||||
import {useRemoteUserList} from "@/hooks/useRemoteUserList.ts";
|
||||
import {getUserPermissionList, removeUserPermission} from "@/service/api/user.ts";
|
||||
|
||||
|
||||
const PermissionList = [
|
||||
'manual',
|
||||
'bill.query',
|
||||
'bill.check',
|
||||
'bill.pay',
|
||||
'bill.btn.confirm',
|
||||
'bill.btn.check',
|
||||
'permission'
|
||||
]
|
||||
// const PermissionList = [
|
||||
// 'manual',
|
||||
// 'bill.query',
|
||||
// 'bill.check',
|
||||
// 'bill.pay',
|
||||
// 'bill.btn.confirm',
|
||||
// 'bill.btn.check',
|
||||
// 'permission'
|
||||
// ]
|
||||
|
||||
type UserPermissionType = {
|
||||
id: number;
|
||||
username: string;
|
||||
role: string;
|
||||
permissions: string[];
|
||||
|
||||
const UserPermissionItem = ({it, onChange, usernameOptionList}: {
|
||||
it: UserPermission;
|
||||
onChange: (action:'remove'|'saved'|'modify',value:UserPermission) => void;
|
||||
usernameOptionList: OptionValue[]
|
||||
}) => {
|
||||
const {t} = useTranslation()
|
||||
const onValueChange = (value: {
|
||||
[key:string]:string|boolean
|
||||
})=>{
|
||||
onChange('modify',{
|
||||
...it,
|
||||
...value
|
||||
})
|
||||
}
|
||||
const onSave = ()=>{
|
||||
console.log(it)
|
||||
if(!it.role || !it.username){
|
||||
Toast.warning(t('permission.message.error_require'))
|
||||
return;
|
||||
}
|
||||
}
|
||||
// const [values,setValues] = use
|
||||
return (<div className="table-row">
|
||||
<div className="item item-username item-type">
|
||||
<div className="form-item">
|
||||
{it.id > 0 ? <span>{it.username}</span> : <Select
|
||||
style={{width: '100%'}}
|
||||
filter
|
||||
optionList={usernameOptionList}
|
||||
placeholder={t('base.please_select')}
|
||||
defaultValue={it.username}
|
||||
onChange={(value) => onValueChange({username: String(value)})}
|
||||
/>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="item item-role item-type">
|
||||
<div className="form-item">
|
||||
<Select
|
||||
style={{width: '100%'}}
|
||||
optionList={[
|
||||
{label: 'ROOT', value: 'root'},
|
||||
{label: 'RO', value: 'ro'},
|
||||
{label: 'FO', value: 'fo'},
|
||||
]}
|
||||
defaultValue={it.role}
|
||||
onChange={(value) => onValueChange({role: String(value)})}
|
||||
placeholder={t('base.please_select')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`item item-type item-type-manual`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.manual_payment}
|
||||
onChange={e => onValueChange({manual_payment: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-query`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.bill_page}
|
||||
onChange={e => onValueChange({bill_page: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-check`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.apply_page}
|
||||
onChange={e => onValueChange({apply_page: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-pay`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.complete_button}
|
||||
onChange={e => onValueChange({complete_button: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-btn-confirm`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.confirm_button}
|
||||
onChange={e => onValueChange({confirm_button: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-btn-check`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.apply_button}
|
||||
onChange={e => onValueChange({apply_button: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className={`item item-type item-type-bill-permission`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.permission_edit}
|
||||
onChange={e => onValueChange({permission_edit: !!e.target.checked})}/>
|
||||
</div>
|
||||
<div className="item item-operation text-center">
|
||||
<Space>
|
||||
<Popconfirm
|
||||
title={t('base.warning')} onConfirm={() => onChange('remove',it)}
|
||||
position={'topRight'}
|
||||
content={`${t('base.confirm_delete')}?`}
|
||||
>
|
||||
<Button theme={'solid'} type="danger" size={'small'}>{t('base.delete')}</Button>
|
||||
</Popconfirm>
|
||||
<Button size={'small'} theme={'solid'} onClick={onSave}>{t('base.save')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
const Permission = () => {
|
||||
@ -31,44 +126,83 @@ const Permission = () => {
|
||||
const usernameList = useRemoteUserList();
|
||||
|
||||
const [state, setState] = useSetState<{
|
||||
list: UserPermissionType[];
|
||||
list: UserPermission[];
|
||||
loading?: boolean;
|
||||
}>({
|
||||
list: []
|
||||
})
|
||||
const usernameOptionList = useMemo(() => (usernameList.map(name => ({label: name, value: name}))), [usernameList])
|
||||
|
||||
// load user permission list
|
||||
const loadUserPermissionList = () => {
|
||||
setState({loading: true})
|
||||
getUserPermissionList().then(list => {
|
||||
setState({loading: false, list})
|
||||
})
|
||||
}
|
||||
useEffect(loadUserPermissionList, [])
|
||||
|
||||
// remove a user permission
|
||||
const removeItem = (index: number, id: number) => {
|
||||
const removeFromList = ()=>{
|
||||
const newList = [...state.list];
|
||||
newList.splice(index, 1)
|
||||
setState({list: newList})
|
||||
}
|
||||
if (id > 0) { // 数据库数据 需要调用接口
|
||||
|
||||
removeUserPermission(id).then(removeFromList)
|
||||
return;
|
||||
}
|
||||
const newList = [...state.list];
|
||||
newList.splice(index, 1)
|
||||
setState({list: newList})
|
||||
removeFromList();
|
||||
}
|
||||
|
||||
const onUsernameChange = (index: number, name: string, role: string) => {
|
||||
const newList = [...state.list];
|
||||
newList[index].username = name;
|
||||
newList[index].role = role;
|
||||
setState({list: newList})
|
||||
}
|
||||
const onPermissionChange = (_index: number, name: string, checked?: boolean) => {
|
||||
const newList = [...state.list];
|
||||
const index = newList[_index].permissions.indexOf(name)
|
||||
if (index == -1) { // 没有找到
|
||||
if (checked) { // 已经选中
|
||||
newList[index].permissions.push(name)
|
||||
}
|
||||
} else {
|
||||
if (!checked) { // 已经选中
|
||||
newList[index].permissions.splice(index, 1)
|
||||
}
|
||||
}
|
||||
setState({list: newList})
|
||||
}
|
||||
// const onUsernameChange = (index: number, name: string, role: string) => {
|
||||
// const newList = [...state.list];
|
||||
// newList[index].username = name;
|
||||
// newList[index].role = role;
|
||||
// setState({list: newList})
|
||||
// }
|
||||
// const onPermissionChange = (_index: number, name: string, checked?: boolean) => {
|
||||
// const newList = [...state.list];
|
||||
// const index = newList[_index].permissions.indexOf(name)
|
||||
// if (index == -1) { // 没有找到
|
||||
// if (checked) { // 已经选中
|
||||
// newList[index].permissions.push(name)
|
||||
// }
|
||||
// } else {
|
||||
// if (!checked) { // 已经选中
|
||||
// newList[index].permissions.splice(index, 1)
|
||||
// }
|
||||
// }
|
||||
// setState({list: newList})
|
||||
// }
|
||||
|
||||
const addNewRecord = () => {
|
||||
setState({list: [...state.list, {username: '', role: '', permissions: [], id: 0}]})
|
||||
setState({
|
||||
list: [...state.list, {
|
||||
id: 0,
|
||||
username: '',
|
||||
role: '',
|
||||
manual_payment: false,
|
||||
bill_page: false,
|
||||
apply_page: false,
|
||||
complete_button: false,
|
||||
confirm_button: false,
|
||||
apply_button: false,
|
||||
permission_edit: false
|
||||
}]
|
||||
})
|
||||
}
|
||||
|
||||
const handleChange = (action:'remove'|'saved'|'modify',value:UserPermission,index:number) =>{
|
||||
if(action == 'remove') removeItem(index,value.id)
|
||||
else if(action == 'modify'){
|
||||
const newList = [...state.list];
|
||||
newList[index] = {
|
||||
...value
|
||||
}
|
||||
setState({list: newList})
|
||||
}
|
||||
}
|
||||
|
||||
return (<Card style={{marginBottom: 20}}>
|
||||
@ -77,59 +211,27 @@ const Permission = () => {
|
||||
<div className="header table-row">
|
||||
<div className="item item-username item-type">{t('permission.title.username')}</div>
|
||||
<div className="item item-role item-type">{t('permission.title.role')}</div>
|
||||
{PermissionList.map((key, idx) => (
|
||||
<div key={idx}
|
||||
className={`item item-type item-type-${key.replace(/\./g, '-')}`}>{t(`permission.title.${key}`)}</div>
|
||||
))}
|
||||
<div className={`item item-type item-type-manual`}>{t(`permission.title.manual`)}</div>
|
||||
<div className={`item item-type item-type-bill-query`}>{t(`permission.title.bill.query`)}</div>
|
||||
<div className={`item item-type item-type-bill-check`}>{t(`permission.title.bill.check`)}</div>
|
||||
<div className={`item item-type item-type-bill-pay`}>{t(`permission.title.bill.pay`)}</div>
|
||||
<div
|
||||
className={`item item-type item-type-bill-btn-confirm`}>{t(`permission.title.bill.btn.confirm`)}</div>
|
||||
<div
|
||||
className={`item item-type item-type-bill-btn-check`}>{t(`permission.title.bill.btn.check`)}</div>
|
||||
<div className={`item item-type item-type-bill-permission`}>{t(`permission.title.permission`)}</div>
|
||||
<div className="item item-operation">{t('bill.title_operate')}</div>
|
||||
</div>
|
||||
{state.list.map((it, index) => (<div className="table-row" key={index}>
|
||||
<div className="item item-username item-type">
|
||||
<div className="form-item">
|
||||
<Select
|
||||
style={{width: '100%'}}
|
||||
filter
|
||||
optionList={usernameOptionList}
|
||||
placeholder={t('base.please_select')}
|
||||
onChange={(name) => onUsernameChange(index, String(name), it.role)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="item item-role item-type">
|
||||
<div className="form-item">
|
||||
<Select
|
||||
style={{width: '100%'}}
|
||||
optionList={[
|
||||
{label: 'ROOT', value: 'root'},
|
||||
{label: 'RO', value: 'ro'},
|
||||
{label: 'FO', value: 'fo'},
|
||||
]}
|
||||
onChange={(role) => onUsernameChange(index, it.username, String(role))}
|
||||
placeholder={t('base.please_select')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{PermissionList.map((key, idx) => (
|
||||
<div key={idx} className={`item item-type item-type-${key.replace(/\./g, '-')}`}>
|
||||
<Checkbox
|
||||
defaultChecked={it.permissions.includes(key)}
|
||||
onChange={e => onPermissionChange(index, key, e.target.checked)}/>
|
||||
</div>
|
||||
))}
|
||||
<div className="item item-operation text-center">
|
||||
<Space>
|
||||
<Popconfirm
|
||||
title={t('base.warning')} onConfirm={() => removeItem(index, it.id)}
|
||||
position={'topRight'}
|
||||
content={`${t('base.confirm_delete')}?`}
|
||||
>
|
||||
<Button theme={'solid'} type="danger" size={'small'}>{t('base.delete')}</Button>
|
||||
</Popconfirm>
|
||||
<Button size={'small'} theme={'solid'}>{t('base.save')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</div>))}
|
||||
{state.list.map((it, index) => (<UserPermissionItem
|
||||
key={index} it={it} onChange={(action,value) => handleChange(action,value,index)}
|
||||
usernameOptionList={usernameOptionList}
|
||||
/>))}
|
||||
{state.list.length == 0 && <div style={{backgroundColor:'#fafafa'}}>
|
||||
<Empty
|
||||
description={t('permission.message.empty_tips')}
|
||||
style={{paddingBottom:20}}
|
||||
/>
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
<Space style={{marginTop: 20}}>
|
||||
|
@ -1,60 +1,173 @@
|
||||
import {Button, Col, Form, Modal, Row, Select, Space} from "@douyinfe/semi-ui";
|
||||
import {Button, Col, Divider, Form, InputNumber, Modal, Row, Select, Space} from "@douyinfe/semi-ui";
|
||||
import {IconAlertCircle} from "@douyinfe/semi-icons";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useSetState} from "ahooks";
|
||||
|
||||
import {useBillTypes} from "@/hooks/useBillTypes.ts";
|
||||
import {usePaymentChannels} from "@/hooks/usePaymentChannels.ts";
|
||||
import {addBillRecord} from "@/service/api/bill.ts"
|
||||
import dayjs from "dayjs";
|
||||
|
||||
type BillPaidModalProps = {
|
||||
onConfirm: () => void
|
||||
onCancel?: () => void
|
||||
}
|
||||
type BillTypeListProps = {
|
||||
onClose?: (refresh?: boolean) => void;
|
||||
onChange?: (confirms: ConfirmedBillDetail[]) => void;
|
||||
}
|
||||
|
||||
export const BillTypeList: React.FC<BillTypeListProps> = (props) => {
|
||||
const {t} = useTranslation()
|
||||
const [state, setState] = useSetState<{
|
||||
confirmed: ConfirmedBillDetail[]
|
||||
}>({
|
||||
confirmed: [{bill_type: '', amount: 0}]
|
||||
})
|
||||
const BillTypes = useBillTypes()
|
||||
|
||||
const onChange = (value: string, index: number, type: 'type' | 'amount') => {
|
||||
if (state.confirmed.length <= index || !value) return;
|
||||
const confirmed = [...state.confirmed]
|
||||
if (type == 'type') {
|
||||
confirmed[index].bill_type = value
|
||||
} else {
|
||||
confirmed[index].amount = Number(value)
|
||||
}
|
||||
|
||||
setState({confirmed})
|
||||
props.onChange?.(confirmed)
|
||||
}
|
||||
|
||||
const addOrRemove = (index: number) => {
|
||||
// 不允许删除最后一个
|
||||
if (index > -1 && state.confirmed.length <= 1) return;
|
||||
const confirmed = [...state.confirmed, ...(index == -1 ? [{bill_type: '', amount: 0}] : [])]
|
||||
if (index > -1) confirmed.splice(index, 1)
|
||||
setState({confirmed})
|
||||
props.onChange?.(confirmed)
|
||||
}
|
||||
|
||||
|
||||
return (<div className={'bill-type-list'} style={{marginTop: 10}}>
|
||||
<Divider>{t('bill.title_bill_detail')}</Divider>
|
||||
{
|
||||
state.confirmed.map((item, index) => (
|
||||
<div key={index} className="confirm-item-btn align-center space-between" style={{marginTop: 20}}>
|
||||
<Select
|
||||
value={item.bill_type}
|
||||
style={{width: 240}}
|
||||
onChange={v => onChange(String(v), index, 'type')}
|
||||
placeholder={t('base.please_select_bill_type')}>
|
||||
{
|
||||
BillTypes.map((it, idx) => (
|
||||
<Select.Option key={idx} value={it.label}>{it.label}</Select.Option>))
|
||||
}
|
||||
</Select>
|
||||
|
||||
<Space spacing={10}>
|
||||
<InputNumber
|
||||
hideButtons precision={2} value={item.amount} type={'number'}
|
||||
onChange={v => onChange(String(v), index, 'amount')} style={{width: 140}}/>
|
||||
<Button
|
||||
disabled={state.confirmed.length <= 1} onClick={() => addOrRemove(index)}
|
||||
theme={'solid'} type={'secondary'}>{t('base.remove')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<div style={{marginTop: 10, marginBottom: 20}}>
|
||||
<Button onClick={() => addOrRemove(-1)}>{t('base.add')}</Button>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
|
||||
export const AddBillModal: React.FC<BillPaidModalProps> = (props) => {
|
||||
const {t} = useTranslation()
|
||||
const BillTypes = useBillTypes()
|
||||
const {paymentChannelList, paymentMethodList} = usePaymentChannels();
|
||||
|
||||
const [state, setState] = useSetState<{
|
||||
loading?: boolean;
|
||||
open?:boolean
|
||||
}>({})
|
||||
open?: boolean;
|
||||
details: ConfirmedBillDetail[];
|
||||
errorMessage?: string;
|
||||
}>({
|
||||
details: []
|
||||
})
|
||||
|
||||
|
||||
const onSubmit = (values: BillUpdateParams) => {
|
||||
const onSubmit = (values: CreateBillRecordModel) => {
|
||||
if (state.details.length == 0) {
|
||||
setState({errorMessage: t('base.validate.error_details_message')})
|
||||
return;
|
||||
}
|
||||
for (const it of state.details) {
|
||||
if (!it.bill_type || it.amount <= 0) {
|
||||
setState({errorMessage: t('base.validate.error_details_message')})
|
||||
return;
|
||||
}
|
||||
}
|
||||
setState({
|
||||
loading: true
|
||||
loading: true, errorMessage: undefined
|
||||
})
|
||||
values.details = state.details
|
||||
values.check_student = true;
|
||||
values.initiated_paid_date = dayjs(values.initiated_paid_date).format("YYYY-MM-DD")
|
||||
values.paid_date = dayjs(values.paid_date).format("YYYY-MM-DD")
|
||||
values.delivered_date = dayjs(values.delivered_date).format("YYYY-MM-DD")
|
||||
addBillRecord(values).then(()=>{
|
||||
setState({open:false})
|
||||
props.onConfirm()
|
||||
}).catch(e => {
|
||||
setState({errorMessage: e.message})
|
||||
}).finally(() => {
|
||||
setState({
|
||||
loading: false
|
||||
})
|
||||
})
|
||||
//
|
||||
}
|
||||
const handleClose = ()=>{
|
||||
setState({
|
||||
loading: false,
|
||||
open:false,
|
||||
errorMessage:undefined
|
||||
})
|
||||
console.log(values)
|
||||
}
|
||||
return (<>
|
||||
<Button onClick={()=>setState({open:true})} theme={'solid'}>{t('bill.import_bill')}</Button>
|
||||
<Button onClick={() => setState({open: true})} theme={'solid'}>{t('bill.add_bill_record')}</Button>
|
||||
<Modal
|
||||
title={t('bill.import_bill')}
|
||||
title={t('bill.add_bill_record')}
|
||||
visible={state.open}
|
||||
closeOnEsc={true}
|
||||
onCancel={()=>setState({open:false})}
|
||||
onCancel={handleClose}
|
||||
footer={null}
|
||||
width={600}
|
||||
okText={t('base.confirm')}
|
||||
maskClosable={false}
|
||||
>
|
||||
|
||||
<Form<BillUpdateParams> onSubmit={onSubmit} initValues={{
|
||||
payment_channel: 'FLYWIRE',
|
||||
payment_method: '',
|
||||
<Form<CreateBillRecordModel> onSubmit={onSubmit} initValues={{
|
||||
merchant_ref: '',
|
||||
payment_amount: '',
|
||||
actual_payment_amount: '',
|
||||
application_number: '',
|
||||
student_email: '',
|
||||
paid_area: '',
|
||||
initiated_paid_date: '',
|
||||
paid_date: '',
|
||||
delivered_date: '',
|
||||
payment_method: '',
|
||||
payment_channel: '',
|
||||
check_student: true,
|
||||
details: []
|
||||
}}>
|
||||
<Row gutter={20}>
|
||||
<Col span={12}>
|
||||
<Form.Select
|
||||
field={t('manual.bill_type')}
|
||||
style={{width: '100%'}}
|
||||
placeholder={t('base.please_select')}>
|
||||
{
|
||||
BillTypes.map((it, idx) => (
|
||||
<Select.Option key={idx} value={it.label}>{it.label}</Select.Option>))
|
||||
}
|
||||
</Form.Select>
|
||||
<Form.Input
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
showClear field="merchant_ref" label="Merchant Ref"
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Input
|
||||
@ -68,67 +181,97 @@ export const AddBillModal: React.FC<BillPaidModalProps> = (props) => {
|
||||
<Row gutter={20}>
|
||||
<Col span={12}>
|
||||
<Form.Input
|
||||
type={'number'}
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
{type: 'email', message: t('base.validate.email')}
|
||||
]}
|
||||
type={'email'}
|
||||
showClear field="student_email" label="Email"
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Input
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
showClear field="amount" label={t('bill.title_amount')}
|
||||
showClear field="paid_area" label={t('bill.create.pay_area')}
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={20}>
|
||||
<Col span={12}>
|
||||
<Form.Select
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
optionList={paymentChannelList}
|
||||
allowCreate filter showClear field="payment_channel" label={t('bill.title_pay_channel')}
|
||||
placeholder={t('base.please_select')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Select
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
optionList={[
|
||||
{value: 'card', label: 'Card(VISA,MasterCard,UnionPay,JCB...)'},
|
||||
{value: 'wechat', label: 'Wechat'},
|
||||
{value: 'alipay', label: 'Alipay'},
|
||||
{value: 'other', label: 'Other'},
|
||||
]}
|
||||
optionList={paymentMethodList}
|
||||
allowCreate filter showClear field="payment_method" label={t('bill.title_pay_method')}
|
||||
placeholder={t('base.please_select')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={20}>
|
||||
<Col span={12}>
|
||||
<Form.Input
|
||||
<Form.DatePicker
|
||||
showClear
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
showClear field="merchant_ref" label="Merchant Ref"
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
type={'date'}
|
||||
field="initiated_paid_date"
|
||||
label={t('bill.title_initiated_paid_at')}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Input
|
||||
<Col span={12}>
|
||||
<Form.DatePicker
|
||||
showClear
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]} type={'number'} showClear
|
||||
field="payment_amount" label={t('bill.title_pay_amount')}
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Form.Input
|
||||
type={'number'}
|
||||
showClear field="actual_payment_amount" label={t('bill.title_actual_payment_amount')}
|
||||
placeholder={t('base.please_enter')} style={{width: '100%'}}/>
|
||||
]}
|
||||
type={'date'}
|
||||
field="paid_date"
|
||||
label={t('bill.title_paid_at')}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={20}>
|
||||
<Col span={24}>
|
||||
<Form.TextArea
|
||||
rules={[{required: true, message: 'required error'}]}
|
||||
showClear field="remark" label={t('bill.title_remark')} placeholder={t('base.please_enter')}
|
||||
style={{width: '100%'}}/>
|
||||
<Col span={12}>
|
||||
<Form.DatePicker
|
||||
showClear
|
||||
rules={[
|
||||
{required: true, message: 'required error'},
|
||||
]}
|
||||
type={'date'}
|
||||
field="delivered_date"
|
||||
label={t('bill.title_delivered_at')}
|
||||
style={{width: '100%'}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
{/*<p style={{marginTop: 10}}>{t('bill.paid_confirm')}</p>*/}
|
||||
<div className={'text-right'} style={{margin: '10px 0 20px'}}>
|
||||
<div>
|
||||
<BillTypeList onChange={(details) => setState({details})}/>
|
||||
{state.errorMessage && <div className="semi-form-field-error-message" style={{marginBottom: 10}}>
|
||||
<IconAlertCircle/>
|
||||
<span style={{marginLeft: 5}}>{state.errorMessage}</span>
|
||||
</div>}
|
||||
<Divider/>
|
||||
</div>
|
||||
<div className={'text-right'} style={{margin: '20px 0'}}>
|
||||
<Space spacing={12}>
|
||||
<Button onClick={props.onCancel} type={'tertiary'}>{t('base.cancel')}</Button>
|
||||
<Button onClick={handleClose} type={'tertiary'}>{t('base.cancel')}</Button>
|
||||
<Button
|
||||
loading={state.loading} htmlType={'submit'} theme={'solid'}
|
||||
type={'primary'}>{t('base.confirm_paid')}</Button>
|
||||
type={'primary'}>{t('bill.create.confirm')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Form>
|
||||
|
@ -163,9 +163,12 @@ export const BillTypeConfirmModal: React.FC<BillTypeConfirmProps> = (props) => {
|
||||
</div>
|
||||
<BillTypeConfirm bill={props.bill} onChange={detail_confirms => setState({detail_confirms})}/>
|
||||
<div className={'text-center'} style={{paddingBottom: 20,marginTop:20}}>
|
||||
<Button
|
||||
onClick={onBillConfirm} loading={state.loading} type={'primary'}
|
||||
theme={'solid'}>{t('base.confirm')}</Button>
|
||||
<Space>
|
||||
<Button onClick={() => props.onClose?.()}>{t('base.cancel')}</Button>
|
||||
<Button
|
||||
onClick={onBillConfirm} loading={state.loading} type={'primary'}
|
||||
theme={'solid'}>{t('base.confirm')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Modal>)
|
||||
}
|
145
src/pages/bill/components/import_bill_modal.tsx
Normal file
145
src/pages/bill/components/import_bill_modal.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
import {Button, Modal, Select, Space, Upload} from "@douyinfe/semi-ui";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useSetState} from "ahooks";
|
||||
import {usePaymentChannels} from "@/hooks/usePaymentChannels.ts";
|
||||
import {IconUpload} from "@douyinfe/semi-icons";
|
||||
import readXlsxFile from "read-excel-file";
|
||||
import dayjs from "dayjs";
|
||||
import {OnChangeProps} from "@douyinfe/semi-ui/lib/es/upload/interface";
|
||||
import Alert from "@/components/alert";
|
||||
import {uploadBillingRecordFile} from "@/service/api/bill.ts";
|
||||
|
||||
type BillPaidModalProps = {
|
||||
onConfirm: () => void
|
||||
onCancel?: () => void
|
||||
}
|
||||
// type ImportRecordValue = number | string | null | Date;
|
||||
|
||||
export const ImportBillModal: React.FC<BillPaidModalProps> = (props) => {
|
||||
const {t, i18n} = useTranslation()
|
||||
const {paymentChannelList} = usePaymentChannels()
|
||||
|
||||
const [state, setState] = useSetState<{
|
||||
loading?: boolean;
|
||||
open?: boolean;
|
||||
errorMessage?:string;
|
||||
currentFile?: File;
|
||||
paymentChannel?: string;
|
||||
}>({})
|
||||
|
||||
const [records, setImportRecords] = useState<string[][]>([])
|
||||
|
||||
const closeModal = () => {
|
||||
setImportRecords([])
|
||||
setState({open: false, loading: false,errorMessage:undefined})
|
||||
}
|
||||
const onSubmit = () => {
|
||||
if(!state.paymentChannel){
|
||||
setState({errorMessage:'bill.import.error_require_payment_channel'})
|
||||
return;
|
||||
}
|
||||
if(records.length == 0 || !state.currentFile){
|
||||
setState({errorMessage:'bill.import.error_require_file'})
|
||||
return;
|
||||
}
|
||||
setState({
|
||||
loading: true,
|
||||
errorMessage:undefined
|
||||
})
|
||||
uploadBillingRecordFile(state.currentFile,state.paymentChannel).then(()=>{
|
||||
closeModal()
|
||||
props.onConfirm()
|
||||
}).catch(e => {
|
||||
setState({errorMessage: e.message,loading: false})
|
||||
})
|
||||
}
|
||||
const onFileChange = ({fileList, currentFile}: OnChangeProps) => {
|
||||
if (fileList.length == 0) {
|
||||
setImportRecords([])
|
||||
return;
|
||||
}
|
||||
if (currentFile.fileInstance) {
|
||||
readXlsxFile(currentFile.fileInstance).then(rows => {
|
||||
const records = rows.map((row) => {
|
||||
return row.map((item) => {
|
||||
if (item && item instanceof Date) return dayjs(item).format('YYYY-MM-DD HH:mm:ss');
|
||||
return String(item)
|
||||
})
|
||||
})
|
||||
setImportRecords(records)
|
||||
setState({currentFile:currentFile.fileInstance})
|
||||
})
|
||||
}
|
||||
}
|
||||
return (<>
|
||||
<Button onClick={() => setState({open: true})} theme={'solid'}>{t('bill.import_bill')}</Button>
|
||||
<Modal
|
||||
title={t('bill.import_bill')}
|
||||
visible={state.open}
|
||||
closeOnEsc={true}
|
||||
onCancel={closeModal}
|
||||
footer={null}
|
||||
width={650}
|
||||
okText={t('base.confirm')}
|
||||
maskClosable={false}
|
||||
>
|
||||
|
||||
<Space vertical align={'start'} spacing={20}>
|
||||
<Space>
|
||||
<div style={{
|
||||
width: i18n.language == 'en-US' ? 140 : 'auto',
|
||||
textAlign: 'right'
|
||||
}}>{t('bill.title_pay_channel')}:
|
||||
</div>
|
||||
<Select
|
||||
style={{width: 160}} placeholder={t('base.please_select')}
|
||||
optionList={paymentChannelList}
|
||||
onChange={v=>setState({paymentChannel: String(v)})}
|
||||
/>
|
||||
</Space>
|
||||
<Space>
|
||||
<div style={{
|
||||
width: i18n.language == 'en-US' ? 140 : 'auto',
|
||||
textAlign: 'right'
|
||||
}}>{t('base.select_excel_file')}:
|
||||
</div>
|
||||
<div className={"upload-wrapper"}>
|
||||
<Upload
|
||||
onChange={e => onFileChange(e)}
|
||||
accept={'.xlsx,.csv'} uploadTrigger="custom" action={''}
|
||||
>
|
||||
<Button style={{width: 160}} icon={<IconUpload/>}
|
||||
theme="light">{t('base.select_upload_file')}</Button>
|
||||
</Upload>
|
||||
</div>
|
||||
</Space>
|
||||
</Space>
|
||||
<div className="import-record-wrapper">
|
||||
<div className="table-list">
|
||||
<table>
|
||||
{records.map((record, index) => {
|
||||
return <tr key={index}>
|
||||
{record.map((item, index) => {
|
||||
return <td key={index}>
|
||||
<div className={'import-record-item'}>{item}</div>
|
||||
</td>
|
||||
})}
|
||||
</tr>
|
||||
})}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<Alert message={state.errorMessage?t(state.errorMessage):undefined} />
|
||||
<div className={'text-right'} style={{margin: '50px 0 20px'}}>
|
||||
<Space spacing={12}>
|
||||
<Button onClick={closeModal} type={'tertiary'}>{t('base.cancel')}</Button>
|
||||
<Button
|
||||
loading={state.loading} htmlType={'submit'} theme={'solid'}
|
||||
type={'primary'} onClick={onSubmit}
|
||||
>{t('base.confirm_import')}</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Modal>
|
||||
</>)
|
||||
}
|
@ -2,19 +2,20 @@ import {Button, ButtonGroup, Modal, Notification, Popconfirm, Space, Toast} from
|
||||
import {useState} from "react";
|
||||
import {useRequest, useSetState} from "ahooks";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {saveAs} from "file-saver";
|
||||
|
||||
import {BillList} from "@/components/bill/list.tsx";
|
||||
import SearchForm from "@/components/bill/search-form.tsx";
|
||||
import BillDetail from "@/components/bill/detail.tsx";
|
||||
import {billList, BillQueryParams, exportBillList, modifyBillStatus} from "@/service/api/bill.ts";
|
||||
import {billList, BillQueryParams, cancelConfirmBill, exportBillList, modifyBillStatus} from "@/service/api/bill.ts";
|
||||
import {BillStatus, BizError} from "@/service/types.ts";
|
||||
import {useDownloadReceiptPDF} from "@/service/generate-pdf.ts";
|
||||
import {BillPaidModal} from "@/pages/bill/components/bill_paid_modal.tsx";
|
||||
import {BillTypeConfirmModal} from "@/pages/bill/components/bill_type_confirm.tsx";
|
||||
import {saveAs} from "file-saver";
|
||||
// import {AddBillModal} from "@/pages/bill/components/add_bill_modal.tsx";
|
||||
import {BillTypeConfirmBatch} from "@/pages/bill/components/bill_type_confirm_batch.tsx";
|
||||
import {AddBillModal} from "@/pages/bill/components/add_bill_modal.tsx";
|
||||
import {ImportBillModal} from "@/pages/bill/components/import_bill_modal.tsx";
|
||||
|
||||
|
||||
const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => {
|
||||
@ -28,12 +29,14 @@ const DownloadButton = ({bill, text}: { bill: BillModel; text: string }) => {
|
||||
// }
|
||||
|
||||
const BillQuery = () => {
|
||||
// const {createPDF,downloadPDF} = useDownloadReceiptPDF()
|
||||
const [state, setState] = useSetState<{
|
||||
updateBill?: BillModel
|
||||
confirmBill?: BillModel
|
||||
updateBill?: BillModel;
|
||||
confirmBill?: BillModel;
|
||||
previewPDFUrl?: string;
|
||||
updateLoading?: boolean
|
||||
exporting?: boolean
|
||||
confirmBillId: number
|
||||
exporting?: boolean;
|
||||
confirmBillId: number;
|
||||
}>({confirmBillId: 0})
|
||||
const [showBill, setShowBill] = useState<BillModel>()
|
||||
const [queryParams, setBillQueryParams] = useState<BillQueryParams>({
|
||||
@ -42,8 +45,8 @@ const BillQuery = () => {
|
||||
});
|
||||
const {data, loading, refresh} = useRequest(() => billList(queryParams), {
|
||||
refreshDeps: [queryParams],
|
||||
onSuccess:()=>{
|
||||
document.documentElement.scrollTo({top:0});
|
||||
onSuccess: () => {
|
||||
document.documentElement.scrollTo({top: 0});
|
||||
},
|
||||
onError: (e) => {
|
||||
Notification.error({title: 'Error', content: e.message})
|
||||
@ -63,6 +66,26 @@ const BillQuery = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onCancelBillConfirm = (id: number) => {
|
||||
cancelConfirmBill(id).then(() => {
|
||||
Toast.success({
|
||||
content: `${t('base.operate_success')}`,
|
||||
duration: 3
|
||||
})
|
||||
refresh()
|
||||
}).catch((e: BizError) => {
|
||||
Toast.error({
|
||||
content: `${t('base.operate_fail')}:${e.message}`,
|
||||
duration: 3
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// const showBillPDF = (bill: BillModel) => {
|
||||
// setState({
|
||||
// previewPDFUrl: createPDF(bill).output('bloburi').toString()
|
||||
// })
|
||||
// }
|
||||
const operation = (bill: BillModel) => {
|
||||
return (<div className={'table-operation-render'}>
|
||||
{bill.status != BillStatus.PAID &&
|
||||
@ -83,11 +106,19 @@ const BillQuery = () => {
|
||||
</>}
|
||||
{
|
||||
bill.status == BillStatus.PAID && <>
|
||||
<DownloadButton bill={bill} text={t('bill.download_receipt')}/>
|
||||
{bill.confirm_status == 'UNCONFIRMED' && <Button
|
||||
onClick={() => setState({confirmBill: bill})}
|
||||
size={'small'} theme={'solid'}
|
||||
type={'primary'}>{t('bill.confirm_bill_type')}</Button>}
|
||||
<DownloadButton bill={bill} text={t('bill.download_receipt')}/>
|
||||
{/*<Button*/}
|
||||
{/* onClick={() => showBillPDF(bill)} size={'small'}>download pdf</Button>*/}
|
||||
{bill.confirm_status == 'UNCONFIRMED' ? <Button
|
||||
onClick={() => setState({confirmBill: bill})}
|
||||
size={'small'} theme={'solid'}
|
||||
type={'primary'}>{t('bill.confirm_bill_type')}</Button>
|
||||
: <Popconfirm
|
||||
title={'Warning'} onConfirm={() => onCancelBillConfirm(bill.id)} position={'topRight'}
|
||||
content={`${t('base.confirm_next_operation')}?`}
|
||||
>
|
||||
<Button size={'small'} theme={'solid'} type={'danger'}>{t('bill.btn_cancel_confirm')}</Button>
|
||||
</Popconfirm>}
|
||||
</>
|
||||
}
|
||||
</div>)
|
||||
@ -111,9 +142,7 @@ const BillQuery = () => {
|
||||
})
|
||||
}
|
||||
|
||||
const onImportExcel = () => {
|
||||
Toast.warning({content: 'Not implemented'})
|
||||
}
|
||||
|
||||
const [selectKeys, setSelectedKeys] = useState<number[]>([])
|
||||
|
||||
|
||||
@ -131,7 +160,8 @@ const BillQuery = () => {
|
||||
<BillTypeConfirmBatch data={data} selectKeys={selectKeys} onConfirm={refresh}/>
|
||||
<AddBillModal onConfirm={refresh}/>
|
||||
<ButtonGroup style={{marginRight: 20}} theme={'solid'}>
|
||||
<Button onClick={onImportExcel}>{t('bill.import_excel')}</Button>
|
||||
{/*<Button onClick={onImportExcel}>{t('bill.import_excel')}</Button>*/}
|
||||
<ImportBillModal onConfirm={refresh}/>
|
||||
<Button loading={state.exporting} onClick={onExportExcel}>{t('bill.export_excel')}</Button>
|
||||
</ButtonGroup>
|
||||
</Space>}
|
||||
@ -162,6 +192,21 @@ const BillQuery = () => {
|
||||
>
|
||||
{showBill && <BillDetail bill={showBill} onCancel={() => setShowBill(undefined)}/>}
|
||||
</Modal>
|
||||
{/* preview and download pdf */}
|
||||
{/*<Modal*/}
|
||||
{/* title={'PDF Preview'} width={800} visible={!!state.previewPDFUrl}*/}
|
||||
{/* maskClosable={false} onCancel={() => setState({previewPDFUrl: undefined})}*/}
|
||||
{/* onOk={()=>{*/}
|
||||
{/* if(state.previewPDFUrl) {*/}
|
||||
{/* saveAs(state.previewPDFUrl, 'pdf.pdf')*/}
|
||||
{/* }*/}
|
||||
{/* }}*/}
|
||||
{/*>*/}
|
||||
{/* {state.previewPDFUrl && <div className={'bill-pdf-previewer'}>*/}
|
||||
{/* <iframe allowFullScreen={false} src={state.previewPDFUrl + '#view=FitH,top&toolbar=0'}*/}
|
||||
{/* className={'bill-pdf-container'}/>*/}
|
||||
{/* </div>}*/}
|
||||
{/*</Modal>*/}
|
||||
<BillPaidModal
|
||||
open={!!state.updateBill}
|
||||
onCancel={() => setState({updateBill: undefined})}
|
||||
|
@ -1,143 +1,157 @@
|
||||
import {get, post, put} from "@/service/request.ts";
|
||||
import {get, post, put, uploadFile} from "@/service/request.ts";
|
||||
import dayjs from "dayjs";
|
||||
import {getAuthToken} from "@/hooks/useAuth.ts";
|
||||
import {stringify} from "qs";
|
||||
|
||||
export type BillQueryResult = {
|
||||
result: BillModel[];
|
||||
total_count: number;
|
||||
query_total_count: number;
|
||||
query_total_amount: number;
|
||||
result: BillModel[];
|
||||
total_count: number;
|
||||
query_total_count: number;
|
||||
query_total_amount: number;
|
||||
}
|
||||
export type BillQueryParams = Partial<BillQueryParam>;
|
||||
const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
function formatBillQueryResult(params: BillQueryParams, result: BillQueryResult) {
|
||||
// 将 BillQueryResult 转成成 RecordList
|
||||
const formatData: RecordList<BillModel> = {
|
||||
list: result.result,
|
||||
pagination: {
|
||||
total: result.query_total_count,
|
||||
pageSize: params.page_size || DEFAULT_PAGE_SIZE,
|
||||
current: params.page_number || 1,
|
||||
recordTotal: Number(result.query_total_amount || 0)
|
||||
}
|
||||
}
|
||||
return formatData
|
||||
// 将 BillQueryResult 转成成 RecordList
|
||||
const formatData: RecordList<BillModel> = {
|
||||
list: result.result,
|
||||
pagination: {
|
||||
total: result.query_total_count,
|
||||
pageSize: params.page_size || DEFAULT_PAGE_SIZE,
|
||||
current: params.page_number || 1,
|
||||
recordTotal: Number(result.query_total_amount || 0)
|
||||
}
|
||||
}
|
||||
return formatData
|
||||
}
|
||||
|
||||
export async function billList(params: BillQueryParams) {
|
||||
const result = await get<BillQueryResult>('/bills', params)
|
||||
return formatBillQueryResult(params, result);
|
||||
const result = await get<BillQueryResult>('/bills', params)
|
||||
return formatBillQueryResult(params, result);
|
||||
}
|
||||
|
||||
export function exportBillList(params: BillQueryParams) {
|
||||
const token = getAuthToken();
|
||||
const headers = new Headers({
|
||||
'Token': token || ''
|
||||
});
|
||||
const token = getAuthToken();
|
||||
const headers = new Headers({
|
||||
'Token': token || ''
|
||||
});
|
||||
|
||||
return fetch(`${AppConfig.API_PREFIX || '/api'}/bills/export?${stringify(params)}`,{
|
||||
headers,
|
||||
method:'GET'
|
||||
}).then(r=>r.blob())
|
||||
// return get<Blob>('/bills/export', params,true)
|
||||
return fetch(`${AppConfig.API_PREFIX || '/api'}/bills/export?${stringify(params)}`, {
|
||||
headers,
|
||||
method: 'GET'
|
||||
}).then(r => r.blob())
|
||||
// return get<Blob>('/bills/export', params,true)
|
||||
}
|
||||
|
||||
//现场支付创建账单接口
|
||||
export function createManualBill(params: ManualCreateBillParam) {
|
||||
return post<BillModel>('/manual_payment', params)
|
||||
return post<BillModel>('/manual_payment', params)
|
||||
}
|
||||
|
||||
export function selectBillTypeList(){
|
||||
return get<BillType[]>('/billing_types')
|
||||
export function selectBillTypeList() {
|
||||
return get<BillType[]>('/billing_types')
|
||||
}
|
||||
|
||||
// 获取账单详情
|
||||
export function getBillDetail(id: number) {
|
||||
return get<BillModel>('/bills/' + id)
|
||||
return get<BillModel>('/bills/' + id)
|
||||
}
|
||||
|
||||
export function addBillRecord(bill: CreateBillRecordModel) {
|
||||
return post('/add_bill', bill);
|
||||
}
|
||||
|
||||
export function uploadBillingRecordFile(file: File, payment_channel: string, check_student = 'true') {
|
||||
return uploadFile('/bill/import', file, {payment_channel, check_student})
|
||||
}
|
||||
|
||||
export function getAsiaPayData(id: number) {
|
||||
return get<AsiaPayModel>(`/bills/${id}/asiapay`)
|
||||
return get<AsiaPayModel>(`/bills/${id}/asiapay`)
|
||||
}
|
||||
|
||||
export function getFlywirePayUrl(id: number, return_cta: string, return_cta_name: string) {
|
||||
return post<{ url: string }>(`/bills/${id}/flywire`, {return_cta, return_cta_name})
|
||||
return post<{ url: string }>(`/bills/${id}/flywire`, {return_cta, return_cta_name})
|
||||
}
|
||||
|
||||
// 作废订单
|
||||
export function modifyBillStatus(id: number,status: BillStatus) {
|
||||
return put(`/bills/${id}/cancel`,{status})
|
||||
export function modifyBillStatus(id: number, status: BillStatus) {
|
||||
return put(`/bills/${id}/cancel`, {status})
|
||||
}
|
||||
|
||||
export function confirmBillType(bills:BillConfirmParams[]) {
|
||||
return post<BillModel>(`/bills/confirm`, {bills})
|
||||
export function confirmBillType(bills: BillConfirmParams[]) {
|
||||
return post<BillModel>(`/bills/confirm`, {bills})
|
||||
}
|
||||
|
||||
export function cancelConfirmBill(bill_id: number) {
|
||||
return post<BillModel>(`/bill/cancel_confirm`, {bill_id})
|
||||
}
|
||||
|
||||
export function confirmBills(bill_ids: number[] | string[]) {
|
||||
return post(`/bills/apply`, {bill_ids})
|
||||
return post(`/bills/apply`, {bill_ids})
|
||||
}
|
||||
|
||||
export function updateBillPaymentSuccess(bill_id: number, merchant_ref: string, payment_channel: string) {
|
||||
return post<BillModel>(`/bills/finish`, {merchant_ref, payment_channel, bill_id})
|
||||
return post<BillModel>(`/bills/finish`, {merchant_ref, payment_channel, bill_id})
|
||||
}
|
||||
|
||||
|
||||
export function createExternalBill(params: ExternalCreateParamsType) {
|
||||
return post<BillModel>('/bill', params)
|
||||
return post<BillModel>('/bill', params)
|
||||
}
|
||||
|
||||
type BillUpdateFormParams = {
|
||||
bill:BillModel;
|
||||
param:BillUpdateParams
|
||||
bill: BillModel;
|
||||
param: BillUpdateParams
|
||||
}
|
||||
export async function finishAsiapay({param}: BillUpdateFormParams){
|
||||
const paramUrl = `?prc=0&src=0&Ord=12345678&Ref=${param.merchant_ref}&PayRef=123456&successcode=0&Amt=10.00&Cur=344&Holder=Test Card&AuthId=123456&AlertCode=&remark=
|
||||
|
||||
export async function finishAsiapay({param}: BillUpdateFormParams) {
|
||||
const paramUrl = `?prc=0&src=0&Ord=12345678&Ref=${param.merchant_ref}&PayRef=123456&successcode=0&Amt=10.00&Cur=344&Holder=Test Card&AuthId=123456&AlertCode=&remark=
|
||||
&eci=07&payerAuth=U&sourceIp=192.1.1.1&ipCountry=HK&payMethod=VISA
|
||||
x&cardIssuingCountry=HK&channelType=SPC&`
|
||||
const ret = await post<string>(`/flywire/feedback?${paramUrl}`, {},true)
|
||||
const ret = await post<string>(`/flywire/feedback?${paramUrl}`, {}, true)
|
||||
|
||||
return ret?.toLowerCase() == 'ok'
|
||||
return ret?.toLowerCase() == 'ok'
|
||||
}
|
||||
|
||||
export async function finishFlywire({bill,param}: BillUpdateFormParams){
|
||||
const eventData = {
|
||||
"event_type": "guaranteed",
|
||||
"event_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'),
|
||||
"event_resource": "payments",
|
||||
"data": {
|
||||
"remark": param.remark,
|
||||
"payment_id": param.merchant_ref,
|
||||
"amount_from": Number(param.actual_payment_amount) * 100,
|
||||
"currency_from": "HKD",
|
||||
"amount_to": Number(param.payment_amount) * 100,
|
||||
"currency_to": "HKD",
|
||||
"status": "guaranteed",
|
||||
"expiration_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'),
|
||||
"external_reference": bill.id,
|
||||
"country": "CN",
|
||||
"payment_method": {
|
||||
"type": param.payment_method
|
||||
},
|
||||
"fields": {
|
||||
"student_email": bill.student_email,
|
||||
"student_last_name": bill.student_last_name || bill.student_english_name,
|
||||
"student_id": bill.student_number,
|
||||
"resident_no": null,
|
||||
"contact_no": "",
|
||||
"payment_type_other": null,
|
||||
"payment_type": null
|
||||
}
|
||||
}
|
||||
}
|
||||
const ret = await post<string>('/flywire/feedback', eventData,true)
|
||||
export async function finishFlywire({bill, param}: BillUpdateFormParams) {
|
||||
const eventData = {
|
||||
"event_type": "guaranteed",
|
||||
"event_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'),
|
||||
"event_resource": "payments",
|
||||
"data": {
|
||||
"remark": param.remark,
|
||||
"payment_id": param.merchant_ref,
|
||||
"amount_from": Number(param.actual_payment_amount) * 100,
|
||||
"currency_from": "HKD",
|
||||
"amount_to": Number(param.payment_amount) * 100,
|
||||
"currency_to": "HKD",
|
||||
"status": "guaranteed",
|
||||
"expiration_date": dayjs().format('YYYY-MM-DDTHH:mm:ss[Z]'),
|
||||
"external_reference": bill.id,
|
||||
"country": "CN",
|
||||
"payment_method": {
|
||||
"type": param.payment_method
|
||||
},
|
||||
"fields": {
|
||||
"student_email": bill.student_email,
|
||||
"student_last_name": bill.student_last_name || bill.student_english_name,
|
||||
"student_id": bill.student_number,
|
||||
"resident_no": null,
|
||||
"contact_no": "",
|
||||
"payment_type_other": null,
|
||||
"payment_type": null
|
||||
}
|
||||
}
|
||||
}
|
||||
const ret = await post<string>('/flywire/feedback', eventData, true)
|
||||
|
||||
return ret?.toLowerCase() == 'ok'
|
||||
return ret?.toLowerCase() == 'ok'
|
||||
}
|
||||
|
||||
export function finishBill(params: BillUpdateFormParams) {
|
||||
if(params.param.payment_channel === 'flywire'){
|
||||
return finishAsiapay(params)
|
||||
}
|
||||
return finishFlywire(params)
|
||||
if (params.param.payment_channel === 'flywire') {
|
||||
return finishAsiapay(params)
|
||||
}
|
||||
return finishFlywire(params)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import {get,post} from "@/service/request.ts";
|
||||
import {get, post, remove} from "@/service/request.ts";
|
||||
|
||||
export function getUserInfo() {
|
||||
return get<UserProfile>('/userinfo')
|
||||
@ -18,4 +18,18 @@ export function getPermissionList(){
|
||||
}
|
||||
export function savePermissionList(roles:PermissionUserList[]){
|
||||
return post('/roles',{roles})
|
||||
}
|
||||
|
||||
export function getUserPermissionList(){
|
||||
return get<UserPermission[]>('/permissions')
|
||||
}
|
||||
|
||||
export function createUserPermission(){
|
||||
return get<UserPermission[]>('/permissions')
|
||||
}
|
||||
export function updateUserPermission(){
|
||||
return get<UserPermission[]>('/permissions')
|
||||
}
|
||||
export function removeUserPermission(id:number){
|
||||
return remove(`/permissions/${id}`)
|
||||
}
|
@ -14,7 +14,7 @@ function drawItem(doc: JsPDF, item: {
|
||||
if (item.content && item.content.length > 0) doc.text(item.content, align == 'left' ? 65 : 230, y, {maxWidth: 150});
|
||||
}
|
||||
|
||||
export function GeneratePdf(bill: BillModel) {
|
||||
export function GeneratePdf(bill: BillModel,save = true) {
|
||||
const doc = new JsPDF({
|
||||
orientation: 'landscape',
|
||||
format: 'a4'
|
||||
@ -61,7 +61,7 @@ export function GeneratePdf(bill: BillModel) {
|
||||
...(bill.details.map(it=>{
|
||||
return [
|
||||
`#${it.id}`,
|
||||
bill.paid_at?dayjs(bill.paid_at).format('YYYY-MM-DD'):'',
|
||||
bill.paid_at?dayjs(bill.initiated_paid_at).format('YYYY-MM-DD'):'',
|
||||
it.bill_type,
|
||||
`${bill.payment_channel}` + (bill.payment_method && bill.payment_channel != bill.payment_method ? `(${bill.payment_method})` : ''),
|
||||
`${it.amount}`
|
||||
@ -81,7 +81,8 @@ export function GeneratePdf(bill: BillModel) {
|
||||
doc.setFont('Helvetica', 'italic');
|
||||
drawItem(doc, {title: 'Please retain this acknowledgement receipt for your record.'}, 185)
|
||||
|
||||
doc.save(`Receipt-${bill.id}.pdf`);
|
||||
if(save) doc.save(`Receipt-${bill.id}.pdf`);
|
||||
return doc
|
||||
}
|
||||
|
||||
|
||||
@ -99,6 +100,9 @@ export function useDownloadReceiptPDF() {
|
||||
|
||||
return {
|
||||
loading,
|
||||
downloadPDF
|
||||
downloadPDF,
|
||||
createPDF(bill: BillModel){
|
||||
return GeneratePdf(bill,false)
|
||||
}
|
||||
}
|
||||
}
|
@ -7,74 +7,131 @@ const JSON_FORMAT: string = 'application/json';
|
||||
const REQUEST_TIMEOUT = 300000; // 超时时长5min
|
||||
|
||||
const Axios = axios.create({
|
||||
baseURL: AppConfig.API_PREFIX || '/api',
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
headers: {'Content-Type': JSON_FORMAT}
|
||||
baseURL: AppConfig.API_PREFIX || '/api',
|
||||
timeout: REQUEST_TIMEOUT,
|
||||
headers: {'Content-Type': JSON_FORMAT}
|
||||
})
|
||||
|
||||
|
||||
// 请求前拦截
|
||||
Axios.interceptors.request.use(config => {
|
||||
const token = getAuthToken();
|
||||
if (token) {
|
||||
config.headers['Token'] = `${token}`;
|
||||
}
|
||||
if (config.data && config.data instanceof FormData) {
|
||||
config.headers['Content-Type'] = 'multipart/form-data';
|
||||
}
|
||||
return config
|
||||
const token = getAuthToken();
|
||||
if (token) {
|
||||
config.headers['Token'] = `${token}`;
|
||||
}
|
||||
if (config.data && config.data instanceof FormData) {
|
||||
config.headers['Content-Type'] = 'multipart/form-data';
|
||||
}
|
||||
return config
|
||||
}, err => {
|
||||
return Promise.reject(err)
|
||||
return Promise.reject(err)
|
||||
})
|
||||
|
||||
export function request<T>(url: string, method: RequestMethod, data: AllType = null, getOriginResult = false) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
Axios.request<APIResponse<T>>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
}).then(res => {
|
||||
if (res.status != 200) {
|
||||
reject(new BizError("Service Internal Exception,Please Try Later!", res.status))
|
||||
return;
|
||||
}
|
||||
if (getOriginResult) {
|
||||
resolve(res.data as unknown as T)
|
||||
return;
|
||||
}
|
||||
// const
|
||||
const {code, message, data,request_id} = res.data
|
||||
if (code == 0) {
|
||||
resolve(data as unknown as T)
|
||||
} else {
|
||||
reject(new BizError(message, code,request_id, data as unknown as AllType))
|
||||
}
|
||||
}).catch(e => {
|
||||
reject(new BizError(e.message, 500))
|
||||
})
|
||||
})
|
||||
export function request<T>(url: string, method: RequestMethod, data: AllType = null, getOriginResult = false, headers: RequestHeaders = {}) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
Axios.request<APIResponse<T>>({
|
||||
url,
|
||||
method,
|
||||
data,
|
||||
headers
|
||||
}).then(res => {
|
||||
if (res.status != 200) {
|
||||
reject(new BizError("Service Internal Exception,Please Try Later!", res.status))
|
||||
return;
|
||||
}
|
||||
if (getOriginResult) {
|
||||
resolve(res.data as unknown as T)
|
||||
return;
|
||||
}
|
||||
// const
|
||||
const {code, message, data, request_id} = res.data
|
||||
if (code == 0) {
|
||||
resolve(data as unknown as T)
|
||||
} else {
|
||||
reject(new BizError(message, code, request_id, data as unknown as AllType))
|
||||
}
|
||||
}).catch(e => {
|
||||
reject(new BizError(e.message, 500))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function post<T>(url: string, data: AllType = {}, returnOrigin = false) {
|
||||
return request<T>(url, 'post', data, returnOrigin)
|
||||
return request<T>(url, 'post', data, returnOrigin)
|
||||
}
|
||||
|
||||
export function get<T>(url: string, data: AllType = null, returnOrigin = false) {
|
||||
if (data) {
|
||||
url += (url.indexOf('?') === -1 ? '?' : '&') + stringify(data)
|
||||
}
|
||||
return request<T>(url, 'get', data, returnOrigin)
|
||||
if (data) {
|
||||
url += (url.indexOf('?') === -1 ? '?' : '&') + stringify(data)
|
||||
}
|
||||
return request<T>(url, 'get', data, returnOrigin)
|
||||
}
|
||||
|
||||
export function put<T>(url: string, data: AllType = {}) {
|
||||
return request<T>(url, 'put', data)
|
||||
return request<T>(url, 'put', data)
|
||||
}
|
||||
|
||||
export function remove<T>(url: string, data: AllType = {}) {
|
||||
return request<T>(url, 'delete', data)
|
||||
}
|
||||
|
||||
type UploadFileModel = {
|
||||
field_name?: string;
|
||||
origin_file: File
|
||||
}
|
||||
|
||||
export function uploadFile<T>(url: string, file: UploadFileModel | File, data: AllType = null, returnOrigin = false) {
|
||||
|
||||
const formData = new FormData();
|
||||
if (file && file instanceof File) {
|
||||
formData.append('file', file);
|
||||
} else {
|
||||
formData.append(file.field_name || 'file', file.origin_file);
|
||||
}
|
||||
if (data) {
|
||||
Object.keys(data).forEach(key => {
|
||||
formData.append(key, data[key]);
|
||||
})
|
||||
}
|
||||
|
||||
return request<T>(url, 'post', data, returnOrigin, {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
})
|
||||
// return new Promise<T>((resolve, reject) => {
|
||||
// Axios.request<APIResponse<T>>({
|
||||
// url,
|
||||
// method,
|
||||
// data,
|
||||
// headers:{
|
||||
// 'Content-Type': 'multipart/form-data'
|
||||
// }
|
||||
// }).then(res => {
|
||||
// if (res.status != 200) {
|
||||
// reject(new BizError("Service Internal Exception,Please Try Later!", res.status))
|
||||
// return;
|
||||
// }
|
||||
// if (getOriginResult) {
|
||||
// resolve(res.data as unknown as T)
|
||||
// return;
|
||||
// }
|
||||
// // const
|
||||
// const {code, message, data,request_id} = res.data
|
||||
// if (code == 0) {
|
||||
// resolve(data as unknown as T)
|
||||
// } else {
|
||||
// reject(new BizError(message, code,request_id, data as unknown as AllType))
|
||||
// }
|
||||
// }).catch(e => {
|
||||
// reject(new BizError(e.message, 500))
|
||||
// })
|
||||
// })
|
||||
}
|
||||
|
||||
export function getFileBlob(url: string) {
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
fetch(url).then(res => res.blob()).then(res => {
|
||||
resolve(res)
|
||||
}).catch(reject);
|
||||
});
|
||||
return new Promise<Blob>((resolve, reject) => {
|
||||
fetch(url).then(res => res.blob()).then(res => {
|
||||
resolve(res)
|
||||
}).catch(reject);
|
||||
});
|
||||
}
|
||||
|
3
src/types/api.d.ts
vendored
3
src/types/api.d.ts
vendored
@ -1,5 +1,8 @@
|
||||
// 请求方式
|
||||
declare type RequestMethod = 'get' | 'post' | 'put' | 'delete'
|
||||
declare type RequestHeaders = {
|
||||
[key:string]:string
|
||||
}
|
||||
|
||||
// 接口返回数据类型
|
||||
declare interface APIResponse<T> {
|
||||
|
67
src/types/auth.d.ts
vendored
67
src/types/auth.d.ts
vendored
@ -1,40 +1,53 @@
|
||||
declare type UserRole = 'root' | 'ro' | 'fo' | 'staff'
|
||||
|
||||
declare type UserProfile = {
|
||||
id: string | number;
|
||||
token: string;
|
||||
email: string;
|
||||
username: string;
|
||||
department: string;
|
||||
exp: number;
|
||||
expiration_time: string;
|
||||
iat: number;
|
||||
iss: string;
|
||||
nbf: number;
|
||||
type: string;
|
||||
roles: UserRole[];
|
||||
role: UserRole;
|
||||
origin_role?: UserRole;
|
||||
id: string | number;
|
||||
token: string;
|
||||
email: string;
|
||||
username: string;
|
||||
department: string;
|
||||
exp: number;
|
||||
expiration_time: string;
|
||||
iat: number;
|
||||
iss: string;
|
||||
nbf: number;
|
||||
type: string;
|
||||
roles: UserRole[];
|
||||
role: UserRole;
|
||||
origin_role?: UserRole;
|
||||
}
|
||||
|
||||
declare interface AuthProps {
|
||||
isLoggedIn: boolean;
|
||||
isInitialized?: boolean;
|
||||
user?: UserProfile | null;
|
||||
token?: string | null;
|
||||
isLoggedIn: boolean;
|
||||
isInitialized?: boolean;
|
||||
user?: UserProfile | null;
|
||||
token?: string | null;
|
||||
}
|
||||
|
||||
declare type AuthContextType = {
|
||||
isLoggedIn: boolean;
|
||||
isInitialized?: boolean;
|
||||
user?: UserProfile | null | undefined;
|
||||
logout: () => Promise<void>;
|
||||
mockLogin: () => Promise<void>;
|
||||
login: (code:string,state:string) => Promise<void>;
|
||||
updateUser: (user:Partial<UserProfile>) => Promise<void>;
|
||||
isLoggedIn: boolean;
|
||||
isInitialized?: boolean;
|
||||
user?: UserProfile | null | undefined;
|
||||
logout: () => Promise<void>;
|
||||
mockLogin: () => Promise<void>;
|
||||
login: (code: string, state: string) => Promise<void>;
|
||||
updateUser: (user: Partial<UserProfile>) => Promise<void>;
|
||||
};
|
||||
|
||||
declare type PermissionUserList = {
|
||||
role_name:string;
|
||||
username_list: string[];
|
||||
role_name: string;
|
||||
username_list: string[];
|
||||
}
|
||||
|
||||
declare type UserPermission = {
|
||||
id: number;
|
||||
username: string;
|
||||
role: string;
|
||||
manual_payment: boolean;
|
||||
bill_page: boolean;
|
||||
apply_page: boolean;
|
||||
complete_button: boolean;
|
||||
confirm_button: boolean;
|
||||
apply_button: boolean;
|
||||
permission_edit: boolean;
|
||||
}
|
241
src/types/bill.d.ts
vendored
241
src/types/bill.d.ts
vendored
@ -1,152 +1,167 @@
|
||||
|
||||
declare type BaseDate = number | Date | string | null;
|
||||
declare type BillStatus = 'PAID' | 'CANCELLED' | 'PENDING'
|
||||
declare type ConfirmStatus = 'UNCONFIRMED' | 'CONFIRMED'
|
||||
declare type SortOrderType = 'ASC'|'DESC' | string;
|
||||
declare type SortOrderType = 'ASC' | 'DESC' | string;
|
||||
|
||||
// 现场支付账单数据
|
||||
declare type ManualCreateBillParam = {
|
||||
bill_type:string;
|
||||
amount:decimal;
|
||||
student_number:string;
|
||||
bill_type: string;
|
||||
amount: decimal;
|
||||
student_number: string;
|
||||
}
|
||||
declare type BillDetail = {
|
||||
id: number;
|
||||
bill_type: string;
|
||||
amount: decimal;
|
||||
confirmed?: ConfirmedBillDetail[];
|
||||
id: number;
|
||||
bill_type: string;
|
||||
amount: decimal;
|
||||
confirmed?: ConfirmedBillDetail[];
|
||||
}
|
||||
|
||||
declare type ConfirmedBillDetail = {
|
||||
id?: number;
|
||||
bill_detail_id: number;
|
||||
bill_type: string;
|
||||
amount: decimal;
|
||||
id?: number;
|
||||
bill_detail_id?: number;
|
||||
bill_type: string;
|
||||
amount: decimal;
|
||||
}
|
||||
/**
|
||||
* 账单查询参数
|
||||
*/
|
||||
declare type BillQueryParam = {
|
||||
page_size:int;
|
||||
page_number:int;
|
||||
status:string;
|
||||
apply_status:string;
|
||||
id:string|number;
|
||||
merchant_ref:string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
bill_type:string;
|
||||
confirm_bill_type:string;
|
||||
student_number:string;
|
||||
application_number:string;
|
||||
payment_channel:string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
start_date:string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
end_date:string;
|
||||
start_initiated:string;
|
||||
end_initiated:string;
|
||||
start_delivered:string;
|
||||
end_delivered:string;
|
||||
merchant_ref:string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
department:string;
|
||||
confirm_status: ConfirmStatus;
|
||||
sort_field:string;
|
||||
sort_order:SortOrderType;
|
||||
page_size: int;
|
||||
page_number: int;
|
||||
status: string;
|
||||
apply_status: string;
|
||||
id: string | number;
|
||||
merchant_ref: string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
bill_type: string;
|
||||
confirm_bill_type: string;
|
||||
student_number: string;
|
||||
application_number: string;
|
||||
payment_channel: string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
start_date: string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
end_date: string;
|
||||
start_initiated: string;
|
||||
end_initiated: string;
|
||||
start_delivered: string;
|
||||
end_delivered: string;
|
||||
merchant_ref: string;
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
department: string;
|
||||
confirm_status: ConfirmStatus;
|
||||
sort_field: string;
|
||||
sort_order: SortOrderType;
|
||||
}
|
||||
declare type BillType = {
|
||||
type: string;
|
||||
description: string;
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
/**
|
||||
* 账单模型
|
||||
*/
|
||||
declare type BillModel = {
|
||||
id: number;
|
||||
student_number: string;
|
||||
student_number_confirm?: string;
|
||||
application_number: null | string;
|
||||
confirm_application_number?: null | string;
|
||||
student_email: string;
|
||||
student_tc_name?: string;
|
||||
student_sc_name?: string;
|
||||
student_chinese_name: string;
|
||||
student_english_name: string;
|
||||
programme_chinese_name: string;
|
||||
programme_english_name: string;
|
||||
attendance_mode: string;
|
||||
department_chinese_name: string;
|
||||
department_english_name: string;
|
||||
intake_year: string;
|
||||
intake_semester: string;
|
||||
amount: number|string;
|
||||
service_charge?: number;
|
||||
payment_amount?: number;
|
||||
actual_payment_amount?: null | number;
|
||||
payment_method?: null | string;
|
||||
currency?: string;
|
||||
payment_channel?: null | string ;
|
||||
expiration_time?: BaseDate;
|
||||
status: string;
|
||||
apply_status: string;
|
||||
paid_area?: null | string | number;
|
||||
paid_at?:BaseDate;
|
||||
merchant_ref?: string;
|
||||
payment_id?: null | string | number;
|
||||
create_at: BaseDate;
|
||||
update_at: BaseDate;
|
||||
student_first_name: string;
|
||||
student_last_name: string;
|
||||
remark: string;
|
||||
confirm_status: ConfirmStatus;
|
||||
details: BillDetail[]
|
||||
detail_confirms: ConfirmedBillDetail[] | null
|
||||
id: number;
|
||||
student_number: string;
|
||||
student_number_confirm?: string;
|
||||
application_number: null | string;
|
||||
confirm_application_number?: null | string;
|
||||
student_email: string;
|
||||
student_tc_name?: string;
|
||||
student_sc_name?: string;
|
||||
student_chinese_name: string;
|
||||
student_english_name: string;
|
||||
programme_chinese_name: string;
|
||||
programme_english_name: string;
|
||||
attendance_mode: string;
|
||||
department_chinese_name: string;
|
||||
department_english_name: string;
|
||||
intake_year: string;
|
||||
intake_semester: string;
|
||||
amount: number | string;
|
||||
service_charge?: number;
|
||||
payment_amount?: number;
|
||||
actual_payment_amount?: null | number;
|
||||
payment_method?: null | string;
|
||||
currency?: string;
|
||||
payment_channel?: null | string;
|
||||
expiration_time?: BaseDate;
|
||||
status: string;
|
||||
apply_status: string;
|
||||
paid_area?: null | string | number;
|
||||
paid_at?: BaseDate;
|
||||
initiated_paid_at?: BaseDate;
|
||||
delivered_at?: BaseDate;
|
||||
merchant_ref?: string;
|
||||
payment_id?: null | string | number;
|
||||
create_at: BaseDate;
|
||||
update_at: BaseDate;
|
||||
student_first_name: string;
|
||||
student_last_name: string;
|
||||
remark: string;
|
||||
confirm_status: ConfirmStatus;
|
||||
details: BillDetail[]
|
||||
detail_confirms: ConfirmedBillDetail[] | null
|
||||
}
|
||||
|
||||
|
||||
declare type AsiaPayModel = {
|
||||
merchantId: number;
|
||||
amount: string;
|
||||
payment_amount: string;
|
||||
service_charge: string;
|
||||
merchant_ref: string;
|
||||
currCode: number;
|
||||
mpsMode: string;
|
||||
payType: string;
|
||||
payMethod: string;
|
||||
pay_url: string;
|
||||
details: BillDetail[]
|
||||
merchantId: number;
|
||||
amount: string;
|
||||
payment_amount: string;
|
||||
service_charge: string;
|
||||
merchant_ref: string;
|
||||
currCode: number;
|
||||
mpsMode: string;
|
||||
payType: string;
|
||||
payMethod: string;
|
||||
pay_url: string;
|
||||
details: BillDetail[]
|
||||
}
|
||||
|
||||
type ExternalCreateParamsType = {
|
||||
details: BillDetail[];
|
||||
[key: string]: string | null;
|
||||
details: BillDetail[];
|
||||
[key: string]: string | null;
|
||||
}
|
||||
|
||||
type BillUpdateParams = {
|
||||
payment_channel: string;
|
||||
payment_method: string;
|
||||
actual_payment_amount?: number | string;
|
||||
remark?: string;
|
||||
merchant_ref?: string;
|
||||
payment_amount?: number | string;
|
||||
payment_channel: string;
|
||||
payment_method: string;
|
||||
actual_payment_amount?: number | string;
|
||||
remark?: string;
|
||||
merchant_ref?: string;
|
||||
payment_amount?: number | string;
|
||||
}
|
||||
|
||||
type CreateBillRecordModel = {
|
||||
merchant_ref: string;
|
||||
application_number: string;
|
||||
student_email: string;
|
||||
paid_area: string;
|
||||
initiated_paid_date: string;
|
||||
paid_date: string;
|
||||
delivered_date: string;
|
||||
payment_method: string;
|
||||
payment_channel: string;
|
||||
check_student: 'true' | 'false' | string|boolean;
|
||||
details: ConfirmedBillDetail[]
|
||||
}
|
||||
|
||||
type BillTypeConfirm = {
|
||||
bill_type: string;
|
||||
amount: number;
|
||||
bill_type: string;
|
||||
amount: number;
|
||||
}
|
||||
type BillConfirmParams = {
|
||||
id:number;
|
||||
confirm_application_number:string;
|
||||
confirm_student_number:string;
|
||||
detail_confirms:ConfirmedBillDetail[]
|
||||
id: number;
|
||||
confirm_application_number: string;
|
||||
confirm_student_number: string;
|
||||
detail_confirms: ConfirmedBillDetail[]
|
||||
}
|
7
src/vite-env.d.ts
vendored
7
src/vite-env.d.ts
vendored
@ -27,4 +27,9 @@ declare const AppMode: 'test' | 'production' | 'development';
|
||||
declare type BasicComponentProps = {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
declare type BasicComponent<T> = React.FC<BasicComponentProps & T>
|
||||
declare type BasicComponent<T> = React.FC<BasicComponentProps & T>
|
||||
|
||||
declare type OptionValue = {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
@ -36,7 +36,7 @@ export default defineConfig(({mode}) => {
|
||||
target: 'https://test-payment-be.hkchc.team', //
|
||||
// target: 'http://127.0.0.1:50000', //
|
||||
changeOrigin: true,
|
||||
//rewrite: (path) => path.replace(/^\/api/, '')
|
||||
rewrite: (path) => path.replace(/^\/api/, '/api/')
|
||||
},
|
||||
'/staff-api': {
|
||||
target: 'https://test-api.hkchc.team', //
|
||||
|
119
yarn.lock
119
yarn.lock
@ -1057,6 +1057,11 @@
|
||||
"@types/babel__core" "^7.20.5"
|
||||
react-refresh "^0.14.0"
|
||||
|
||||
"@xmldom/xmldom@^0.8.2":
|
||||
version "0.8.10"
|
||||
resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99"
|
||||
integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==
|
||||
|
||||
acorn-jsx@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
|
||||
@ -1169,6 +1174,11 @@ bezier-easing@^2.1.0:
|
||||
resolved "https://registry.npmmirror.com/bezier-easing/-/bezier-easing-2.1.0.tgz#c04dfe8b926d6ecaca1813d69ff179b7c2025d86"
|
||||
integrity sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==
|
||||
|
||||
bluebird@~3.7.2:
|
||||
version "3.7.2"
|
||||
resolved "https://registry.npmmirror.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
@ -1341,6 +1351,11 @@ core-js@^3.6.0, core-js@^3.8.3:
|
||||
resolved "https://registry.npmmirror.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9"
|
||||
integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmmirror.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
cosmiconfig@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.npmmirror.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
|
||||
@ -1435,6 +1450,13 @@ dompurify@^2.2.0:
|
||||
resolved "https://registry.npmmirror.com/dompurify/-/dompurify-2.5.3.tgz#bc901a9c40a7d97176c1d0ab9a24939db54270a2"
|
||||
integrity sha512-09uyBM2URzOfXMUAqGRnm9R9IUeSkzO9PktXc2eVQIsBmmJUqRmfL1xW2QPBxVJEtlEVs5d8ndrsIQsyAqs81g==
|
||||
|
||||
duplexer2@~0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.npmmirror.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
|
||||
integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==
|
||||
dependencies:
|
||||
readable-stream "^2.0.2"
|
||||
|
||||
electron-to-chromium@^1.4.668:
|
||||
version "1.4.774"
|
||||
resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.774.tgz#1017d1758aaeeefe5423aa9d67b4b1e5d1d0a856"
|
||||
@ -1653,6 +1675,11 @@ fflate@^0.4.8:
|
||||
resolved "https://registry.npmmirror.com/fflate/-/fflate-0.4.8.tgz#f90b82aefbd8ac174213abb338bd7ef848f0f5ae"
|
||||
integrity sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==
|
||||
|
||||
fflate@^0.7.3:
|
||||
version "0.7.4"
|
||||
resolved "https://registry.npmmirror.com/fflate/-/fflate-0.7.4.tgz#61587e5d958fdabb5a9368a302c25363f4f69f50"
|
||||
integrity sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==
|
||||
|
||||
file-entry-cache@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
|
||||
@ -1713,6 +1740,15 @@ form-data@^4.0.0:
|
||||
combined-stream "^1.0.8"
|
||||
mime-types "^2.1.12"
|
||||
|
||||
fs-extra@^11.2.0:
|
||||
version "11.2.0"
|
||||
resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b"
|
||||
integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@ -1801,7 +1837,7 @@ gopd@^1.0.1:
|
||||
dependencies:
|
||||
get-intrinsic "^1.1.3"
|
||||
|
||||
graceful-fs@^4.1.2:
|
||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.2:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
@ -1912,7 +1948,7 @@ inflight@^1.0.4:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
inherits@2, inherits@~2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
@ -1961,6 +1997,11 @@ is-what@^3.14.1:
|
||||
resolved "https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1"
|
||||
integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@ -2013,6 +2054,15 @@ json5@^2.2.3:
|
||||
resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||
dependencies:
|
||||
universalify "^2.0.0"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jspdf-autotable@^3.8.2:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.npmmirror.com/jspdf-autotable/-/jspdf-autotable-3.8.2.tgz#44d4c4e18494ccd6e31765e4d2adadda25b9713e"
|
||||
@ -2181,6 +2231,11 @@ needle@^3.1.0:
|
||||
iconv-lite "^0.6.3"
|
||||
sax "^1.2.4"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
|
||||
|
||||
node-releases@^2.0.14:
|
||||
version "2.0.14"
|
||||
resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
|
||||
@ -2310,6 +2365,11 @@ prelude-ls@^1.2.1:
|
||||
resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
|
||||
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
|
||||
|
||||
prop-types@15.x, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
@ -2440,6 +2500,28 @@ react@^18.2.0:
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
|
||||
read-excel-file@^5.8.5:
|
||||
version "5.8.5"
|
||||
resolved "https://registry.npmmirror.com/read-excel-file/-/read-excel-file-5.8.5.tgz#42e3b0bc967bc4e90c64e51204cc7d8ab157cc62"
|
||||
integrity sha512-KDDcSsI3VzXTNUBs8q7RwTYrGRE8RZgNwGUivYq13bQtMp1KJmocyBs/EiPTJaFk4I8Ri9iDF+ht2A4GUrudMg==
|
||||
dependencies:
|
||||
"@xmldom/xmldom" "^0.8.2"
|
||||
fflate "^0.7.3"
|
||||
unzipper "^0.12.2"
|
||||
|
||||
readable-stream@^2.0.2:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b"
|
||||
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
regenerator-runtime@^0.13.7:
|
||||
version "0.13.11"
|
||||
resolved "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
|
||||
@ -2518,6 +2600,11 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
"safer-buffer@>= 2.1.2 < 3.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
@ -2621,6 +2708,13 @@ stackblur-canvas@^2.0.0:
|
||||
resolved "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz#af931277d0b5096df55e1f91c530043e066989b6"
|
||||
integrity sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
@ -2718,6 +2812,22 @@ undici-types@~5.26.4:
|
||||
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||
|
||||
unzipper@^0.12.2:
|
||||
version "0.12.3"
|
||||
resolved "https://registry.npmmirror.com/unzipper/-/unzipper-0.12.3.tgz#31958f5eed7368ed8f57deae547e5a673e984f87"
|
||||
integrity sha512-PZ8hTS+AqcGxsaQntl3IRBw65QrBI6lxzqDEL7IAo/XCEqRTKGfOX56Vea5TH9SZczRVxuzk1re04z/YjuYCJA==
|
||||
dependencies:
|
||||
bluebird "~3.7.2"
|
||||
duplexer2 "~0.1.4"
|
||||
fs-extra "^11.2.0"
|
||||
graceful-fs "^4.2.2"
|
||||
node-int64 "^0.4.0"
|
||||
|
||||
update-browserslist-db@^1.0.13:
|
||||
version "1.0.16"
|
||||
resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356"
|
||||
@ -2733,6 +2843,11 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
utility-types@^3.10.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c"
|
||||
|
Loading…
x
Reference in New Issue
Block a user