diff --git a/package-lock.json b/package-lock.json index e296aea..a562b6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "yuanhe-ipad", "version": "0.0.0", "dependencies": { + "axios": "^1.13.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6" @@ -1986,6 +1987,12 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.22", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", @@ -2024,6 +2031,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2112,6 +2130,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2228,6 +2259,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2321,6 +2364,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2335,6 +2387,20 @@ "dev": true, "license": "MIT" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.259", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", @@ -2342,6 +2408,51 @@ "dev": true, "license": "ISC" }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -2716,6 +2827,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", @@ -2749,7 +2896,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2765,6 +2911,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2791,6 +2974,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2808,11 +3003,37 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3095,6 +3316,15 @@ "yallist": "^3.0.2" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3119,6 +3349,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3533,6 +3784,12 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 9b85790..d86d4d7 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,13 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "vite --host 0.0.0.0", + "dev": "export NODE_ENV=development && vite --host 0.0.0.0", "build": "tsc -b && vite build", "lint": "eslint .", "preview": "vite preview" }, "dependencies": { + "axios": "^1.13.2", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.6" @@ -31,4 +32,4 @@ "typescript-eslint": "^8.46.4", "vite": "^7.2.4" } -} +} \ No newline at end of file diff --git a/src/api/his.ts b/src/api/his.ts new file mode 100644 index 0000000..a5bf655 --- /dev/null +++ b/src/api/his.ts @@ -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 => { + return request.post( + `${MEDICAL_EXAM_BASE_PATH}/today-exam-statistics`, + data + ).then(res => res.data); +}; + +/** + * 营收数据统计 + * @param data 请求参数(空对象) + * @returns 营收数据统计信息 + */ +export const getRevenueStatistics = ( + data: InputRevenueStatisticsInfo = {} +): Promise => { + return request.post( + `${MEDICAL_EXAM_BASE_PATH}/revenue-statistics`, + data + ).then(res => res.data); +}; + diff --git a/src/api/index.ts b/src/api/index.ts new file mode 100644 index 0000000..cd7fb23 --- /dev/null +++ b/src/api/index.ts @@ -0,0 +1,7 @@ +/** + * API 模块统一导出 + */ +export * from './request'; +export * from './types'; +export * from './his'; + diff --git a/src/api/request.ts b/src/api/request.ts new file mode 100644 index 0000000..a736b28 --- /dev/null +++ b/src/api/request.ts @@ -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 }; + diff --git a/src/api/types.ts b/src/api/types.ts new file mode 100644 index 0000000..1d02d48 --- /dev/null +++ b/src/api/types.ts @@ -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 { + 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; + +/** + * 营收数据统计入参 + */ +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; + diff --git a/src/components/home/HomeSection.tsx b/src/components/home/HomeSection.tsx index 9f29008..be668f1 100644 --- a/src/components/home/HomeSection.tsx +++ b/src/components/home/HomeSection.tsx @@ -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 = () => ( -
- - 今日体检统计 - -
- {HOME_STATS.map(([label, value]) => ( - - ))} -
-
-
+type HomeStatItem = { label: string; value: number }; +type RevenueStatItem = { label: string; value: string | number }; - - 今日营收数据统计 - -
- {REVENUE_STATS.map(([label, value]) => ( - - ))} -
-
-
+export const HomeSection = () => { + const [homeStats, setHomeStats] = useState([ + { label: '今日预约', value: 0 }, + { label: '签到人数', value: 0 }, + { label: '在检人数', value: 0 }, + { label: '打印导检单', value: 0 }, + { label: '已完成人数', value: 0 }, + ]); + const [revenueStats, setRevenueStats] = useState([ + { label: '体检收入', value: '¥ 0' }, + { label: '加项收入', value: '¥ 0' }, + { label: '整体收入', value: '¥ 0' }, + { label: '目标收入', value: '¥ 0' }, + { label: '完成百分比', value: '0%' }, + { label: '缺口', value: '¥ 0' }, + ]); -
+ 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 ( +
- B1 服务看板 + 今日体检统计 -
- - - +
+ {homeStats.map(({ label, value }) => ( + + ))}
- - - - - - - - - - - - - - - {B1_ROWS.map(([dept, doctor, done, inExam, waiting, avg]) => { - const parts = done * 3; - const totalTime = done * avg; - return ( - - - - - - - - - - - ); - })} - -
科室医生已检人数已检部位数总时长平均时长在检待检
{dept}{doctor}{done}{parts}{totalTime}{avg}{inExam}{waiting}
- 北3服务看板 + 今日营收数据统计 -
- - - +
+ {revenueStats.map(({ label, value }) => ( + + ))}
- - - - - - - - - - {NORTH3_ROWS.map(([name, total, consult]) => ( - - - - + + + +
+ + B1 服务看板 + +
+ + + +
+
家医分配客户数面诊数
{name}{total}{consult}
+ + + + + + + + + + - ))} - -
科室医生已检人数已检部位数总时长平均时长在检待检
- - + + + {B1_ROWS.map(([dept, doctor, done, inExam, waiting, avg]) => { + const parts = done * 3; + const totalTime = done * avg; + return ( + + {dept} + {doctor} + {done} + {parts} + {totalTime} + {avg} + {inExam} + {waiting} + + ); + })} + + + + + + + 北3服务看板 + +
+ + + +
+ + + + + + + + + + {NORTH3_ROWS.map(([name, total, consult]) => ( + + + + + + ))} + +
家医分配客户数面诊数
{name}{total}{consult}
+
+
+
-
-); + ); +}; diff --git a/src/data/mockData.ts b/src/data/mockData.ts index 3214263..ad736b5 100644 --- a/src/data/mockData.ts +++ b/src/data/mockData.ts @@ -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],