溢出(两处,同一类成因):
- vivid 时间线:短文正文用的是 .note .body 而不是 .prose,所以 c6eb0bb 给
.prose 加的 overflow-wrap:anywhere 没覆盖到它,正文里的裸链接把列撑到 552px
(视口 516)。补上 anywhere 而非 break-word——要参与 min-content 计算才压得住
- classic 时间线:.shell 的页边距在 640px 断点从 20px 收到 12px,而 .row-feed 的
负出血要到 520px 才跟着收到 -12px,521~640 这段出血比父容器内边距多 4px,
整页横向溢出。把出血断点对齐到 640px(桌面视觉不动)
- LinkCard 的 .lc-site 同样补 anywhere:主机名是最典型的不可断行长 token
vivid 页脚:源码仓库入口原先被 concat 进社交数组,和站主的 GitHub/X 排成同一行,
看起来像「又一个社交账号」。改成社交排只放账号(同 classic RightRail 的过滤),
源码图标与版权单独成 .foot-meta 压在页脚最底;样式写在 .foot a 之后,靠同特指度
的后者压掉页脚链接默认的强调色
128 lines
3.0 KiB
Vue
128 lines
3.0 KiB
Vue
<script setup>
|
||
import { ref } from 'vue'
|
||
|
||
// 链接预览卡片:正文里第一个外链在发布时由后端抓好(posts.link_card),
|
||
// 这里只负责展示。没有可展示内容时父组件根本不会渲染它。
|
||
//
|
||
// 用 div[role=link] 而不是 <a>:vivid 的整条短文包在 RouterLink(一个 <a>)里,
|
||
// 再嵌一个 <a> 是非法 HTML。也因此点击必须 preventDefault——
|
||
// 只 stop 的话祖先 <a> 的默认激活行为照样会把点击变成跳转。
|
||
const props = defineProps({
|
||
card: { type: Object, required: true }
|
||
})
|
||
|
||
const imgBroken = ref(false)
|
||
|
||
function open() {
|
||
window.open(props.card.url, '_blank', 'noopener,noreferrer')
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
class="link-card"
|
||
role="link"
|
||
tabindex="0"
|
||
:title="card.url"
|
||
@click.prevent.stop="open"
|
||
@keydown.enter.prevent="open"
|
||
>
|
||
<div class="lc-text">
|
||
<span v-if="card.site" class="lc-site">{{ card.site }}</span>
|
||
<span v-if="card.title" class="lc-title">{{ card.title }}</span>
|
||
<span v-if="card.desc" class="lc-desc">{{ card.desc }}</span>
|
||
</div>
|
||
<img
|
||
v-if="card.image && !imgBroken"
|
||
class="lc-img"
|
||
:src="card.image"
|
||
alt=""
|
||
loading="lazy"
|
||
decoding="async"
|
||
referrerpolicy="no-referrer"
|
||
@error="imgBroken = true"
|
||
/>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* 颜色一律写成「vivid 变量优先、classic 变量兜底」:同一份组件在两套 UI 里
|
||
各归各的调色板,不必再维护第二份样式表。 */
|
||
.link-card {
|
||
display: flex;
|
||
align-items: stretch;
|
||
gap: 12px;
|
||
margin-top: 10px;
|
||
padding: 10px 12px;
|
||
border: 1px solid var(--v-line, var(--line-soft));
|
||
border-radius: var(--v-radius-sm, 10px);
|
||
background: var(--v-surface, var(--card));
|
||
cursor: pointer;
|
||
transition: border-color 0.15s ease, background 0.15s ease;
|
||
}
|
||
|
||
.link-card:hover {
|
||
border-color: var(--v-line-strong, var(--accent-line));
|
||
}
|
||
|
||
.link-card:focus-visible {
|
||
outline: 2px solid var(--v-accent, var(--accent));
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.lc-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 3px;
|
||
min-width: 0;
|
||
flex: 1;
|
||
}
|
||
|
||
.lc-site {
|
||
font-size: 11.5px;
|
||
letter-spacing: 0.04em;
|
||
text-transform: lowercase;
|
||
color: var(--v-muted, var(--faint));
|
||
/* 主机名是最典型的不可断行长 token(子域一长串),不给它 anywhere
|
||
就会把卡片左列顶宽、整行溢出 */
|
||
overflow-wrap: anywhere;
|
||
}
|
||
|
||
.lc-title {
|
||
font-size: 15px;
|
||
line-height: 1.45;
|
||
font-weight: 600;
|
||
color: var(--v-text, var(--ink));
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.lc-desc {
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
color: var(--v-text-soft, var(--muted));
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.lc-img {
|
||
flex: 0 0 auto;
|
||
width: 84px;
|
||
height: 84px;
|
||
object-fit: cover;
|
||
border-radius: var(--v-radius-sm, 8px);
|
||
background: var(--v-surface-2, var(--paper-sunken));
|
||
}
|
||
|
||
@media (max-width: 520px) {
|
||
.lc-img {
|
||
width: 64px;
|
||
height: 64px;
|
||
}
|
||
}
|
||
</style>
|