feat(uniapp): restore home page design

This commit is contained in:
hajimi
2026-07-31 18:12:46 +08:00
parent fdf8022662
commit deb6ce0e97
5 changed files with 667 additions and 258 deletions
@@ -1,7 +1,18 @@
<template>
<view class="ai-entry" :class="[`ai-entry--${size}`, `ai-entry--align-${align}`]" @tap="goAssistant">
<text class="ai-entry__line ai-entry__line--top">AI</text>
<text class="ai-entry__line ai-entry__line--bottom">助手</text>
<view class="ai-entry" :class="[`ai-entry--${size}`, `ai-entry--align-${align}`, `ai-entry--${variant}`]"
@tap="goAssistant">
<template v-if="variant === 'orb'">
<view class="ai-entry__orb">
<view class="ai-entry__face">
<view class="ai-entry__eye" />
<view class="ai-entry__eye" />
</view>
</view>
</template>
<template v-else>
<text class="ai-entry__line ai-entry__line--top">AI</text>
<text class="ai-entry__line ai-entry__line--bottom">助手</text>
</template>
</view>
</template>
@@ -9,9 +20,11 @@
withDefaults(defineProps<{
align?: 'left' | 'right'
size?: 'compact' | 'regular' | 'large'
variant?: 'badge' | 'orb'
}>(), {
align: 'left',
size: 'regular'
size: 'regular',
variant: 'badge'
})
const goAssistant = () => {
@@ -60,6 +73,45 @@ const goAssistant = () => {
--entry-bottom-font: 14rpx;
}
&--orb {
--entry-width: 72rpx;
--entry-height: 72rpx;
--entry-radius: 50%;
background: #f7f9ff;
box-shadow: none;
}
&__orb {
width: 54rpx;
height: 54rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: radial-gradient(circle at 34% 28%, #8ec4ff 0%, #387df6 48%, #1557dc 100%);
box-shadow: 0 5rpx 10rpx rgba(20, 104, 245, 0.2);
}
&__face {
width: 37rpx;
height: 25rpx;
display: flex;
align-items: center;
justify-content: space-around;
padding: 0 6rpx;
border-radius: 14rpx;
background: rgba(255, 255, 255, 0.94);
box-sizing: border-box;
}
&__eye {
width: 6rpx;
height: 11rpx;
border-radius: 999rpx;
background: #1f61e8;
}
&__line {
color: #fff;
font-weight: 700;
+89 -63
View File
@@ -2,25 +2,25 @@
<navigator :url="`/pages/news_detail/news_detail?id=${item.id}`" class="feed-card">
<view class="feed-card__body">
<view class="feed-card__info">
<view class="feed-card__title">{{ item.title }}</view>
<view v-if="descText" class="feed-card__desc">{{ descText }}</view>
<text class="feed-card__title">{{ item.title }}</text>
<text v-if="descText" class="feed-card__desc">{{ descText }}</text>
<view class="feed-card__footer">
<text class="feed-card__author">{{ authorText }}</text>
<view class="feed-card__footer-right">
<text class="feed-card__meta">{{ item.comment_count || item.click || 0 }}阅读</text>
<text class="feed-card__meta">{{ formatTime(item.create_time) }}</text>
<text class="feed-card__time">{{ formatRelativeTime(item.create_time) }}</text>
<view class="feed-card__comments">
<view class="feed-card__comment-icon" />
<text>{{ item.comment_count || item.click || 0 }}</text>
</view>
</view>
</view>
<UImage v-if="item.image" :src="item.image" width="220" height="140" border-radius="8" mode="aspectFill"
class="feed-card__img" />
<image v-if="item.image && !imageFailed" :src="item.image" mode="aspectFill" class="feed-card__img"
referrerpolicy="no-referrer" @error="imageFailed = true" />
</view>
</navigator>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import UImage from '@/uni_modules/vk-uview-ui/components/u-image/u-image.vue'
import { computed, ref } from 'vue'
const props = withDefaults(
defineProps<{
@@ -31,119 +31,145 @@ const props = withDefaults(
}
)
const imageFailed = ref(false)
const stripHtml = (html: string) => {
if (!html) return ''
return html.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim()
return html.replace(/<[^>]+>/g, ' ').replace(/&nbsp;/g, ' ').replace(/\s+/g, ' ').trim()
}
const descText = computed(() => {
if (props.item.desc) return props.item.desc
if (props.item.content) return stripHtml(props.item.content).substring(0, 100)
return ''
return stripHtml(props.item.desc || props.item.content || '').slice(0, 72)
})
const authorText = computed(() => {
const rawAuthor = String(props.item.author || '')
const cleanedAuthor = rawAuthor.replace(/@懂球帝/g, '').trim()
return cleanedAuthor || '体育小编'
return cleanedAuthor || 'SB 体育头条'
})
const formatTime = (t: string) => {
if (!t) return ''
const d = new Date(t.replace(/-/g, '/'))
if (isNaN(d.getTime())) return t
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
const h = String(d.getHours()).padStart(2, '0')
const min = String(d.getMinutes()).padStart(2, '0')
return `${m}-${day} ${h}:${min}`
const formatRelativeTime = (time: string) => {
if (!time) return ''
const date = new Date(String(time).replace(/-/g, '/'))
const delta = Date.now() - date.getTime()
if (Number.isNaN(date.getTime()) || delta < 0) return String(time)
const minute = 60 * 1000
if (delta < minute) return '刚刚'
if (delta < 60 * minute) return `${Math.floor(delta / minute)}分钟前`
if (delta < 24 * 60 * minute) return `${Math.floor(delta / (60 * minute))}小时前`
if (delta < 7 * 24 * 60 * minute) return `${Math.floor(delta / (24 * 60 * minute))}天前`
return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
}
</script>
<style lang="scss" scoped>
.feed-card {
display: block;
padding: 20rpx 20rpx;
padding: 22rpx 20rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
border-top: 1rpx solid #edf0f5;
&:first-child {
border-top: 0;
}
&__body {
display: flex;
align-items: stretch;
gap: 20rpx;
align-items: flex-start;
min-height: 144rpx;
}
&__info {
min-width: 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
min-width: 0;
}
&__title {
font-size: 28rpx;
font-weight: 600;
color: #111;
line-height: 1.45;
white-space: nowrap;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
color: #151b26;
font-size: 28rpx;
font-weight: 700;
line-height: 1.4;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
&__desc {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
white-space: nowrap;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 8rpx;
color: #7c8798;
font-size: 24rpx;
line-height: 1.42;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
&__footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 18rpx;
min-width: 0;
margin-top: auto;
padding-top: 12rpx;
gap: 16rpx;
white-space: nowrap;
color: #909aaa;
font-size: 22rpx;
line-height: 1.2;
}
&__author,
&__time {
overflow: hidden;
min-width: 0;
white-space: nowrap;
text-overflow: ellipsis;
}
&__author {
flex: 1;
font-size: 24rpx;
color: #999;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
max-width: 156rpx;
}
&__footer-right {
&__comments {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
gap: 12rpx;
margin-left: auto;
flex-shrink: 0;
gap: 6rpx;
min-width: 0;
margin-left: auto;
white-space: nowrap;
}
&__meta {
font-size: 24rpx;
color: #bbb;
white-space: nowrap;
flex-shrink: 0;
&__comment-icon {
position: relative;
width: 24rpx;
height: 19rpx;
box-sizing: border-box;
border: 2rpx solid #8e99a9;
border-radius: 10rpx;
&::after {
content: '';
position: absolute;
right: 1rpx;
bottom: -5rpx;
width: 7rpx;
height: 7rpx;
box-sizing: border-box;
border-right: 2rpx solid #8e99a9;
border-bottom: 2rpx solid #8e99a9;
background: #fff;
transform: rotate(28deg);
}
}
&__img {
width: 216rpx;
height: 144rpx;
flex-shrink: 0;
border-radius: 12rpx;
background: #eef1f5;
}
}
</style>