issueService.vue 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365
  1. <script setup lang="ts">
  2. import { useRoute } from 'vue-router';
  3. import { ref, computed, nextTick, h, watch, onMounted } from 'vue';
  4. import { MinusCircleOutlined, EyeOutlined, EditOutlined } from '@ant-design/icons-vue';
  5. import { notification } from 'ant-design-vue';
  6. import { message } from 'ant-design-vue';
  7. import type { OpenConditioningSchemeModel, SystemCwModel } from '@/model/care.model';
  8. import PatientTagWidget from '@/widgets/PatientTagWidget.vue';
  9. import AcupointEdit from '@/service/AcupointEdit.vue';
  10. import ServiceDetail from '@/service/ServiceDetail.vue';
  11. import {
  12. getCpContentListMethod,
  13. getPatientListMethod,
  14. getConditioningRecordDetailMethod,
  15. getPatientConditioningRecordMethod,
  16. addConditioningSchemeMethod,
  17. getCpDetailMethod,
  18. getProvinceMethod,
  19. getCityMethod,
  20. getAreaMethod,
  21. getAvailableCwMethod,
  22. } from '@/request/api/care.api';
  23. import { patientMethod, getPatientTagsMethod } from '@/request/api/patient.api';
  24. import { useRequest, usePagination } from 'alova/client';
  25. import ServicePackageDetail from '@/service/ServicePackageDetail.vue';
  26. import { VxeUI } from 'vxe-pc-ui';
  27. import type { HealthReportVO } from '@/model/health-report.model';
  28. import type { PatientTagVO } from '@/model/patient.model';
  29. import dayjs from 'dayjs';
  30. import { getPatientHealthRecordsMethod } from '@/request/api/report.api';
  31. type FollowModel = Partial<OpenConditioningSchemeModel>;
  32. // const props = defineProps<{ data: FollowModel }>();
  33. const form = reactive<FollowModel>({
  34. id: 0,
  35. patientId: '',
  36. patientName: '',
  37. patientSex: '',
  38. patientAge: 0,
  39. diagnosis: '',
  40. symptom: '',
  41. conditioningWrapId: '',
  42. conditioningWrapName: '',
  43. estimatedStartDate: '',
  44. estimatedEndDate: '',
  45. isDelivery: null,
  46. cost: 0,
  47. healthAnalysisReport: {
  48. willillStateName: '',
  49. willillDegreeName: '',
  50. willillSocialName: '',
  51. willillFunctionName: '',
  52. },
  53. patientMedicalRecord: {
  54. patientId: '', // 患者ID
  55. institutionId: '', // 机构ID
  56. institutionName: '', // 机构名称
  57. diagnosis: '', // 疾病
  58. symptom: '', // 症型
  59. syndrome: '', // 证状
  60. },
  61. items: [],
  62. provinceName: '',
  63. provinceCode: '',
  64. cityName: '',
  65. cityCode: '',
  66. // districtName: '',
  67. // districtCode: '',
  68. areaName: '',
  69. areaCode: '',
  70. detailAddress: '',
  71. phone: '',
  72. progress: '0',
  73. photo: '',
  74. });
  75. const searchName = ref('');
  76. const selectedPackage = ref('');
  77. const currentSelectedPackage = ref<any>(null);
  78. const deliveryChecked = ref(true);
  79. const address = ref({
  80. province: '',
  81. city: '',
  82. district: '',
  83. detail: '',
  84. phone: '',
  85. });
  86. // 机构调理包
  87. const lifeCwData = ref([]);
  88. // 获取可用的服务包
  89. const suggestCwData = ref<string[]>([]);
  90. async function loadAvailableCw(patientId: string,patientConditioningRecordId:string) {
  91. const res: any = await getAvailableCwMethod(patientId,patientConditioningRecordId);
  92. if (res.length > 0) {
  93. lifeCwData.value = res;
  94. // 筛选出可推荐的
  95. suggestCwData.value = lifeCwData.value.filter((item: any) => item.isSuggest==='Y');
  96. }
  97. }
  98. export interface PatientModel {
  99. id: string;
  100. patientId: string;
  101. patientName: string;
  102. patientSex: string;
  103. patientAge: number;
  104. diagnosis: string;
  105. symptom: string;
  106. healthAnalysisReportId: string;
  107. healthAnalysisReport: {
  108. willillStateName: string;
  109. willillDegreeName: string;
  110. willillSocialName: string;
  111. willillFunctionName: string;
  112. };
  113. constitutionGroupName: string;
  114. status?: string;
  115. }
  116. // 患者列表
  117. const patients = ref([]);
  118. // 患者详细信息
  119. interface CpDetail {
  120. womenSpecialPeriod?: string;
  121. cardno?: string;
  122. }
  123. let cpDetail = ref<CpDetail & Partial<PatientModel>>({});
  124. async function getCpDetail(id: string) {
  125. await patientMethod(id).then((res2) => {
  126. cpDetail.value = res2;
  127. });
  128. }
  129. interface PatientRecord {
  130. id: string;
  131. estimatedStartDate: string;
  132. estimatedEndDate: string;
  133. }
  134. // 患者健康分析报告记录
  135. const patientHealthRecords = ref<HealthReportVO[]>([]);
  136. async function loadHealthRecords(patientId?: string) {
  137. try {
  138. const { data } = await getPatientHealthRecordsMethod(1, 100, { patientId: patientId ?? currentPatient.value?.patientId! });
  139. patientHealthRecords.value = data;
  140. } catch (err) {
  141. patientHealthRecords.value = [];
  142. }
  143. }
  144. const patientRecord = ref<PatientRecord[]>([]);
  145. // 获取患者调养记录
  146. function getPatientRecord(id: any) {
  147. patientRecord.value = [];
  148. getPatientConditioningRecordMethod(id).then((res) => {
  149. if (res && res.length > 0) {
  150. patientRecord.value = res;
  151. }
  152. });
  153. }
  154. // const report=ref({})
  155. async function getCpRecordDetail(id: string) {
  156. await getCpDetailMethod({ id }).then((res) => {
  157. formData.items = res?.items ?? [];
  158. // form = res;
  159. form.conditioningWrapName = res?.conditioningWrapName;
  160. form.estimatedStartDate = res?.estimatedStartDate;
  161. form.estimatedEndDate = res?.estimatedEndDate;
  162. form.provinceName = res?.provinceName;
  163. form.cityName = res?.cityName;
  164. form.areaName = res?.areaName;
  165. form.detailAddress = res?.detailAddress;
  166. form.phone = res?.phone;
  167. form.healthAnalysisReport = res.healthAnalysisReport;
  168. selectedProvince.value=res.provinceCode;
  169. selectedCity.value=res.cityCode;
  170. selectedArea.value=res.areaCode
  171. });
  172. }
  173. function getPatientList(id: string) {
  174. if (id) {
  175. getCpDetail(id);
  176. loadTags(id);
  177. loadHealthRecords(id);
  178. getPatientRecord(id);
  179. }
  180. }
  181. const route = useRoute();
  182. onMounted(async () => {
  183. const id = Number(route.query.id); // 获取查询参数 id
  184. // 获取患者列表
  185. const res: any = await getPatientListMethod();
  186. if (res && res.length > 0) {
  187. patients.value = res;
  188. if (id) {
  189. let index = res.findIndex((item: any) => item.patientId === id);
  190. currentPatient.value = res[index];
  191. } else {
  192. currentPatient.value = res[0];
  193. }
  194. }
  195. if (currentPatient.value?.patientId) {
  196. // 获取患者列表
  197. await getPatientList(currentPatient.value?.patientId || '');
  198. // 获取服务包选择列表
  199. await loadAvailableCw(currentPatient.value?.patientId || '',currentPatient.value?.id || '');
  200. }
  201. if (currentPatient.value?.id) {
  202. // 获取调养记录
  203. await getCpRecordDetail(currentPatient.value?.id || '');
  204. }
  205. // 获取省份
  206. await loadProvinces();
  207. });
  208. // 患者标签
  209. const patientTags = ref<PatientTagVO[]>([]);
  210. function loadTags(patientId: string) {
  211. getPatientTagsMethod(patientId).then((res) => {
  212. patientTags.value = res;
  213. });
  214. }
  215. // 展示的患者数据
  216. const filteredPatients: any = computed(() => {
  217. if(patients.value.length > 0){
  218. return patients.value.filter((p: any) => (p.patientName || '').includes(searchName.value));
  219. }
  220. return [];
  221. });
  222. // 默认显示第一个患者
  223. const currentPatient = ref<PatientModel>();
  224. // 点击切换患者
  225. async function selectPatient(item: any) {
  226. currentPatient.value = item;
  227. // 获取患者信息
  228. await getPatientList(item.patientId);
  229. await loadAvailableCw(item.patientId || '',item.id || '');
  230. // 获取患者调养记录
  231. await getCpRecordDetail(item.id);
  232. // 清空服务包选择
  233. selectedPackage.value = '';
  234. currentSelectedPackage.value = null;
  235. btnType.value=''
  236. }
  237. const btnType = ref('');
  238. // 打开调养记录
  239. function openRecord(item: any) {
  240. const status=currentPatient.value.status;
  241. const types = 'record';
  242. VxeUI.modal.open({
  243. id: 'servicePackageDetail-modal',
  244. title: '调养记录详情',
  245. height: window.innerHeight,
  246. width: window.innerWidth,
  247. escClosable: true,
  248. destroyOnClose: true,
  249. slots: {
  250. default() {
  251. return h(ServicePackageDetail, <any>{
  252. data: { ...item, types ,status},
  253. onSubmit(data: any) {
  254. selectedPackage.value=data.conditioningWrapId;
  255. formData.items = data?.items ?? [];
  256. form.conditioningWrapName = data.conditioningWrapName;
  257. form.conditioningWrapId = data.conditioningWrapId;
  258. },
  259. });
  260. },
  261. },
  262. });
  263. }
  264. const formData = reactive<FollowModel>({
  265. price: 0, //总计价格
  266. cwPatientMatchRules: [
  267. {
  268. diagnoseDiseaseNames: [],
  269. diagnoseSyndromeNames: [],
  270. constitutionGroupNames: [],
  271. diagnoseDisease: {
  272. id: '',
  273. code: '',
  274. name: '',
  275. optionalWords: '',
  276. attributes: [],
  277. children: [],
  278. },
  279. diagnoseSyndrome: {
  280. code: '',
  281. name: '',
  282. analysis: '',
  283. remark: '',
  284. },
  285. constitutionGroup: {
  286. id: '',
  287. code: '',
  288. name: '',
  289. definition: '',
  290. remark: '',
  291. },
  292. },
  293. ], // 适用情况
  294. items: [], // Initialize as empty array
  295. });
  296. const emptyRow = {
  297. id: '',
  298. conditioningWrapId: '',
  299. conditioningProgramId: 0,
  300. days: '',
  301. frequencyType: '',
  302. frequencyTypeing: [],
  303. frequencyMeasure: '',
  304. totalMeasure: '',
  305. totalPrice: '',
  306. initialDay: '',
  307. conditioningProgramDetail: {
  308. id: '',
  309. name: '',
  310. conditioningProgramType: '',
  311. pricingType: '',
  312. cpFixedPricingRule: {
  313. unitPrice: 0,
  314. pricingUnit: '',
  315. convertDose: 0,
  316. convertUnit: '',
  317. },
  318. cpDynamicPricingRule: [],
  319. cpMedicines: [],
  320. effect: '',
  321. isOffline: null,
  322. isDelivery: null,
  323. photo: '',
  324. institutionId: '',
  325. institutionName: '',
  326. remark: '',
  327. },
  328. cwcpAcuMeridians: [],
  329. cwcpAcuPoints: [],
  330. conditioningProgramSupplierName: '',
  331. };
  332. const projectSearch = ref('');
  333. const showProjectPopover = ref(false);
  334. const displayTableData = computed(() => {
  335. return [...(formData.items ?? []), { ...emptyRow }];
  336. });
  337. const isShowDelivery = ref<boolean>(false);
  338. // 监听 displayTableData 的变化
  339. watch(displayTableData, (newValue, oldValue) => {
  340. if (newValue.length > 0) {
  341. isShowDelivery.value = newValue.some((item) => {
  342. return item.conditioningProgramDetail?.isDelivery === 'Y';
  343. });
  344. newValue.forEach((row: any) => {
  345. row.frequencyTypeing = row.frequencyType ? [row.frequencyType] : [];
  346. });
  347. }
  348. });
  349. const totalPrice = computed(() => {
  350. return (formData.items ?? []).reduce((sum, row) => {
  351. const price = Number(row?.totalPrice) || 0;
  352. return sum + price;
  353. }, 0);
  354. });
  355. function onSelectProject({ row }: any) {
  356. if ((formData.items ?? []).some((item) => item.conditioningProgramDetail?.name === row.name)) {
  357. notification.warning({ message: '不能重复添加该项目' });
  358. return;
  359. }
  360. // 添加新行到主表格
  361. if (!formData.items) formData.items = [];
  362. formData.items.push({
  363. id: '',
  364. conditioningProgramId: row.id || '',
  365. // conditioningProgramId: 0,
  366. days: '',
  367. frequencyType: '',
  368. frequencyTypeing: [],
  369. frequencyMeasure: '',
  370. totalMeasure: '',
  371. totalPrice: '',
  372. initialDay: '',
  373. cwcpAcuMeridians: [],
  374. cwcpAcuPoints: [],
  375. conditioningProgramDetail: {
  376. ...row,
  377. id: row.id || '',
  378. name: row.name || '',
  379. conditioningProgramType: row.conditioningProgramType || '',
  380. effect: row.effect || '',
  381. pricingType: row.pricingType || '',
  382. cpFixedPricingRule: {
  383. unitPrice: row?.cpFixedPricingRule?.unitPrice || 0,
  384. pricingUnit: row?.cpFixedPricingRule?.pricingUnit || '',
  385. convertDose: row?.cpFixedPricingRule?.convertDose || 0,
  386. convertUnit: row?.cpFixedPricingRule?.convertUnit || '',
  387. },
  388. cpDynamicPricingRule: row?.cpDynamicPricingRule || [],
  389. cpMedicines: row?.cpMedicines || [],
  390. isOffline: row?.isOffline || null,
  391. isDelivery: row?.isDelivery || null,
  392. photo: row?.photo || '',
  393. conditioningProgramSupplierName: row?.conditioningProgramSupplierName || '',
  394. },
  395. remark: '',
  396. });
  397. // 关闭弹窗
  398. showProjectPopover.value = false;
  399. // 清空搜索
  400. projectSearch.value = '';
  401. }
  402. function removeTableRow(idx: number) {
  403. if (idx < (formData.items ?? []).length) {
  404. formData.items?.splice(idx, 1);
  405. }
  406. }
  407. function onPreview(row) {
  408. if (row.conditioningProgramDetail.id) {
  409. // 这里写你的预览逻辑
  410. VxeUI.modal.open({
  411. title: `预览`,
  412. height: 600,
  413. width: 950,
  414. escClosable: true,
  415. destroyOnClose: true,
  416. id: `preview-modal`,
  417. remember: true,
  418. storage: true,
  419. slots: {
  420. default() {
  421. return h(ServiceDetail, {
  422. data: row.conditioningProgramDetail,
  423. onSubmit(data: SystemCwModel) {
  424. VxeUI.modal.close(`preview-modal`);
  425. },
  426. });
  427. },
  428. },
  429. });
  430. } else {
  431. message.warning('请先添加服务包');
  432. }
  433. }
  434. function detailPreview(row) {
  435. if (row.id) {
  436. // 这里写你的预览逻辑
  437. VxeUI.modal.open({
  438. title: `预览`,
  439. height: 600,
  440. width: 950,
  441. escClosable: true,
  442. destroyOnClose: true,
  443. id: `preview-modal`,
  444. remember: true,
  445. storage: true,
  446. slots: {
  447. default() {
  448. return h(ServiceDetail, {
  449. data: row,
  450. onSubmit: (data) => {
  451. VxeUI.modal.close(`preview-modal`);
  452. },
  453. });
  454. },
  455. },
  456. });
  457. showProjectPopover.value = false;
  458. } else {
  459. message.warning('请先添加服务包');
  460. }
  461. }
  462. function editPart(row) {
  463. VxeUI.modal.open({
  464. title: `编辑部位`,
  465. height: 700,
  466. width: 750,
  467. escClosable: true,
  468. destroyOnClose: true,
  469. id: `edit-part-modal`,
  470. remember: true,
  471. storage: true,
  472. slots: {
  473. default() {
  474. return h(AcupointEdit, <any>{
  475. data: row,
  476. onSubmit(data: any) {
  477. VxeUI.modal.close(`edit-part-modal`);
  478. },
  479. });
  480. },
  481. },
  482. });
  483. }
  484. const allProjects = ref<
  485. Array<{
  486. name: string;
  487. conditioningProgramType?: string;
  488. effect?: string;
  489. }>
  490. >([]);
  491. const { loading: projectLoading, onSuccess } = useRequest(() => getCpContentListMethod(), {
  492. immediate: true,
  493. });
  494. onSuccess(({ data }) => {
  495. allProjects.value = data;
  496. });
  497. const filteredProjects = computed(() => {
  498. const searchText = projectSearch.value.toLowerCase();
  499. return allProjects.value.filter(
  500. (p) => p.name.toLowerCase().includes(searchText) || p?.conditioningProgramType?.toLowerCase().includes(searchText) || p.effect?.toLowerCase().includes(searchText)
  501. );
  502. });
  503. // 添加计算数量的函数
  504. function calculateCount(row: any) {
  505. const pricingType = row.conditioningProgramDetail.pricingType;
  506. const period = Number(row.days) || 0;
  507. const frequency = Number(row.frequencyMeasure) || 0;
  508. const maxCount = row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.max;
  509. const acCount = (row.acuMeridianNames?.length ?? 0) + (row.acuPointNames?.length ?? 0);
  510. // 一口价
  511. if (pricingType === '0') {
  512. // 检查是否选择了"不限"
  513. if (row.frequencyType === '不限') {
  514. row.frequencyMeasure = ''; // 重置 frequencyMeasure
  515. row.totalMeasure = 1;
  516. } else {
  517. const convertDose = Number(row.conditioningProgramDetail.cpFixedPricingRule.convertDose) || 0;
  518. const frequencyType = Number(row.frequencyType) || 0;
  519. row.totalMeasure = Math.ceil(((period / frequencyType) * frequency) / convertDose);
  520. }
  521. // 获取单价
  522. const unitPrice = Number(row.conditioningProgramDetail?.cpFixedPricingRule?.unitPrice) || 0;
  523. // 计算总价
  524. row.totalPrice = (row.totalMeasure * unitPrice).toFixed(2);
  525. } else if (pricingType === '1') {
  526. // 按穴位计价
  527. const frequencyType = Number(row.frequencyType) || 0;
  528. row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  529. if (acCount > maxCount) {
  530. if (row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.priceType === 0) {
  531. // 单价
  532. if (acCount > 0) {
  533. let unitPrice: number = row.conditioningProgramDetail.cpDynamicPricingRule[1].price * acCount;
  534. row.unitPrice = unitPrice;
  535. row.totalPrice = Math.ceil((period / frequencyType) * frequency * unitPrice);
  536. } else {
  537. row.unitPrice = '-';
  538. row.totalPrice = 0;
  539. }
  540. } else if (row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.priceType === 1) {
  541. // 一口价
  542. row.unitPrice = '-';
  543. row.totalPrice = row.conditioningProgramDetail.cpDynamicPricingRule[1].price;
  544. }
  545. } else {
  546. if (row.conditioningProgramDetail.cpDynamicPricingRule?.length > 0) {
  547. if (row.conditioningProgramDetail.cpDynamicPricingRule?.[0]?.priceType === 0) {
  548. // 单价
  549. if (acCount > 0) {
  550. let unitPrice: number = row.conditioningProgramDetail.cpDynamicPricingRule[0].price * acCount;
  551. row.unitPrice = unitPrice;
  552. row.totalPrice = Math.ceil((period / frequencyType) * frequency * unitPrice);
  553. } else {
  554. row.unitPrice = '-';
  555. row.totalPrice = 0;
  556. }
  557. } else if (row.conditioningProgramDetail.cpDynamicPricingRule?.[0]?.priceType === 1) {
  558. // 一口价
  559. row.unitPrice = '-';
  560. row.totalPrice = row.conditioningProgramDetail.cpDynamicPricingRule[0].price;
  561. }
  562. }
  563. }
  564. }
  565. }
  566. // 添加监听器
  567. watch(totalPrice, (val) => {
  568. formData.price = val;
  569. form.cost = val;
  570. });
  571. // 获取调理包详情
  572. async function selectCw(item: any) {
  573. currentSelectedPackage.value = item;
  574. selectedPackage.value = item.id;
  575. item.types = 'institution';
  576. const res: any = await getConditioningRecordDetailMethod(item);
  577. Object.assign(formData, res);
  578. formData.items = res?.items ?? [];
  579. form.conditioningWrapName = item.name;
  580. form.conditioningWrapId = item.id;
  581. form.phone = item.phone;
  582. }
  583. function handleCancel() {
  584. console.log('取消');
  585. }
  586. // 添加监听器
  587. watch(
  588. () => formData.items,
  589. (newData) => {
  590. if (!newData) return;
  591. newData.forEach((row) => {
  592. calculateCount(row);
  593. });
  594. },
  595. { deep: true }
  596. );
  597. // 添加电话号码验证函数
  598. function isValidPhone(phone: string): boolean {
  599. // 中国大陆手机号码正则表达式
  600. const phoneRegex = /^1[3-9]\d{9}$/;
  601. return phoneRegex.test(phone);
  602. }
  603. async function handleSubmit() {
  604. if (!currentPatient.value) {
  605. message.error('请选择患者');
  606. return;
  607. }
  608. if (formData.items) {
  609. formData.items.forEach((item) => {
  610. delete item.id;
  611. });
  612. }
  613. form.id = Number(currentPatient.value.id);
  614. form.patientId = currentPatient.value.patientId;
  615. form.patientName = currentPatient.value.patientName;
  616. form.patientSex = currentPatient.value.patientSex;
  617. form.patientAge = currentPatient.value.patientAge;
  618. form.diagnosis = currentPatient.value.diagnosis;
  619. form.symptom = currentPatient.value.symptom;
  620. form.items = formData.items;
  621. // 添加电话号码验证
  622. if (form.phone && !isValidPhone(form.phone)) {
  623. message.error('请输入有效的手机号码');
  624. return;
  625. }
  626. await addConditioningSchemeMethod(form).then(async () => {
  627. notification.success({ message: '开立成功' });
  628. // 开立成功之后 刷新列表
  629. // 刷新患者列表,并保持当前患者高亮
  630. const patientList = await getPatientListMethod();
  631. patients.value = patientList;
  632. // 重新设置 currentPatient 为刚才的患者
  633. if (currentPatient.value) {
  634. const newCurrent = patientList.find((p) => p.patientId === currentPatient.value.patientId);
  635. if (newCurrent) {
  636. currentPatient.value = newCurrent;
  637. // 获取患者列表
  638. await getPatientList(currentPatient.value?.patientId || '');
  639. // 获取服务包选择列表
  640. await loadAvailableCw(currentPatient.value?.patientId || '',currentPatient.value?.id || '');
  641. // 获取调养记录
  642. await getCpRecordDetail(currentPatient.value?.id || '');
  643. }
  644. }
  645. });
  646. }
  647. // 省市区的数据
  648. const provinceOptions = ref([]);
  649. const cityOptions = ref([]);
  650. const areaOptions = ref([]);
  651. const selectedProvince = ref('');
  652. const selectedCity = ref('');
  653. const selectedArea = ref('');
  654. async function loadProvinces() {
  655. const res: any = await getProvinceMethod();
  656. provinceOptions.value = res.map((item) => ({
  657. value: item.code,
  658. label: item.name,
  659. }));
  660. }
  661. async function loadCities(name: string, provincecode: string) {
  662. if (!provincecode) {
  663. cityOptions.value = [];
  664. areaOptions.value = [];
  665. selectedCity.value = '';
  666. selectedArea.value = '';
  667. return;
  668. }
  669. const res: any = await getCityMethod(name, provincecode);
  670. cityOptions.value = res.map((item) => ({
  671. value: item.code,
  672. label: item.name,
  673. }));
  674. }
  675. async function loadAreas(name: string, citycode: string) {
  676. if (!citycode) {
  677. areaOptions.value = [];
  678. selectedArea.value = '';
  679. return;
  680. }
  681. const res: any = await getAreaMethod(name, citycode);
  682. areaOptions.value = res.map((item) => ({
  683. value: item.code,
  684. label: item.name,
  685. }));
  686. }
  687. function handleProvinceChange(value: string) {
  688. if (btnType.value === '转方案') {
  689. selectedCity.value = '';
  690. selectedArea.value = '';
  691. form.cityCode = '';
  692. form.areaCode = '';
  693. form.detailAddress=''
  694. }
  695. selectedProvince.value = value;
  696. const selectedProvinceOption = provinceOptions.value.find((p) => p.value === value);
  697. form.provinceName = selectedProvinceOption?.label || '';
  698. form.provinceCode = selectedProvinceOption?.value || '';
  699. // 获取城市
  700. loadCities(selectedProvinceOption?.name, value);
  701. }
  702. function handleCityChange(value: string) {
  703. selectedCity.value = value;
  704. const selectedCityOption = cityOptions.value.find((c) => c.value === value);
  705. form.cityName = selectedCityOption?.label || '';
  706. form.cityCode = selectedCityOption?.value || '';
  707. if (btnType.value === '转方案') {
  708. selectedArea.value = '';
  709. form.areaCode = '';
  710. form.detailAddress=''
  711. }
  712. loadAreas(selectedCityOption?.name, value);
  713. }
  714. function handleAreaChange(value: string) {
  715. selectedArea.value = value;
  716. const selectedAreaOption = areaOptions.value.find((a) => a.value === value);
  717. form.areaName = selectedAreaOption?.label || '';
  718. form.areaCode = selectedAreaOption?.value || '';
  719. if (btnType.value === '转方案') {
  720. form.detailAddress=''
  721. }
  722. }
  723. interface PatientInfo {
  724. patientName?: string;
  725. patientSex?: string;
  726. patientAge?: number;
  727. }
  728. interface CpDetailInfo {
  729. womenSpecialPeriod?: string;
  730. cardno?: string;
  731. }
  732. function formatPatientInfo(patient: PatientInfo | null, cpDetail: CpDetailInfo | null): string {
  733. if (!patient) return '';
  734. const parts: string[] = [];
  735. // 姓名
  736. if (patient.patientName) {
  737. parts.push(patient.patientName);
  738. }
  739. // 性别
  740. if (patient.patientSex) {
  741. const gender = patient.patientSex === '1' ? '女' : patient.patientSex === '0' ? '男' : '';
  742. if (gender) {
  743. parts.push(gender);
  744. }
  745. }
  746. // 年龄
  747. if (patient.patientAge) {
  748. parts.push(`${patient.patientAge}岁`);
  749. }
  750. // 特殊时期
  751. parts.push(getWomenSpecialPeriod(cpDetail?.womenSpecialPeriod || '0'));
  752. // 身份证号
  753. if (cpDetail?.cardno) {
  754. parts.push(`身份证号:${cpDetail.cardno}`);
  755. }
  756. return parts.join(',');
  757. }
  758. function getWomenSpecialPeriod(type: string) {
  759. if (type === '0') {
  760. return '无';
  761. } else if (type === '1') {
  762. return '月经期';
  763. } else if (type === '2') {
  764. return '孕期';
  765. } else if (type === '3') {
  766. return '产后';
  767. } else if (type === '4') {
  768. return '哺乳期';
  769. }
  770. }
  771. // 处理日期选择
  772. const handleDateChange = (date: any) => {
  773. if (date) {
  774. form.estimatedStartDate = dayjs(date).format('YYYY-MM-DD');
  775. } else {
  776. form.estimatedStartDate = '';
  777. }
  778. };
  779. watch(showProjectPopover, (val) => {
  780. if (!val) {
  781. projectSearch.value = '';
  782. }
  783. });
  784. function openPatientTagEdit(event: MouseEvent) {
  785. const width = 500;
  786. const offset = 32;
  787. const component = defineAsyncComponent(() => import('@/components/PatientTagEdit.vue'));
  788. const id = `PatientTagEdit`;
  789. VxeUI.modal.open({
  790. id,
  791. title: '标签',
  792. type: 'modal',
  793. position: {
  794. top: event.pageY + offset,
  795. left: event.pageX - width,
  796. },
  797. escClosable: true,
  798. resize: true,
  799. width,
  800. minWidth: width,
  801. mask: false,
  802. slots: {
  803. default() {
  804. return h(component, {
  805. id: currentPatient.value?.patientId!,
  806. tags: patientTags.value,
  807. onDestroy(values?: PatientTagVO[]) {
  808. if (values) {
  809. patientTags.value = values;
  810. }
  811. VxeUI.modal.close(id);
  812. },
  813. });
  814. },
  815. },
  816. });
  817. }
  818. function openHistoryPreviewHandle() {
  819. const data = currentPatient.value;
  820. const patient = { id: data?.patientId };
  821. const report = { id: data?.healthAnalysisReportId };
  822. const component = defineAsyncComponent(() => import('@/components/PatientHealthRecordPreview.vue'));
  823. const id = `drawer:report-history:preview`;
  824. const onDestroy = () => {
  825. VxeUI.drawer.close(id);
  826. };
  827. onDestroy();
  828. VxeUI.drawer.open({
  829. id,
  830. title: `健康档案`,
  831. maskClosable: true,
  832. escClosable: true,
  833. padding: false,
  834. width: window.innerWidth - 256,
  835. slots: {
  836. default() {
  837. return h(component, {
  838. patient,
  839. report,
  840. onDestroy,
  841. onRefresh() {},
  842. });
  843. },
  844. },
  845. onHide() {
  846. VxeUI.modal.close();
  847. },
  848. });
  849. }
  850. function openPatientHealthRecord(row: { id: string }, showType: 'analysis' | 'scheme' = 'analysis') {
  851. const component = defineAsyncComponent(() => import('@/components/ReportPreview.vue'));
  852. const id = `drawer:report:preview`;
  853. const onDestroy = () => {
  854. VxeUI.drawer.close(id);
  855. };
  856. onDestroy();
  857. VxeUI.drawer.open({
  858. id,
  859. mask: true,
  860. lockView: false,
  861. padding: false,
  862. width: window.innerWidth - 256,
  863. escClosable: true,
  864. maskClosable: true,
  865. title: { analysis: `健康分析报告`, scheme: `调理方案` }[showType],
  866. slots: {
  867. default() {
  868. return h(component, {
  869. reportId: row.id.toString(),
  870. type: showType,
  871. onDestroy,
  872. });
  873. },
  874. },
  875. });
  876. }
  877. </script>
  878. <template>
  879. <div class="issue-service-page">
  880. <!-- 左侧患者列表 -->
  881. <div class="left-panel">
  882. <a-input v-model:value="searchName" placeholder="输入姓名搜索" style="margin-bottom: 12px" />
  883. <div
  884. v-if="filteredPatients.length > 0"
  885. class="patient-list"
  886. >
  887. <div
  888. class="patient-item"
  889. v-for="item in filteredPatients"
  890. :key="item.id"
  891. @click="selectPatient(item)"
  892. :class="{ active: currentPatient?.id === item.id }"
  893. style="cursor: pointer"
  894. >
  895. <span>{{ item.patientName }}</span><span v-if="item.patientAge">{{ item.patientAge }}岁</span>
  896. <span v-if="item.status === '0'" style="color: #aaa">(已开)</span>
  897. </div>
  898. </div>
  899. <div v-else style="padding-bottom: 8px; text-align: center; margin-top: 40px">暂无数据</div>
  900. </div>
  901. <!-- 中间主内容 -->
  902. <div class="main-panel">
  903. <!-- 顶部患者信息 -->
  904. <div class="patient-info">
  905. <div style="font-size: 12px">
  906. {{ formatPatientInfo(currentPatient || null, cpDetail) }}
  907. </div>
  908. <div style="margin: 12px 0 12px 0; font-size: 12px">
  909. <span style="color: lightgray" v-if="currentPatient?.diagnosis">诊断:</span><span v-if="currentPatient?.diagnosis">{{ currentPatient?.diagnosis }}</span>
  910. <span style="margin-left: 20px; color: lightgray" v-if="form?.healthAnalysisReport?.willillStateName">欲病状态:</span
  911. ><span>{{ form?.healthAnalysisReport?.willillStateName }}</span>
  912. <span style="margin-left: 20px; color: lightgray" v-if="form?.healthAnalysisReport?.willillDegreeName">欲病程度:</span
  913. ><span>{{ form?.healthAnalysisReport?.willillDegreeName }}</span>
  914. <span style="margin-left: 20px; color: lightgray" v-if="form?.healthAnalysisReport?.willillSocialName">欲病类型:</span
  915. ><span>{{ form?.healthAnalysisReport?.willillSocialName }}</span>
  916. <span style="margin-left: 20px; color: lightgray" v-if="form?.healthAnalysisReport?.willillFunctionName">欲病表现:</span
  917. ><span>{{ form?.healthAnalysisReport?.willillFunctionName }}</span>
  918. <span style="margin-left: 20px; color: lightgray" v-if="form?.healthAnalysisReport?.constitutionGroupName">体质:</span>
  919. <span>{{ form?.healthAnalysisReport?.constitutionGroupName }}</span>
  920. </div>
  921. </div>
  922. <!-- 服务包选择 -->
  923. <div v-if="filteredPatients.length > 0">
  924. <div class="service-select-row">
  925. <span>服务包选择:</span>
  926. <template v-if="currentPatient?.status === '0'">
  927. <span>{{ form.conditioningWrapName }}</span>
  928. </template>
  929. <template v-else>
  930. <a-select v-model:value="selectedPackage" style="width: 180px" placeholder="请选择服务包" >
  931. <a-select-option v-for="item in lifeCwData" :key="item.id" :value="item.id" @click="selectCw(item)">
  932. {{ item.name }}
  933. </a-select-option>
  934. </a-select>
  935. </template>
  936. <div v-if="currentPatient?.status === '1' && suggestCwData.length > 0">
  937. <span style="margin-left: 16px; color: #1890ff">推荐:</span>
  938. <a class="suggest-cw" v-for="item in suggestCwData" :key="item.id" @click="selectCw(item)">{{ item.name }}</a>
  939. </div>
  940. </div>
  941. <!-- 服务包内容表格 -->
  942. <div class="table-section">
  943. <div class="table-title">服务包内容</div>
  944. <vxe-table :data="displayTableData" border style="margin-top: 8px" max-height="380px">
  945. <vxe-column width="60" title="">
  946. <template #default="{ rowIndex }">
  947. <a-button type="text" danger @click="removeTableRow(rowIndex)" :disabled="rowIndex === displayTableData.length - 1">
  948. <MinusCircleOutlined />
  949. </a-button>
  950. </template>
  951. </vxe-column>
  952. <vxe-column field="conditioningProgramDetail.name" title="项目名称" width="180">
  953. <template #default="{ row, rowIndex }">
  954. <template v-if="rowIndex === displayTableData.length - 1">
  955. <a-popover v-model:open="showProjectPopover" trigger="click" placement="bottomLeft" :overlayStyle="{ width: '350px', padding: 0 }">
  956. <template #content>
  957. <a-input v-model:value="projectSearch" placeholder="输入项目名称搜索" style="margin: 8px; width: 90%" @input="() => nextTick()" />
  958. <vxe-table :data="filteredProjects" border size="small" style="max-height: 240px; overflow-y: auto" @cell-click="onSelectProject">
  959. <vxe-column field="name" title="项目名称" />
  960. <vxe-column field="conditioningProgramType" title="方案类型" />
  961. <vxe-column field="effect" title="功效" />
  962. <vxe-column title="预览" width="60">
  963. <template #default="{ row }">
  964. <EyeOutlined style="font-size: 18px; color: #1890ff; cursor: pointer" @click.stop="detailPreview(row)" />
  965. </template>
  966. </vxe-column>
  967. </vxe-table>
  968. </template>
  969. <a-input v-model:value="row.name" placeholder="请搜索" style="width: 120px" @click="showProjectPopover = true" />
  970. </a-popover>
  971. </template>
  972. <template v-else>
  973. {{ row.conditioningProgramDetail?.name }}
  974. </template>
  975. </template>
  976. </vxe-column>
  977. <vxe-column title="预览" width="60">
  978. <template #default="{ row }">
  979. <EyeOutlined style="font-size: 18px; color: #1890ff; cursor: pointer" @click="onPreview(row)" />
  980. </template>
  981. </vxe-column>
  982. <vxe-column field="days" title="周期" width="120">
  983. <template #default="{ row }">
  984. <div style="display: flex; align-items: center">
  985. <a-input v-model:value="row.days" @change="() => calculateCount(row)" />
  986. <span>天</span>
  987. </div>
  988. </template>
  989. </vxe-column>
  990. <vxe-column field="frequencyType" title="频率" width="auto">
  991. <template #default="{ row }">
  992. <div v-if="row.conditioningProgramDetail?.name === '健康咨询' || row.conditioningProgramDetail?.name === '健康评估'" class="flex items-center">
  993. <div class="flex items-center mr-4">
  994. <span>每</span>
  995. <a-input v-model:value="row.frequencyType" style="width: 50px" @change="() => calculateCount(row)" :disabled="row.frequencyType === '不限'" />
  996. <span>天</span>
  997. <a-input v-model:value="row.frequencyMeasure" style="width: 50px" @change="() => calculateCount(row)" :disabled="row.frequencyType === '不限'" />
  998. <span>{{ row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit ? row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit : '次' }}</span>
  999. </div>
  1000. <div>
  1001. <a-checkbox-group
  1002. v-model:value="row.frequencyTypeing"
  1003. @change="
  1004. (value) => {
  1005. row.frequencyTypeing = value.includes('不限') ? ['不限'] : [];
  1006. row.frequencyType = value.includes('不限') ? '不限' : '';
  1007. calculateCount(row);
  1008. }
  1009. "
  1010. >
  1011. <a-checkbox value="不限">不限</a-checkbox>
  1012. </a-checkbox-group>
  1013. </div>
  1014. </div>
  1015. <div class="flex items-center" v-else>
  1016. <span>每</span>
  1017. <a-input v-model:value="row.frequencyType" style="width: 50px" @change="() => calculateCount(row)" />
  1018. <span>天</span>
  1019. <a-input v-model:value="row.frequencyMeasure" style="width: 50px" @change="() => calculateCount(row)" />
  1020. <span>{{ row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit ? row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit : '次' }}</span>
  1021. </div>
  1022. </template>
  1023. </vxe-column>
  1024. <vxe-column field="row?.conditioningProgramDetail?.conditioningProgramType" title="方案类型" width="120">
  1025. <template #default="{ row }">
  1026. {{ row?.conditioningProgramDetail?.conditioningProgramType || '-' }}
  1027. </template>
  1028. </vxe-column>
  1029. <vxe-column field="row.totalMeasure" title="数量" width="60">
  1030. <template #default="{ row }">
  1031. {{ row.totalMeasure || 0 }}
  1032. </template>
  1033. </vxe-column>
  1034. <vxe-column field="row?.conditioningProgramDetail?.cpFixedPricingRule?.unit" title="单位" width="60">
  1035. <template #default="{ row }">
  1036. {{ row?.conditioningProgramDetail?.cpFixedPricingRule?.pricingUnit ? row?.conditioningProgramDetail?.cpFixedPricingRule?.pricingUnit : '次' }}
  1037. </template>
  1038. </vxe-column>
  1039. <vxe-column field="row.conditioningProgramDetail.cpFixedPricingRule.unitPrice" title="单价(元)" width="100">
  1040. <template #default="{ row }">
  1041. {{ row?.conditioningProgramDetail?.pricingType === '0' ? row.conditioningProgramDetail?.cpFixedPricingRule?.unitPrice : row?.unitPrice }}
  1042. </template>
  1043. </vxe-column>
  1044. <vxe-column field="row.totalPrice" title="总价(元)" width="auto" @change="() => calculateCount(row)">
  1045. <template #default="{ row }">
  1046. <span>{{ row.totalPrice || 0 }}</span>
  1047. </template>
  1048. </vxe-column>
  1049. <vxe-column field="start" title="开始时间" width="190">
  1050. <template #default="{ row }">
  1051. <div style="display: flex; align-items: center">
  1052. <span>调养开始第</span>
  1053. <a-input v-model:value="row.initialDay" style="width: 80px" />
  1054. <span>天</span>
  1055. </div>
  1056. </template>
  1057. </vxe-column>
  1058. <vxe-column field="conditioningProgramDetail.isOffline" title="线下项目" width="100">
  1059. <template #default="{ row }">
  1060. <span v-if="row.conditioningProgramDetail?.isOffline">
  1061. {{ row.conditioningProgramDetail.isOffline === 'Y' ? '是' : '否' }}
  1062. </span>
  1063. <span v-else>-</span>
  1064. </template>
  1065. </vxe-column>
  1066. <vxe-column field="conditioningProgramDetail.pricingType" title="穴位/经络/部位" width="160">
  1067. <template #default="{ row }">
  1068. <!-- <a-input v-model:value="row.desc" style="width: 120px" :disabled="currentPatient?.status === '0' ? true : false" /> -->
  1069. <span>
  1070. <a @click="editPart(row)" style="color: #1890ff; cursor: pointer" v-if="row.conditioningProgramDetail.pricingType === '1'">编辑</a>
  1071. </span>
  1072. </template>
  1073. </vxe-column>
  1074. <vxe-column field="remark" title="说明" width="180">
  1075. <template #default="{ row }">
  1076. <!-- <a-input v-model:value="row.remark" style="width: 120px" :disabled="currentPatient?.status === '0' ? true : false" /> -->
  1077. <a-textarea
  1078. v-model:value="row.remark"
  1079. style="max-width: 180px; width: 100%; height: 50px"
  1080. :rows="2"
  1081. show-count
  1082. :maxLength="200"
  1083. :disabled="currentPatient?.status === '0' ? true : false"
  1084. />
  1085. </template>
  1086. </vxe-column>
  1087. </vxe-table>
  1088. </div>
  1089. <div style="display: flex; justify-content: flex-end; margin-top: 16px">
  1090. <span style="font-weight: bold">合计:{{ totalPrice }}元</span>
  1091. </div>
  1092. <!-- 调养日期 -->
  1093. <div class="delivery-row">
  1094. <span>开始调养日期:</span>
  1095. <span v-if="currentPatient?.status === '0' && btnType !== '转方案'">
  1096. {{ form.estimatedStartDate }}
  1097. </span>
  1098. <span v-else>
  1099. <a-date-picker
  1100. :value="form.estimatedStartDate ? dayjs(form.estimatedStartDate) : null"
  1101. @change="handleDateChange"
  1102. placeholder="请选择日期"
  1103. :disabledDate="(current) => current && current < dayjs().startOf('day')"
  1104. />
  1105. </span>
  1106. </div>
  1107. <!-- 配送信息 -->
  1108. <div class="delivery-row" v-if="isShowDelivery">
  1109. <div v-if="(btnType !== '转方案' && currentPatient?.status === '0')">
  1110. <a-checkbox v-model:checked="deliveryChecked" disabled>配送</a-checkbox>
  1111. <span>地址:</span>
  1112. <span>{{ form.provinceName }}{{ form.cityName }}{{ form.areaName }}{{ form.detailAddress }}</span>
  1113. <span style="margin-left: 16px" v-if="form.phone">电话:</span>
  1114. <span>{{ form.phone }}</span>
  1115. </div>
  1116. <!-- <div v-if="!form.progress || currentPatient?.status === '1'"> -->
  1117. <div v-else>
  1118. <a-checkbox v-model:checked="deliveryChecked">配送</a-checkbox>
  1119. <template v-if="deliveryChecked">
  1120. <span>地址:</span>
  1121. <a-select v-model:value="selectedProvince" :options="provinceOptions" placeholder="请选择省" style="width: 100px; margin-right: 4px" @change="handleProvinceChange" />
  1122. <a-select
  1123. v-model:value="selectedCity"
  1124. :options="cityOptions"
  1125. placeholder="请选择市"
  1126. style="width: 100px; margin-right: 4px"
  1127. :disabled="!selectedProvince"
  1128. @change="handleCityChange"
  1129. />
  1130. <a-select
  1131. v-model:value="selectedArea"
  1132. :options="areaOptions"
  1133. placeholder="请选择区"
  1134. style="width: 100px; margin-right: 4px"
  1135. :disabled="!selectedCity"
  1136. @change="handleAreaChange"
  1137. />
  1138. <a-input v-model:value="form.detailAddress" placeholder="详细地址" style="width: 120px; margin-right: 4px" />
  1139. <span>电话:</span>
  1140. <a-input v-model:value="form.phone" placeholder="请输入" style="width: 120px" />
  1141. </template>
  1142. </div>
  1143. </div>
  1144. <!-- 操作按钮 -->
  1145. <div class="footer-btns" v-if="currentPatient?.status === '1'">
  1146. <a-button @click="handleCancel">取消</a-button>
  1147. <a-button type="primary" style="margin-left: 24px" @click="handleSubmit">确认</a-button>
  1148. </div>
  1149. </div>
  1150. <a-result class="area" v-else style="background-color: #fff" status="warning" title="暂无数据" />
  1151. </div>
  1152. <!-- 右侧调养记录 -->
  1153. <div class="right-panel flex flex-col overflow-hidden">
  1154. <section style="flex: 0 0 auto; max-height: 270px; overflow-y: auto">
  1155. <div style="margin-top: -6px; padding-right: 8px">
  1156. <label>标签:</label>
  1157. <a-tag v-for="tag in patientTags" :key="tag.id" :color="tag.color">{{ tag.name }}</a-tag>
  1158. <a-button type="link" @click="openPatientTagEdit($event)">
  1159. <template #icon>
  1160. <EditOutlined />
  1161. </template>
  1162. </a-button>
  1163. </div>
  1164. </section>
  1165. <section style="margin: 8px">
  1166. <a-button type="primary" block @click="openHistoryPreviewHandle()">健康档案</a-button>
  1167. </section>
  1168. <div class="record-title">报告记录</div>
  1169. <section class="overflow-auto" style="flex: 4 4 auto; min-height: 240px">
  1170. <div class="record-list">
  1171. <div class="record-item flex justify-between" v-for="item in patientHealthRecords" :key="item.id" @click="openPatientHealthRecord(item, 'analysis')">
  1172. {{ item.date }}
  1173. <a @click.stop="openPatientHealthRecord(item, 'scheme')">方案</a>
  1174. </div>
  1175. <div v-if="!patientRecord.length" style="padding-bottom: 8px; text-align: center; margin-top: 40px">暂无数据</div>
  1176. </div>
  1177. </section>
  1178. <div class="record-title">调养记录</div>
  1179. <section class="overflow-auto" style="flex: 4 4 auto; min-height: 240px">
  1180. <div class="record-list">
  1181. <div class="record-item" v-for="item in patientRecord" :key="item.id" @click="openRecord(item)">{{ item.estimatedStartDate }}~{{ item.estimatedEndDate }}</div>
  1182. <div v-if="!patientRecord.length" style="padding-bottom: 8px; text-align: center; margin-top: 40px">暂无数据</div>
  1183. </div>
  1184. </section>
  1185. </div>
  1186. </div>
  1187. </template>
  1188. <style scoped lang="scss">
  1189. .issue-service-page {
  1190. display: flex;
  1191. flex-direction: row;
  1192. width: 100vw;
  1193. /* min-height: 100vh; */
  1194. height: var(--page-main-container);
  1195. overflow: hidden;
  1196. background: #fff;
  1197. }
  1198. .left-panel {
  1199. flex: none;
  1200. width: 180px;
  1201. border-right: 1px solid #eee;
  1202. padding: 16px 8px 0 16px;
  1203. background: #fafbfc;
  1204. box-sizing: border-box;
  1205. display: flex;
  1206. flex-direction: column;
  1207. min-height: 0;
  1208. }
  1209. .patient-list {
  1210. flex: 1 1 0;
  1211. overflow: auto;
  1212. min-height: 0;
  1213. // 让内容撑满剩余空间
  1214. }
  1215. .patient-item {
  1216. padding: 10px 0;
  1217. font-size: 14px;
  1218. color: #333;
  1219. }
  1220. .patient-item.active {
  1221. background: #e6f7ff;
  1222. color: #1890ff;
  1223. }
  1224. .main-panel {
  1225. flex: 1 1 0;
  1226. min-width: 0;
  1227. padding: 16px 32px 0 32px;
  1228. display: flex;
  1229. flex-direction: column;
  1230. box-sizing: border-box;
  1231. overflow: auto;
  1232. }
  1233. .patient-info {
  1234. font-size: 14px;
  1235. color: #333;
  1236. }
  1237. .service-select-row {
  1238. margin-bottom: 16px;
  1239. display: flex;
  1240. align-items: center;
  1241. gap: 8px;
  1242. font-size: 14px;
  1243. }
  1244. .table-section {
  1245. margin-bottom: 16px;
  1246. }
  1247. .table-title {
  1248. font-weight: bold;
  1249. margin-bottom: 8px;
  1250. }
  1251. .table-total {
  1252. text-align: right;
  1253. margin-top: 8px;
  1254. font-weight: bold;
  1255. }
  1256. .delivery-row {
  1257. display: flex;
  1258. align-items: center;
  1259. gap: 4px;
  1260. margin-bottom: 24px;
  1261. margin-top: 16px;
  1262. }
  1263. .footer-btns {
  1264. display: flex;
  1265. justify-content: flex-end;
  1266. gap: 24px;
  1267. }
  1268. .right-panel {
  1269. flex: none;
  1270. width: 190px;
  1271. border-left: 1px solid #eee;
  1272. //padding: 16px 8px;
  1273. background: #fafbfc;
  1274. /* min-height: 100vh; */
  1275. box-sizing: border-box;
  1276. > section {
  1277. margin: 16px 0;
  1278. padding-left: 8px;
  1279. .ant-tag {
  1280. margin-top: 6px;
  1281. }
  1282. }
  1283. }
  1284. .record-title {
  1285. font-weight: bold;
  1286. text-align: center;
  1287. }
  1288. .record-list {
  1289. display: flex;
  1290. flex-direction: column;
  1291. gap: 8px;
  1292. padding-right: 8px;
  1293. }
  1294. .record-item {
  1295. background: #fff;
  1296. border: 1px solid #eee;
  1297. border-radius: 4px;
  1298. padding: 8px;
  1299. text-align: center;
  1300. font-size: 12px;
  1301. font-weight: bold;
  1302. cursor: pointer;
  1303. &:hover {
  1304. color: #1890ff;
  1305. }
  1306. }
  1307. .suggest-cw {
  1308. color: #1890ff;
  1309. cursor: pointer;
  1310. margin-right: 10px;
  1311. }
  1312. .scroll-content {
  1313. /* max-height: 600px; */
  1314. overflow-y: auto;
  1315. /* 可选:让滚动条更美观 */
  1316. scrollbar-width: thin;
  1317. scrollbar-color: #aaa #f5f5f5;
  1318. }
  1319. </style>