Files
FeedLite/new.php
T
laoyang c09a9875a3 标题收到最上顶头,标签下移一行;顶栏标题回当前软件;去掉跨软件引导
用户三个问题:

一、软件名/分类/状态三个标签挤在标题前面,把标题推到右边很难看
  列表页:item-top 里 badges 在前、标题在后 → 标题被挤到右边
  详情页:同一个毛病
  改法(两处一致):
    行1 标题(顶头、独占一行)
    行2 标签(分类/状态;详情页还有软件名)
    行3 摘要 / 作者信息
  列表页新增 .item-head + .item-tags;详情页新增 .subject-head + .subject-tags。
  ⚠️ .item-top 保留 —— admin.php 有三处内部列表还在用,删了后台会塌。

二、顶栏「XX 反馈系统」点完跳首页
  用户:「点完之后应该还是进入这个反馈系统,而不是进到首页。首页没有太大作用,
        只是演示说明用的。正常使用都是每个软件独立,要让用户觉得
        这套反馈系统就是专门为这个软件做的。」
  改法:brand-mini 的 href 从 "./" 改为 fb_software_list_url()。

三、软件内部不该引导去其他软件
  用户:「在一个反馈系统里面,我要反馈、问题列表、左上角那个链接,
        全部都是这个软件相关的内容,跟其他软件没关系。」
  - 提交成功页删掉整个「也可以去其他软件反馈」区块及其 CSS
  - 删掉成功页的「返回首页」按钮(已有「看该软件其他反馈」,重复且无意义)
  - 跨软件只剩顶栏三横线菜单一个入口

验证:列表页/详情页 DOM 顺序均为 标题→标签→作者(实测 item-title 58 < item-tags 171;
subject-title 49 < subject-tags 151 < item-meta 592);
浏览器实测标题 top=282 标签 top=315(列表)、155→194(详情),标签确实在下方;
标题左边缘距卡片边 18/23px,就是卡片自身 padding,没有额外缩进;
顶栏标题 href=index.php?sw=synccalendar,不再指向根路径;全站 8 个路由 200。
2026-11-03 13:43:15 +08:00

362 lines
18 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
/**
* 发布新问题
*/
require __DIR__ . '/lib/bootstrap.php';
$pdo = fb_db();
$err = '';
$atts = []; // 校验失败时保留已上传的附件
/* ---- 软件:URL 带 sw 就用它 ----
* 🔴 不再「没带 sw 就默认落到第一个软件」:
* 首页现在是介绍页,直接访问 new.php 没有软件上下文,
* 默默替他选一个软件会让反馈进错地方、发错通知邮件。
* 没有软件时给一句引导,让用户先选软件。 */
$swList = fb_softwares(true);
$sw = null;
$swKey = isset($_GET['sw']) ? (string)$_GET['sw'] : '';
if ($swKey !== '') {
$sw = fb_software($swKey);
if (!$sw || (int)$sw['enabled'] !== 1) { $sw = null; }
}
if ($sw === null) {
fb_layout_head('请先选择软件', ['robots' => 'noindex,follow']);
?>
<div class="empty">
<h2 style="margin:0 0 12px">请先选择要给哪个软件提反馈</h2>
<p class="muted" style="margin:0 0 16px">不同软件的问题列表是分开的,请先选一个。</p>
<div class="home-sw-list" style="max-width:420px;margin:0 auto 18px">
<?php foreach ($swList as $s): ?>
<?php
list($c) = fb_software_colors($s);
if ($c === '') { $c = '#0057B8'; }
?>
<a class="home-sw" href="new.php?sw=<?php echo e(urlencode((string)$s['slug'])); ?>"
style="--swc:<?php echo e($c); ?>">
<span class="home-sw-nm"><?php echo e((string)$s['name']); ?></span>
<span class="home-sw-go">去反馈 →</span>
</a>
<?php endforeach; ?>
</div>
<p><a class="btn" href="index.php">返回首页</a></p>
</div>
<?php
fb_layout_foot();
exit;
}
$swId = (int)$sw['id'];
$cats = fb_categories($swId);
$catKeys = [];
foreach ($cats as $c) { $catKeys[] = (int)$c['id']; }
$form = [
'category_id' => $catKeys ? (string)$catKeys[0] : '',
'title' => '',
'body' => '',
'name' => '',
'email' => '',
'phone' => '',
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
foreach (['category_id', 'title', 'body', 'name', 'email', 'phone'] as $k) {
$form[$k] = trim(isset($_POST[$k]) ? (string)$_POST[$k] : '');
}
// POST 里带了 software_id 时以它为准(切换软件后表单会跟着变)
if (isset($_POST['software_id']) && ctype_digit((string)$_POST['software_id'])) {
$swPost = fb_software((int)$_POST['software_id']);
if ($swPost && (int)$swPost['enabled'] === 1) {
$sw = $swPost;
$swId = (int)$swPost['id'];
}
}
$capInput = isset($_POST['captcha']) ? (string)$_POST['captcha'] : '';
$hp = trim(isset($_POST['website']) ? (string)$_POST['website'] : '');
$t0 = isset($_POST['t0']) ? (int)$_POST['t0'] : 0;
list($attOk, $attList, $attErr) = fb_attach_parse(isset($_POST['attachments']) ? $_POST['attachments'] : '');
// 分类必须属于这个软件,否则视为未选
$curCatId = ctype_digit($form['category_id']) ? (int)$form['category_id'] : 0;
$curCat = $curCatId ? fb_category($curCatId) : null;
if (!$curCat || (int)$curCat['software_id'] !== $swId) { $curCat = null; $form['category_id'] = ''; }
if (!fb_csrf_ok(isset($_POST['csrf']) ? $_POST['csrf'] : '')) {
$err = '会话已过期,请重新提交。';
} elseif ($hp !== '') {
$err = '提交未通过校验,请重试。';
} elseif ($t0 > 0 && (time() - $t0) < max(0, (int)fb_cfg('post_min_seconds', 3))) {
$err = '提交过于频繁,请稍候再试。';
} elseif (fb_len($form['title']) < 4 || fb_len($form['title']) > 80) {
$err = '标题请填写 4~80 个字符。';
} elseif (!fb_valid_body($form['body'])) {
$err = '问题描述请填写 2~5000 个字符。';
} elseif (!fb_valid_name($form['name'])) {
$err = '请填写昵称(1~24 个字符)。';
} elseif (!filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
$err = '请填写有效的邮箱地址(用于接收回复通知)。';
} elseif ($form['phone'] !== '' && !preg_match('/^[0-9+\-\s()]{5,30}$/', $form['phone'])) {
$err = '手机号格式不正确(可留空)。';
} elseif (!$attOk) {
$err = $attErr;
} elseif (!fb_rate_check('thread', (int)fb_cfg('rate_limit.thread_per_hour', 5))) {
$err = '发帖过于频繁,请稍后再试。';
} else {
// 附件回源复核 + 全站总量检查(放在人机验证之前,避免验证码被无效消耗)
list($vOk, $vList, $vErr) = fb_attach_verify($attList);
if (!$vOk) {
$err = $vErr;
$atts = $attList;
} elseif (!fb_attach_cap_ok($pdo, $vList)) {
$err = '附件存储空间已满,请减少附件后重试。';
$atts = $vList;
} elseif (!fb_captcha_check($capInput)) {
$err = '人机验证未通过,请重新作答。';
$atts = $vList;
} else {
$now = time();
/* 审核:开启后新提交默认不公开(visible=0),管理员通过后才出现在列表。
*
* 🔴 状态与可见性必须同时设,两者是同一件事的两个侧面:
* 未公开 → status='hidden'(未公开),公开 → status='open'(待处理)。
* 以前一律写 'open',导致后台「待审核」里全是「待处理」,
* 状态根本反映不出"还没公开"这个事实。 */
$visible = fb_moderation_on() ? 0 : 1;
$initStatus = $visible === 1 ? 'open' : 'hidden';
/* track_token:提交人的私人追踪钥匙,只发到他的邮箱。
* 有了它,即使这条还没审核通过,他也能凭链接看到进度。 */
$trackToken = fb_track_new_token();
$ins = $pdo->prepare('INSERT INTO threads(title, body, category, category_id, software_id, author_name, author_email, author_phone, status, visible, reply_count, created_at, updated_at, ip_hash, track_token) VALUES(?,?,?,?,?,?,?,?,?,?,0,?,?,?,?)');
$ins->execute([
$form['title'], $form['body'],
$curCat ? (string)$curCat['slug'] : 'other', // 老列留个值,便于回退与检索
$curCat ? (int)$curCat['id'] : null,
$swId > 0 ? $swId : null,
$form['name'], $form['email'],
$form['phone'] !== '' ? $form['phone'] : null,
$initStatus, $visible, $now, $now, fb_ip_hash(), $trackToken,
]);
$newId = (int)$pdo->lastInsertId();
fb_attach_save($pdo, $newId, null, $vList);
fb_rate_hit('thread');
// 通知管理员「有新反馈」(工单 #2)
// 此前 notify.admin_new_thread 这个设置项是**假的** ——
// 白名单里有、后台有勾选框,但没有任何代码实现它。
// 用户反馈「提交了反馈却收不到邮件」正是这个原因。
try {
$stNt = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
$stNt->execute([$newId]);
$newThread = $stNt->fetch(PDO::FETCH_ASSOC);
if ($newThread) {
$swName = '';
if (!empty($swId)) {
$swRow = fb_software((int)$swId);
if ($swRow) { $swName = (string)$swRow['name']; }
}
/* ① 先给提交人发「已收到」回执 —— 让他知道东西送达了、正在等审核。
* 这一步是用户最在意的:提交完页面跳走了,没有回执就等于石沉大海。
* 放在管理员通知之前,是因为回执对人的价值更高,且失败互不影响。 */
fb_notify_event($pdo, 'receipt', $newThread, ['software_name' => $swName]);
/* ② 再通知管理员「有新反馈」 */
fb_notify_event($pdo, 'new', $newThread, ['software_name' => $swName]);
}
} catch (Throwable $e) {
// 发信失败绝不能让用户提交失败 —— 反馈已经存库了
}
/* 提交完成 → 原地渲染「已收到」页面,不跳转。
*
* 用户 2026-11-03:「提交完怎么直接跳转到首页去了?应该给它一个提示,
* 说你反馈我们已经收到…里面有一个标志码,是你以后跟踪它的唯一凭据…
* 然后下面再列出来其他功能(返回首页、继续提交、其他软件链接、开源链接),
* 不能直接跳转,这给人感觉很突兀。」
*
* 🔴 关键取舍:**不跳转、不用 302**。跳转会让用户失去"我刚提交成功"这一帧,
* 而且详情页此刻还是「未公开」状态,跳过去反而像出错了。
* 这里直接把成功页渲染出来,把 token 明明白白摆在眼前。 */
fb_render_submitted($sw, $newId, $trackToken, $form);
exit;
}
}
}
fb_layout_head('我要反馈', ['robots' => 'noindex,follow', 'software' => $sw]);
?>
<div class="crumbs"><a href="<?php echo e(fb_software_list_url($sw)); ?>">← 返回问题列表</a></div>
<section class="narrow">
<h1>我要反馈</h1>
<?php if ($sw): ?>
<div class="sw-context"><?php echo fb_software_badge($sw, 'md'); ?>
<span class="muted small">您正在为「<?php echo e(fb_software_site_title($sw)); ?>」提交反馈</span></div>
<?php endif; ?>
<p class="muted">请描述您遇到的问题或想要的功能。提交后会先进入审核,确认为有效问题后才会公开显示;<b>若经确认不是应用的问题,我们会把结论与说明通过邮件发送给您</b>(请填写常用邮箱)。</p>
<?php fb_flash_box($err, 'error'); ?>
<form method="post" action="new.php" class="card-form">
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
<input type="hidden" name="t0" value="<?php echo time(); ?>">
<div class="hp-field" aria-hidden="true">
<label>请勿填写此栏<input type="text" name="website" tabindex="-1" autocomplete="off"></label>
</div>
<?php if ($sw): ?>
<input type="hidden" name="software_id" value="<?php echo (int)$sw['id']; ?>">
<?php /* 软件切换统一收到顶栏的汉堡菜单里,表单内不再平铺一排 chip ——
免得同一个选择在两个地方出现,用户不知道该点哪个。 */ ?>
<?php endif; ?>
<?php if ($cats): ?>
<label class="field">
<span>问题分类 <b class="req">*</b></span>
<select name="category_id" required>
<?php foreach ($cats as $c): ?>
<option value="<?php echo (int)$c['id']; ?>" <?php echo $form['category_id'] === (string)$c['id'] ? 'selected' : ''; ?>><?php echo e((string)$c['name']); ?></option>
<?php endforeach; ?>
</select>
</label>
<?php endif; ?>
<label class="field">
<span>标题 <b class="req">*</b></span>
<input type="text" name="title" maxlength="80" value="<?php echo e($form['title']); ?>" placeholder="一句话概括您的问题" required>
</label>
<label class="field">
<span>详细描述 <b class="req">*</b></span>
<textarea name="body" rows="9" maxlength="5000" placeholder="请尽量说明:您做了什么、期望的结果、实际的结果;如是同步问题,可说明使用的邮箱 / 服务器类型。" required><?php echo e($form['body']); ?></textarea>
</label>
<div class="row2">
<label class="field">
<span>昵称 <b class="req">*</b></span>
<input type="text" name="name" maxlength="24" value="<?php echo e($form['name']); ?>" placeholder="怎么称呼您" required>
</label>
<label class="field">
<span>邮箱 <b class="req">*</b><i class="hint">仅用于接收回复通知,不会公开</i></span>
<input type="email" name="email" maxlength="120" value="<?php echo e($form['email']); ?>" placeholder="you@example.com" required>
</label>
</div>
<label class="field">
<span>手机号 <i class="hint">选填;仅用于必要时联系,不会公开</i></span>
<input type="tel" name="phone" maxlength="30" value="<?php echo e($form['phone']); ?>" placeholder="选填">
</label>
<?php fb_uploaders($atts); ?>
<div class="captcha">
<label class="field">
<span>人机验证 <b class="req">*</b> <i class="hint">防机器人,随手答一下</i></span>
<div class="captcha-line">
<span class="c-q"><?php echo e(fb_captcha_question()); ?></span>
<input type="text" name="captcha" value="" placeholder="答案" autocomplete="off" required>
<a class="reload" href="captcha.php" data-captcha-refresh>换一题</a>
</div>
</label>
</div>
<div class="form-actions">
<button class="btn primary" type="submit">提交反馈</button>
</div>
</form>
<p class="muted small">提示:本页面不会公开展示您的邮箱与手机号。请勿在内容中填写密码等敏感信息。可上传截图或录屏帮助说明问题;文件会直接传到对象存储,不占用本站服务器空间。</p>
</section>
<?php
fb_layout_foot();
/* ==================================================================
* 提交成功页
* ------------------------------------------------------------------
* 用户 2026-11-03 要求:提交完不要直接跳走,要给一个明确回执。
* 页面结构(按他的话组织):
* ① 你的反馈我们已经收到
* ② 详细信息已发到你邮箱
* ③ 下面这个标志码是**你以后跟踪它的唯一凭据**,请保存好
* ④ 我们会尽快处理,你可以凭它持续关注进展
* ⑤ 再往下给出路:查看这条反馈 / 继续提交 / 返回列表 / 其他软件 / 开源仓库
*
* 为什么要显式展示 token(而不只是说"已发到邮箱"):
* 邮件可能进垃圾箱、也可能被误删。把码当场亮出来,用户至少能复制存档,
* 不至于彻底失去跟踪能力。这是"唯一凭据",丢了就找不回来(我们不认人)。
* ================================================================== */
function fb_render_submitted($sw, $newId, $token, array $form) {
$swName = $sw ? (string)$sw['name'] : '';
fb_layout_head('反馈已提交', ['robots' => 'noindex,nofollow', 'software' => $sw]);
?>
<section class="narrow">
<div class="submitted">
<div class="submitted-ok" aria-hidden="true">✓</div>
<h1>您的反馈我们已经收到</h1>
<p class="submitted-lead">
我们已经把这条反馈存下来了,会尽快安排查看与处理。
<?php if (trim((string)$form['email']) !== ''): ?>
这条反馈的详细信息也已发送到 <b><?php echo e((string)$form['email']); ?></b>,
请留意收件箱(如果没看到,可以看一下垃圾邮件)。
<?php endif; ?>
</p>
<div class="token-box">
<div class="token-box-hd">您跟踪这条反馈的唯一凭据</div>
<div class="token-code" id="trackToken"><?php echo e((string)$token); ?></div>
<p class="token-box-tip">
请把这段标志码保存好。今后凭它就能查看这条反馈的处理进度 ——
即使它还没有通过审核、甚至最终被关闭,也依然能看。
我们也会在每封通知邮件里带上它,所以务必留意邮箱。
</p>
<p class="token-box-warn">
这段码等同于您的身份凭证:谁拿到它,谁就能查看这条反馈。
请不要发给别人,也不要发到公开的地方。
</p>
</div>
<p class="submitted-next">接下来您可以:</p>
<div class="submitted-actions">
<a class="btn primary" href="thread.php?id=<?php echo (int)$newId; ?>&amp;t=<?php echo e(rawurlencode((string)$token)); ?>">
查看这条反馈
</a>
<a class="btn" href="new.php<?php echo $sw ? '?sw=' . e(urlencode((string)$sw['slug'])) : ''; ?>">继续提交一条</a>
<?php /* 不再放「返回首页」—— 在某个软件的反馈系统里,首页(全局介绍页)
对用户没有意义。要回就回这个软件的问题列表。
想看别的软件,只有右上角三横线一个入口。 */ ?>
<a class="btn" href="<?php echo e(fb_software_list_url($sw)); ?>">
<?php echo $swName !== '' ? '看看「' . e($swName) . '」的其他反馈' : '返回问题列表'; ?>
</a>
</div>
<?php
/* ⚠️ 这里原来有一个「也可以去其他软件反馈」的列表,已删除。
*
* 用户 2026-11-03:「在某个软件的反馈系统里面,什么我要反馈、问题列表、
* 还有左上角的那个链接,全部都是这个软件相关的内容,跟其他软件没关系 ——
* 相对来讲它是独立的。他如果想进别的软件,只有右上角三个横线点开。」
*
* 所以在软件内部(列表 / 详情 / 提交页 / 成功页)都不引导用户去别的软件。
* 唯一的跨软件入口就是顶栏那个三横线菜单。
*/
?>
<?php
$creditUrl = (string)fb_cfg('footer.credit_url', fb_credit_url_default());
if ($creditUrl !== ''):
?>
<p class="submitted-credit">
这套反馈系统是开源的:<a href="<?php echo e($creditUrl); ?>" target="_blank" rel="noopener"><?php echo e($creditUrl); ?></a>
</p>
<?php endif; ?>
</div>
</section>
<?php
fb_layout_foot();
}