approval-progress.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <script setup>
  2. import { ref } from 'vue'
  3. import TopNavBar from '@/business/top-nav-bar.vue'
  4. import { getLeaveApplicationApproval, getDiagnosisAttachments } from '@/services/student-prosthetics'
  5. const info = computed(() => {
  6. const storage = JSON.parse(localStorage.getItem('studentInfo') || '{}')
  7. return storage
  8. })
  9. // 审批进度数据
  10. const progressData = ref({
  11. applyStatusStr: '审批中',
  12. name: '',
  13. schoolName: '',
  14. gradeClass: '',
  15. createdTime: '',
  16. details: {
  17. expectedStartDate: '',
  18. expectedEndDate: '',
  19. auditOpinion: '',
  20. diseaseDiagnosis: ''
  21. },
  22. progressSteps: [
  23. // {
  24. // nodeName: '家长提交',
  25. // operatorRole: '张三(家长)',
  26. // lastUpdatedTime: '2024-01-15 10:30',
  27. // action: '同意',
  28. // comment: '情况属实',
  29. // status: 'finish'
  30. // },
  31. // {
  32. // nodeName: '学校审批',
  33. // operatorRole: '李老师',
  34. // lastUpdatedTime: '2024-01-15 14:20',
  35. // action: '同意',
  36. // comment: '情况属实',
  37. // status: 'process'
  38. // },
  39. // {
  40. // nodeName: '教育局审批',
  41. // operatorRole: '',
  42. // lastUpdatedTime: '',
  43. // action: '',
  44. // comment: '',
  45. // status: 'pending'
  46. // }
  47. ],
  48. attachments: [
  49. // { url: 'https://fastly.jsdelivr.net/npm/@vant/assets/leaf.jpeg' },
  50. // { url: 'https://fastly.jsdelivr.net/npm/@vant/assets/tree.jpeg' }
  51. ]
  52. })
  53. // 计算当前激活的步骤索引
  54. const activeStep = ref(0)
  55. onLoad((options) => {
  56. for (const key in progressData.value) {
  57. if (!Object.hasOwn(info.value, key)) continue
  58. progressData.value[key] = info.value[key]
  59. }
  60. getLeaveApplicationApproval({ applicationId: info.value.applicationId, tenantId: info.value.tenantId }).then((res) => {
  61. if (res.success) {
  62. const infos = (res.infos || []).map((item, index) => {
  63. item.status = item.action ? 'finish' : 'process'
  64. return item
  65. })
  66. progressData.value.progressSteps = infos
  67. activeStep.value = infos.length - 1
  68. }
  69. })
  70. getDiagnosisAttachments({ applicationId: info.value.applicationId, tenantId: info.value.tenantId }).then((res) => {
  71. if (res.success) {
  72. const infos = (res.infos || []).map((item) => {
  73. if (item.fileUrl && item.fileUrl.length) {
  74. return { url: item.fileUrl[0].temp }
  75. } else {
  76. return { url: item.fileUrlStr }
  77. }
  78. })
  79. progressData.value.attachments = infos
  80. }
  81. })
  82. })
  83. </script>
  84. <template>
  85. <div class="progress-page">
  86. <TopNavBar></TopNavBar>
  87. <!-- 申请信息头部 -->
  88. <div class="progress-header">
  89. <div class="status-tag">{{ progressData.applyStatusStr }}</div>
  90. <div class="apply-info">
  91. <div class="info-row">
  92. <div class="label">学生姓名</div>
  93. <div class="value">{{ progressData.name }}</div>
  94. </div>
  95. <div class="info-row">
  96. <div class="label">学校</div>
  97. <div class="value">{{ progressData.schoolName }}</div>
  98. </div>
  99. <div class="info-row">
  100. <div class="label">班级</div>
  101. <div class="value">{{ progressData.gradeClass }}</div>
  102. </div>
  103. <div class="info-row">
  104. <div class="label">提交时间</div>
  105. <div class="value">{{ progressData.createdTime }}</div>
  106. </div>
  107. </div>
  108. </div>
  109. <!-- 审批进度 - 使用van-steps -->
  110. <div class="section">
  111. <div class="section-title">审批进度</div>
  112. <div class="steps-container">
  113. <van-steps direction="vertical" :active="activeStep">
  114. <van-step v-for="(step, index) in progressData.progressSteps" :key="index">
  115. <div>
  116. <!-- 步骤标题区域 -->
  117. <div class="step-header">
  118. <div class="step-title">{{ step.nodeName }}</div>
  119. <div class="step-status" :class="step.status">
  120. {{
  121. step.status === 'finish' ? '已完成' :
  122. step.status === 'process' ? '进行中' : '待处理'
  123. }}
  124. </div>
  125. </div>
  126. <!-- 处理人信息 -->
  127. <div class="step-info">
  128. <div class="info-item">
  129. <span class="info-label">处理人:</span>
  130. <span class="info-value">{{ step.operatorRole || '待处理' }}</span>
  131. </div>
  132. <div v-if="step.lastUpdatedTime" class="info-item">
  133. <span class="info-label">日期:</span>
  134. <span class="info-value">{{ step.lastUpdatedTime }}</span>
  135. </div>
  136. </div>
  137. <!-- 审批意见 -->
  138. <div v-if="step.action || step.comment" class="step-opinion">
  139. {{ step.action }} {{ step.comment }}
  140. </div>
  141. <!-- 空状态提示 -->
  142. <div v-if="!step.operatorRole && !step.lastUpdatedTime" class="step-empty">
  143. 等待处理中...
  144. </div>
  145. </div>
  146. </van-step>
  147. </van-steps>
  148. </div>
  149. </div>
  150. <!-- 申请详情 -->
  151. <div class="section">
  152. <div class="section-title">申请详情</div>
  153. <div class="detail-list">
  154. <div class="detail-item">
  155. <div class="label">开始日期</div>
  156. <div class="value">{{ progressData.details.expectedStartDate }}</div>
  157. </div>
  158. <div class="detail-item">
  159. <div class="label">结束日期</div>
  160. <div class="value">{{ progressData.details.expectedEndDate }}</div>
  161. </div>
  162. <div class="detail-item">
  163. <div class="label">申请原因</div>
  164. <div class="value">{{ progressData.details.auditOpinion }}</div>
  165. </div>
  166. <div class="detail-item">
  167. <div class="label">疾病诊断</div>
  168. <div class="value">{{ progressData.details.diseaseDiagnosis }}</div>
  169. </div>
  170. </div>
  171. </div>
  172. <!-- 证明材料 -->
  173. <div class="section">
  174. <div class="section-title">证明材料</div>
  175. <van-uploader v-model="progressData.attachments" multiple disabled :deletable="false" />
  176. <!-- <div class="attachment-images">
  177. <div class="image-item">
  178. <div class="image-placeholder">图片1: 医院诊断证明</div>
  179. </div>
  180. <div class="image-item">
  181. <div class="image-placeholder">图片2: 病历资料</div>
  182. </div>
  183. </div> -->
  184. </div>
  185. </div>
  186. </template>
  187. <style lang="scss" scoped>
  188. .progress-page {
  189. padding: 24rpx;
  190. background-color: #f7f8fa;
  191. min-height: 100vh;
  192. padding-top: 120rpx;
  193. }
  194. .progress-header {
  195. background: #fff;
  196. border-radius: 16rpx;
  197. padding: 32rpx 24rpx;
  198. margin-bottom: 24rpx;
  199. position: relative;
  200. .status-tag {
  201. position: absolute;
  202. top: 24rpx;
  203. right: 24rpx;
  204. background-color: #ff9800;
  205. color: #fff;
  206. font-size: 24rpx;
  207. padding: 4rpx 16rpx;
  208. border-radius: 20rpx;
  209. }
  210. .apply-info {
  211. .info-row {
  212. display: flex;
  213. padding: 12rpx 0;
  214. .label {
  215. width: 180rpx;
  216. font-size: 28rpx;
  217. color: #666;
  218. flex-shrink: 0;
  219. }
  220. .value {
  221. flex: 1;
  222. font-size: 28rpx;
  223. color: #333;
  224. }
  225. }
  226. }
  227. }
  228. .section {
  229. background: #fff;
  230. border-radius: 16rpx;
  231. padding: 24rpx;
  232. margin-bottom: 24rpx;
  233. .section-title {
  234. font-size: 32rpx;
  235. font-weight: 600;
  236. color: #333;
  237. margin-bottom: 24rpx;
  238. padding-bottom: 16rpx;
  239. border-bottom: 1rpx solid #eee;
  240. }
  241. }
  242. .steps-container {
  243. .van-step {
  244. padding: 32rpx 0 32rpx 0;
  245. position: relative;
  246. &:first-child {
  247. &::before {
  248. top: 72rpx;
  249. }
  250. }
  251. &:last-child {
  252. &::before {
  253. bottom: auto;
  254. height: 72rpx;
  255. }
  256. }
  257. .van-step__circle {
  258. width: 24rpx;
  259. height: 24rpx;
  260. background-color: #fff;
  261. border: 2rpx solid #666;
  262. box-shadow: 0 0 0 4rpx #fff;
  263. z-index: 1;
  264. &.van-step__circle--finish {
  265. background-color: #4caf50;
  266. border-color: #4caf50;
  267. }
  268. &.van-step__circle--process {
  269. background-color: #ff9800;
  270. border-color: #ff9800;
  271. }
  272. }
  273. .van-step__line {
  274. background-color: #e0e0e0;
  275. &.van-step__line--vertical {
  276. width: 2rpx;
  277. left: 21rpx;
  278. }
  279. }
  280. }
  281. .van-step__title {
  282. font-size: 0;
  283. line-height: 0;
  284. }
  285. }
  286. .step-header {
  287. display: flex;
  288. justify-content: space-between;
  289. align-items: center;
  290. margin-bottom: 16rpx;
  291. .step-title {
  292. font-size: 30rpx;
  293. font-weight: 500;
  294. color: #333;
  295. }
  296. .step-status {
  297. font-size: 26rpx;
  298. padding: 4rpx 12rpx;
  299. border-radius: 12rpx;
  300. &.finish {
  301. background-color: #e8f5e9;
  302. color: #4caf50;
  303. }
  304. &.process {
  305. background-color: #fff3e0;
  306. color: #ff9800;
  307. }
  308. &.wait {
  309. background-color: #f5f5f5;
  310. color: #999;
  311. }
  312. }
  313. }
  314. .step-info {
  315. margin-bottom: 16rpx;
  316. .info-item {
  317. font-size: 26rpx;
  318. color: #666;
  319. margin-bottom: 8rpx;
  320. line-height: 1.4;
  321. &:last-child {
  322. margin-bottom: 0;
  323. }
  324. .info-label {
  325. color: #888;
  326. }
  327. .info-value {
  328. color: #333;
  329. }
  330. }
  331. }
  332. .step-opinion {
  333. background: #f9f9f9;
  334. border-radius: 8rpx;
  335. padding: 16rpx;
  336. font-size: 28rpx;
  337. color: #333;
  338. border-left: 4rpx solid #4caf50;
  339. margin-bottom: 8rpx;
  340. }
  341. .step-empty {
  342. color: #999;
  343. font-size: 26rpx;
  344. padding: 16rpx 0;
  345. font-style: italic;
  346. }
  347. .detail-list {
  348. .detail-item {
  349. display: flex;
  350. padding: 20rpx 0;
  351. border-bottom: 1rpx solid #f0f0f0;
  352. &:last-child {
  353. border-bottom: none;
  354. }
  355. .label {
  356. width: 200rpx;
  357. font-size: 28rpx;
  358. color: #666;
  359. flex-shrink: 0;
  360. }
  361. .value {
  362. flex: 1;
  363. font-size: 28rpx;
  364. color: #333;
  365. }
  366. }
  367. }
  368. .attachment-images {
  369. display: grid;
  370. grid-template-columns: repeat(2, 1fr);
  371. gap: 24rpx;
  372. .image-item {
  373. aspect-ratio: 1;
  374. background: #f0f0f0;
  375. border-radius: 12rpx;
  376. display: flex;
  377. align-items: center;
  378. justify-content: center;
  379. border: 2rpx dashed #ddd;
  380. .image-placeholder {
  381. font-size: 26rpx;
  382. color: #888;
  383. text-align: center;
  384. padding: 16rpx;
  385. }
  386. }
  387. }
  388. </style>