Files
FamilyBook/templates/books/form.php
T
laoyang daf5ca904e v94 必填项改成「标签红色加粗」,去掉红色星号 (#3)
需求(用户 2026-10-25):必填项原来在标题后面加一个红色星号,排版经常不齐。
改成「标题本身红色 + 加粗」——用户习惯后,看到红色加粗的标题就知道是必填。

CSS(assets/css/app.css):
- :root 新增 --req:#B03A2E、--req-w:700。
  · 不用 --red(#C0533F):那是动作红(删除按钮底色),当文字色放在暖纸底
    #F7F3EA 上对比度只有 ≈3.4:1,13-14px 标签偏浅;#B03A2E 约 4.6:1。
  · --req-w 必须 700 不能 650/600:本站标签基线字重本来就是 600
    (.form-grid > .field > label / .card .field > label / .mv-row > .mv-lb /
    .dt-range-label 四处都是 600),650 会被取整回 600 = 完全没加粗。
- 文件末尾新增「必填项统一标记」段,覆盖四种标签形态:
  label.is-req / .mv-lb.is-req / .dt-inline-lab.is-req / .dt-range-label.is-req,
  用 !important 打穿各宿主选择器的特异性(颜色与字重是语义标记,不该被面板样式盖掉)。
- 冒号跟着变红:.mv-lb::after / .dt-inline-lab::after 原本是灰色 400,
  只改标签本体会剩「红字 + 灰冒号」的半截效果。
- 包裹层兜底(.field.is-req > label 等),只作用直接子级 ——
  不写 .field.is-req label,那会把「提醒」那排 checkbox 的小标签一起染红。
- 深色主题给备用色 #F08A78。
- 保留 .mv-row > .mv-lb > i(万一有没清干净的星号不至于露灰星花,且静态断言会报出来)。

模板(16 个文件 / 30 处):
- _event_fields.php / _datetime.php 的标签闭包第三参数 $star → $req,
  $dtLabelStar → $dtLabelReq,删掉死引用 $evLabelStar。
- 其余 14 个模板把 `<i>*</i>` 换成给标签加 is-req 类。
- 用显式 .is-req 而不靠 required 属性反推:CSS 无父选择器,:has() 旧 WebView
  不可用,且影院名称/板块名本来就靠 JS 校验没有 required。

验证:
- 静态 _fb_v94_static.py 22/0,反向 9/9(含「--req-w 必须严格大于从 CSS 解析出的
  标签基线字重」,不写死 700)。
- 真浏览器 _fb_v94_req.py 63/0,反向 5/5(加粗/必须红/!important 必要性/
  星号必须消失/每形态单独覆盖,五类注入各自精确命中预期断言)。
- 全量流水线(含 60+ 套件)通过并部署。
- 线上真实页面 _fb_v94_live.py 31/0:观影「片名」、旅程「旅程名」、
  成员「姓名」、导航「网址/所属板块」实测 color=rgb(176,58,46) weight=700,
  同页非必填 color=rgb(44,35,29) weight=600,冒号同色,无星号。
2026-10-26 06:45:38 +08:00

214 lines
9.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* 添加 / 编辑一本书(v24 第 14 批 用户第 5 条)。
*
* 两条进书的路线,**同一张表单**:
* ① 上传文件(EPUB / TXT)→ 服务端解析出书名、作者、简介、封面与**每一章正文**;
* 解析出来的值只填**空着的**字段(用户已经手填的不覆盖)。
* ② 手填一条"想读的书"(还没电子版时先记一笔)。
*
* ⚠️ 文件与封面是两个**不同**的上传位:
* 文件是原书(整存,可能几十 MB),封面是一张图(走裁剪,2:3)。
* 混成一个会让"换封面"变成"重新上传整本书"。
*
* @param array|null $b 编辑时的书;null = 新建
* @param array $people 名册("谁的书")
* @param array $errors 校验错误
*/
$isEdit = !empty($b);
$canWrite = \App\Auth::canWrite();
$people = $people ?? [];
$errors = $errors ?? [];
$cvSlot = \App\ImageSlot::get('bookcover') ?: ['ratio' => '2:3', 'w' => 600, 'h' => 900,
'label' => '封面', 'hint' => ''];
$curCover = $isEdit ? (string)($b['cover_key'] ?? '') : '';
$rate = $isEdit && $b['rating'] !== null ? (int)$b['rating'] : -1;
$ownerSel = $isEdit && (int)($b['owner_id'] ?? 0) > 0 ? [(int)$b['owner_id']] : [];
?>
<div class="page-head">
<h1><?= $isEdit ? '编辑图书' : '添加图书' ?></h1>
<div class="actions">
<a class="btn btn-ghost" href="<?= e($isEdit ? url('books/show', ['id' => (int)$b['id']]) : url('books')) ?>">
<?= $isEdit ? '返回这本书' : '返回书架' ?>
</a>
<?php $bkSetCurrent = 'form'; require __DIR__ . '/_settings_menu.php'; ?>
</div>
</div>
<div class="card card-pad">
<form method="post" enctype="multipart/form-data"
action="<?= e(url($isEdit ? 'books/update' : 'books/create')) ?>">
<?= csrf_field() ?>
<?php if ($isEdit): ?>
<input type="hidden" name="id" value="<?= (int)$b['id'] ?>">
<?php endif; ?>
<?php /* ---- ① 上传图书文件(解析的入口)----
⚠️ 放在最上面:多数人是"先传书、再让系统填好",
把文件框藏在最底下会让人以为只能手填。 */ ?>
<div class="field full">
<label for="bk-file">上传图书(EPUB / TXT / PDF…)</label>
<?php if ($isEdit && !empty($b['file_key'])): ?>
<p class="bk-file-now">
已存文件:<?= e((string)($b['file_name'] ?: $b['file_key'])) ?>
(<?= e(\App\Book::sizeWord($b['file_size'] ?? 0) ?: '大小未知') ?>)
<?php if ((int)($b['chapter_count'] ?? 0) > 0): ?>
· 已解析 <?= (int)$b['chapter_count'] ?> 章
<?php endif; ?>
<br><span class="tip">再选一个文件会替换它,并按新文件重新解析正文。</span>
</p>
<?php endif; ?>
<?php $sdName = 'bookfile'; $sdId = 'bk-file';
$sdAccept = '.epub,.txt,.pdf,.mobi,.azw3,.azw,.doc,.docx,application/epub+zip,text/plain,application/pdf';
$sdMultiple = false;
require __DIR__ . '/../_stage_drop.php'; ?>
<span class="tip">
EPUB 与 TXT 会自动读出书名 / 作者 / 简介 / 封面,并把正文拆成章节(可以直接在线读)。
PDF 只存文件(用浏览器自带的阅读器看)。MOBI / AZW3 存文件、正文解析不了。
</span>
</div>
<div class="form-grid">
<div class="field full">
<label class="is-req" for="bk-title">书名</label>
<input class="input" id="bk-title" name="title" required
value="<?= e(old('title', $b['title'] ?? '')) ?>" placeholder="如:活着">
<?php if (!empty($errors['title'])): ?><span class="err"><?= e($errors['title']) ?></span><?php endif; ?>
</div>
<div class="field">
<label for="bk-sub">副标题</label>
<input class="input" id="bk-sub" name="subtitle"
value="<?= e(old('subtitle', $b['subtitle'] ?? '')) ?>">
</div>
<div class="field">
<label for="bk-author">作者</label>
<input class="input" id="bk-author" name="author"
value="<?= e(old('author', $b['author'] ?? '')) ?>" placeholder="如:余华">
</div>
<div class="field">
<label for="bk-trans">译者</label>
<input class="input" id="bk-trans" name="translator"
value="<?= e(old('translator', $b['translator'] ?? '')) ?>">
</div>
<div class="field">
<label for="bk-pub">出版社</label>
<input class="input" id="bk-pub" name="publisher"
value="<?= e(old('publisher', $b['publisher'] ?? '')) ?>">
</div>
<div class="field">
<label for="bk-date">出版时间</label>
<input class="input" id="bk-date" name="published"
value="<?= e(old('published', $b['published'] ?? '')) ?>" placeholder="如:2012 或 2012-05">
</div>
<div class="field">
<label for="bk-isbn">ISBN</label>
<input class="input" id="bk-isbn" name="isbn" value="<?= e(old('isbn', $b['isbn'] ?? '')) ?>">
</div>
<div class="field">
<label for="bk-lang">语言</label>
<input class="input" id="bk-lang" name="language"
value="<?= e(old('language', $b['language'] ?? '')) ?>" placeholder="如:中文">
</div>
<div class="field">
<label for="bk-cat">分类</label>
<input class="input" id="bk-cat" name="category"
value="<?= e(old('category', $b['category'] ?? '')) ?>" placeholder="如:小说 / 历史 / 技术">
</div>
<div class="field">
<label for="bk-tags">标签(逗号分隔)</label>
<input class="input" id="bk-tags" name="tags" value="<?= e(old('tags', $b['tags'] ?? '')) ?>"
placeholder="如:长篇,当代文学">
</div>
<div class="field">
<label for="bk-status">状态</label>
<select class="select" id="bk-status" name="status">
<?php foreach (\App\Book::STATUS as $sk => $sv): ?>
<option value="<?= e($sk) ?>"
<?= old('status', $b['status'] ?? 'wish') === $sk ? 'selected' : '' ?>><?= e($sv) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="field">
<label for="bk-rate">评分</label>
<select class="select" id="bk-rate" name="rating">
<option value="" <?= $rate < 0 ? 'selected' : '' ?>>还没打分</option>
<?php for ($bi = 0; $bi <= 10; $bi++): ?>
<option value="<?= $bi ?>" <?= $rate === $bi ? 'selected' : '' ?>><?= $bi ?> 分</option>
<?php endfor; ?>
</select>
</div>
<div class="field">
<label for="bk-pages">页数</label>
<input class="input" type="number" min="0" id="bk-pages" name="pages"
value="<?= e((string)old('pages', $b['pages'] ?? '')) ?>" placeholder="有就填,用于统计">
</div>
<div class="field">
<label>谁的书</label>
<?php /* 与全站统一的选人组件(弹窗 + 头像 + 拼音联想);
留「不指定」= 全家共用这一本。 */ ?>
<?php
$ppName = 'owner_id';
$ppPeople = $people;
$ppSelected = $ownerSel;
$ppMode = 'single';
$ppOpenBtn = '选择是谁在读';
$ppTitle = '这本书是谁的';
$ppEmptyText = '全家共用';
$ppAllowEmpty = true;
require __DIR__ . '/../_person_picker.php';
?>
</div>
<div class="field full">
<label for="bk-sum">内容简介</label>
<textarea class="input" id="bk-sum" name="summary" rows="4"><?= e(old('summary', $b['summary'] ?? '')) ?></textarea>
</div>
<?php /* ---- ② 封面(与头像同一套裁剪,2:3)---- */ ?>
<div class="field full">
<label>封面</label>
<div class="cover-picker book-cover-picker">
<?php /* ⚠️ data-pick-input 是 app.js 里的通用"点图选文件"绑定 ——
少了它这块图点下去毫无反应(本批用户报的就是这个)。 */ ?>
<img id="book-cover-preview"
src="<?= e($curCover !== '' ? media_url($curCover) : '') ?>"
alt="" title="点击选封面"
data-pick-input="#bk-cover"
data-has-cover="<?= $curCover !== '' ? '1' : '0' ?>">
<input class="sr-only" id="bk-cover" name="cover" type="file" accept="image/*"
data-crop-ratio="<?= e((string)$cvSlot['ratio']) ?>"
data-crop-w="<?= (int)$cvSlot['w'] ?>"
data-crop-h="<?= (int)$cvSlot['h'] ?>"
data-crop-label="<?= e((string)$cvSlot['label']) ?>"
data-crop-hint="<?= e((string)$cvSlot['hint']) ?>"
data-crop-preview="#book-cover-preview">
<input type="hidden" name="cover_crop" value="">
<span class="avatar-hint small muted">
点左边那块选封面,选完弹裁剪框。<br>
上传 EPUB 时如果书里自带封面,会**自动**用它(你另外选一张就覆盖它)。
</span>
</div>
</div>
</div>
<div class="btn-row">
<button class="btn" type="submit"><?= $isEdit ? '保存' : '加进书架' ?></button>
<a class="btn btn-ghost"
href="<?= e($isEdit ? url('books/show', ['id' => (int)$b['id']]) : url('books')) ?>">取消</a>
</div>
</form>
</div>