EditSystemService.vue 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. <script setup lang="ts">
  2. import { ref, computed, nextTick, h, watch, onMounted } from 'vue';
  3. import { notification } from 'ant-design-vue';
  4. import { getDictionaryMethod } from '@/request/api/dictionary.api';
  5. import { UploadIFile } from '@/request/api/follow.api';
  6. import type { UploadFile } from 'ant-design-vue/es/upload/interface';
  7. import { branchMethod } from '@/request/api/system.api';
  8. import { message } from 'ant-design-vue';
  9. import {
  10. pageMedicineMethod,
  11. pageDiagnoseTypeMethod,
  12. addSystemCwMethod,
  13. addOrgCwMethod,
  14. getConditioningRecordDetailMethod,
  15. getAllSystemCpMethod,
  16. getCpContentListMethod,
  17. } from '@/request/api/care.api';
  18. import RemoteSelect from '@/libs/v-select-page/RemoteSelect.vue';
  19. import { usePagination, useRequest } from 'alova/client';
  20. import 'ant-design-vue/dist/reset.css';
  21. import type { SystemCwModel } from '@/model/care.model';
  22. import AcupointEdit from '@/service/AcupointEdit.vue';
  23. import ServiceDetail from '@/service/ServiceDetail.vue';
  24. import ServicePackageList from '@/service/ServicePackageList.vue';
  25. import { MinusCircleOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons-vue';
  26. import { VxeUI } from 'vxe-pc-ui';
  27. type FollowModel = Partial<SystemCwModel>;
  28. const props = defineProps<{ data: FollowModel }>();
  29. const fileList = ref<UploadFile[]>([]);
  30. const uploadProps = reactive({ showRemoveIcon: true });
  31. const emit = defineEmits<{ submit: [data?: SystemCwModel] }>();
  32. // 获取所有的机构
  33. const branch = ref<any[]>([]);
  34. const { loading: branchLoading } = useRequest(branchMethod).onSuccess(({ data }) => {
  35. const to = (data?: any[]): any[] => {
  36. return Array.isArray(data)
  37. ? data.map((item) => {
  38. return {
  39. ...item,
  40. value: item.id,
  41. key: item.id.toString(),
  42. children: to(item.children),
  43. };
  44. })
  45. : [];
  46. };
  47. branch.value = to(data);
  48. });
  49. const { loading: addSystemCwLoading, send: addSystemCw } = useRequest(addSystemCwMethod, {
  50. immediate: false,
  51. }).onSuccess(({ data }) => {
  52. emit('submit');
  53. });
  54. const { loading: addOrgCwLoading, send: addOrgCw } = useRequest(addOrgCwMethod, {
  55. immediate: false,
  56. }).onSuccess(({ data }) => {
  57. emit('submit');
  58. });
  59. const formData = reactive<FollowModel>({
  60. name: '', //服务包名称
  61. price: 0, //总计价格
  62. conditioningWrapPatientMatchRule: {
  63. sex: '',
  64. age: '',
  65. diagnoseDiseaseNames: [],
  66. diagnoseSyndromeNames: [],
  67. constitutionGroupNames: [],
  68. willillStateNames: [],
  69. },
  70. cwPatientMatchRules: [
  71. {
  72. diagnoseDiseaseNames: [],
  73. diagnoseSyndromeNames: [],
  74. constitutionGroupNames: [],
  75. diagnoseDisease: {
  76. id: '',
  77. code: '',
  78. name: '',
  79. optionalWords: '',
  80. attributes: [],
  81. children: [],
  82. },
  83. diagnoseSyndrome: {
  84. code: '',
  85. name: '',
  86. analysis: '',
  87. remark: '',
  88. },
  89. constitutionGroup: {
  90. id: '',
  91. code: '',
  92. name: '',
  93. definition: '',
  94. remark: '',
  95. },
  96. },
  97. ], // 适用情况
  98. items: [], // Initialize as empty array
  99. });
  100. const emptyRow = {
  101. id: '',
  102. conditioningWrapId: '',
  103. conditioningProgramId: 0,
  104. days: '',
  105. frequencyType: '',
  106. frequencyTypeing: [],
  107. frequencyMeasure: '',
  108. totalMeasure: '',
  109. totalPrice: '',
  110. initialDay: '',
  111. conditioningProgramDetail: {
  112. id: '',
  113. name: '',
  114. conditioningProgramType: '',
  115. pricingType: '',
  116. cpFixedPricingRule: {
  117. unitPrice: 0,
  118. pricingUnit: '',
  119. convertDose: 0,
  120. convertUnit: '',
  121. },
  122. cpDynamicPricingRule: [],
  123. cpMedicines: [],
  124. effect: '',
  125. isOffline: null,
  126. isDelivery: null,
  127. photo: '',
  128. institutionId: '',
  129. institutionName: '',
  130. remark: '',
  131. },
  132. cwcpAcuMeridians: [],
  133. cwcpAcuPoints: [],
  134. conditioningProgramSupplierName: '',
  135. };
  136. function getInstitutionProjectList() {
  137. const {
  138. loading: projectLoading,
  139. onSuccess,
  140. replace,
  141. refresh,
  142. remove,
  143. } = usePagination(() => getCpContentListMethod(), {
  144. initialData: { data: [] },
  145. immediate: true,
  146. });
  147. onSuccess(({ data }: any) => {
  148. allProjects.value = data;
  149. });
  150. }
  151. function getSystemProjectList() {
  152. const { loading: projectLoading, onSuccess } = useRequest(getAllSystemCpMethod, {
  153. immediate: true,
  154. });
  155. onSuccess(({ data }: any) => {
  156. allProjects.value = data;
  157. });
  158. }
  159. async function getProjectList() {
  160. if (props.data?.types === 'system') {
  161. getSystemProjectList();
  162. } else {
  163. getInstitutionProjectList();
  164. }
  165. }
  166. const totalPrice = computed(() => {
  167. return (formData.items ?? []).reduce((sum, row) => {
  168. const price = Number(row?.totalPrice) || 0;
  169. return sum + price;
  170. }, 0);
  171. });
  172. watch(totalPrice, (val) => {
  173. formData.price = val;
  174. });
  175. // function addSituation() {
  176. // if (!formData.cwPatientMatchRules) formData.cwPatientMatchRules = [];
  177. // formData.cwPatientMatchRules.push({
  178. // diagnoseDiseaseNames: [],
  179. // diagnoseSyndromeNames: [],
  180. // constitutionGroupNames: [],
  181. // diagnoseDisease: { id: '', code: '', name: '' },
  182. // diagnoseSyndrome: { code: '', name: '', analysis: '', remark: '' },
  183. // constitutionGroup: { id: '', code: '', name: '', definition: '', remark: '' },
  184. // });
  185. // }
  186. // function removeSituation(idx: number) {
  187. // if (formData.cwPatientMatchRules) {
  188. // formData.cwPatientMatchRules.splice(idx, 1);
  189. // }
  190. // }
  191. const projectSearchRef = useTemplateRef<HTMLInputElement>('projectSearchRef');
  192. const projectSearchFocus = (visible: boolean) => {
  193. if (visible) setTimeout(() => projectSearchRef.value?.focus?.(), 300);
  194. };
  195. const projectSearch = ref('');
  196. const showProjectPopover = ref(false);
  197. const allProjects = ref<
  198. Array<{
  199. name: string;
  200. conditioningProgramType?: string;
  201. effect?: string;
  202. }>
  203. >([]);
  204. const filteredProjects = computed(() => {
  205. const searchText = projectSearch.value.toLowerCase();
  206. if (allProjects.value.length > 0) {
  207. return allProjects.value.filter(
  208. (p) => p.name.toLowerCase().includes(searchText) || p?.conditioningProgramType?.toLowerCase().includes(searchText) || p.effect?.toLowerCase().includes(searchText)
  209. );
  210. } else {
  211. return [];
  212. }
  213. });
  214. function onSelectProject({ row }: any) {
  215. console.log(row, 'onSelectProject');
  216. if ((formData.items ?? []).some((item) => item.conditioningProgramDetail?.name === row.name)) {
  217. message.warning('不能重复添加该项目');
  218. return;
  219. }
  220. // 添加新行到主表格
  221. if (!formData.items) formData.items = [];
  222. formData.items.push({
  223. id: '',
  224. conditioningWrapId: '',
  225. conditioningProgramId: row.id,
  226. days: '',
  227. frequencyType: '',
  228. frequencyMeasure: '',
  229. totalMeasure: '',
  230. totalPrice: '',
  231. initialDay: '',
  232. cwcpAcuMeridians: row?.cwcpAcuMeridians ?? [],
  233. cwcpAcuPoints: row?.cwcpAcuPoints ?? [],
  234. conditioningProgramDetail: {
  235. ...row,
  236. id: row?.id || '',
  237. name: row?.name,
  238. conditioningProgramType: row?.conditioningProgramType,
  239. effect: row?.effect,
  240. pricingType: row?.pricingType,
  241. cpFixedPricingRule: {
  242. unitPrice: row?.cpFixedPricingRule?.unitPrice,
  243. pricingUnit: row?.cpFixedPricingRule?.pricingUnit,
  244. convertDose: row?.cpFixedPricingRule?.convertDose,
  245. convertUnit: row?.cpFixedPricingRule?.convertUnit,
  246. },
  247. cpDynamicPricingRule: row?.cpDynamicPricingRule,
  248. cpMedicines: row?.cpMedicines,
  249. isOffline: row?.isOffline,
  250. isDelivery: row?.isDelivery,
  251. photo: row?.photo,
  252. conditioningProgramSupplierName: row?.conditioningProgramSupplierName,
  253. },
  254. remark: row?.remark,
  255. });
  256. // 关闭弹窗
  257. showProjectPopover.value = false;
  258. // 清空搜索
  259. projectSearch.value = '';
  260. }
  261. // 预览
  262. function onPreview(row) {
  263. if (row.conditioningProgramDetail.id) {
  264. // 这里写你的预览逻辑
  265. VxeUI.modal.open({
  266. title: `预览`,
  267. height: 600,
  268. width: 950,
  269. escClosable: true,
  270. destroyOnClose: true,
  271. id: `preview-modal`,
  272. remember: true,
  273. storage: true,
  274. slots: {
  275. default() {
  276. return h(ServiceDetail, {
  277. data: row.conditioningProgramDetail,
  278. onSubmit: (data) => {
  279. VxeUI.modal.close(`preview-modal`);
  280. },
  281. });
  282. },
  283. },
  284. });
  285. } else {
  286. message.warning('请先添加服务包');
  287. }
  288. }
  289. function detailPreview(row: any) {
  290. if (row.id) {
  291. // 这里写你的预览逻辑
  292. VxeUI.modal.open({
  293. title: `预览`,
  294. height: 600,
  295. width: 950,
  296. escClosable: true,
  297. destroyOnClose: true,
  298. id: `preview-modal`,
  299. remember: true,
  300. storage: true,
  301. slots: {
  302. default() {
  303. return h(ServiceDetail, {
  304. data: row,
  305. onSubmit: (data) => {
  306. VxeUI.modal.close(`preview-modal`);
  307. },
  308. });
  309. },
  310. },
  311. onHide() {
  312. showProjectPopover.value = true;
  313. },
  314. });
  315. showProjectPopover.value = false;
  316. } else {
  317. message.warning('请先添加服务包');
  318. }
  319. }
  320. function editPart(row: any) {
  321. VxeUI.modal.open({
  322. title: `编辑部位`,
  323. height: 700,
  324. width: 750,
  325. escClosable: true,
  326. destroyOnClose: true,
  327. id: `edit-part-modal`,
  328. remember: true,
  329. storage: true,
  330. slots: {
  331. default() {
  332. return h(AcupointEdit, {
  333. data: row,
  334. onSubmit: (data:any) => {
  335. console.log(data, '编辑穴位传递的参数');
  336. // refresh(page.value);
  337. VxeUI.modal.close(`edit-part-modal`);
  338. },
  339. });
  340. },
  341. },
  342. });
  343. }
  344. // 添加计算数量的函数
  345. function calculateCount(row: any) {
  346. const pricingType = row.conditioningProgramDetail.pricingType;
  347. const period = Number(row.days) || 0;
  348. const frequency = Number(row.frequencyMeasure) || 0;
  349. const maxCount = row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.max;
  350. console.log(row, '添加计算数量的函数');
  351. const acCount = (row.acuMeridianNames?.length ?? 0) + (row.acuPointNames?.length ?? 0);
  352. console.log(acCount, 'acCount');
  353. // 一口价
  354. if (pricingType === '0') {
  355. // 检查是否选择了"不限"
  356. if (row.frequencyType === '不限') {
  357. row.frequencyMeasure = ''; // 重置 frequencyMeasure
  358. row.totalMeasure = 1;
  359. } else {
  360. const convertDose = Number(row.conditioningProgramDetail.cpFixedPricingRule.convertDose) || 0;
  361. const frequencyType = Number(row.frequencyType) || 0;
  362. row.totalMeasure = Math.ceil(((period / frequencyType) * frequency) / convertDose);
  363. }
  364. // 获取单价
  365. const unitPrice = Number(row.conditioningProgramDetail?.cpFixedPricingRule?.unitPrice) || 0;
  366. // 计算总价
  367. row.totalPrice = (row.totalMeasure * unitPrice).toFixed(2);
  368. } else if (pricingType === '1') {
  369. // 按穴位计价
  370. const frequencyType = Number(row.frequencyType) || 0;
  371. row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  372. let unitPrice: number = row.conditioningProgramDetail.cpDynamicPricingRule[0].price;
  373. if (acCount > maxCount) {
  374. if (row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.priceType === 0) {
  375. // 单价
  376. // let unitPrice: number = row.conditioningProgramDetail.cpDynamicPricingRule[1].price;
  377. row.unitPrice = unitPrice;
  378. // row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  379. row.totalPrice = Math.ceil((period / frequencyType) * unitPrice * acCount);
  380. } else if (row.conditioningProgramDetail.cpDynamicPricingRule?.[1]?.priceType === 1) {
  381. // 一口价
  382. row.conditioningProgramDetail.unitPrice = '-';
  383. row.totalPrice = row.conditioningProgramDetail.cpDynamicPricingRule[1].price;
  384. // row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  385. }
  386. } else {
  387. if (row.conditioningProgramDetail.cpDynamicPricingRule?.length > 0) {
  388. if (row.conditioningProgramDetail.cpDynamicPricingRule?.[0]?.priceType === 0) {
  389. // 单价
  390. // let unitPrice: number = row.conditioningProgramDetail.cpDynamicPricingRule[0].price;
  391. row.unitPrice = unitPrice;
  392. // row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  393. row.totalPrice = Math.ceil((period / frequencyType) * unitPrice * acCount);
  394. } else if (row.conditioningProgramDetail.cpDynamicPricingRule?.[0]?.priceType === 1) {
  395. // 一口价
  396. row.unitPrice = '-';
  397. row.totalPrice = row.conditioningProgramDetail.cpDynamicPricingRule[0].price;
  398. // row.totalMeasure = Math.ceil((period / frequencyType) * frequency);
  399. }
  400. }
  401. }
  402. }
  403. }
  404. // 添加监听器
  405. watch(
  406. () => formData.items,
  407. (newData) => {
  408. if (!newData) return;
  409. newData.forEach((row: any) => {
  410. if (row?.days || row?.frequencyType || row?.frequencyMeasure) {
  411. calculateCount(row);
  412. }
  413. });
  414. },
  415. { deep: true }
  416. );
  417. function cancel() {
  418. VxeUI.modal.close(`edit-system-service-modal`);
  419. }
  420. function confirm() {
  421. const isValid = (formData.items ?? []).every((item: any) => {
  422. delete item.id; // 删除 id 属性
  423. if (item?.conditioningProgramDetail && item?.conditioningProgramDetail?.pricingType === '1') {
  424. // 确保 cwcpAcuMeridians 和 cwcpAcuPoints 存在
  425. const hasMeridians = item.cwcpAcuMeridians?.length > 0; // 使用可选链
  426. const hasPoints = item.cwcpAcuPoints?.length > 0; // 使用可选链
  427. // 只要有一个长度大于0,就满足条件
  428. if (!hasMeridians && !hasPoints) {
  429. // 如果两个都没有,显示提示
  430. message.warning('请至少选择一个穴位或经络');
  431. return false; // 返回 false 表示条件不满足
  432. }
  433. }
  434. return true; // 返回 true 表示条件满足
  435. });
  436. formData?.items?.forEach((row: any) => {
  437. if (row.frequencyTypeing && row.frequencyTypeing.length > 0) {
  438. row.frequencyType = row.frequencyTypeing[0];
  439. }
  440. });
  441. // console.log(props.data.types, 'formData.items');
  442. // 如果所有条件都满足,继续执行后续代码
  443. if (isValid) {
  444. // 系统服务包
  445. if (props.data.types === 'system') {
  446. delete formData.types;
  447. addSystemCw({
  448. ...formData,
  449. }).then((res) => {
  450. notification.success({ message: '操作成功' });
  451. VxeUI.modal.close(`edit-system-service-modal`);
  452. });
  453. } else if (props.data.types === 'institution') {
  454. if (fileList.value.length > 0) {
  455. const upImg = fileList.value[0].response?.url;
  456. formData.photo = upImg;
  457. fileList.value = upImg
  458. ? [
  459. {
  460. uid: '-1',
  461. name: 'image.png',
  462. status: 'done',
  463. url: upImg,
  464. thumbUrl: upImg,
  465. response: { url: upImg },
  466. },
  467. ]
  468. : [];
  469. }
  470. addOrgCw({
  471. ...formData,
  472. }).then((res) => {
  473. notification.success({ message: '操作成功' });
  474. VxeUI.modal.close(`edit-system-service-modal`);
  475. });
  476. }
  477. }
  478. }
  479. // 欲病状态
  480. const desiredConditions = ref<{ id: string; name: string }[]>([]);
  481. const constitutionGroups = ref<{ id: string; name: string }[]>([]);
  482. async function getDesiredConditions() {
  483. const res = await getDictionaryMethod('conditioning_wrap_willill_state');
  484. if (res?.length > 0) {
  485. desiredConditions.value = res.map((item: any) => ({
  486. id: item.value,
  487. name: item.label,
  488. }));
  489. }
  490. }
  491. // 获取性别
  492. const genders = ref<{ id: string; name: string }[]>([]);
  493. async function getGender() {
  494. const res = await getDictionaryMethod('sys_user_sex');
  495. if (res && res.length > 0) {
  496. genders.value = res.map((item: any) => ({
  497. id: item.label,
  498. name: item.label,
  499. }));
  500. } else {
  501. console.log('性别数据未获取到');
  502. }
  503. }
  504. // 获取年龄
  505. const ages = ref<{ id: string; name: string }[]>([]);
  506. async function getAge() {
  507. const res = await getDictionaryMethod('conditioning_wrap_rule_age');
  508. if (res && res.length > 0) {
  509. ages.value = res.map((item: any) => ({
  510. id: item.label,
  511. name: item.label,
  512. }));
  513. }
  514. }
  515. async function getConstitutionGroup() {
  516. const res = await getDictionaryMethod('constitution_group');
  517. if (res && res.length > 0) {
  518. constitutionGroups.value = res.map((item: any) => ({
  519. id: item.value,
  520. name: item.label,
  521. }));
  522. }
  523. }
  524. onMounted(async () => {
  525. getDesiredConditions();
  526. getGender();
  527. getAge();
  528. getConstitutionGroup();
  529. if (props.data.id) {
  530. props.data.types = 'institution'; // Modify the local copy instead
  531. // 调编辑接口获取数据
  532. const res: any = await getConditioningRecordDetailMethod(props.data);
  533. Object.assign(formData, res); // Use the response to update formData
  534. await nextTick(); // 确保视图更新
  535. formData.conditioningWrapPatientMatchRule = res?.conditioningWrapPatientMatchRule ?? {
  536. sex: '',
  537. age: '',
  538. diagnoseDiseaseNames: [],
  539. diagnoseSyndromeNames: [],
  540. constitutionGroupNames: [],
  541. willillStateNames: [],
  542. };
  543. formData.items = res?.items ?? [];
  544. fileList.value = res?.photo
  545. ? [
  546. {
  547. uid: '-1',
  548. name: 'image.png',
  549. status: 'done',
  550. url: res?.photo,
  551. thumbUrl: res?.photo,
  552. },
  553. ]
  554. : [];
  555. formData?.items?.forEach((row: any) => {
  556. row.frequencyTypeing = row.frequencyType ? [row.frequencyType] : [];
  557. });
  558. }
  559. await getProjectList();
  560. });
  561. const tableData = computed(() => {
  562. console.log(formData, 'formData.items');
  563. return [...(formData.items ?? []), { ...emptyRow }];
  564. });
  565. function removeTableRow(idx: number) {
  566. if (idx < (formData.items ?? []).length) {
  567. formData.items?.splice(idx, 1);
  568. }
  569. }
  570. // 引入服务包
  571. function addInstitution() {
  572. VxeUI.modal.open({
  573. title: '选择引入',
  574. width: 1000,
  575. height: 700,
  576. escClosable: true,
  577. destroyOnClose: true,
  578. id: `systemService-list-modal`,
  579. remember: true,
  580. storage: true,
  581. slots: {
  582. default() {
  583. return h(ServicePackageList, {
  584. data: formData,
  585. onSubmit(data: SystemCwModel) {
  586. VxeUI.modal.close(`systemService-list-modal`);
  587. },
  588. });
  589. },
  590. },
  591. });
  592. }
  593. function customUpload(e: any) {
  594. // 二次封装上传接口
  595. UploadIFile(e.file)
  596. .then((res) => {
  597. // 文件上传成功
  598. e.onSuccess(res, e);
  599. })
  600. .catch((err) => {
  601. // 文件上传失败
  602. e.onError(err);
  603. });
  604. }
  605. const visible = ref<boolean>(false);
  606. const setVisible = (value: boolean): void => {
  607. visible.value = value;
  608. };
  609. const previewImg = ref<string>('');
  610. // 预览图片
  611. const handlePreview = async (file: UploadFile) => {
  612. previewImg.value = file.response?.url ?? file.thumbUrl;
  613. visible.value = true;
  614. };
  615. let multiple = ref<boolean>(true);
  616. function handleSelect(value: string, node: any, extra: any) {
  617. // console.log(value, 'value');
  618. // console.log(node, 'node');
  619. // console.log(extra, 'extra');
  620. formData.institutionId = value;
  621. formData.institutionName = node.label;
  622. }
  623. </script>
  624. <template>
  625. <div style="padding: 0 24px">
  626. <div class="flex" style="align-items: center">
  627. <!-- 机构名称 -->
  628. <div class="mr-10" v-if="props.data?.types === 'institution'" style="display: flex; align-items: center; margin-bottom: 0">
  629. <span style="white-space: nowrap; margin-right: 8px">机构名称:</span>
  630. <a-tree-select
  631. v-model:value="formData.institutionId"
  632. show-search
  633. style="width: 220px"
  634. :dropdown-style="{ maxHeight: '400px', overflow: 'auto' }"
  635. placeholder="请选择"
  636. allow-clear
  637. tree-default-expand-all
  638. :tree-data="branch"
  639. @select="handleSelect"
  640. />
  641. </div>
  642. <!-- 服务包名称 -->
  643. <div class="mr-6" style="display: flex; align-items: center; margin-bottom: 0">
  644. <span style="white-space: nowrap; margin-right: 8px">服务包名称:</span>
  645. <a-input style="width: 200px" v-model:value="formData.name" />
  646. <a-button type="primary" @click="addInstitution" class="ml-4" v-if="props.data?.types === 'institution'">引入</a-button>
  647. </div>
  648. <!-- 服务形象照 -->
  649. <div class="flex" v-if="props.data?.types === 'institution'" style="align-items: center; margin-bottom: 0">
  650. <div class="w-35" style="white-space: nowrap; margin-right: 8px">服务形象照:</div>
  651. <a-upload
  652. :showUploadList="uploadProps"
  653. v-model:file-list="fileList"
  654. name="avatar"
  655. list-type="picture-card"
  656. class="avatar-uploader"
  657. @preview="handlePreview"
  658. :maxCount="1"
  659. action="https://www.mocky.io/v2/5cc8019d300000980a055e76"
  660. :customRequest="customUpload"
  661. >
  662. <div v-if="fileList.length < 1">
  663. <plus-outlined />
  664. </div>
  665. </a-upload>
  666. </div>
  667. </div>
  668. <div style="margin-bottom: 16px">
  669. <div class="mb-3">适用情况</div>
  670. <div class="flex items-center mb-3">
  671. <span class="w-20">使用限制</span>
  672. <div class="mr-10">
  673. <span>性别:</span>
  674. <a-select placeholder="请选择" v-model:value="formData.conditioningWrapPatientMatchRule.sex" style="width: 150px; margin: 0 5px" allowClear>
  675. <a-select-option v-for="option in genders" :key="option.id" :value="option.id" placeholder="请选择">
  676. {{ option.name }}
  677. </a-select-option>
  678. </a-select>
  679. </div>
  680. <div>
  681. <span>年龄:</span>
  682. <a-select v-model:value="formData.conditioningWrapPatientMatchRule.age" style="width: 150px; margin: 0 8px" placeholder="请选择" allowClear>
  683. <a-select-option v-for="option in ages" :key="option.id" :value="option.id" placeholder="请选择">{{ option.name }}</a-select-option>
  684. </a-select>
  685. </div>
  686. </div>
  687. <div class="flex items-center">
  688. <span class="w-20">适用</span>
  689. <div class="mr-10 flex items-center">
  690. <span>专病:</span>
  691. <RemoteSelect
  692. :load="pageMedicineMethod"
  693. key-prop="name"
  694. v-model:value="formData.conditioningWrapPatientMatchRule.diagnoseDiseaseNames"
  695. style="margin: 0 8px"
  696. :multiple="multiple"
  697. />
  698. </div>
  699. <div class="mr-10 flex items-center">
  700. <span>证型:</span>
  701. <RemoteSelect
  702. :load="pageDiagnoseTypeMethod"
  703. key-prop="name"
  704. v-model:value="formData.conditioningWrapPatientMatchRule.diagnoseSyndromeNames"
  705. style="margin: 0 8px"
  706. :multiple="multiple"
  707. />
  708. </div>
  709. <div class="flex items-center mr-10">
  710. <span>体质:</span>
  711. <a-select
  712. v-model:value="formData.conditioningWrapPatientMatchRule.constitutionGroupNames"
  713. style="width: 120px; margin: 0 8px"
  714. placeholder="请选择"
  715. allowClear
  716. mode="multiple"
  717. >
  718. <a-select-option v-for="option in constitutionGroups" :key="option.id" :value="option.id" placeholder="请选择">{{ option.name }}</a-select-option>
  719. </a-select>
  720. </div>
  721. <div>
  722. <span>欲病状态:</span>
  723. <a-select
  724. v-model:value="formData.conditioningWrapPatientMatchRule.willillStateNames"
  725. style="width: 120px; margin: 0 8px"
  726. placeholder="请选择"
  727. allowClear
  728. mode="multiple"
  729. @change="(value) => (formData.conditioningWrapPatientMatchRule.willillStateNames = value)"
  730. >
  731. <a-select-option v-for="option in desiredConditions" :key="option.id" :value="option.id" placeholder="请选择">{{ option.name }}</a-select-option>
  732. </a-select>
  733. </div>
  734. </div>
  735. <!-- <div v-for="(item, idx) in formData.cwPatientMatchRules" :key="idx" style="display: flex; align-items: center; margin-bottom: 8px">
  736. <span>情况{{ idx + 1 }} 专病:</span>
  737. <RemoteSelect :load="pageMedicineMethod" key-prop="name" v-model:value="item.diagnoseDisease" style="margin: 0 8px" :multiple="multiple"/>
  738. <span>且 证型:</span>
  739. <RemoteSelect :load="pageDiagnoseTypeMethod" key-prop="name" v-model:value="item.diagnoseSyndrome.name" style="margin: 0 8px" :multiple="multiple" />
  740. <span>且 体质:</span>
  741. <a-select v-model="item.constitutionGroup" style="width: 120px; margin: 0 8px" placeholder="请选择" allowClear mode="multiple">
  742. <a-select-option v-for="option in constitutionGroups" :key="option.id" :value="option.id" placeholder="请选择">{{ option.name }}</a-select-option>
  743. </a-select>
  744. <a-button v-if="(formData.cwPatientMatchRules?.length ?? 0) > 1" @click="removeSituation(idx)" type="text" danger>
  745. <MinusCircleOutlined />
  746. </a-button>
  747. <a-button v-if="idx === (formData.cwPatientMatchRules?.length ?? 0) - 1" @click="addSituation" type="text">
  748. <PlusCircleOutlined />
  749. </a-button>
  750. </div> -->
  751. </div>
  752. <div style="margin-bottom: 16px">
  753. <span>服务包内容</span>
  754. <vxe-table :data="tableData" border style="margin-top: 8px">
  755. <vxe-column width="60" title="">
  756. <template #default="{ rowIndex }">
  757. <a-button type="text" danger @click="removeTableRow(rowIndex)" :disabled="rowIndex === tableData.length - 1">
  758. <MinusCircleOutlined />
  759. </a-button>
  760. </template>
  761. </vxe-column>
  762. <vxe-column field="conditioningProgramDetail.name" title="项目名称" width="180">
  763. <template #default="{ row, rowIndex }">
  764. <template v-if="rowIndex === tableData.length - 1">
  765. <a-popover
  766. v-model:open="showProjectPopover"
  767. trigger="click"
  768. placement="bottomLeft"
  769. :overlayStyle="{ width: '350px', padding: 0 }"
  770. @openChange="projectSearchFocus($event)"
  771. >
  772. <template #content>
  773. <a-input ref="projectSearchRef" v-model:value="projectSearch" placeholder="输入项目名称搜索" style="margin: 8px; width: 90%" @input="() => nextTick()" />
  774. <vxe-table :data="filteredProjects" border size="small" style="max-height: 240px; overflow-y: auto" @cell-click="onSelectProject">
  775. <vxe-column field="name" title="项目名称" />
  776. <vxe-column field="conditioningProgramType" title="方案类型" />
  777. <vxe-column field="effect" title="功效" />
  778. <vxe-column title="预览" width="60">
  779. <template #default="{ row }">
  780. <EyeOutlined style="font-size: 18px; color: #1890ff; cursor: pointer" @click.stop="detailPreview(row)" />
  781. </template>
  782. </vxe-column>
  783. </vxe-table>
  784. </template>
  785. <a-input v-model:value="row.name" placeholder="请搜索" style="width: 120px" @click="showProjectPopover = true" readonly />
  786. </a-popover>
  787. </template>
  788. <template v-else>
  789. {{ row.conditioningProgramDetail?.name }}
  790. </template>
  791. </template>
  792. </vxe-column>
  793. <vxe-column title="预览" width="50">
  794. <template #default="{ row }">
  795. <EyeOutlined style="font-size: 18px; color: #1890ff; cursor: pointer" @click="onPreview(row)" v-if="row?.conditioningProgramDetail?.id" />
  796. </template>
  797. </vxe-column>
  798. <vxe-column field="days" title="周期" width="100">
  799. <template #default="{ row }">
  800. <div style="display: flex; align-items: center">
  801. <a-input v-model:value="row.days" @change="() => calculateCount(row)" />
  802. <span>天</span>
  803. </div>
  804. </template>
  805. </vxe-column>
  806. <vxe-column field="frequencyType" title="频率" width="auto">
  807. <template #default="{ row }">
  808. <div v-if="row.conditioningProgramDetail?.name === '健康咨询' || row.conditioningProgramDetail?.name === '健康评估'" class="flex items-center">
  809. <div class="flex items-center mr-4">
  810. <span>每</span>
  811. <a-input v-model:value="row.frequencyType" style="width: 50px" @change="() => calculateCount(row)" :disabled="row.frequencyType === '不限'" />
  812. <span>天</span>
  813. <a-input v-model:value="row.frequencyMeasure" style="width: 50px" @change="() => calculateCount(row)" :disabled="row.frequencyType === '不限'" />
  814. <span>{{ row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit ? row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit : '次' }}</span>
  815. </div>
  816. <div>
  817. <a-checkbox-group
  818. v-model:value="row.frequencyTypeing"
  819. @change="
  820. (value) => {
  821. row.frequencyTypeing = value.includes('不限') ? ['不限'] : [];
  822. row.frequencyType = value.includes('不限') ? '不限' : '';
  823. calculateCount(row);
  824. }
  825. "
  826. >
  827. <a-checkbox value="不限">不限</a-checkbox>
  828. </a-checkbox-group>
  829. </div>
  830. </div>
  831. <div class="flex items-center" v-else>
  832. <span>每</span>
  833. <a-input v-model:value="row.frequencyType" style="width: 50px" @change="() => calculateCount(row)" />
  834. <span>天</span>
  835. <a-input v-model:value="row.frequencyMeasure" style="width: 50px" @change="() => calculateCount(row)" />
  836. <span>{{ row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit ? row.conditioningProgramDetail?.cpFixedPricingRule?.convertUnit : '次' }}</span>
  837. </div>
  838. </template>
  839. </vxe-column>
  840. <vxe-column field="row?.conditioningProgramDetail?.conditioningProgramType" title="方案类型" width="120">
  841. <template #default="{ row }">
  842. {{ row?.conditioningProgramDetail?.conditioningProgramType || '-' }}
  843. </template>
  844. </vxe-column>
  845. <vxe-column field="row.totalMeasure" title="数量" width="60">
  846. <template #default="{ row }">
  847. {{ row.totalMeasure || 0 }}
  848. </template>
  849. </vxe-column>
  850. <vxe-column field="row?.conditioningProgramDetail?.cpFixedPricingRule?.unit" title="单位" width="60">
  851. <template #default="{ row }">
  852. {{ row?.conditioningProgramDetail?.cpFixedPricingRule?.pricingUnit ? row?.conditioningProgramDetail?.cpFixedPricingRule?.pricingUnit : '次' }}
  853. </template>
  854. </vxe-column>
  855. <vxe-column field="row.conditioningProgramDetail.cpFixedPricingRule.unitPrice" title="单价(元)" width="100">
  856. <template #default="{ row }">
  857. {{ row?.conditioningProgramDetail?.pricingType === '0' ? row.conditioningProgramDetail?.cpFixedPricingRule?.unitPrice : row?.unitPrice }}
  858. </template>
  859. </vxe-column>
  860. <vxe-column field="row.totalPrice" title="总价(元)" width="auto" @change="() => calculateCount(row)">
  861. <template #default="{ row }">
  862. <span>{{ row.totalPrice || 0 }}</span>
  863. </template>
  864. </vxe-column>
  865. <vxe-column field="start" title="开始时间" width="190">
  866. <template #default="{ row }">
  867. <div style="display: flex; align-items: center">
  868. <span>调养开始第</span>
  869. <a-input v-model:value="row.initialDay" style="width: 60px" />
  870. <span>天</span>
  871. </div>
  872. </template>
  873. </vxe-column>
  874. <vxe-column field="conditioningProgramDetail.isOffline" title="线下项目" width="100">
  875. <template #default="{ row }">
  876. <span v-if="row.conditioningProgramDetail?.isOffline">
  877. {{ row.conditioningProgramDetail.isOffline === 'Y' ? '是' : '否' }}
  878. </span>
  879. <span v-else>-</span>
  880. </template>
  881. </vxe-column>
  882. <vxe-column field="conditioningProgramDetail.pricingType" title="穴位/经络/部位" width="120">
  883. <template #default="{ row }">
  884. <a @click="editPart(row)" style="color: #1890ff; cursor: pointer" v-if="row?.conditioningProgramDetail?.pricingType === '1'">编辑</a>
  885. </template>
  886. </vxe-column>
  887. <vxe-column field="remark" title="说明" width="180">
  888. <template #default="{ row }">
  889. <a-textarea v-model:value="row.remark" style="max-width: 180px; width: 100%; height: 50px" :rows="2" show-count :maxLength="200" />
  890. </template>
  891. </vxe-column>
  892. </vxe-table>
  893. </div>
  894. <a-image
  895. :width="200"
  896. :style="{ display: 'none' }"
  897. :preview="{
  898. visible,
  899. onVisibleChange: setVisible,
  900. }"
  901. :src="previewImg"
  902. />
  903. <div style="display: flex; justify-content: flex-end">
  904. <span style="font-weight: bold">合计:{{ formData.price }}元</span>
  905. </div>
  906. <div style="display: flex; justify-content: center">
  907. <a-button style="margin-right: 24px" @click="cancel">取消</a-button>
  908. <a-button type="primary" @click="confirm">确认</a-button>
  909. </div>
  910. </div>
  911. </template>
  912. <style scoped lang="scss">
  913. /* 可根据实际需求自定义样式 */
  914. .v-dropdown-trigger {
  915. width: 160px !important;
  916. }
  917. .sp-trigger-container .sp-trigger.sp-select .sp-select-content {
  918. overflow: hidden !important;
  919. width: 100px !important;
  920. white-space: nowrap !important;
  921. text-overflow: ellipsis !important;
  922. }
  923. .avatar-uploader > .ant-upload {
  924. width: 128px;
  925. height: 128px;
  926. }
  927. .ant-upload-select-picture-card i {
  928. font-size: 32px;
  929. color: #999;
  930. }
  931. .ant-upload-select-picture-card .ant-upload-text {
  932. margin-top: 8px;
  933. color: #666;
  934. }
  935. </style>