Compare commits
13 Commits
362b13ac43
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e420897878 | ||
|
|
d451cb1172 | ||
|
|
e37d605e38 | ||
|
|
debed766d5 | ||
|
|
abc0a6051d | ||
|
|
513e113ea9 | ||
|
|
07c74c956e | ||
|
|
29f6a6e696 | ||
|
|
e2158286be | ||
|
|
db6a8bc97f | ||
|
|
41ce02512a | ||
|
|
d09bfc4ec6 | ||
|
|
f75e19cf85 |
@@ -1,5 +1,7 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
`cp node_modules/pdfjs-dist/build/pdf.worker.min.mjs public/pdf.worker.min.mjs`
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
2540
package-lock.json
generated
2540
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,14 +4,14 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "export NODE_ENV=development && vite --host 0.0.0.0",
|
||||
"dev": "export NODE_ENV=development && vite --host 0.0.0.0 --port 5777",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.2",
|
||||
"pdfjs-dist": "^5.4.449",
|
||||
"pdfjs-dist": "^4.4.168",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-router-dom": "^7.9.6"
|
||||
|
||||
21
public/pdf.worker.min.js
vendored
Normal file
21
public/pdf.worker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,7 +4,11 @@ import { RouterProvider } from 'react-router-dom';
|
||||
import { router } from './router';
|
||||
|
||||
function App() {
|
||||
return <RouterProvider router={router} />;
|
||||
return (
|
||||
<div className='h-screen max-h-screen overflow-hidden'>
|
||||
<RouterProvider router={router} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -6,13 +6,10 @@ const API_CONFIG = {
|
||||
// 内网地址(HTTP)
|
||||
INTERNAL_URL: 'http://10.1.5.118:8077/platform-api',
|
||||
// 外网地址(HTTPS)
|
||||
EXTERNAL_URL: 'https://apihis.circleharmonyhospital.cn:8982/platform-api',
|
||||
// 默认使用外网地址,可根据环境变量切换
|
||||
// 开发环境使用内网,生产环境使用外网
|
||||
BASE_URL: import.meta.env.NODE_ENV === 'development'
|
||||
? 'http://10.1.5.118:8077/platform-api'
|
||||
: 'http://apihis.circleharmonyhospital.cn:8982/platform-api',
|
||||
// 请求超时时间(120秒)
|
||||
EXTERNAL_URL: 'http://apihis.circleharmonyhospital.cn:8982/platform-api',
|
||||
BASE_URL: import.meta.env.MODE === 'development'
|
||||
? '/platform-api'
|
||||
: '/platform-api',
|
||||
TIMEOUT: 120000,
|
||||
};
|
||||
|
||||
@@ -63,7 +60,10 @@ request.interceptors.response.use(
|
||||
localStorage.removeItem('operatorName');
|
||||
localStorage.removeItem('operatorUsername');
|
||||
// 跳转到首页并添加登录参数
|
||||
window.location.href = '/home?login=true';
|
||||
const baseUrl = import.meta.env.MODE === 'development' ? '/' : '/tijian-zongkong/';
|
||||
window.location.href = `${baseUrl}#/home?login=true`;
|
||||
// navigate('/home?login=true');
|
||||
// return;
|
||||
}
|
||||
break;
|
||||
case 403:
|
||||
|
||||
@@ -548,6 +548,8 @@ export interface InputPhysicalExamAddItem {
|
||||
scrm_account_name?: string | null;
|
||||
/** 项目名称(默认空值,传入项目名称过滤数据) */
|
||||
item_name?: string;
|
||||
/** 折扣率 */
|
||||
discount_rate?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -720,11 +722,11 @@ export type PhysicalExamQrcodeCreateResponse = CommonActionResult<string>;
|
||||
*/
|
||||
export interface InputAddItemCombinationInfo {
|
||||
/** 体检组合项目代码 */
|
||||
combination_item_code: string;
|
||||
combination_item_code?: string | null;
|
||||
/** 体检组合项目价格 */
|
||||
combination_item_price: number;
|
||||
combination_item_price?: number | null;
|
||||
/** 折扣比例 */
|
||||
discount_rate: number;
|
||||
discount_rate?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -741,6 +743,8 @@ export interface InputOrderPaymentInfo {
|
||||
pay_type: number;
|
||||
/** 挂账公司ID(挂账公司传对应的ID,其他传0) */
|
||||
company_id: number;
|
||||
/** 订单总金额 */
|
||||
orderAmount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1396,6 +1400,15 @@ export interface InputCustomSettlementApplyApprove {
|
||||
add_item_id?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 体检加项自定义结算申请-加项组合项
|
||||
*/
|
||||
export interface OutputCustomSettlementApplyApproveItem {
|
||||
combination_item_code?: string | null;
|
||||
combination_item_price?: number | null;
|
||||
discount_rate?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 体检加项自定义结算申请状态编辑出参
|
||||
*/
|
||||
@@ -1408,6 +1421,12 @@ export interface OutputCustomSettlementApplyApprove {
|
||||
apply_status_name?: string | null;
|
||||
/** 最终结算金额 */
|
||||
final_settlement_price?: number | null;
|
||||
/** 申请理由 */
|
||||
apply_reason?: string;
|
||||
/** 折扣 */
|
||||
discount_ratio?: number | null;
|
||||
/** 加项组合列表 */
|
||||
listAddItemCombination?: OutputCustomSettlementApplyApproveItem[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1427,6 +1446,8 @@ export interface InputAddItemCustomSettlementDetail {
|
||||
original_price: number;
|
||||
/** 结算金额 */
|
||||
settlement_price: number;
|
||||
/** 折扣率 */
|
||||
discount_ratio?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1447,6 +1468,8 @@ export interface InputCustomSettlementApply {
|
||||
final_settlement_price?: number;
|
||||
/** 申请理由 */
|
||||
apply_reason?: string;
|
||||
/** 申请人 */
|
||||
apply_user: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1470,6 +1493,8 @@ export interface InputCustomSettlementApplyCancel {
|
||||
physical_exam_id: number;
|
||||
/** 体检加项组合ID(多个逗号分隔,例如:123,456) */
|
||||
add_item_id: string;
|
||||
/** 申请人 */
|
||||
cancel_user: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
|
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
21
src/assets/pdf.worker.min.mjs
Normal file
21
src/assets/pdf.worker.min.mjs
Normal file
File diff suppressed because one or more lines are too long
BIN
src/assets/sign.png
Normal file
BIN
src/assets/sign.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
File diff suppressed because it is too large
Load Diff
@@ -113,70 +113,70 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
|
||||
</div>
|
||||
|
||||
{viewMode === 'form' ? (
|
||||
<>
|
||||
<div className='overflow-y-auto overflow-x-hidden max-h-[clamp(260px,calc(100vh-520px),560px)] pr-1'>
|
||||
{infoLoading && (
|
||||
<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>
|
||||
收件人姓名
|
||||
<span className='text-[11px] text-gray-500'>收件人姓名</span>
|
||||
<Input
|
||||
placeholder='请输入收件人姓名'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={addressContact}
|
||||
onChange={(e) => setAddressContact(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
联系电话
|
||||
<span className='text-[11px] text-gray-500'>联系电话</span>
|
||||
<Input
|
||||
placeholder='用于快递联系'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={addressMobile}
|
||||
onChange={(e) => setAddressMobile(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
省份
|
||||
<span className='text-[11px] text-gray-500'>省份</span>
|
||||
<Input
|
||||
placeholder='例如:上海市'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={provinceName}
|
||||
onChange={(e) => setProvinceName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
城市
|
||||
<span className='text-[11px] text-gray-500'>城市</span>
|
||||
<Input
|
||||
placeholder='例如:上海市'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={cityName}
|
||||
onChange={(e) => setCityName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
区县
|
||||
<span className='text-[11px] text-gray-500'>区县</span>
|
||||
<Input
|
||||
placeholder='例如:浦东新区'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={countryName}
|
||||
onChange={(e) => setCountryName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className='col-span-2'>
|
||||
详细地址
|
||||
<span className='text-[11px] text-gray-500'>详细地址</span>
|
||||
<Input
|
||||
placeholder='请输入详细寄送地址'
|
||||
className='mt-1'
|
||||
className='mt-0.5 h-8 text-xs'
|
||||
value={addressContent}
|
||||
onChange={(e) => setAddressContent(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='space-y-2'>
|
||||
<div>备注说明</div>
|
||||
<div className='space-y-1'>
|
||||
<div className='text-[11px] text-gray-500'>备注说明</div>
|
||||
<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='如需多份报告、加急寄送等,请在此备注'
|
||||
value={addressRemark}
|
||||
onChange={(e) => setAddressRemark(e.target.value)}
|
||||
@@ -190,12 +190,12 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
|
||||
{saveMessage}
|
||||
</div>
|
||||
)}
|
||||
<div className='mt-4 flex items-center justify-between text-[11px] text-gray-500'>
|
||||
<div>
|
||||
当前客户:<span className='font-medium text-gray-800'>{client.name}</span>(体检号:{client.id})
|
||||
<div className='mt-2 flex items-center justify-between text-[10px] text-gray-500'>
|
||||
<div className='truncate mr-2'>
|
||||
当前客户:<span className='font-medium text-gray-800'>{client.name}</span> ({client.id})
|
||||
</div>
|
||||
<Button
|
||||
className='px-4 py-1.5 text-xs'
|
||||
className='px-4 py-1.5 h-8 text-xs flex-shrink-0'
|
||||
onClick={async () => {
|
||||
const physical_exam_id = Number(client.id);
|
||||
if (!physical_exam_id) {
|
||||
@@ -261,7 +261,7 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
|
||||
{saveLoading ? '保存中...' : '保存寄送信息'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<div className='flex flex-col items-center justify-center py-8'>
|
||||
{qrcodeLoading ? (
|
||||
@@ -271,7 +271,7 @@ export const ExamDeliveryPanel = ({ client }: { client: ExamClient }) => {
|
||||
<img
|
||||
src={qrcodeUrl.startsWith('data:') ? qrcodeUrl : `data:image/png;base64,${qrcodeUrl}`}
|
||||
alt='报告寄送登记二维码'
|
||||
className='max-w-full max-h-[400px] object-contain'
|
||||
className='max-w-full max-h-[clamp(240px,calc(100vh-520px),400px)] object-contain'
|
||||
/>
|
||||
<div className='text-xs text-gray-500'>请扫描二维码进行报告寄送登记</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ interface ExamDetailPanelProps {
|
||||
addItemInfoList: CustomerExamAddItem[] | null;
|
||||
progressList: PhysicalExamProgressItem[] | null;
|
||||
loading: boolean;
|
||||
onCustomerUpdated?: () => void;
|
||||
}
|
||||
|
||||
const getMaritalCodeFromText = (text: string): number => {
|
||||
@@ -33,6 +34,7 @@ export const ExamDetailPanel = ({
|
||||
addItemInfoList,
|
||||
progressList,
|
||||
loading,
|
||||
onCustomerUpdated,
|
||||
}: ExamDetailPanelProps) => {
|
||||
const basePhone = customerInfo?.phone || (client['mobile' as keyof ExamClient] as string | undefined) || '';
|
||||
const baseMaritalText =
|
||||
@@ -108,6 +110,7 @@ export const ExamDetailPanel = ({
|
||||
if (res.Status === 200) {
|
||||
setEditMessage('保存成功');
|
||||
setPhoneEditing(false);
|
||||
onCustomerUpdated?.();
|
||||
setTimeout(() => setEditMessage(null), 2000);
|
||||
} else {
|
||||
setEditMessage(res.Message || '保存失败');
|
||||
@@ -140,6 +143,7 @@ export const ExamDetailPanel = ({
|
||||
if (res.Status === 200) {
|
||||
setEditMessage('保存成功');
|
||||
setMaritalEditing(false);
|
||||
onCustomerUpdated?.();
|
||||
setTimeout(() => setEditMessage(null), 2000);
|
||||
} else {
|
||||
setEditMessage(res.Message || '保存失败');
|
||||
|
||||
@@ -76,6 +76,16 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
.finally(() => setDetailLoading(false));
|
||||
}, [client.id]);
|
||||
|
||||
const refetchDetail = () => {
|
||||
const physical_exam_id = Number(client.id);
|
||||
if (!physical_exam_id) return;
|
||||
getCustomerDetail({ physical_exam_id }).then((detailRes) => {
|
||||
setCustomerInfo(detailRes.Data?.customerInfo ?? null);
|
||||
setAppointmentInfo(detailRes.Data?.appointmentInfo ?? null);
|
||||
setAddItemInfoList(detailRes.Data?.addItemInfoList ?? null);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className='fixed inset-0 z-40 flex items-center justify-center bg-black/50'
|
||||
@@ -153,6 +163,7 @@ export const ExamModal = ({ client, tab, onTabChange, onClose }: ExamModalProps)
|
||||
addItemInfoList={addItemInfoList}
|
||||
progressList={progressList}
|
||||
loading={detailLoading}
|
||||
onCustomerUpdated={refetchDetail}
|
||||
/>
|
||||
)}
|
||||
{tab === 'sign' && <ExamSignPanel examId={Number(client.id)} onBusyChange={setSignBusy} />}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
||||
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
|
||||
|
||||
import { getDaojiandanPdf as getDaojiandanPdfApi, submitDaojiandanSign, editDaojiandanPrintStatus } from '../../api';
|
||||
import type { ExamClient } from '../../data/mockData';
|
||||
import type { SignaturePadHandle } from '../ui';
|
||||
import { Button, SignaturePad } from '../ui';
|
||||
|
||||
// Polyfill for Promise.withResolvers
|
||||
if (typeof (Promise as any).withResolvers === 'undefined') {
|
||||
(Promise as any).withResolvers = function <T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
@@ -20,8 +18,14 @@ if (typeof (Promise as any).withResolvers === 'undefined') {
|
||||
};
|
||||
}
|
||||
|
||||
// 配置 PDF.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
|
||||
if (typeof (Promise as any).try === 'undefined') {
|
||||
(Promise as any).try = function (fn: () => any) {
|
||||
return new Promise((resolve) => resolve(fn()));
|
||||
};
|
||||
}
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = import.meta.env.MODE === 'development' ? '/pdf.worker.min.js' : '/tijian-zongkong/pdf.worker.min.js';
|
||||
|
||||
|
||||
export const ExamPrintPanel = ({ client }: { client: ExamClient }) => {
|
||||
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
|
||||
|
||||
@@ -127,7 +127,7 @@ export const ExamSection = ({
|
||||
const hasMore = displayCount < filteredClients.length;
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
<div className='space-y-4 h-full overflow-y-auto p-4 pb-10'>
|
||||
<Card>
|
||||
<CardHeader>今日体检进度</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import * as pdfjsLib from 'pdfjs-dist';
|
||||
import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.min.mjs?url';
|
||||
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
|
||||
|
||||
import type { OutputTongyishuFileInfo, OutputTijianPdfFileInfo, OutputPhysicalExamItemInfo } from '../../api';
|
||||
import {
|
||||
@@ -20,7 +19,6 @@ import {
|
||||
import type { SignaturePadHandle } from '../ui';
|
||||
import { Button, SignaturePad } from '../ui';
|
||||
|
||||
// Polyfill for Promise.withResolvers
|
||||
if (typeof (Promise as any).withResolvers === 'undefined') {
|
||||
(Promise as any).withResolvers = function <T>() {
|
||||
let resolve!: (value: T | PromiseLike<T>) => void;
|
||||
@@ -33,8 +31,13 @@ if (typeof (Promise as any).withResolvers === 'undefined') {
|
||||
};
|
||||
}
|
||||
|
||||
// 配置 PDF.js worker
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
|
||||
if (typeof (Promise as any).try === 'undefined') {
|
||||
(Promise as any).try = function (fn: () => any) {
|
||||
return new Promise((resolve) => resolve(fn()));
|
||||
};
|
||||
}
|
||||
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = import.meta.env.MODE === 'development' ? '/pdf.worker.min.js' : '/tijian-zongkong/pdf.worker.min.js';
|
||||
|
||||
interface ExamSignPanelProps {
|
||||
examId?: number;
|
||||
@@ -418,7 +421,6 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
||||
}, [examId, optionalConfirmed]);
|
||||
|
||||
const handlePickFile = () => {
|
||||
// 有可选项目但尚未确认时,禁止拍照并提示先确认项目
|
||||
if (optionalItemList.length > 0 && !optionalConfirmed) {
|
||||
setMessage('请先确认体检项目');
|
||||
setShowOptionalConfirmTip(true);
|
||||
@@ -465,24 +467,25 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
||||
|
||||
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMessage(null);
|
||||
|
||||
try {
|
||||
const jpgFile = await convertToJpg(file);
|
||||
setIdCardFile(jpgFile);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setPreviewImage(event.target?.result as string);
|
||||
};
|
||||
reader.readAsDataURL(jpgFile);
|
||||
} catch (err) {
|
||||
alert('err: ' + String(err));
|
||||
console.error('图片转换失败', err);
|
||||
setMessage('图片处理失败,请重试');
|
||||
}
|
||||
|
||||
e.target.value = '';
|
||||
};
|
||||
|
||||
@@ -2053,7 +2056,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
||||
<span className='truncate'>{item.pdf_name.length > 10 ? item.pdf_name.slice(0, 10) + "..." : item.pdf_name}</span>
|
||||
{item.combination_code !== undefined && signedCombinationCodes.includes(Number(item.combination_code)) && (
|
||||
<img
|
||||
src='/sign.png'
|
||||
src={import.meta.env.MODE === 'development' ? '/sign.png' : '/tijian-zongkong/sign.png'}
|
||||
alt='已签名'
|
||||
className='w-16 h-16 absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none select-none'
|
||||
loading='lazy'
|
||||
@@ -2105,7 +2108,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
||||
<span className='truncate'>导检单</span>
|
||||
{isDaojiandanSigned && (
|
||||
<img
|
||||
src='/sign.png'
|
||||
src={import.meta.env.MODE === 'development' ? '/sign.png' : '/tijian-zongkong/sign.png'}
|
||||
alt='已签名'
|
||||
className='w-16 h-16 absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none select-none'
|
||||
loading='lazy'
|
||||
@@ -2228,7 +2231,7 @@ export const ExamSignPanel = ({ examId, onBusyChange }: ExamSignPanelProps) => {
|
||||
)}
|
||||
{isSigned && (
|
||||
<img
|
||||
src='/sign.png'
|
||||
src={import.meta.env.MODE === 'development' ? '/sign.png' : '/tijian-zongkong/sign.png'}
|
||||
alt='已签名'
|
||||
className='w-16 h-16 absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none select-none'
|
||||
loading='lazy'
|
||||
|
||||
@@ -180,7 +180,7 @@ export const HomeSection = () => {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='grid grid-cols-2 gap-4 pb-10'>
|
||||
<Card>
|
||||
<CardHeader>B1 服务看板</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
@@ -22,10 +22,18 @@ const NAV_ITEMS = [
|
||||
{ key: 'support', icon: IconSupport, label: '客服咨询' },
|
||||
] as const;
|
||||
|
||||
const toggleFullscreen = () => {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen?.();
|
||||
} else {
|
||||
document.exitFullscreen?.();
|
||||
}
|
||||
};
|
||||
|
||||
export const Sidebar = ({ active, onNavigate, onQuickAction }: SidebarProps) => (
|
||||
<aside className='bg-white border-r p-4 flex flex-col gap-4'>
|
||||
<aside className='h-screen max-h-screen bg-white border-r p-4 flex flex-col gap-4 overflow-hidden'>
|
||||
<div>
|
||||
<div className='text-base font-semibold'>圆和医疗 · 体检中心</div>
|
||||
<button type='button' onClick={toggleFullscreen} className='text-base font-semibold cursor-pointer hover:opacity-80 text-left'>圆和医疗 · 体检中心</button>
|
||||
<div className='text-xs text-gray-500 mt-1'></div>
|
||||
</div>
|
||||
|
||||
@@ -73,13 +81,13 @@ export const Sidebar = ({ active, onNavigate, onQuickAction }: SidebarProps) =>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className='mt-auto p-3 rounded-2xl border bg-gray-50/70 text-xs flex flex-col gap-2'>
|
||||
{/* <section className='mt-auto p-3 rounded-2xl border bg-gray-50/70 text-xs flex flex-col gap-2'>
|
||||
<div className='text-sm font-medium flex items-center gap-2'>
|
||||
<span>💻</span>
|
||||
<span>客服 / IT 支持</span>
|
||||
</div>
|
||||
<div className='text-gray-600'>遇到系统问题可一键联系 IT / 运营支持。</div>
|
||||
</section>
|
||||
</section> */}
|
||||
</aside>
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ interface TopBarProps {
|
||||
onLogout?: () => void;
|
||||
}
|
||||
|
||||
export const TopBar = ({ enableSearch = true, operatorName, onLoginClick, onLogout }: TopBarProps) => {
|
||||
export const TopBar = ({ operatorName, onLoginClick, onLogout }: TopBarProps) => {
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const [displayName, setDisplayName] = useState(operatorName || '');
|
||||
|
||||
@@ -28,18 +28,18 @@ export const TopBar = ({ enableSearch = true, operatorName, onLoginClick, onLogo
|
||||
return (
|
||||
<header className='flex items-center gap-3 p-2 border-b bg-white'>
|
||||
<div className='flex-1 flex items-center gap-3'>
|
||||
{enableSearch ? (
|
||||
{/* {enableSearch ? (
|
||||
<div className='w-[420px] max-w-[60vw] h-[36px] flex items-center'>
|
||||
{/* <Input
|
||||
<Input
|
||||
placeholder='搜索 姓名 / 证件号 / 手机号'
|
||||
value={search}
|
||||
onChange={(e) => onSearch(e.target.value)}
|
||||
className='text-sm'
|
||||
/> */}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className='text-sm text-gray-500 flex items-center p-[9px] pl-[14px]'>圆和医疗 · 体检驾驶舱</div>
|
||||
)}
|
||||
)} */}
|
||||
</div>
|
||||
<div className='flex items-center gap-3 text-xs relative'>
|
||||
<button
|
||||
|
||||
@@ -95,9 +95,9 @@ export const NoteModal = ({ noteText, onNoteChange, onClose }: NoteModalProps) =
|
||||
{saving ? '保存中...' : '保存'}
|
||||
</button>
|
||||
</div>
|
||||
<div className='text-right text-[11px] text-gray-500'>
|
||||
{/* <div className='text-right text-[11px] text-gray-500'>
|
||||
备注内容会同步至客户详情页,供前台和导检护士查看。
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Card, CardContent, CardHeader } from '../ui';
|
||||
import { Card, CardContent } from '../ui';
|
||||
|
||||
export const SupportSection = () => (
|
||||
<Card>
|
||||
<CardHeader>客服咨询 · 圆圆客服台卡</CardHeader>
|
||||
{/* <CardHeader>客服咨询 · 圆圆客服台卡</CardHeader> */}
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-[1.2fr_1fr] gap-6 items-center'>
|
||||
<div className='space-y-3 text-sm text-gray-700'>
|
||||
<div className='items-center'>
|
||||
{/* <div className='space-y-3 text-sm text-gray-700'>
|
||||
<p>通过「圆圆客服」二维码,客户可获得一站式健康服务:包含体检预约、报告查询、报告解读等。</p>
|
||||
<ul className='list-disc ml-5 space-y-1 text-xs text-gray-600'>
|
||||
<li>支持体检当天现场扫码添加,绑定客户信息</li>
|
||||
@@ -13,19 +13,19 @@ export const SupportSection = () => (
|
||||
<li>提供一对一健康咨询与报告解读服务</li>
|
||||
</ul>
|
||||
<div className='text-xs text-gray-500'>注:实际系统中可上传设计好的「圆圆客服二维码台卡」图片,用于前台展示与打印。</div>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className='h-64 rounded-3xl overflow-hidden shadow-inner flex items-center justify-center bg-gradient-to-b from-[#152749] to-[#c73545]'>
|
||||
<div className='flex flex-col items-center gap-3 text-white'>
|
||||
<div className='text-[11px] tracking-[0.2em] opacity-80'>CIRCLE HARMONY · 圆和医疗</div>
|
||||
<div className='text-sm font-medium'>圆圆客服 · 一站式健康服务</div>
|
||||
<div className='w-28 h-28 bg-none flex items-center justify-center'>
|
||||
<div className='w-30 h-30 rounded-md overflow-hidden'>
|
||||
<div className='h-[70vh] rounded-3xl overflow-hidden shadow-inner flex items-center justify-center bg-gradient-to-b from-[#152749] to-[#c73545]'>
|
||||
<div className='flex flex-col items-center gap-5 text-white'>
|
||||
<div className='text-base tracking-[0.2em] opacity-80'>CIRCLE HARMONY · 圆和医疗</div>
|
||||
<div className='text-lg font-medium'>圆圆客服 · 一站式健康服务</div>
|
||||
<div className='w-72 h-72 bg-none flex items-center justify-center'>
|
||||
<div className='w-72 h-72 rounded-md overflow-hidden'>
|
||||
<img src="https://datacenter-open.oss-cn-hangzhou.aliyuncs.com/his/kefu-zixun.png" alt='圆圆客服二维码' className='w-full h-full object-cover' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='text-sm font-semibold'>一对一专属服务</div>
|
||||
<div className='px-4 py-1.5 rounded-full border border-white/70 text-[11px] flex gap-2'>
|
||||
<div className='text-lg font-semibold'>一对一专属服务</div>
|
||||
<div className='px-6 py-2 rounded-full border border-white/70 text-sm flex gap-2'>
|
||||
<span>服务预约</span>
|
||||
<span>/</span>
|
||||
<span>报告查询</span>
|
||||
|
||||
@@ -7,17 +7,21 @@
|
||||
scrollbar-width: none;
|
||||
scrollbar-color: #cbd5e1 #f8fafc;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar-track {
|
||||
background: #f8fafc;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar-thumb {
|
||||
background: #cbd5e1;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.custom-scroll::-webkit-scrollbar-thumb:hover {
|
||||
background: #94a3b8;
|
||||
}
|
||||
@@ -30,9 +34,13 @@
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: #f8fafc;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { ExamClient, ExamModalTab } from '../data/mockData';
|
||||
import { EXAM_TAGS } from '../data/mockData';
|
||||
@@ -17,11 +18,14 @@ export const ExamPage = () => {
|
||||
const [examFilterTags, setExamFilterTags] = useState<Set<(typeof EXAM_TAGS)[number]>>(new Set(['全部']));
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [refreshSeq, setRefreshSeq] = useState(0);
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 进入页面时获取用户菜单权限
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
if (!token) {
|
||||
// 跳转登录
|
||||
navigate('/home');
|
||||
return;
|
||||
}
|
||||
getUserOwnedMenus({ app_id: APP_ID })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createBrowserRouter, Navigate } from 'react-router-dom';
|
||||
import { createHashRouter, Navigate } from 'react-router-dom';
|
||||
|
||||
import { MainLayout } from './layouts/MainLayout';
|
||||
import { HomePage } from './pages/HomePage';
|
||||
@@ -6,7 +6,7 @@ import { ExamPage } from './pages/ExamPage';
|
||||
import { BookingPage } from './pages/BookingPage';
|
||||
import { SupportPage } from './pages/SupportPage';
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
export const router = createHashRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: <MainLayout />,
|
||||
|
||||
6
src/vite-env.d.ts
vendored
Normal file
6
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*?url' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
@@ -3,5 +3,12 @@ import react from '@vitejs/plugin-react-swc'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
plugins: [react()],
|
||||
server: {
|
||||
allowedHosts: ['ipad.shenynet.com'],
|
||||
},
|
||||
resolve: {
|
||||
dedupe: ['react', 'react-dom']
|
||||
},
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user