index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. <template>
  2. <view class="content">
  3. <view class="page-section-spacing">
  4. <image src="/static/poster-bg-new.png" mode="scaleToFill" style="width: 100%; height: 100%"></image>
  5. </view>
  6. <view class="notice-box" style="width:90%;margin-top: 50rpx;">
  7. <view class="notice-title-box" style="margin: 0 auto;">
  8. <view class="notice-font margin-right-30">初诊/复诊问卷</view>
  9. <view class="notice-more-font">
  10. <image
  11. src="/static/dot.svg"
  12. mode="aspectFill"
  13. class="margin-right-10"
  14. style="width: 16rpx; height: 16rpx"
  15. ></image>
  16. <view class="dot-line margin-right-30"></view>
  17. <view class="margin-right-10" style="font-size: 20rpx;color: #70b62b;">已完成</view>
  18. <view style="font-size: 30rpx;color: #70b62b;">{{ totalFinish }}项</view>
  19. </view>
  20. </view>
  21. </view>
  22. <view class="page-nav-box">
  23. <view v-for="(item, index) in navList" :key="index" class="nav-content" @click="goDetailPage(item)">
  24. <view class="nav-badge" :style="{ background: badgeOf(item.task_name).color }">{{ badgeOf(item.task_name).label }}</view>
  25. <view class="nav-task-name">{{ item.task_name }}</view>
  26. <view v-if="item.finish_flag" style="color: #ff7356;font-size: 22rpx;">已完成</view>
  27. <view v-else style="color: #d9001b;font-size: 22rpx;">未完成</view>
  28. </view>
  29. </view>
  30. <view class="notice-box" style="width:90%;margin-top: 30rpx;" v-if="showCaseShare">
  31. <view class="notice-title-box" style="margin: 0 auto;">
  32. <view class="notice-font margin-right-30">病例分析分享</view>
  33. </view>
  34. <scroll-view scroll-y class="article-scroll" @scrolltolower="loadMoreCaseShare">
  35. <view class="article-item" v-for="item in caseShareList" :key="item.id" @click="goArticleDetail(item, 'case_share')">
  36. <view class="article-item-text">
  37. <view class="article-item-title">{{ item.title }}</view>
  38. <view class="article-item-sub">{{ item.hospital }} {{ item.doctor }}</view>
  39. <view class="article-item-date">{{ item.date }}</view>
  40. </view>
  41. <image class="article-item-img" :src="item.cover" mode="aspectFill"></image>
  42. </view>
  43. <view class="scroll-end-tip" v-if="!caseShareHasMore && caseShareList.length">没有更多了</view>
  44. </scroll-view>
  45. </view>
  46. <view class="notice-box" style="width:90%;margin-top: 30rpx;">
  47. <view class="notice-title-box" style="margin: 0 auto;">
  48. <view class="notice-font margin-right-30">用嗓科普</view>
  49. </view>
  50. <scroll-view scroll-y class="article-scroll" @scrolltolower="loadMoreVoiceScience">
  51. <view class="article-item" v-for="item in voiceScienceList" :key="item.id" @click="goArticleDetail(item, 'voice_science')">
  52. <view class="article-item-text">
  53. <view class="article-item-title">{{ item.title }}</view>
  54. <view class="article-item-sub">{{ item.hospital }} {{ item.doctor }}</view>
  55. <view class="article-item-date">{{ item.date }}</view>
  56. </view>
  57. <image class="article-item-img" :src="item.cover" mode="aspectFill"></image>
  58. </view>
  59. <view class="scroll-end-tip" v-if="!voiceScienceHasMore && voiceScienceList.length">没有更多了</view>
  60. </scroll-view>
  61. </view>
  62. </view>
  63. </template>
  64. <script>
  65. import { request, currentOpenId } from '@/utils/request.js'
  66. // 量表名称 -> 徽标文案/颜色,未命中时回退为取名称前两个字 + 按序号轮换配色
  67. const SCALE_BADGE_MAP = {
  68. '病史信息': { label: '病史', color: '#ff7356' },
  69. '疲劳量表': { label: 'Zzz', color: '#19be6b' },
  70. 'SAS量表': { label: 'SAS', color: '#5b9bf5' },
  71. 'SDS量表': { label: 'SDS', color: '#8f8fe0' },
  72. '反流量表': { label: 'RSI', color: '#5b6b8c' },
  73. 'VHI量表': { label: 'VHI', color: '#f2a93b' },
  74. }
  75. const FALLBACK_COLORS = ['#ff7356', '#19be6b', '#5b9bf5', '#8f8fe0', '#5b6b8c', '#f2a93b']
  76. export default {
  77. data() {
  78. return {
  79. navList: [],
  80. totalFinish: 0,
  81. // 病例分析分享是否可见依赖用户角色(普通用户不可见),判断依据是 info_by_openid 接口返回的 is_doctor 字段
  82. showCaseShare: false,
  83. caseShareList: [],
  84. caseSharePage: 1,
  85. caseShareHasMore: false,
  86. caseShareLoading: false,
  87. voiceScienceList: [],
  88. voiceSciencePage: 1,
  89. voiceScienceHasMore: false,
  90. voiceScienceLoading: false,
  91. articlePageSize: 10,
  92. };
  93. },
  94. async onShow() {
  95. await this.$onLaunched;
  96. this.getUserTask();
  97. this.getUserRole();
  98. this.getArticleList('case_share');
  99. this.getArticleList('voice_science');
  100. },
  101. onShareAppMessage() {
  102. return {
  103. title: '耳鸣测试',
  104. path: '/pages/index/index',
  105. imageUrl: '/static/shareImg2.png',
  106. }
  107. },
  108. methods: {
  109. badgeOf(name) {
  110. if (SCALE_BADGE_MAP[name]) return SCALE_BADGE_MAP[name]
  111. const idx = this.navList.findIndex(item => item.task_name === name)
  112. return { label: (name || '').slice(0, 2), color: FALLBACK_COLORS[idx % FALLBACK_COLORS.length] }
  113. },
  114. async getUserTask() {
  115. const res = await request('user', 'task', { openId: currentOpenId() })
  116. if (res.code === 200) {
  117. const result = res.data
  118. let total = 0
  119. result.forEach((item) => {
  120. item.finish_flag = Number(item.finish_flag)
  121. total += item.finish_flag
  122. })
  123. this.navList = result
  124. this.totalFinish = total
  125. }
  126. },
  127. async getUserRole() {
  128. // 普通用户看不到"病例分析分享",判断依据沿用1.0"我的"模块里的 is_doctor 字段
  129. const res = await request('user', 'info_by_openid', { openId: currentOpenId() })
  130. if (res.code === 200) {
  131. this.showCaseShare = Number(res.data.is_doctor) === 1
  132. }
  133. },
  134. async getArticleList(category, loadMore = false) {
  135. const categoryId = category === 'case_share' ? 1 : 2
  136. const pageKey = category === 'case_share' ? 'caseSharePage' : 'voiceSciencePage'
  137. const hasMoreKey = category === 'case_share' ? 'caseShareHasMore' : 'voiceScienceHasMore'
  138. const listKey = category === 'case_share' ? 'caseShareList' : 'voiceScienceList'
  139. const loadingKey = category === 'case_share' ? 'caseShareLoading' : 'voiceScienceLoading'
  140. if (this[loadingKey]) return
  141. if (loadMore && !this[hasMoreKey]) return
  142. this[loadingKey] = true
  143. const page = loadMore ? this[pageKey] + 1 : 1
  144. const res = await request('article', 'list', { category_id: categoryId, page, page_size: this.articlePageSize })
  145. this[loadingKey] = false
  146. if (res.code === 200) {
  147. const rawList = (res.data && res.data.list) || []
  148. const list = rawList.map(item => ({
  149. id: item.id,
  150. title: item.title,
  151. hospital: item.hospital_name,
  152. doctor: item.doctor_name,
  153. date: this.formatDate(item.create_time),
  154. cover: item.pic_url ? (getApp().globalData.shareUrl + item.pic_url) : '',
  155. }))
  156. this[listKey] = loadMore ? this[listKey].concat(list) : list
  157. this[pageKey] = page
  158. const pageCount = Number(res.data && res.data.page_count) || 1
  159. this[hasMoreKey] = page < pageCount
  160. } else {
  161. console.error('getArticleList failed', category, res)
  162. }
  163. },
  164. formatDate(ts) {
  165. if (!ts) return ''
  166. const d = new Date(Number(ts) * 1000)
  167. const y = d.getFullYear()
  168. const m = String(d.getMonth() + 1).padStart(2, '0')
  169. const day = String(d.getDate()).padStart(2, '0')
  170. return `${y}-${m}-${day}`
  171. },
  172. loadMoreCaseShare() {
  173. this.getArticleList('case_share', true)
  174. },
  175. loadMoreVoiceScience() {
  176. this.getArticleList('voice_science', true)
  177. },
  178. goArticleDetail(item, category) {
  179. uni.navigateTo({
  180. url: `/pages/index/articleDetail?id=${item.id}&category=${category}`
  181. })
  182. },
  183. goDetailPage(item) {
  184. item.category_id == 1 ? uni.navigateTo({
  185. url: "/pages/sickForm/history?finish=" + item.finish_flag
  186. }) :
  187. uni.navigateTo({
  188. url: "/pages/sickForm/blurb?id=" + item.category_id + '&name=' + item.task_name + '&finish=' + item.finish_flag
  189. })
  190. },
  191. },
  192. };
  193. </script>
  194. <style>
  195. .content {
  196. display: flex;
  197. flex-direction: column;
  198. align-items: center;
  199. justify-content: center;
  200. position: relative;
  201. }
  202. .page-section-spacing{
  203. width: 100%;
  204. height: 400rpx;
  205. }
  206. .page-nav-box {
  207. width: 98%;
  208. display: flex;
  209. flex-wrap: wrap;
  210. align-items: flex-start;
  211. padding: 20rpx 10rpx 30rpx 10rpx;
  212. border-radius: 16rpx;
  213. background-color: #fff;
  214. }
  215. .nav-content {
  216. width: 25%;
  217. display: flex;
  218. flex-direction: column;
  219. align-items: center;
  220. margin-top: 40rpx;
  221. padding: 0 6rpx;
  222. font-size: 28rpx;
  223. letter-spacing: 0.02em;
  224. color: #0d1937;
  225. font-family: PingFang SC;
  226. font-style: normal;
  227. box-sizing: border-box;
  228. }
  229. .nav-badge {
  230. width: 80px;
  231. height: 80px;
  232. border-radius: 50%;
  233. display: flex;
  234. align-items: center;
  235. justify-content: center;
  236. color: #fff;
  237. font-weight: bold;
  238. font-size: 26rpx;
  239. margin-bottom: 20rpx;
  240. }
  241. .nav-task-name {
  242. height: 76rpx;
  243. line-height: 38rpx;
  244. text-align: center;
  245. overflow: hidden;
  246. display: -webkit-box;
  247. -webkit-box-orient: vertical;
  248. -webkit-line-clamp: 2;
  249. margin-bottom: 2rpx;
  250. }
  251. .notice-box {
  252. width: 90%;
  253. margin-bottom: 20rpx;
  254. }
  255. .notice-title-box {
  256. width: 100%;
  257. display: flex;
  258. align-items: center;
  259. margin-bottom: 40rpx;
  260. }
  261. .notice-font {
  262. font-family: PingFang SC;
  263. font-style: normal;
  264. font-weight: bold;
  265. font-size: 38rpx;
  266. letter-spacing: 0.02em;
  267. color: #0d1937;
  268. }
  269. .notice-more-font {
  270. font-size: 28rpx;
  271. color: #146afb;
  272. display: flex;
  273. align-items: center;
  274. }
  275. .dot-line {
  276. width: 50rpx;
  277. height: 2rpx;
  278. background-color: #bfbfbf;
  279. }
  280. .margin-right-10 {
  281. margin-right: 10rpx;
  282. }
  283. .margin-right-30 {
  284. margin-right: 30rpx;
  285. }
  286. .article-item {
  287. display: flex;
  288. justify-content: space-between;
  289. align-items: center;
  290. background: #fff;
  291. border-radius: 12rpx;
  292. padding: 24rpx;
  293. margin-bottom: 20rpx;
  294. }
  295. .article-item-text {
  296. flex: 1;
  297. margin-right: 20rpx;
  298. }
  299. .article-item-title {
  300. font-size: 28rpx;
  301. color: #0d1937;
  302. font-weight: bold;
  303. line-height: 40rpx;
  304. }
  305. .article-item-sub {
  306. font-size: 24rpx;
  307. color: #5f5f5f;
  308. margin-top: 12rpx;
  309. }
  310. .article-item-date {
  311. font-size: 22rpx;
  312. color: #a7adba;
  313. margin-top: 6rpx;
  314. }
  315. .article-item-img {
  316. width: 140rpx;
  317. height: 140rpx;
  318. border-radius: 8rpx;
  319. flex-shrink: 0;
  320. }
  321. .article-scroll {
  322. max-height: 700rpx;
  323. }
  324. .scroll-end-tip {
  325. text-align: center;
  326. font-size: 24rpx;
  327. color: #a7adba;
  328. padding: 16rpx 0;
  329. }
  330. </style>