| | |
| | | <view class="nav-shell"> |
| | | <view class="safe-space" :style="'height:' + navTop + 'px'"></view> |
| | | <view class="nav-row" :style="'height:' + navHeight + 'px;padding-right:' + navRight + 'px'"> |
| | | <view class="back" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view> |
| | | <view class="nav-back" @tap="goBack"><image src="/static/news-assets/arrow-left.svg" mode="aspectFit"></image></view> |
| | | <text class="nav-title">人才资讯</text> |
| | | <view class="capsule-reserve" :style="'width:' + navWidth + 'px'"></view> |
| | | </view> |
| | | <view class="nav-bottom-space"></view> |
| | | </view> |
| | | |
| | | <scroll-view class="news-scroll" scroll-y lower-threshold="80" @scrolltolower="loadMoreNews"> |
| | | <scroll-view :show-scrollbar="false" class="news-scroll" scroll-y lower-threshold="80" @scrolltolower="loadMoreNews"> |
| | | <view v-if="initialLoading" class="list-state"><view class="loading-spinner"></view><text>正在加载人才资讯</text></view> |
| | | <view v-else-if="initialError" class="list-state error-state"><text>人才资讯加载失败</text><text class="retry-text" @tap="retryInitialLoad">重新加载</text></view> |
| | | <view v-else-if="hasLoaded && visibleNews.length == 0" class="list-state"><text>暂无人才资讯</text></view> |
| | |
| | | <view class="news-body"> |
| | | <view class="news-copy"> |
| | | <text class="news-summary">{{item.summary}}</text> |
| | | <view class="news-meta"><text class="news-tag">公招信息</text><text class="news-time">{{item.time}}</text></view> |
| | | <view class="news-meta"><text v-if="item.tag != ''" class="news-tag">{{item.tag}}</text><text class="news-time">{{item.time}}</text></view> |
| | | </view> |
| | | <image class="news-thumb" src="/static/news-assets/meeting-thumb.jpg" mode="aspectFill"></image> |
| | | <!-- 缩略图用后台配置的封面;没配就留空(灰底占位),不再统一显示那张默认会议厅图。 --> |
| | | <image class="news-thumb" :src="item.image != null ? item.image : ''" mode="aspectFill" @error="replaceImage(item)"></image> |
| | | </view> |
| | | </view> |
| | | <view v-if="loadingMore" class="loading-row"><view class="loading-spinner"></view><text>正在加载</text></view> |
| | |
| | | <script setup lang="uts"> |
| | | import { isApiConfigured } from '../../config/api.uts' |
| | | import { getNews } from '../../services/portal.uts' |
| | | import { resolveApiUrl } from '../../services/http.uts' |
| | | import { safeBack } from '../../utils/navigation.uts' |
| | | |
| | | const navTop = ref(28) |
| | |
| | | const failInitialOnce = ref(false) |
| | | const failLoadMoreOnce = ref(false) |
| | | const forceEmpty = ref(false) |
| | | // 封面图加载失败时置空,交给灰底占位,避免出现破图。 |
| | | const replaceImage = (item: any) => { item.image = '' } |
| | | |
| | | onLoad((options: any) => { |
| | | // #ifdef MP-WEIXIN |
| | |
| | | const text = '' + value |
| | | return text.length >= 16 ? text.substring(0, 16).replace('T', ' ') : text.replace('T', ' ') |
| | | } |
| | | const contentPreview = (value: any): string => { |
| | | if (value == null) return '' |
| | | const plain = (value as string).replace(/\s+/g, ' ').trim() |
| | | // 摘要区固定 3 行:按 3 行能放下的字数截断并补省略号, |
| | | // 避免文字被容器高度从中间切断(只露出半行、又没有省略号)。 |
| | | return plain.length > 54 ? plain.substring(0, 54) + '...' : plain |
| | | } |
| | | // 标签只占一行:压掉换行/连续空格(text 组件会把 \n 渲染成换行), |
| | | // 放不下时由样式的单行省略号收尾。 |
| | | const newsTagText = (value: any): string => { |
| | | if (value == null) return '' |
| | | return ('' + value).replace(/\s+/g, ' ').trim() |
| | | } |
| | | const mapNewsRecords = (records: any[]): any[] => { |
| | | const mapped: any[] = [] |
| | | for (let index = 0; index < records.length; index++) { |
| | |
| | | mapped.push({ |
| | | id: '' + item.id, |
| | | title: item.title != null ? item.title as string : '人才资讯', |
| | | summary: item.summary != null ? item.summary as string : '', |
| | | time: formatNewsTime(item.publishTime) |
| | | summary: contentPreview(item.contentPreview), |
| | | tag: newsTagText(item.summary), |
| | | time: formatNewsTime(item.publishTime), |
| | | // 后台配置的封面图;相对路径补成接口域名,没配就留空走灰底占位。 |
| | | image: item.coverUrl != null && ('' + item.coverUrl).trim() != '' ? resolveApiUrl('' + item.coverUrl) : '' |
| | | }) |
| | | } |
| | | return mapped |
| | |
| | | .page { display: flex; flex-direction: column; width: 100%; height: 100vh; background-color: #FFFFFF; } |
| | | .nav-shell { flex-shrink: 0; background-image: linear-gradient(180deg, #FFF0B8 0%, #FFF8E4 58%, #FFFFFF 100%); } |
| | | .safe-space { height: 28px; } |
| | | .nav-row { display: flex; position: relative; height: 32px; align-items: center; justify-content: flex-end; box-sizing: border-box; } |
| | | .back { display: flex; position: absolute; z-index: 2; left: 24rpx; top: 0; width: 72rpx; height: 100%; justify-content: center; align-items: center; } .back image { width: 38rpx; height: 38rpx; } |
| | | .nav-row { display: flex; position: relative; height: 32px; box-sizing: border-box; align-items: center; justify-content: flex-end; } |
| | | .nav-back { display: flex; position: absolute; z-index: 9; left: 16rpx; top: 0; bottom: 0; width: 88rpx; min-height: 64rpx; justify-content: center; align-items: center; } |
| | | .nav-back image { width: 40rpx; height: 40rpx; } |
| | | .nav-title { position: absolute; left: 190rpx; right: 190rpx; color: #171717; font-size: 35rpx; font-weight: 700; text-align: center; white-space: nowrap; } |
| | | .capsule-reserve { width: 88px; height: 100%; flex-shrink: 0; } |
| | | .nav-bottom-space { height: 34rpx; } |
| | |
| | | .news-title { display: block; color: #252525; font-size: 30rpx; font-weight: 700; line-height: 41rpx; } |
| | | .news-body { display: flex; height: 174rpx; margin-top: 13rpx; flex-direction: row; } |
| | | .news-copy { display: flex; flex: 1; min-width: 0; padding-right: 18rpx; flex-direction: column; justify-content: space-between; } |
| | | .news-summary { display: block; height: 105rpx; overflow: hidden; color: #8E9299; font-size: 23rpx; line-height: 34rpx; } |
| | | .news-meta { display: flex; height: 42rpx; flex-direction: row; align-items: center; } |
| | | .news-tag { padding: 5rpx 9rpx; border-radius: 6rpx; background-color: #FFF8EA; color: #F1A32D; font-size: 19rpx; } |
| | | .news-time { margin-left: 13rpx; color: #A5A8AE; font-size: 20rpx; } |
| | | .news-thumb { width: 236rpx; height: 168rpx; border-radius: 7rpx; flex-shrink: 0; } |
| | | /* 摘要固定 3 行(3×34rpx=102rpx),超出用省略号,不再从文字中间截断。 */ |
| | | .news-summary { display: -webkit-box; height: 102rpx; overflow: hidden; -webkit-line-clamp: 3; -webkit-box-orient: vertical; color: #8E9299; font-size: 23rpx; line-height: 34rpx; } |
| | | .news-meta { display: flex; height: 42rpx; min-width: 0; flex-direction: row; align-items: center; } |
| | | /* 来源/摘要这行可能很长:按内容宽度排,放不下时收缩并省略,不去挤右侧时间。 |
| | | flex-shrink 必须显式写 1:uni-app x 的全局样式给 view/text 默认 flex-shrink: 0, |
| | | 不覆盖的话标签不会收缩,只会顶出卡片、把时间挤没(省略号也不会出现)。 */ |
| | | .news-tag { display: block; flex: 0 1 auto; flex-grow: 0; flex-shrink: 1; flex-basis: auto; min-width: 0; padding: 5rpx 9rpx; border-radius: 6rpx; background-color: #FFF8EA; color: #F1A32D; font-size: 19rpx; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } |
| | | .news-time { margin-left: 13rpx; color: #A5A8AE; font-size: 20rpx; flex-shrink: 0; } |
| | | /* 灰底占位:后台没配封面时不会出现破图或那张默认会议厅图。 */ |
| | | .news-thumb { width: 236rpx; height: 168rpx; border-radius: 7rpx; background-color: #EEEEEE; flex-shrink: 0; } |
| | | .bottom-space { height: calc(28rpx + env(safe-area-inset-bottom)); } |
| | | </style> |