This commit is contained in:
xx
2026-02-28 10:05:36 +08:00
parent debed766d5
commit e37d605e38
12 changed files with 970 additions and 887 deletions

1697
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,10 @@ const API_CONFIG = {
// 内网地址HTTP // 内网地址HTTP
INTERNAL_URL: 'http://10.1.5.118:8077/platform-api', INTERNAL_URL: 'http://10.1.5.118:8077/platform-api',
// 外网地址HTTPS // 外网地址HTTPS
EXTERNAL_URL: 'https://apihis.circleharmonyhospital.cn:8982/platform-api', EXTERNAL_URL: 'http://apihis.circleharmonyhospital.cn:8982/platform-api',
// 默认使用外网地址,可根据环境变量切换
// 开发环境使用内网,生产环境使用外网
BASE_URL: import.meta.env.NODE_ENV === 'development' BASE_URL: import.meta.env.NODE_ENV === 'development'
? 'http://10.1.5.118:8077/platform-api' ? 'http://10.1.5.118:8077/platform-api'
: '/platform-api', : 'http://apihis.circleharmonyhospital.cn:8982/platform-api',
// 请求超时时间120秒
TIMEOUT: 120000, TIMEOUT: 120000,
}; };
@@ -63,7 +60,10 @@ request.interceptors.response.use(
localStorage.removeItem('operatorName'); localStorage.removeItem('operatorName');
localStorage.removeItem('operatorUsername'); localStorage.removeItem('operatorUsername');
// 跳转到首页并添加登录参数 // 跳转到首页并添加登录参数
window.location.href = '/home?login=true'; const baseUrl = import.meta.env.NODE_ENV === 'development' ? '/' : '/tijian-zongkong/';
window.location.href = `${baseUrl}#/home?login=true`;
// navigate('/home?login=true');
// return;
} }
break; break;
case 403: case 403:

View File

@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState, useRef, useCallback } from 'react'; import { useEffect, useMemo, useState, useRef, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import type { ExamClient } from '../../data/mockData'; import type { ExamClient } from '../../data/mockData';
import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf, getCustomSettlementApproveStatus, customSettlementApply, customSettlementApplyCancel } from '../../api'; import { searchPhysicalExamAddItem, getAddItemCustomerInfo, getChannelCompanyList, createNativePaymentQrcode, checkNativePaymentStatus, getAddItemBillPdf, getCustomSettlementApproveStatus, customSettlementApply, customSettlementApplyCancel } from '../../api';
@@ -30,6 +31,7 @@ interface ExamAddonPanelProps {
} }
export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => { export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
const navigate = useNavigate();
const [addonList, setAddonList] = useState<AddonItem[]>([]); const [addonList, setAddonList] = useState<AddonItem[]>([]);
const allAddons = useMemo(() => addonList, [addonList]); const allAddons = useMemo(() => addonList, [addonList]);
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
@@ -589,7 +591,8 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
// 等待计时器:当审核中时开始计时 // 等待计时器:当审核中时开始计时
useEffect(() => { useEffect(() => {
if (customSettlementStatus?.apply_status === 1) { const isWaiting = customSettlementStatus?.apply_status === 1 || (paymentLoading && !showQrcodeModal);
if (isWaiting) {
// 开始计时 // 开始计时
setWaitingSeconds(0); setWaitingSeconds(0);
waitingTimerRef.current = window.setInterval(() => { waitingTimerRef.current = window.setInterval(() => {
@@ -610,7 +613,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
waitingTimerRef.current = null; waitingTimerRef.current = null;
} }
}; };
}, [customSettlementStatus?.apply_status]); }, [customSettlementStatus?.apply_status, paymentLoading, showQrcodeModal]);
// 提交自定义结算申请 // 提交自定义结算申请
const handleSubmitCustomSettlement = async () => { const handleSubmitCustomSettlement = async () => {
@@ -678,7 +681,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
const apply_user = localStorage.getItem('operatorName'); const apply_user = localStorage.getItem('operatorName');
if (!apply_user) { if (!apply_user) {
alert('请先登录'); alert('请先登录');
window.location.href = '/home'; navigate('/home');
return; return;
} }
const res = await customSettlementApply({ const res = await customSettlementApply({
@@ -813,6 +816,8 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
clearInterval(pollingTimerRef.current); clearInterval(pollingTimerRef.current);
} }
setPaymentMessage('等待支付确认...');
pollingTimerRef.current = window.setInterval(async () => { pollingTimerRef.current = window.setInterval(async () => {
try { try {
const res = await checkNativePaymentStatus({ const res = await checkNativePaymentStatus({
@@ -844,10 +849,14 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => {
if (success) { if (success) {
setPaymentMessage('支付成功,加项单已生成,正在跳转签署...'); setPaymentMessage('支付成功,加项单已生成,正在跳转签署...');
// 跳转到签署页面 // 延迟跳转,让用户看到消息
onGoToSign?.(); setTimeout(() => {
onGoToSign?.();
setPaymentLoading(false);
}, 1000);
} else { } else {
setPaymentMessage('支付成功,但加项单生成失败'); setPaymentMessage('支付成功,但加项单生成失败');
setPaymentLoading(false);
} }
setTimeout(() => { setTimeout(() => {
setPaymentMessage(null); setPaymentMessage(null);
@@ -859,6 +868,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
clearInterval(pollingTimerRef.current); clearInterval(pollingTimerRef.current);
pollingTimerRef.current = null; pollingTimerRef.current = null;
} }
setPaymentLoading(false);
setPaymentMessage('支付失败'); setPaymentMessage('支付失败');
setTimeout(() => { setTimeout(() => {
setPaymentMessage(null); setPaymentMessage(null);
@@ -888,7 +898,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
} }
setPaymentLoading(true); setPaymentLoading(true);
setPaymentMessage(null); setPaymentMessage('正在发起支付...');
try { try {
const selectedItems = allAddons.filter((item) => selectedIds.has(item.id || item.name)); const selectedItems = allAddons.filter((item) => selectedIds.has(item.id || item.name));
@@ -985,6 +995,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
); );
} else { } else {
setPaymentMessage(res.Message || '生成支付二维码失败'); setPaymentMessage(res.Message || '生成支付二维码失败');
setPaymentLoading(false);
} }
} else { } else {
// 挂账模式:直接查询 // 挂账模式:直接查询
@@ -1025,9 +1036,13 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => { fetchAddItemBillPdf(physical_exam_id, combinationItemCodes).then((success) => {
if (success) { if (success) {
setPaymentMessage('挂账成功,加项单已生成,正在跳转签署...'); setPaymentMessage('挂账成功,加项单已生成,正在跳转签署...');
onGoToSign?.(); setTimeout(() => {
onGoToSign?.();
setPaymentLoading(false);
}, 1000);
} else { } else {
setPaymentMessage('挂账成功,但加项单生成失败'); setPaymentMessage('挂账成功,但加项单生成失败');
setPaymentLoading(false);
} }
setTimeout(() => { setTimeout(() => {
setPaymentMessage(null); setPaymentMessage(null);
@@ -1035,12 +1050,14 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
}); });
} else { } else {
setPaymentMessage('挂账失败'); setPaymentMessage('挂账失败');
setPaymentLoading(false);
setTimeout(() => { setTimeout(() => {
setPaymentMessage(null); setPaymentMessage(null);
}, 3000); }, 3000);
} }
} else { } else {
setPaymentMessage(res.Message || '挂账失败'); setPaymentMessage(res.Message || '挂账失败');
setPaymentLoading(false);
} }
} }
} catch (err) { } catch (err) {
@@ -1392,6 +1409,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
clearInterval(pollingTimerRef.current); clearInterval(pollingTimerRef.current);
pollingTimerRef.current = null; pollingTimerRef.current = null;
} }
setPaymentLoading(false);
setShowQrcodeModal(false); setShowQrcodeModal(false);
setQrcodeUrl(null); setQrcodeUrl(null);
}} }}
@@ -1435,15 +1453,15 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
</button> </button>
</div> </div>
<div className='px-4 py-6 space-y-4'> <div className='px-4 py-1 space-y-4'>
{/* 选中项目信息 */} {/* 选中项目信息 */}
<div className='text-sm text-gray-700'> <div className='text-sm text-gray-700'>
<div className='font-medium mb-2'> ({selectedItems.length}):</div> <div className='font-medium mb-2'> ({selectedItems.length}):</div>
<div className='max-h-32 overflow-y-auto space-y-1'> <div className='max-h-[44px] overflow-y-auto space-y-0.5 pr-1'>
{selectedItems.map((item, idx) => ( {selectedItems.map((item, idx) => (
<div key={idx} className='text-xs text-gray-600 flex justify-between'> <div key={idx} className='text-[11px] text-gray-500 flex justify-between'>
<span>{item.name}</span> <span className='truncate mr-2'>{item.name}</span>
<span>¥{parseFloat(item.currentPrice || item.originalPrice || '0').toFixed(2)}</span> <span className='flex-shrink-0 font-mono'>¥{parseFloat(item.currentPrice || item.originalPrice || '0').toFixed(2)}</span>
</div> </div>
))} ))}
</div> </div>
@@ -1577,7 +1595,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
<div className='flex gap-3 pt-2'> <div className='flex gap-3 pt-2'>
<Button <Button
onClick={() => setShowCustomSettlementModal(false)} onClick={() => setShowCustomSettlementModal(false)}
className='flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700' className='flex-1 px-4 py-2 bg-gray-100 hover:bg-gray-200 text-gray-700 text-center items-center justify-center'
> >
{isApprovedOrRejected ? '关闭' : '取消'} {isApprovedOrRejected ? '关闭' : '取消'}
</Button> </Button>
@@ -1586,7 +1604,7 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
onClick={handleSubmitCustomSettlement} onClick={handleSubmitCustomSettlement}
disabled={customSettlementLoading || !customApplyReason.trim()} disabled={customSettlementLoading || !customApplyReason.trim()}
className={cls( className={cls(
'flex-1 px-4 py-2 text-white font-medium', 'flex-1 px-4 py-2 text-white font-medium text-center items-center justify-center',
customSettlementLoading || !customApplyReason.trim() customSettlementLoading || !customApplyReason.trim()
? 'bg-gray-400 cursor-not-allowed' ? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700' : 'bg-blue-600 hover:bg-blue-700'
@@ -1602,25 +1620,33 @@ export const ExamAddonPanel = ({ client, onGoToSign }: ExamAddonPanelProps) => {
) )
} }
{/* 审核中全屏遮罩 */} {/* 审核中/支付中全屏遮罩 */}
{ {
customSettlementStatus?.apply_status === 1 && ( (customSettlementStatus?.apply_status === 1 || (paymentLoading && !showQrcodeModal)) && (
<div className='fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50'> <div className='fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50'>
<div className='bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl'> <div className='bg-white rounded-2xl p-6 max-w-md w-full mx-4 shadow-xl'>
<div className='text-center mb-6'> <div className='text-center mb-6'>
<div className='text-lg font-semibold text-blue-600 mb-2'>...</div> <div className='text-lg font-semibold text-blue-600 mb-2'>
<div className='text-sm text-gray-500 mb-1'></div> {customSettlementStatus?.apply_status === 1 ? '审核中...' : '支付处理中...'}
</div>
<div className='text-sm text-gray-500 mb-1'>
{customSettlementStatus?.apply_status === 1
? '正在等待审核结果,请稍候'
: (paymentMessage || '正在处理,请稍候...')}
</div>
<div className='text-xs text-gray-400'> {waitingSeconds} </div> <div className='text-xs text-gray-400'> {waitingSeconds} </div>
</div> </div>
<div className='flex justify-center'> {customSettlementStatus?.apply_status === 1 && (
<Button <div className='flex justify-center'>
onClick={handleCancelCustomSettlement} <Button
disabled={customSettlementLoading} onClick={handleCancelCustomSettlement}
className='px-6 py-2 bg-gray-500 hover:bg-gray-600 text-white disabled:opacity-50' disabled={customSettlementLoading}
> className='px-6 py-2 bg-gray-500 hover:bg-gray-600 text-white disabled:opacity-50'
{customSettlementLoading ? '取消中...' : '取消申请'} >
</Button> {customSettlementLoading ? '取消中...' : '取消申请'}
</div> </Button>
</div>
)}
</div> </div>
</div> </div>
) )

View File

@@ -117,66 +117,66 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
{infoLoading && ( {infoLoading && (
<div className='mb-3 text-xs text-gray-500'>...</div> <div className='mb-3 text-xs text-gray-500'>...</div>
)} )}
<div className='grid grid-cols-2 gap-3 mb-3'> <div className='grid grid-cols-2 gap-x-3 gap-y-1.5 mb-2'>
<div> <div>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='请输入收件人姓名' placeholder='请输入收件人姓名'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={addressContact} value={addressContact}
onChange={(e) => setAddressContact(e.target.value)} onChange={(e) => setAddressContact(e.target.value)}
/> />
</div> </div>
<div> <div>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='用于快递联系' placeholder='用于快递联系'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={addressMobile} value={addressMobile}
onChange={(e) => setAddressMobile(e.target.value)} onChange={(e) => setAddressMobile(e.target.value)}
/> />
</div> </div>
<div> <div>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='例如:上海市' placeholder='例如:上海市'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={provinceName} value={provinceName}
onChange={(e) => setProvinceName(e.target.value)} onChange={(e) => setProvinceName(e.target.value)}
/> />
</div> </div>
<div> <div>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='例如:上海市' placeholder='例如:上海市'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={cityName} value={cityName}
onChange={(e) => setCityName(e.target.value)} onChange={(e) => setCityName(e.target.value)}
/> />
</div> </div>
<div> <div>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='例如:浦东新区' placeholder='例如:浦东新区'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={countryName} value={countryName}
onChange={(e) => setCountryName(e.target.value)} onChange={(e) => setCountryName(e.target.value)}
/> />
</div> </div>
<div className='col-span-2'> <div className='col-span-2'>
<span className='text-[11px] text-gray-500'></span>
<Input <Input
placeholder='请输入详细寄送地址' placeholder='请输入详细寄送地址'
className='mt-1' className='mt-0.5 h-8 text-xs'
value={addressContent} value={addressContent}
onChange={(e) => setAddressContent(e.target.value)} onChange={(e) => setAddressContent(e.target.value)}
/> />
</div> </div>
</div> </div>
<div className='space-y-2'> <div className='space-y-1'>
<div></div> <div className='text-[11px] text-gray-500'></div>
<textarea <textarea
className='w-full rounded-2xl border px-3 py-2 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[80px]' className='w-full rounded-xl border px-3 py-1.5 text-xs outline-none focus:ring-2 focus:ring-gray-200 min-h-[60px] resize-none'
placeholder='如需多份报告、加急寄送等,请在此备注' placeholder='如需多份报告、加急寄送等,请在此备注'
value={addressRemark} value={addressRemark}
onChange={(e) => setAddressRemark(e.target.value)} onChange={(e) => setAddressRemark(e.target.value)}
@@ -190,12 +190,12 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
{saveMessage} {saveMessage}
</div> </div>
)} )}
<div className='mt-4 flex items-center justify-between text-[11px] text-gray-500'> <div className='mt-2 flex items-center justify-between text-[10px] text-gray-500'>
<div> <div className='truncate mr-2'>
<span className='font-medium text-gray-800'>{client.name}</span>{client.id} <span className='font-medium text-gray-800'>{client.name}</span> ({client.id})
</div> </div>
<Button <Button
className='px-4 py-1.5 text-xs' className='px-4 py-1.5 h-8 text-xs flex-shrink-0'
onClick={async () => { onClick={async () => {
const physical_exam_id = Number(client.id); const physical_exam_id = Number(client.id);
if (!physical_exam_id) { if (!physical_exam_id) {

View File

@@ -127,7 +127,7 @@ export const ExamSection = ({
const hasMore = displayCount < filteredClients.length; const hasMore = displayCount < filteredClients.length;
return ( return (
<div className='space-y-4'> <div className='space-y-4 h-full overflow-y-auto p-4 pb-10'>
<Card> <Card>
<CardHeader></CardHeader> <CardHeader></CardHeader>
<CardContent> <CardContent>

View File

@@ -418,7 +418,6 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
}, [examId, optionalConfirmed]); }, [examId, optionalConfirmed]);
const handlePickFile = () => { const handlePickFile = () => {
// 有可选项目但尚未确认时,禁止拍照并提示先确认项目
if (optionalItemList.length > 0 && !optionalConfirmed) { if (optionalItemList.length > 0 && !optionalConfirmed) {
setMessage('请先确认体检项目'); setMessage('请先确认体检项目');
setShowOptionalConfirmTip(true); setShowOptionalConfirmTip(true);
@@ -465,24 +464,25 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]; const file = e.target.files?.[0];
if (!file) return; if (!file) {
return;
}
setMessage(null); setMessage(null);
try { try {
const jpgFile = await convertToJpg(file); const jpgFile = await convertToJpg(file);
setIdCardFile(jpgFile); setIdCardFile(jpgFile);
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (event) => { reader.onload = (event) => {
setPreviewImage(event.target?.result as string); setPreviewImage(event.target?.result as string);
}; };
reader.readAsDataURL(jpgFile); reader.readAsDataURL(jpgFile);
} catch (err) { } catch (err) {
alert('err: ' + String(err));
console.error('图片转换失败', err); console.error('图片转换失败', err);
setMessage('图片处理失败,请重试'); setMessage('图片处理失败,请重试');
} }
e.target.value = ''; e.target.value = '';
}; };

View File

@@ -180,7 +180,7 @@ export const HomeSection = () => {
</Card> </Card>
)} )}
<div className='grid grid-cols-2 gap-4'> <div className='grid grid-cols-2 gap-4 pb-4'>
<Card> <Card>
<CardHeader>B1 </CardHeader> <CardHeader>B1 </CardHeader>
<CardContent> <CardContent>

View File

@@ -10,7 +10,7 @@ interface TopBarProps {
onLogout?: () => void; onLogout?: () => void;
} }
export const TopBar = ({ enableSearch = true, operatorName, onLoginClick, onLogout }: TopBarProps) => { export const TopBar = ({ operatorName, onLoginClick, onLogout }: TopBarProps) => {
const [showMenu, setShowMenu] = useState(false); const [showMenu, setShowMenu] = useState(false);
const [displayName, setDisplayName] = useState(operatorName || ''); const [displayName, setDisplayName] = useState(operatorName || '');

View File

@@ -1,4 +1,4 @@
import { Card, CardContent, CardHeader } from '../ui'; import { Card, CardContent } from '../ui';
export const SupportSection = () => ( export const SupportSection = () => (
<Card> <Card>

View File

@@ -1,4 +1,5 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import type { ExamClient, ExamModalTab } from '../data/mockData'; import type { ExamClient, ExamModalTab } from '../data/mockData';
import { EXAM_TAGS } from '../data/mockData'; import { EXAM_TAGS } from '../data/mockData';
@@ -17,13 +18,14 @@ export const ExamPage = () => {
const [examFilterTags, setExamFilterTags] = useState<Set<(typeof EXAM_TAGS)[number]>>(new Set(['全部'])); const [examFilterTags, setExamFilterTags] = useState<Set<(typeof EXAM_TAGS)[number]>>(new Set(['全部']));
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [refreshSeq, setRefreshSeq] = useState(0); const [refreshSeq, setRefreshSeq] = useState(0);
const navigate = useNavigate();
// 进入页面时获取用户菜单权限 // 进入页面时获取用户菜单权限
useEffect(() => { useEffect(() => {
const token = localStorage.getItem('accessToken'); const token = localStorage.getItem('accessToken');
if (!token) { if (!token) {
// 跳转登录 // 跳转登录
window.location.href = '/home'; navigate('/home');
return; return;
} }
getUserOwnedMenus({ app_id: APP_ID }) getUserOwnedMenus({ app_id: APP_ID })

View File

@@ -1,4 +1,4 @@
import { createBrowserRouter, Navigate } from 'react-router-dom'; import { createHashRouter, Navigate } from 'react-router-dom';
import { MainLayout } from './layouts/MainLayout'; import { MainLayout } from './layouts/MainLayout';
import { HomePage } from './pages/HomePage'; import { HomePage } from './pages/HomePage';
@@ -6,7 +6,7 @@ import { ExamPage } from './pages/ExamPage';
import { BookingPage } from './pages/BookingPage'; import { BookingPage } from './pages/BookingPage';
import { SupportPage } from './pages/SupportPage'; import { SupportPage } from './pages/SupportPage';
export const router = createBrowserRouter([ export const router = createHashRouter([
{ {
path: '/', path: '/',
element: <MainLayout />, element: <MainLayout />,

View File

@@ -3,8 +3,12 @@ import react from '@vitejs/plugin-react-swc'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base: "./",
plugins: [react()], plugins: [react()],
server: { server: {
allowedHosts: ['ipad.shenynet.com'], allowedHosts: ['ipad.shenynet.com'],
}, },
resolve: {
dedupe: ['react', 'react-dom']
},
}) })