需求:免费发布不能是「要么全免费、要么一开付费就是硬付费墙」,需要可配「前 N 条免费」, 超出的按付费发布标准计费。 - 后台「发布与费用配置」新增「免费发布条数 + 统计周期」:post.freeQuota / post.freeQuotaCycle (day 每日 / month 每月 / total 累计;0 = 不限制,免费发布模式下「免费到底」) Publish::save 校验周期枚举与条数非负;页面补说明(计费顺序:套餐额度 → 免费发布条数 → 单条付费) - FcInstall::upgradePublish 与内置种子各补两枚配置(幂等,不覆盖运营已改的值;老库缺失时 API 回落 fee.freePostPerDay 按日计,行为与升级前一致) - POST /post/create 计费重构为三级顺序: ① 套餐额度(fc_user_ext.post_quota_left)→ ② 免费发布条数(按周期统计本人已发布条数,不含软删) → ③ 单条付费(post.singlePrice,即「付费发布」标准) 免费发布模式下免费条数用完时明确提示「免费额度已用完,请按 X.XX 元/条付费发布」 (服务端不主动扣款,需客户端切到 plan=single 并选支付方式后重新提交) - GET /post/publish-config 字段调整:freePerDay/freeUsedToday/freeLeftToday → freeQuota / freeQuotaCycle / freeQuotaUsed / freeQuotaLeft / freeUnlimited(-1 = 不限制) - tools/verify-api.mjs P9 同步断言新字段与「能否免费发布」判定;后台语言包补齐中文映射 - application/config.php:site.version 1.6.6.20261016 → 1.6.7.20261016(后台视图/JS 变更后强制刷新静态资源缓存) 自检:node tools/check-admin-i18n.mjs(错误 0/警告 0)、node tools/check-theme.mjs(错误 0/警告 0)、 5 个 PHP 文件 php -n -l 无语法错误。
159 行
6.2 KiB
PHP
159 行
6.2 KiB
PHP
<?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.freeQuota 免费发布条数(0 = 不限制/免费到底;超出即按 singlePrice 计费)
|
||
* post.freeQuotaCycle 免费条数统计周期:day 每日 / month 每月 / total 累计
|
||
* post.monthPrice 按月发布价格(元)
|
||
* post.monthQuota 按月含发布条数
|
||
* post.yearPrice 按年发布价格(元)
|
||
* post.yearQuota 按年含发布条数
|
||
*
|
||
* @icon fa fa-cny
|
||
*/
|
||
class Publish extends Backend
|
||
{
|
||
/** 发布模式取值 */
|
||
const MODES = ['free', 'paid'];
|
||
|
||
/** 免费条数统计周期取值 */
|
||
const CYCLES = ['day', 'month', 'total'];
|
||
|
||
/**
|
||
* 配置页(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);
|
||
// 免费发布条数:0 = 不限制(免费到底);达到条数后按「单条发布价格」计费
|
||
$freeQuota = $int('freeQuota', 0);
|
||
$freeQuotaCycle = isset($params['freeQuotaCycle']) ? trim((string)$params['freeQuotaCycle']) : 'day';
|
||
if (!in_array($freeQuotaCycle, self::CYCLES, true)) {
|
||
$this->error('免费条数统计周期只支持「每日 / 每月 / 累计」');
|
||
}
|
||
|
||
foreach (['单条发布价格' => $single, '按月发布价格' => $monthPrice, '按年发布价格' => $yearPrice] as $label => $v) {
|
||
if ($v < 0) {
|
||
$this->error($label . '不能为负');
|
||
}
|
||
}
|
||
if ($monthQuota < 0 || $yearQuota < 0) {
|
||
$this->error('含发布条数不能为负');
|
||
}
|
||
if ($freeQuota < 0) {
|
||
$this->error('免费发布条数不能为负(0 表示不限制)');
|
||
}
|
||
|
||
$now = time();
|
||
$values = [
|
||
'post.mode' => $mode,
|
||
'post.singlePrice' => $single,
|
||
'post.freeQuota' => $freeQuota,
|
||
'post.freeQuotaCycle' => $freeQuotaCycle,
|
||
'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')),
|
||
// 免费发布条数:老库未保存过时回落 fee.freePostPerDay(默认 0 = 不限制)
|
||
'freeQuota' => $pick('post.freeQuota', $pick('fee.freePostPerDay', '0')),
|
||
'freeQuotaCycle' => $pick('post.freeQuotaCycle', 'day'),
|
||
'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'),
|
||
];
|
||
}
|
||
}
|