Form.vue 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. <template>
  2. <Dialog
  3. v-model="dialogVisible"
  4. :title="itemTitle"
  5. width="70%"
  6. class="form-tag-dialog life-care-plan-form special-nursing-plan-form"
  7. @close="handleClosed"
  8. scroll
  9. >
  10. <el-form
  11. ref="formRef"
  12. :model="dataForm"
  13. :rules="dataRule"
  14. label-width="80px"
  15. :toggleType="isDetail"
  16. >
  17. <div class="info-title">基本信息</div>
  18. <div class="info-wrap">
  19. <el-row :gutter="20">
  20. <el-col :span="12" :xs="24">
  21. <el-form-item label="长者姓名" prop="elderId">
  22. <SelectElder
  23. v-model="dataForm.elderId"
  24. @elder="handleElder"
  25. :style="[{ width: dataForm.id ? '30%' : '100%' }]"
  26. :toggleType="!!dataForm.id"
  27. />
  28. </el-form-item>
  29. </el-col>
  30. <el-col :span="12" :xs="24">
  31. <el-form-item label="床位号" prop="bedName">
  32. <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
  33. <el-text v-else>{{ dataForm.bedName }}</el-text>
  34. </el-form-item>
  35. </el-col>
  36. </el-row>
  37. <el-row :gutter="20">
  38. <el-col :span="12" :xs="24">
  39. <el-form-item label="护理等级" prop="nurseLevelName">
  40. <el-input v-if="!isDetail" v-model="dataForm.nurseLevelName" disabled />
  41. <el-text v-else>{{ dataForm.nurseLevelName }}</el-text>
  42. </el-form-item>
  43. </el-col>
  44. </el-row>
  45. </div>
  46. <div class="info">
  47. <div class="info-title">护理项目</div>
  48. <span v-show="dataForm.elderId">
  49. <el-button type="primary" class="left" @click="handleAdd" v-if="!isDetail">
  50. <Icon icon="ep:zoom-in" class="mr-5px" />添加项目
  51. </el-button>
  52. </span>
  53. </div>
  54. <div class="info-wrap">
  55. <el-row>
  56. <el-col
  57. :xs="24"
  58. :sm="24"
  59. :md="24"
  60. :lg="24"
  61. class="mb5"
  62. v-for="(p, i) in dataForm.items"
  63. :key="i"
  64. >
  65. <div class="border item-row">
  66. <div class="item-left">
  67. <el-checkbox
  68. :label="p.nurseItemName"
  69. v-model="p.checked"
  70. @change="(v) => handleChangeCheckBox(!!v, p)"
  71. v-if="!isDetail"
  72. />
  73. <div v-else class="itemName">{{ p.nurseItemName }}</div>
  74. </div>
  75. </div>
  76. </el-col>
  77. </el-row>
  78. </div>
  79. </el-form>
  80. <template #footer>
  81. <el-button @click="handleClosed">取消</el-button>
  82. <el-button v-loading="formLoading" type="primary" @click="submitForm" v-if="!isDetail"
  83. >确定</el-button
  84. >
  85. </template>
  86. </Dialog>
  87. <lifeItem ref="itemRef" category-type-name="生活护理" @success="getItemList" />
  88. </template>
  89. <script setup lang="ts">
  90. import {
  91. createSpecialNursingPlan,
  92. updateSpecialNursingPlan,
  93. getSpecialNursingPlan
  94. } from '@/api/elderly/nursing/special-plan'
  95. import type {
  96. SpecialNursingPlanItemRespVO,
  97. SpecialNursingPlanItemSaveReqVO,
  98. SpecialNursingPlanSaveReqVO
  99. } from '@/api/elderly/nursing/special-plan'
  100. import { getTenantId } from '@/utils/auth'
  101. import { getElderInfoById } from '@/api/elderly/elder/elderly-Info'
  102. import lifeItem from './life-item-dialog.vue'
  103. defineOptions({ name: 'SpecialNursingPlanForm' })
  104. interface PlanItemRow {
  105. /** 明细表主键,编辑时回填 */
  106. id?: number
  107. nurseItemId: number | string
  108. nurseItemName: string
  109. checked?: boolean
  110. }
  111. const { t } = useI18n()
  112. const message = useMessage()
  113. const dialogVisible = ref(false)
  114. const dataForm = ref({
  115. id: '' as number | string | '',
  116. elderId: '' as number | string | '',
  117. elderName: '',
  118. bedName: '',
  119. nurseLevelName: '',
  120. items: [] as PlanItemRow[],
  121. tenantId: undefined as number | undefined
  122. })
  123. const resetSnapshot = reactive({
  124. id: '',
  125. elderId: '',
  126. elderName: '',
  127. bedName: '',
  128. nurseLevelName: '',
  129. items: [] as PlanItemRow[],
  130. tenantId: undefined as number | undefined
  131. })
  132. const dataRule = ref({
  133. elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }]
  134. })
  135. const formLoading = ref(false)
  136. const isDetail = ref(false)
  137. const itemTitle = computed(() => {
  138. return isDetail.value ? '详情' : !dataForm.value.id ? '新增' : '修改'
  139. })
  140. const normalizeItemsFromDetail = (list?: SpecialNursingPlanItemRespVO[]): PlanItemRow[] =>
  141. (list || []).map((row) => ({
  142. id: row.id != null ? Number(row.id) : undefined,
  143. nurseItemId: row.nurseItemId as number | string,
  144. nurseItemName: row.nurseItemName || '',
  145. checked: true
  146. }))
  147. /** 编辑/详情时计划接口不包含床位与护理等级,根据长者详情补全 */
  148. const fillElderBedAndNurseLevel = async (elderId?: number | string) => {
  149. if (elderId == null || elderId === '') return
  150. try {
  151. const elder = (await getElderInfoById(elderId)) as Recordable
  152. if (!elder) return
  153. const bed = elder.bedName ?? elder.bed
  154. const level = elder.nurseLevelName ?? elder.nursingLevelName ?? elder.nursingLevel ?? ''
  155. if (bed !== undefined && bed !== null && bed !== '') {
  156. dataForm.value.bedName = String(bed)
  157. }
  158. if (level !== undefined && level !== null && String(level).length) {
  159. dataForm.value.nurseLevelName = String(level)
  160. }
  161. } catch {
  162. // 静默:仍展示计划中已有或空白
  163. }
  164. }
  165. const open = async (id?: number | string, detail?: boolean, prefetch?: Recordable) => {
  166. dataForm.value = JSON.parse(JSON.stringify(resetSnapshot))
  167. dataForm.value.id = ''
  168. isDetail.value = !!detail
  169. dialogVisible.value = true
  170. if (!id) return
  171. try {
  172. const res = await getSpecialNursingPlan(id)
  173. dataForm.value.id = res.id ?? id
  174. dataForm.value.elderId = res.elderId ?? ''
  175. dataForm.value.elderName = res.elderName ?? ''
  176. dataForm.value.tenantId = res.tenantId
  177. dataForm.value.bedName = res.bedName ?? prefetch?.bedName ?? ''
  178. dataForm.value.nurseLevelName = res.nurseLevelName ?? prefetch?.nurseLevelName ?? ''
  179. await fillElderBedAndNurseLevel(res.elderId)
  180. dataForm.value.items = normalizeItemsFromDetail(res.items || [])
  181. if (!dataForm.value.items.length && prefetch?.elderId) {
  182. message.warning('该计划暂未返回明细,请补充护理项目后保存')
  183. }
  184. } catch {
  185. dialogVisible.value = false
  186. message.error('获取特殊护理计划详情失败')
  187. }
  188. }
  189. defineExpose({ open })
  190. const handleClosed = () => {
  191. dataForm.value = JSON.parse(JSON.stringify(resetSnapshot))
  192. dialogVisible.value = false
  193. }
  194. const itemRef = ref()
  195. const handleAdd = () => {
  196. itemRef.value.open(dataForm.value.items)
  197. }
  198. const getItemList = (val: Recordable[]) => {
  199. val.forEach((item) => {
  200. dataForm.value.items.push({
  201. nurseItemId: item.id,
  202. nurseItemName: item.itemName,
  203. checked: true
  204. })
  205. })
  206. }
  207. const handleElder = (item: Recordable) => {
  208. dataForm.value.elderName = item.elderName
  209. dataForm.value.bedName = item.bedName
  210. dataForm.value.nurseLevelName = item.nurseLevelName
  211. }
  212. const handleChangeCheckBox = (val: boolean, item: PlanItemRow) => {
  213. if (!val) {
  214. const index = dataForm.value.items.findIndex((p) => p.nurseItemId == item.nurseItemId)
  215. if (index > -1) dataForm.value.items.splice(index, 1)
  216. }
  217. }
  218. const formRef = ref()
  219. const emit = defineEmits(['success'])
  220. const buildTenantId = () => Number(getTenantId())
  221. const submitForm = async () => {
  222. if (!formRef.value) return
  223. try {
  224. await formRef.value.validate()
  225. } catch {
  226. return
  227. }
  228. const tenantId = buildTenantId()
  229. const planId = dataForm.value.id ? Number(dataForm.value.id) : undefined
  230. const itemsPayload: SpecialNursingPlanItemSaveReqVO[] = []
  231. ;(dataForm.value.items || []).forEach((row) => {
  232. if (!row.checked && row.checked !== undefined) return
  233. const one: SpecialNursingPlanItemSaveReqVO = {
  234. nurseItemId: Number(row.nurseItemId),
  235. nurseItemName: row.nurseItemName,
  236. tenantId
  237. }
  238. if (planId) {
  239. one.specialNursingPlanId = planId
  240. if (row.id != null) one.id = row.id
  241. }
  242. itemsPayload.push(one)
  243. })
  244. const payload: SpecialNursingPlanSaveReqVO = {
  245. elderId: Number(dataForm.value.elderId),
  246. elderName: dataForm.value.elderName || undefined,
  247. tenantId,
  248. items: itemsPayload
  249. }
  250. if (planId) payload.id = planId
  251. try {
  252. formLoading.value = true
  253. await (planId ? updateSpecialNursingPlan(payload) : createSpecialNursingPlan(payload))
  254. message.success(planId ? t('common.updateSuccess') : t('common.createSuccess'))
  255. handleClosed()
  256. emit('success')
  257. } finally {
  258. formLoading.value = false
  259. }
  260. }
  261. </script>
  262. <style lang="scss" scoped>
  263. .special-nursing-plan-form {
  264. .item-row {
  265. display: flex;
  266. align-items: center;
  267. }
  268. .item-left {
  269. display: flex;
  270. align-items: center;
  271. min-height: 32px;
  272. padding: 1px 11px;
  273. flex: 1;
  274. box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
  275. border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  276. }
  277. .itemName {
  278. line-height: 30px;
  279. }
  280. .info {
  281. position: relative;
  282. .left {
  283. position: absolute;
  284. left: 150px;
  285. top: 8px;
  286. }
  287. }
  288. }
  289. </style>