AddItems.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. <script lang="ts" setup>
  2. import { ref, watch } from 'vue';
  3. import { Form, message } from 'ant-design-vue';
  4. import VxeUI from 'vxe-table';
  5. import { useRequest } from 'alova/client';
  6. import { getDictionaryMethod, cpMedicinesMethod } from '@/request/api/dictionary.api';
  7. import { branchMethod } from '@/request/api/system.api';
  8. import { systemCpEditMethod, getAllSupplierMethod, getConditioningSchemeDetailMethod } from '@/request/api/care.api';
  9. import { UploadIFile } from '@/request/api/follow.api';
  10. import type { SystemItemModel } from '@/model/care.model';
  11. import RemoteSelect from '@/libs/v-select-page/RemoteSelect.vue';
  12. import type { UploadFile } from 'ant-design-vue/es/upload/interface';
  13. import type { FormInstance } from 'ant-design-vue';
  14. type SystemModel = Partial<SystemItemModel>;
  15. const props = defineProps<{ data: SystemModel; title: string }>();
  16. const formRef = ref<FormInstance>();
  17. const typeOptions = ref<{ label: string; value: string }[]>([]);
  18. const supplierOptions = ref<{ label: string; value: string }[]>([]);
  19. const branchOptions = ref<{ label: string; value: string }[]>([]);
  20. const unitOptions = [
  21. { label: '袋', value: '袋' },
  22. { label: '包', value: '包' },
  23. { label: '贴', value: '贴' },
  24. { label: '次', value: '次' },
  25. ];
  26. // 获取所有的机构
  27. const { data: branch, loading: branchLoading } = useRequest(branchMethod).onSuccess(({ data }) => {
  28. if (data?.length > 0) {
  29. branchOptions.value = data.map((item: any) => ({
  30. label: item.label,
  31. value: item.id,
  32. }));
  33. }
  34. });
  35. const form = reactive<SystemModel>({
  36. conditioningProgramType: '',
  37. conditioningProgramSupplierId: '',
  38. institutionId: '',
  39. cpDynamicPricingRule: [
  40. { min: 0, max: 0, priceType: 0, price: 0 },
  41. { min: 0, max: 0, priceType: 0, price: 0 },
  42. ],
  43. pricingType: '0',
  44. cpFixedPricingRule: {
  45. unitPrice: 0,
  46. pricingUnit: '',
  47. convertDose: 0,
  48. convertUnit: '',
  49. },
  50. cpMedicines: [{ name: '', dosage: '', id: '' }],
  51. isOnline: '',
  52. isDelivery: '',
  53. });
  54. const onlineArr = ref<string[]>([]);
  55. const deliverArr = ref<string[]>([]);
  56. const rules = {
  57. name: [{ required: true, message: '请输入项目名称', trigger: 'blur' }],
  58. conditioningProgramType: [{ required: true, message: '请选择方案类型', trigger: 'change' }],
  59. pricingType: [{ required: true, message: '请选择计价规则', trigger: 'change' }],
  60. conditioningProgramSupplierId: [{ required: true, message: '请选择供应商', trigger: 'change' }],
  61. institutionId: [{ required: true, message: '请选择机构名称', trigger: 'change' }],
  62. // isOnline: [{ required: true, message: '请选择线上项目', trigger: 'change' }],
  63. };
  64. const isShowOnline = ref<boolean>(false);
  65. const isShowDelivery = ref<boolean>(false);
  66. const supplierArr = ref<any[]>([]);
  67. // 获取所有的供应商
  68. async function getSupplier(params: any) {
  69. const res = await getAllSupplierMethod(params);
  70. if (res && res.length > 0) {
  71. supplierArr.value = res;
  72. supplierOptions.value = res.map((item: any) => ({
  73. label: item.name,
  74. value: item.id,
  75. }));
  76. getIsOnline(params.conditioningProgramSupplierId, params.isOnline, params.isDelivery, params.conditioningProgramTypes);
  77. }
  78. }
  79. function getIsOnline(e: any, newOnline: any, newDelivery: any, newType: any) {
  80. let filterSupplierArr = supplierArr.value.filter((item: any) => item.id === e);
  81. if (filterSupplierArr.length > 0) {
  82. isShowOnline.value = filterSupplierArr.every((items: any) => {
  83. if (newType.length > 0) {
  84. return items.offlineCPTypes.includes(newType[0]) && items.onlineCPTypes.includes(newType[0]);
  85. } else {
  86. return false;
  87. }
  88. });
  89. if (isShowOnline.value) {
  90. if (newOnline) {
  91. onlineArr.value = [newOnline];
  92. if (onlineArr.value.length > 1) {
  93. onlineArr.value = [onlineArr.value[onlineArr.value.length - 1]];
  94. } else if (onlineArr.value.length === 0) {
  95. onlineArr.value = [];
  96. }
  97. if (newOnline === 'Y') {
  98. isShowDelivery.value = true;
  99. } else {
  100. form.isDelivery = '';
  101. isShowDelivery.value = false;
  102. }
  103. }
  104. if (newDelivery) {
  105. deliverArr.value = [newDelivery];
  106. if (deliverArr.value.length > 1) {
  107. deliverArr.value = [deliverArr.value[deliverArr.value.length - 1]];
  108. } else if (deliverArr.value.length === 0) {
  109. deliverArr.value = [];
  110. }
  111. }
  112. }
  113. }else{
  114. isShowOnline.value = false;
  115. isShowDelivery.value = false;
  116. onlineArr.value = [];
  117. deliverArr.value = [];
  118. }
  119. }
  120. watch(
  121. [() => form.conditioningProgramType, () => form.institutionId, () => form.conditioningProgramSupplierId, () => form.isOnline, () => form.isDelivery],
  122. ([newType, newInstitutionId, newSupplierId, newOnline, newDelivery]) => {
  123. getSupplier({
  124. conditioningProgramTypes: newType ? [newType] : form.conditioningProgramType ? [form.conditioningProgramType] : [],
  125. institutionId: newInstitutionId ? newInstitutionId : form.institutionId ? form.institutionId : '',
  126. conditioningProgramSupplierId: newSupplierId ? newSupplierId : form.conditioningProgramSupplierId ? form.conditioningProgramSupplierId : '',
  127. isOnline: newOnline,
  128. isDelivery: newDelivery,
  129. });
  130. },
  131. { immediate: true }
  132. );
  133. function addHerb() {
  134. if (!form.cpMedicines) {
  135. form.cpMedicines = [];
  136. }
  137. form.cpMedicines.push({ name: '', dosage: '', id: '' });
  138. }
  139. function removeHerb(idx: number) {
  140. if (form.cpMedicines) {
  141. form.cpMedicines.splice(idx, 1);
  142. }
  143. }
  144. function cancel() {
  145. VxeUI.modal.close(`add-items-modal`);
  146. }
  147. function doSubmit() {
  148. console.log(formRef.value, 'formRef==>');
  149. console.log(form, 'Form Data Before Submit');
  150. formRef.value
  151. ?.validate()
  152. .then((res) => {
  153. console.log(res, 'res==>111');
  154. form.photo = fileList.value[0]?.response?.url || '';
  155. submit(form);
  156. })
  157. .catch((error) => {
  158. console.error('Validation Error:', error);
  159. message.error('请完善必填项');
  160. });
  161. }
  162. // 获取方案类型
  163. async function getConditioningProgramType() {
  164. try {
  165. const res = await getDictionaryMethod('condition_type');
  166. if (res?.length > 0) {
  167. typeOptions.value = res; // 直接使用返回的数据
  168. }
  169. } catch (error) {
  170. console.error('获取方案类型列表失败:', error);
  171. }
  172. }
  173. onMounted(async () => {
  174. const deptId = localStorage.getItem('deptId');
  175. if (form.addType === 'system' && deptId) {
  176. form.institutionId = deptId;
  177. }
  178. form.addType = props.data.addType;
  179. if (props.data.id) {
  180. const res: any = await getConditioningSchemeDetailMethod(props.data);
  181. Object.assign(form, res);
  182. form.cpMedicines = (res.cpMedicines ?? []).map((item: any) => ({
  183. name: item.name || item.herbName || item.medicineName || '',
  184. dosage: item.dosage,
  185. id: item.id,
  186. }));
  187. fileList.value = res.photo
  188. ? [
  189. {
  190. uid: '-1',
  191. name: 'image.png',
  192. status: 'done',
  193. url: res.photo,
  194. thumbUrl: res.photo,
  195. },
  196. ]
  197. : [];
  198. }
  199. // 获取供应商
  200. // getSupplier({});
  201. // 获取方案类型
  202. getConditioningProgramType();
  203. });
  204. const emits = defineEmits<{
  205. submit: [data?: SystemItemModel];
  206. addSubmit: [data?: SystemItemModel];
  207. }>();
  208. const { loading: submitting, send: submit } = useRequest(systemCpEditMethod, {
  209. immediate: false,
  210. }).onSuccess(({ data }) => {
  211. emits('submit');
  212. });
  213. const visible = ref<boolean>(false);
  214. const setVisible = (value): void => {
  215. visible.value = value;
  216. };
  217. const previewImg = ref<string>('');
  218. const uploadProps = reactive({ showRemoveIcon: true });
  219. const fileList = ref<UploadFile[]>([]);
  220. // 预览图片
  221. const handlePreview = async (file: UploadFile) => {
  222. previewImg.value = file.response?.url ?? file.thumbUrl;
  223. visible.value = true;
  224. };
  225. function customUpload(e: any) {
  226. // uploadApi 你的二次封装上传接口
  227. UploadIFile(e.file)
  228. .then((res) => {
  229. // 调用实例的成功方法通知组件该文件上传成功
  230. e.onSuccess(res, e);
  231. })
  232. .catch((err) => {
  233. // 调用实例的失败方法通知组件该文件上传失败
  234. e.onError(err);
  235. });
  236. }
  237. watch(
  238. () => form.pricingType,
  239. (val) => {
  240. if (val === '0' && !form.cpFixedPricingRule) {
  241. form.cpFixedPricingRule = {
  242. unitPrice: 0,
  243. pricingUnit: '',
  244. convertDose: 0,
  245. convertUnit: '',
  246. };
  247. } else {
  248. form.cpFixedPricingRule = null;
  249. }
  250. }
  251. );
  252. function bindchange(e: any) {
  253. form.conditioningProgramSupplierId = '';
  254. form.isOnline = '';
  255. }
  256. function onlineChange(value: any) {
  257. form.isOnline = value[value.length - 1];
  258. deliverArr.value = [];
  259. form.isDelivery = '';
  260. }
  261. function deliveryChange(value: any) {
  262. form.isDelivery = value[value.length - 1];
  263. }
  264. function getConditioningProgramSupplier(value: any) {
  265. onlineArr.value = [];
  266. deliverArr.value = [];
  267. form.isOnline = '';
  268. form.isDelivery = '';
  269. isShowDelivery.value = false;
  270. }
  271. </script>
  272. <template>
  273. <div class="form-container">
  274. <a-form ref="formRef" :model="form" :rules="rules" layout="horizontal">
  275. <a-form-item label="项目名称:" name="name" required>
  276. <a-input v-model:value="form.name" placeholder="请输入" />
  277. </a-form-item>
  278. <a-form-item label="方案类型:" name="conditioningProgramType" required>
  279. <a-select v-model:value="form.conditioningProgramType" :options="typeOptions" placeholder="请选择" allowClear @change="bindchange" />
  280. </a-form-item>
  281. <a-form-item label="计价规则:" name="pricingType" required>
  282. <a-radio-group v-model:value="form.pricingType">
  283. <a-radio value="0">一口价</a-radio>
  284. <a-radio value="1">按穴位/经络/部位</a-radio>
  285. </a-radio-group>
  286. </a-form-item>
  287. <div class="price-row" v-if="form.pricingType === '0' && form.cpFixedPricingRule">
  288. <span class="label">单价:</span>
  289. <a-input v-model:value="form.cpFixedPricingRule.unitPrice" placeholder="请输入" style="width: 100px" />
  290. <span style="margin: 0 8px">元</span>
  291. <span class="label" style="margin-left: 32px">计价单位:</span>
  292. <a-input v-model:value="form.cpFixedPricingRule.pricingUnit" placeholder="请输入" style="width: 100px" />
  293. <span style="margin-left: 32px">相当于</span>
  294. <a-input v-model:value="form.cpFixedPricingRule.convertDose" placeholder="请输入" style="width: 100px; margin-left: 8px" />
  295. <a-select v-model:value="form.cpFixedPricingRule.convertUnit" style="width: 100px; margin-left: 8px" :options="unitOptions" placeholder="请选择" />
  296. <span style="color: #aaa; margin-left: 8px">(使用单位)</span>
  297. </div>
  298. <div v-if="form.pricingType === '1'" class="per-rule">
  299. <div class="price-row">
  300. <span>计价1:</span>
  301. <span class="flex items-center"
  302. >当"穴位/经络/部位" ≤
  303. <a-input
  304. placeholder="请输入"
  305. class="w-20 ml-2 mr-2"
  306. v-model:value="form.cpDynamicPricingRule[0].min"
  307. @change="() => (form.cpDynamicPricingRule[1].max = form.cpDynamicPricingRule[0].min)"
  308. />个时,</span
  309. >
  310. <a-select
  311. v-model:value="form.cpDynamicPricingRule[0].priceType"
  312. :options="[
  313. { label: '单价', value: 0, priceType: 0 },
  314. { label: '一口价', value: 1, priceType: 1 },
  315. ]"
  316. style="width: 100px; margin: 0 4px"
  317. placeholder="请选择"
  318. />
  319. <span>=</span>
  320. <a-input v-model:value="form.cpDynamicPricingRule[0].price" style="width: 80px; margin: 0 4px" placeholder="请输入" />
  321. <span>元</span>
  322. </div>
  323. <div class="price-row">
  324. <span>计价2:</span>
  325. <span class="flex items-center"
  326. >当"穴位/经络/部位" &gt; <a-input placeholder="请输入" class="w-20 ml-2 mr-2" v-model:value="form.cpDynamicPricingRule[1].max" disabled />个时,</span
  327. >
  328. <a-select
  329. v-model:value="form.cpDynamicPricingRule[1].priceType"
  330. :options="[
  331. { label: '单价', value: 0, priceType: 0 },
  332. { label: '一口价', value: 1, priceType: 1 },
  333. ]"
  334. style="width: 100px; margin: 0 4px"
  335. placeholder="请选择"
  336. />
  337. <span>=</span>
  338. <a-input v-model:value="form.cpDynamicPricingRule[1].price" style="width: 80px; margin: 0 4px" placeholder="请输入" />
  339. <span>元</span>
  340. </div>
  341. </div>
  342. <a-form-item label="中药组成:">
  343. <div class="herb-list">
  344. <template v-for="(herb, idx) in form.cpMedicines" :key="herb.id">
  345. <div class="herb-item">
  346. <button class="herb-remove" v-if="form?.cpMedicines?.length > 1" @click="removeHerb(idx)" type="button">×</button>
  347. <RemoteSelect :load="cpMedicinesMethod" key-prop="name" v-model:value="herb.name" />
  348. <a-input v-model:value="herb.dosage" class="herb-dosage" placeholder="剂量" />
  349. <span>g</span>
  350. </div>
  351. </template>
  352. <button style="margin-left: 8px" @click="addHerb" type="button">+</button>
  353. </div>
  354. </a-form-item>
  355. <a-form-item label="功效:">
  356. <a-input v-model:value="form.effect" placeholder="请输入" />
  357. </a-form-item>
  358. <!-- 机构名称 -->
  359. <a-form-item label="机构名称:" v-if="form?.addType === 'itemsList'" required name="institutionId">
  360. <a-select v-model:value="form.institutionId" :options="branchOptions" placeholder="请选择" allowClear />
  361. </a-form-item>
  362. <a-form-item label="供应商:" required name="conditioningProgramSupplierId">
  363. <a-select v-model:value="form.conditioningProgramSupplierId" :options="supplierOptions" placeholder="请选择" allowClear @change="getConditioningProgramSupplier" />
  364. </a-form-item>
  365. <a-form-item label="线上项目:" name="isOnline" v-if="isShowOnline" required>
  366. <a-checkbox-group v-model:value="onlineArr" @change="onlineChange">
  367. <a-checkbox value="Y">是</a-checkbox>
  368. <a-checkbox value="N">否</a-checkbox>
  369. </a-checkbox-group>
  370. </a-form-item>
  371. <a-form-item label="配送:" name="isDelivery" v-if="isShowDelivery">
  372. <a-checkbox-group v-model:value="deliverArr" @change="deliveryChange">
  373. <a-checkbox value="Y">支持</a-checkbox>
  374. <a-checkbox value="N">不支持</a-checkbox>
  375. </a-checkbox-group>
  376. </a-form-item>
  377. <a-form-item label="图片:">
  378. <a-upload :showUploadList="uploadProps" v-model:file-list="fileList" list-type="picture-card" @preview="handlePreview" :maxCount="1" :customRequest="customUpload">
  379. <div v-if="fileList.length < 1">
  380. <plus-outlined />
  381. <div style="margin-top: 8px">上传</div>
  382. </div>
  383. </a-upload>
  384. </a-form-item>
  385. <a-image
  386. :width="200"
  387. :style="{ display: 'none' }"
  388. :preview="{
  389. visible,
  390. onVisibleChange: setVisible,
  391. }"
  392. :src="previewImg"
  393. />
  394. <div class="form-actions-center">
  395. <a-button @click="cancel">取消</a-button>
  396. <a-button type="primary" style="background: #faad14; border: none" @click="doSubmit">确定</a-button>
  397. </div>
  398. </a-form>
  399. </div>
  400. </template>
  401. <style scoped>
  402. .form-container {
  403. width: 760px;
  404. margin: 0 auto;
  405. padding: 10px 0px 0 0;
  406. }
  407. .per-rule {
  408. margin-bottom: 16px;
  409. }
  410. .price-row {
  411. display: flex;
  412. align-items: center;
  413. margin-bottom: 10px;
  414. }
  415. .label {
  416. font-weight: 500;
  417. color: #222;
  418. margin-right: 8px;
  419. }
  420. .herb-list {
  421. display: flex;
  422. align-items: center;
  423. flex-wrap: wrap;
  424. }
  425. .herb-item {
  426. position: relative;
  427. display: flex;
  428. align-items: center;
  429. margin-right: 8px;
  430. margin-bottom: 8px;
  431. padding-left: 16px;
  432. }
  433. .herb-dosage {
  434. width: 70px;
  435. padding: 2px 6px;
  436. font-size: 14px;
  437. margin-right: 10px;
  438. }
  439. .herb-remove {
  440. position: absolute;
  441. left: 0;
  442. top: 0;
  443. background: #fff;
  444. border: none;
  445. color: #ff4d4f;
  446. font-size: 14px;
  447. cursor: pointer;
  448. line-height: 1;
  449. width: 16px;
  450. height: 16px;
  451. padding: 0;
  452. display: flex;
  453. align-items: center;
  454. justify-content: center;
  455. }
  456. .form-actions-center {
  457. display: flex;
  458. justify-content: center;
  459. gap: 16px;
  460. /* margin-top: 32px; */
  461. }
  462. .slider-section {
  463. margin-bottom: 16px;
  464. }
  465. .slider-labels {
  466. display: flex;
  467. justify-content: space-between;
  468. width: 200px;
  469. margin-left: 230px;
  470. margin-bottom: 4px;
  471. font-weight: 500;
  472. color: #222;
  473. }
  474. .slider-row {
  475. display: flex;
  476. align-items: center;
  477. }
  478. .slider-desc {
  479. color: #222;
  480. white-space: nowrap;
  481. display: flex;
  482. flex-direction: column;
  483. align-items: center;
  484. justify-content: center;
  485. }
  486. .slider-value {
  487. margin-left: 16px;
  488. color: #faad14;
  489. font-weight: bold;
  490. }
  491. </style>