完善后台页面
This commit is contained in:
parent
7d022bc4fa
commit
44095afbc7
@ -18,7 +18,6 @@
|
||||
"@emotion/react": "^11.11.4",
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@mui/system": "^5.15.15",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"ahooks": "^3.7.11",
|
||||
"clsx": "^2.1.1",
|
||||
"i18next": "^23.11.4",
|
||||
@ -26,11 +25,9 @@
|
||||
"jspdf-autotable": "^3.8.2",
|
||||
"less": "^4.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-countup": "^6.5.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^14.1.1",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"zustand": "^4.5.2"
|
||||
"react-router-dom": "^6.23.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.17.1",
|
||||
@ -43,7 +40,6 @@
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.6",
|
||||
"file-saver": "^2.0.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.2.0"
|
||||
}
|
||||
|
@ -17,27 +17,62 @@
|
||||
outline: none;
|
||||
font-family: -apple-system, "PingFang SC", 'Microsoft YaHei', sans-serif;
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.semi-layout{
|
||||
|
||||
.semi-layout {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.space-between{
|
||||
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.align-center{
|
||||
|
||||
.align-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.page-content-container{
|
||||
|
||||
.page-content-container {
|
||||
max-width: 90%;
|
||||
width: 1400px;
|
||||
margin: auto;
|
||||
}
|
||||
.auth-container{
|
||||
|
||||
.auth-container {
|
||||
background: url(./images/auth/bg.png);
|
||||
}
|
||||
.semi-dropdown-item-active{
|
||||
|
||||
.semi-dropdown-item-active {
|
||||
background-color: var(--semi-color-default-active);
|
||||
}
|
||||
|
||||
.dashboard-menu-container {
|
||||
padding: 15px;
|
||||
.nav-item {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
border-radius: 10px;
|
||||
color: #999999;
|
||||
.svg-icon{
|
||||
font-size: 22px;
|
||||
}
|
||||
.menu-text{
|
||||
margin-left: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #fff;
|
||||
.svg-icon{
|
||||
color: #FFC65F;
|
||||
}
|
||||
.menu-text{
|
||||
color: #2D3748;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
import React from "react";
|
||||
|
||||
export const IconQRCode = ({style}: { style?: React.CSSProperties }) => (
|
||||
<svg className={'svg-icon'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
|
||||
width="1em" height="1em" viewBox="0 0 15 15">
|
||||
<defs>
|
||||
<clipPath id="master_svg0_1_3654/1_0549">
|
||||
<rect x="0" y="0" width="15" height="15" rx="6"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#master_svg0_1_3654/1_0549)">
|
||||
<g>
|
||||
<path
|
||||
d="M1.40625,1.875C1.40625,1.616118,1.616118,1.40625,1.875,1.40625L5.625,1.40625C5.88388,1.40625,6.09375,1.616118,6.09375,1.875L6.09375,5.625C6.09375,5.88388,5.88388,6.09375,5.625,6.09375L1.875,6.09375C1.616118,6.09375,1.40625,5.88388,1.40625,5.625L1.40625,1.875ZM2.8125,4.6875L4.6875,4.6875L4.6875,2.8125L2.8125,2.8125L2.8125,4.6875Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M1.40631103515625,9.37506103515625L1.40631103515625,13.125001035156249C1.40631103515625,13.38388103515625,1.61618103515625,13.593751035156249,1.87506203515625,13.593751035156249L5.62500103515625,13.593751035156249C5.88388103515625,13.593751035156249,6.09375103515625,13.38388103515625,6.09375103515625,13.125001035156249L6.09375103515625,9.37506103515625C6.09375103515625,9.11617903515625,5.88388103515625,8.90631103515625,5.62500103515625,8.90631103515625L1.87506203515625,8.90631103515625C1.61618103515625,8.90631103515625,1.40631103515625,9.11617903515625,1.40631103515625,9.37506103515625ZM2.81256103515625,12.18750103515625L2.81256103515625,10.31256103515625L4.6875010351562505,10.31256103515625L4.6875010351562505,12.18750103515625L2.81256103515625,12.18750103515625Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M9.375,1.40625C9.116119,1.40625,8.90625,1.616118,8.90625,1.875L8.90625,5.625C8.90625,5.88388,9.116119,6.09375,9.375,6.09375L13.125,6.09375C13.383880000000001,6.09375,13.59375,5.88388,13.59375,5.625L13.59375,1.875C13.59375,1.616118,13.383880000000001,1.40625,13.125,1.40625L9.375,1.40625ZM10.3125,2.8125L12.1875,2.8125L12.1875,4.6875L10.3125,4.6875L10.3125,2.8125Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M1.40625,7.03131103515625L13.59375,7.03131103515625L13.59375,7.96881103515625L1.40625,7.96881103515625L1.40625,7.03131103515625Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M7.03125,1.40625L7.03125,6.09375L7.96875,6.09375L7.96875,1.40625L7.03125,1.40625Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M7.03125,13.593782109374999L7.03125,8.909912109375L7.96875,8.909912109375L7.96875,13.593782109374999L7.03125,13.593782109374999Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M8.902130126953125,11.718782109375L11.718760126953125,11.718782109375L11.718760126953125,10.312532109375L10.308390126953125,10.312532109375L10.308410126953126,8.909927308575L8.902158737153124,8.909912109375L8.902130126953125,11.718782109375Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M8.90625,12.65622L12.6537,12.65622L12.653690000000001,8.9062643051L13.591190000000001,8.90625L13.5912,12.937470000000001C13.5912,13.299900000000001,13.29738,13.593720000000001,12.93495,13.593720000000001L8.90625,13.593720000000001L8.90625,12.65622Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
export const IconQuery = ({style}: { style?: React.CSSProperties }) => (
|
||||
<svg className={'svg-icon'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
|
||||
width="1em" height="1em" viewBox="0 0 15 15">
|
||||
<defs>
|
||||
<clipPath id="master_svg0_1_3654/1_0572">
|
||||
<rect x="0" y="0" width="15" height="15" rx="0"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#master_svg0_1_3654/1_0572)">
|
||||
<g>
|
||||
<path
|
||||
d="M8.4375,8.20313Q8.4375,8.2607,8.43186,8.31799Q8.426210000000001,8.37528,8.41498,8.431750000000001Q8.40375,8.48821,8.387039999999999,8.5433Q8.37033,8.59839,8.3483,8.65158Q8.32626,8.70477,8.29913,8.75554Q8.27199,8.80632,8.24,8.85418Q8.20802,8.90205,8.1715,8.94655Q8.13497,8.991060000000001,8.09427,9.03177Q8.053560000000001,9.07247,8.00905,9.109Q7.96455,9.14552,7.9166799999999995,9.1775Q7.8688199999999995,9.20949,7.81804,9.23663Q7.76727,9.26376,7.71408,9.2858Q7.66089,9.30783,7.6058,9.324539999999999Q7.5507100000000005,9.34125,7.49425,9.35248Q7.43778,9.363710000000001,7.38049,9.36936Q7.3232,9.375,7.26563,9.375Q7.20805,9.375,7.15076,9.36936Q7.093468,9.363710000000001,7.037003,9.35248Q6.980539,9.34125,6.925448,9.324539999999999Q6.870356,9.30783,6.817168,9.2858Q6.76398,9.26376,6.713207,9.23663Q6.662434,9.20949,6.614566,9.1775Q6.566698,9.14552,6.522195,9.109Q6.477693,9.07247,6.436984,9.03177Q6.396276,8.991060000000001,6.3597529999999995,8.94655Q6.323231,8.90205,6.291247,8.85418Q6.259262,8.80632,6.232123,8.75554Q6.204985,8.70477,6.1829537,8.65158Q6.1609224,8.59839,6.1442105,8.5433Q6.1274987,8.48821,6.1162672,8.431750000000001Q6.1050358,8.37528,6.0993929,8.31799Q6.09375,8.2607,6.09375,8.20313Q6.09375,8.14555,6.0993929,8.08826Q6.1050358,8.030968,6.1162672,7.974503Q6.1274987,7.918039,6.1442105,7.862948Q6.1609224,7.807856,6.1829537,7.754668Q6.204985,7.70148,6.232123,7.650707Q6.259262,7.599934,6.291247,7.552066Q6.323231,7.504198,6.3597529999999995,7.459695Q6.396276,7.415193,6.436984,7.374484Q6.477693,7.333776,6.522195,7.2972529999999995Q6.566698,7.260731,6.614566,7.228747Q6.662434,7.196762,6.713207,7.169623Q6.76398,7.142485,6.817168,7.1204537Q6.870356,7.0984224,6.925448,7.0817105Q6.980539,7.0649987,7.037003,7.0537672Q7.093468,7.0425358,7.15076,7.0368929Q7.20805,7.03125,7.26563,7.03125Q7.3232,7.03125,7.38049,7.0368929Q7.43778,7.0425358,7.49425,7.0537672Q7.5507100000000005,7.0649987,7.6058,7.0817105Q7.66089,7.0984224,7.71408,7.1204537Q7.76727,7.142485,7.81804,7.169623Q7.8688199999999995,7.196762,7.9166799999999995,7.228747Q7.96455,7.260731,8.00905,7.2972529999999995Q8.053560000000001,7.333776,8.09427,7.374484Q8.13497,7.415193,8.1715,7.459695Q8.20802,7.504198,8.24,7.552066Q8.27199,7.599934,8.29913,7.650707Q8.32626,7.70148,8.3483,7.754668Q8.37033,7.807856,8.387039999999999,7.862948Q8.40375,7.918039,8.41498,7.974503Q8.426210000000001,8.030968,8.43186,8.08826Q8.4375,8.14555,8.4375,8.20313Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M3.28125,0.9375C2.5046049999999997,0.9375,1.875,1.567105,1.875,2.34375L1.875,12.6563C1.875,13.4329,2.5046049999999997,14.0625,3.28125,14.0625L11.71875,14.0625C12.4954,14.0625,13.125,13.4329,13.125,12.6563L13.125,5.15622L10.3125,5.15622C9.53585,5.15622,8.90625,4.526619999999999,8.90625,3.74997L8.90625,0.9375L3.28125,0.9375ZM9.375,8.20312C9.375,8.61589,9.25644,9.00097,9.05152,9.32616L10.32722,10.60187L9.66431,11.2648L8.38859,9.98906C8.06342,10.19396,7.67836,10.3125,7.26562,10.3125C6.10065,10.3125,5.15625,9.3681,5.15625,8.20312C5.15625,7.03815,6.10065,6.09375,7.26562,6.09375C8.4306,6.09375,9.375,7.03815,9.375,8.20312Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M12.84958,3.55950103515625C13.02483,3.73431103515625,13.1238,3.97131103515625,13.12499,4.21872103515625L10.3125,4.21872103515625C10.053619,4.21872103515625,9.84375,4.008851035156249,9.84375,3.74997103515625L9.84375,0.93756103515625C10.088203,0.94027833515625,10.322109,1.03840803515625,10.495386,1.21125403515625L12.84958,3.55950103515625Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const IconReconciliation = ({style}: { style?: React.CSSProperties }) => (
|
||||
<svg className={'svg-icon'} style={style} xmlns="http://www.w3.org/2000/svg" fill="none" version="1.1"
|
||||
width="1em" height="1em" viewBox="0 0 15 15">
|
||||
<defs>
|
||||
<clipPath id="master_svg0_1_3654/1_0566">
|
||||
<rect x="0" y="0" width="15" height="15" rx="6"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#master_svg0_1_3654/1_0566)">
|
||||
<g>
|
||||
<path
|
||||
d="M2.81335,5.56129C2.8471,4.29633,3.88314,3.28125,5.15625,3.28125C5.34382,3.28125,5.52626,3.30328,5.70108,3.34491C6.28865,2.459048,7.29486,1.875,8.4375,1.875C10.04117,1.875,11.3761,3.0254399999999997,11.6621,4.54604C13.0745,5.15864,14.0625,6.5655,14.0625,8.20312C14.0625,9.294170000000001,13.624,10.28278,12.9136,11.00232C13.1438,10.2376,12.5883,9.3752,11.6976,9.3752L11.247,9.3752L11.247,7.03119C11.247,6.51343,10.82728,6.09369,10.30951,6.09369L9.37201,6.09369C9.13329,6.09369,8.91541,6.18292,8.74989,6.32984C8.19456,5.96552,7.39427,5.99146,6.88692,6.60201L5.15099,8.69101C4.46064,9.52176,5.05144,10.78088,6.1316,10.78087L6.56222,10.78087L6.56222,12.1875L4.45312,12.1875C2.5115,12.1875,0.9375,10.6135,0.9375,8.67188C0.9375,7.32245,1.697769,6.1506,2.81335,5.56129Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M10.20127127734375,12.95489896484375C9.92087127734375,13.29233896484375,9.37200927734375,13.09405896484375,9.37200927734375,12.65530896484375L9.37200927734375,7.03118896484375L10.30950927734375,7.03118896484375L10.30950927734375,10.31269896484375L11.69760927734375,10.31269896484375C11.975589277343751,10.31269896484375,12.12763927734375,10.63672896484375,11.949969277343751,10.85053896484375L10.20127127734375,12.95489896484375Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
<g>
|
||||
<path
|
||||
d="M8.43601703125,7.46545189453125C8.43681703125,7.47703889453125,8.43721703125,7.48880089453125,8.43721703125,7.50075389453125L8.43721703125,13.124936894531249L7.49971703125,13.124936894531249L7.49971703125,9.84335689453125L6.13159703125,9.84335689453125C5.84567423125,9.84335689453125,5.68928803125,9.51007689453125,5.87202693125,9.29016689453125L7.60795703125,7.20116489453125C7.87685703125,6.87756589453125,8.39263703125,7.04663799453125,8.43450703125,7.44799089453125C8.43510703125,7.45376189453125,8.43560703125,7.45957789453125,8.43601703125,7.46545189453125Z"
|
||||
fill="currentColor" fill-opacity="1"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
@ -47,7 +47,12 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
||||
dispatch({
|
||||
payload: {
|
||||
isInitialized: true,
|
||||
isLoggedIn: true
|
||||
isLoggedIn: true,
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'test-user',
|
||||
department: 'root',
|
||||
}
|
||||
}
|
||||
})
|
||||
}, 300)
|
||||
@ -62,7 +67,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
||||
user: {
|
||||
id: 1,
|
||||
nickname: 'test-user',
|
||||
department: 'ro',
|
||||
department: 'root',
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -30,6 +30,7 @@ export const I18nSwitcher = () => {
|
||||
<Dropdown.Item
|
||||
active={locale == it.key} key={it.key}
|
||||
onClick={() => changeLocale(it.key)}
|
||||
icon={<span>{it.key.split('-')[1].toUpperCase()}</span>}
|
||||
>{it.text}</Dropdown.Item>
|
||||
))}
|
||||
</Dropdown.Menu>
|
||||
@ -37,7 +38,8 @@ export const I18nSwitcher = () => {
|
||||
>
|
||||
<Button theme="borderless">
|
||||
<Space align={'center'} spacing={2} style={{marginTop:2}}>
|
||||
<IconLanguage/> {LocaleList.find(s => s.key == locale)?.text}
|
||||
<IconLanguage style={{ color: '#666' }} />
|
||||
{/* {LocaleList.find(s => s.key == locale)?.text}*/}
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
@ -4,7 +4,12 @@
|
||||
"go_home": "Go Home"
|
||||
},
|
||||
"layout": {
|
||||
"logout": "Logout"
|
||||
"logout": "Logout",
|
||||
"menu": {
|
||||
"bill": "Bill Query",
|
||||
"check": "Reconciliation",
|
||||
"manual": "Manual Pay"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"submit": "LOGIN WITH SSO",
|
||||
|
@ -4,7 +4,12 @@
|
||||
"go_home": "回到首页"
|
||||
},
|
||||
"layout": {
|
||||
"logout": "注销登录"
|
||||
"logout": "注销登录",
|
||||
"menu": {
|
||||
"bill": "账单查询",
|
||||
"check": "对账",
|
||||
"manual": "现场支付"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"submit": "使用SSO登录",
|
||||
|
@ -4,7 +4,12 @@
|
||||
"go_home": "回到首页"
|
||||
},
|
||||
"layout": {
|
||||
"logout": "注销登录"
|
||||
"logout": "注销登录",
|
||||
"menu": {
|
||||
"bill": "账单查询",
|
||||
"check": "对账",
|
||||
"manual": "现场支付"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"submit": "使用SSO登入",
|
||||
|
@ -4,6 +4,7 @@ import useConfig from "@/hooks/useConfig.ts";
|
||||
import styled from "@emotion/styled";
|
||||
import AppLogo from "@/assets/AppLogo";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import useAuth from "@/hooks/useAuth.ts";
|
||||
|
||||
|
||||
const LoginContainer = styled.div({
|
||||
@ -22,12 +23,15 @@ const SubmitContainer = styled.div({
|
||||
margin: '70px auto 20px'
|
||||
})
|
||||
const AuthLogin = () => {
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const {t} = useTranslation();
|
||||
const {appName} = useConfig()
|
||||
|
||||
const showDashboard = ()=> {
|
||||
navigate('/dashboard')
|
||||
login().then(()=>{
|
||||
navigate('/dashboard')
|
||||
})
|
||||
}
|
||||
|
||||
return (<LoginContainer>
|
||||
|
7
src/pages/bill/query.tsx
Normal file
7
src/pages/bill/query.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
const BillQuery = () => {
|
||||
return (<div>
|
||||
<h1>BillQuery</h1>
|
||||
</div>)
|
||||
}
|
||||
export default BillQuery
|
7
src/pages/bill/reconciliation.tsx
Normal file
7
src/pages/bill/reconciliation.tsx
Normal file
@ -0,0 +1,7 @@
|
||||
|
||||
const BillReconciliation = () => {
|
||||
return (<div>
|
||||
<h1>BillQuery</h1>
|
||||
</div>)
|
||||
}
|
||||
export default BillReconciliation
|
@ -1,99 +1,10 @@
|
||||
import {Button} from "@douyinfe/semi-ui";
|
||||
import JsPDF from "jspdf";
|
||||
import autoTable from 'jspdf-autotable'
|
||||
import {useEffect, useState} from "react";
|
||||
import {saveAs} from "file-saver";
|
||||
import {GeneratePdf} from "@/service/generate-pdf.ts";
|
||||
|
||||
function drawItem(doc: JsPDF, item: {
|
||||
title: string;
|
||||
content?: string
|
||||
}, y: number, align: 'left' | 'right' = 'left', fontSize: number = 14) {
|
||||
doc.setFontSize(fontSize);
|
||||
// const width = doc.internal.pageSize.getWidth();
|
||||
doc.text(item.title, align == 'left' ? 20 : 180, y);
|
||||
if(item.content && item.content.length > 0) doc.text(item.content, align == 'left' ? 65 : 230, y);
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
|
||||
// doc.save('a4.pdf');
|
||||
const [viewUrl, setViewUrl] = useState('');
|
||||
useEffect(() => {
|
||||
const doc = new JsPDF({
|
||||
orientation: 'landscape',
|
||||
format:'a4'
|
||||
});
|
||||
console.log(doc.getFontList())
|
||||
// const width = doc.internal.pageSize.getWidth();
|
||||
// const height = doc.internal.pageSize.getHeight();
|
||||
doc.setFont('Helvetica','normal','bold');
|
||||
doc.setFontSize(20);
|
||||
doc.text('ACKNOWLEDGEMENT RECEIPT', 100, 20, {});
|
||||
|
||||
doc.setFont('Helvetica','normal','normal');
|
||||
drawItem(doc, {title: 'Student Name:', content: 'BAI ZHONGHONG'}, 40)
|
||||
drawItem(doc, {title: 'Reference Number:', content: '202402655'}, 40, "right")
|
||||
drawItem(doc, {title: 'Student Number:', content: '236171275'}, 48)
|
||||
drawItem(doc, {title: 'Print Date:', content: '2024-05-19'}, 48, "right")
|
||||
|
||||
drawItem(doc, {
|
||||
title: 'Programme:',
|
||||
content: 'MASTER OF SOCIAL SCIENCES IN INTERNATIONAL RELATIONS FOR \nBELT AND ROAD COUNTRIES'
|
||||
}, 56)
|
||||
drawItem(doc, {title: 'Mode of Study:', content: 'FULL-TIME'}, 70)
|
||||
// draw table
|
||||
autoTable(doc, {
|
||||
startY: 80,
|
||||
theme: 'grid',
|
||||
|
||||
margin: { top: 37,left:20,right:20 },
|
||||
styles: {
|
||||
fontSize: 13,
|
||||
fillColor: [255, 255, 255],
|
||||
textColor: [0, 0, 0],
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.2,
|
||||
minCellHeight:10,
|
||||
valign: 'middle'
|
||||
},
|
||||
headStyles: {
|
||||
fontSize: 15,
|
||||
},
|
||||
head: [['No.', 'Transaction Date', 'Payment Method', 'HK$']],
|
||||
body: [
|
||||
['#87254', '2024-05-13', 'DOCUMENT FEE', 'AsiaPay(Wechat Pay)', '100.00'],
|
||||
['#87255', '2024-05-13', 'VISA FEE', 'AsiaPay(Wechat Pay)', '50.00'],
|
||||
[{
|
||||
colSpan: 3,
|
||||
content: 'TOTAL:',
|
||||
styles: {valign: 'middle', halign: 'right'},
|
||||
}, '150.00'],
|
||||
],
|
||||
})
|
||||
// draw foot
|
||||
drawItem(doc, {title: 'Remarks:', content: ''}, 155)
|
||||
drawItem(doc, {title: 'Cashier:', content: ''}, 155,"right")
|
||||
|
||||
doc.setFont('Helvetica','italic');
|
||||
drawItem(doc, {title: 'Please retain this acknowledgement receipt for your record.'}, 185)
|
||||
|
||||
const url = doc.output('bloburl')
|
||||
setViewUrl(url.toString())
|
||||
}, [])
|
||||
|
||||
function downloadPDF() {
|
||||
saveAs(viewUrl, 'receipt-87254.pdf');
|
||||
}
|
||||
|
||||
return (<div>
|
||||
<h1>Index</h1>
|
||||
<Button onClick={downloadPDF}>生成PDF</Button>
|
||||
<div>
|
||||
<iframe src={viewUrl} style={{
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
height: 800
|
||||
}}></iframe>
|
||||
</div>
|
||||
<Button onClick={()=>GeneratePdf('test-11.pdf')}>生成PDF</Button>
|
||||
</div>)
|
||||
}
|
@ -6,6 +6,8 @@ import AuthLogin from "@/pages/auth/login.tsx";
|
||||
import AuthLayout from "@/routes/layout/auth-layout.tsx";
|
||||
|
||||
const ManualIndex = lazy(() => import("@/pages/manual/index.tsx"));
|
||||
const BillQuery = lazy(() => import("@/pages/bill/query.tsx"));
|
||||
const BillReconciliation = lazy(() => import("@/pages/bill/reconciliation.tsx"));
|
||||
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
@ -37,6 +39,14 @@ const routes: RouteObject[] = [
|
||||
path: 'manual',
|
||||
element: <ManualIndex/>
|
||||
},
|
||||
{
|
||||
path: 'bill',
|
||||
element: <BillQuery/>
|
||||
},
|
||||
{
|
||||
path: 'reconciliation',
|
||||
element: <BillReconciliation/>
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
@ -7,6 +7,8 @@ import AuthGuard from "@/routes/layout/auth-guard.tsx";
|
||||
import AppLogo from "@/assets/AppLogo.tsx";
|
||||
import useAuth from "@/hooks/useAuth.ts";
|
||||
import {I18nSwitcher} from "@/i18n";
|
||||
import {DashboardNavigation} from "@/routes/layout/dashboard-navigation.tsx";
|
||||
import { IconExit } from "@douyinfe/semi-icons";
|
||||
|
||||
const {Header, Content, Sider} = Layout;
|
||||
|
||||
@ -22,7 +24,7 @@ export const HeaderUserAvatar = () => {
|
||||
position={'bottomRight'}
|
||||
render={
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item onClick={logout}>{t('layout.logout')}</Dropdown.Item>
|
||||
<Dropdown.Item icon={<IconExit />} onClick={logout}>{t('layout.logout')}</Dropdown.Item>
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
@ -64,15 +66,19 @@ export const CommonHeader: React.FC<CommonHeaderProps> = ({children, title, righ
|
||||
type LayoutProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
|
||||
export const BaseLayout: React.FC<LayoutProps> = ({children}) => {
|
||||
return (<Layout>
|
||||
<CommonHeader/>
|
||||
<Layout style={{
|
||||
backgroundColor: '#f8f9fa'
|
||||
}}>
|
||||
<Sider style={{width: '250px'}}>Sider</Sider>
|
||||
<Sider style={{width: '250px'}}>
|
||||
<DashboardNavigation />
|
||||
</Sider>
|
||||
<Content style={{padding: '15px'}}>
|
||||
<div className="content-container" style={{backgroundColor: '#fff', minHeight: 300, borderRadius: 10}}>
|
||||
<div className="content-container" style={{backgroundColor: '#fff', minHeight: 300, borderRadius: 10,padding:15}}>
|
||||
{children}
|
||||
</div>
|
||||
</Content>
|
||||
|
48
src/routes/layout/dashboard-navigation.tsx
Normal file
48
src/routes/layout/dashboard-navigation.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import {NavLink} from "react-router-dom";
|
||||
import {useMemo} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import useAuth from "@/hooks/useAuth.ts";
|
||||
import {IconQRCode, IconQuery, IconReconciliation} from "@/components/logo";
|
||||
|
||||
const AllDashboardMenu = [
|
||||
{
|
||||
key: 'manual',
|
||||
icon: <IconQRCode/>,
|
||||
path: '/dashboard/manual',
|
||||
},
|
||||
{
|
||||
key: 'bill',
|
||||
icon: <IconQuery/>,
|
||||
path: '/dashboard/bill',
|
||||
role: ['root', 'ro', 'fo']
|
||||
},
|
||||
{
|
||||
key: 'check',
|
||||
icon: <IconReconciliation/>,
|
||||
path: '/dashboard/reconciliation',
|
||||
role: ['root', 'ro', 'fo']
|
||||
}
|
||||
]
|
||||
|
||||
export function DashboardNavigation() {
|
||||
const {t} = useTranslation()
|
||||
|
||||
const {user} = useAuth();
|
||||
const navItems = useMemo(() => {
|
||||
if (!user) return [];
|
||||
|
||||
return AllDashboardMenu.filter(it => {
|
||||
return !it.role || it.role.includes(user.department)
|
||||
});
|
||||
}, [user])
|
||||
return (<div className={'dashboard-menu-container'}>
|
||||
{navItems.map((it) => (
|
||||
<NavLink to={it.path} key={it.key} className={'nav-item'}>
|
||||
{it.icon}
|
||||
<span className="menu-text">{t(`layout.menu.${it.key}`)}</span>
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
73
src/service/generate-pdf.ts
Normal file
73
src/service/generate-pdf.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import JsPDF from "jspdf";
|
||||
import autoTable from "jspdf-autotable";
|
||||
|
||||
function drawItem(doc: JsPDF, item: {
|
||||
title: string;
|
||||
content?: string
|
||||
}, y: number, align: 'left' | 'right' = 'left', fontSize: number = 13) {
|
||||
doc.setFontSize(fontSize);
|
||||
// const width = doc.internal.pageSize.getWidth();
|
||||
doc.text(item.title, align == 'left' ? 20 : 180, y);
|
||||
if (item.content && item.content.length > 0) doc.text(item.content, align == 'left' ? 65 : 230, y, {maxWidth: 150});
|
||||
}
|
||||
|
||||
export function GeneratePdf(filename: string) {
|
||||
const doc = new JsPDF({
|
||||
orientation: 'landscape',
|
||||
format: 'a4'
|
||||
});
|
||||
// const width = doc.internal.pageSize.getWidth();
|
||||
// const height = doc.internal.pageSize.getHeight();
|
||||
doc.setFont('Helvetica', 'normal', 'bold');
|
||||
doc.setFontSize(20);
|
||||
doc.text('ACKNOWLEDGEMENT RECEIPT', 100, 20, {});
|
||||
|
||||
doc.setFont('Helvetica', 'normal', 'normal');
|
||||
drawItem(doc, {title: 'Student Name:', content: 'BAI ZHONGHONG'}, 40)
|
||||
drawItem(doc, {title: 'Reference Number:', content: '202402655'}, 40, "right")
|
||||
drawItem(doc, {title: 'Student Number:', content: '236171275'}, 48)
|
||||
drawItem(doc, {title: 'Print Date:', content: '2024-05-19'}, 48, "right")
|
||||
|
||||
drawItem(doc, {
|
||||
title: 'Programme:',
|
||||
content: 'MASTER OF SOCIAL SCIENCES IN INTERNATIONAL RELATIONS FOR BELT AND ROAD COUNTRIES'
|
||||
}, 56)
|
||||
drawItem(doc, {title: 'Mode of Study:', content: 'FULL-TIME'}, 70)
|
||||
// draw table
|
||||
autoTable(doc, {
|
||||
startY: 80,
|
||||
theme: 'grid',
|
||||
|
||||
margin: {top: 37, left: 20, right: 20},
|
||||
styles: {
|
||||
fontSize: 13,
|
||||
fillColor: [255, 255, 255],
|
||||
textColor: [0, 0, 0],
|
||||
lineColor: [0, 0, 0],
|
||||
lineWidth: 0.2,
|
||||
minCellHeight: 10,
|
||||
valign: 'middle'
|
||||
},
|
||||
headStyles: {
|
||||
fontSize: 15,
|
||||
},
|
||||
head: [['No.', 'Transaction Date', 'Payment Type', 'Payment Method', 'HK$']],
|
||||
body: [
|
||||
['#87254', '2024-05-13', 'DOCUMENT FEE ', 'AsiaPay(Wechat Pay)', '100.00'],
|
||||
['#87255', '2024-05-13', 'VISA FEE', 'AsiaPay(Wechat Pay)', '50.00'],
|
||||
[{
|
||||
colSpan: 3,
|
||||
content: 'TOTAL:',
|
||||
styles: {valign: 'middle', halign: 'right'},
|
||||
}, '150.00'],
|
||||
],
|
||||
})
|
||||
// draw foot
|
||||
drawItem(doc, {title: 'Remarks:', content: ''}, 155)
|
||||
drawItem(doc, {title: 'Cashier:', content: ''}, 155, "right")
|
||||
|
||||
doc.setFont('Helvetica', 'italic');
|
||||
drawItem(doc, {title: 'Please retain this acknowledgement receipt for your record.'}, 185)
|
||||
|
||||
doc.save(filename);
|
||||
}
|
34
yarn.lock
34
yarn.lock
@ -826,11 +826,6 @@
|
||||
resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||
|
||||
"@types/file-saver@^2.0.7":
|
||||
version "2.0.7"
|
||||
resolved "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz#8dbb2f24bdc7486c54aa854eb414940bbd056f7d"
|
||||
integrity sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==
|
||||
|
||||
"@types/lodash@^4.17.1":
|
||||
version "4.17.4"
|
||||
resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.4.tgz#0303b64958ee070059e3a7184048a55159fe20b7"
|
||||
@ -1233,11 +1228,6 @@ cosmiconfig@^7.0.0:
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.10.0"
|
||||
|
||||
countup.js@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.npmmirror.com/countup.js/-/countup.js-2.8.0.tgz#64951f2df3ede28839413d654d8fef28251c32a8"
|
||||
integrity sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ==
|
||||
|
||||
cross-spawn@^7.0.2:
|
||||
version "7.0.3"
|
||||
resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
|
||||
@ -1520,11 +1510,6 @@ file-entry-cache@^6.0.1:
|
||||
dependencies:
|
||||
flat-cache "^3.0.4"
|
||||
|
||||
file-saver@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
|
||||
integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
|
||||
@ -2135,13 +2120,6 @@ raf@^3.4.1:
|
||||
dependencies:
|
||||
performance-now "^2.1.0"
|
||||
|
||||
react-countup@^6.5.3:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.npmmirror.com/react-countup/-/react-countup-6.5.3.tgz#e892aa3eab2d6ba9c3cdba30bf4ed6764826d848"
|
||||
integrity sha512-udnqVQitxC7QWADSPDOxVWULkLvKUWrDapn5i53HE4DPRVgs+Y5rr4bo25qEl8jSh+0l2cToJgGMx+clxPM3+w==
|
||||
dependencies:
|
||||
countup.js "^2.8.0"
|
||||
|
||||
react-dom@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4"
|
||||
@ -2495,11 +2473,6 @@ uri-js@^4.2.2:
|
||||
dependencies:
|
||||
punycode "^2.1.0"
|
||||
|
||||
use-sync-external-store@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||
|
||||
utility-types@^3.10.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz#607c40edb4f258915e901ea7995607fdf319424c"
|
||||
@ -2559,10 +2532,3 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zustand@^4.5.2:
|
||||
version "4.5.2"
|
||||
resolved "https://registry.npmmirror.com/zustand/-/zustand-4.5.2.tgz#fddbe7cac1e71d45413b3682cdb47b48034c3848"
|
||||
integrity sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==
|
||||
dependencies:
|
||||
use-sync-external-store "1.2.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user