AddItems.vue 18 KB

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