index.vue 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <template>
  2. <div class="app-container">
  3. <div class="search-wrap">
  4. <van-search v-model="searchKey" placeholder="搜索菌毒种名称" @search="onSearch" />
  5. </div>
  6. <van-tabs v-model:active="activeTab" @change="onTabChange">
  7. <van-tab title="入库记录" name="inbound"></van-tab>
  8. <van-tab title="领用记录" name="usage"></van-tab>
  9. </van-tabs>
  10. <div class="list-container">
  11. <van-list v-model:loading="state.loading" :finished="state.finished" finished-text="没有更多了" @load="onLoad">
  12. <van-cell v-for="item in state.list" :key="item.id" @click="onDetail(item)">
  13. <template #default>
  14. <!-- 入库记录卡片 -->
  15. <div v-if="activeTab === 'inbound'" class="card-item">
  16. <header class="flex justify-between">
  17. <strong class="title">{{ item.strainName }}</strong>
  18. <van-tag type="primary" size="medium">{{ item.quantity }}</van-tag>
  19. </header>
  20. <p class="info-row"><span>保存温度</span><span class="val">{{ item.preservationTemp }}</span></p>
  21. <p class="info-row"><span>存放地点</span><span class="val">{{ item.storageLocation }}</span></p>
  22. <p class="info-row"><span>入库日期</span><span class="val">{{ item.inboundDate ? formatDate(new Date(item.inboundDate), 'YYYY-mm-dd') : '-' }}</span></p>
  23. <p class="info-row"><span>入库人</span><span class="val">{{ item.inboundPerson }}</span></p>
  24. <div class="flex mt10 btns">
  25. <van-button v-auth="'instr_strain_inbound_edit'" size="small" type="primary" @click.stop="onEditInbound(item)">修改</van-button>
  26. <van-button v-auth="'instr_strain_inbound_del'" size="small" type="danger" @click.stop="onDelInbound(item)">删除</van-button>
  27. </div>
  28. </div>
  29. <!-- 领用记录卡片 -->
  30. <div v-else class="card-item">
  31. <header class="flex justify-between">
  32. <strong class="title">{{ item.strainName }}</strong>
  33. <van-tag type="success" size="medium">{{ item.usageQuantity }}</van-tag>
  34. </header>
  35. <p class="info-row"><span>领用日期</span><span class="val">{{ item.usageDate ? formatDate(new Date(item.usageDate), 'YYYY-mm-dd') : '-' }}</span></p>
  36. <p class="info-row"><span>领用人</span><span class="val">{{ item.usagePerson }}</span></p>
  37. <p class="info-row" v-if="item.purpose"><span>用途</span><span class="val">{{ item.purpose }}</span></p>
  38. <p class="info-row"><span>剩余量</span><span class="val">{{ item.remainingQuantity || '-' }}</span></p>
  39. <div class="flex mt10 btns">
  40. <van-button v-auth="'instr_strain_usage_del'" size="small" type="danger" @click.stop="onDelUsage(item)">删除</van-button>
  41. </div>
  42. </div>
  43. </template>
  44. </van-cell>
  45. </van-list>
  46. </div>
  47. <van-floating-bubble v-if="hasAddPerm" icon="plus" @click="onAdd" axis="y" />
  48. </div>
  49. </template>
  50. <script lang="ts" setup>
  51. import to from 'await-to-js'
  52. import { formatDate } from '/@/utils/formatTime'
  53. import { onMounted, reactive, ref, computed } from 'vue'
  54. import { useRouter } from 'vue-router'
  55. import { useStrainApi } from '/@/api/strain'
  56. import { useUserInfo } from '/@/stores/userInfo'
  57. import { showConfirmDialog, showNotify } from 'vant'
  58. const router = useRouter()
  59. const strainApi = useStrainApi()
  60. const userStore = useUserInfo()
  61. const activeTab = ref('inbound')
  62. const searchKey = ref('')
  63. const hasAddPerm = computed(() => {
  64. const key = activeTab.value === 'inbound' ? 'instr_strain_inbound_add' : 'instr_strain_usage_add'
  65. return userStore.userInfos.authBtnList?.includes(key)
  66. })
  67. const state = reactive({
  68. loading: true,
  69. finished: false,
  70. list: [] as any[],
  71. queryForm: {
  72. strainName: '',
  73. pageNum: 1,
  74. pageSize: 10
  75. }
  76. })
  77. const resetList = () => {
  78. state.queryForm.pageNum = 1
  79. state.list = []
  80. state.finished = false
  81. state.loading = true
  82. }
  83. const onLoad = async () => {
  84. const query = { ...state.queryForm }
  85. const api = activeTab.value === 'inbound' ? strainApi.getInboundList : strainApi.getUsageList
  86. const [err, res]: any = await to(api(query))
  87. if (err) {
  88. state.loading = false
  89. return
  90. }
  91. const list = res?.data?.list|| []
  92. for (const item of list) {
  93. state.list.push(item)
  94. }
  95. state.queryForm.pageNum++
  96. state.loading = false
  97. if (list.length < state.queryForm.pageSize) {
  98. state.finished = true
  99. }
  100. }
  101. const onSearch = () => {
  102. state.queryForm.strainName = searchKey.value
  103. resetList()
  104. onLoad()
  105. }
  106. const onTabChange = () => {
  107. resetList()
  108. onLoad()
  109. }
  110. const onAdd = () => {
  111. if (activeTab.value === 'inbound') {
  112. router.push('/strain/inbound/add')
  113. } else {
  114. router.push('/strain/usage/add')
  115. }
  116. }
  117. const onDetail = (item: any) => {
  118. if (activeTab.value === 'inbound') {
  119. router.push(`/strain/inbound/add?mode=detail&id=${item.id}`)
  120. } else {
  121. router.push(`/strain/usage/add?mode=detail&id=${item.id}`)
  122. }
  123. }
  124. const onEditInbound = (item: any) => {
  125. router.push(`/strain/inbound/add?mode=edit&id=${item.id}`)
  126. }
  127. const onDelInbound = async (item: any) => {
  128. showConfirmDialog({
  129. title: '提示',
  130. message: `确认删除入库记录(${item.strainName})?`
  131. }).then(async () => {
  132. const [err]: any = await to(strainApi.deleteInbound({ ids: [item.id] }))
  133. if (err) return
  134. showNotify({ type: 'success', message: '删除成功' })
  135. resetList()
  136. onLoad()
  137. }).catch(() => {})
  138. }
  139. const onDelUsage = async (item: any) => {
  140. showConfirmDialog({
  141. title: '提示',
  142. message: `确认删除领用记录(${item.strainName})?`
  143. }).then(async () => {
  144. const [err]: any = await to(strainApi.deleteUsage({ ids: [item.id] }))
  145. if (err) return
  146. showNotify({ type: 'success', message: '删除成功' })
  147. resetList()
  148. onLoad()
  149. }).catch(() => {})
  150. }
  151. onMounted(() => {
  152. onLoad()
  153. })
  154. </script>
  155. <style lang="scss" scoped>
  156. .app-container {
  157. display: flex;
  158. flex-direction: column;
  159. min-height: 100vh;
  160. background-color: #f7f8fa;
  161. }
  162. .search-wrap {
  163. background: #fff;
  164. }
  165. .list-container {
  166. overflow-y: auto;
  167. padding: 10px;
  168. flex: 1;
  169. .van-list {
  170. .van-cell {
  171. background-color: #fff;
  172. border-radius: 8px;
  173. + .van-cell {
  174. margin-top: 10px;
  175. }
  176. }
  177. }
  178. }
  179. .card-item {
  180. header {
  181. .title {
  182. flex: 1;
  183. white-space: nowrap;
  184. overflow: hidden;
  185. text-overflow: ellipsis;
  186. font-size: 16px;
  187. }
  188. }
  189. .info-row {
  190. display: flex;
  191. justify-content: space-between;
  192. color: #333;
  193. margin-top: 4px;
  194. span:first-child {
  195. color: #787878;
  196. flex: 0 0 70px;
  197. }
  198. .val {
  199. flex: 1;
  200. text-align: right;
  201. }
  202. }
  203. .btns {
  204. justify-content: flex-end;
  205. gap: 10px;
  206. }
  207. }
  208. </style>