文件
fenleixinxi/application/admin/controller/fc/Publish.php
linda 043227dee7 feat(admin): 信息置顶 + 发布与费用配置(免费/付费开关、单条与月年卡价格含条数)
需求落地:
- 置顶:fc/post 新增 top 动作(批量置顶/取消置顶 + 站内消息通知),列表加「置顶/取消置顶」工具栏按钮、
  行内按钮与置顶状态徽章;置顶信息在用户端首页与「我的发布」中优先展示(Home::feed 按 is_top desc)
- 发布与费用配置(新增页面:同城业务 → 基础数据 → 发布与费用配置,fc/publish):
  发布模式(免费发布 / 付费发布)、会员单条发布价格、按月(价格 + 含条数)、按年(价格 + 含条数);
  真源为 fc_config 的 post.*,保存时同步 fc_plan 的月卡(M1)/年卡(M12) 价格与 post_quota
- FcInstall:新增 post.* 六项配置(默认免费发布,避免上线即撞付费墙);fc_user_ext 加 post_quota_left /
  post_quota_expire(套餐额度与有效期)、fc_plan 加 post_quota;新增 upgradePublish() 幂等升级老库并同步月/年卡
- tools/verify-api.mjs:新增 P9 发布配置 / P8 我的发布 契约校验,P1 发布改为「免费模式/额度内发布」并按付费模式容错
- 语言包与视图同步中文,均通过 php -l 与 node --check
2026-10-15 23:24:03 +08:00

140 行
4.9 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace app\admin\controller\fc;
use app\common\controller\Backend;
use think\Db;
/**
* 同城-发布与费用配置
*
* 本页是「发布模式 + 发布价格/条数」的唯一入口:
* 真源 = fa_fc_config 的 post.* 分组;保存时同步 fa_fc_plan 的月/年卡价格与含发布条数,
* 保证「发布页价格」「会员中心套餐价」「后端计费」三者一致。
*
* post.mode free 免费发布 / paid 付费发布
* post.singlePrice 单条发布价格(元)
* post.monthPrice 按月发布价格(元)
* post.monthQuota 按月含发布条数
* post.yearPrice 按年发布价格(元)
* post.yearQuota 按年含发布条数
*
* @icon fa fa-cny
*/
class Publish extends Backend
{
/** 发布模式取值 */
const MODES = ['free', 'paid'];
/**
* 配置页(GET 显示 / POST 保存)
*/
public function index()
{
if ($this->request->isPost()) {
return $this->save();
}
$this->view->assign('row', $this->current());
$this->view->assign('plans', Db::name('fc_plan')->order('sort asc')->select());
return $this->view->fetch();
}
/**
* 保存配置并同步会员套餐
*/
protected function save()
{
$params = $this->request->post('row/a');
if (!is_array($params)) {
$params = $this->request->post();
}
$mode = isset($params['mode']) ? trim((string)$params['mode']) : 'paid';
if (!in_array($mode, self::MODES, true)) {
$this->error('发布模式只支持「免费发布」或「付费发布」');
}
$num = function ($key, $default) use ($params) {
if (!isset($params[$key]) || $params[$key] === '') {
return $default;
}
return round((float)$params[$key], 2);
};
$int = function ($key, $default) use ($params) {
if (!isset($params[$key]) || $params[$key] === '') {
return $default;
}
return (int)$params[$key];
};
$single = $num('singlePrice', 5);
$monthPrice = $num('monthPrice', 19.9);
$yearPrice = $num('yearPrice', 168);
$monthQuota = $int('monthQuota', 30);
$yearQuota = $int('yearQuota', 500);
foreach (['单条发布价格' => $single, '按月发布价格' => $monthPrice, '按年发布价格' => $yearPrice] as $label => $v) {
if ($v < 0) {
$this->error($label . '不能为负');
}
}
if ($monthQuota < 0 || $yearQuota < 0) {
$this->error('含发布条数不能为负');
}
$now = time();
$values = [
'post.mode' => $mode,
'post.singlePrice' => $single,
'post.monthPrice' => $monthPrice,
'post.monthQuota' => $monthQuota,
'post.yearPrice' => $yearPrice,
'post.yearQuota' => $yearQuota,
];
foreach ($values as $key => $val) {
$exist = Db::name('fc_config')->where('key', $key)->find();
if ($exist) {
Db::name('fc_config')->where('key', $key)->update(['value' => (string)$val, 'updatetime' => $now]);
} else {
Db::name('fc_config')->insert([
'key' => $key, 'value' => (string)$val, 'group_key' => 'post',
'remark' => '发布与费用配置', 'createtime' => $now, 'updatetime' => $now,
]);
}
}
// 同步月/年卡(价格 + 含发布条数);季卡价格按「月卡 ×3」的默认口径不动
Db::name('fc_plan')->where('plan_code', 'M1')->update([
'price' => $monthPrice, 'post_quota' => $monthQuota, 'updatetime' => $now,
]);
Db::name('fc_plan')->where('plan_code', 'M12')->update([
'price' => $yearPrice, 'post_quota' => $yearQuota, 'updatetime' => $now,
]);
$this->success('发布配置已保存并同步会员套餐');
}
/** 当前配置(缺失时回落 fee.* 与内置默认) */
protected function current()
{
$cfg = [];
foreach (Db::name('fc_config')->select() as $r) {
$cfg[$r['key']] = $r['value'];
}
$pick = function ($key, $default) use ($cfg) {
return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
};
$mode = (string)$pick('post.mode', 'paid');
return [
'mode' => in_array($mode, self::MODES, true) ? $mode : 'paid',
'singlePrice' => $pick('post.singlePrice', $pick('fee.singlePost', '5')),
'monthPrice' => $pick('post.monthPrice', $pick('fee.memberMonth', '19.9')),
'monthQuota' => $pick('post.monthQuota', '30'),
'yearPrice' => $pick('post.yearPrice', $pick('fee.memberYear', '168')),
'yearQuota' => $pick('post.yearQuota', '500'),
];
}
}