prescription-review.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  1. import type { TransformList, TransformRecord } from '#/api';
  2. /** 处方点评(接口就绪后替换为真实请求) */
  3. export namespace PrescriptionReviewModel {
  4. export interface ReviewExpert extends TransformRecord {
  5. access: string;
  6. name: string;
  7. hospitalName: string;
  8. phone: string;
  9. /** 0 启用,1 禁用 */
  10. status: 0 | 1;
  11. institutionId?: string;
  12. pid?: string;
  13. }
  14. /** 点评指标来源:系统内置 / 用户新增 */
  15. export type ReviewIndicatorSource = 'custom' | 'system';
  16. export interface ReviewIndicatorCategory extends TransformRecord {
  17. name: string;
  18. /** 系统内置分类不可删除 */
  19. source: ReviewIndicatorSource;
  20. /** 分类下点评项数量 */
  21. indicatorCount?: number;
  22. }
  23. export interface ReviewIndicator extends TransformRecord {
  24. categoryId: string;
  25. categoryName: string;
  26. name: string;
  27. source: ReviewIndicatorSource;
  28. /** 是否关联中药 */
  29. associatedChineseMedicine: boolean;
  30. remark?: string;
  31. /** 0 启用,1 禁用 */
  32. status: 0 | 1;
  33. }
  34. }
  35. const MOCK_EXPERTS: PrescriptionReviewModel.ReviewExpert[] = [
  36. {
  37. id: '1',
  38. access: 'za00011',
  39. name: '孙明1',
  40. hospitalName: '蒋村社区卫生服务中心',
  41. phone: '13112342344',
  42. status: 1,
  43. institutionId: 'org-1',
  44. createUser: '张三',
  45. createTime: '2023-05-15 13:35',
  46. },
  47. {
  48. id: '2',
  49. access: 'za00012',
  50. name: '孙明2',
  51. hospitalName: '蒋村社区卫生服务中心',
  52. phone: '13156782344',
  53. status: 0,
  54. institutionId: 'org-1',
  55. createUser: '张三',
  56. createTime: '2023-05-16 09:20',
  57. },
  58. {
  59. id: '3',
  60. access: 'za00013',
  61. name: '李华',
  62. hospitalName: '西溪社区卫生服务中心',
  63. phone: '13298765432',
  64. status: 0,
  65. institutionId: 'org-2',
  66. createUser: '李四',
  67. createTime: '2023-06-01 10:15',
  68. },
  69. {
  70. id: '4',
  71. access: 'za00014',
  72. name: '王芳',
  73. hospitalName: '西溪社区卫生服务中心',
  74. phone: '13387654321',
  75. status: 1,
  76. institutionId: 'org-2',
  77. createUser: '李四',
  78. createTime: '2023-06-02 14:40',
  79. },
  80. {
  81. id: '5',
  82. access: 'za00015',
  83. name: '赵强',
  84. hospitalName: '蒋村社区卫生服务中心',
  85. phone: '13567891234',
  86. status: 0,
  87. institutionId: 'org-1',
  88. createUser: '王五',
  89. createTime: '2023-07-10 08:50',
  90. },
  91. {
  92. id: '6',
  93. access: 'za00016',
  94. name: '周敏',
  95. hospitalName: '文新社区卫生服务中心',
  96. phone: '13678901234',
  97. status: 1,
  98. institutionId: 'org-3',
  99. createUser: '王五',
  100. createTime: '2023-07-11 16:25',
  101. },
  102. {
  103. id: '7',
  104. access: 'za00017',
  105. name: '吴磊',
  106. hospitalName: '文新社区卫生服务中心',
  107. phone: '13789012345',
  108. status: 0,
  109. institutionId: 'org-3',
  110. createUser: '赵六',
  111. createTime: '2023-08-05 11:30',
  112. },
  113. {
  114. id: '8',
  115. access: 'za00018',
  116. name: '郑洁',
  117. hospitalName: '蒋村社区卫生服务中心',
  118. phone: '13890123456',
  119. status: 0,
  120. institutionId: 'org-1',
  121. createUser: '赵六',
  122. createTime: '2023-08-06 15:45',
  123. },
  124. {
  125. id: '9',
  126. access: 'za00019',
  127. name: '钱伟',
  128. hospitalName: '西溪社区卫生服务中心',
  129. phone: '13901234567',
  130. status: 1,
  131. institutionId: 'org-2',
  132. createUser: '张三',
  133. createTime: '2023-09-12 09:10',
  134. },
  135. {
  136. id: '10',
  137. access: 'za00020',
  138. name: '孙丽',
  139. hospitalName: '文新社区卫生服务中心',
  140. phone: '13012345678',
  141. status: 0,
  142. institutionId: 'org-3',
  143. createUser: '李四',
  144. createTime: '2023-09-13 13:55',
  145. },
  146. ];
  147. function filterExperts(
  148. items: PrescriptionReviewModel.ReviewExpert[],
  149. query?: Partial<PrescriptionReviewModel.ReviewExpert>,
  150. ) {
  151. const institutionId = query?.institutionId;
  152. if (!institutionId) return items;
  153. return items.filter((item) => item.institutionId === String(institutionId));
  154. }
  155. /** 点评专家列表(当前为本地 mock,后期对接后端分页接口) */
  156. export function listReviewExpertsMethod(
  157. page = 1,
  158. size = 10,
  159. query?: Partial<PrescriptionReviewModel.ReviewExpert>,
  160. ): Promise<TransformList<PrescriptionReviewModel.ReviewExpert>> {
  161. const filtered = filterExperts(MOCK_EXPERTS, query);
  162. const start = (page - 1) * size;
  163. const items = filtered.slice(start, start + size);
  164. return Promise.resolve({
  165. items,
  166. total: filtered.length,
  167. data: { page, size, total: filtered.length },
  168. });
  169. }
  170. /** 点评专家状态更改(当前为本地 mock,后期对接后端接口) */
  171. export function updateReviewExpertStatusMethod(
  172. expertId: string,
  173. { status }: { status: 0 | 1 },
  174. ) {
  175. const expert = MOCK_EXPERTS.find(
  176. (item) => item.id === expertId || item.pid === expertId,
  177. );
  178. if (!expert) {
  179. return Promise.reject(new Error('专家不存在'));
  180. }
  181. expert.status = status;
  182. return Promise.resolve(true);
  183. }
  184. const MOCK_INDICATOR_CATEGORIES: PrescriptionReviewModel.ReviewIndicatorCategory[] =
  185. [
  186. { id: 'cat-1', name: '适应症', source: 'system' },
  187. { id: 'cat-2', name: '用法用量', source: 'system' },
  188. { id: 'cat-3', name: '配伍禁忌', source: 'system' },
  189. { id: 'cat-4', name: '特殊人群禁忌', source: 'system' },
  190. { id: 'cat-5', name: '其他禁忌', source: 'system' },
  191. ];
  192. const MOCK_INDICATORS: PrescriptionReviewModel.ReviewIndicator[] = [
  193. {
  194. id: 'ind-1',
  195. categoryId: 'cat-1',
  196. categoryName: '适应症',
  197. name: '病证禁忌',
  198. source: 'system',
  199. associatedChineseMedicine: false,
  200. status: 1,
  201. },
  202. {
  203. id: 'ind-2',
  204. categoryId: 'cat-1',
  205. categoryName: '适应症',
  206. name: '慎用',
  207. source: 'system',
  208. associatedChineseMedicine: false,
  209. status: 1,
  210. },
  211. {
  212. id: 'ind-3',
  213. categoryId: 'cat-1',
  214. categoryName: '适应症',
  215. name: '禁用',
  216. source: 'system',
  217. associatedChineseMedicine: false,
  218. status: 1,
  219. },
  220. {
  221. id: 'ind-4',
  222. categoryId: 'cat-1',
  223. categoryName: '适应症',
  224. name: '忌用',
  225. source: 'system',
  226. associatedChineseMedicine: false,
  227. status: 1,
  228. },
  229. {
  230. id: 'ind-5',
  231. categoryId: 'cat-2',
  232. categoryName: '用法用量',
  233. name: '超剂量用药',
  234. source: 'system',
  235. associatedChineseMedicine: false,
  236. status: 1,
  237. },
  238. {
  239. id: 'ind-6',
  240. categoryId: 'cat-3',
  241. categoryName: '配伍禁忌',
  242. name: '十八反十九畏',
  243. source: 'system',
  244. associatedChineseMedicine: true,
  245. status: 0,
  246. },
  247. {
  248. id: 'ind-7',
  249. categoryId: 'cat-1',
  250. categoryName: '适应症',
  251. name: '点评项1',
  252. source: 'custom',
  253. associatedChineseMedicine: true,
  254. createUser: '陆长林',
  255. createTime: '2023-09-23 15:29:38',
  256. status: 0,
  257. },
  258. {
  259. id: 'ind-8',
  260. categoryId: 'cat-1',
  261. categoryName: '适应症',
  262. name: '点评项2',
  263. source: 'custom',
  264. associatedChineseMedicine: true,
  265. createUser: '陆长林',
  266. createTime: '2023-09-23 15:29:38',
  267. status: 0,
  268. },
  269. ];
  270. function getCategoryIndicatorCount(categoryId: string) {
  271. return MOCK_INDICATORS.filter((item) => item.categoryId === categoryId).length;
  272. }
  273. function withCategoryIndicatorCount(
  274. categories: PrescriptionReviewModel.ReviewIndicatorCategory[],
  275. ) {
  276. return categories.map((item) => ({
  277. ...item,
  278. indicatorCount: getCategoryIndicatorCount(item.id),
  279. }));
  280. }
  281. function getNextCustomCategoryName() {
  282. const prefix = '新加分类';
  283. let index = 1;
  284. while (
  285. MOCK_INDICATOR_CATEGORIES.some((item) => item.name === `${prefix}${index}`)
  286. ) {
  287. index += 1;
  288. }
  289. return `${prefix}${index}`;
  290. }
  291. function syncIndicatorCategoryName(
  292. indicator: PrescriptionReviewModel.ReviewIndicator,
  293. ) {
  294. const category = MOCK_INDICATOR_CATEGORIES.find(
  295. (item) => item.id === indicator.categoryId,
  296. );
  297. indicator.categoryName = category?.name ?? indicator.categoryName;
  298. }
  299. function filterIndicators(
  300. items: PrescriptionReviewModel.ReviewIndicator[],
  301. query?: Partial<
  302. PrescriptionReviewModel.ReviewIndicator & { categoryId?: string }
  303. >,
  304. ) {
  305. let result = [...items];
  306. if (query?.categoryId) {
  307. result = result.filter((item) => item.categoryId === query.categoryId);
  308. }
  309. if (query?.name?.trim()) {
  310. const keyword = query.name.trim();
  311. result = result.filter((item) => item.name.includes(keyword));
  312. }
  313. return result;
  314. }
  315. /** 点评指标分类列表(当前为本地 mock) */
  316. export function listReviewIndicatorCategoriesMethod(): Promise<
  317. PrescriptionReviewModel.ReviewIndicatorCategory[]
  318. > {
  319. return Promise.resolve(withCategoryIndicatorCount([...MOCK_INDICATOR_CATEGORIES]));
  320. }
  321. /** 新增点评指标分类(当前为本地 mock) */
  322. export function createReviewIndicatorCategoryMethod(name?: string) {
  323. const trimmed = (name ?? getNextCustomCategoryName()).trim();
  324. if (!trimmed) {
  325. return Promise.reject(new Error('分类名称不能为空'));
  326. }
  327. if (MOCK_INDICATOR_CATEGORIES.some((item) => item.name === trimmed)) {
  328. return Promise.reject(new Error('分类名称已存在'));
  329. }
  330. const category: PrescriptionReviewModel.ReviewIndicatorCategory = {
  331. id: `cat-${Date.now()}`,
  332. name: trimmed,
  333. source: 'custom',
  334. indicatorCount: 0,
  335. };
  336. MOCK_INDICATOR_CATEGORIES.push(category);
  337. return Promise.resolve(category);
  338. }
  339. /** 更新点评指标分类名称(当前为本地 mock,系统内置不可改) */
  340. export function updateReviewIndicatorCategoryMethod(
  341. categoryId: string,
  342. { name }: { name: string },
  343. ) {
  344. const category = MOCK_INDICATOR_CATEGORIES.find((item) => item.id === categoryId);
  345. if (!category) {
  346. return Promise.reject(new Error('分类不存在'));
  347. }
  348. if (category.source === 'system') {
  349. return Promise.reject(new Error('系统内置分类不可修改'));
  350. }
  351. const trimmed = name.trim();
  352. if (!trimmed) {
  353. return Promise.reject(new Error('分类名称不能为空'));
  354. }
  355. if (
  356. MOCK_INDICATOR_CATEGORIES.some(
  357. (item) => item.id !== categoryId && item.name === trimmed,
  358. )
  359. ) {
  360. return Promise.reject(new Error('分类名称已存在'));
  361. }
  362. category.name = trimmed;
  363. MOCK_INDICATORS.forEach((item) => {
  364. if (item.categoryId === categoryId) {
  365. item.categoryName = trimmed;
  366. }
  367. });
  368. return Promise.resolve({
  369. ...category,
  370. indicatorCount: getCategoryIndicatorCount(categoryId),
  371. });
  372. }
  373. /** 点评指标分类排序(当前为本地 mock) */
  374. export function sortReviewIndicatorCategoriesMethod(orderedIds: string[]) {
  375. if (orderedIds.length !== MOCK_INDICATOR_CATEGORIES.length) {
  376. return Promise.reject(new Error('分类排序数据不完整'));
  377. }
  378. const categoryMap = new Map(
  379. MOCK_INDICATOR_CATEGORIES.map((item) => [item.id, item]),
  380. );
  381. const sorted = orderedIds.map((id) => categoryMap.get(id)).filter(Boolean) as
  382. PrescriptionReviewModel.ReviewIndicatorCategory[];
  383. if (sorted.length !== MOCK_INDICATOR_CATEGORIES.length) {
  384. return Promise.reject(new Error('分类排序数据无效'));
  385. }
  386. MOCK_INDICATOR_CATEGORIES.splice(0, MOCK_INDICATOR_CATEGORIES.length, ...sorted);
  387. return Promise.resolve(withCategoryIndicatorCount([...MOCK_INDICATOR_CATEGORIES]));
  388. }
  389. /** 删除点评指标分类(当前为本地 mock,系统内置不可删) */
  390. export function deleteReviewIndicatorCategoryMethod(categoryId: string) {
  391. const index = MOCK_INDICATOR_CATEGORIES.findIndex(
  392. (item) => item.id === categoryId,
  393. );
  394. if (index === -1) {
  395. return Promise.reject(new Error('分类不存在'));
  396. }
  397. const category = MOCK_INDICATOR_CATEGORIES[index];
  398. if (!category) {
  399. return Promise.reject(new Error('分类不存在'));
  400. }
  401. if (category.source === 'system') {
  402. return Promise.reject(new Error('系统内置分类不可删除'));
  403. }
  404. const inUse = MOCK_INDICATORS.some((item) => item.categoryId === categoryId);
  405. if (inUse) {
  406. return Promise.reject(new Error('该分类下存在点评项,无法删除'));
  407. }
  408. MOCK_INDICATOR_CATEGORIES.splice(index, 1);
  409. return Promise.resolve(true);
  410. }
  411. /** 按分类获取点评指标列表(当前为本地 mock) */
  412. export function listReviewIndicatorsByCategoryMethod(categoryId: string) {
  413. const category = MOCK_INDICATOR_CATEGORIES.find((item) => item.id === categoryId);
  414. if (!category) {
  415. return Promise.reject(new Error('分类不存在'));
  416. }
  417. return Promise.resolve(
  418. MOCK_INDICATORS.filter((item) => item.categoryId === categoryId),
  419. );
  420. }
  421. /** 点评指标排序(当前为本地 mock,按分类内顺序) */
  422. export function sortReviewIndicatorsMethod(
  423. categoryId: string,
  424. orderedIds: string[],
  425. ) {
  426. const categoryIndicators = MOCK_INDICATORS.filter(
  427. (item) => item.categoryId === categoryId,
  428. );
  429. if (orderedIds.length !== categoryIndicators.length) {
  430. return Promise.reject(new Error('点评项排序数据不完整'));
  431. }
  432. const indicatorMap = new Map(categoryIndicators.map((item) => [item.id, item]));
  433. const sorted = orderedIds.map((id) => indicatorMap.get(id)).filter(Boolean) as
  434. PrescriptionReviewModel.ReviewIndicator[];
  435. if (sorted.length !== categoryIndicators.length) {
  436. return Promise.reject(new Error('点评项排序数据无效'));
  437. }
  438. const others = MOCK_INDICATORS.filter((item) => item.categoryId !== categoryId);
  439. MOCK_INDICATORS.splice(0, MOCK_INDICATORS.length, ...others, ...sorted);
  440. return Promise.resolve(sorted);
  441. }
  442. /** 点评指标列表(当前为本地 mock,后期对接后端分页接口) */
  443. export function listReviewIndicatorsMethod(
  444. page = 1,
  445. size = 10,
  446. query?: Partial<
  447. PrescriptionReviewModel.ReviewIndicator & { categoryId?: string }
  448. >,
  449. ): Promise<TransformList<PrescriptionReviewModel.ReviewIndicator>> {
  450. const filtered = filterIndicators(MOCK_INDICATORS, query);
  451. const start = (page - 1) * size;
  452. const items = filtered.slice(start, start + size);
  453. return Promise.resolve({
  454. items,
  455. total: filtered.length,
  456. data: { page, size, total: filtered.length },
  457. });
  458. }
  459. /** 新增点评指标(当前为本地 mock) */
  460. export function createReviewIndicatorMethod(
  461. data: Pick<
  462. PrescriptionReviewModel.ReviewIndicator,
  463. 'categoryId' | 'name' | 'associatedChineseMedicine' | 'remark' | 'status'
  464. >,
  465. ) {
  466. const category = MOCK_INDICATOR_CATEGORIES.find(
  467. (item) => item.id === data.categoryId,
  468. );
  469. if (!category) {
  470. return Promise.reject(new Error('点评项分类不存在'));
  471. }
  472. const indicator: PrescriptionReviewModel.ReviewIndicator = {
  473. id: `ind-${Date.now()}`,
  474. categoryId: data.categoryId,
  475. categoryName: category.name,
  476. name: data.name.trim(),
  477. source: 'custom',
  478. associatedChineseMedicine: data.associatedChineseMedicine,
  479. remark: data.remark,
  480. status: data.status ?? 0,
  481. createUser: '陆长林',
  482. createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
  483. };
  484. MOCK_INDICATORS.push(indicator);
  485. return Promise.resolve(indicator);
  486. }
  487. /** 编辑点评指标(当前为本地 mock,系统内置仅可改部分字段) */
  488. export function updateReviewIndicatorMethod(
  489. indicatorId: string,
  490. data: Partial<
  491. Pick<
  492. PrescriptionReviewModel.ReviewIndicator,
  493. | 'categoryId'
  494. | 'name'
  495. | 'associatedChineseMedicine'
  496. | 'remark'
  497. | 'status'
  498. >
  499. >,
  500. ) {
  501. const indicator = MOCK_INDICATORS.find((item) => item.id === indicatorId);
  502. if (!indicator) {
  503. return Promise.reject(new Error('点评项不存在'));
  504. }
  505. if (data.categoryId) {
  506. indicator.categoryId = data.categoryId;
  507. syncIndicatorCategoryName(indicator);
  508. }
  509. if (data.name !== undefined) indicator.name = data.name.trim();
  510. if (data.associatedChineseMedicine !== undefined) {
  511. indicator.associatedChineseMedicine = data.associatedChineseMedicine;
  512. }
  513. if (data.remark !== undefined) indicator.remark = data.remark;
  514. if (data.status !== undefined) indicator.status = data.status;
  515. return Promise.resolve(indicator);
  516. }
  517. /** 删除点评指标(当前为本地 mock,系统内置不可删) */
  518. export function deleteReviewIndicatorMethod(indicatorId: string) {
  519. const index = MOCK_INDICATORS.findIndex((item) => item.id === indicatorId);
  520. if (index === -1) {
  521. return Promise.reject(new Error('点评项不存在'));
  522. }
  523. const indicator = MOCK_INDICATORS[index];
  524. if (!indicator) {
  525. return Promise.reject(new Error('点评项不存在'));
  526. }
  527. if (indicator.source === 'system') {
  528. return Promise.reject(new Error('系统内置点评项不可删除'));
  529. }
  530. MOCK_INDICATORS.splice(index, 1);
  531. return Promise.resolve(true);
  532. }
  533. /** 点评指标状态更改(当前为本地 mock,后期对接后端接口) */
  534. export function updateReviewIndicatorStatusMethod(
  535. indicatorId: string,
  536. { status }: { status: 0 | 1 },
  537. ) {
  538. const indicator = MOCK_INDICATORS.find((item) => item.id === indicatorId);
  539. if (!indicator) {
  540. return Promise.reject(new Error('点评项不存在'));
  541. }
  542. indicator.status = status;
  543. return Promise.resolve(true);
  544. }