write-pharmacy.cjs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. const fs = require('fs');
  2. const path = 'D:\\new_project\\plus-ui\\src\\views\\health\\pharmacy\\index.vue';
  3. const content = `<template>
  4. <div class="analysis-page">
  5. <div class="page-header">
  6. <div>
  7. <h2>全市药房药物分析</h2>
  8. <p class="subtitle">通过门诊发药、住院发药、药房进销存、药库进销存监控药房药库的情况</p>
  9. </div>
  10. </div>
  11. <!-- 一、门诊发药模块 -->
  12. <div class="section-title">一、门诊发药</div>
  13. <!-- 发药工作量维度 -->
  14. <div class="sub-title">1. 发药工作量维度</div>
  15. <div class="metrics-grid-4">
  16. <div class="metric-card">
  17. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  18. <el-icon :size="24"><Document /></el-icon>
  19. </div>
  20. <div class="metric-content">
  21. <div class="metric-value">{{ stats.outpatientPrescriptionCount ?? '---' }}</div>
  22. <div class="metric-label">处方总数</div>
  23. </div>
  24. </div>
  25. <div class="metric-card">
  26. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  27. <el-icon :size="24"><CircleCheck /></el-icon>
  28. </div>
  29. <div class="metric-content">
  30. <div class="metric-value">{{ stats.validPrescriptionCount ?? '---' }}</div>
  31. <div class="metric-label">有效发药处方数</div>
  32. </div>
  33. </div>
  34. <div class="metric-card">
  35. <div class="metric-icon" style="background:linear-gradient(135deg,#f5576c,#f093fb)">
  36. <el-icon :size="24"><CircleClose /></el-icon>
  37. </div>
  38. <div class="metric-content">
  39. <div class="metric-value">{{ stats.returnPrescriptionCount ?? '---' }}</div>
  40. <div class="metric-label">退方处方数</div>
  41. </div>
  42. </div>
  43. <div class="metric-card">
  44. <div class="metric-icon" style="background:linear-gradient(135deg,#4facfe,#00f2fe)">
  45. <el-icon :size="24"><User /></el-icon>
  46. </div>
  47. <div class="metric-content">
  48. <div class="metric-value">{{ stats.avgPrescriptionPerWindow ?? '---' }}</div>
  49. <div class="metric-label">窗口人均调配处方量</div>
  50. </div>
  51. </div>
  52. </div>
  53. <!-- 时效服务维度 -->
  54. <div class="sub-title">2. 时效服务维度</div>
  55. <div class="metrics-grid-2">
  56. <div class="metric-card">
  57. <div class="metric-icon" style="background:linear-gradient(135deg,#fa709a,#fee140)">
  58. <el-icon :size="24"><Clock /></el-icon>
  59. </div>
  60. <div class="metric-content">
  61. <div class="metric-value">{{ stats.avgWaitTime ?? '---' }}<span class="unit">分钟</span></div>
  62. <div class="metric-label">平均候药时长</div>
  63. </div>
  64. </div>
  65. <div class="metric-card">
  66. <div class="metric-icon" style="background:linear-gradient(135deg,#a18cd1,#fbc2eb)">
  67. <el-icon :size="24"><Warning /></el-icon>
  68. </div>
  69. <div class="metric-content">
  70. <div class="metric-value">{{ stats.overtimeWaitCount ?? '---' }}</div>
  71. <div class="metric-label">超时候药处方数量</div>
  72. </div>
  73. </div>
  74. </div>
  75. <!-- 药品消耗维度 + 处方异常维度 -->
  76. <div class="charts-row">
  77. <div class="chart-card">
  78. <div class="chart-header"><h3>3. 药品消耗维度 - 西药/中成药/中药饮片调配金额占比</h3></div>
  79. <div ref="outpatientDrugTypeChartRef" class="chart-container"></div>
  80. </div>
  81. <div class="chart-card">
  82. <div class="chart-header"><h3>4. 处方异常维度 - 退方原因统计占比</h3></div>
  83. <div ref="returnReasonChartRef" class="chart-container"></div>
  84. </div>
  85. </div>
  86. <!-- 二、住院发药模块 -->
  87. <div class="section-title">二、住院发药</div>
  88. <!-- 药品消耗维度 -->
  89. <div class="sub-title">1. 药品消耗维度</div>
  90. <div class="metrics-grid-2">
  91. <div class="metric-card">
  92. <div class="metric-icon" style="background:linear-gradient(135deg,#f093fb,#f5576c)">
  93. <el-icon :size="24"><Money /></el-icon>
  94. </div>
  95. <div class="metric-content">
  96. <div class="metric-value">{{ stats.inpatientDrugAmount ?? '---' }}<span class="unit">万元</span></div>
  97. <div class="metric-label">住院发药总金额</div>
  98. </div>
  99. </div>
  100. <div class="metric-card">
  101. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  102. <el-icon :size="24"><Box /></el-icon>
  103. </div>
  104. <div class="metric-content">
  105. <div class="metric-value">{{ stats.inpatientDrugCount ?? '---' }}</div>
  106. <div class="metric-label">住院发药数量</div>
  107. </div>
  108. </div>
  109. </div>
  110. <!-- 摆药/发药工作量维度 + 退药管控维度 -->
  111. <div class="charts-row">
  112. <div class="chart-card">
  113. <div class="chart-header"><h3>2. 摆药/发药工作量维度</h3></div>
  114. <div class="inline-metrics">
  115. <div class="inline-metric">
  116. <div class="inline-value">{{ stats.avgDailyBatch ?? '---' }}</div>
  117. <div class="inline-label">日均摆药批次</div>
  118. </div>
  119. <div class="inline-metric">
  120. <div class="inline-value">{{ stats.avgDailyBag ?? '---' }}</div>
  121. <div class="inline-label">日均摆药袋数</div>
  122. </div>
  123. <div class="inline-metric">
  124. <div class="inline-value">{{ stats.ivMixBagCount ?? '---' }}</div>
  125. <div class="inline-label">静配输液调配袋数</div>
  126. </div>
  127. </div>
  128. </div>
  129. <div class="chart-card">
  130. <div class="chart-header"><h3>3. 退药管控维度</h3></div>
  131. <div class="inline-metrics">
  132. <div class="inline-metric">
  133. <div class="inline-value">{{ stats.inpatientReturnCount ?? '---' }}</div>
  134. <div class="inline-label">住院退药次数</div>
  135. </div>
  136. <div class="inline-metric">
  137. <div class="inline-value">{{ stats.inpatientReturnAmount ?? '---' }}<span class="unit">万元</span></div>
  138. <div class="inline-label">住院退药金额</div>
  139. </div>
  140. <div class="inline-metric">
  141. <div class="inline-value">{{ stats.inpatientReturnRate ?? '---' }}<span class="unit">%</span></div>
  142. <div class="inline-label">住院退药率</div>
  143. </div>
  144. </div>
  145. </div>
  146. </div>
  147. <!-- 三、药房进销存模块 -->
  148. <div class="section-title">三、药房进销存</div>
  149. <!-- 入库维度 -->
  150. <div class="sub-title">1. 入库维度</div>
  151. <div class="metrics-grid-5">
  152. <div class="metric-card">
  153. <div class="metric-icon" style="background:linear-gradient(135deg,#4facfe,#00f2fe)">
  154. <el-icon :size="24"><Money /></el-icon>
  155. </div>
  156. <div class="metric-content">
  157. <div class="metric-value">{{ stats.pharmacyInAmount ?? '---' }}<span class="unit">万元</span></div>
  158. <div class="metric-label">药房申领入库金额</div>
  159. </div>
  160. </div>
  161. <div class="metric-card">
  162. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  163. <el-icon :size="24"><Box /></el-icon>
  164. </div>
  165. <div class="metric-content">
  166. <div class="metric-value">{{ stats.pharmacyInCount ?? '---' }}</div>
  167. <div class="metric-label">入库数量</div>
  168. </div>
  169. </div>
  170. <div class="metric-card">
  171. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  172. <el-icon :size="24"><PieChart /></el-icon>
  173. </div>
  174. <div class="metric-content">
  175. <div class="metric-value">{{ stats.pharmacyWesternInRate ?? '---' }}<span class="unit">%</span></div>
  176. <div class="metric-label">西药入库占比</div>
  177. </div>
  178. </div>
  179. <div class="metric-card">
  180. <div class="metric-icon" style="background:linear-gradient(135deg,#f093fb,#f5576c)">
  181. <el-icon :size="24"><PieChart /></el-icon>
  182. </div>
  183. <div class="metric-content">
  184. <div class="metric-value">{{ stats.pharmacyChinesePatentInRate ?? '---' }}<span class="unit">%</span></div>
  185. <div class="metric-label">中成药入库占比</div>
  186. </div>
  187. </div>
  188. <div class="metric-card">
  189. <div class="metric-icon" style="background:linear-gradient(135deg,#fa709a,#fee140)">
  190. <el-icon :size="24"><PieChart /></el-icon>
  191. </div>
  192. <div class="metric-content">
  193. <div class="metric-value">{{ stats.pharmacyHerbalInRate ?? '---' }}<span class="unit">%</span></div>
  194. <div class="metric-label">中药饮片入库占比</div>
  195. </div>
  196. </div>
  197. </div>
  198. <!-- 出库消耗维度 -->
  199. <div class="sub-title">2. 出库消耗维度</div>
  200. <div class="metrics-grid-3">
  201. <div class="metric-card">
  202. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  203. <el-icon :size="24"><FirstAidKit /></el-icon>
  204. </div>
  205. <div class="metric-content">
  206. <div class="metric-value">{{ stats.outpatientConsumeCount ?? '---' }}</div>
  207. <div class="metric-label">门诊发药消耗量</div>
  208. </div>
  209. </div>
  210. <div class="metric-card">
  211. <div class="metric-icon" style="background:linear-gradient(135deg,#4facfe,#00f2fe)">
  212. <el-icon :size="24"><Box /></el-icon>
  213. </div>
  214. <div class="metric-content">
  215. <div class="metric-value">{{ stats.inpatientConsumeCount ?? '---' }}</div>
  216. <div class="metric-label">住院摆药消耗量</div>
  217. </div>
  218. </div>
  219. <div class="metric-card">
  220. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  221. <el-icon :size="24"><Switch /></el-icon>
  222. </div>
  223. <div class="metric-content">
  224. <div class="metric-value">{{ stats.internalTransferCount ?? '---' }}</div>
  225. <div class="metric-label">药房内部调拨数量</div>
  226. </div>
  227. </div>
  228. </div>
  229. <!-- 库存现状维度 -->
  230. <div class="sub-title">3. 库存现状维度</div>
  231. <div class="metrics-grid-4">
  232. <div class="metric-card">
  233. <div class="metric-icon" style="background:linear-gradient(135deg,#f5576c,#f093fb)">
  234. <el-icon :size="24"><Money /></el-icon>
  235. </div>
  236. <div class="metric-content">
  237. <div class="metric-value">{{ stats.currentStockAmount ?? '---' }}<span class="unit">万元</span></div>
  238. <div class="metric-label">当前库存总金额</div>
  239. </div>
  240. </div>
  241. <div class="metric-card">
  242. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  243. <el-icon :size="24"><Coin /></el-icon>
  244. </div>
  245. <div class="metric-content">
  246. <div class="metric-value">{{ stats.westernStockAmount ?? '---' }}<span class="unit">万元</span></div>
  247. <div class="metric-label">西药库存金额</div>
  248. </div>
  249. </div>
  250. <div class="metric-card">
  251. <div class="metric-icon" style="background:linear-gradient(135deg,#4facfe,#00f2fe)">
  252. <el-icon :size="24"><Coin /></el-icon>
  253. </div>
  254. <div class="metric-content">
  255. <div class="metric-value">{{ stats.chinesePatentStockAmount ?? '---' }}<span class="unit">万元</span></div>
  256. <div class="metric-label">中成药库存金额</div>
  257. </div>
  258. </div>
  259. <div class="metric-card">
  260. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  261. <el-icon :size="24"><Coin /></el-icon>
  262. </div>
  263. <div class="metric-content">
  264. <div class="metric-value">{{ stats.herbalStockAmount ?? '---' }}<span class="unit">万元</span></div>
  265. <div class="metric-label">中药饮片库存金额</div>
  266. </div>
  267. </div>
  268. </div>
  269. <div class="charts-row">
  270. <div class="chart-card" style="max-width:50%">
  271. <div class="chart-header"><h3>药房库存类型占比</h3></div>
  272. <div ref="stockTypeChartRef" class="chart-container"></div>
  273. </div>
  274. </div>
  275. <!-- 四、药库进销存模块 -->
  276. <div class="section-title">四、药库进销存</div>
  277. <!-- 采购入库维度 -->
  278. <div class="sub-title">1. 采购入库维度</div>
  279. <div class="metrics-grid-2">
  280. <div class="metric-card">
  281. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  282. <el-icon :size="24"><Money /></el-icon>
  283. </div>
  284. <div class="metric-content">
  285. <div class="metric-value">{{ stats.purchaseAmount ?? '---' }}<span class="unit">万元</span></div>
  286. <div class="metric-label">药品采购入库金额</div>
  287. </div>
  288. </div>
  289. <div class="metric-card">
  290. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  291. <el-icon :size="24"><List /></el-icon>
  292. </div>
  293. <div class="metric-content">
  294. <div class="metric-value">{{ stats.purchaseBatchCount ?? '---' }}</div>
  295. <div class="metric-label">入库批次</div>
  296. </div>
  297. </div>
  298. </div>
  299. <!-- 供应商占比 + 调拨出库 -->
  300. <div class="charts-row">
  301. <div class="chart-card">
  302. <div class="chart-header"><h3>各供应商采购金额占比</h3></div>
  303. <div ref="supplierChartRef" class="chart-container"></div>
  304. </div>
  305. <div class="chart-card">
  306. <div class="chart-header"><h3>2. 调拨出库维度 - 向各药房调拨金额</h3></div>
  307. <div ref="transferChartRef" class="chart-container"></div>
  308. </div>
  309. </div>
  310. <!-- 药库库存现状维度 -->
  311. <div class="sub-title">3. 库存现状维度</div>
  312. <div class="metrics-grid-4">
  313. <div class="metric-card">
  314. <div class="metric-icon" style="background:linear-gradient(135deg,#f5576c,#f093fb)">
  315. <el-icon :size="24"><Money /></el-icon>
  316. </div>
  317. <div class="metric-content">
  318. <div class="metric-value">{{ stats.drugStoreStockAmount ?? '---' }}<span class="unit">万元</span></div>
  319. <div class="metric-label">药库当前库存总金额</div>
  320. </div>
  321. </div>
  322. <div class="metric-card">
  323. <div class="metric-icon" style="background:linear-gradient(135deg,#667eea,#764ba2)">
  324. <el-icon :size="24"><Coin /></el-icon>
  325. </div>
  326. <div class="metric-content">
  327. <div class="metric-value">{{ stats.drugStoreWesternAmount ?? '---' }}<span class="unit">万元</span></div>
  328. <div class="metric-label">西药库存金额</div>
  329. </div>
  330. </div>
  331. <div class="metric-card">
  332. <div class="metric-icon" style="background:linear-gradient(135deg,#4facfe,#00f2fe)">
  333. <el-icon :size="24"><Coin /></el-icon>
  334. </div>
  335. <div class="metric-content">
  336. <div class="metric-value">{{ stats.drugStoreChinesePatentAmount ?? '---' }}<span class="unit">万元</span></div>
  337. <div class="metric-label">中成药库存金额</div>
  338. </div>
  339. </div>
  340. <div class="metric-card">
  341. <div class="metric-icon" style="background:linear-gradient(135deg,#43e97b,#38f9d7)">
  342. <el-icon :size="24"><Coin /></el-icon>
  343. </div>
  344. <div class="metric-content">
  345. <div class="metric-value">{{ stats.drugStoreHerbalAmount ?? '---' }}<span class="unit">万元</span></div>
  346. <div class="metric-label">中药饮片库存金额</div>
  347. </div>
  348. </div>
  349. </div>
  350. <div class="charts-row">
  351. <div class="chart-card" style="max-width:50%">
  352. <div class="chart-header"><h3>药库库存类型占比</h3></div>
  353. <div ref="drugStoreStockChartRef" class="chart-container"></div>
  354. </div>
  355. </div>
  356. <!-- 效期与损耗维度 -->
  357. <div class="sub-title">4. 效期与损耗维度</div>
  358. <div class="metrics-grid-2">
  359. <div class="metric-card">
  360. <div class="metric-icon" style="background:linear-gradient(135deg,#fa709a,#fee140)">
  361. <el-icon :size="24"><Warning /></el-icon>
  362. </div>
  363. <div class="metric-content">
  364. <div class="metric-value">{{ stats.expiredBatchCount ?? '---' }}</div>
  365. <div class="metric-label">过期药品批次数</div>
  366. </div>
  367. </div>
  368. <div class="metric-card">
  369. <div class="metric-icon" style="background:linear-gradient(135deg,#f5576c,#f093fb)">
  370. <el-icon :size="24"><Money /></el-icon>
  371. </div>
  372. <div class="metric-content">
  373. <div class="metric-value">{{ stats.expiredLossAmount ?? '---' }}<span class="unit">万元</span></div>
  374. <div class="metric-label">过期药品报损金额</div>
  375. </div>
  376. </div>
  377. </div>
  378. </div>
  379. </template>
  380. <script setup name="Pharmacy" lang="ts">
  381. import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
  382. import * as echarts from 'echarts';
  383. import { Document, CircleCheck, CircleClose, User, Clock, Warning, Money, Box, PieChart, Switch, Coin, List, FirstAidKit } from '@element-plus/icons-vue';
  384. import { getPharmacyStats } from '@/api/health/pharmacy';
  385. import type { PharmacyStatsVO } from '@/api/health/pharmacy/types';
  386. const stats = ref<PharmacyStatsVO>({} as PharmacyStatsVO);
  387. const outpatientDrugTypeChartRef = ref<HTMLElement>();
  388. const returnReasonChartRef = ref<HTMLElement>();
  389. const stockTypeChartRef = ref<HTMLElement>();
  390. const supplierChartRef = ref<HTMLElement>();
  391. const transferChartRef = ref<HTMLElement>();
  392. const drugStoreStockChartRef = ref<HTMLElement>();
  393. let outpatientDrugTypeChart: echarts.ECharts | null = null;
  394. let returnReasonChart: echarts.ECharts | null = null;
  395. let stockTypeChart: echarts.ECharts | null = null;
  396. let supplierChart: echarts.ECharts | null = null;
  397. let transferChart: echarts.ECharts | null = null;
  398. let drugStoreStockChart: echarts.ECharts | null = null;
  399. function loadStats() {
  400. getPharmacyStats().then((response: any) => {
  401. stats.value = response.data || {};
  402. nextTick(() => {
  403. renderOutpatientDrugTypeChart();
  404. renderReturnReasonChart();
  405. renderStockTypeChart();
  406. renderSupplierChart();
  407. renderTransferChart();
  408. renderDrugStoreStockChart();
  409. });
  410. });
  411. }
  412. function renderOutpatientDrugTypeChart() {
  413. if (!outpatientDrugTypeChartRef.value) return;
  414. if (outpatientDrugTypeChart) outpatientDrugTypeChart.dispose();
  415. outpatientDrugTypeChart = echarts.init(outpatientDrugTypeChartRef.value);
  416. const list = stats.value.outpatientDrugTypeList || [];
  417. const data = list.map((item: any) => ({ name: item.name, value: Number(item.value) }));
  418. outpatientDrugTypeChart.setOption({
  419. tooltip: { trigger: 'item', formatter: '{b}: {c}万元 ({d}%)' },
  420. legend: { orient: 'vertical', left: 10, top: 'middle' },
  421. series: [{
  422. name: '药品消耗占比', type: 'pie', radius: ['40%', '70%'], center: ['60%', '50%'],
  423. data: data, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
  424. label: { formatter: '{b}: {d}%' },
  425. emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
  426. }]
  427. });
  428. }
  429. function renderReturnReasonChart() {
  430. if (!returnReasonChartRef.value) return;
  431. if (returnReasonChart) returnReasonChart.dispose();
  432. returnReasonChart = echarts.init(returnReasonChartRef.value);
  433. const list = stats.value.returnReasonList || [];
  434. const data = list.map((item: any) => ({ name: item.name, value: Number(item.value) }));
  435. returnReasonChart.setOption({
  436. tooltip: { trigger: 'item', formatter: '{b}: {c}例 ({d}%)' },
  437. legend: { orient: 'vertical', left: 10, top: 'middle' },
  438. color: ['#f5576c', '#fa709a', '#a18cd1', '#667eea', '#4facfe'],
  439. series: [{
  440. name: '退方原因', type: 'pie', radius: ['40%', '70%'], center: ['60%', '50%'],
  441. data: data, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
  442. label: { formatter: '{b}: {d}%' },
  443. emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
  444. }]
  445. });
  446. }
  447. function renderStockTypeChart() {
  448. if (!stockTypeChartRef.value) return;
  449. if (stockTypeChart) stockTypeChart.dispose();
  450. stockTypeChart = echarts.init(stockTypeChartRef.value);
  451. const list = stats.value.stockTypeList || [];
  452. const data = list.map((item: any) => ({ name: item.name, value: Number(item.value) }));
  453. stockTypeChart.setOption({
  454. tooltip: { trigger: 'item', formatter: '{b}: {c}万元 ({d}%)' },
  455. legend: { orient: 'vertical', left: 10, top: 'middle' },
  456. series: [{
  457. name: '药房库存占比', type: 'pie', radius: ['40%', '70%'], center: ['60%', '50%'],
  458. data: data, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
  459. label: { formatter: '{b}: {d}%' },
  460. emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
  461. }]
  462. });
  463. }
  464. function renderSupplierChart() {
  465. if (!supplierChartRef.value) return;
  466. if (supplierChart) supplierChart.dispose();
  467. supplierChart = echarts.init(supplierChartRef.value);
  468. const list = stats.value.supplierList || [];
  469. const data = list.map((item: any) => ({ name: item.name, value: Number(item.value) }));
  470. supplierChart.setOption({
  471. tooltip: { trigger: 'item', formatter: '{b}: {c}万元 ({d}%)' },
  472. legend: { orient: 'vertical', left: 10, top: 'middle' },
  473. color: ['#667eea', '#4facfe', '#43e97b', '#fa709a', '#f5576c'],
  474. series: [{
  475. name: '供应商采购占比', type: 'pie', radius: ['40%', '70%'], center: ['60%', '50%'],
  476. data: data, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
  477. label: { formatter: '{b}: {d}%' },
  478. emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
  479. }]
  480. });
  481. }
  482. function renderTransferChart() {
  483. if (!transferChartRef.value) return;
  484. if (transferChart) transferChart.dispose();
  485. transferChart = echarts.init(transferChartRef.value);
  486. const list = stats.value.transferList || [];
  487. const names = list.map((item: any) => item.name);
  488. const values = list.map((item: any) => Number(item.value));
  489. transferChart.setOption({
  490. tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
  491. grid: { top: 30, left: 100, right: 30, bottom: 30 },
  492. xAxis: { type: 'value', name: '万元' },
  493. yAxis: { type: 'category', data: names },
  494. series: [{
  495. name: '调拨金额', type: 'bar', data: values,
  496. itemStyle: {
  497. borderRadius: [0, 4, 4, 0],
  498. color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
  499. { offset: 0, color: '#4facfe' }, { offset: 1, color: '#00f2fe' }
  500. ])
  501. },
  502. barWidth: 24,
  503. label: { show: true, position: 'right', formatter: '{c}万元' }
  504. }]
  505. });
  506. }
  507. function renderDrugStoreStockChart() {
  508. if (!drugStoreStockChartRef.value) return;
  509. if (drugStoreStockChart) drugStoreStockChart.dispose();
  510. drugStoreStockChart = echarts.init(drugStoreStockChartRef.value);
  511. const list = stats.value.drugStoreStockTypeList || [];
  512. const data = list.map((item: any) => ({ name: item.name, value: Number(item.value) }));
  513. drugStoreStockChart.setOption({
  514. tooltip: { trigger: 'item', formatter: '{b}: {c}万元 ({d}%)' },
  515. legend: { orient: 'vertical', left: 10, top: 'middle' },
  516. series: [{
  517. name: '药库库存占比', type: 'pie', radius: ['40%', '70%'], center: ['60%', '50%'],
  518. data: data, itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
  519. label: { formatter: '{b}: {d}%' },
  520. emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } }
  521. }]
  522. });
  523. }
  524. function handleResize() {
  525. outpatientDrugTypeChart?.resize();
  526. returnReasonChart?.resize();
  527. stockTypeChart?.resize();
  528. supplierChart?.resize();
  529. transferChart?.resize();
  530. drugStoreStockChart?.resize();
  531. }
  532. onMounted(() => {
  533. loadStats();
  534. window.addEventListener('resize', handleResize);
  535. });
  536. onBeforeUnmount(() => {
  537. window.removeEventListener('resize', handleResize);
  538. outpatientDrugTypeChart?.dispose();
  539. returnReasonChart?.dispose();
  540. stockTypeChart?.dispose();
  541. supplierChart?.dispose();
  542. transferChart?.dispose();
  543. drugStoreStockChart?.dispose();
  544. });
  545. </script>
  546. <style scoped lang="scss">
  547. .analysis-page { padding: 20px; background: #f5f7fa; min-height: 100vh; }
  548. .page-header { background: white; padding: 20px 24px; border-radius: 8px; margin-bottom: 16px;
  549. h2 { margin: 0 0 4px; font-size: 22px; color: #2c3e50; }
  550. .subtitle { margin: 0; font-size: 13px; color: #909399; }
  551. }
  552. .section-title { font-size: 18px; font-weight: 700; color: #2c3e50; margin: 24px 0 12px; padding-left: 12px; border-left: 4px solid #667eea; }
  553. .sub-title { font-size: 15px; font-weight: 600; color: #606266; margin: 16px 0 10px; }
  554. .metrics-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 16px; }
  555. .metrics-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 16px; }
  556. .metrics-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 16px; }
  557. .metrics-grid-5 { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 16px; }
  558. .metric-card { background: white; padding: 20px; border-radius: 8px; display: flex; gap: 16px; align-items: center;
  559. .metric-icon { width: 56px; height: 56px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: white; flex-shrink: 0; }
  560. .metric-content { flex: 1; }
  561. .metric-value { font-size: 22px; font-weight: 700; color: #2c3e50;
  562. .unit { font-size: 13px; font-weight: 400; color: #909399; margin-left: 2px; }
  563. }
  564. .metric-label { font-size: 13px; color: #909399; margin-top: 4px; }
  565. }
  566. .charts-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px; }
  567. .chart-card { background: white; border-radius: 8px; padding: 20px;
  568. .chart-header { margin-bottom: 12px; h3 { margin: 0; font-size: 15px; color: #2c3e50; } }
  569. .chart-container { height: 320px; }
  570. }
  571. .inline-metrics { display: flex; gap: 24px; padding: 20px 0; justify-content: space-around; }
  572. .inline-metric { text-align: center;
  573. .inline-value { font-size: 28px; font-weight: 700; color: #2c3e50;
  574. .unit { font-size: 13px; font-weight: 400; color: #909399; margin-left: 2px; }
  575. }
  576. .inline-label { font-size: 13px; color: #909399; margin-top: 8px; }
  577. }
  578. </style>
  579. `;
  580. fs.writeFileSync(path, content, 'utf8');
  581. console.log('File written successfully, size:', fs.statSync(path).size);