封装AXIOS,添加接口
- 今日体检统计信息 - 营收数据统计
This commit is contained in:
48
src/api/his.ts
Normal file
48
src/api/his.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import request from './request';
|
||||
import type {
|
||||
InputTodayExamStatisticsInfo,
|
||||
TodayExamStatisticsResponse,
|
||||
InputRevenueStatisticsInfo,
|
||||
RevenueStatisticsResponse,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 自助机HIS接口
|
||||
* 基础路径: /api/his-web/self-service-machine/
|
||||
*/
|
||||
const HIS_BASE_PATH = '/api/his-web/self-service-machine';
|
||||
|
||||
/**
|
||||
* 体检中心HIS接口
|
||||
* 基础路径: /api/his-web/medical-exam-center-app/
|
||||
*/
|
||||
const MEDICAL_EXAM_BASE_PATH = '/api/his-web/medical-exam-center-app';
|
||||
|
||||
/**
|
||||
* 今日体检统计信息
|
||||
* @param data 请求参数(空对象)
|
||||
* @returns 今日体检统计信息
|
||||
*/
|
||||
export const getTodayExamStatistics = (
|
||||
data: InputTodayExamStatisticsInfo = {}
|
||||
): Promise<TodayExamStatisticsResponse> => {
|
||||
return request.post<TodayExamStatisticsResponse>(
|
||||
`${MEDICAL_EXAM_BASE_PATH}/today-exam-statistics`,
|
||||
data
|
||||
).then(res => res.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 营收数据统计
|
||||
* @param data 请求参数(空对象)
|
||||
* @returns 营收数据统计信息
|
||||
*/
|
||||
export const getRevenueStatistics = (
|
||||
data: InputRevenueStatisticsInfo = {}
|
||||
): Promise<RevenueStatisticsResponse> => {
|
||||
return request.post<RevenueStatisticsResponse>(
|
||||
`${MEDICAL_EXAM_BASE_PATH}/revenue-statistics`,
|
||||
data
|
||||
).then(res => res.data);
|
||||
};
|
||||
|
||||
7
src/api/index.ts
Normal file
7
src/api/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* API 模块统一导出
|
||||
*/
|
||||
export * from './request';
|
||||
export * from './types';
|
||||
export * from './his';
|
||||
|
||||
83
src/api/request.ts
Normal file
83
src/api/request.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosInstance, AxiosResponse } from 'axios';
|
||||
|
||||
// API 配置
|
||||
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',
|
||||
// 请求超时时间(30秒)
|
||||
TIMEOUT: 30000,
|
||||
};
|
||||
|
||||
// 创建 axios 实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: API_CONFIG.BASE_URL,
|
||||
timeout: API_CONFIG.TIMEOUT,
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8',
|
||||
},
|
||||
});
|
||||
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加 token 等认证信息
|
||||
// const token = localStorage.getItem('token');
|
||||
// if (token && config.headers) {
|
||||
// config.headers.Authorization = `Bearer ${token}`;
|
||||
// }
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
// 统一错误处理
|
||||
if (error.response) {
|
||||
const { status, data } = error.response;
|
||||
switch (status) {
|
||||
case 400:
|
||||
console.error('请求参数错误', data);
|
||||
break;
|
||||
case 401:
|
||||
console.error('未授权,请重新登录', data);
|
||||
// 可以在这里处理登录跳转
|
||||
break;
|
||||
case 403:
|
||||
console.error('拒绝访问', data);
|
||||
break;
|
||||
case 404:
|
||||
console.error('请求地址不存在', data);
|
||||
break;
|
||||
case 500:
|
||||
console.error('服务器内部错误', data);
|
||||
break;
|
||||
default:
|
||||
console.error('请求失败', data);
|
||||
}
|
||||
} else if (error.request) {
|
||||
console.error('请求超时或网络错误');
|
||||
} else {
|
||||
console.error('请求配置错误', error.message);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
export default request;
|
||||
export { API_CONFIG };
|
||||
|
||||
87
src/api/types.ts
Normal file
87
src/api/types.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 通用接口返回状态码类型
|
||||
*/
|
||||
export type CommonActionResultStatusCode = 200 | 400 | 401 | 403 | 500 | 4011 | -1;
|
||||
|
||||
/**
|
||||
* 状态码常量(用于运行时访问)
|
||||
*/
|
||||
export const StatusCode = {
|
||||
Success: 200,
|
||||
BadRequest: 400,
|
||||
Unauthorized: 401,
|
||||
Forbidden: 403,
|
||||
InternalException: 500,
|
||||
Unidentified: 4011,
|
||||
Fail: -1,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 通用接口返回数据实体
|
||||
*/
|
||||
export interface CommonActionResult<T = any> {
|
||||
Status: CommonActionResultStatusCode;
|
||||
Message?: string | null;
|
||||
Data?: T;
|
||||
TraceId?: string | null;
|
||||
CopyRight?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日体检统计信息入参
|
||||
*/
|
||||
export interface InputTodayExamStatisticsInfo {
|
||||
// 空对象,无需参数
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日体检统计信息出参
|
||||
*/
|
||||
export interface OutputTodayExamStatisticsInfo {
|
||||
/** 今日预约人数 */
|
||||
today_appointment_count?: number | null;
|
||||
/** 今日签到人数 */
|
||||
today_signin_count?: number | null;
|
||||
/** 今日签在检人数 */
|
||||
today_in_exam_count?: number | null;
|
||||
/** 今日签打印导检单 */
|
||||
today_print_guide_count?: number | null;
|
||||
/** 今日签已完成人数 */
|
||||
today_completed_count?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 今日体检统计信息接口返回
|
||||
*/
|
||||
export type TodayExamStatisticsResponse = CommonActionResult<OutputTodayExamStatisticsInfo>;
|
||||
|
||||
/**
|
||||
* 营收数据统计入参
|
||||
*/
|
||||
export interface InputRevenueStatisticsInfo {
|
||||
// 空对象,无需参数
|
||||
}
|
||||
|
||||
/**
|
||||
* 营收数据统计出参
|
||||
*/
|
||||
export interface OutputRevenueStatisticsInfo {
|
||||
/** 体检收入 */
|
||||
physical_exam_income?: number | null;
|
||||
/** 加项收入 */
|
||||
add_item_income?: number | null;
|
||||
/** 整体收入 */
|
||||
total_income?: number | null;
|
||||
/** 目标收入 */
|
||||
target_income?: number | null;
|
||||
/** 完成百分比 */
|
||||
completion_percentage?: string | null;
|
||||
/** 缺口金额 */
|
||||
gap_amount?: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 营收数据统计接口返回
|
||||
*/
|
||||
export type RevenueStatisticsResponse = CommonActionResult<OutputRevenueStatisticsInfo>;
|
||||
|
||||
@@ -1,104 +1,170 @@
|
||||
import { B1_ROWS, B1_SUMMARY, HOME_STATS, NORTH3_ROWS, NORTH3_SUMMARY, REVENUE_STATS } from '../../data/mockData';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { getRevenueStatistics, getTodayExamStatistics } from '../../api';
|
||||
import { B1_ROWS, B1_SUMMARY, NORTH3_ROWS, NORTH3_SUMMARY } from '../../data/mockData';
|
||||
import { Card, CardContent, CardHeader, InfoCard } from '../ui';
|
||||
|
||||
export const HomeSection = () => (
|
||||
<div className='space-y-6'>
|
||||
<Card>
|
||||
<CardHeader>今日体检统计</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-5 gap-3'>
|
||||
{HOME_STATS.map(([label, value]) => (
|
||||
<InfoCard key={label} label={label} value={value} />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
type HomeStatItem = { label: string; value: number };
|
||||
type RevenueStatItem = { label: string; value: string | number };
|
||||
|
||||
<Card>
|
||||
<CardHeader>今日营收数据统计</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-3 gap-3'>
|
||||
{REVENUE_STATS.map(([label, value]) => (
|
||||
<InfoCard key={label} label={label} value={value} />
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
export const HomeSection = () => {
|
||||
const [homeStats, setHomeStats] = useState<HomeStatItem[]>([
|
||||
{ label: '今日预约', value: 0 },
|
||||
{ label: '签到人数', value: 0 },
|
||||
{ label: '在检人数', value: 0 },
|
||||
{ label: '打印导检单', value: 0 },
|
||||
{ label: '已完成人数', value: 0 },
|
||||
]);
|
||||
const [revenueStats, setRevenueStats] = useState<RevenueStatItem[]>([
|
||||
{ label: '体检收入', value: '¥ 0' },
|
||||
{ label: '加项收入', value: '¥ 0' },
|
||||
{ label: '整体收入', value: '¥ 0' },
|
||||
{ label: '目标收入', value: '¥ 0' },
|
||||
{ label: '完成百分比', value: '0%' },
|
||||
{ label: '缺口', value: '¥ 0' },
|
||||
]);
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
const currencyFormatter = useMemo(() => new Intl.NumberFormat('zh-CN', {
|
||||
style: 'currency',
|
||||
currency: 'CNY',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}), []);
|
||||
|
||||
useEffect(() => {
|
||||
getTodayExamStatistics({})
|
||||
.then((res) => {
|
||||
const d = res.Data;
|
||||
setHomeStats([
|
||||
{ label: '今日预约', value: Number(d?.today_appointment_count ?? 0) },
|
||||
{ label: '签到人数', value: Number(d?.today_signin_count ?? 0) },
|
||||
{ label: '在检人数', value: Number(d?.today_in_exam_count ?? 0) },
|
||||
{ label: '打印导检单', value: Number(d?.today_print_guide_count ?? 0) },
|
||||
{ label: '已完成人数', value: Number(d?.today_completed_count ?? 0) },
|
||||
]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('获取今日体检统计失败', err);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getRevenueStatistics({})
|
||||
.then((res) => {
|
||||
const d = res.Data;
|
||||
const fmt = (n?: number | null) => currencyFormatter.format(Number(n ?? 0));
|
||||
setRevenueStats([
|
||||
{ label: '体检收入', value: fmt(d?.physical_exam_income) },
|
||||
{ label: '加项收入', value: fmt(d?.add_item_income) },
|
||||
{ label: '整体收入', value: fmt(d?.total_income) },
|
||||
{ label: '目标收入', value: fmt(d?.target_income) },
|
||||
{ label: '完成百分比', value: d?.completion_percentage ?? '0%' },
|
||||
{ label: '缺口', value: fmt(d?.gap_amount) },
|
||||
]);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('获取营收数据统计失败', err);
|
||||
});
|
||||
}, [currencyFormatter]);
|
||||
|
||||
return (
|
||||
<div className='space-y-6'>
|
||||
<Card>
|
||||
<CardHeader>B1 服务看板</CardHeader>
|
||||
<CardHeader>今日体检统计</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-3 gap-3 mb-3'>
|
||||
<InfoCard label='当前客户总数' value={B1_SUMMARY.totalClients} />
|
||||
<InfoCard label='待检人数' value={B1_SUMMARY.waiting} />
|
||||
<InfoCard label='在检人数' value={B1_SUMMARY.inExam} />
|
||||
<div className='grid grid-cols-5 gap-3'>
|
||||
{homeStats.map(({ label, value }) => (
|
||||
<InfoCard key={label} label={label} value={value} />
|
||||
))}
|
||||
</div>
|
||||
<table className='w-full text-xs'>
|
||||
<thead>
|
||||
<tr className='text-gray-500 border-b'>
|
||||
<th className='py-2 text-left'>科室</th>
|
||||
<th className='py-2 text-left'>医生</th>
|
||||
<th className='py-2 text-right'>已检人数</th>
|
||||
<th className='py-2 text-right'>已检部位数</th>
|
||||
<th className='py-2 text-right'>总时长</th>
|
||||
<th className='py-2 text-right'>平均时长</th>
|
||||
<th className='py-2 text-right'>在检</th>
|
||||
<th className='py-2 text-right'>待检</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{B1_ROWS.map(([dept, doctor, done, inExam, waiting, avg]) => {
|
||||
const parts = done * 3;
|
||||
const totalTime = done * avg;
|
||||
return (
|
||||
<tr key={dept} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{dept}</td>
|
||||
<td className='py-2'>{doctor}</td>
|
||||
<td className='py-2 text-right'>{done}</td>
|
||||
<td className='py-2 text-right'>{parts}</td>
|
||||
<td className='py-2 text-right'>{totalTime}</td>
|
||||
<td className='py-2 text-right'>{avg}</td>
|
||||
<td className='py-2 text-right'>{inExam}</td>
|
||||
<td className='py-2 text-right'>{waiting}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>北3服务看板</CardHeader>
|
||||
<CardHeader>今日营收数据统计</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-3 gap-3 mb-3'>
|
||||
<InfoCard label='今日家医数' value={NORTH3_SUMMARY.totalDoctor} />
|
||||
<InfoCard label='分配客户数' value={NORTH3_SUMMARY.totalAssigned} />
|
||||
<InfoCard label='面诊数' value={NORTH3_SUMMARY.consult} />
|
||||
<div className='grid grid-cols-3 gap-3'>
|
||||
{revenueStats.map(({ label, value }) => (
|
||||
<InfoCard key={label} label={label} value={value} />
|
||||
))}
|
||||
</div>
|
||||
<table className='w-full text-xs'>
|
||||
<thead>
|
||||
<tr className='text-gray-500 border-b'>
|
||||
<th className='py-2 text-left'>家医</th>
|
||||
<th className='py-2 text-right'>分配客户数</th>
|
||||
<th className='py-2 text-right'>面诊数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{NORTH3_ROWS.map(([name, total, consult]) => (
|
||||
<tr key={name} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{name}</td>
|
||||
<td className='py-2 text-right'>{total}</td>
|
||||
<td className='py-2 text-right'>{consult}</td>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<Card>
|
||||
<CardHeader>B1 服务看板</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-3 gap-3 mb-3'>
|
||||
<InfoCard label='当前客户总数' value={B1_SUMMARY.totalClients} />
|
||||
<InfoCard label='待检人数' value={B1_SUMMARY.waiting} />
|
||||
<InfoCard label='在检人数' value={B1_SUMMARY.inExam} />
|
||||
</div>
|
||||
<table className='w-full text-xs'>
|
||||
<thead>
|
||||
<tr className='text-gray-500 border-b'>
|
||||
<th className='py-2 text-left'>科室</th>
|
||||
<th className='py-2 text-left'>医生</th>
|
||||
<th className='py-2 text-right'>已检人数</th>
|
||||
<th className='py-2 text-right'>已检部位数</th>
|
||||
<th className='py-2 text-right'>总时长</th>
|
||||
<th className='py-2 text-right'>平均时长</th>
|
||||
<th className='py-2 text-right'>在检</th>
|
||||
<th className='py-2 text-right'>待检</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</thead>
|
||||
<tbody>
|
||||
{B1_ROWS.map(([dept, doctor, done, inExam, waiting, avg]) => {
|
||||
const parts = done * 3;
|
||||
const totalTime = done * avg;
|
||||
return (
|
||||
<tr key={dept} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{dept}</td>
|
||||
<td className='py-2'>{doctor}</td>
|
||||
<td className='py-2 text-right'>{done}</td>
|
||||
<td className='py-2 text-right'>{parts}</td>
|
||||
<td className='py-2 text-right'>{totalTime}</td>
|
||||
<td className='py-2 text-right'>{avg}</td>
|
||||
<td className='py-2 text-right'>{inExam}</td>
|
||||
<td className='py-2 text-right'>{waiting}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>北3服务看板</CardHeader>
|
||||
<CardContent>
|
||||
<div className='grid grid-cols-3 gap-3 mb-3'>
|
||||
<InfoCard label='今日家医数' value={NORTH3_SUMMARY.totalDoctor} />
|
||||
<InfoCard label='分配客户数' value={NORTH3_SUMMARY.totalAssigned} />
|
||||
<InfoCard label='面诊数' value={NORTH3_SUMMARY.consult} />
|
||||
</div>
|
||||
<table className='w-full text-xs'>
|
||||
<thead>
|
||||
<tr className='text-gray-500 border-b'>
|
||||
<th className='py-2 text-left'>家医</th>
|
||||
<th className='py-2 text-right'>分配客户数</th>
|
||||
<th className='py-2 text-right'>面诊数</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{NORTH3_ROWS.map(([name, total, consult]) => (
|
||||
<tr key={name} className='border-b last:border-b-0'>
|
||||
<td className='py-2'>{name}</td>
|
||||
<td className='py-2 text-right'>{total}</td>
|
||||
<td className='py-2 text-right'>{consult}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -21,23 +21,6 @@ export interface ExamClient {
|
||||
export type ExamModalTab = 'detail' | 'sign' | 'addon' | 'print' | 'delivery';
|
||||
export type QuickActionType = 'none' | 'meal' | 'vip' | 'note';
|
||||
|
||||
export const HOME_STATS: [string, number][] = [
|
||||
['今日预约', 80],
|
||||
['签到人数', 60],
|
||||
['在检人数', 25],
|
||||
['打印导检单', 40],
|
||||
['已完成人数', 30],
|
||||
];
|
||||
|
||||
export const REVENUE_STATS: [string, string][] = [
|
||||
['体检收入', '¥ 86,000'],
|
||||
['加项收入', '¥ 12,400'],
|
||||
['整体收入', '¥ 98,400'],
|
||||
['目标收入', '¥ 120,000'],
|
||||
['完成百分比', '82%'],
|
||||
['缺口', '¥ 21,600'],
|
||||
];
|
||||
|
||||
export const B1_ROWS: [string, string, number, number, number, number][] = [
|
||||
['B超1', '张医生', 6, 2, 2, 15],
|
||||
['B超2', '李医生', 5, 2, 1, 14],
|
||||
|
||||
Reference in New Issue
Block a user