student-list.vue 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. <template>
  2. <view class="page">
  3. <van-nav-bar :title="`${grade} ${className}`" left-arrow @click-left="onBack" fixed />
  4. <view class="search-bar">
  5. <van-search v-model="searchKeyword" placeholder="搜索学生姓名或身份证号" @input="onSearch" />
  6. <van-button type="primary" size="small" @click="addStudent" style="width: 120rpx;">新增</van-button>
  7. </view>
  8. <view class="tab-bar">
  9. <van-tabs v-model:active="activeTab" @change="onTabChange">
  10. <van-tab title="未录入"></van-tab>
  11. <van-tab title="已录入"></van-tab>
  12. <van-tab title="全部"></van-tab>
  13. </van-tabs>
  14. </view>
  15. <view class="content">
  16. <view class="student-card" v-for="student in studentList" :key="student.id" @click="goToVisionInput(student)">
  17. <view class="card-header">
  18. <view class="left">
  19. <view class="name">{{ student.name }}</view>
  20. <image class="gender-icon" :src="student.gender === '男' ? '/static/images/icon/man.png' : '/static/images/icon/woman.png'" mode="aspectFit"></image>
  21. </view>
  22. <view class="status-tag" :class="student.input_status === '未录入' ? 'warning' : ''">{{ student.input_status }}</view>
  23. </view>
  24. <view class="card-body">
  25. <view class="info-row">
  26. <text class="label">身份证号</text>
  27. <text class="value">{{ student.id_card }}</text>
  28. </view>
  29. </view>
  30. <view class="divider" v-if="(student.vision_input_user || student.refraction_input_user) && activeTab !== 0"></view>
  31. <view class="card-footer" v-if="activeTab !== 0">
  32. <view class="info-row" v-if="student.vision_input_user">
  33. <text class="label">视力录入医生</text>
  34. <text class="value">{{ student.vision_input_user }}</text>
  35. </view>
  36. <view class="info-row" v-if="student.vision_input_time">
  37. <text class="label">视力录入时间</text>
  38. <text class="value">{{ student.vision_input_time }}</text>
  39. </view>
  40. <view class="info-row" v-if="student.refraction_input_user">
  41. <text class="label">屈光度录入医生</text>
  42. <text class="value">{{ student.refraction_input_user }}</text>
  43. </view>
  44. <view class="info-row" v-if="student.refraction_input_time">
  45. <text class="label">屈光度录入时间</text>
  46. <text class="value">{{ student.refraction_input_time }}</text>
  47. </view>
  48. </view>
  49. </view>
  50. </view>
  51. </view>
  52. </template>
  53. <script setup>
  54. import { getIncompleteVisionStudents } from '@/services/common'
  55. const studentList = ref([])
  56. const activeTab = ref(0)
  57. const classId = ref('')
  58. const className = ref('')
  59. const grade = ref('')
  60. const searchKeyword = ref('')
  61. const schoolName = ref('')
  62. const userName = ref('')
  63. const userPhone = ref('')
  64. const userOrg = ref('')
  65. let searchTimer = null
  66. const isEntered = (student) => {
  67. return student.vision_input_status === 1 && student.refraction_input_status === 1
  68. }
  69. const formatTime = (time) => {
  70. if (!time) return ''
  71. try {
  72. const date = new Date(time)
  73. if (isNaN(date.getTime())) return time
  74. date.setHours(date.getHours() + 8)
  75. const year = date.getFullYear()
  76. const month = String(date.getMonth() + 1).padStart(2, '0')
  77. const day = String(date.getDate()).padStart(2, '0')
  78. const hour = String(date.getHours()).padStart(2, '0')
  79. const minute = String(date.getMinutes()).padStart(2, '0')
  80. const second = String(date.getSeconds()).padStart(2, '0')
  81. return `${year}-${month}-${day} ${hour}:${minute}:${second}`
  82. } catch (e) {
  83. return time
  84. }
  85. }
  86. const onTabChange = () => {
  87. fetchStudents()
  88. }
  89. const fetchStudents = async () => {
  90. if (!classId.value) return
  91. try {
  92. const params = {}
  93. if (searchKeyword.value) {
  94. if (/^\d+$/.test(searchKeyword.value)) {
  95. params.id_card = searchKeyword.value
  96. } else {
  97. params.name = searchKeyword.value
  98. }
  99. }
  100. if (activeTab.value === 0) {
  101. params.status = 'incomplete'
  102. } else if (activeTab.value === 1) {
  103. params.status = 'complete'
  104. } else if (activeTab.value === 2) {
  105. params.status = 'all'
  106. }
  107. const res = await getIncompleteVisionStudents(classId.value, params)
  108. if (res && res.code === 200) {
  109. studentList.value = res.data.list || []
  110. }
  111. } catch (error) {
  112. console.error('获取学生列表失败', error)
  113. }
  114. }
  115. const onSearch = () => {
  116. if (searchTimer) clearTimeout(searchTimer)
  117. searchTimer = setTimeout(() => {
  118. fetchStudents()
  119. }, 500)
  120. }
  121. onLoad(async (options) => {
  122. classId.value = options.classId || ''
  123. className.value = options.className || ''
  124. grade.value = options.grade || ''
  125. schoolName.value = decodeURIComponent(options.schoolName || '')
  126. userName.value = decodeURIComponent(options.userName || '')
  127. userPhone.value = decodeURIComponent(options.userPhone || '')
  128. userOrg.value = decodeURIComponent(options.userOrg || '')
  129. if (!classId.value) {
  130. uni.showToast({
  131. title: '班级信息缺失',
  132. icon: 'none'
  133. })
  134. return
  135. }
  136. fetchStudents()
  137. })
  138. onShow(() => {
  139. if (classId.value) {
  140. fetchStudents()
  141. }
  142. })
  143. const addStudent = () => {
  144. uni.navigateTo({
  145. url: `/pages/index/add-student?classId=${classId.value}&userName=${encodeURIComponent(userName.value)}&userPhone=${encodeURIComponent(userPhone.value)}`
  146. })
  147. }
  148. const onBack = () => {
  149. uni.navigateBack()
  150. }
  151. const goToVisionInput = (student) => {
  152. const params = {
  153. studentId: student.id,
  154. studentName: student.name,
  155. studentGender: student.gender,
  156. studentIdCard: student.id_card,
  157. classId: classId.value,
  158. className: className.value,
  159. grade: grade.value,
  160. visionDataId: student.vision_data_id || '',
  161. schoolName: schoolName.value,
  162. userName: userName.value,
  163. userPhone: userPhone.value,
  164. userOrg: userOrg.value,
  165. visionStatus: student.vision_input_status,
  166. refractionStatus: student.refraction_input_status
  167. }
  168. const visionStatus = student.vision_input_status
  169. const refractionStatus = student.refraction_input_status
  170. if (visionStatus === 1 && refractionStatus === 1) {
  171. return uni.navigateTo({
  172. url: `/pages/index/vision-detail?${Object.keys(params).map(k => `${k}=${encodeURIComponent(params[k])}`).join('&')}`
  173. })
  174. }
  175. uni.navigateTo({
  176. url: `/pages/index/select-input-type?${Object.keys(params).map(k => `${k}=${encodeURIComponent(params[k])}`).join('&')}`
  177. })
  178. }
  179. </script>
  180. <style lang="scss" scoped>
  181. .page {
  182. min-height: 100vh;
  183. background: #f5f5f5;
  184. }
  185. .content {
  186. padding: 20rpx;
  187. padding-top: calc(20rpx + 46px + 54px + 44px);
  188. }
  189. .tab-bar {
  190. position: fixed;
  191. top: calc(46px + 54px);
  192. left: 0;
  193. right: 0;
  194. z-index: 999;
  195. background: #fff;
  196. }
  197. .search-bar {
  198. position: fixed;
  199. top: 46px;
  200. left: 0;
  201. right: 0;
  202. z-index: 999;
  203. background: #fff;
  204. display: flex;
  205. align-items: center;
  206. padding: 10rpx 20rpx;
  207. gap: 10rpx;
  208. }
  209. .search-bar :deep(.van-search) {
  210. flex: 1;
  211. padding: 0;
  212. }
  213. .search-bar :deep(.van-button) {
  214. flex-shrink: 0;
  215. }
  216. .student-card {
  217. background: #fff;
  218. border-radius: 20rpx;
  219. padding: 30rpx;
  220. margin-bottom: 20rpx;
  221. box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
  222. }
  223. .card-header {
  224. display: flex;
  225. align-items: center;
  226. justify-content: space-between;
  227. margin-bottom: 20rpx;
  228. padding-bottom: 20rpx;
  229. border-bottom: 1rpx solid #f0f0f0;
  230. }
  231. .left {
  232. display: flex;
  233. align-items: center;
  234. gap: 20rpx;
  235. }
  236. .name {
  237. font-size: 32rpx;
  238. font-weight: bold;
  239. color: #333;
  240. }
  241. .gender-icon {
  242. width: 40rpx;
  243. height: 40rpx;
  244. }
  245. .status-tag {
  246. padding: 8rpx 20rpx;
  247. border-radius: 30rpx;
  248. font-size: 24rpx;
  249. background: #e8f8f5;
  250. color: #07c160;
  251. }
  252. .status-tag.warning {
  253. background: #fff3e8;
  254. color: #ff976a;
  255. }
  256. .card-body {
  257. padding-top: 10rpx;
  258. }
  259. .divider {
  260. height: 1rpx;
  261. background: #f0f0f0;
  262. margin: 20rpx 0;
  263. }
  264. .card-footer {
  265. padding-top: 10rpx;
  266. }
  267. .info-row {
  268. display: flex;
  269. align-items: center;
  270. font-size: 26rpx;
  271. color: #666;
  272. margin-bottom: 10rpx;
  273. justify-content: space-between;
  274. }
  275. .info-row:last-child {
  276. margin-bottom: 0;
  277. }
  278. .label {
  279. color: #999;
  280. }
  281. .value {
  282. color: #333;
  283. text-align: right;
  284. }
  285. </style>