Form.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. <template>
  2. <Dialog
  3. v-model="dialogVisible"
  4. :title="itemTitle"
  5. width="70%"
  6. class="form-tag-dialog life-care-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. <span v-show="dataForm.id">
  29. {{ dataForm.bedName }}&nbsp;&nbsp;&nbsp;&nbsp;{{ dataForm.nurseLevelName }}
  30. </span>
  31. </el-form-item>
  32. </el-col>
  33. <el-col :span="12" :xs="24">
  34. <el-form-item label="床位号" prop="bedName">
  35. <el-input v-if="!isDetail" v-model="dataForm.bedName" disabled />
  36. <el-text v-else>{{ dataForm.bedName }}</el-text>
  37. </el-form-item>
  38. </el-col>
  39. </el-row>
  40. <el-row :gutter="20">
  41. <el-col :span="12" :xs="24">
  42. <el-form-item label="护理等级" prop="nurseLevelName">
  43. <el-input v-if="!isDetail" v-model="dataForm.nurseLevelName" disabled />
  44. <el-text v-else>{{ dataForm.nurseLevelName }}</el-text>
  45. </el-form-item>
  46. </el-col>
  47. <el-col :span="12" :xs="24">
  48. <el-form-item label="生效日期" prop="effectiveDate">
  49. <TgDatePicker v-model="dataForm.effectiveDate" />
  50. </el-form-item>
  51. </el-col>
  52. </el-row>
  53. </div>
  54. <div class="info">
  55. <div class="info-title">护理项目</div>
  56. <span v-show="dataForm.elderId">
  57. <el-button type="primary" class="left" @click="handleAdd" v-if="!isDetail">
  58. <Icon icon="ep:zoom-in" class="mr-5px" />添加项目
  59. </el-button>
  60. </span>
  61. </div>
  62. <div class="info-wrap">
  63. <el-row>
  64. <el-col
  65. :xs="24"
  66. :sm="24"
  67. :md="24"
  68. :lg="24"
  69. class="mb5"
  70. v-for="(p, i) in dataForm.items"
  71. :key="i"
  72. >
  73. <el-row :gutter="20">
  74. <el-col :span="16">
  75. <div class="item-left">
  76. <el-checkbox
  77. :label="`${p.nurseItemName}(${p.frequencyCategory})`"
  78. v-model="p.checked"
  79. @change="(arg) => handleChangeCheckBox(arg, p)"
  80. v-if="!isDetail"
  81. />
  82. <div v-else class="itemName"
  83. >{{ p.nurseItemName }}({{ p.frequencyCategory }})</div
  84. >
  85. </div>
  86. </el-col>
  87. <el-col :span="8">
  88. <div class="item-time" v-if="!isDetail">
  89. <span class="time-label required" v-if="p.frequencyCategoryType == 1"
  90. >护理开始时间</span
  91. >
  92. <el-time-picker
  93. v-if="p.frequencyCategoryType == 1"
  94. v-model="p.beginTime"
  95. value-format="HH:mm:ss"
  96. placeholder="请选择开始时间"
  97. />
  98. </div>
  99. <div v-else class="item-time detail-time">
  100. <span class="time-label" v-if="p.frequencyCategoryType == 1">护理开始时间</span>
  101. <span v-if="p.frequencyCategoryType == 1">{{
  102. formatBeginTimeDetail(p.beginTime)
  103. }}</span>
  104. </div>
  105. </el-col>
  106. </el-row>
  107. </el-col>
  108. </el-row>
  109. </div>
  110. </el-form>
  111. <template #footer>
  112. <el-button @click="handleClosed">取消</el-button>
  113. <el-button v-loading="formLoading" type="primary" @click="submitForm" v-if="!isDetail"
  114. >确定</el-button
  115. >
  116. </template>
  117. </Dialog>
  118. <lifeItem ref="itemRef" @success="getItemList" />
  119. </template>
  120. <script setup lang="ts">
  121. import {
  122. createNursingPlan,
  123. updateNursingPlan,
  124. getNursingPlanById,
  125. findNurseItemListByName
  126. } from '@/api/elderly/nursing'
  127. import lifeItem from './life-item-dialog.vue'
  128. import { planType } from '../types'
  129. defineOptions({ name: 'LifeCarePlanForm' })
  130. const { t } = useI18n() // 国际化
  131. const message = useMessage() // 消息弹窗
  132. const dialogVisible = ref(false)
  133. const state = reactive<planType>({
  134. dataForm: {
  135. id: '',
  136. elderId: '',
  137. elderName: '',
  138. bedName: '',
  139. nurseLevelName: '',
  140. effectiveDate: '',
  141. items: [],
  142. extraItems: []
  143. },
  144. dataRule: {
  145. elderId: [{ required: true, message: '长者姓名不能为空', trigger: 'blur' }],
  146. effectiveDate: [{ required: true, message: '生效日期不能为空', trigger: 'blur' }]
  147. }
  148. })
  149. const { dataForm, dataRule } = toRefs(state)
  150. const resetFormField = reactive({ ...dataForm.value })
  151. const route = useRoute()
  152. const formLoading = ref(false)
  153. const isDetail = ref(false)
  154. // 弹窗标题
  155. const itemTitle = computed(() => {
  156. return isDetail.value ? '详情' : !dataForm.value.id ? '新增' : '修改'
  157. })
  158. /** 详情接口可能返回时间戳或整段日期时间,转为时间选择器用的 HH:mm:ss */
  159. const toTimePickerValue = (beginTime: unknown): string => {
  160. if (beginTime == null || beginTime === '') return ''
  161. if (typeof beginTime === 'number') {
  162. const d = new Date(beginTime)
  163. if (Number.isNaN(d.getTime())) return ''
  164. const pad = (n: number) => String(n).padStart(2, '0')
  165. return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
  166. }
  167. if (typeof beginTime === 'string') {
  168. if (/^\d+$/.test(beginTime)) return toTimePickerValue(Number(beginTime))
  169. if (beginTime.includes(' ')) return beginTime.split(' ')[1] || ''
  170. }
  171. return String(beginTime)
  172. }
  173. /** 详情展示:时间戳或日期时间转为可读时间 */
  174. const formatBeginTimeDetail = (beginTime: unknown): string => {
  175. if (beginTime == null || beginTime === '') return '-'
  176. if (typeof beginTime === 'number' || (typeof beginTime === 'string' && /^\d+$/.test(beginTime))) {
  177. const d = new Date(Number(beginTime))
  178. if (Number.isNaN(d.getTime())) return '-'
  179. const pad = (n: number) => String(n).padStart(2, '0')
  180. return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
  181. }
  182. if (typeof beginTime === 'string' && beginTime.includes(' ')) {
  183. return beginTime.split(' ')[1] || '-'
  184. }
  185. return String(beginTime)
  186. }
  187. /** 打开弹窗 */
  188. const open = async (id, detail) => {
  189. dataForm.value.id = ''
  190. dialogVisible.value = true
  191. isDetail.value = detail
  192. if (id) {
  193. const res = await getNursingPlanById(id)
  194. // 默认勾选护理项目
  195. res.items.map((item) => {
  196. item.checked = true
  197. })
  198. res.extraItems.map((item) => {
  199. item.checked = true
  200. })
  201. // 匹配额外项目价格
  202. dataForm.value = res
  203. await formatExtraItem()
  204. dataForm.value.items = res.items?.length > 0 ? res.items : res.extraItems
  205. dataForm.value.items?.forEach((item) => {
  206. if (item.frequencyCategoryType === 1 && item.beginTime != null && item.beginTime !== '') {
  207. item.beginTime = toTimePickerValue(item.beginTime)
  208. }
  209. })
  210. }
  211. }
  212. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  213. const formatExtraItem = async () => {
  214. if (dataForm.value.extraItems.length) {
  215. const res = await findNurseItemListByName({
  216. categoryTypeName: '生活护理',
  217. nurseItemName: ''
  218. })
  219. dataForm.value.extraItems.map((e) => {
  220. res.map((r) => {
  221. if (e.nurseItemId == r.id) {
  222. e.price = r.price
  223. }
  224. })
  225. })
  226. }
  227. }
  228. const handleClosed = () => {
  229. dataForm.value = { ...resetFormField }
  230. dialogVisible.value = false
  231. }
  232. const itemRef = ref()
  233. const handleAdd = () => {
  234. itemRef.value.open(dataForm.value.items)
  235. }
  236. const getItemList = (val) => {
  237. console.log('val', val)
  238. // 添加的内容放到护理项目中
  239. val.map((item) => {
  240. dataForm.value.items.push({
  241. nurseItemId: item.id,
  242. nurseItemName: item.itemName,
  243. frequencyType: item.frequencyType,
  244. frequency: item.frequency,
  245. frequencyCategory: item.frequencyCategory,
  246. frequencyCategoryType: item.frequencyCategoryType,
  247. checked: true,
  248. price: item.price,
  249. isExtra: item.type == 1 ? 1 : 0,
  250. beginTime: ''
  251. })
  252. })
  253. }
  254. // 选择长者
  255. const handleElder = async (item) => {
  256. dataForm.value.elderName = item.elderName
  257. dataForm.value.bedName = item.bedName
  258. dataForm.value.nurseLevelName = item.nurseLevelName
  259. }
  260. const handleChangeCheckBox = (val, item) => {
  261. if (!val) {
  262. const index = dataForm.value.items.findIndex((p) => item.nurseItemId == p.nurseItemId)
  263. dataForm.value.items.splice(index, 1)
  264. }
  265. }
  266. const formRef = ref()
  267. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  268. const submitForm = async () => {
  269. // 校验表单
  270. if (!formRef.value) return
  271. const valid = await formRef.value.validate()
  272. if (!valid) return
  273. const missingTime = dataForm.value.items.find(
  274. (item) => item.checked && !item.beginTime && item.frequencyCategoryType == 1
  275. )
  276. if (missingTime) {
  277. message.warning(`请先选择【${missingTime.nurseItemName}】的护理开始时间`)
  278. return
  279. }
  280. try {
  281. formLoading.value = true
  282. let params = JSON.parse(JSON.stringify(dataForm.value))
  283. ;(params.type = route.path.indexOf('medical-care-plan') > -1 ? 2 : 1), // 2:医疗护理 1 生活护理,
  284. params.items.forEach((item) => {
  285. if (item.frequencyCategoryType == 1 && item.beginTime) {
  286. item.beginTime = new Date(`${params.effectiveDate} ${item.beginTime}`).getTime()
  287. }
  288. })
  289. const res = params.id ? await updateNursingPlan(params) : await createNursingPlan(params)
  290. if (res) {
  291. message.success(t('common.updateSuccess'))
  292. handleClosed()
  293. // 发送操作成功的事件
  294. emit('success')
  295. }
  296. } finally {
  297. formLoading.value = false
  298. }
  299. }
  300. </script>
  301. <style lang="scss" scoped>
  302. .life-care-plan-form {
  303. .sInput {
  304. border: none;
  305. border-bottom: 1px solid #999;
  306. outline: none;
  307. background-color: transparent;
  308. }
  309. .itemName {
  310. line-height: 30px;
  311. }
  312. .item-left {
  313. display: flex;
  314. align-items: center;
  315. min-height: 32px;
  316. padding: 1px 11px;
  317. flex: 1;
  318. box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
  319. border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  320. }
  321. .item-time {
  322. display: flex;
  323. align-items: center;
  324. gap: 8px;
  325. }
  326. .time-label {
  327. white-space: nowrap;
  328. color: var(--el-text-color-secondary);
  329. }
  330. .detail-time {
  331. color: var(--el-text-color-regular);
  332. }
  333. .border-warning {
  334. border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  335. box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-color-warning)) inset;
  336. }
  337. .warning {
  338. background-color: var(--el-color-warning-light-9);
  339. border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
  340. box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-color-warning)) inset;
  341. }
  342. .info {
  343. position: relative;
  344. .left {
  345. position: absolute;
  346. left: 150px;
  347. top: 8px;
  348. }
  349. .right {
  350. position: absolute;
  351. right: 10px;
  352. top: 10px;
  353. }
  354. .el-dropdown-link {
  355. cursor: pointer;
  356. color: var(--el-color-primary);
  357. display: flex;
  358. align-items: center;
  359. }
  360. }
  361. }
  362. </style>
  363. <style lang="scss">
  364. .life-care-plan-form {
  365. .extra {
  366. .el-select__wrapper {
  367. box-shadow: 0 0 0 1px var(--el-color-warning) inset;
  368. background-color: var(--el-color-warning-light-9);
  369. }
  370. .el-input-number {
  371. .el-input__wrapper {
  372. box-shadow: 0 0 0 1px var(--el-color-warning) inset;
  373. }
  374. }
  375. }
  376. }
  377. </style>