index.vue 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383
  1. <!-- 实验室审批列表 -->
  2. <template>
  3. <view class="safetyEducationExamination-approval">
  4. <!-- 标签栏 -->
  5. <view class="tag-scroll">
  6. <text
  7. v-for="(tag, index) in statusTags"
  8. :key="index"
  9. class="tag tag-gray"
  10. :class="{ active: activeTagIndex === index }"
  11. @click="onTagChange(index)"
  12. >{{ tag.label }}<text v-if="tag.value === 0 && pendingCount > 0" class="tag-badge">{{ pendingCount }}</text></text>
  13. </view>
  14. <!-- 列表 -->
  15. <scroll-view scroll-y class="scroll-content" @scrolltolower="onScrollToLower">
  16. <view class="panel">
  17. <view
  18. v-for="(item, index) in approvalList"
  19. :key="index"
  20. class="approval-card"
  21. :style="item.statusCode != 0 ? 'opacity:0.85;' : ''"
  22. >
  23. <view class="row-between mb8">
  24. <view class="fw500" style="font-size:15px;">{{ item.userName }}</view>
  25. <text class="approval-status" :class="'approval-status-' + getStatusType(item.statusCode)">
  26. {{ item.statusCode == 0 ? '待审批' : item.statusCode == 1 ? '已通过' : '已驳回' }}
  27. </text>
  28. </view>
  29. <view class="info-grid">
  30. <view><text class="text-gray">学工号:</text>{{ item.userAccount }}</view>
  31. <view><text class="text-gray">类别:</text>{{ item.educationName || '-' }}</view>
  32. <view><text class="text-gray">所属单位:</text>{{ item.deptName || '-' }}</view>
  33. <view><text class="text-gray">提交时间:</text>{{ item.submitTime }}</view>
  34. </view>
  35. <view class="lab-info-box" :style="item.statusCode == 1 ? 'background:#f6ffed;' : ''">
  36. <view class="mb4">
  37. <text class="text-gray">申请实验室:</text>
  38. <text class="fw500">{{ item.subName }}</text>
  39. </view>
  40. <view class="mb4">
  41. <text class="text-gray">风险等级:</text>
  42. <text v-if="item.levelName" class="tag tag-risk text-xs">{{ item.levelName }}</text>
  43. <text class="text-gray" style="margin-left:6px;">安全类别:</text>
  44. <text class="fw500">{{ item.typeName || '-' }}</text>
  45. </view>
  46. <view v-if="item.reason">
  47. <text class="text-gray">进入原因:</text>{{ item.reason }}
  48. </view>
  49. </view>
  50. <view v-if="item.statusCode == 0" class="btn-row">
  51. <view class="btn btn-green" @click="handleAudit(item, 1)">通过</view>
  52. <view class="btn btn-reject" @click="handleAudit(item, 2)">驳回</view>
  53. </view>
  54. </view>
  55. <view v-if="!approvalList[0] && !loading" class="empty-tip">暂无审批数据</view>
  56. <view v-if="loading" class="loading-tip">加载中...</view>
  57. <view v-if="noMore && approvalList[0]" class="loading-tip">没有更多了</view>
  58. </view>
  59. </scroll-view>
  60. </view>
  61. </template>
  62. <script>
  63. import {
  64. parseTime
  65. } from '@/component/public.js'
  66. import {
  67. examElLabEduRelationAuditList,
  68. examElLabEduRelationAudit,
  69. } from '@/pages_safetyEducationExamination/api/index.js'
  70. export default {
  71. data() {
  72. return {
  73. statusTags: [
  74. { label: '全部', value: null },
  75. { label: '待审批', value: 0 },
  76. { label: '已通过', value: 1 },
  77. { label: '已驳回', value: 2 },
  78. ],
  79. activeTagIndex: 0,
  80. approvalList: [],
  81. loading: false,
  82. page: 1,
  83. pageSize: 10,
  84. total: 0,
  85. noMore: false,
  86. pendingCount: 0,
  87. }
  88. },
  89. onShow() {
  90. this.loadPendingCount();
  91. this.resetList();
  92. },
  93. methods: {
  94. async loadPendingCount() {
  95. try {
  96. const { data } = await examElLabEduRelationAuditList({
  97. page: 1,
  98. pageSize: 1,
  99. statusCode: 0,
  100. });
  101. if (data.code === 200) {
  102. this.pendingCount = data.data.total || 0;
  103. }
  104. } catch (e) {
  105. console.error('获取待审批数量失败', e);
  106. }
  107. },
  108. resetList() {
  109. this.page = 1;
  110. this.noMore = false;
  111. this.approvalList = [];
  112. this.loadApprovalList();
  113. },
  114. async loadApprovalList() {
  115. if (this.loading || this.noMore) return;
  116. this.loading = true;
  117. try {
  118. const tag = this.statusTags[this.activeTagIndex];
  119. const { data } = await examElLabEduRelationAuditList({
  120. page: this.page,
  121. pageSize: this.pageSize,
  122. statusCode: tag.value !== null ? tag.value : undefined,
  123. });
  124. if (data.code === 200) {
  125. for(let i=0;i<data.data.records.length;i++){
  126. data.data.records[i].submitTime = parseTime(data.data.records[i].submitTime, "{y}-{m}-{d}")
  127. }
  128. const records = data.data.records || [];
  129. const newList = this.page === 1 ? records : [...this.approvalList, ...records];
  130. this.$set(this, 'approvalList', newList);
  131. this.total = data.data.total;
  132. if (newList.length >= this.total) {
  133. this.noMore = true;
  134. } else {
  135. this.page++;
  136. }
  137. }
  138. } catch (e) {
  139. console.error('获取审批列表失败', e);
  140. } finally {
  141. this.loading = false;
  142. }
  143. },
  144. onScrollToLower() {
  145. this.loadApprovalList();
  146. },
  147. onTagChange(index) {
  148. if (this.activeTagIndex !== index) {
  149. this.activeTagIndex = index;
  150. this.resetList();
  151. }
  152. },
  153. getStatusType(statusCode) {
  154. const map = { 0: 'pending', 1: 'pass', 2: 'reject' };
  155. return map[statusCode] || 'pending';
  156. },
  157. handleAudit(item, statusCode) {
  158. const text = statusCode == 1 ? '通过' : '驳回';
  159. uni.showModal({
  160. title: '确认审批',
  161. content: `确定${text}该学生的实验室绑定申请吗?`,
  162. success: async (res) => {
  163. if (res.confirm) {
  164. await this.submitAudit(item.id, statusCode);
  165. }
  166. }
  167. });
  168. },
  169. async submitAudit(id, statusCode) {
  170. uni.showLoading({ title: '提交中...' });
  171. try {
  172. const { data } = await examElLabEduRelationAudit({
  173. id,
  174. statusCode,
  175. });
  176. uni.hideLoading();
  177. if (data.code === 200) {
  178. uni.showToast({
  179. title: '审批成功',
  180. icon: 'success',
  181. });
  182. this.loadPendingCount();
  183. this.resetList();
  184. } else {
  185. uni.showToast({
  186. title: data.msg || '审批失败',
  187. icon: 'none',
  188. });
  189. }
  190. } catch (e) {
  191. uni.hideLoading();
  192. console.error('审批失败', e);
  193. uni.showToast({
  194. title: '审批失败',
  195. icon: 'none',
  196. });
  197. }
  198. },
  199. },
  200. }
  201. </script>
  202. <style lang="stylus" scoped>
  203. .safetyEducationExamination-approval {
  204. height: 100%;
  205. display: flex;
  206. flex-direction: column;
  207. overflow: hidden;
  208. background-color: #f0f4fb;
  209. }
  210. .tag-scroll {
  211. background: #fff;
  212. padding: 20rpx 24rpx 10rpx;
  213. white-space: nowrap;
  214. flex-shrink: 0;
  215. }
  216. .tag {
  217. display: inline-block;
  218. padding: 12rpx 28rpx;
  219. border-radius: 30rpx;
  220. font-size: 24rpx;
  221. margin-right: 16rpx;
  222. background: #eef1f6;
  223. color: #666;
  224. position: relative;
  225. }
  226. .tag.active {
  227. background: #1857d4;
  228. color: #fff;
  229. font-weight: 600;
  230. }
  231. .tag-badge {
  232. display: inline-block;
  233. background: #ff4d4f;
  234. color: #fff;
  235. font-size: 20rpx;
  236. border-radius: 20rpx;
  237. margin-left: 8rpx;
  238. min-width: 32rpx;
  239. text-align: center;
  240. }
  241. .tag.active .tag-badge {
  242. background: #fff;
  243. color: #1857d4;
  244. }
  245. .scroll-content {
  246. flex: 1;
  247. overflow: hidden;
  248. }
  249. .panel {
  250. padding: 16rpx 0;
  251. }
  252. .approval-card {
  253. background: #fff;
  254. border-radius: 20rpx;
  255. padding: 28rpx;
  256. margin: 0 24rpx 20rpx;
  257. border: 1px solid #dde3ed;
  258. }
  259. .row-between {
  260. display: flex;
  261. justify-content: space-between;
  262. align-items: center;
  263. }
  264. .mb8 {
  265. margin-bottom: 16rpx;
  266. }
  267. .mb4 {
  268. margin-bottom: 8rpx;
  269. }
  270. .fw500 {
  271. font-weight: 500;
  272. }
  273. .text-gray {
  274. color: #999;
  275. }
  276. .approval-status {
  277. font-size: 22rpx;
  278. padding: 6rpx 16rpx;
  279. border-radius: 20rpx;
  280. }
  281. .approval-status-pending {
  282. background: #fff7e6;
  283. color: #d46b08;
  284. }
  285. .approval-status-pass {
  286. background: #f6ffed;
  287. color: #389e0d;
  288. }
  289. .approval-status-reject {
  290. background: #fff1f0;
  291. color: #cf1322;
  292. }
  293. .info-grid {
  294. display: grid;
  295. grid-template-columns: 1fr 1fr;
  296. gap: 8rpx;
  297. font-size: 26rpx;
  298. margin-bottom: 20rpx;
  299. }
  300. .lab-info-box {
  301. background: #f5f5f5;
  302. border-radius: 16rpx;
  303. padding: 20rpx;
  304. margin-bottom: 20rpx;
  305. font-size: 26rpx;
  306. }
  307. .tag-risk {
  308. display: inline-block;
  309. padding: 4rpx 12rpx;
  310. border-radius: 8rpx;
  311. background: #fff7e6;
  312. color: #d46b08;
  313. font-size: 20rpx;
  314. }
  315. .text-xs {
  316. font-size: 20rpx;
  317. }
  318. .btn-row {
  319. display: flex;
  320. gap: 16rpx;
  321. }
  322. .btn {
  323. flex: 1;
  324. height: 68rpx;
  325. line-height: 68rpx;
  326. text-align: center;
  327. font-size: 26rpx;
  328. border-radius: 12rpx;
  329. }
  330. .btn-green {
  331. background: #52c41a;
  332. color: #fff;
  333. }
  334. .btn-reject {
  335. background: #fff1f0;
  336. color: #ff4d4f;
  337. border: 1px solid #ff4d4f;
  338. }
  339. .empty-tip {
  340. text-align: center;
  341. line-height: 80rpx;
  342. color: #aaa;
  343. font-size: 24rpx;
  344. margin-top: 80rpx;
  345. }
  346. .loading-tip {
  347. text-align: center;
  348. line-height: 60rpx;
  349. color: #aaa;
  350. font-size: 22rpx;
  351. }
  352. </style>