From 44095afbc7657a8c88ac7c9eb2d4739c4513d83d Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sun, 19 May 2024 23:22:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=90=8E=E5=8F=B0=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +- src/assets/index.less | 47 +++++++-- src/components/logo/index.tsx | 108 +++++++++++++++++++++ src/contexts/auth/index.tsx | 9 +- src/i18n/index.tsx | 4 +- src/i18n/translations/en.json | 7 +- src/i18n/translations/sc.json | 7 +- src/i18n/translations/tc.json | 7 +- src/pages/auth/login.tsx | 6 +- src/pages/bill/query.tsx | 7 ++ src/pages/bill/reconciliation.tsx | 7 ++ src/pages/manual/index.tsx | 93 +----------------- src/routes/index.tsx | 10 ++ src/routes/layout/dashboard-layout.tsx | 12 ++- src/routes/layout/dashboard-navigation.tsx | 48 +++++++++ src/service/generate-pdf.ts | 73 ++++++++++++++ yarn.lock | 34 ------- 17 files changed, 339 insertions(+), 146 deletions(-) create mode 100644 src/pages/bill/query.tsx create mode 100644 src/pages/bill/reconciliation.tsx create mode 100644 src/routes/layout/dashboard-navigation.tsx create mode 100644 src/service/generate-pdf.ts diff --git a/package.json b/package.json index 2404806..08b9622 100644 --- a/package.json +++ b/package.json @@ -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" } diff --git a/src/assets/index.less b/src/assets/index.less index 7c32c8f..335846f 100644 --- a/src/assets/index.less +++ b/src/assets/index.less @@ -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; + } + } + } } \ No newline at end of file diff --git a/src/components/logo/index.tsx b/src/components/logo/index.tsx index e69de29..076d017 100644 --- a/src/components/logo/index.tsx +++ b/src/components/logo/index.tsx @@ -0,0 +1,108 @@ +import React from "react"; + +export const IconQRCode = ({style}: { style?: React.CSSProperties }) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +) +export const IconQuery = ({style}: { style?: React.CSSProperties }) => ( + + + + + + + + + + + + + + + + + + +) + +export const IconReconciliation = ({style}: { style?: React.CSSProperties }) => ( + + + + + + + + + + + + + + + + + + +) \ No newline at end of file diff --git a/src/contexts/auth/index.tsx b/src/contexts/auth/index.tsx index 99c154f..4b3a7e4 100644 --- a/src/contexts/auth/index.tsx +++ b/src/contexts/auth/index.tsx @@ -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', } } }) diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx index f29c886..b8f7ea9 100644 --- a/src/i18n/index.tsx +++ b/src/i18n/index.tsx @@ -30,6 +30,7 @@ export const I18nSwitcher = () => { changeLocale(it.key)} + icon={{it.key.split('-')[1].toUpperCase()}} >{it.text} ))} @@ -37,7 +38,8 @@ export const I18nSwitcher = () => { > diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index 8bf6a4d..ebe9c23 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -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", diff --git a/src/i18n/translations/sc.json b/src/i18n/translations/sc.json index 407a9e6..c8fea40 100644 --- a/src/i18n/translations/sc.json +++ b/src/i18n/translations/sc.json @@ -4,7 +4,12 @@ "go_home": "回到首页" }, "layout": { - "logout": "注销登录" + "logout": "注销登录", + "menu": { + "bill": "账单查询", + "check": "对账", + "manual": "现场支付" + } }, "login": { "submit": "使用SSO登录", diff --git a/src/i18n/translations/tc.json b/src/i18n/translations/tc.json index 60a1a81..1f8ab05 100644 --- a/src/i18n/translations/tc.json +++ b/src/i18n/translations/tc.json @@ -4,7 +4,12 @@ "go_home": "回到首页" }, "layout": { - "logout": "注销登录" + "logout": "注销登录", + "menu": { + "bill": "账单查询", + "check": "对账", + "manual": "现场支付" + } }, "login": { "submit": "使用SSO登入", diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx index ae5b576..0a501b5 100644 --- a/src/pages/auth/login.tsx +++ b/src/pages/auth/login.tsx @@ -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 ( diff --git a/src/pages/bill/query.tsx b/src/pages/bill/query.tsx new file mode 100644 index 0000000..28d8eca --- /dev/null +++ b/src/pages/bill/query.tsx @@ -0,0 +1,7 @@ + +const BillQuery = () => { + return (
+

BillQuery

+
) +} +export default BillQuery \ No newline at end of file diff --git a/src/pages/bill/reconciliation.tsx b/src/pages/bill/reconciliation.tsx new file mode 100644 index 0000000..e88de97 --- /dev/null +++ b/src/pages/bill/reconciliation.tsx @@ -0,0 +1,7 @@ + +const BillReconciliation = () => { + return (
+

BillQuery

+
) +} +export default BillReconciliation \ No newline at end of file diff --git a/src/pages/manual/index.tsx b/src/pages/manual/index.tsx index c2976d4..f3a3c52 100644 --- a/src/pages/manual/index.tsx +++ b/src/pages/manual/index.tsx @@ -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 (

Index

- -
- -
+
) } \ No newline at end of file diff --git a/src/routes/index.tsx b/src/routes/index.tsx index d04e1c6..5bc6475 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -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: }, + { + path: 'bill', + element: + }, + { + path: 'reconciliation', + element: + }, ] }, ] diff --git a/src/routes/layout/dashboard-layout.tsx b/src/routes/layout/dashboard-layout.tsx index f80a34d..d29188b 100644 --- a/src/routes/layout/dashboard-layout.tsx +++ b/src/routes/layout/dashboard-layout.tsx @@ -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={ - {t('layout.logout')} + } onClick={logout}>{t('layout.logout')} } > @@ -64,15 +66,19 @@ export const CommonHeader: React.FC = ({children, title, righ type LayoutProps = { children: React.ReactNode } + + export const BaseLayout: React.FC = ({children}) => { return ( - Sider + + + -
+
{children}
diff --git a/src/routes/layout/dashboard-navigation.tsx b/src/routes/layout/dashboard-navigation.tsx new file mode 100644 index 0000000..9dd3787 --- /dev/null +++ b/src/routes/layout/dashboard-navigation.tsx @@ -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: , + path: '/dashboard/manual', + }, + { + key: 'bill', + icon: , + path: '/dashboard/bill', + role: ['root', 'ro', 'fo'] + }, + { + key: 'check', + icon: , + 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 (
+ {navItems.map((it) => ( + + {it.icon} + {t(`layout.menu.${it.key}`)} + + ))} +
+ ); +} \ No newline at end of file diff --git a/src/service/generate-pdf.ts b/src/service/generate-pdf.ts new file mode 100644 index 0000000..be5f7c5 --- /dev/null +++ b/src/service/generate-pdf.ts @@ -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); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ce1761b..d440bbd 100644 --- a/yarn.lock +++ b/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"