EditEquirement.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. <script setup lang="ts">
  2. import { VxeUI, type VxeFormProps, type VxeFormListeners } from 'vxe-pc-ui';
  3. import { useRequest } from 'alova/client';
  4. import { addDeviceRegisterMethod, getDeviceRegisterDetailMethod } from '@/request/api/device.api';
  5. import { branchMethod } from '@/request/api/system.api';
  6. import { notification } from 'ant-design-vue';
  7. import { getDictionaryMethod } from '@/request/api/dictionary.api';
  8. import type { EquirementModel } from '@/model/device.model';
  9. type FollowModel = Partial<EquirementModel>;
  10. const defaultModel = {
  11. deviceIds: [''],
  12. };
  13. const props = defineProps<{ data: FollowModel }>();
  14. const emits = defineEmits<{
  15. submit: [data?: EquirementModel];
  16. }>();
  17. const model = ref<EquirementModel>({ ...defaultModel });
  18. watchEffect(() => {
  19. if (props.data) {
  20. model.value = { ...defaultModel, ...props.data };
  21. }
  22. });
  23. // 确保processConfig始终存在
  24. // watchEffect(() => {
  25. // if (!model.value.processConfig) {
  26. // model.value.processConfig = {
  27. // archiving: false,
  28. // tongueDiagnosis: false,
  29. // tongueReport: 'none' as const,
  30. // pulseDiagnosis: false,
  31. // pulseReport: 'none' as const,
  32. // inquiry: false,
  33. // healthReport: 'none' as const,
  34. // conditioningPlan: 'none' as const
  35. // };
  36. // }
  37. // });
  38. // 获取详情
  39. const getDetail = async () => {
  40. const res = await getDeviceRegisterDetailMethod(props.data);
  41. if (res) {
  42. model.value = { ...defaultModel, ...res };
  43. model.value.deviceIds = [model.value.deviceCode ?? ''];
  44. }
  45. };
  46. onMounted(() => {
  47. if (props.data && props.data.id) {
  48. getDetail();
  49. }
  50. });
  51. const branch = ref<any[]>([]);
  52. const { loading: branchLoading } = useRequest(branchMethod(0, 1, 1)).onSuccess(({ data }) => {
  53. const to = (data?: any[]): any[] => {
  54. return Array.isArray(data)
  55. ? data.map((item) => {
  56. return {
  57. ...item,
  58. value: item.id,
  59. key: item.id.toString(),
  60. children: to(item.children),
  61. };
  62. })
  63. : [];
  64. };
  65. branch.value = to(data);
  66. });
  67. const { loading: submitting, send: submit } = useRequest(addDeviceRegisterMethod, { immediate: false }).onSuccess(({ data }) => {
  68. emits('submit');
  69. });
  70. // 获取设备类型
  71. const deviceTypes = ref<{ id: string; name: string }[]>([]);
  72. const deviceTypesLoading = ref(false);
  73. // 获取设备类型
  74. async function getDeviceType() {
  75. deviceTypesLoading.value = true;
  76. const res = await getDictionaryMethod('fdhb_device_type');
  77. if (res && res.length > 0) {
  78. deviceTypes.value = res.map((item: any) => ({
  79. id: item.value,
  80. name: item.label,
  81. }));
  82. }
  83. deviceTypesLoading.value = false;
  84. }
  85. const showDept = ref(false);
  86. const insArr = ref<any[]>([]);
  87. const insLoading = ref(false);
  88. async function getInstitution(orgId: string | number) {
  89. insLoading.value = true;
  90. const res = await branchMethod(1, 0, Number(orgId));
  91. if (res && res.length > 0) {
  92. insArr.value = res;
  93. }
  94. insLoading.value = false;
  95. }
  96. watch(
  97. () => model.value.orgId,
  98. async (newVal) => {
  99. showDept.value = !!newVal;
  100. if (showDept.value) {
  101. // 请求获取机构
  102. getInstitution(newVal ?? '');
  103. }
  104. if (!newVal || newVal !== model.value.institutionId) {
  105. model.value.institutionId = ''; // 或者 ''
  106. }
  107. }
  108. );
  109. const formItems = computed(() => {
  110. const baseItems: any[] = [
  111. {
  112. field: 'deviceType',
  113. title: '设备名称',
  114. span: 13,
  115. itemRender: {
  116. name: 'VxeSelect',
  117. props: {
  118. placeholder: '请选择',
  119. loading: deviceTypesLoading.value,
  120. options: computed(() => deviceTypes.value),
  121. optionProps: { value: 'id', label: 'name' },
  122. optionGroupProps: { options: 'groups' },
  123. clearable: true,
  124. filterable: true,
  125. },
  126. },
  127. },
  128. {
  129. field: 'id',
  130. title: '设备ID',
  131. span: 24,
  132. slots: {
  133. title: 'deviceIdTitleSlot',
  134. default: 'deviceIdSlot',
  135. },
  136. },
  137. {
  138. field: 'orgId',
  139. title: '组织名称',
  140. span: 13,
  141. itemRender: {
  142. name: 'VxeSelect',
  143. props: {
  144. placeholder: '请选择',
  145. loading: computed(() => branchLoading.value),
  146. options: computed(() => branch.value),
  147. optionProps: {
  148. value: 'value',
  149. label: 'label',
  150. },
  151. clearable: true,
  152. },
  153. },
  154. },
  155. // {
  156. // field: 'processConfig',
  157. // title: '',
  158. // span: 24,
  159. // slots: {
  160. // default: 'processConfigSlot',
  161. // },
  162. // },
  163. {
  164. field: 'remarks',
  165. title: '备注',
  166. span: 24,
  167. slots: {
  168. default: 'remarksSlot',
  169. },
  170. },
  171. { align: 'center', span: 24, slots: { default: 'active' } },
  172. ];
  173. // 如果 showDept 为 true,在 institutionId 后面插入 institutionId
  174. if (showDept.value) {
  175. baseItems.splice(3, 0, {
  176. field: 'institutionId',
  177. title: '机构名称',
  178. span: 13,
  179. itemRender: {
  180. name: 'VxeTreeSelect',
  181. props: {
  182. placeholder: '请选择',
  183. loading: computed(() => insLoading.value),
  184. options: computed(() => insArr.value),
  185. optionProps: { value: 'id', label: 'label' },
  186. clearable: true,
  187. },
  188. },
  189. });
  190. }
  191. return baseItems;
  192. });
  193. const formProps = reactive<VxeFormProps>({
  194. titleWidth: 100,
  195. titleAlign: 'right',
  196. titleColon: true,
  197. data: computed(() => model.value),
  198. items: [] as any, // 临时设置为空数组,我们将在模板中使用动态items
  199. rules: {
  200. deviceType: [{ required: true, message: '请选择设备名称' }],
  201. deviceIds: [{ required: true, message: '请输入设备ID' }],
  202. orgId: [{ required: true, message: '请选择组织名称' }],
  203. institutionId: [{ required: true, message: '请选择机构名称' }],
  204. },
  205. });
  206. const formEmits: VxeFormListeners = {
  207. submit({ data }) {
  208. // 验证必填字段
  209. if (!data.deviceType) {
  210. notification.error({
  211. message: '请选择设备名称',
  212. });
  213. return false; // 阻止表单提交
  214. }
  215. if (!data.deviceIds || data.deviceIds.length === 0 || data.deviceIds.every((id: string) => !id.trim())) {
  216. notification.error({
  217. message: '请输入设备ID',
  218. });
  219. return false; // 阻止表单提交
  220. }
  221. if (!data.orgId) {
  222. notification.error({
  223. message: '请选择组织名称',
  224. });
  225. return false; // 阻止表单提交
  226. }
  227. if (!data.institutionId) {
  228. notification.error({
  229. message: '请选择机构名称',
  230. });
  231. return false; // 阻止表单提交
  232. }
  233. if (data.id) {
  234. data.deviceCode = data.deviceIds[0];
  235. }
  236. submit(data).then(() => {
  237. notification.success({
  238. message: '操作成功',
  239. });
  240. VxeUI.modal.close('equipment-modal');
  241. });
  242. },
  243. };
  244. function cancel() {
  245. VxeUI.modal.close('equirement-modal');
  246. }
  247. function addDeviceId() {
  248. if (!model.value.deviceIds) {
  249. model.value.deviceIds = [''];
  250. }
  251. model.value.deviceIds.push('');
  252. }
  253. function removeDeviceId(index: number) {
  254. if (model.value.deviceIds && model.value.deviceIds.length > 1) {
  255. model.value.deviceIds.splice(index, 1);
  256. }
  257. }
  258. onBeforeMount(async () => {
  259. if (props.data) {
  260. model.value = { ...defaultModel, ...props.data };
  261. }
  262. // 获取设备名称·
  263. getDeviceType();
  264. });
  265. </script>
  266. <template>
  267. <div class="form-container">
  268. <vxe-form
  269. :title-width="formProps.titleWidth"
  270. :title-align="formProps.titleAlign"
  271. :title-colon="formProps.titleColon"
  272. :data="formProps.data"
  273. :items="formItems"
  274. :rules="formProps.rules"
  275. v-on="formEmits"
  276. :loading="submitting"
  277. >
  278. <template #deviceIdTitleSlot>
  279. <span style="color: #f56c6c;font-size: 20px;">*</span> 设备ID
  280. </template>
  281. <template #deviceIdSlot>
  282. <div class="device-ids-container">
  283. <div v-for="(deviceId, index) in model.deviceIds || []" :key="index" class="device-id-item">
  284. <vxe-input v-model="model.deviceIds[index]" placeholder="请输入" style="width: 200px" />
  285. <vxe-button v-if="(model.deviceIds || []).length > 1" type="text" style="color: #ff4d4f; margin-left: 8px" @click="removeDeviceId(index)">
  286. <template #default>×</template>
  287. </vxe-button>
  288. </div>
  289. <vxe-button type="text" style="border: 1px dashed #d9d9d9; width: 32px; height: 32px; margin-bottom: 8px" @click="addDeviceId" v-if="!model?.id">
  290. <template #default>+</template>
  291. </vxe-button>
  292. </div>
  293. </template>
  294. <!-- <template #processConfigSlot>
  295. <div class="section-container">
  296. <div class="section-title">流程配置</div>
  297. <div class="section-divider"></div>
  298. <div class="process-config">
  299. <div class="config-item">
  300. <span class="config-label">建档:</span>
  301. <div class="radio-group">
  302. <label class="radio-item">
  303. <input
  304. type="radio"
  305. v-model="model.processConfig.archiving"
  306. :value="true"
  307. :checked="model.processConfig?.archiving"
  308. />
  309. <span>有</span>
  310. </label>
  311. <label class="radio-item">
  312. <input
  313. type="radio"
  314. v-model="model.processConfig.archiving"
  315. :value="false"
  316. :checked="!model.processConfig?.archiving"
  317. />
  318. <span>无</span>
  319. </label>
  320. </div>
  321. </div>
  322. <div class="config-item">
  323. <span class="config-label">舌面诊:</span>
  324. <div class="radio-group">
  325. <label class="radio-item">
  326. <input
  327. type="radio"
  328. v-model="model.processConfig.tongueDiagnosis"
  329. :value="true"
  330. :checked="model.processConfig?.tongueDiagnosis"
  331. disabled
  332. />
  333. <span>有</span>
  334. </label>
  335. <label class="radio-item">
  336. <input
  337. type="radio"
  338. v-model="model.processConfig.tongueDiagnosis"
  339. :value="false"
  340. :checked="!model.processConfig?.tongueDiagnosis"
  341. />
  342. <span>无</span>
  343. </label>
  344. </div>
  345. <span class="report-label">舌面分析报告:</span>
  346. <div class="radio-group">
  347. <label class="radio-item">
  348. <input
  349. type="radio"
  350. v-model="model.processConfig.tongueReport"
  351. value="full"
  352. :checked="model.processConfig?.tongueReport === 'full'"
  353. />
  354. <span>完整展示</span>
  355. </label>
  356. <label class="radio-item">
  357. <input
  358. type="radio"
  359. v-model="model.processConfig.tongueReport"
  360. value="scan"
  361. :checked="model.processConfig?.tongueReport === 'scan'"
  362. />
  363. <span>扫码查看</span>
  364. </label>
  365. <label class="radio-item">
  366. <input
  367. type="radio"
  368. v-model="model.processConfig.tongueReport"
  369. value="none"
  370. :checked="model.processConfig?.tongueReport === 'none'"
  371. />
  372. <span>无</span>
  373. </label>
  374. </div>
  375. </div>
  376. <div class="config-item">
  377. <span class="config-label">脉诊:</span>
  378. <div class="radio-group">
  379. <label class="radio-item">
  380. <input
  381. type="radio"
  382. v-model="model.processConfig.pulseDiagnosis"
  383. :value="true"
  384. :checked="model.processConfig?.pulseDiagnosis"
  385. />
  386. <span>有</span>
  387. </label>
  388. <label class="radio-item">
  389. <input
  390. type="radio"
  391. v-model="model.processConfig.pulseDiagnosis"
  392. :value="false"
  393. :checked="!model.processConfig?.pulseDiagnosis"
  394. />
  395. <span>无</span>
  396. </label>
  397. </div>
  398. <span class="report-label">脉象分析报告:</span>
  399. <div class="radio-group">
  400. <label class="radio-item">
  401. <input
  402. type="radio"
  403. v-model="model.processConfig.pulseReport"
  404. value="full"
  405. :checked="model.processConfig?.pulseReport === 'full'"
  406. />
  407. <span>完整展示</span>
  408. </label>
  409. <label class="radio-item">
  410. <input
  411. type="radio"
  412. v-model="model.processConfig.pulseReport"
  413. value="scan"
  414. :checked="model.processConfig?.pulseReport === 'scan'"
  415. />
  416. <span>扫码查看</span>
  417. </label>
  418. <label class="radio-item">
  419. <input
  420. type="radio"
  421. v-model="model.processConfig.pulseReport"
  422. value="none"
  423. :checked="model.processConfig?.pulseReport === 'none'"
  424. />
  425. <span>无</span>
  426. </label>
  427. </div>
  428. </div>
  429. <div class="config-item">
  430. <span class="config-label">问诊:</span>
  431. <div class="radio-group">
  432. <label class="radio-item">
  433. <input
  434. type="radio"
  435. v-model="model.processConfig.inquiry"
  436. :value="true"
  437. :checked="model.processConfig?.inquiry"
  438. />
  439. <span>有</span>
  440. </label>
  441. <label class="radio-item">
  442. <input
  443. type="radio"
  444. v-model="model.processConfig.inquiry"
  445. :value="false"
  446. :checked="!model.processConfig?.inquiry"
  447. />
  448. <span>无</span>
  449. </label>
  450. </div>
  451. </div>
  452. <div class="config-item">
  453. <span class="config-label">健康分析报告:</span>
  454. <div class="radio-group">
  455. <label class="radio-item">
  456. <input
  457. type="radio"
  458. v-model="model.processConfig.healthReport"
  459. value="full"
  460. :checked="model.processConfig?.healthReport === 'full'"
  461. />
  462. <span>完整展示</span>
  463. </label>
  464. <label class="radio-item">
  465. <input
  466. type="radio"
  467. v-model="model.processConfig.healthReport"
  468. value="scan"
  469. :checked="model.processConfig?.healthReport === 'scan'"
  470. />
  471. <span>扫码查看</span>
  472. </label>
  473. <label class="radio-item">
  474. <input
  475. type="radio"
  476. v-model="model.processConfig.healthReport"
  477. value="none"
  478. :checked="model.processConfig?.healthReport === 'none'"
  479. />
  480. <span>无</span>
  481. </label>
  482. </div>
  483. </div>
  484. <div class="config-item">
  485. <span class="config-label">调理方案:</span>
  486. <div class="radio-group">
  487. <label class="radio-item">
  488. <input
  489. type="radio"
  490. v-model="model.processConfig.conditioningPlan"
  491. value="full"
  492. :checked="model.processConfig?.conditioningPlan === 'full'"
  493. />
  494. <span>完整展示</span>
  495. </label>
  496. <label class="radio-item">
  497. <input
  498. type="radio"
  499. v-model="model.processConfig.conditioningPlan"
  500. value="scan"
  501. :checked="model.processConfig?.conditioningPlan === 'scan'"
  502. />
  503. <span>扫码查看</span>
  504. </label>
  505. <label class="radio-item">
  506. <input
  507. type="radio"
  508. v-model="model.processConfig.conditioningPlan"
  509. value="none"
  510. :checked="model.processConfig?.conditioningPlan === 'none'"
  511. />
  512. <span>无</span>
  513. </label>
  514. </div>
  515. </div>
  516. </div>
  517. </div>
  518. </template> -->
  519. <template #remarksSlot>
  520. <div class="section-container">
  521. <textarea
  522. v-model="model.remark"
  523. placeholder="请输入"
  524. rows="3"
  525. style="width: 100%; padding: 8px; border: 1px solid #d9d9d9; border-radius: 4px; resize: vertical; font-family: inherit"
  526. />
  527. </div>
  528. </template>
  529. <template #active>
  530. <vxe-button type="reset" content="取消" :disabled="submitting" @click="cancel"></vxe-button>
  531. <vxe-button type="submit" status="warning" content="确定" :loading="submitting"></vxe-button>
  532. </template>
  533. </vxe-form>
  534. </div>
  535. </template>
  536. <style scoped lang="scss">
  537. .form-container {
  538. padding: 20px;
  539. }
  540. .device-ids-container {
  541. display: flex;
  542. flex-direction: row;
  543. flex-wrap: wrap;
  544. gap: 8px;
  545. align-items: flex-start;
  546. .device-id-item {
  547. display: flex;
  548. align-items: center;
  549. gap: 8px;
  550. margin-bottom: 8px;
  551. }
  552. }
  553. .section-container {
  554. margin-bottom: 20px;
  555. .section-title {
  556. font-size: 16px;
  557. font-weight: bold;
  558. margin-bottom: 10px;
  559. color: #333;
  560. }
  561. .section-divider {
  562. height: 1px;
  563. background-color: #d9d9d9;
  564. margin-bottom: 15px;
  565. }
  566. }
  567. .process-config {
  568. .config-item {
  569. display: flex;
  570. align-items: center;
  571. margin-bottom: 16px;
  572. flex-wrap: wrap;
  573. gap: 8px;
  574. .config-label {
  575. font-weight: bold;
  576. min-width: 80px;
  577. }
  578. .report-label {
  579. margin-left: 40px;
  580. color: #666;
  581. margin-right: 10px;
  582. }
  583. .radio-group {
  584. display: flex;
  585. align-items: center;
  586. gap: 30px;
  587. .radio-item {
  588. display: flex;
  589. align-items: center;
  590. gap: 4px;
  591. cursor: pointer;
  592. input[type='radio'] {
  593. margin: 0;
  594. cursor: pointer;
  595. }
  596. span {
  597. font-size: 14px;
  598. }
  599. }
  600. .badge {
  601. background-color: #faad14;
  602. color: white;
  603. border-radius: 12px;
  604. padding: 2px 8px;
  605. font-size: 12px;
  606. min-width: 20px;
  607. text-align: center;
  608. }
  609. }
  610. }
  611. }
  612. :deep(.vxe-form--item) {
  613. .vxe-form--item-wrapper {
  614. .vxe-form--item-content {
  615. .vxe-input {
  616. width: 100%;
  617. }
  618. }
  619. }
  620. }
  621. .required-field {
  622. :deep(.vxe-form--item-title) {
  623. position: relative;
  624. &::before {
  625. content: '*';
  626. color: #ff4d4f;
  627. position: absolute;
  628. left: -8px;
  629. top: 0;
  630. }
  631. }
  632. }
  633. </style>