Enabled.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <script setup lang="ts">
  2. import type { PlanModel } from '@/model/system.model';
  3. import { tagMethod } from '@/request/api/system.api';
  4. import { message, notification } from 'ant-design-vue';
  5. import {
  6. doctorMethod,
  7. departmentsMethod,
  8. planEditMethod,
  9. allTagsSearchMethod,
  10. } from '@/request/api/follow.api';
  11. import { useRequest } from 'alova/client';
  12. import {
  13. type VxeFormListeners,
  14. type VxeFormProps,
  15. VxeUI,
  16. type VxeFormInstance,
  17. } from 'vxe-pc-ui';
  18. import { list2Groups } from '@/tools/data';
  19. (notification.config as any)({
  20. zIndex: 10000, // 直接设置层级
  21. });
  22. type FollowModel = Partial<PlanModel>;
  23. const defaultModel = {};
  24. const props = defineProps<{ data: FollowModel }>();
  25. const emits = defineEmits<{
  26. submit: [data?: PlanModel];
  27. }>();
  28. const { loading, send: load } = useRequest(tagMethod, {
  29. immediate: false,
  30. initialData: props.data ?? defaultModel,
  31. }).onSuccess(({ data }) => {
  32. formProps.data = { ...data };
  33. });
  34. const { loading: submitting, send: submit } = useRequest(planEditMethod, {
  35. immediate: false,
  36. }).onSuccess(({ data }) => {
  37. emits('submit');
  38. });
  39. // 获取就诊医生
  40. const { data: doctorData, loading: doctorDataLoading } = useRequest(doctorMethod, {
  41. initialData: { total: 0, data: [] },
  42. });
  43. // 获取就诊科室
  44. const { data: departmentsData, loading: depDataLoading } = useRequest(departmentsMethod, {
  45. initialData: { total: 0, data: [] },
  46. });
  47. // 获取患者标签
  48. const { data: tagData, loading: tagDataLoading } = useRequest(allTagsSearchMethod, {
  49. initialData: { total: 0, data: [] },
  50. });
  51. // 第一步 填写随访计划
  52. const formProps = reactive<VxeFormProps<FollowModel>>({
  53. titleWidth: 110,
  54. titleAlign: 'right',
  55. titleBold: true,
  56. titleColon: true,
  57. data: { ...props.data, frequency: props.data?.frequency ? props.data.frequency : 1 },
  58. // data:{frequency:1},
  59. items: [
  60. {
  61. field: 'name',
  62. title: '计划名称',
  63. span: 24,
  64. itemRender: { name: 'VxeInput', props: { placeholder: '请输入随访计划名称' } },
  65. },
  66. {
  67. field: 'startDate',
  68. title: '开始日期',
  69. span: 24,
  70. itemRender: { name: 'VxeDatePicker', props: { placeholder: '请选择开始时间' } },
  71. },
  72. {
  73. field: 'endDate',
  74. title: '截止日期',
  75. span: 24,
  76. itemRender: { name: 'VxeDatePicker', props: { placeholder: '请选择截止时间' } },
  77. },
  78. {
  79. field: 'purpose',
  80. title: '随访目的',
  81. span: 24,
  82. itemRender: { name: 'VxeInput', props: { placeholder: '请编辑随访目的' } },
  83. },
  84. {
  85. field: 'arrangeTime',
  86. align: 'center',
  87. title: '随访推送时间',
  88. span: 24,
  89. slots: { default: 'pushTime' },
  90. },
  91. { field: 'frequency', title: '随访次数', span: 24, slots: { default: 'followTimes' } },
  92. { field: 'secondTimes', title: '', span: 24, slots: { default: 'secondTimes' } },
  93. {
  94. field: 'remindTime',
  95. align: 'center',
  96. title: '患者未填写,重复提醒时间',
  97. span: 24,
  98. slots: { default: 'remindTime' },
  99. },
  100. { align: 'center', span: 24, slots: { default: 'active' } },
  101. ],
  102. rules: {
  103. name: [{ required: true, message: '请输入计划名称' }],
  104. startDate: [{ required: true, message: '请选择开始日期' }],
  105. endDate: [{ required: true, message: '请选择截止日期' }],
  106. },
  107. });
  108. function formCheck(data) {
  109. if (new Date(data?.endDate) < new Date(data?.startDate)) {
  110. notification.warning({
  111. message: '结束日期不能晚于开始日期',
  112. });
  113. return;
  114. }
  115. if (!data.arrangeTime) {
  116. notification.warning({
  117. message: '请选择随访推送时间!',
  118. });
  119. return;
  120. }
  121. if (!data.remindTime) {
  122. notification.warning({
  123. message: '请选择提醒时间!',
  124. });
  125. return;
  126. }
  127. if (compareTime(data.remindTime, data.arrangeTime)) {
  128. notification.warning({
  129. message: '推送时间不能晚于提醒时间',
  130. });
  131. return;
  132. }
  133. current.value++;
  134. }
  135. const arrangeTime = ref<string>('');
  136. const remindTime = ref<string>('');
  137. const follow = shallowRef<PlanModel>();
  138. const frequencyArr = ref<any[]>([]);
  139. const formEmits: VxeFormListeners<PlanModel> = {
  140. submit({ data }) {
  141. if (!arrangeTime.value) {
  142. data.arrangeTime = currentPushTime.value;
  143. } else {
  144. data.arrangeTime = arrangeTime.value;
  145. }
  146. if (remindTime.value) {
  147. data.remindTime = remindTime.value;
  148. }
  149. frequencyArr.value = [];
  150. if (followTimesArr.value.length > 0) {
  151. followTimesArr.value.forEach((item) => frequencyArr.value.push(item.data));
  152. data.frequencyDays = frequencyArr.value?.join(',');
  153. }
  154. formCheck(data);
  155. follow.value = { ...data };
  156. },
  157. };
  158. // 第二步 填写筛选病人表单
  159. const patientsForm = reactive<VxeFormProps<PlanModel['filter']>>({
  160. titleWidth: 110,
  161. titleAlign: 'right',
  162. titleBold: true,
  163. titleColon: true,
  164. data: { ...props.data?.filter },
  165. items: [
  166. {
  167. field: 'tagIds',
  168. title: '患者标签',
  169. span: 24,
  170. itemRender: {
  171. name: 'VxeSelect',
  172. props: {
  173. placeholder: '患者标签',
  174. loading: tagDataLoading,
  175. optionGroups: computed(() =>
  176. list2Groups(
  177. tagData.value.data.filter((tag) => !tag.disabled),
  178. 'category',
  179. (key) => ({ 1: '系统标签', 2: '个人标签' })[key]!
  180. )
  181. ),
  182. optionProps: { value: 'id', label: 'name' },
  183. optionGroupProps: { options: 'groups' },
  184. clearable: true,
  185. multiple: true,
  186. filterable: true,
  187. },
  188. },
  189. },
  190. {
  191. field: 'departments',
  192. title: '就诊科室',
  193. span: 24,
  194. itemRender: {
  195. name: 'VxeSelect',
  196. props: {
  197. loading: depDataLoading,
  198. options: computed(() => departmentsData.value.departmentsData),
  199. optionProps: { value: 'name', label: 'name' },
  200. clearable: true,
  201. multiple: true,
  202. filterable: true,
  203. },
  204. },
  205. },
  206. {
  207. field: 'doctors',
  208. title: '就诊医生',
  209. span: 24,
  210. itemRender: {
  211. name: 'VxeSelect',
  212. props: {
  213. loading: doctorDataLoading,
  214. options: computed(() => doctorData.value.doctorData),
  215. optionProps: { value: 'name', label: 'name' },
  216. clearable: true,
  217. multiple: true,
  218. filterable: true,
  219. },
  220. },
  221. },
  222. { align: 'center', span: 24, slots: { default: 'patientsBtn' } },
  223. ],
  224. });
  225. const tagNames = ref<any[]>([]);
  226. const submitData = ref({});
  227. const patientsEmits: VxeFormListeners<PlanModel['filter']> = {
  228. async submit({ data }) {
  229. if (data?.tagIds?.length > 0) {
  230. const tagMap = new Map(tagData.value?.data?.map(tag => [tag.id, tag.name]) || []);
  231. const tagIds = Array.isArray(data.tagIds) ? data.tagIds : [data.tagIds];
  232. tagNames.value = tagIds
  233. .map(tagId => tagMap.get(tagId))
  234. .filter(Boolean); // 过滤掉 undefined 值
  235. } else {
  236. tagNames.value = [];
  237. }
  238. const tagIdsArr = Array.isArray(data.tagIds)
  239. ? data.tagIds
  240. : data.tagIds
  241. ? [data.tagIds]
  242. : [];
  243. const filterPayload = {
  244. ...data,
  245. tagIds: tagIdsArr,
  246. tagNames: tagNames.value,
  247. };
  248. submitData.value = { ...formProps.data, filter: filterPayload };
  249. await submit(submitData.value);
  250. // 关闭弹窗
  251. VxeUI.modal.close(`plan-modal`);
  252. },
  253. };
  254. // 步骤条样式
  255. const stepStyle = {
  256. width: '100%',
  257. marginBottom: '20px',
  258. boxShadow: '0px -1px 0 0 #e8e8e8 inset',
  259. };
  260. // 点击下一步
  261. const current = ref<number>(0);
  262. onBeforeMount(() => {
  263. changeFrequency();
  264. });
  265. const timeArr = Array.from({ length: 16 }, (_, i) =>
  266. i + 6 >= 10 ? `${6 + i}:00` : `0${6 + i}:00`
  267. );
  268. // 随访计划填写取消
  269. function cancel() {
  270. VxeUI.modal.close(`plan-modal`);
  271. }
  272. const followTimesArr = ref<any[]>([]);
  273. function changeFrequency() {
  274. const length = followTimesArr.value.length;
  275. const diff = (formProps.data?.frequency ?? 0) - length;
  276. if (diff > 0) {
  277. for (var i = 0; i < diff; i++) {
  278. followTimesArr.value.push({
  279. field: 'secondTimes',
  280. title: `第${length + i + 1}随访时间`,
  281. span: 8,
  282. slots: { default: 'secondTimes' },
  283. data: formProps?.data?.frequencyDays?.split(',')[i]
  284. ? formProps?.data?.frequencyDays?.split(',')[i]
  285. : props.data?.frequency === undefined
  286. ? length + i + 1
  287. : 1,
  288. });
  289. }
  290. } else {
  291. followTimesArr.value = followTimesArr.value.slice(0, formProps.data?.frequency);
  292. }
  293. }
  294. function compareTime(t1, t2) {
  295. var date = new Date();
  296. var a = t1.split(':');
  297. var b = t2.split(':');
  298. return date.setHours(a[0], a[1]) < date.setHours(b[0], b[1]);
  299. }
  300. const form1 = ref<VxeFormInstance>();
  301. // 点击下一步
  302. function changeStep(currentStep) {
  303. // debugger
  304. if (currentStep === 1) {
  305. if (arrangeTime.value) {
  306. formProps.data.arrangeTime = arrangeTime.value;
  307. }
  308. if (remindTime.value) {
  309. formProps.data.remindTime = remindTime.value;
  310. }
  311. form1.value?.validate().then((haveValidate) => {
  312. if (haveValidate) {
  313. current.value = 0;
  314. } else {
  315. if (new Date(formProps?.data?.endDate) < new Date(formProps?.data?.startDate)) {
  316. message.error('结束日期不能晚于开始日期');
  317. current.value = 0;
  318. return;
  319. }
  320. if (!formProps.data.arrangeTime) {
  321. message.error('请选择随访推送时间!');
  322. current.value = 0;
  323. return;
  324. }
  325. if (!formProps.data.remindTime) {
  326. message.error('请选择提醒时间!');
  327. current.value = 0;
  328. return;
  329. }
  330. if (compareTime(formProps.data.remindTime, formProps.data.arrangeTime)) {
  331. message.error('推送时间不能晚于提醒时间');
  332. current.value = 0;
  333. return;
  334. }
  335. // 验证通过后,保存第一步的数据到 follow.value
  336. follow.value = { ...formProps.data };
  337. }
  338. });
  339. }
  340. }
  341. // 推送时间
  342. const currentPushTime = ref<string>(timeArr[0]);
  343. function selectPushTime(item, pushIndex) {
  344. arrangeTime.value = item;
  345. currentPushTime.value = item;
  346. }
  347. // 提醒时间
  348. const currentRemindTime = ref<string>();
  349. function selectRemindTime(item, remindIndex) {
  350. remindTime.value = item;
  351. currentRemindTime.value = item;
  352. }
  353. onMounted(() => {
  354. if(formProps.data.arrangeTime){
  355. currentPushTime.value = formProps.data.arrangeTime;
  356. }
  357. })
  358. </script>
  359. <template>
  360. <div>
  361. <a-steps
  362. @change="changeStep"
  363. v-model:current="current"
  364. size="small"
  365. :style="stepStyle"
  366. type="navigation"
  367. :items="[
  368. {
  369. title: '填写随访计划',
  370. },
  371. {
  372. title: '筛选病人',
  373. },
  374. ]"
  375. >
  376. </a-steps>
  377. </div>
  378. <vxe-form
  379. v-bind="formProps"
  380. v-on="formEmits"
  381. :loading
  382. v-show="current != 1"
  383. class="flex flex-col"
  384. ref="form1"
  385. >
  386. <template #active="{ data }">
  387. <vxe-button
  388. type="submit"
  389. status="primary"
  390. content="下一步"
  391. :loading="submitting"
  392. ></vxe-button>
  393. </template>
  394. <template #followTimes>
  395. <div class="flex items-center">
  396. <vxe-input
  397. type="integer"
  398. v-model="formProps.data!.frequency"
  399. @change="changeFrequency"
  400. min="1"
  401. ></vxe-input>
  402. <div class="ml-3">次</div>
  403. </div>
  404. </template>
  405. <template #secondTimes>
  406. <div class="flex items-center mb-3" v-for="item in followTimesArr" :key="item.title">
  407. <div style="width: 110px" class="flex justify-end pr-1 font-bold">{{ item.title }}:</div>
  408. <div class="mr-2">就诊后第</div>
  409. <div>
  410. <vxe-input type="integer" min="0" v-model="item.data"></vxe-input>
  411. </div>
  412. <div class="ml-3">天</div>
  413. </div>
  414. </template>
  415. <!--随访推送时间-->
  416. <template #pushTime>
  417. <div class="flex flex-wrap border border-solid border-gray-200">
  418. <div
  419. class="flex-none border border-solid border-gray-200 text-xs"
  420. style="width: 25%; height: 50px; line-height: 50px"
  421. v-for="(pushTime, pushIndex) in timeArr"
  422. :key="pushTime"
  423. @click="selectPushTime(pushTime, pushIndex)"
  424. :class="
  425. currentPushTime === pushTime
  426. ? 'bg-blue color-white'
  427. : ''
  428. "
  429. >
  430. {{ pushTime }}
  431. </div>
  432. </div>
  433. </template>
  434. <!--重复提醒时间-->
  435. <template #remindTime="{ data }">
  436. <div class="flex flex-wrap border border-solid border-gray-200">
  437. <div
  438. class="flex-none border border-solid border-gray-200 text-xs"
  439. style="width: 25%; height: 50px; line-height: 50px"
  440. v-for="(remindTime, remindIndex) in timeArr"
  441. :key="remindTime"
  442. @click="selectRemindTime(remindTime, remindIndex)"
  443. :class="
  444. (currentRemindTime ? currentRemindTime : data.remindTime) === remindTime
  445. ? 'bg-blue color-white'
  446. : ''
  447. "
  448. >
  449. {{ remindTime }}
  450. </div>
  451. </div>
  452. </template>
  453. </vxe-form>
  454. <!--筛选病人 -->
  455. <vxe-form v-bind="patientsForm" v-on="patientsEmits" :loading v-show="current === 1">
  456. <template #patientsBtn>
  457. <div class="tips">请按照需求选择纳入随访的病人</div>
  458. <vxe-button
  459. content="取消"
  460. :loading="submitting"
  461. name="cancel"
  462. class="mr-2"
  463. @click="cancel"
  464. ></vxe-button>
  465. <vxe-button type="submit" content="完成" :disabled="submitting" status="primary"></vxe-button>
  466. </template>
  467. </vxe-form>
  468. </template>
  469. <style scoped lang="scss">
  470. .tips {
  471. text-align: center;
  472. margin: 40px auto 100px auto;
  473. font-weight: bold;
  474. font-size: 14px;
  475. }
  476. .mesh-grid {
  477. border-collapse: collapse;
  478. }
  479. .mesh-grid td {
  480. border: 1px solid black;
  481. width: 100px;
  482. padding: 20px 20px;
  483. text-align: center;
  484. }
  485. </style>