ConfigureTable.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  1. <template>
  2. <div class="nutrition-table">
  3. <el-table :data="tableData" border>
  4. <el-table-column type="index" label="组号" width="60" align="center" />
  5. <el-table-column label="营养产品" min-width="300">
  6. <template #default="{row}">
  7. <div class="product-group">
  8. <div v-for="(product, index) in row.products" :key="index" class="product-row" style="margin-bottom:5px;">
  9. <el-select v-model="product.nutritionProductId" placeholder="请选择" @change="changeProductSelect">
  10. <el-option v-for="pn in productNutritionList" :key="pn.id" :label="pn.productName"
  11. :value="pn.id+'_'+row.sn+'_'+index"></el-option>
  12. </el-select>
  13. <div class="action-buttons">
  14. <el-button class="icon-btn" @click="addProduct(row)">
  15. <el-icon>
  16. <Plus />
  17. </el-icon>
  18. </el-button>
  19. <el-button class="icon-btn" @click="removeProduct(row, index)">
  20. <el-icon>
  21. <Delete />
  22. </el-icon>
  23. </el-button>
  24. </div>
  25. </div>
  26. </div>
  27. </template>
  28. </el-table-column>
  29. <el-table-column label="用量/次" width="150" align="center">
  30. <template #default="{row}">
  31. <div class="dosage-group">
  32. <div v-for="(product, index) in row.products" :key="index" class="dosage-input">
  33. <el-input v-model="product.dosePerTime" placeholder="请输入" class="input-center"
  34. @input="dosePerTimeInput(product.dosePerTime,row.sn+'_'+index)"/>
  35. <span class="unit">g</span>
  36. </div>
  37. </div>
  38. </template>
  39. </el-table-column>
  40. <el-table-column label="餐次时间" width="500">
  41. <template #default="{row}">
  42. <div class="time-slots">
  43. <div class="time-row">
  44. <div class="time-group">
  45. <el-checkbox v-model="row.timeSlots[0].checked" @change="changeTimeSelection(row.timeSlots[0].checked,row.sn)"/>
  46. <el-time-select v-model="row.timeSlots[0].time" start="00:00" step="00:30" end="23:30"
  47. placeholder="选择时间" :disabled="!row.timeSlots[0].checked"
  48. />
  49. </div>
  50. <div class="time-group">
  51. <el-checkbox v-model="row.timeSlots[1].checked" @change="changeTimeSelection(row.timeSlots[1].checked,row.sn)"/>
  52. <el-time-select v-model="row.timeSlots[1].time" start="00:00" step="00:30" end="23:30"
  53. placeholder="选择时间" :disabled="!row.timeSlots[1].checked"
  54. />
  55. </div>
  56. <div class="time-group">
  57. <el-checkbox v-model="row.timeSlots[2].checked" @change="changeTimeSelection(row.timeSlots[2].checked,row.sn)"/>
  58. <el-time-select v-model="row.timeSlots[2].time" start="00:00" step="00:30" end="23:30"
  59. placeholder="选择时间" :disabled="!row.timeSlots[2].checked"
  60. />
  61. </div>
  62. </div>
  63. <div class="time-row">
  64. <div class="time-group">
  65. <el-checkbox v-model="row.timeSlots[3].checked" @change="changeTimeSelection(row.timeSlots[3].checked,row.sn)"/>
  66. <el-time-select v-model="row.timeSlots[3].time" start="00:00" step="00:30" end="23:30"
  67. placeholder="选择时间" :disabled="!row.timeSlots[3].checked"
  68. />
  69. </div>
  70. <div class="time-group">
  71. <el-checkbox v-model="row.timeSlots[4].checked" @change="changeTimeSelection(row.timeSlots[4].checked,row.sn)"/>
  72. <el-time-select v-model="row.timeSlots[4].time" start="00:00" step="00:30" end="23:30"
  73. placeholder="选择时间" :disabled="!row.timeSlots[4].checked"
  74. />
  75. </div>
  76. <div class="time-group">
  77. <el-checkbox v-model="row.timeSlots[5].checked" @change="changeTimeSelection(row.timeSlots[5].checked,row.sn)"/>
  78. <el-time-select v-model="row.timeSlots[5].time" start="00:00" step="00:30" end="23:30"
  79. placeholder="选择时间" :disabled="!row.timeSlots[5].checked"
  80. />
  81. </div>
  82. </div>
  83. </div>
  84. </template>
  85. </el-table-column>
  86. <el-table-column label="频次" width="120" align="center">
  87. <template #default="{row}">
  88. <span>一天{{row.frequency}}次</span>
  89. </template>
  90. </el-table-column>
  91. <el-table-column label="首日" width="150" align="center">
  92. <template #default="{row}">
  93. <el-input v-model="row.firstDay" @input="firstDayInput(row.firstDay,row.sn)">
  94. <template #append>次</template>
  95. </el-input>
  96. </template>
  97. </el-table-column>
  98. <el-table-column label="用量/日" width="100" align="center">
  99. <template #default="{row}">
  100. <div class="daily-dosage" v-for="(product, index) in row.products" :key="index">
  101. <span>{{product.dosePerDay }}</span>
  102. </div>
  103. </template>
  104. </el-table-column>
  105. <el-table-column label="使用天数" width="200" align="center">
  106. <template #default="{row}">
  107. <div class="daily-size">
  108. <el-input-number v-model="row.usageDays" :min="1" @change="changeUsageDays(row.usageDays,row.sn)"/>
  109. </div>
  110. </template>
  111. </el-table-column>
  112. <el-table-column label="用量/总" width="100" align="center">
  113. <template #default="{row}">
  114. <div class="daily-dosage" v-for="(product, index) in row.products" :key="index">
  115. <span>{{product.totalDose }}</span>
  116. </div>
  117. </template>
  118. </el-table-column>
  119. <el-table-column label="规格" width="100" align="center">
  120. <template #default="{row}">
  121. <div class="daily-dosage" v-for="(product, index) in row.products" :key="index">
  122. <span>{{product.specification }}</span>
  123. </div>
  124. </template>
  125. </el-table-column>
  126. <el-table-column label="用法" width="150" align="center">
  127. <template #default="{row}">
  128. <el-select v-model="row.usage" placeholder="请选择" >
  129. <el-option v-for="dict in default_usage" :key="dict.value" :label="dict.label"
  130. :value="dict.value"></el-option>
  131. </el-select>
  132. </template>
  133. </el-table-column>
  134. <el-table-column label="制剂液量/次" width="170" align="center">
  135. <template #default="{row}">
  136. <el-input v-model="row.preparationVolumePerTime">
  137. <template #append>ml</template>
  138. </el-input>
  139. </template>
  140. </el-table-column>
  141. <el-table-column label="制剂浓度/次" width="100" align="center">
  142. <template #default="{row}">
  143. <div class="daily-dosage">
  144. <span>{{ row.preparationConcentrationPerTime }}</span>
  145. <span class="unit">%</span>
  146. </div>
  147. </template>
  148. </el-table-column>
  149. <el-table-column label="能量密度/次" width="200" align="center">
  150. <template #default="{row}">
  151. <el-input v-model="row.energyDensityPerTime">
  152. <template #append>kcal/ml</template>
  153. </el-input>
  154. </template>
  155. </el-table-column>
  156. <el-table-column label="处方备注" width="200" align="center">
  157. <template #default="{row}">
  158. <template v-for="(product, index) in row.products" >
  159. <el-input v-model="product.prescriptionRemark" :maxlength="100" :show-word-limit="true" />
  160. </template>
  161. </template>
  162. </el-table-column>
  163. <el-table-column label="每日热量" width="100" align="center">
  164. <template #default="{row}">
  165. <div class="daily-dosage" v-for="(product, index) in row.products" :key="index">
  166. <span>{{product.dailyCalories }}</span>
  167. </div>
  168. </template>
  169. </el-table-column>
  170. <el-table-column label="金额" width="100" align="center">
  171. <template #default="{row}">
  172. <div class="daily-dosage" v-for="(product, index) in row.products" :key="index">
  173. <span>{{product.amount }}</span>
  174. </div>
  175. </template>
  176. </el-table-column>
  177. <el-table-column label="操作" width="160" align="center" fixed="right">
  178. <template #default="{$index}">
  179. <div class="operation-cell">
  180. <el-button type="primary" link @click="copyRow($index)">复制餐次</el-button>
  181. <el-button type="danger" link @click="deleteRow($index)">删除</el-button>
  182. </div>
  183. </template>
  184. </el-table-column>
  185. </el-table>
  186. <div class="table-footer">
  187. <el-button type="primary" class="add-prescription-btn" @click="addNewRow">
  188. <el-icon>
  189. <CirclePlus />
  190. </el-icon>
  191. 开具多处方
  192. </el-button>
  193. </div>
  194. </div>
  195. </template>
  196. <script setup lang="ts">
  197. import {ref} from 'vue';
  198. import {Plus, Delete, CirclePlus} from '@element-plus/icons-vue';
  199. import { listEnteralNutrition, getEnteralNutrition, delEnteralNutrition, addEnteralNutrition, updateEnteralNutrition } from '@/api/patients/nutrition';
  200. import { EnteralNutritionVO, EnteralNutritionQuery, EnteralNutritionForm } from '@/api/patients/nutrition/types';
  201. import { listNutrition, listAllNutrition } from '@/api/warehouse/productNutrition/index';
  202. import { NutritionVO, NutritionQuery, NutritionForm } from '@/api/warehouse/productNutrition/types';
  203. import { it } from 'node:test';
  204. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  205. const { default_usage } = toRefs < any > (proxy ?.useDict('default_usage'));
  206. let productNutritionList=ref<NutritionVO[]>([]);
  207. const getProductNutritionList = async () => {
  208. const res = await listAllNutrition();
  209. productNutritionList.value = res.rows;
  210. }
  211. interface TimeSlot {
  212. checked: boolean;
  213. time: string;
  214. }
  215. interface TableRow extends EnteralNutritionForm {
  216. products: EnteralNutritionForm[];
  217. timeSlots: TimeSlot[];
  218. sn: number;
  219. }
  220. // 创建默认行数据的函数
  221. const createDefaultRow = (): TableRow => ({
  222. products: [{specification:'g',totalDose:'g',dosePerDay:'g'}],
  223. timeSlots: [
  224. {checked: true, time: '08:00'},
  225. {checked: false, time: '08:30'},
  226. {checked: true, time: '12:00'},
  227. {checked: false, time: '12:30'},
  228. {checked: true, time: '18:00'},
  229. {checked: false, time: '18:30'}
  230. ],
  231. usageDays:1,
  232. frequency:3,
  233. firstDay:1,
  234. sn:0,
  235. });
  236. const emit = defineEmits(['change']);
  237. // 示例数据
  238. const tableData = ref<TableRow[]>([createDefaultRow()]);
  239. // 新增行
  240. const addNewRow = () => {
  241. let row: TableRow = createDefaultRow();
  242. row.sn = tableData.value.length ;
  243. tableData.value.push(row);
  244. emit('change',JSON.stringify(tableData.value))
  245. };
  246. const changeProductSelect = async (id: string) => {
  247. let arr=id.split('_');
  248. for (let item of productNutritionList.value) {
  249. if (item.id.toString() != arr[0]) {
  250. continue
  251. }
  252. let row = tableData.value[Number(arr[1])];
  253. let pt = row.products[Number(arr[2])];
  254. pt.specification=item.productSpec;
  255. pt.dosePerTime=null;
  256. pt.dailyCalories=null;
  257. pt.amount=null;
  258. row.usage=item.defaultUsage;
  259. pt.totalDose='0'+item.productSpecUnit;
  260. pt.dosePerDay='0'+item.productSpecUnit;
  261. break;
  262. }
  263. };
  264. // 新增产品输入框
  265. const addProduct = (row: TableRow) => {
  266. row.products.push({});
  267. };
  268. // 删除产品输入框
  269. const removeProduct = (row: TableRow, index: number) => {
  270. // 如果删除后只剩一个输入框,删除整行
  271. if (row.products.length <= 1) {
  272. const rowIndex = tableData.value.findIndex((item) => item === row);
  273. if (rowIndex !== -1) {
  274. tableData.value.splice(rowIndex, 1);
  275. }
  276. } else {
  277. // 否则只删除当前输入框
  278. row.products.splice(index, 1);
  279. }
  280. };
  281. // 复制行
  282. const copyRow = (index: number) => {
  283. const newRow = JSON.parse(JSON.stringify(tableData.value[index]));
  284. tableData.value.splice(index + 1, 0, newRow);
  285. emit('change',JSON.stringify(tableData.value))
  286. };
  287. // 删除行
  288. const deleteRow = (index: number) => {
  289. if (tableData.value.length === 1) return;
  290. tableData.value.splice(index, 1);
  291. emit('change',JSON.stringify(tableData.value))
  292. };
  293. const changeUsageDays = (value: string, sn:string) => {
  294. changeTimeSelection(value,sn);
  295. emit('change',JSON.stringify(tableData.value))
  296. }
  297. const firstDayInput = (value: string, sn:string) => {
  298. changeTimeSelection(value,sn);
  299. emit('change',JSON.stringify(tableData.value))
  300. }
  301. const changeTimeSelection = (value: string, sn:string) => {
  302. let row = tableData.value[sn];
  303. let frequency=0;
  304. let oldFrequency=row.frequency;
  305. row.timeSlots.forEach(item=>{
  306. if(item.checked){
  307. frequency++;
  308. }
  309. })
  310. row.frequency=frequency;
  311. if(oldFrequency!==frequency){
  312. emit('change',JSON.stringify(tableData.value))
  313. }
  314. row.products.forEach(pt => {
  315. if (!pt.dosePerTime || pt.dosePerTime.trim().length == 0) {
  316. return;
  317. }
  318. let arr=pt.nutritionProductId.split('_');
  319. for (let item of productNutritionList.value) {
  320. if (item.id.toString() != arr[0]) {
  321. continue
  322. }
  323. let val=item.calorie*row.frequency*Number(pt.dosePerTime)/100.0
  324. pt.dailyCalories=val.toFixed(4);
  325. val=(row.frequency*(row.usageDays-1)+row.firstDay)*item.configSalePrice*Number(pt.dosePerTime)
  326. pt.amount=Math.round(val*100)/100;
  327. val=(row.frequency*(row.usageDays-1)+row.firstDay)*Number(pt.dosePerTime)
  328. pt.totalDose=val.toFixed(4)+item.productSpecUnit;
  329. val=row.frequency*Number(pt.dosePerTime)
  330. pt.dosePerDay=val.toFixed(4)+item.productSpecUnit;
  331. break;
  332. }
  333. });
  334. };
  335. const dosePerTimeInput = (value: string, str:string) => {
  336. let arr=str.split('_');
  337. let row = tableData.value[Number(arr[0])];
  338. let pt = row.products[Number(arr[1])];
  339. if(!pt.dosePerTime||pt.dosePerTime.trim().length==0){
  340. pt.dailyCalories=null;
  341. pt.amount=null;
  342. return;
  343. }
  344. arr=pt.nutritionProductId.split('_');
  345. for (let item of productNutritionList.value) {
  346. if (item.id.toString() != arr[0]) {
  347. continue
  348. }
  349. let val=item.calorie*row.frequency*Number(pt.dosePerTime)/100.0
  350. pt.dailyCalories=val.toFixed(4);
  351. val=(row.frequency*(row.usageDays-1)+row.firstDay)*item.configSalePrice*Number(pt.dosePerTime)
  352. pt.amount=Math.round(val*100)/100;
  353. val=(row.frequency*(row.usageDays-1)+row.firstDay)*Number(pt.dosePerTime)
  354. pt.totalDose=val.toFixed(4)+item.productSpecUnit;
  355. val=row.frequency*Number(pt.dosePerTime)
  356. pt.dosePerDay=val.toFixed(4)+item.productSpecUnit;
  357. break;
  358. }
  359. };
  360. onMounted(() => {
  361. getProductNutritionList();
  362. });
  363. </script>
  364. <style lang="scss" scoped>
  365. .nutrition-table {
  366. :deep(.el-table) {
  367. // 表头样式
  368. .el-table__header {
  369. th {
  370. background-color: #f5f7fa;
  371. color: #606266;
  372. font-weight: 500;
  373. padding: 8px 0;
  374. height: 40px;
  375. }
  376. }
  377. // 表格行样式
  378. .el-table__row {
  379. td {
  380. padding: 4px 8px;
  381. }
  382. }
  383. }
  384. // 产品输入框组样式
  385. .product-group {
  386. display: flex;
  387. flex-direction: column;
  388. gap: 8px;
  389. .product-row {
  390. display: flex;
  391. align-items: center;
  392. gap: 8px;
  393. .el-input {
  394. flex: 1;
  395. }
  396. .action-buttons {
  397. display: flex;
  398. gap: 1px;
  399. .icon-btn {
  400. padding: 6px;
  401. height: 32px;
  402. border: 1px solid #dcdfe6;
  403. &:hover {
  404. border-color: var(--el-color-primary);
  405. color: var(--el-color-primary);
  406. }
  407. .el-icon {
  408. font-size: 14px;
  409. }
  410. }
  411. }
  412. }
  413. }
  414. // 用量输入框组样式
  415. .dosage-group {
  416. display: flex;
  417. flex-direction: column;
  418. gap: 8px;
  419. .dosage-input {
  420. display: flex;
  421. align-items: center;
  422. justify-content: center;
  423. gap: 4px;
  424. .input-center {
  425. width: 100px;
  426. :deep(.el-input__inner) {
  427. text-align: center;
  428. }
  429. }
  430. .unit {
  431. color: #909399;
  432. font-size: 14px;
  433. flex-shrink: 0;
  434. }
  435. }
  436. }
  437. // 餐次时间样式
  438. .time-slots {
  439. display: flex;
  440. flex-direction: column;
  441. gap: 8px;
  442. .time-row {
  443. display: flex;
  444. gap: 8px;
  445. .time-group {
  446. display: flex;
  447. align-items: center;
  448. gap: 4px;
  449. :deep(.el-checkbox) {
  450. margin-right: 0;
  451. .el-checkbox__label {
  452. display: none;
  453. }
  454. }
  455. :deep(.el-time-select) {
  456. .el-input {
  457. width: 120px;
  458. }
  459. .el-input__wrapper {
  460. padding: 0 8px;
  461. border-radius: 2px;
  462. box-shadow: 0 0 0 1px #dcdfe6 inset;
  463. &:hover:not(.is-disabled) {
  464. box-shadow: 0 0 0 1px #c0c4cc inset;
  465. }
  466. &.is-disabled {
  467. background-color: #f5f7fa;
  468. box-shadow: 0 0 0 1px #e4e7ed inset;
  469. .el-input__inner {
  470. color: #909399;
  471. -webkit-text-fill-color: #909399;
  472. }
  473. }
  474. }
  475. .el-input__inner {
  476. height: 32px;
  477. text-align: center;
  478. font-size: 14px;
  479. color: #606266;
  480. }
  481. }
  482. }
  483. }
  484. }
  485. .daily-dosage,
  486. .days {
  487. display: flex;
  488. align-items: center;
  489. justify-content: center;
  490. gap: 4px;
  491. .unit {
  492. color: #909399;
  493. font-size: 14px;
  494. }
  495. }
  496. .operation-cell {
  497. display: flex;
  498. justify-content: center;
  499. gap: 12px;
  500. .el-button {
  501. padding: 4px 8px;
  502. }
  503. }
  504. .table-footer {
  505. height: 50px;
  506. border: 1px solid var(--el-table-border-color);
  507. border-top: none;
  508. display: flex;
  509. align-items: center;
  510. justify-content: center;
  511. background-color: #f5f7fa;
  512. .add-prescription-btn {
  513. display: flex;
  514. align-items: center;
  515. gap: 4px;
  516. height: 32px;
  517. padding: 0 16px;
  518. font-size: 14px;
  519. background-color: var(--el-color-primary-light-8);
  520. color: var(--el-color-primary);
  521. border: none;
  522. &:hover {
  523. background-color: var(--el-color-primary-light-9);
  524. }
  525. .el-icon {
  526. font-size: 16px;
  527. }
  528. }
  529. }
  530. .stop-date {
  531. display: flex;
  532. align-items: center;
  533. gap: 8px;
  534. margin-bottom: 16px;
  535. .required {
  536. color: #f56c6c;
  537. font-size: 14px;
  538. }
  539. .label {
  540. color: #606266;
  541. font-size: 14px;
  542. }
  543. .days {
  544. color: #f56c6c;
  545. font-size: 14px;
  546. }
  547. }
  548. }
  549. :deep(.daily-size div) {
  550. width: 100px;
  551. }
  552. </style>