detail.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767
  1. <!--
  2. * @Author: wanglj wanglijie@dashoo.cn
  3. * @Date: 2025-03-24 09:17:15
  4. * @LastEditors: wanglj wanglijie@dashoo.cn
  5. * @LastEditTime: 2025-03-28 11:39:21
  6. * @FilePath: \labsop_h5\src\view\instr\detail.vue
  7. * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  8. -->
  9. <template>
  10. <div class="instr-detail">
  11. <van-swipe
  12. v-if="noticeInfo.noticeTitle"
  13. class="my-swipe"
  14. :autoplay="5000"
  15. :show-indicators="false"
  16. vertical
  17. height="30"
  18. >
  19. <van-swipe-item @click="state.popupShow = true">
  20. <div class="flex">
  21. <van-icon
  22. name="volume-o"
  23. class="mr4"
  24. :size="20"
  25. />
  26. {{ noticeInfo.noticeTitle }}
  27. </div>
  28. </van-swipe-item>
  29. </van-swipe>
  30. <header class="flex">
  31. <div class="h100">
  32. <!-- <img :showLoading="true" :src="state.instDetail.instPicture" width="80px" height="80px" /> -->
  33. <van-image
  34. width="80px"
  35. height="80px"
  36. :src="getImageUrl(state.instDetail.instPicture)"
  37. />
  38. </div>
  39. <div class="i-right ml10">
  40. <div class="h100 flex flex-top flex-column flex-between">
  41. <div class="flex flex-top mb4 ml2">
  42. <div class="detailTxt name">{{ state.instDetail.instName }}({{ state.instDetail.instCode }})</div>
  43. </div>
  44. <footer>
  45. <div class="flex flex-top mb4 mt-auto">
  46. <img
  47. class="i-r-icon"
  48. src="../../assets/img/user.png"
  49. v-if="state.instDetail.instHeadName"
  50. />
  51. <div class="detailTxt">{{ state.instDetail.instHeadName }}</div>
  52. </div>
  53. <div class="flex flex-top">
  54. <img
  55. class="i-r-icon"
  56. src="../../assets/img/address.png"
  57. v-if="state.instDetail.placeAddress"
  58. />
  59. <div class="detailTxt">
  60. {{ state.instDetail.placeAddress + setLaboratoryName(state.instDetail.laboratoryName) }}
  61. </div>
  62. </div>
  63. </footer>
  64. </div>
  65. </div>
  66. </header>
  67. <van-tabs
  68. v-model:active="active"
  69. @change="tabChange"
  70. >
  71. <van-tab
  72. title="仪器信息"
  73. name="info"
  74. ></van-tab>
  75. <van-tab
  76. title="待审核"
  77. name="approval"
  78. ></van-tab>
  79. <van-tab
  80. title="历史申请"
  81. name="history"
  82. ></van-tab>
  83. </van-tabs>
  84. <div
  85. v-if="active === 'info'"
  86. class="content"
  87. >
  88. <div class="card">
  89. <h4>仪器信息</h4>
  90. <ul>
  91. <li>
  92. <label>名称</label>
  93. <span>{{ state.instDetail.instName }}</span>
  94. </li>
  95. <li>
  96. <label>编号</label>
  97. <span>{{ state.instDetail.instCode }}</span>
  98. </li>
  99. <li>
  100. <label>仪器型号</label>
  101. <span>{{ state.instDetail.instNameEn }}</span>
  102. </li>
  103. <li>
  104. <label>当前状态</label>
  105. <span>{{ state.instStatus[state.instDetail.instStatus] }}</span>
  106. </li>
  107. <li>
  108. <label>所属组织</label>
  109. <span>{{ state.instDetail.belongOrgName }}</span>
  110. </li>
  111. <li>
  112. <label>位置</label>
  113. <span>{{ state.instDetail.placeAddress }}</span>
  114. </li>
  115. <li>
  116. <label>负责人</label>
  117. <span>{{ state.instDetail.instHeadName }}</span>
  118. </li>
  119. <li>
  120. <label>联系方式</label>
  121. <span>{{ state.instDetail.instHeadTel }}</span>
  122. </li>
  123. </ul>
  124. </div>
  125. <div class="card">
  126. <h4>申请须知</h4>
  127. <div class="text">{{ state.instDetail.applicationNotes }}</div>
  128. </div>
  129. <div class="card">
  130. <h4>主要功能</h4>
  131. <div class="text">{{ state.instDetail.instFunctFeat }}</div>
  132. </div>
  133. <div
  134. class="card"
  135. v-if="isNeedGrant"
  136. @click="applicationAuth"
  137. >
  138. <h4>资质申请</h4>
  139. </div>
  140. <div
  141. class="card"
  142. v-if="isNeedGrant"
  143. @click="applyTraining"
  144. >
  145. <h4>培训申请</h4>
  146. </div>
  147. <!-- <div class="card">
  148. <h4>相关附件</h4>
  149. <template v-for="item in state.instFiles">
  150. <div class="file-item">
  151. <a href="javascript: void(0);" @click="realDown(item.docName, item.docUrl)">{{ item.docName }}</a>
  152. </div>
  153. </template>
  154. </div> -->
  155. </div>
  156. <van-list
  157. v-else
  158. v-model:loading="state.loading"
  159. :finished="state.finished"
  160. finished-text="没有更多了"
  161. @load="onLoad"
  162. >
  163. <van-cell
  164. v-for="item in state.list"
  165. :key="item.id"
  166. >
  167. <template #default>
  168. <div class="list">
  169. <header class="flex justify-between">
  170. <strong class="title">{{ item.userName }}的预约</strong>
  171. <van-tag
  172. v-if="item.appointStatus == '10'"
  173. type="default"
  174. >
  175. 待审核
  176. </van-tag>
  177. <van-tag
  178. v-else-if="item.appointStatus == '11'"
  179. type="warning"
  180. >
  181. 已退回
  182. </van-tag>
  183. <van-tag
  184. v-else-if="item.appointStatus == '20'"
  185. type="success"
  186. >
  187. 已通过
  188. </van-tag>
  189. <van-tag
  190. v-else-if="item.appointStatus == '30'"
  191. type="danger"
  192. >
  193. 已驳回
  194. </van-tag>
  195. <van-tag
  196. v-else-if="item.appointStatus == '40'"
  197. type="warning"
  198. >
  199. 已取消
  200. </van-tag>
  201. <van-tag
  202. v-else-if="item.appointStatus == '50'"
  203. type="default"
  204. >
  205. 已上机
  206. </van-tag>
  207. <van-tag
  208. v-else-if="item.appointStatus == '60'"
  209. type="primary"
  210. >
  211. 已完成
  212. </van-tag>
  213. <van-tag
  214. v-else-if="item.appointStatus == '70'"
  215. type="warning"
  216. >
  217. 审核超时
  218. </van-tag>
  219. <van-tag
  220. v-else-if="item.appointStatus == '80'"
  221. type="danger"
  222. >
  223. 超时取消
  224. </van-tag>
  225. <van-tag
  226. v-else-if="item.appointStatus == '90'"
  227. type="danger"
  228. >
  229. 超时未上机
  230. </van-tag>
  231. </header>
  232. <p class="inst-title">
  233. <span>预约仪器</span>
  234. <span class="title ml8">{{ item.instName }}({{ item.instCode }})</span>
  235. </p>
  236. <p class="inst-title">
  237. <span>预约时间</span>
  238. <span class="title ml8">
  239. {{ formatDate(new Date(item.startTime), 'YYYY-mm-dd HH:MM') }}~{{
  240. formatDate(new Date(item.endTime), 'YYYY-mm-dd HH:MM')
  241. }}
  242. </span>
  243. </p>
  244. <p class="inst-title">
  245. <span>预约时长</span>
  246. <span class="title ml8">{{ getAppointTime(item) }}</span>
  247. </p>
  248. <p class="inst-title">
  249. <span>违约情况</span>
  250. <span class="title ml8">{{ getBreachTypes(item) }}</span>
  251. </p>
  252. <p class="inst-title">
  253. <span>扣分明细</span>
  254. <span class="title ml8">{{ item.breachScore }}分</span>
  255. </p>
  256. <p class="inst-title">
  257. <span>备注</span>
  258. <span class="title ml8">{{ item.remark }}</span>
  259. </p>
  260. <footer class="flex justify-between mt4">
  261. <span class="title">{{ item.userName }}</span>
  262. <span class="time">{{ formatDate(new Date(item.createdTime), 'mm-dd HH:MM') }}</span>
  263. </footer>
  264. </div>
  265. </template>
  266. </van-cell>
  267. </van-list>
  268. <van-back-top
  269. target=".instr-detail"
  270. bottom="10vh"
  271. />
  272. </div>
  273. <van-action-bar placeholder>
  274. <van-action-bar-icon
  275. icon="wap-home-o"
  276. text="首页"
  277. @click="onRouterPush('/home')"
  278. />
  279. <van-action-bar-icon
  280. :icon="state.instDetail.following ? 'star' : 'star-o'"
  281. :class="{ follow: state.instDetail.following }"
  282. :text="state.instDetail.following ? '取消收藏' : '收藏'"
  283. @click="handleFollowInst"
  284. />
  285. <van-action-bar-icon
  286. icon="revoke"
  287. text="返回"
  288. @click="onRouterPush('/instr-list')"
  289. />
  290. <van-action-bar-button
  291. v-if="state.instDetail.instStatus == '10' && state.instDetail.isAppointment == '10'"
  292. type="primary"
  293. text="使用预约"
  294. @click="onAppoint('use')"
  295. />
  296. <van-action-bar-button
  297. v-if="state.instDetail.instStatus == '10' && state.instDetail.isSampleDelivery == '10'"
  298. type="warning"
  299. text="送样预约"
  300. @click="onAppoint('sample')"
  301. />
  302. </van-action-bar>
  303. <!-- 通知 -->
  304. <van-popup
  305. v-model:show="state.popupShow"
  306. round
  307. :closeable="true"
  308. position="top"
  309. :style="{ padding: '20px' }"
  310. >
  311. <h4>{{ noticeInfo.noticeTitle }}</h4>
  312. <div
  313. class="notice-container"
  314. v-html="noticeInfo.noticeContent"
  315. ></div>
  316. </van-popup>
  317. <!-- 申请须知 -->
  318. <van-popup
  319. v-model:show="state.needToKnowShow"
  320. round
  321. :closeable="true"
  322. position="bottom"
  323. :style="{ height: '90vh' }"
  324. >
  325. <div class="need-to-know">
  326. <h4 class="mt8 mb8">申请须知</h4>
  327. <p>{{ state.instDetail.applicationNotes }}</p>
  328. <footer>
  329. <van-button
  330. class="w100"
  331. type="primary"
  332. round
  333. @click="confirmAppoint"
  334. >
  335. 我知道了
  336. </van-button>
  337. </footer>
  338. </div>
  339. </van-popup>
  340. <AddAuthDialog ref="addAuthDialogRef" />
  341. </template>
  342. <script lang="ts" setup>
  343. import to from 'await-to-js'
  344. import { useRoute, useRouter } from 'vue-router'
  345. import { ElMessageBox, ElMessage } from 'element-plus'
  346. import { useInstrApi } from '/@/api/instr'
  347. import { useInstDocApi } from '/@/api/instr/document'
  348. import { onMounted, reactive, ref } from 'vue'
  349. import { formatDate } from '/@/utils/formatTime'
  350. import { showNotify } from 'vant'
  351. import download from 'downloadjs'
  352. import { useNoticeApi } from '/@/api/instr/notice'
  353. import { useUseAppointApi } from '/@/api/instr/useAppoint'
  354. import { useBlackApi } from '/@/api/blacklist'
  355. import AddAuthDialog from './addAuthorization/index.vue'
  356. import { useUserInfos } from '/@/hooks/useUserInfos'
  357. import { useTrainingApi } from '/@/api/instr/inst/training'
  358. import { getImageUrl } from '/@/utils/url'
  359. const route = useRoute()
  360. const router = useRouter()
  361. const instApi = useInstrApi()
  362. const instDocApi = useInstDocApi()
  363. const noticeApi = useNoticeApi()
  364. const useAppointApi = useUseAppointApi()
  365. const blacklistApi = useBlackApi()
  366. const trainingApi = useTrainingApi()
  367. const active = ref('info')
  368. const state = reactive({
  369. detailsLoading: false,
  370. instStatus: {
  371. 10: '正常',
  372. 20: '故障',
  373. 30: '报废',
  374. },
  375. instDetail: {} as any,
  376. instFiles: [] as any[],
  377. loading: false,
  378. finished: false,
  379. queryParams: {
  380. pageNum: 1,
  381. pageSize: 10,
  382. instId: 0,
  383. appointStatus: [],
  384. },
  385. list: [] as any[],
  386. popupShow: false,
  387. needToKnowShow: false,
  388. appointType: '',
  389. })
  390. const noticeInfo = ref({ noticeTitle: '', noticeContent: '' })
  391. const isNeedGrant = ref(false)
  392. const addAuthDialogRef = ref()
  393. const { userInfos } = useUserInfos()
  394. const getNeedGrant = async (instId: number) => {
  395. const [err, res]: ToResponse = await to(useAppointApi.getNeedGrant({ instId }))
  396. if (err) return
  397. isNeedGrant.value = res?.data
  398. }
  399. // 获取仪器详情
  400. const getDetail = async (id: number) => {
  401. state.detailsLoading = true
  402. const [err, res]: ToResponse = await to(instApi.getDetail({ id }))
  403. state.detailsLoading = false
  404. if (err) return
  405. if (res?.code === 200) {
  406. state.instDetail = res.data
  407. getDocs()
  408. getNotice()
  409. }
  410. }
  411. const getNotice = async () => {
  412. const param = {
  413. pageNum: 1,
  414. pageSize: 1,
  415. instId: state.instDetail.instId,
  416. }
  417. const [err, res]: ToResponse = await to(noticeApi.list({ ...param }))
  418. if (err) return
  419. noticeInfo.value = res?.data?.list.length > 0 ? res?.data?.list[0] : {}
  420. }
  421. // 附件列表
  422. const getDocs = async () => {
  423. const [err, res]: ToResponse = await to(
  424. instDocApi.list({ noPage: true, instId: state.instDetail.instId, docType: '' }),
  425. )
  426. if (err) return
  427. state.instFiles = res?.data.list || []
  428. }
  429. const realDown = (filename: string, fileurl: string) => {
  430. let ua = navigator.userAgent.toLowerCase()
  431. if (ua.includes('mac')) {
  432. //iOS 将文件url转换为文件流 在下载
  433. downloadFun(fileurl + '?response-content-type=application/octet-stream', filename)
  434. } else {
  435. //android 直接用插件的方法下载即可
  436. download(fileurl, filename)
  437. }
  438. }
  439. // 创建a标签 实现下载
  440. const downloadFun = async (blobFile, fileName) => {
  441. let blob = new Blob([blobFile], {
  442. type: 'application/pdf;charset=UTF-8',
  443. })
  444. // @ts-ignore
  445. if (window.navigator.msSaveOrOpenBlob) {
  446. // @ts-ignore
  447. navigator.msSaveBlob(blob, fileName)
  448. } else {
  449. let link = document.createElement('a')
  450. link.href = window.URL.createObjectURL(blob)
  451. link.download = fileName
  452. link.click()
  453. window.URL.revokeObjectURL(link.href) //释放内存
  454. }
  455. }
  456. const setLaboratoryName = (name) => {
  457. return name ? `(${name})` : ''
  458. }
  459. const tabChange = (name: string) => {
  460. if (name === 'history' || name === 'approval') {
  461. state.finished = false
  462. state.list = []
  463. state.queryParams = {
  464. pageNum: 1,
  465. pageSize: 10,
  466. instId: state.instDetail.id,
  467. appointStatus: name === 'approval' ? ['10'] : [],
  468. }
  469. onLoad()
  470. }
  471. }
  472. const onLoad = async () => {
  473. state.loading = true
  474. const [err, res]: ToResponse = await to(useAppointApi.getListByPermission(state.queryParams))
  475. if (err) return
  476. const list = res?.data?.list || []
  477. for (const item of list) {
  478. state.list.push(item)
  479. }
  480. state.loading = false
  481. state.queryParams.pageNum++
  482. if (list.length < state.queryParams.pageSize) {
  483. state.finished = true
  484. }
  485. }
  486. const getBreachTypes = (row: any) => {
  487. let breachTypes = <string[]>[]
  488. if (row.isLate) breachTypes.push('迟到')
  489. if (row.isOvertime) breachTypes.push('超时')
  490. if (row.isLeaveEarly) breachTypes.push('早退')
  491. if (row.isAbsence) breachTypes.push('爽约')
  492. return breachTypes.join('、') || '-'
  493. }
  494. const getAppointTime = (row: any) => {
  495. const startDate = new Date(row.startTime)
  496. const endDate = new Date(row.endTime)
  497. // 计算两个日期之间的时间差(以毫秒为单位)
  498. const timeDifference = endDate.getTime() - startDate.getTime()
  499. // 计算天数
  500. const days = Math.floor(timeDifference / (1000 * 60 * 60 * 24))
  501. // 计算剩余的毫秒数
  502. const remainingMilliseconds = timeDifference % (1000 * 60 * 60 * 24)
  503. // 计算小时数
  504. const hours = Math.floor(remainingMilliseconds / (1000 * 60 * 60))
  505. // 计算剩余的毫秒数
  506. const remainingMillisecondsAfterHours = remainingMilliseconds % (1000 * 60 * 60)
  507. // 计算分钟数
  508. const minutes = Math.floor(remainingMillisecondsAfterHours / (1000 * 60))
  509. return `${days}天${hours}小时${minutes}分`
  510. }
  511. // 关注/取关
  512. const handleFollowInst = async () => {
  513. const [err] = state.instDetail.following
  514. ? await to(instApi.unfollow({ ids: [state.instDetail.id] }))
  515. : await to(instApi.follow({ ids: [state.instDetail.id] }))
  516. if (err) return
  517. showNotify({ type: 'success', message: !state.instDetail.following ? '收藏成功' : '已取消收藏' })
  518. getDetail(state.instDetail.id)
  519. }
  520. const onAppoint = async (type: string) => {
  521. state.appointType = type
  522. state.needToKnowShow = true
  523. }
  524. const confirmAppoint = async () => {
  525. const [err, res]: ToResponse = await to(blacklistApi.checkInBlacklist())
  526. if (err) return
  527. if (res.data) {
  528. showNotify({ type: 'danger', message: '您已被拉入黑名单,无法预约,请联系管理员' })
  529. return
  530. }
  531. if (state.appointType === 'sample') {
  532. onRouterPush('/sample-appoint', { id: state.instDetail.id })
  533. } else {
  534. onRouterPush('/instr-appoint', { id: state.instDetail.id })
  535. }
  536. }
  537. const onRouterPush = (val: string, params?: any) => {
  538. router.push({
  539. path: val,
  540. query: { ...params },
  541. })
  542. }
  543. const applicationAuth = async () => {
  544. console.log('applicationAuth')
  545. addAuthDialogRef.value.openDialog('personal', state.instDetail)
  546. }
  547. const applyTraining = () => {
  548. console.log('applyTraining')
  549. ElMessageBox.confirm('确认发起培训申请?', '提示', {
  550. confirmButtonText: '确认',
  551. cancelButtonText: '取消',
  552. type: 'warning',
  553. })
  554. .then(async () => {
  555. const params = {
  556. instCode: state.instDetail.instCode,
  557. instId: state.instDetail.id,
  558. instName: state.instDetail.instName,
  559. userId: userInfos.value.id,
  560. userName: userInfos.value.nickName,
  561. }
  562. const [err]: ToResponse = await to(trainingApi.add({ ...params }))
  563. if (err) return
  564. ElMessage.success('培训申请提交成功')
  565. })
  566. .catch(() => {})
  567. }
  568. onMounted(() => {
  569. const id = route.query.id ? +route.query.id : 0
  570. getDetail(id)
  571. getNeedGrant(id)
  572. })
  573. </script>
  574. <style lang="scss" scoped>
  575. .instr-detail {
  576. flex: 1;
  577. overflow-y: auto;
  578. background-color: #f7f8fa;
  579. .my-swipe {
  580. background-color: #fff;
  581. height: 30px !important;
  582. line-height: 30px !important;
  583. :deep(.flex) {
  584. height: 30px;
  585. overflow: hidden;
  586. padding: 0 12px;
  587. span {
  588. display: inline-block;
  589. height: 30px;
  590. line-height: 30px;
  591. }
  592. span:first-child {
  593. flex: 1;
  594. white-space: nowrap;
  595. overflow: hidden;
  596. text-overflow: ellipsis;
  597. }
  598. }
  599. }
  600. > header {
  601. height: auto;
  602. min-height: 80px;
  603. background-color: #fff;
  604. padding: 12px;
  605. }
  606. .inst-info {
  607. display: flex;
  608. }
  609. .i-right {
  610. flex: 1;
  611. font-size: 14px;
  612. height: auto;
  613. min-height: 80px;
  614. .i-r-icon {
  615. width: 15px;
  616. height: 15px;
  617. margin-right: 10px;
  618. }
  619. }
  620. .detailTxt {
  621. font-size: 12px;
  622. color: #333333;
  623. white-space: normal;
  624. overflow: visible;
  625. text-overflow: unset;
  626. word-break: break-all;
  627. &.name {
  628. font-weight: bold;
  629. font-size: 16px;
  630. }
  631. }
  632. .content {
  633. padding: 10px;
  634. }
  635. .card {
  636. border-radius: 4px;
  637. background-color: #fff;
  638. padding: 10px;
  639. box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
  640. & + .card {
  641. margin-top: 10px;
  642. }
  643. h4 {
  644. height: 18px;
  645. line-height: 18px;
  646. display: flex;
  647. margin-bottom: 10px;
  648. span {
  649. font-weight: normal;
  650. margin-left: auto;
  651. }
  652. &::before {
  653. display: inline-block;
  654. content: '';
  655. width: 3px;
  656. height: 18px;
  657. background-color: #1c9bfd;
  658. margin-right: 4px;
  659. vertical-align: middle;
  660. }
  661. }
  662. > ul {
  663. li {
  664. display: flex;
  665. padding: 6px 0;
  666. label {
  667. width: 80px;
  668. min-width: 80px;
  669. color: #969799;
  670. }
  671. span {
  672. word-break: break-all;
  673. }
  674. }
  675. }
  676. .text {
  677. white-space: pre-wrap;
  678. }
  679. }
  680. .van-list {
  681. padding: 10px;
  682. border-radius: 4px;
  683. flex: 1;
  684. .van-cell {
  685. background-color: #fff;
  686. + .van-cell {
  687. margin-top: 10px;
  688. }
  689. header,
  690. footer {
  691. color: #333;
  692. }
  693. .title {
  694. flex: 1;
  695. white-space: nowrap;
  696. overflow: hidden;
  697. text-overflow: ellipsis;
  698. text-align: left;
  699. }
  700. .inst-title {
  701. color: #333;
  702. text-align: left;
  703. flex: 1;
  704. overflow: hidden;
  705. white-space: nowrap;
  706. text-overflow: ellipsis;
  707. margin-top: 4px;
  708. span:first-child {
  709. display: inline-block;
  710. width: 80px;
  711. min-width: 80px;
  712. color: rgb(120, 120, 120);
  713. }
  714. }
  715. .time {
  716. color: #f69a4d;
  717. }
  718. }
  719. }
  720. }
  721. .btns {
  722. flex: 1;
  723. display: flex;
  724. li {
  725. display: flex;
  726. flex-direction: column;
  727. align-items: center;
  728. justify-content: center;
  729. padding: 0 8px;
  730. font-size: 12px;
  731. i {
  732. margin-bottom: 4px;
  733. }
  734. }
  735. }
  736. :deep(.follow .van-icon) {
  737. color: #fdc33e;
  738. }
  739. .need-to-know {
  740. height: calc(100% - 20px);
  741. overflow: hidden;
  742. display: flex;
  743. flex-direction: column;
  744. padding: 10px 20px;
  745. white-space: pre-wrap;
  746. p {
  747. flex: 1;
  748. overflow-y: auto;
  749. }
  750. footer {
  751. flex: 0 0 45px;
  752. margin-top: 4px;
  753. border-top: 1px solid #f7f8fa;
  754. }
  755. }
  756. </style>