修复:英文标识留空真能自动生成;表单必填前端就地提示不刷新 (#1)
## 问题 1:文案说「留空自动生成」,实际却报错
界面上英文标识写的是「留空自动生成」,且不带红星;但纯中文名
(如「同步日历」)经 slugify 过滤后是空串,服务端照旧报「请填写英文标识」。
文案与行为自相矛盾,用户不明所以。
根因:fb_sw_slugify() 只保留 ASCII,中文全被过滤掉。
修法(不靠加红星了事):给 slugify 加兜底前缀 + 序号
- 软件:中文名 → sw<序号>(如 sw2)
- 分类:中文名 → cat<序号>
- 编辑时留空 = 保持原标识,不因改名把已有网址打散
- 纯英文名行为不变(FeedLite → feedlite)
实测:同步日历 → sw1 / 我的软件 → sw2 / FeedLite → feedlite
## 问题 2:必填项没填,整页刷新才看到错误
原先完全靠服务端校验:提交 → POST → 整页重刷 → 页面顶部弹一条错误。
用户视角就是「什么都没填,页面闪了一下」。
新增 bindInlineValidate()(assets/app.js):
- 给 form 加 data-validate 即启用,零配置
- 就地校验:字段下方显示红框 + 原因,聚焦并滚到可见
- 用户一改动立刻撤掉提示,不一直红着
- 规则取自原生 required / minlength / pattern / type=email
🔴 关键实现细节:用**捕获阶段**监听,并调 stopImmediatePropagation()。
因为 bindNoDoubleSubmit 会在 submit 时禁用按钮 5 秒,若校验排在它之后,
被拦下的表单按钮会被锁住,用户以为点坏了。所以校验必须先跑且拦住它。
## 服务端仍保留兜底
JS 被绕过(禁用脚本、直接构造请求)时,后端照样拒绝空用户名、短口令。
## 顺带收敛资源版本
app.js?v=5 与 style.css?v=N 原先各自写死,已统一到 FB_ASSET_V(升到 11)。
加上前一轮的 layout.php / install.php,现在全站只有 bootstrap.php 一处定义。
验证:
- 标识生成与前端校验专项 21/21
- 全新安装 33/33、老基线 25/25、附件专项 22/22
- 安装向导 60/60、后台管理 30/30
This commit is contained in:
+17
-11
@@ -21,8 +21,11 @@ function fb_admin_taxonomy_action($pdo, $act, $post) {
|
||||
$note = trim((string)(isset($post['note']) ? $post['note'] : ''));
|
||||
if ($name === '') { return ['软件名称不能为空。', 'error']; }
|
||||
if (fb_len($name) > 40) { return ['软件名称最多 40 个字符。', 'error']; }
|
||||
if ($slug === '') { $slug = fb_sw_slugify($name); }
|
||||
if ($slug === '') { return ['请填写英文标识(用于网址)。', 'error']; }
|
||||
// 留空就自动生成。中文名生成不出 ASCII 时退化成 sw<序号>,不打扰用户。
|
||||
if ($slug === '') {
|
||||
$next = (int)$pdo->query('SELECT COALESCE(MAX(id),0)+1 FROM software')->fetchColumn();
|
||||
$slug = fb_sw_slugify($name, 'sw', $next);
|
||||
}
|
||||
if (fb_software($slug)) { return ['英文标识「' . $slug . '」已被占用,换一个。', 'error']; }
|
||||
$sort = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM software')->fetchColumn();
|
||||
$pdo->prepare('INSERT INTO software(slug,name,note,sort_order,enabled,created_at) VALUES(?,?,?,?,1,?)')
|
||||
@@ -45,8 +48,8 @@ function fb_admin_taxonomy_action($pdo, $act, $post) {
|
||||
$slug = fb_sw_slugify(isset($post['slug']) ? $post['slug'] : '');
|
||||
$note = trim((string)(isset($post['note']) ? $post['note'] : ''));
|
||||
if ($name === '') { return ['软件名称不能为空。', 'error']; }
|
||||
if ($slug === '') { $slug = fb_sw_slugify($name); }
|
||||
if ($slug === '') { return ['请填写英文标识。', 'error']; }
|
||||
// 编辑时留空 = 保持原标识,避免改名把已有网址打散
|
||||
if ($slug === '') { $slug = (string)$sw['slug']; }
|
||||
$other = fb_software($slug);
|
||||
if ($other && (int)$other['id'] !== $id) { return ['英文标识「' . $slug . '」已被其他软件占用。', 'error']; }
|
||||
$pdo->prepare('UPDATE software SET name=?, slug=?, note=?, sort_order=? WHERE id=?')
|
||||
@@ -92,8 +95,11 @@ function fb_admin_taxonomy_action($pdo, $act, $post) {
|
||||
$slug = fb_sw_slugify(isset($post['slug']) ? $post['slug'] : '');
|
||||
if ($name === '') { return ['分类名称不能为空。', 'error']; }
|
||||
if (fb_len($name) > 30) { return ['分类名称最多 30 个字符。', 'error']; }
|
||||
if ($slug === '') { $slug = fb_sw_slugify($name); }
|
||||
if ($slug === '') { return ['请填写英文标识。', 'error']; }
|
||||
// 留空自动生成;中文名退化成 cat<序号>(在本软件内取号,保证不冲突)
|
||||
if ($slug === '') {
|
||||
$next = (int)$pdo->query('SELECT COALESCE(MAX(id),0)+1 FROM categories')->fetchColumn();
|
||||
$slug = fb_sw_slugify($name, 'cat', $next);
|
||||
}
|
||||
if (fb_category_by_slug($swId, $slug)) { return ['该软件下已有标识为「' . $slug . '」的分类。', 'error']; }
|
||||
$sort = (int)$pdo->query('SELECT COALESCE(MAX(sort_order),0)+1 FROM categories WHERE software_id = ' . $swId)->fetchColumn();
|
||||
$pdo->prepare('INSERT INTO categories(software_id,slug,name,sort_order,enabled,created_at) VALUES(?,?,?,?,1,?)')
|
||||
@@ -108,8 +114,8 @@ function fb_admin_taxonomy_action($pdo, $act, $post) {
|
||||
$name = trim((string)(isset($post['name']) ? $post['name'] : ''));
|
||||
$slug = fb_sw_slugify(isset($post['slug']) ? $post['slug'] : '');
|
||||
if ($name === '') { return ['分类名称不能为空。', 'error']; }
|
||||
if ($slug === '') { $slug = fb_sw_slugify($name); }
|
||||
if ($slug === '') { return ['请填写英文标识。', 'error']; }
|
||||
// 编辑时留空 = 保持原标识,避免改名把已有链接打散
|
||||
if ($slug === '') { $slug = (string)$cat['slug']; }
|
||||
$other = fb_category_by_slug((int)$cat['software_id'], $slug);
|
||||
if ($other && (int)$other['id'] !== $id) { return ['该软件下已有标识为「' . $slug . '」的分类。', 'error']; }
|
||||
$pdo->prepare('UPDATE categories SET name=?, slug=?, sort_order=? WHERE id=?')
|
||||
@@ -285,7 +291,7 @@ function fb_admin_taxonomy_page($pdo, $editSw, $editCat) {
|
||||
if ($editSw > 0) { $esw = fb_software($editSw); }
|
||||
echo '<details class="tax-form-box"' . ($esw ? ' open' : '') . '>';
|
||||
echo '<summary>' . ($esw ? '编辑软件「' . e((string)$esw['name']) . '」' : '添加软件') . '</summary>';
|
||||
echo '<form method="post" action="admin.php?tab=taxonomy" class="card-form sm-form">';
|
||||
echo '<form method="post" action="admin.php?tab=taxonomy" class="card-form sm-form" data-validate>';
|
||||
echo ' <input type="hidden" name="csrf" value="' . e(fb_csrf_token()) . '">';
|
||||
if ($esw) { echo ' <input type="hidden" name="id" value="' . (int)$esw['id'] . '">'; }
|
||||
echo ' <div class="row2">';
|
||||
@@ -356,7 +362,7 @@ function fb_admin_taxonomy_page($pdo, $editSw, $editCat) {
|
||||
$ecat = $editCat > 0 ? fb_category($editCat) : null;
|
||||
echo '<details class="tax-form-box"' . ($ecat ? ' open' : '') . '>';
|
||||
echo '<summary>' . ($ecat ? '编辑分类「' . e((string)$ecat['name']) . '」' : '添加分类') . '</summary>';
|
||||
echo '<form method="post" action="admin.php?tab=taxonomy&sw=' . (int)$curSw['id'] . '" class="card-form sm-form">';
|
||||
echo '<form method="post" action="admin.php?tab=taxonomy&sw=' . (int)$curSw['id'] . '" class="card-form sm-form" data-validate>';
|
||||
echo ' <input type="hidden" name="csrf" value="' . e(fb_csrf_token()) . '">';
|
||||
echo ' <input type="hidden" name="software_id" value="' . (int)$curSw['id'] . '">';
|
||||
if ($ecat) { echo ' <input type="hidden" name="id" value="' . (int)$ecat['id'] . '">'; }
|
||||
@@ -426,7 +432,7 @@ function fb_admin_status_page($pdo, $editId) {
|
||||
}
|
||||
echo '<details class="tax-form-box"' . ($est ? ' open' : '') . '>';
|
||||
echo '<summary>' . ($est ? '编辑状态「' . e((string)$est['name']) . '」' : '添加状态') . '</summary>';
|
||||
echo '<form method="post" action="admin.php?tab=statuses" class="card-form sm-form">';
|
||||
echo '<form method="post" action="admin.php?tab=statuses" class="card-form sm-form" data-validate>';
|
||||
echo ' <input type="hidden" name="csrf" value="' . e(fb_csrf_token()) . '">';
|
||||
if ($est) { echo ' <input type="hidden" name="id" value="' . (int)$est['id'] . '">'; }
|
||||
echo ' <div class="row2">';
|
||||
|
||||
Reference in New Issue
Block a user