add role switch only for test
This commit is contained in:
parent
a4e21ec909
commit
d49ce2b815
44
Dockerfile-prod
Normal file
44
Dockerfile-prod
Normal file
@ -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_url-->~<script>const APP_SITE_URL='${APP_SITE_URL}';</script>~" /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;"]
|
@ -7,7 +7,8 @@
|
|||||||
"dev": "vite --host",
|
"dev": "vite --host",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"build-test": "tsc && vite build --mode=test",
|
"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",
|
"clean-build": "rm -rf dist",
|
||||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
|
@ -94,7 +94,7 @@ export const IconStudentEmail = ({style}: IconProps) => {
|
|||||||
width="1em" height="1em" style={style}>
|
width="1em" height="1em" style={style}>
|
||||||
<path
|
<path
|
||||||
d="M926.47619 355.644952V780.190476a73.142857 73.142857 0 0 1-73.142857 73.142857H170.666667a73.142857 73.142857 0 0 1-73.142857-73.142857V355.644952l73.142857 62.000762V780.190476h682.666666V417.645714l73.142857-62.000762zM853.333333 170.666667a74.044952 74.044952 0 0 1 26.087619 4.778666 72.704 72.704 0 0 1 30.622477 22.186667 73.508571 73.508571 0 0 1 10.678857 17.67619c3.169524 7.509333 5.12 15.652571 5.607619 24.210286L926.47619 243.809524v24.380952L559.469714 581.241905a73.142857 73.142857 0 0 1-91.306666 2.901333l-3.632762-2.925714L97.52381 268.190476v-24.380952a72.899048 72.899048 0 0 1 40.155428-65.292191A72.97219 72.97219 0 0 1 170.666667 170.666667h682.666666z m-10.971428 73.142857H181.638095L512 525.58019 842.361905 243.809524z"
|
d="M926.47619 355.644952V780.190476a73.142857 73.142857 0 0 1-73.142857 73.142857H170.666667a73.142857 73.142857 0 0 1-73.142857-73.142857V355.644952l73.142857 62.000762V780.190476h682.666666V417.645714l73.142857-62.000762zM853.333333 170.666667a74.044952 74.044952 0 0 1 26.087619 4.778666 72.704 72.704 0 0 1 30.622477 22.186667 73.508571 73.508571 0 0 1 10.678857 17.67619c3.169524 7.509333 5.12 15.652571 5.607619 24.210286L926.47619 243.809524v24.380952L559.469714 581.241905a73.142857 73.142857 0 0 1-91.306666 2.901333l-3.632762-2.925714L97.52381 268.190476v-24.380952a72.899048 72.899048 0 0 1 40.155428-65.292191A72.97219 72.97219 0 0 1 170.666667 170.666667h682.666666z m-10.971428 73.142857H181.638095L512 525.58019 842.361905 243.809524z"
|
||||||
fill="#00C479" />
|
fill="#00C479"/>
|
||||||
</svg>)
|
</svg>)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,12 +132,27 @@ export const IconBillType = ({style}: IconProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconLoading = ({size}:{size?:string|number})=>(<svg xmlns="http://www.w3.org/2000/svg" style={{
|
export const IconLoading = ({size,color}: { size?: string | number,color?:string; }) => (<svg xmlns="http://www.w3.org/2000/svg" style={{
|
||||||
margin: 'auto', display: 'block',...(size?{fontSize:size}:{})
|
margin: 'auto', display: 'block', ...(size ? {fontSize: size} : {}), ...(color ? {color} : {})
|
||||||
}} width="1em" height="1em" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
}} width="1em" height="1em" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
|
||||||
<circle cx="50" cy="50" r="30" stroke="#6a6a6a" strokeWidth="6" fill="none"></circle>
|
<circle cx="50" cy="50" r="30" stroke="#6a6a6a" strokeWidth="6" fill="none"></circle>
|
||||||
<circle cx="50" cy="50" r="30" stroke="#aaaaaa" strokeWidth="6" strokeLinecap="round" fill="none">
|
<circle cx="50" cy="50" r="30" stroke="#aaaaaa" strokeWidth="6" strokeLinecap="round" fill="none">
|
||||||
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
|
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s"
|
||||||
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s" values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882" keyTimes="0;0.5;1"></animate>
|
values="0 50 50;180 50 50;720 50 50" keyTimes="0;0.5;1"></animateTransform>
|
||||||
|
<animate attributeName="stroke-dasharray" repeatCount="indefinite" dur="1s"
|
||||||
|
values="18.84955592153876 169.64600329384882;94.2477796076938 94.24777960769377;18.84955592153876 169.64600329384882"
|
||||||
|
keyTimes="0;0.5;1"></animate>
|
||||||
</circle>
|
</circle>
|
||||||
</svg>)
|
</svg>)
|
||||||
|
|
||||||
|
export const IconRoles = ({size,color}: { size?: string | number,color?:string; }) => (<svg xmlns="http://www.w3.org/2000/svg" style={{
|
||||||
|
margin: 'auto', display: 'block', ...(size ? {fontSize: size} : {}), ...(color ? {color} : {})
|
||||||
|
}} width="1em" height="1em" viewBox="0 0 1024 1024" preserveAspectRatio="xMidYMid">
|
||||||
|
<path
|
||||||
|
d="M559 470.9h137c26.9 0 48.6-22 47-48.8-1.5-24-21.6-43.2-45.9-43.2H560c-26.9 0-48.6 22-47 48.8 1.5 24 21.6 43.2 46 43.2M779.2 553.1H557.3c-24.4 0-44.4 20.7-44.4 46s20 46 44.4 46h221.9c24.4 0 44.4-20.7 44.4-46s-20-46-44.4-46M379 445.8c0-37.9-29-67-67-67-37.9 0-67 29-67 67s29 67 67 67 67-29 67-67M312 512.8c-62.5 0-111.6 42.4-111.6 93.8s223.2 51.3 223.2 0-49.1-93.8-111.6-93.8"
|
||||||
|
fill={'currentColor'}></path>
|
||||||
|
<path
|
||||||
|
d="M966.6 97.8H57.4c-31.6 0-57.4 25.7-57.4 57v714.4c0 31.4 25.8 57 57.4 57h909.3c31.6 0 57.4-25.7 57.4-57V154.8c-0.1-31.4-25.9-57-57.5-57zM932 834.2H92V189.8h840v644.4z"
|
||||||
|
fill={'currentColor'}></path>
|
||||||
|
</svg>)
|
||||||
|
|
||||||
|
@ -117,6 +117,20 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateUser = async (user: Partial<UserProfile>) => {
|
||||||
|
dispatch({
|
||||||
|
action: 'updateUser',
|
||||||
|
payload: {
|
||||||
|
user:{
|
||||||
|
...state.user,
|
||||||
|
...user
|
||||||
|
} as any,
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init().then(console.log)
|
init().then(console.log)
|
||||||
}, [])
|
}, [])
|
||||||
@ -128,7 +142,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
|
|||||||
return (<AuthContext.Provider value={{
|
return (<AuthContext.Provider value={{
|
||||||
...state,
|
...state,
|
||||||
login, logout,
|
login, logout,
|
||||||
mockLogin
|
mockLogin,updateUser
|
||||||
}}>{children}</AuthContext.Provider>)
|
}}>{children}</AuthContext.Provider>)
|
||||||
}
|
}
|
||||||
export default AuthContext
|
export default AuthContext
|
@ -36,9 +36,9 @@ export const I18nSwitcher = () => {
|
|||||||
</Dropdown.Menu>
|
</Dropdown.Menu>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button theme="borderless">
|
<Button theme="borderless" style={{marginRight:3}}>
|
||||||
<Space align={'center'} spacing={2} style={{marginTop:2}}>
|
<Space align={'center'} spacing={2} style={{transform:'translateY(3px)'}}>
|
||||||
<IconLanguage style={{ color: '#666' }} />
|
<IconLanguage style={{ color: '#fff' }} />
|
||||||
{/* {LocaleList.find(s => s.key == locale)?.text}*/}
|
{/* {LocaleList.find(s => s.key == locale)?.text}*/}
|
||||||
</Space>
|
</Space>
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -84,7 +84,7 @@
|
|||||||
},
|
},
|
||||||
"pay": {
|
"pay": {
|
||||||
"amount": "Amount",
|
"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",
|
"charge": "Charge",
|
||||||
"confirm_pay": "CONFIRM PAYMENT",
|
"confirm_pay": "CONFIRM PAYMENT",
|
||||||
"query_pay_status": "Check payment status...",
|
"query_pay_status": "Check payment status...",
|
||||||
|
13
src/main.tsx
13
src/main.tsx
@ -1,22 +1,9 @@
|
|||||||
// import React from "react";
|
// import React from "react";
|
||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import * as Sentry from '@sentry/react';
|
|
||||||
|
|
||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import '@/assets/index.less'
|
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(
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<App/>,
|
<App/>,
|
||||||
// <React.StrictMode></React.StrictMode>,
|
// <React.StrictMode></React.StrictMode>,
|
||||||
|
69
src/pages/bill/external_create.tsx
Normal file
69
src/pages/bill/external_create.tsx
Normal file
@ -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 (<div className={`${styles.container} text-center`}>
|
||||||
|
{state.loading && <div>
|
||||||
|
<div><IconLoading size={70} /></div>
|
||||||
|
<div></div>
|
||||||
|
</div>}
|
||||||
|
{state.error && <div>
|
||||||
|
<h3>{state.error}</h3>
|
||||||
|
</div>}
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
export default ExternalCreate
|
@ -2,7 +2,6 @@ import {Button, Modal, Notification, Popconfirm, Space} from "@douyinfe/semi-ui"
|
|||||||
import {useState} from "react";
|
import {useState} from "react";
|
||||||
import {useRequest} from "ahooks";
|
import {useRequest} from "ahooks";
|
||||||
import {useTranslation} from "react-i18next";
|
import {useTranslation} from "react-i18next";
|
||||||
import * as Sentry from "@sentry/react";
|
|
||||||
|
|
||||||
import {BillList} from "@/components/bill/list.tsx";
|
import {BillList} from "@/components/bill/list.tsx";
|
||||||
import SearchForm from "@/components/bill/search-form.tsx";
|
import SearchForm from "@/components/bill/search-form.tsx";
|
||||||
@ -29,13 +28,7 @@ const BillQuery = () => {
|
|||||||
}), {
|
}), {
|
||||||
refreshDeps: [queryParams],
|
refreshDeps: [queryParams],
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
Sentry.captureMessage('Error: Query bill error', {
|
Notification.error({title: 'Error', content: e.message})
|
||||||
level: 'error',
|
|
||||||
extra: {
|
|
||||||
message: e.message,
|
|
||||||
...queryParams
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
|
@ -12,7 +12,6 @@ import {useSetState} from "ahooks";
|
|||||||
import {BizError} from "@/service/types.ts";
|
import {BizError} from "@/service/types.ts";
|
||||||
import {IconAlertTriangle} from "@douyinfe/semi-icons";
|
import {IconAlertTriangle} from "@douyinfe/semi-icons";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import * as Sentry from '@sentry/react'
|
|
||||||
|
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
@ -36,14 +35,6 @@ export default function Index() {
|
|||||||
setBillInfo(ret)
|
setBillInfo(ret)
|
||||||
//getBillDetail(ret.id).then(setBillInfo);
|
//getBillDetail(ret.id).then(setBillInfo);
|
||||||
}).catch((e: BizError) => {
|
}).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})
|
setState({errorMessage: e.message})
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
setState({loading: false})
|
setState({loading: false})
|
||||||
|
@ -24,7 +24,7 @@ const PayIndex = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const billId = search.get('bill')
|
const billId = search.get('bill')
|
||||||
if (!billId || !/^\d+$/.test(billId)) {
|
if (!billId || !/^\d+$/.test(billId)) {
|
||||||
navigate(`/fail?bill=${billId}`, {replace: true})
|
navigate(`/pay/error?bill=${billId}`, {replace: true})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getBillDetail(Number(billId)).then((bill) => {
|
getBillDetail(Number(billId)).then((bill) => {
|
||||||
@ -34,7 +34,7 @@ const PayIndex = () => {
|
|||||||
}
|
}
|
||||||
setBill(bill)
|
setBill(bill)
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
navigate(`/fail?bill=${billId}&msg=bill_error`, {replace: true})
|
navigate(`/pay/error?bill=${billId}&msg=bill_error`, {replace: true})
|
||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
return (<div className={styles.container}>
|
return (<div className={styles.container}>
|
||||||
|
@ -20,6 +20,7 @@ import Loader from "@/components/loader.tsx";
|
|||||||
import ManualIndex from "@/pages/manual/index.tsx";
|
import ManualIndex from "@/pages/manual/index.tsx";
|
||||||
import BillQuery from "@/pages/bill/query.tsx";
|
import BillQuery from "@/pages/bill/query.tsx";
|
||||||
import BillReconciliation from "@/pages/bill/reconciliation.tsx";
|
import BillReconciliation from "@/pages/bill/reconciliation.tsx";
|
||||||
|
import ExternalCreate from "@/pages/bill/external_create.tsx";
|
||||||
|
|
||||||
|
|
||||||
const routes: RouteObject[] = [
|
const routes: RouteObject[] = [
|
||||||
@ -48,6 +49,10 @@ const routes: RouteObject[] = [
|
|||||||
path: 'pay/:result',
|
path: 'pay/:result',
|
||||||
element: <PayResult/>
|
element: <PayResult/>
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'bill/external_create',
|
||||||
|
element: <ExternalCreate/>
|
||||||
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {Outlet, useLocation} from "react-router-dom";
|
import {Outlet, useLocation} from "react-router-dom";
|
||||||
import React, {useMemo} from "react";
|
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 {useTranslation} from "react-i18next";
|
||||||
|
|
||||||
import AuthGuard from "@/routes/layout/auth-guard.tsx";
|
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 {IconExit, IconUser} from "@douyinfe/semi-icons";
|
||||||
import styled from "@emotion/styled";
|
import styled from "@emotion/styled";
|
||||||
import useConfig from "@/hooks/useConfig.ts";
|
import useConfig from "@/hooks/useConfig.ts";
|
||||||
|
import {IconRoles} from "@/components/icons";
|
||||||
|
|
||||||
const {Header, Content, Sider} = Layout;
|
const {Header, Content, Sider} = Layout;
|
||||||
|
|
||||||
@ -56,8 +57,36 @@ export const HeaderUserAvatar = () => {
|
|||||||
<Avatar color="orange" size="small"><IconUser /></Avatar>
|
<Avatar color="orange" size="small"><IconUser /></Avatar>
|
||||||
</Dropdown>)
|
</Dropdown>)
|
||||||
}
|
}
|
||||||
|
const RoleList = ['root', 'ro', 'fo','staff']
|
||||||
|
const RoleSwitcher = ()=>{
|
||||||
|
const {user, updateUser} = useAuth()
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{AppMode !== 'production' && (<Dropdown
|
||||||
|
clickToHide
|
||||||
|
render={
|
||||||
|
<Dropdown.Menu>
|
||||||
|
{RoleList.map((key) => (
|
||||||
|
<Dropdown.Item
|
||||||
|
active={user?.department == key} key={key}
|
||||||
|
onClick={() => updateUser({department: key})}
|
||||||
|
><span>{key.toUpperCase()}</span></Dropdown.Item>
|
||||||
|
))}
|
||||||
|
</Dropdown.Menu>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button theme="borderless">
|
||||||
|
<Space style={{transform:'translateY(3px)'}}>
|
||||||
|
<IconRoles size={20} color={'white'} />
|
||||||
|
<span style={{color:'white'}}>{user?.department?.toUpperCase()}</span>
|
||||||
|
</Space>
|
||||||
|
</Button>
|
||||||
|
</Dropdown>) }
|
||||||
|
</>)
|
||||||
|
}
|
||||||
export const CommonHeader: React.FC<CommonHeaderProps> = ({children, title, rightExtra}) => {
|
export const CommonHeader: React.FC<CommonHeaderProps> = ({children, title, rightExtra}) => {
|
||||||
const {appName} = useConfig()
|
const {appName} = useConfig()
|
||||||
|
|
||||||
return (<Header style={{position: 'sticky', top: 0, zIndex: 100}}>
|
return (<Header style={{position: 'sticky', top: 0, zIndex: 100}}>
|
||||||
<div>
|
<div>
|
||||||
<Nav mode="horizontal" defaultSelectedKeys={['Home']}
|
<Nav mode="horizontal" defaultSelectedKeys={['Home']}
|
||||||
@ -82,6 +111,7 @@ export const CommonHeader: React.FC<CommonHeaderProps> = ({children, title, righ
|
|||||||
<Nav.Footer>
|
<Nav.Footer>
|
||||||
<Space>
|
<Space>
|
||||||
{rightExtra}
|
{rightExtra}
|
||||||
|
<RoleSwitcher />
|
||||||
<I18nSwitcher/>
|
<I18nSwitcher/>
|
||||||
<HeaderUserAvatar/>
|
<HeaderUserAvatar/>
|
||||||
</Space>
|
</Space>
|
||||||
|
@ -58,3 +58,8 @@ export function confirmBills(bill_ids: number[]) {
|
|||||||
export function updateBillPaymentSuccess(bill_id: number, merchant_ref: string, payment_channel: string) {
|
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)
|
||||||
|
}
|
1
src/types/auth.d.ts
vendored
1
src/types/auth.d.ts
vendored
@ -25,4 +25,5 @@ declare type AuthContextType = {
|
|||||||
logout: () => Promise<void>;
|
logout: () => Promise<void>;
|
||||||
mockLogin: () => Promise<void>;
|
mockLogin: () => Promise<void>;
|
||||||
login: (code:string,state:string) => Promise<void>;
|
login: (code:string,state:string) => Promise<void>;
|
||||||
|
updateUser: (user:Partial<UserProfile>) => Promise<void>;
|
||||||
};
|
};
|
6
src/types/bill.d.ts
vendored
6
src/types/bill.d.ts
vendored
@ -80,4 +80,8 @@ declare type AsiaPayModel = {
|
|||||||
payMethod: string;
|
payMethod: string;
|
||||||
pay_url: string;
|
pay_url: string;
|
||||||
details: BillDetail[]
|
details: BillDetail[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalCreateParamsType = {
|
||||||
|
[key: string]: string | null | BillDetail[];
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ export default defineConfig(({mode}) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
base: mode == 'test' ? './' : '/',
|
base: mode == 'for-wm' ? './' : '/',
|
||||||
define: {
|
define: {
|
||||||
AppConfig: JSON.stringify({
|
AppConfig: JSON.stringify({
|
||||||
SITE_URL: process.env.APP_SITE_URL || null,
|
SITE_URL: process.env.APP_SITE_URL || null,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user