EditSystemService.vue 30 KB

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