评论阶段一(前端):评论区 + 登录引导卡 + dev mock,经典头像留白修复
评论区(按已批准的设计文档,阶段一=前端;后端与多用户为阶段二/三)
- classic / vivid 共用同一份 DOM:CommentSection + CommentItem(回复一层、
自引用渲染,回复/编辑框模板只写一份)+ CommentMarkdown(唯一 v-html 出口,
必过 sanitizeHtml)。比计划里的两套组件少一半文件,也避免两边结构漂移
- 数据层:api.js 追加 readerApi(me/providers/logout/comments/thread/create/
edit/remove);src/reader.js 读者身份 reactive 单例(仿 site.js,
挂 one:unauthorized 清 user);composables/useComments 按文章实例化
(刻意不做单例,换文章必须重置),乐观插入按当前排序落位
- 交互:排序(默认/最新/最早)、发表、回复(一层 + 加载更多回复)、
10 分钟内编辑、软删、分页(显示更多 N 条)、骨架屏、空态、错误态、审核中徽章
- 登录引导卡:三种 provider(GitHub/Google/Telegram);redirect 类整页跳转,
widget 类(Telegram)点击时内联官方脚本(无跳转、无回调,官方按钮落位)
- 样式:styles.css 追加 .cm-* 基础段(经典 token,vivid 靠别名继承同一份规则);
ui/yohaku/comments.css 只写 vivid 差异(全前缀、特指度盖过基础版),
自己的窄屏/触屏规则写在文件内,不塞 mobile.css
- main.ts:comments.css 插在 pages 之后 mobile 之前(层叠契约不变);
dev mock 用 ?mock 门控动态 import,await 装完再挂载,否则首屏请求会打到真接口
修三处真问题
- vivid 的 a{color:inherit} 重置(特指度 0,1,2)把作者链接名拉回 body 色,
暗色下暗底暗字 —— 用 a.cm-name-a 压回
- classic 文章页整页掉进 236px 左列:PostView import 了 LeftNav/RightRail
却没渲染,补上(与 HomeView 三栏一致),文章页回到 640px
- classic 信息流头像贴死列边框:.row-feed 的负 margin 正好抵光左右内边距,
改 padding 14/16 -> 14/30(30 = 16 出血 + 14 留白,hover 底色与分隔线仍铺满整列)
mock 假数据
- 8 位作者(三种登录方式、不同色相头像、两个走首字母兜底)、
默认一屏 8 条各测一个排版点(代码块/引用/列表/已删除/审核中/已编辑/站长徽章/
加载更多回复),长列表态 26 条测分页;右下角浮层切 9 个状态(?mock&state=),
?ui=classic 可强制 classic 壳(平时渲染不出来,没法做探针)
验证(headless Chromium + CDP 真实时间截图;virtual-time 对含动态 import 的页面
截图时机不稳,会截出全白,已换 CDP 方案)
- 头部计数 13px/tabular-nums、.seg 3 选 1;头像 50% 圆、36/24px;
气泡圆角 3,20,20,20(缺角);回复缩进 36px 且无二层嵌套;360 宽无横向溢出
- 暗色:气泡/输入框/名字全部适配(量的时候被 a 的 150ms 颜色 transition 骗过,
t0 与 t400 两次测量值不同才看清,稳态正确)
- 产物:CSS 顺序 styles→pages→comments→mobile;49 条评论规则全带
html[data-ui=vivid] 前缀;grep __COMMENT_MOCK__ 为空、无 mock chunk —— 生产零 mock
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { sanitizeHtml } from '../../utils'
|
||||
import CommentSection from '../../components/comments/CommentSection.vue'
|
||||
|
||||
// 余白文章页。对应设计稿 viewPost() 的三栏「纸」:
|
||||
// 左 side-l : 时间线(当前篇前后各两篇,标签来自位置)
|
||||
@@ -171,6 +172,9 @@ function onTocClick(e) {
|
||||
<RouterLink v-for="t in props.post.tags || []" :key="t" class="tg" to="/tags">{{ t }}</RouterLink>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- 评论在 .paper 外面:评论接口挂了也不该把文章一起带下去 -->
|
||||
<CommentSection :post-id="props.post.id" />
|
||||
</main>
|
||||
|
||||
<aside class="side-r">
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
/* ==========================================================================
|
||||
评论区:vivid(余白)的差异部分
|
||||
--------------------------------------------------------------------------
|
||||
基础样式在 styles.css 的 .cm-*(classic 和 vivid 共用同一份 DOM)。
|
||||
这里只写 vivid 和 classic 长得不一样的地方 —— 全部带 html[data-ui='vivid']
|
||||
前缀,特指度比基础版高,天然盖住它。
|
||||
自己的移动端覆盖也写在下面(不要挪进 mobile.css:那是「靠最后导入赢」,
|
||||
语义上不该承担某个具体组件的窄屏规则)。
|
||||
========================================================================== */
|
||||
|
||||
/* 和 .paper 的正文列左缘对齐(.paper 的横向内边距是 24px)。
|
||||
桌面不给上分隔线:文章末尾已经有一条 .end 的虚线,再画一条就重了。 */
|
||||
html[data-ui='vivid'] .cm-sec {margin-top: 8px;padding: 24px 24px 0;border-top: 0;}
|
||||
|
||||
html[data-ui='vivid'] .cm-count {font-size: 13px;color: var(--muted);letter-spacing: .01em;}
|
||||
/* .cm-sort 在 vivid 里同时带 .seg 类,pill 外观由 home.css 的 .seg 规则给 */
|
||||
|
||||
/* 未登录:一张安静的纸片,用和输入框同一套「场地」观感 */
|
||||
html[data-ui='vivid'] .cm-gate {padding: 26px 22px;border-radius: var(--radius-lg);
|
||||
border-color: var(--line);background: var(--card);box-shadow: var(--shadow);}
|
||||
html[data-ui='vivid'] .cm-gate-skel {box-shadow: none;border-radius: var(--radius);}
|
||||
html[data-ui='vivid'] .cm-provider {width: 40px;height: 40px;border-color: var(--line);
|
||||
background: var(--card);color: var(--soft);
|
||||
transition: border-color .3s var(--spring),color .3s var(--spring),transform .5s var(--spring);}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
html[data-ui='vivid'] .cm-provider:hover {border-color: var(--accent);color: var(--accent);
|
||||
transform: translateY(-2px);}
|
||||
}
|
||||
|
||||
/* 输入框:vivid 的 .search / .seg 是同一套「无边框 + 聚焦才亮」的思路 */
|
||||
html[data-ui='vivid'] .cm-field {border-radius: var(--radius);background: var(--card);
|
||||
box-shadow: var(--shadow);
|
||||
transition: border-color .3s var(--ease),box-shadow .3s var(--ease);}
|
||||
html[data-ui='vivid'] .cm-field:focus-within {border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px var(--accent-soft);}
|
||||
html[data-ui='vivid'] .cm-input {font-size: 14.5px;line-height: 1.8;}
|
||||
html[data-ui='vivid'] .cm-bar {border-top-color: var(--line);}
|
||||
html[data-ui='vivid'] .cm-send {background: var(--accent);color: #fff;
|
||||
transition: transform .5s var(--spring),box-shadow .3s;}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
html[data-ui='vivid'] .cm-send:not(:disabled):hover {transform: scale(1.04);
|
||||
box-shadow: 0 4px 12px var(--accent-glow);}
|
||||
}
|
||||
html[data-ui='vivid'] .cm-send:active {transform: scale(.97);}
|
||||
|
||||
/* 列表 */
|
||||
html[data-ui='vivid'] .cm-list {gap: 20px;}
|
||||
html[data-ui='vivid'] .cm-av {width: 36px;height: 36px;background: var(--accent-soft);color: var(--accent);
|
||||
box-shadow: 0 0 0 2px var(--paper);}
|
||||
/* 站长的头像 ring 用强调色,一眼能认出来 */
|
||||
html[data-ui='vivid'] .cm-av.is-owner {box-shadow: 0 0 0 2px var(--paper),0 0 0 3.5px var(--accent-soft);}
|
||||
html[data-ui='vivid'] .cm-pv {box-shadow: 0 0 0 1.5px var(--paper);color: var(--muted);}
|
||||
html[data-ui='vivid'] .cm-name-a {font-size: 13.5px;color: var(--text);letter-spacing: .01em;}
|
||||
/* tokens.css 的 a 重置(html[data-ui=vivid] a,特指度 0,1,2)会把链接名拉回
|
||||
inherit —— 继承到的是 body 的颜色,暗色下正好是深底深字。得在这里压回去。 */
|
||||
html[data-ui='vivid'] a.cm-name-a {color: var(--text);}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
html[data-ui='vivid'] a.cm-name-a:hover {color: var(--accent);}
|
||||
}
|
||||
html[data-ui='vivid'] .cm-owner {background: var(--accent-soft);color: var(--accent);}
|
||||
html[data-ui='vivid'] .cm-time {letter-spacing: .02em;}
|
||||
html[data-ui='vivid'] .cm-pending {background: color-mix(in srgb,var(--ink) 6%,transparent);}
|
||||
|
||||
/* 气泡:vivid 里用「场地」那套(细边 + 顶上一点渐变),缺角保留 */
|
||||
html[data-ui='vivid'] .cm-bubble {padding: 9px 14px;border-radius: var(--radius-lg);
|
||||
border-top-left-radius: 3px;border-color: var(--line);background: var(--card);
|
||||
color: var(--text);
|
||||
background-image: linear-gradient(180deg,var(--accent-soft) 0,transparent 26%);}
|
||||
html[data-ui='vivid'] .cm-item.is-reply .cm-bubble {border-top-left-radius: var(--radius-lg);}
|
||||
html[data-ui='vivid'] .cm-md {font-size: 14.5px;line-height: 1.75;}
|
||||
html[data-ui='vivid'] .cm-md a {text-decoration: underline;
|
||||
text-decoration-color: color-mix(in srgb,var(--accent) 40%,transparent);
|
||||
text-underline-offset: 2px;}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
html[data-ui='vivid'] .cm-md a:hover {text-decoration-color: var(--accent);}
|
||||
}
|
||||
html[data-ui='vivid'] .cm-md code {background: var(--accent-soft);color: var(--accent);}
|
||||
html[data-ui='vivid'] .cm-md pre {background: color-mix(in srgb,var(--ink) 92%,var(--accent));
|
||||
color: #e8e8f0;box-shadow: var(--shadow-lg);}
|
||||
html[data-ui='vivid'] .cm-md pre code {background: none;color: inherit;}
|
||||
html[data-ui='vivid'] .cm-md blockquote {border-left-color: var(--accent);
|
||||
font-family: var(--serif);font-style: italic;}
|
||||
|
||||
/* 操作钮 */
|
||||
html[data-ui='vivid'] .cm-act {border-color: var(--line);background: var(--card);color: var(--muted);
|
||||
transition: border-color .3s var(--spring),color .3s var(--spring),transform .5s var(--spring);}
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
html[data-ui='vivid'] .cm-act:hover {border-color: var(--accent);color: var(--accent);
|
||||
transform: translateY(-1px);}
|
||||
}
|
||||
|
||||
/* 回复 */
|
||||
html[data-ui='vivid'] .cm-more-r {border-color: var(--line);color: var(--muted);}
|
||||
html[data-ui='vivid'] .cm-empty .chip {font-size: 13px;padding: 9px 22px;}
|
||||
|
||||
/* --------------------------------------------------------------------------
|
||||
手机:头像 24px(桌面 36px),触屏的可点目标 44px、输入框 16px(防聚焦缩放)
|
||||
-------------------------------------------------------------------------- */
|
||||
@media (max-width:640px) {
|
||||
html[data-ui='vivid'] .cm-sec {margin-top: 4px;padding: 20px 20px 0;}
|
||||
html[data-ui='vivid'] .cm-av,
|
||||
html[data-ui='vivid'] .cm-av-me {width: 24px;height: 24px;font-size: 11px;}
|
||||
html[data-ui='vivid'] .cm-replies {padding-left: 20px;}
|
||||
html[data-ui='vivid'] .cm-more-r {margin-left: 20px;}
|
||||
html[data-ui='vivid'] .cm-bubble {max-width: calc(100% - 1rem);}
|
||||
html[data-ui='vivid'] .cm-provider {width: 44px;height: 44px;}
|
||||
html[data-ui='vivid'] .cm-act {width: 32px;height: 32px;font-size: 13px;}
|
||||
html[data-ui='vivid'] .cm-sort button {min-height: 44px;}
|
||||
html[data-ui='vivid'] .cm-input {font-size: 16px;}
|
||||
}
|
||||
/* 触屏(可能是平板/桌面触屏,不限于窄屏):可点目标给足 */
|
||||
@media (pointer: coarse) {
|
||||
html[data-ui='vivid'] .cm-provider {width: 44px;height: 44px;}
|
||||
html[data-ui='vivid'] .cm-act {width: 32px;height: 32px;}
|
||||
html[data-ui='vivid'] .cm-more-r {padding: 10px 16px;}
|
||||
html[data-ui='vivid'] .cm-link {padding: 8px 2px;}
|
||||
html[data-ui='vivid'] .cm-input {font-size: 16px;}
|
||||
}
|
||||
Reference in New Issue
Block a user