diff --git a/Dockerfile-prod b/Dockerfile-prod new file mode 100644 index 0000000..f94cb50 --- /dev/null +++ b/Dockerfile-prod @@ -0,0 +1,44 @@ +# Dockerfile for a React app build and run + +FROM node:18.19.1-alpine AS builder +MAINTAINER yaclty2@gmail.com +WORKDIR /app + +# envs 配置 +# 应用部署后的URL +ENV APP_SITE_URL "" +# 应用接口前缀 +ENV APP_API_URL "" + +# Copy source code to the builder +COPY package.json yarn.lock* ./ +COPY public ./public +COPY src ./src +COPY index.html ./ +COPY *.ts . +COPY *.json . + +# Install dependencies and build the app +RUN yarn install +RUN yarn build-prod + + +# Step 2. Production image, copy all the files and run nginx +FROM nginx:1.26-alpine3.19 AS runner + +WORKDIR /app + +# envs 配置 +ENV APP_API_URL localhost:50000 + +# nginx配置文件 +COPY nginx.conf /etc/nginx/conf.d/default.conf.template +# 编译文件 +COPY --from=builder /app/dist ./ +# RUN /bin/sh envsubst /etc/nginx/templates/*.template /etc/nginx/conf.d +WORKDIR /etc/nginx/conf.d/ +ENTRYPOINT sed -i "s~~~" /app/index.html && envsubst '$APP_API_URL' < default.conf.template > default.conf && cat default.conf && nginx -g 'daemon off;' +# 暴露80端口 +EXPOSE 80 +# 启动Nginx服务 +# CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/package.json b/package.json index aab8aad..d32459a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite --host", "build": "tsc && vite build", "build-test": "tsc && vite build --mode=test", - "build-docker:latest": "docker build -t payment-front:latest .", + "build-for-wm": "tsc && vite build --mode=for-wm", + "build-prod": "tsc && vite build --mode=production", "clean-build": "rm -rf dist", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" diff --git a/src/components/icons/index.tsx b/src/components/icons/index.tsx index be49cc9..69a98e0 100644 --- a/src/components/icons/index.tsx +++ b/src/components/icons/index.tsx @@ -94,7 +94,7 @@ export const IconStudentEmail = ({style}: IconProps) => { width="1em" height="1em" style={style}> + fill="#00C479"/> ) } @@ -132,12 +132,27 @@ export const IconBillType = ({style}: IconProps) => { ) } -export const IconLoading = ({size}:{size?:string|number})=>( +export const IconLoading = ({size,color}: { size?: string | number,color?:string; }) => ( - - + + -) \ No newline at end of file +) + +export const IconRoles = ({size,color}: { size?: string | number,color?:string; }) => ( + + +) + diff --git a/src/contexts/auth/index.tsx b/src/contexts/auth/index.tsx index 7d36177..698b91a 100644 --- a/src/contexts/auth/index.tsx +++ b/src/contexts/auth/index.tsx @@ -117,6 +117,20 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => { } }) } + + const updateUser = async (user: Partial) => { + dispatch({ + action: 'updateUser', + payload: { + user:{ + ...state.user, + ...user + } as any, + + } + }) + }; + useEffect(() => { init().then(console.log) }, []) @@ -128,7 +142,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => { return ({children}) } export default AuthContext \ No newline at end of file diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx index b8f7ea9..5802da4 100644 --- a/src/i18n/index.tsx +++ b/src/i18n/index.tsx @@ -36,9 +36,9 @@ export const I18nSwitcher = () => { } > - diff --git a/src/i18n/translations/en.json b/src/i18n/translations/en.json index bbd6cca..262ad30 100644 --- a/src/i18n/translations/en.json +++ b/src/i18n/translations/en.json @@ -84,7 +84,7 @@ }, "pay": { "amount": "Amount", - "bill_error": "Bills to be paid do not exist or are overdue", + "bill_error": "The bill to be paid does not exist or is overdue", "charge": "Charge", "confirm_pay": "CONFIRM PAYMENT", "query_pay_status": "Check payment status...", diff --git a/src/main.tsx b/src/main.tsx index f6cfaf8..a88a94f 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,22 +1,9 @@ // import React from "react"; import ReactDOM from 'react-dom/client' -import * as Sentry from '@sentry/react'; import App from './App.tsx' import '@/assets/index.less' -Sentry.init({ - dsn: "https://571faf95edca40ac981a573eff1b17fe@logs.1688cd.cn/1", - //dsn: "https://5224dbb04d4a6a521edeb67552310836@o4505757500309504.ingest.us.sentry.io/4507373697564672", - integrations: [ - Sentry.browserTracingIntegration(), - Sentry.replayIntegration(), - ], - tracesSampleRate: 1.0, - tracePropagationTargets: ["localhost", ], // /^https:\/\/yourserver\.io\/api/, - replaysSessionSampleRate: 0.1, - replaysOnErrorSampleRate: 1.0, -}); ReactDOM.createRoot(document.getElementById('root')!).render( , // , diff --git a/src/pages/bill/external_create.tsx b/src/pages/bill/external_create.tsx new file mode 100644 index 0000000..53a7739 --- /dev/null +++ b/src/pages/bill/external_create.tsx @@ -0,0 +1,69 @@ +import styles from "@/pages/pay/pay.module.less"; +import {useNavigate, useSearchParams} from "react-router-dom"; +import {useSetState} from "ahooks"; +import {useEffect} from "react"; +import {createExternalBill} from "@/service/api/bill.ts"; +import {IconLoading} from "@/components/icons"; + +// 获取必填参数 +const RequiredParams = [ + 'application_number', // 'student_number', 可以不设置 + 'source', 'amount', 'student_chinese_name', + 'student_english_name', 'student_email', + 'program_code', 'programme_chinese_name', + 'programme_english_name', 'department_chinese_name', + 'department_english_name', 'attendance_mode', + 'intake_year', 'intake_semester' +] + +const ExternalCreate = () => { + const [state, setState] = useSetState<{ + error?: string; + params?: ExternalCreateParamsType; + loading?: boolean; + }>({ + loading: true + }) + const [searchParams] = useSearchParams(); + const navigate = useNavigate() + const createBill = (params: ExternalCreateParamsType) => { + setState({loading: true}) + createExternalBill(params).then((ret) => { + setState({loading: false}) + navigate(`/pay?bill=${ret.id}`, {replace: true}) + }).catch(() => { + setState({loading: false, 'error': 'create pay order error'}) + }) + } + useEffect(() => { + if (searchParams) { + const paramsContent = searchParams.get('params'); + if (!paramsContent) { + return setState({error: 'params error'}) + } + const params: ExternalCreateParamsType = JSON.parse(paramsContent); + for (let i = 0; i < RequiredParams.length; i++) { + const key = RequiredParams[i]; + if (!params[key]) { + return setState({error: 'params error: require ' + key}) + } + params[key] = searchParams.get(key) + } + if (!params.details || params.details.length == 0) { + return setState({error: 'params error: require detail'}) + } + createBill(params) + return; + } + }, [searchParams]) + return (
+ {state.loading &&
+
+
+
} + {state.error &&
+

{state.error}

+
} +
) +} +export default ExternalCreate \ No newline at end of file diff --git a/src/pages/bill/query.tsx b/src/pages/bill/query.tsx index d01d159..e437694 100644 --- a/src/pages/bill/query.tsx +++ b/src/pages/bill/query.tsx @@ -2,7 +2,6 @@ import {Button, Modal, Notification, Popconfirm, Space} from "@douyinfe/semi-ui" import {useState} from "react"; import {useRequest} from "ahooks"; import {useTranslation} from "react-i18next"; -import * as Sentry from "@sentry/react"; import {BillList} from "@/components/bill/list.tsx"; import SearchForm from "@/components/bill/search-form.tsx"; @@ -29,13 +28,7 @@ const BillQuery = () => { }), { refreshDeps: [queryParams], onError: (e) => { - Sentry.captureMessage('Error: Query bill error', { - level: 'error', - extra: { - message: e.message, - ...queryParams - } - }); + Notification.error({title: 'Error', content: e.message}) } }) const {t} = useTranslation() diff --git a/src/pages/manual/index.tsx b/src/pages/manual/index.tsx index 6e82aa6..6fc9082 100644 --- a/src/pages/manual/index.tsx +++ b/src/pages/manual/index.tsx @@ -12,7 +12,6 @@ import {useSetState} from "ahooks"; import {BizError} from "@/service/types.ts"; import {IconAlertTriangle} from "@douyinfe/semi-icons"; import dayjs from "dayjs"; -import * as Sentry from '@sentry/react' export default function Index() { @@ -36,14 +35,6 @@ export default function Index() { setBillInfo(ret) //getBillDetail(ret.id).then(setBillInfo); }).catch((e: BizError) => { - Sentry.captureMessage('Error: create manual bill error', { - level: 'error', - extra: { - message: e.message, - request_id: e.request_id, - ...values - } - }); setState({errorMessage: e.message}) }).finally(() => { setState({loading: false}) diff --git a/src/pages/pay/index.tsx b/src/pages/pay/index.tsx index 5f60450..ae357a5 100644 --- a/src/pages/pay/index.tsx +++ b/src/pages/pay/index.tsx @@ -24,7 +24,7 @@ const PayIndex = () => { useEffect(() => { const billId = search.get('bill') if (!billId || !/^\d+$/.test(billId)) { - navigate(`/fail?bill=${billId}`, {replace: true}) + navigate(`/pay/error?bill=${billId}`, {replace: true}) return; } getBillDetail(Number(billId)).then((bill) => { @@ -34,7 +34,7 @@ const PayIndex = () => { } setBill(bill) }).catch(() => { - navigate(`/fail?bill=${billId}&msg=bill_error`, {replace: true}) + navigate(`/pay/error?bill=${billId}&msg=bill_error`, {replace: true}) }) }, []) return (
diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 4a51d06..b0e60cc 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -20,6 +20,7 @@ import Loader from "@/components/loader.tsx"; import ManualIndex from "@/pages/manual/index.tsx"; import BillQuery from "@/pages/bill/query.tsx"; import BillReconciliation from "@/pages/bill/reconciliation.tsx"; +import ExternalCreate from "@/pages/bill/external_create.tsx"; const routes: RouteObject[] = [ @@ -48,6 +49,10 @@ const routes: RouteObject[] = [ path: 'pay/:result', element: }, + { + path: 'bill/external_create', + element: + }, ] }, { diff --git a/src/routes/layout/dashboard-layout.tsx b/src/routes/layout/dashboard-layout.tsx index 190440d..5f09af8 100644 --- a/src/routes/layout/dashboard-layout.tsx +++ b/src/routes/layout/dashboard-layout.tsx @@ -1,6 +1,6 @@ import {Outlet, useLocation} from "react-router-dom"; import React, {useMemo} from "react"; -import {Avatar, Dropdown, Layout, Nav, Space, Typography} from "@douyinfe/semi-ui" +import {Avatar, Button, Dropdown, Layout, Nav, Space, Typography} from "@douyinfe/semi-ui" import {useTranslation} from "react-i18next"; import AuthGuard from "@/routes/layout/auth-guard.tsx"; @@ -11,6 +11,7 @@ import {AllDashboardMenu, DashboardNavigation} from "@/routes/layout/dashboard-n import {IconExit, IconUser} from "@douyinfe/semi-icons"; import styled from "@emotion/styled"; import useConfig from "@/hooks/useConfig.ts"; +import {IconRoles} from "@/components/icons"; const {Header, Content, Sider} = Layout; @@ -56,8 +57,36 @@ export const HeaderUserAvatar = () => { ) } +const RoleList = ['root', 'ro', 'fo','staff'] +const RoleSwitcher = ()=>{ + const {user, updateUser} = useAuth() + + return (<> + {AppMode !== 'production' && ( + {RoleList.map((key) => ( + updateUser({department: key})} + >{key.toUpperCase()} + ))} + + } + > + + ) } + ) +} export const CommonHeader: React.FC = ({children, title, rightExtra}) => { const {appName} = useConfig() + return (