评论阶段一(前端):评论区 + 登录引导卡 + 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:
@@ -1092,3 +1092,154 @@ a,
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
评论区
|
||||
--------------------------------------------------------------------------
|
||||
classic 和 vivid 共用同一份 DOM(components/comments/)。
|
||||
这里只写「两边都要」的基础,用经典那套 token —— vivid 把那批名字
|
||||
(--paper/--card/--ink/--accent/--line/--paper-sunken…)都别名到了 --v-*,
|
||||
所以同一份规则在两套 UI 下都成立。
|
||||
vivid 的差异(缺角气泡、渐变底、头像 ring、触屏尺寸)在
|
||||
ui/yohaku/comments.css,那边特指度更高,会盖住这里。
|
||||
========================================================================== */
|
||||
|
||||
/* 头部只放计数 + 排序。刻意不写「评论」二字标题:参考站也是这样,
|
||||
靠计数和排序控件就能认出这是评论区,多一行标题只是噪音。 */
|
||||
.cm-sec {margin-top: 28px;padding: 22px 26px 0;border-top: 1px solid var(--line);}
|
||||
.cm-head {display: flex;align-items: center;justify-content: space-between;
|
||||
gap: 12px;flex-wrap: wrap;margin-bottom: 20px;}
|
||||
.cm-count {font-size: 13px;font-weight: 600;color: var(--muted);
|
||||
font-variant-numeric: tabular-nums;}
|
||||
/* classic 下是一排文字钮;vivid 里这个元素还带 .seg,会被 pill 化 */
|
||||
.cm-sort {display: inline-flex;align-items: center;gap: 2px;}
|
||||
.cm-sort button {font-size: 13px;font-weight: 600;color: var(--muted);
|
||||
padding: 5px 10px;border: 0;border-radius: 999px;background: none;cursor: pointer;}
|
||||
.cm-sort button.on {color: var(--accent);background: var(--accent-soft);}
|
||||
|
||||
/* 未登录:登录引导卡 */
|
||||
.cm-gate {padding: 22px;border: 1px solid var(--line);border-radius: 12px;
|
||||
background: var(--card);text-align: center;}
|
||||
.cm-gate-skel {height: 104px;background: var(--paper-sunken);border-style: dashed;}
|
||||
.cm-gate-t {margin: 0 0 14px;font-size: 13px;color: var(--muted);}
|
||||
.cm-gate-hint {margin: 0;font-size: 13px;color: var(--muted);}
|
||||
.cm-providers {display: flex;justify-content: center;gap: 12px;
|
||||
margin: 0;padding: 0;list-style: none;}
|
||||
.cm-provider {display: grid;place-items: center;width: 40px;height: 40px;
|
||||
border: 1px solid var(--line);border-radius: 50%;background: var(--card);color: var(--ink);
|
||||
cursor: pointer;transition: border-color .2s,color .2s;}
|
||||
.cm-provider:hover {border-color: var(--accent);color: var(--accent);}
|
||||
.cm-brand {width: 18px;height: 18px;fill: currentColor;}
|
||||
.cm-brand-t {font-size: 15px;font-weight: 700;}
|
||||
.cm-tg {margin-top: 12px;}
|
||||
|
||||
/* 输入区 */
|
||||
.cm-compose {display: flex;gap: 12px;align-items: flex-start;}
|
||||
.cm-av-me {width: 36px;height: 36px;}
|
||||
.cm-field {flex: 1;min-width: 0;overflow: hidden;
|
||||
border: 1px solid var(--line);border-radius: 12px;background: var(--card);}
|
||||
.cm-field:focus-within {border-color: var(--accent);}
|
||||
.cm-field-inline {margin-top: 10px;}
|
||||
.cm-field-tip {margin: 0;padding: 10px 12px 0;font-size: 13px;color: var(--muted);}
|
||||
.cm-input {display: block;width: 100%;min-height: 72px;padding: 10px 12px;
|
||||
border: 0;background: none;resize: vertical;
|
||||
font: inherit;font-size: 14px;line-height: 1.7;color: var(--ink);}
|
||||
.cm-input:focus {outline: none;}
|
||||
.cm-bar {display: flex;align-items: center;justify-content: space-between;gap: 10px;
|
||||
padding: 6px 10px;border-top: 1px solid var(--line-soft);
|
||||
font-size: 13px;color: var(--muted);}
|
||||
.cm-bar-l {min-width: 0;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;}
|
||||
.cm-bar-r {display: flex;flex: none;align-items: center;gap: 10px;}
|
||||
.cm-len {font-variant-numeric: tabular-nums;}
|
||||
.cm-len.over {color: #a64953;}
|
||||
.cm-link {padding: 0;border: 0;background: none;color: var(--muted);
|
||||
font-size: 13px;cursor: pointer;}
|
||||
.cm-link:hover {color: var(--accent);}
|
||||
.cm-send {padding: 5px 14px;border: 0;border-radius: 999px;cursor: pointer;
|
||||
background: var(--accent);color: #fff;font-size: 13px;font-weight: 600;}
|
||||
.cm-send:disabled {opacity: .45;cursor: default;}
|
||||
.cm-err {margin: 14px 0 0;font-size: 13px;color: #a64953;}
|
||||
|
||||
/* 列表 */
|
||||
.cm-list {display: flex;flex-direction: column;gap: 18px;
|
||||
margin: 22px 0 0;padding: 0;list-style: none;}
|
||||
.cm-item {display: flex;gap: 12px;align-items: flex-start;}
|
||||
.cm-av {position: relative;display: grid;place-items: center;flex: none;
|
||||
width: 36px;height: 36px;border-radius: 50%;
|
||||
background: var(--paper-sunken);color: var(--muted);
|
||||
font-size: 14px;font-weight: 700;
|
||||
box-shadow: 0 0 0 2px var(--paper);}
|
||||
.cm-av img {display: block;width: 100%;height: 100%;border-radius: 50%;object-fit: cover;}
|
||||
.cm-av.is-owner {box-shadow: 0 0 0 2px var(--accent-soft);}
|
||||
/* 头像角上的登录方式小标 */
|
||||
.cm-pv {position: absolute;right: -2px;bottom: -2px;
|
||||
display: grid;place-items: center;width: 14px;height: 14px;border-radius: 50%;
|
||||
background: var(--card);color: var(--muted);font-style: normal;
|
||||
box-shadow: 0 0 0 1.5px var(--line);}
|
||||
.cm-pv svg {width: 8px;height: 8px;fill: currentColor;}
|
||||
.cm-main {flex: 1;min-width: 0;}
|
||||
.cm-name {display: flex;align-items: center;gap: 8px;flex-wrap: wrap;margin-bottom: 6px;}
|
||||
.cm-name-a {font-size: 13.5px;font-weight: 600;color: var(--ink);}
|
||||
a.cm-name-a:hover {color: var(--accent);}
|
||||
.cm-owner {padding: 1px 6px;border-radius: 999px;background: var(--accent-soft);
|
||||
color: var(--accent);font-size: 11px;font-weight: 600;}
|
||||
.cm-time {font-size: 11.5px;color: var(--muted);font-variant-numeric: tabular-nums;}
|
||||
.cm-pending {padding: 1px 6px;border-radius: 999px;background: var(--paper-sunken);
|
||||
color: var(--muted);font-size: 11px;font-weight: 600;}
|
||||
.cm-edited-tip {font-size: 11px;color: var(--muted);opacity: .75;}
|
||||
|
||||
/* 气泡:左上角不收圆(缺口朝头像),是这套评论的形状特征 */
|
||||
.cm-bubble {position: relative;display: inline-block;
|
||||
max-width: calc(100% - 3rem);padding: 8px 12px;
|
||||
border: 1px solid var(--line);border-radius: 12px;border-top-left-radius: 3px;
|
||||
background: var(--card);color: var(--ink);}
|
||||
.cm-item.is-reply .cm-bubble {border-top-left-radius: 12px;}
|
||||
.cm-item.is-deleted .cm-bubble {background: none;border-style: dashed;}
|
||||
.cm-gone {font-size: 13px;color: var(--muted);font-style: italic;}
|
||||
|
||||
/* 正文(走 CommentMarkdown,后端已渲染 + 前端 DOMPurify 再洗一遍) */
|
||||
.cm-md {font-size: 14px;line-height: 1.65;}
|
||||
.cm-md > :first-child {margin-top: 0;}
|
||||
.cm-md > :last-child {margin-bottom: 0;}
|
||||
.cm-md p {margin: 0 0 .6em;}
|
||||
.cm-md a {color: var(--accent);text-underline-offset: 2px;}
|
||||
.cm-md code {padding: 1px 5px;border-radius: 5px;font-family: var(--mono);font-size: .92em;
|
||||
background: color-mix(in srgb, var(--ink) 6%, transparent);}
|
||||
.cm-md pre {padding: 10px 12px;border-radius: 8px;overflow-x: auto;font-size: 12.5px;
|
||||
background: var(--paper-sunken);}
|
||||
.cm-md pre code {padding: 0;background: none;}
|
||||
.cm-md ul, .cm-md ol {margin: .5em 0;padding-left: 1.4em;}
|
||||
.cm-md blockquote {margin: .6em 0;padding-left: 10px;border-left: 2px solid var(--line);
|
||||
color: var(--muted);}
|
||||
.cm-md img {max-width: 100%;border-radius: 6px;}
|
||||
|
||||
/* 编辑 / 删除 / 回复:hover 或键盘聚焦才出现,触屏直接常显 */
|
||||
.cm-acts {position: absolute;right: 0;bottom: -10px;display: flex;gap: 4px;
|
||||
opacity: 0;transition: opacity .2s;}
|
||||
.cm-item:hover .cm-acts, .cm-item:focus-within .cm-acts {opacity: 1;}
|
||||
@media (hover: none) {
|
||||
.cm-acts {opacity: 1;}
|
||||
}
|
||||
.cm-act {display: grid;place-items: center;width: 24px;height: 24px;
|
||||
border: 1px solid var(--line);border-radius: 50%;background: var(--card);
|
||||
color: var(--muted);font-size: 12px;cursor: pointer;}
|
||||
.cm-act:hover {border-color: var(--accent);color: var(--accent);}
|
||||
|
||||
/* 回复只有一层,缩进 36px */
|
||||
.cm-replies {display: flex;flex-direction: column;gap: 14px;
|
||||
margin: 14px 0 0;padding: 0 0 0 36px;list-style: none;}
|
||||
.cm-replies .cm-av {width: 28px;height: 28px;font-size: 12px;}
|
||||
.cm-more-r {margin: 10px 0 0 36px;padding: 4px 12px;border: 1px solid var(--line);
|
||||
border-radius: 999px;background: none;color: var(--muted);font-size: 12px;cursor: pointer;}
|
||||
.cm-more-r:hover {border-color: var(--accent);color: var(--accent);}
|
||||
|
||||
/* 分页 / 空态 */
|
||||
.cm-more {display: flex;justify-content: center;margin-top: 22px;}
|
||||
.cm-empty {margin-top: 22px;}
|
||||
|
||||
/* 骨架 */
|
||||
.cm-skel-row {display: flex;gap: 12px;align-items: flex-start;}
|
||||
.cm-skel-av {flex: none;width: 36px;height: 36px;border-radius: 50%;background: var(--paper-sunken);}
|
||||
.cm-skel-lines {display: flex;flex: 1;flex-direction: column;gap: 8px;}
|
||||
.cm-skel-lines i {display: block;height: 12px;border-radius: 6px;background: var(--paper-sunken);}
|
||||
.cm-skel-lines i:last-child {width: 55%;}
|
||||
|
||||
Reference in New Issue
Block a user