v24 第 15 批:活动 / 旅程两级语义(术语 · 派生时间城市 · 表单三项 · 题图可点)
用户原话四条,逐条落地:
1. 「旅程模块,按钮是记一次活动,而不是记一趟旅程。旅程是活动里面的概念。」
→ 顶层一律改称**活动**:按钮「记一次活动」、页标题「活动」、`N 次` / `N 段旅程`;
活动详情三组改叫「计划的旅程 / 走过的旅程 / 没成行的」;
节点表单「这段旅程叫什么」「阶段」;flash 文案 17 处同步。
(导航条仍叫「旅程」——用户就是这么称呼这个模块,列表页加副标题说清两级关系。)
2. 「活动不需要开始和结束时间,是根据活动中的旅程,最早的旅程开始时间,和最晚的
旅程结束时间,计算出来的。目的地也不需要…去的城市,也是根据旅程,得到的。」
→ 活动**不再存**这三样,全部**读时算出来**:
· Trip::derived() 最早开始 / 最晚结束 / 城市去重
· Trip::cities() 按最早出现时间排(先后顺序本身是信息,不是字母序)
· Trip::attachDerived() 列表页批量补(避免 N+1)
· Trip::activitySpan() 没有旅程时说「还没有旅程」,不用「日期待补」
create/update 不再写 events 的起止与地点(老值保留不动,页面不读)。
列表排序/搜索、首页「最近去了」改走子查询。
3. 「题图那里,没有办法上传图片,点击哪里都没反应。」——**真 bug**
「点图 = 开文件框」那句 JS 只写在 people/form.php 的内页脚本里(头像专用);
第 14 批加的旅程题图与图书封面照抄了同一套 DOM 却没有对应的 JS,
于是图块有 cursor:pointer 与 hover 高亮(看着能点),点下去毫无反应。
→ 做成声明式通用绑定:图上写 data-pick-input="#fileInputId",
app.js 统一接管(一个页面多个也各绑各的),并删掉旧的那份专用实现。
三处(头像 / 题图 / 图书封面)全挂上。
4. 「添加活动的页面,顺序是这样的:添加标题,上传并裁剪题图,然后一段简介,就可以了。」
→ 主区就这三项,顺序照原话;时间/城市不做输入项(只留一句说明它们从哪来,
不说清用户会满页找"怎么没地方填时间")。
备注 / 公开 / 同行的人收进「更多设置」折叠区——功能一个没丢。
顺带修掉两处会炸的实现问题:
· ORDER BY 引用聚合别名(first_at)在 MariaDB/MySQL 报 1247,且只在运行时炸、
php -l 看不出 → 改成 ORDER BY MIN(starts_at),否则整个旅程模块 Fatal。
· e2e 里"详情页显示了派生时间"原本写成整页查找,下面每段旅程自己也渲染日期,
于是把派生值塞成 null 也照样绿(借光假绿)→ 判据收进页头那一段。
验证:e2e 845/0(+15 条本批断言)· 升级 134/0 · 桌面几何 428/0 · 移动几何 688/0 ·
交互 92/0 · 题图真机点击 4/0 · 读书窄屏 15/0 · 家谱 34/0 + 10/0 + 反向 3/3 ·
观影按钮 60/0 + 反向 3/3 · 本批反向验证 4/4(题图绑定抽掉→红、派生值不挂→红、
城市退回读 location→红、时间字段加回表单→红)· 部署静态断言新增两组(含"活动不许
再写起止时间/目的地"与"ORDER BY 不许引用聚合别名"的反退化防线)。
部署后服务器自检 19/19,五个关键文件 md5 与线上逐一核对一致。
This commit is contained in:
+254
-7
@@ -2507,6 +2507,23 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
.kb-title { font-size: 14px; font-weight: 500; color: var(--ink); }
|
||||
.kb-title:hover { color: var(--brand); }
|
||||
.kb-meta { display: flex; flex-wrap: wrap; gap: 4px 8px; font-size: 12px; color: var(--muted); }
|
||||
/* ---- 状态徽标(v24 第 14 批 用户第 2 条:每张卡都有,点一下推进到下一档 ----
|
||||
三档各有颜色:待办灰 / 进行中品牌色 / 已完成绿 —— 不靠位置也能一眼看出做到哪一步。
|
||||
⚠️ .is-static(只读账号)长得一样但不可点:不给可点的错觉。 */
|
||||
.kb-st {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 1px 8px; border-radius: 999px; border: 1px solid transparent;
|
||||
font-size: 11px; line-height: 1.7; white-space: nowrap; cursor: pointer;
|
||||
}
|
||||
.kb-st.is-todo { background: var(--line-2); color: var(--muted); border-color: var(--line); }
|
||||
.kb-st.is-doing { background: rgba(168, 74, 40, .12); color: var(--brand-ink); border-color: rgba(168, 74, 40, .3); }
|
||||
.kb-st.is-done { background: rgba(111, 158, 92, .14); color: #4C7038; border-color: rgba(111, 158, 92, .35); }
|
||||
.kb-st.is-static { cursor: default; }
|
||||
.kb-st.is-doing:hover { border-color: var(--brand); }
|
||||
/* .is-on = "当前就是这一档"(读书详情页的四个状态按钮用它标选中):
|
||||
加一圈内描边而不是换底色 —— 底色已经表达"这一档是什么颜色",
|
||||
再换一次就会分不清"选中"和"这一档"。 */
|
||||
.kb-st.is-on { box-shadow: inset 0 0 0 1px currentColor; font-weight: 600; }
|
||||
.kb-due.is-late, .kb-late { color: var(--red); }
|
||||
.kb-ico { white-space: nowrap; }
|
||||
.kb-sub { display: flex; align-items: center; gap: 8px; }
|
||||
@@ -2587,6 +2604,21 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
.avatar-picker #avatar-preview[data-has-avatar="0"] { border-style: dashed; }
|
||||
.avatar-hint { line-height: 1.7; }
|
||||
|
||||
/* ---- 旅程题图(v24 第 14 批 用户第 3 条):与头像同一套裁剪,比例 16:9 ----
|
||||
预览块本身就是"选文件"的点击区(与头像一样,省掉一整行重复的文件控件)。
|
||||
⚠️ 比例写死成 16/9 是**故意**的:CSS 不读 ImageSlot,而槽位里的比例就是 16:9,
|
||||
两边各写一份会漂移 —— 部署脚本有一条断言比对 app.js 与 CSS 里的比例数字。 */
|
||||
.cover-picker { display: flex; align-items: flex-start; gap: 14px; flex-wrap: wrap; }
|
||||
.cover-picker #cover-preview {
|
||||
width: 240px; max-width: 100%; aspect-ratio: 16 / 9;
|
||||
object-fit: cover; border-radius: 12px;
|
||||
border: 1px solid var(--line); background: #F1E7D6; cursor: pointer;
|
||||
transition: border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.cover-picker #cover-preview:hover { border-color: var(--brand); box-shadow: 0 0 0 3px rgba(168,74,40,.12); }
|
||||
.cover-picker #cover-preview[data-has-cover="0"] { border-style: dashed; }
|
||||
.trip-people-block { display: flex; flex-direction: column; gap: 6px; margin: 8px 0; }
|
||||
|
||||
/* ---- 工作经历(用户第 4 条:可以有多个)---- */
|
||||
.job-list { display: flex; flex-direction: column; gap: 8px; }
|
||||
.job-row {
|
||||
@@ -2790,15 +2822,41 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
/* 下拉里放的是 Semi 的**内联**日历面板,去掉它自己的浮层阴影/描边避免双层框 */
|
||||
}
|
||||
.cal-jump-drop[hidden] { display: none; }
|
||||
.cal-jump-drop .semi-datepicker,
|
||||
.cal-jump-drop .semi-popover {
|
||||
box-shadow: none !important;
|
||||
border: 0 !important;
|
||||
background: var(--surface) !important;
|
||||
/* ---- v24 第 14 批 #1:下拉里是**自绘**的日历(不再挂 Semi 的浮层面板)----
|
||||
「上一月/上一年」只重画面板、绝不提交(这正是不引第三方控件的原因:
|
||||
它把"翻月"与"选中"混在同一个 onChange 里,于是翻月也会跳走)。 */
|
||||
.cjd-head {
|
||||
display: flex; align-items: center; gap: 4px; margin-bottom: 6px;
|
||||
}
|
||||
.cal-jump-drop .semi-datepicker {
|
||||
display: block;
|
||||
.cjd-title { flex: 1; text-align: center; font-weight: 600; font-size: 14px; }
|
||||
.cjd-nav {
|
||||
width: 28px; height: 28px; flex: 0 0 auto;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
border: 1px solid var(--line); border-radius: 8px;
|
||||
background: var(--surface); color: var(--ink); font-size: 14px; line-height: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.cjd-nav:hover { background: var(--line); }
|
||||
.cjd-grid {
|
||||
display: grid; grid-template-columns: repeat(7, 32px); gap: 2px;
|
||||
}
|
||||
.cjd-wd {
|
||||
text-align: center; font-size: 11px; color: var(--muted); padding: 2px 0 4px;
|
||||
}
|
||||
.cjd-day {
|
||||
width: 32px; height: 30px; padding: 0;
|
||||
border: 0; border-radius: 8px; background: transparent; color: var(--ink);
|
||||
font-size: 13px; cursor: pointer;
|
||||
}
|
||||
.cjd-day:hover { background: var(--line); }
|
||||
.cjd-day.is-out { color: var(--muted); opacity: .55; }
|
||||
.cjd-day.is-today { font-weight: 700; box-shadow: inset 0 0 0 1px var(--line); }
|
||||
.cjd-day.is-on { background: var(--brand); color: #fff; }
|
||||
.cjd-foot {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line);
|
||||
}
|
||||
.cjd-foot .cjd-sel { flex: 1; }
|
||||
|
||||
/* ==================== 周 / 天:按小时铺的时间网格 ==================== */
|
||||
/* 结构:左边一条"小时刻度"沟槽(tg-gutter),右边 N 个日期列(tg-col)。
|
||||
@@ -2960,3 +3018,192 @@ body[class] .semi-datepicker-day-inhover { background-color: color-mix(in srgb,
|
||||
padding: 9px 11px; border: 1px dashed var(--line); border-radius: var(--radius-s);
|
||||
background: var(--line-2); font-size: 13px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
v24 第 14 批:读书模块(用户第 5 条)
|
||||
卡片骨架 / 统计图表一律复用现成的(.card.card-pad / .stats-grid /
|
||||
.stat-card / .chart-card / .bar-chart / .vgrid),这里只写读书**特有**的部件。
|
||||
图表仍然是纯 CSS:不引图表库,服务端画出来、没 JS 也看得见。
|
||||
============================================================ */
|
||||
|
||||
/* ---- 书架:状态筛选条 ---- */
|
||||
.book-filter { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
|
||||
.bf-btn {
|
||||
display: inline-flex; align-items: center; gap: 5px;
|
||||
padding: 5px 14px; border-radius: 999px;
|
||||
border: 1px solid var(--line); background: var(--surface);
|
||||
font-size: 13px; color: var(--ink); text-decoration: none;
|
||||
transition: border-color .15s ease, background .15s ease;
|
||||
}
|
||||
.bf-btn:hover { border-color: var(--brand); text-decoration: none; }
|
||||
.bf-btn.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }
|
||||
.bf-n { font-size: 11px; opacity: .72; font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* ---- 书架:卡片网格 ----
|
||||
封面按 2:3(书的形状),宽度自适应;窄屏自动减列。 */
|
||||
.book-grid {
|
||||
display: grid; gap: 14px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
}
|
||||
.book-card {
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); padding: 12px; box-shadow: var(--shadow);
|
||||
}
|
||||
/* ⚠️ 封面是 <a>(整块可点):居中靠 flex,图自己铺满 —— 只写一次 display,
|
||||
写两遍后者会把前者悄悄改掉(这类"重复声明"最容易在改版时留成陷阱)。 */
|
||||
.book-cover {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
aspect-ratio: 2 / 3; border-radius: var(--radius-s);
|
||||
overflow: hidden; background: #F1E7D6;
|
||||
}
|
||||
.book-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.book-cover-empty { font-size: 32px; opacity: .5; }
|
||||
.book-body { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
|
||||
.book-title {
|
||||
font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.45;
|
||||
text-decoration: none;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.book-title:hover { color: var(--brand); }
|
||||
.book-author { font-size: 12px; color: var(--muted); }
|
||||
.book-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 6px; }
|
||||
.book-rate { font-size: 11px; color: var(--brand-ink); }
|
||||
.book-note-n { font-size: 11px; color: var(--muted); }
|
||||
/* 读到第几章的细进度条 */
|
||||
.bk-bar {
|
||||
height: 4px; border-radius: 999px; background: var(--line-2); overflow: hidden; margin: 2px 0;
|
||||
}
|
||||
.bk-bar i { display: block; height: 100%; background: var(--brand); }
|
||||
.book-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: auto; }
|
||||
|
||||
/* ---- 一本书 ---- */
|
||||
.book-hero { display: flex; gap: 20px; flex-wrap: wrap; }
|
||||
.book-hero-cover {
|
||||
width: 150px; max-width: 46%; flex: 0 0 auto;
|
||||
aspect-ratio: 2 / 3; border-radius: var(--radius-s);
|
||||
overflow: hidden; background: #F1E7D6;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
}
|
||||
.book-hero-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
.book-hero-body { flex: 1 1 300px; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.book-sub { margin: 0; font-size: 14px; color: var(--muted); }
|
||||
.book-states { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.book-sum { margin-top: 4px; }
|
||||
.book-toc-box { margin-top: 4px; }
|
||||
.book-toc { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 3px; }
|
||||
.book-toc li { font-size: 13.5px; display: flex; align-items: baseline; gap: 8px; }
|
||||
.book-toc li.is-cur > a { color: var(--brand); font-weight: 600; }
|
||||
.bk-file-now { margin: 0 0 6px; font-size: 13px; color: var(--muted); }
|
||||
|
||||
/* ---- 笔记 ---- */
|
||||
.book-note-form { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
|
||||
.bn-row { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.bn-row .select { flex: 0 0 auto; width: auto; min-width: 96px; }
|
||||
.bn-row .input { flex: 1 1 160px; min-width: 0; }
|
||||
.book-notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.book-note {
|
||||
border: 1px solid var(--line-2); border-radius: var(--radius-s);
|
||||
padding: 9px 12px; background: #FDFBF7;
|
||||
}
|
||||
.book-note.is-quote { border-left: 3px solid var(--brand-2); }
|
||||
.book-note.is-review { border-left: 3px solid var(--green); }
|
||||
.bn-head { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 5px; }
|
||||
.bn-kind {
|
||||
font-size: 11px; padding: 1px 7px; border-radius: 999px;
|
||||
background: var(--line-2); color: var(--brand-ink);
|
||||
}
|
||||
.bn-ch, .bn-src, .bn-who { font-size: 11.5px; color: var(--muted); }
|
||||
.bn-del { margin-left: auto; }
|
||||
.bn-body { font-size: 14px; line-height: 1.75; color: var(--ink); white-space: normal; }
|
||||
|
||||
/* ---- 阅读器 ----
|
||||
目录在侧栏、正文在右边;窄屏侧栏折到上面(而不是横向挤成一条)。 */
|
||||
.book-read { display: flex; gap: 18px; align-items: flex-start; }
|
||||
.br-side {
|
||||
flex: 0 0 210px; max-height: 72vh; overflow-y: auto;
|
||||
background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); padding: 12px 14px;
|
||||
}
|
||||
.br-side-h { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 8px; }
|
||||
.br-toc { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
|
||||
.br-toc li { font-size: 13px; }
|
||||
.br-toc li a {
|
||||
display: block; padding: 4px 8px; border-radius: 6px; color: var(--ink); text-decoration: none;
|
||||
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
||||
}
|
||||
.br-toc li a:hover { background: var(--line-2); }
|
||||
.br-toc li.is-cur a { background: rgba(168, 74, 40, .12); color: var(--brand-ink); font-weight: 600; }
|
||||
.br-main {
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: var(--radius); padding: 22px 26px; box-shadow: var(--shadow);
|
||||
}
|
||||
.br-ch-title { font-size: 19px; margin: 0 0 14px; }
|
||||
.br-body { font-size: 16px; line-height: 1.95; color: var(--ink); }
|
||||
.br-nav { display: flex; align-items: center; gap: 12px; margin-top: 22px; flex-wrap: wrap; }
|
||||
.br-pos { margin-left: auto; }
|
||||
.br-pdf object { width: 100%; height: 78vh; border: 0; display: block; }
|
||||
@media (max-width: 860px) {
|
||||
.book-read { flex-direction: column; }
|
||||
.br-side { flex: 1 1 auto; width: 100%; max-height: 260px; }
|
||||
.br-main { padding: 16px 18px; }
|
||||
.br-body { font-size: 15.5px; line-height: 1.9; }
|
||||
}
|
||||
|
||||
/* ---- 封面裁剪(书的封面是 2:3,与旅程题图的 16:9 不同)----
|
||||
与 .cover-picker 同一套结构,只是预览框换成竖的。 */
|
||||
.book-cover-picker #book-cover-preview {
|
||||
width: 150px; aspect-ratio: 2 / 3;
|
||||
}
|
||||
|
||||
/* ---- 导入读书笔记 ---- */
|
||||
.imp-how { margin: 0 0 10px; padding-left: 20px; display: flex; flex-direction: column; gap: 5px; }
|
||||
.imp-how li { font-size: 13.5px; line-height: 1.7; }
|
||||
.imp-list { list-style: none; margin: 0 0 14px; padding: 0; display: flex; flex-direction: column; gap: 10px; }
|
||||
.imp-book {
|
||||
border: 1px solid var(--line-2); border-radius: var(--radius-s);
|
||||
padding: 10px 12px; background: #FDFBF7;
|
||||
}
|
||||
.imp-bh { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
|
||||
.imp-t { font-size: 14.5px; font-weight: 600; }
|
||||
.imp-n { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||||
.imp-notes { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
|
||||
.imp-notes li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; font-size: 13px; }
|
||||
.imp-notes li.is-quote .imp-txt { color: var(--muted); }
|
||||
.imp-txt { flex: 1 1 240px; min-width: 0; line-height: 1.6; }
|
||||
.imp-confirm { margin-top: 6px; }
|
||||
|
||||
/* ---- 柱状图:列数不定的(每年的本数 / 评分档位)----
|
||||
观影那套 .vgrid 是写死 12 列的(12 个月),这里列数随数据变。 */
|
||||
.vgrid-auto { grid-template-columns: repeat(auto-fit, minmax(26px, 1fr)); }
|
||||
|
||||
/* ============================================================
|
||||
v24 第 15 批:活动(容器)/ 旅程(行程)两级语义
|
||||
术语改了(用户第 1 条),活动表单砍到三项(用户第 4 条)。
|
||||
============================================================ */
|
||||
|
||||
/* ---- 活动表单里的"说明一句"(时间/城市从哪来)----
|
||||
用左边一条竖线而不是整块底色:它是**解释**、不是输入项,
|
||||
染成和输入区一样的底色会让人以为这里要填东西。 */
|
||||
.form-note {
|
||||
margin: 0 0 14px; padding: 8px 0 8px 12px;
|
||||
border-left: 3px solid var(--line);
|
||||
font-size: 13px; line-height: 1.75; color: var(--muted);
|
||||
}
|
||||
.form-note b { color: var(--ink); font-weight: 600; }
|
||||
|
||||
/* ---- 「更多设置」折叠区:主流程只留三项,其余收在这里 ---- */
|
||||
.more-set {
|
||||
margin: 4px 0 14px; border: 1px solid var(--line);
|
||||
border-radius: var(--radius-s); background: #FDFBF7;
|
||||
}
|
||||
.more-set > summary {
|
||||
cursor: pointer; padding: 10px 14px;
|
||||
font-size: 13.5px; color: var(--muted); font-weight: 500;
|
||||
}
|
||||
.more-set > summary:hover { color: var(--brand); }
|
||||
.more-set[open] > summary { border-bottom: 1px solid var(--line-2); }
|
||||
.more-set .form-grid { padding: 14px; margin: 0; }
|
||||
/* 折叠区里的字段少一点内边距,看起来是"附带的"而不是主流程 */
|
||||
.more-set .field { margin-bottom: 0; }
|
||||
|
||||
@@ -300,6 +300,34 @@
|
||||
}
|
||||
})();
|
||||
|
||||
/**
|
||||
* 通用「点预览图 = 打开文件选择框」。
|
||||
*
|
||||
* ⚠️ 为什么必须做成通用的(2026-10-05 第 15 批 用户第 3 条实测踩到):
|
||||
* 原来这句 `prev.addEventListener('click', () => file.click())` 只写在
|
||||
* **templates/people/form.php 的内页脚本**里(那是"头像"专用的)。
|
||||
* 后来加的「旅程题图」「图书封面」照抄了同一套 DOM(.cover-picker +
|
||||
* #cover-preview + 隐藏的 file input),却**没有对应的 JS** ——
|
||||
* 于是图块看着能点(CSS 给了 cursor:pointer 与 hover 高亮),
|
||||
* 点下去什么都不会发生,用户以为功能坏了(原话:"点击哪里都没反应")。
|
||||
*
|
||||
* 做法:谁想"点图选文件",就在图上写 `data-pick-input="#文件框id"`,
|
||||
* 这里统一接管(一个页面有多个也各绑各的)。声明式,模板里不再各写一段 JS。
|
||||
*/
|
||||
Array.prototype.forEach.call(
|
||||
document.querySelectorAll('[data-pick-input]'),
|
||||
function (pv) {
|
||||
var sel = pv.getAttribute('data-pick-input');
|
||||
var file = sel ? document.querySelector(sel) : null;
|
||||
if (!file) { return; }
|
||||
// ⚠️ 用 <label for> 包住图片也能触发文件框,但那样 hover/光标语义会变、
|
||||
// 且部分浏览器在 label 里点 <img> 不会激活控件(要额外 CSS 才能稳)。
|
||||
// 直接 click() 最省事,也与原来头像那一套保持一致。
|
||||
pv.addEventListener('click', function () { file.click(); });
|
||||
pv.style.cursor = 'pointer';
|
||||
}
|
||||
);
|
||||
|
||||
// 头像选好后即时预览(仅用于**没走裁剪器**的文件框,避免和裁剪预览打架)
|
||||
var fileInput = document.getElementById('avatar');
|
||||
if (fileInput && !fileInput.hasAttribute('data-crop-ratio') && window.FileReader) {
|
||||
|
||||
Reference in New Issue
Block a user