本次提交主要实现微信小程序虚拟支付提审防审机制,包括: 1. 后台配置虚拟支付防审开关,支持开启/关闭模式 2. 前端根据开关状态及设备类型,iOS设备自动触发合规提示 3. 分销模块调整提现规则,无门槛即时提现 4. 登录页面优化,增加返回和暂不登录入口,符合微信审核规范 5. 新增提现记录页面,优化分销中心界面布局 该功能确保微信提审期间能顺利通过审核,审核通过后可一键切换为正常运营模式,无需重新发布小程序代码。
679 行
27 KiB
PHP
679 行
27 KiB
PHP
<?php
|
||
|
||
namespace app\common\library;
|
||
|
||
use think\Db;
|
||
|
||
/**
|
||
* 业务配置分组(系统配置页扩展)
|
||
*
|
||
* 背景:
|
||
* - 小程序、发布、分销三组配置的真源是 fa_fc_config,供小程序接口 / 分销服务直接读取;
|
||
* - FastAdmin 的「系统设置 → 系统配置」页面按 fa_config 分组渲染,原本无法展示 fc_config;
|
||
* - 本类把三组配置以「虚拟分组」的形式并入系统配置页,复用同一套表单与保存逻辑,
|
||
* 值仍写入 fa_fc_config,不复制、不双写。
|
||
*
|
||
* 命名约定:表单字段统一以 fc_ 前缀,保存时映射回 fc_config 的原始 key。
|
||
*/
|
||
class FcConfig
|
||
{
|
||
/**
|
||
* 系统配置页的业务分组(仅保留小程序基础与支付安全配置,发布/会员/分销已独立至专属业务菜单)
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function groups()
|
||
{
|
||
$cfg = self::values();
|
||
$pick = function ($key, $default = '') use ($cfg) {
|
||
return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
|
||
};
|
||
|
||
return [
|
||
'fc_miniapp' => [
|
||
'name' => 'fc_miniapp',
|
||
'title' => '小程序配置',
|
||
'active' => false,
|
||
'list' => [
|
||
self::item('fc_base_appName', '小程序名称', 'string', $pick('base.appName', ''), '显示在小程序首页与分享卡片', 'required'),
|
||
self::item('fc_base_logo', '小程序LOGO', 'image', $pick('base.logo', ''), '留空时使用默认 LOGO'),
|
||
self::item('fc_base_servicePhone', '客服电话', 'string', $pick('base.servicePhone', ''), '展示在小程序「联系客服」入口'),
|
||
self::item('fc_base_shareTitle', '分享标题', 'string', $pick('base.shareTitle', ''), '留空则默认使用小程序名称'),
|
||
self::item('fc_base_aboutUs', '关于我们', 'text', $pick('base.aboutUs', ''), '展示在小程序「关于我们」页面'),
|
||
self::item('fc_wx_appid', '小程序 AppID', 'string', $pick('wx.appid', ''), '微信公众平台 → 开发管理 → 开发设置 → AppID(微信登录用)'),
|
||
self::item('fc_wx_secret', 'AppSecret', 'password', '', '仅服务端保存与调用;已配置时留空表示不修改(微信登录用)'),
|
||
self::item('fc_wx_tplAudit', '审核结果模板 ID', 'string', $pick('wx.tplAudit', ''), '小程序订阅消息:审核结果通知(公众平台 → 订阅消息)'),
|
||
self::item('fc_wx_tplTransfer', '提现到账模板 ID', 'string', $pick('wx.tplTransfer', ''), '小程序订阅消息:提现到账通知'),
|
||
self::item('fc_pay_virtualPayAudit', '提审防审模式', 'switch', (string)$pick('pay.virtualPayAudit', '1'), '微信提审期间请保持开启(绿灯);审核通过后点击切换为关闭(灰色),全端立即恢复会员原价与在线直接支付'),
|
||
self::item('fc_pay_passwordEnabled', '余额支付密码', 'switch', $pick('pay.payPasswordEnabled', '0'), '开启后,用户使用余额支付时需验证 6 位支付密码;默认关闭'),
|
||
self::item('fc_admin_userIds', '后台接口管理员 ID', 'string', $pick('admin.userIds', ''), '允许调用 /api/v1/admin/** 的用户 ID,多个用逗号分隔(留空 = 全部禁止)'),
|
||
self::item('fc_admin_mobiles', '后台接口管理员手机号', 'string', $pick('admin.mobiles', ''), '允许调用管理接口的手机号,多个用逗号分隔'),
|
||
],
|
||
],
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 获取「发布与浏览配置」当前值(供独立菜单 fc/publish 读取)
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function getPublishConfig()
|
||
{
|
||
$cfg = self::values();
|
||
$pick = function ($key, $default = '') use ($cfg) {
|
||
return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
|
||
};
|
||
|
||
$postMode = (string)$pick('post.mode', 'paid');
|
||
if (!in_array($postMode, ['free', 'paid'], true)) {
|
||
$postMode = 'paid';
|
||
}
|
||
|
||
$payViewPrice = round((float)$pick('fee.payView', 2), 2);
|
||
$viewModeRaw = (string)$pick('fee.viewMode', '');
|
||
if (!in_array($viewModeRaw, ['free', 'paid'], true)) {
|
||
$viewMode = $payViewPrice <= 0 ? 'free' : 'paid';
|
||
} else {
|
||
$viewMode = $viewModeRaw;
|
||
}
|
||
|
||
return [
|
||
'postMode' => $postMode,
|
||
'freeQuota' => max(0, (int)$pick('post.freeQuota', 0)),
|
||
'freeQuotaCycle' => in_array($pick('post.freeQuotaCycle', 'day'), ['day', 'month', 'total'], true) ? $pick('post.freeQuotaCycle', 'day') : 'day',
|
||
'singlePrice' => max(0, round((float)$pick('post.singlePrice', 5), 2)),
|
||
'viewMode' => $viewMode,
|
||
'payViewPrice' => max(0, $payViewPrice),
|
||
'callNormalDailyQuota' => max(0, (int)$pick('call.normalDailyQuota', 5)),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 解析会员发布折扣数字(1 ~ 10,如 8 代表 8 折,10 代表不打折/原价)
|
||
* 兼容纯数字("8" / "8.5" / "10")与历史中文格式("8 折" / "8.5折")
|
||
*
|
||
* @param mixed $raw 原始配置值
|
||
* @return float 范围 0.1 ~ 10 的折扣值
|
||
*/
|
||
public static function parseDiscountNum($raw)
|
||
{
|
||
if (is_numeric($raw)) {
|
||
$num = round((float)$raw, 1);
|
||
if ($num > 0 && $num <= 10) {
|
||
return $num;
|
||
}
|
||
}
|
||
$str = trim((string)$raw);
|
||
if ($str === '原价' || $str === '不打折' || $str === '无折扣') {
|
||
return 10.0;
|
||
}
|
||
if (preg_match('/^(\d+(?:\.\d+)?)\s*折?$/u', $str, $m)) {
|
||
$num = round((float)$m[1], 1);
|
||
if ($num > 0 && $num <= 10) {
|
||
return $num;
|
||
}
|
||
}
|
||
return 8.0;
|
||
}
|
||
|
||
/**
|
||
* 将折扣数字格式化为展示文本(如 8 -> "8 折",10 -> "原价")
|
||
*
|
||
* @param float $discountNum 1~10 的折扣数字
|
||
* @return string
|
||
*/
|
||
public static function formatDiscountText($discountNum)
|
||
{
|
||
$num = round((float)$discountNum, 1);
|
||
if ($num <= 0 || $num >= 10) {
|
||
return '原价';
|
||
}
|
||
$clean = rtrim(rtrim(number_format($num, 1, '.', ''), '0'), '.');
|
||
return $clean . ' 折';
|
||
}
|
||
|
||
/**
|
||
* 根据后台实时配置与启用套餐,自动生成 100% 准确的会员权益文案列表(无需手写)
|
||
*
|
||
* @param int $viewQuota 每日免费浏览完整信息条数(0 = 不限)
|
||
* @param int $callQuota 每日拨打电话次数(0 = 不限)
|
||
* @param float $discountNum 超额单条发布折扣值(1~10)
|
||
* @return array<int,string>
|
||
*/
|
||
public static function buildAutoBenefits($viewQuota, $callQuota, $discountNum)
|
||
{
|
||
$list = [];
|
||
|
||
// 1. 完整信息浏览特权
|
||
if ((int)$viewQuota > 0) {
|
||
$list[] = '每日免费浏览 ' . (int)$viewQuota . ' 条完整信息';
|
||
} else {
|
||
$list[] = '完整信息无限次免费浏览';
|
||
}
|
||
|
||
// 2. 拨打电话特权
|
||
if ((int)$callQuota > 0) {
|
||
$list[] = '每日可拨打 ' . (int)$callQuota . ' 次电话联系发布者';
|
||
} else {
|
||
$list[] = '拨打电话不限次数';
|
||
}
|
||
|
||
// 3. 套餐赠送免费发布额度特权(实时读取 fc_plan 启用套餐)
|
||
try {
|
||
$plans = Db::name('fc_plan')->where('enabled', 1)->order('sort asc')->select();
|
||
$quotaParts = [];
|
||
foreach ($plans as $p) {
|
||
$q = (int)(isset($p['post_quota']) ? $p['post_quota'] : 0);
|
||
if ($q > 0) {
|
||
$quotaParts[] = $p['name'] . '赠 ' . $q . ' 条';
|
||
}
|
||
}
|
||
if (!empty($quotaParts)) {
|
||
$list[] = '开通立赠免费发布额度(' . implode(' / ', $quotaParts) . ')';
|
||
} else {
|
||
$list[] = '会员有效期内尊享专属信息发布通道';
|
||
}
|
||
} catch (\Exception $e) {
|
||
$list[] = '开通套餐立赠海量免费发布额度';
|
||
}
|
||
|
||
// 4. 超额单条发布折扣特权
|
||
$num = round((float)$discountNum, 1);
|
||
if ($num > 0 && $num < 10) {
|
||
$clean = rtrim(rtrim(number_format($num, 1, '.', ''), '0'), '.');
|
||
$list[] = '赠送额度用尽后,单条付费发布专享 ' . $clean . ' 折优惠';
|
||
}
|
||
|
||
// 5. 尊贵身份标识
|
||
$list[] = '全站帖子详情与个人主页点亮 VIP 专属金标';
|
||
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* 为单个会员套餐生成专属权益文案列表(直接读取套餐行权益字段)
|
||
*
|
||
* @param array $plan fc_plan 表行数据
|
||
* @return array<int,string>
|
||
*/
|
||
public static function buildPlanBenefits(array $plan)
|
||
{
|
||
$postQuota = isset($plan['post_quota']) ? max(0, (int)$plan['post_quota']) : 0;
|
||
$viewQuota = isset($plan['view_quota']) ? max(0, (int)$plan['view_quota']) : 20;
|
||
$callQuota = isset($plan['call_quota']) ? max(0, (int)$plan['call_quota']) : 30;
|
||
$discount = isset($plan['post_discount']) ? self::parseDiscountNum($plan['post_discount']) : 8.0;
|
||
|
||
$list = [];
|
||
|
||
if ($viewQuota > 0) {
|
||
$list[] = '每日免费浏览 ' . $viewQuota . ' 条完整信息';
|
||
} else {
|
||
$list[] = '完整信息无限次免费浏览';
|
||
}
|
||
|
||
if ($callQuota > 0) {
|
||
$list[] = '每日可拨打 ' . $callQuota . ' 次电话联系发布者';
|
||
} else {
|
||
$list[] = '拨打电话不限次数';
|
||
}
|
||
|
||
if ($postQuota > 0) {
|
||
$list[] = '开通本套餐立赠 ' . $postQuota . ' 条免费信息发布额度';
|
||
} else {
|
||
$list[] = '会员有效期内尊享专属信息发布通道';
|
||
}
|
||
|
||
if ($discount > 0 && $discount < 10) {
|
||
$clean = rtrim(rtrim(number_format($discount, 1, '.', ''), '0'), '.');
|
||
$list[] = '赠送额度用尽后,单条付费发布专享 ' . $clean . ' 折优惠';
|
||
}
|
||
|
||
$list[] = '全站帖子详情与个人主页点亮 VIP 专属金标';
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* 解析指定用户(或默认推荐套餐)对应的会员权益数据(真源:fc_plan 套餐表)
|
||
*
|
||
* @param array|null $ext fc_user_ext 行(可为空)
|
||
* @return array{viewQuota:int,callQuota:int,postDiscountNum:float,postDiscount:string,benefits:array}
|
||
*/
|
||
public static function resolveMemberBenefit($ext = null)
|
||
{
|
||
$plan = null;
|
||
try {
|
||
if (is_array($ext) && !empty($ext['member_level'])) {
|
||
$plan = Db::name('fc_plan')
|
||
->where('name', (string)$ext['member_level'])
|
||
->find();
|
||
}
|
||
if (!$plan) {
|
||
$plan = Db::name('fc_plan')
|
||
->where('enabled', 1)
|
||
->order('sort asc, id asc')
|
||
->find();
|
||
}
|
||
} catch (\Exception $e) {
|
||
$plan = null;
|
||
}
|
||
|
||
if ($plan) {
|
||
$viewQuota = isset($plan['view_quota']) ? max(0, (int)$plan['view_quota']) : 20;
|
||
$callQuota = isset($plan['call_quota']) ? max(0, (int)$plan['call_quota']) : 30;
|
||
$discountNum = isset($plan['post_discount']) ? self::parseDiscountNum($plan['post_discount']) : 8.0;
|
||
$postDiscount = self::formatDiscountText($discountNum);
|
||
$benefitsArr = self::buildPlanBenefits($plan);
|
||
return [
|
||
'viewQuota' => $viewQuota,
|
||
'callQuota' => $callQuota,
|
||
'postDiscountNum' => $discountNum,
|
||
'postDiscount' => $postDiscount,
|
||
'benefits' => $benefitsArr,
|
||
];
|
||
}
|
||
|
||
// 兜底:若套餐表为空,从历史配置回退
|
||
return self::getMemberBenefitConfig();
|
||
}
|
||
public static function getMemberBenefitConfig()
|
||
{
|
||
$cfg = self::values();
|
||
$pick = function ($key, $default = '') use ($cfg) {
|
||
return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
|
||
};
|
||
|
||
$benefitRaw = $pick('member.benefit', '');
|
||
$benefitCfg = [];
|
||
if ($benefitRaw) {
|
||
$decoded = json_decode($benefitRaw, true);
|
||
if (is_array($decoded)) {
|
||
$benefitCfg = $decoded;
|
||
}
|
||
}
|
||
|
||
$viewQuota = isset($benefitCfg['viewQuota']) ? max(0, (int)$benefitCfg['viewQuota']) : 20;
|
||
$callQuota = isset($benefitCfg['callQuota']) ? max(0, (int)$benefitCfg['callQuota']) : 30;
|
||
$rawDiscount = isset($benefitCfg['postDiscountNum'])
|
||
? $benefitCfg['postDiscountNum']
|
||
: (isset($benefitCfg['postDiscount']) ? $benefitCfg['postDiscount'] : 8);
|
||
$discountNum = self::parseDiscountNum($rawDiscount);
|
||
$postDiscount = self::formatDiscountText($discountNum);
|
||
$benefitsArr = self::buildAutoBenefits($viewQuota, $callQuota, $discountNum);
|
||
|
||
return [
|
||
'viewQuota' => $viewQuota,
|
||
'callQuota' => $callQuota,
|
||
'postDiscountNum' => $discountNum,
|
||
'postDiscount' => $postDiscount,
|
||
'benefits' => $benefitsArr,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 获取「二级分销配置」当前值(供 fc/distrelation 页面读取)
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function getDistributionConfig()
|
||
{
|
||
$cfg = self::values();
|
||
$pick = function ($key, $default = '') use ($cfg) {
|
||
return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default;
|
||
};
|
||
|
||
return [
|
||
'distEnabled' => (int)$pick('dist.enabled', 1),
|
||
'distLevel' => (int)$pick('dist.level', 2),
|
||
'distLevel1' => round((float)$pick('dist.level1Rate', 20), 2),
|
||
'distLevel2' => round((float)$pick('dist.level2Rate', 10), 2),
|
||
'distSettle' => (string)$pick('dist.settleCycle', 'T+7'),
|
||
'distMinAmount' => round((float)$pick('dist.minWithdraw', 50), 2),
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 保存业务配置(由系统配置页提交)
|
||
*
|
||
* @param array $row 系统配置页提交的 row 数组
|
||
* @return void
|
||
* @throws \Exception
|
||
*/
|
||
public static function save(array $row)
|
||
{
|
||
$has = function ($prefix) use ($row) {
|
||
foreach ($row as $key => $value) {
|
||
if (strpos($key, $prefix) === 0) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
};
|
||
|
||
if ($has('fc_base_')) {
|
||
self::saveBase($row);
|
||
}
|
||
if ($has('fc_post_')) {
|
||
self::savePublish($row);
|
||
}
|
||
if ($has('fc_member_')) {
|
||
self::saveMember($row);
|
||
}
|
||
if ($has('fc_pay_')) {
|
||
self::savePay($row);
|
||
}
|
||
if ($has('fc_dist_')) {
|
||
self::saveDistribution($row);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 支付配置
|
||
*
|
||
* @param array $row
|
||
* @return void
|
||
*/
|
||
protected static function savePay(array $row)
|
||
{
|
||
$enabled = isset($row['fc_pay_passwordEnabled']) ? (int)$row['fc_pay_passwordEnabled'] : 0;
|
||
self::put('pay.payPasswordEnabled', $enabled ? 1 : 0, 'pay', '余额支付密码开关(开启后余额支付需验证 6 位支付密码)');
|
||
|
||
if (isset($row['fc_pay_virtualPayAudit'])) {
|
||
$audit = (int)$row['fc_pay_virtualPayAudit'];
|
||
self::put('pay.virtualPayAudit', $audit ? 1 : 0, 'pay', '微信提审防审模式开关(1=审核中隐藏支付, 0=正常运营)');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 小程序基础信息
|
||
*
|
||
* @param array $row
|
||
* @return void
|
||
* @throws \Exception
|
||
*/
|
||
protected static function saveBase(array $row)
|
||
{
|
||
$appName = isset($row['fc_base_appName']) ? trim((string)$row['fc_base_appName']) : '';
|
||
if ($appName === '') {
|
||
throw new \Exception('小程序名称不能为空');
|
||
}
|
||
|
||
$values = [
|
||
'base.appName' => $appName,
|
||
'base.logo' => isset($row['fc_base_logo']) ? trim((string)$row['fc_base_logo']) : '',
|
||
'base.servicePhone' => isset($row['fc_base_servicePhone']) ? trim((string)$row['fc_base_servicePhone']) : '',
|
||
'base.shareTitle' => isset($row['fc_base_shareTitle']) ? trim((string)$row['fc_base_shareTitle']) : '',
|
||
'base.aboutUs' => isset($row['fc_base_aboutUs']) ? trim((string)$row['fc_base_aboutUs']) : '',
|
||
];
|
||
foreach ($values as $key => $value) {
|
||
self::put($key, $value, 'base', '小程序配置');
|
||
}
|
||
|
||
// 兼容旧 ui.* 键:名称/LOGO 同步一份,避免旧数据在用户端回落时“复活”
|
||
self::put('ui.siteName', $appName, 'ui', '站点名称');
|
||
self::put('ui.logo', $values['base.logo'], 'ui', '自定义LOGO');
|
||
|
||
// 微信登录参数(真源同为 fa_fc_config,由 WxMini 读取):
|
||
// AppSecret 不回显、留空表示不修改,避免表单提交把已保存的密钥冲成空串
|
||
$wxAppid = isset($row['fc_wx_appid']) ? trim((string)$row['fc_wx_appid']) : '';
|
||
if ($wxAppid !== '') {
|
||
self::put('wx.appid', $wxAppid, 'wx', '小程序AppID(微信登录用)');
|
||
}
|
||
$wxSecret = isset($row['fc_wx_secret']) ? trim((string)$row['fc_wx_secret']) : '';
|
||
if ($wxSecret !== '') {
|
||
self::put('wx.secret', $wxSecret, 'wx', '小程序AppSecret(仅服务端持有,不下发前端)');
|
||
}
|
||
|
||
// 管理接口白名单(/api/v1/admin/**):留空表示不允许任何小程序端账号调用
|
||
if (array_key_exists('fc_admin_userIds', $row)) {
|
||
self::put('admin.userIds', trim((string)$row['fc_admin_userIds']), 'admin', '管理接口允许的用户ID(逗号分隔)');
|
||
}
|
||
if (array_key_exists('fc_admin_mobiles', $row)) {
|
||
self::put('admin.mobiles', trim((string)$row['fc_admin_mobiles']), 'admin', '管理接口允许的手机号(逗号分隔)');
|
||
}
|
||
|
||
// 订阅消息模板 ID
|
||
if (array_key_exists('fc_wx_tplAudit', $row)) {
|
||
self::put('wx.tplAudit', trim((string)$row['fc_wx_tplAudit']), 'wx', '审核结果订阅消息模板ID');
|
||
}
|
||
if (array_key_exists('fc_wx_tplTransfer', $row)) {
|
||
self::put('wx.tplTransfer', trim((string)$row['fc_wx_tplTransfer']), 'wx', '提现到账订阅消息模板ID');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存发布模式、免费额度与联系方式浏览收费配置
|
||
*
|
||
* @param array $row
|
||
* @return void
|
||
* @throws \Exception
|
||
*/
|
||
public static function savePublish(array $row)
|
||
{
|
||
$mode = isset($row['fc_post_mode']) ? trim((string)$row['fc_post_mode']) : 'free';
|
||
if (!in_array($mode, ['free', 'paid'], true)) {
|
||
throw new \Exception('发布模式只支持「免费发布」或「付费发布」');
|
||
}
|
||
|
||
$cycle = isset($row['fc_post_freeQuotaCycle']) ? trim((string)$row['fc_post_freeQuotaCycle']) : 'day';
|
||
if (!in_array($cycle, ['day', 'month', 'total'], true)) {
|
||
throw new \Exception('免费条数统计周期只支持「每日 / 每月 / 累计」');
|
||
}
|
||
|
||
$num = function ($key, $default) use ($row) {
|
||
if (!isset($row[$key]) || $row[$key] === '') {
|
||
return $default;
|
||
}
|
||
return round((float)$row[$key], 2);
|
||
};
|
||
$int = function ($key, $default) use ($row) {
|
||
if (!isset($row[$key]) || $row[$key] === '') {
|
||
return $default;
|
||
}
|
||
return (int)$row[$key];
|
||
};
|
||
|
||
$single = $num('fc_post_singlePrice', 5);
|
||
$freeQuota = $int('fc_post_freeQuota', 0);
|
||
|
||
if ($single < 0) {
|
||
throw new \Exception('单条发布价格不能为负');
|
||
}
|
||
if ($freeQuota < 0) {
|
||
throw new \Exception('免费发布条数不能为负');
|
||
}
|
||
|
||
$values = [
|
||
'post.mode' => $mode,
|
||
'post.singlePrice' => $single,
|
||
'post.freeQuota' => $freeQuota,
|
||
'post.freeQuotaCycle' => $cycle,
|
||
];
|
||
foreach ($values as $key => $value) {
|
||
self::put($key, $value, 'post', '发布配置');
|
||
}
|
||
|
||
// 完整信息浏览规则(若表单同时传入则一并保存)
|
||
if (isset($row['fc_fee_viewMode']) || isset($row['fc_fee_payView'])) {
|
||
$viewMode = isset($row['fc_fee_viewMode']) ? trim((string)$row['fc_fee_viewMode']) : 'paid';
|
||
if (!in_array($viewMode, ['free', 'paid'], true)) {
|
||
$viewMode = 'paid';
|
||
}
|
||
$payView = $num('fc_fee_payView', 2);
|
||
if ($payView < 0) {
|
||
throw new \Exception('非会员单条解锁价格不能为负');
|
||
}
|
||
// 若选了完全免费公开,或单次价格设为 0,统一保障免费可看
|
||
self::put('fee.viewMode', $viewMode, 'fee', '完整信息浏览模式:free免费公开 / paid付费或会员额度解锁');
|
||
self::put('fee.payView', $viewMode === 'free' ? 0 : $payView, 'fee', '非会员单条解锁完整信息价格');
|
||
}
|
||
|
||
// 普通用户(非会员)每日拨打次数限制(会员拨打次数由各套餐 call_quota 控制)
|
||
if (isset($row['fc_call_normalDailyQuota'])) {
|
||
$normalQuota = max(0, $int('fc_call_normalDailyQuota', 5));
|
||
self::put('call.normalDailyQuota', $normalQuota, 'call', '普通用户每日免费拨打次数上限(0表示不限制)');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 保存 VIP 会员通用特权配置(纯数值驱动,自动生成前端展示文案)
|
||
*
|
||
* @param array $row 提交的表单数组
|
||
* @return void
|
||
* @throws \Exception
|
||
*/
|
||
public static function saveMember(array $row)
|
||
{
|
||
$viewQuota = isset($row['fc_member_viewQuota']) ? max(0, (int)$row['fc_member_viewQuota']) : 20;
|
||
$callQuota = isset($row['fc_member_callQuota']) ? max(0, (int)$row['fc_member_callQuota']) : 30;
|
||
$rawDiscount = isset($row['fc_member_postDiscountNum'])
|
||
? $row['fc_member_postDiscountNum']
|
||
: (isset($row['fc_member_postDiscount']) ? $row['fc_member_postDiscount'] : 8);
|
||
$discountNum = self::parseDiscountNum($rawDiscount);
|
||
if ($discountNum <= 0 || $discountNum > 10) {
|
||
throw new \Exception('发布折扣需在 0.1 ~ 10 之间(10 表示不打折)');
|
||
}
|
||
$postDiscount = self::formatDiscountText($discountNum);
|
||
$benefits = self::buildAutoBenefits($viewQuota, $callQuota, $discountNum);
|
||
|
||
$current = [];
|
||
$raw = Db::name('fc_config')->where('key', 'member.benefit')->value('value');
|
||
if ($raw) {
|
||
$decoded = json_decode($raw, true);
|
||
if (is_array($decoded)) {
|
||
$current = $decoded;
|
||
}
|
||
}
|
||
$current['viewQuota'] = $viewQuota;
|
||
$current['callQuota'] = $callQuota;
|
||
$current['postDiscountNum'] = $discountNum;
|
||
$current['postDiscount'] = $postDiscount;
|
||
$current['benefits'] = $benefits;
|
||
|
||
self::put('member.benefit', $current, 'member', '会员权益配置');
|
||
}
|
||
|
||
/**
|
||
* 分销参数保存
|
||
*
|
||
* @param array $row
|
||
* @return void
|
||
* @throws \Exception
|
||
*/
|
||
public static function saveDistribution(array $row)
|
||
{
|
||
$enabled = isset($row['fc_dist_enabled']) ? (int)$row['fc_dist_enabled'] : 1;
|
||
$level = isset($row['fc_dist_level']) ? (int)$row['fc_dist_level'] : 2;
|
||
$rate1 = isset($row['fc_dist_level1Rate']) ? round((float)$row['fc_dist_level1Rate'], 2) : 20;
|
||
$rate2 = isset($row['fc_dist_level2Rate']) ? round((float)$row['fc_dist_level2Rate'], 2) : 10;
|
||
$settle = isset($row['fc_dist_settleCycle']) ? trim((string)$row['fc_dist_settleCycle']) : 'T+7';
|
||
$minWithdraw = isset($row['fc_dist_minWithdraw']) ? round((float)$row['fc_dist_minWithdraw'], 2) : 50;
|
||
|
||
if (!in_array($level, [1, 2], true)) {
|
||
throw new \Exception('分佣层级只支持一级或二级');
|
||
}
|
||
if ($rate1 < 0 || $rate1 > 100 || $rate2 < 0 || $rate2 > 100) {
|
||
throw new \Exception('分佣比例必须在 0-100 之间');
|
||
}
|
||
if ($minWithdraw < 0) {
|
||
throw new \Exception('最低提现金额不能为负');
|
||
}
|
||
if ($settle === '') {
|
||
$settle = 'T+7';
|
||
}
|
||
|
||
$values = [
|
||
'dist.enabled' => $enabled ? 1 : 0,
|
||
'dist.level' => $level,
|
||
'dist.level1Rate' => $rate1,
|
||
'dist.level2Rate' => $rate2,
|
||
'dist.settleCycle' => $settle,
|
||
'dist.minWithdraw' => $minWithdraw,
|
||
];
|
||
foreach ($values as $key => $value) {
|
||
self::put($key, $value, 'dist', '分销配置');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 读取 fc_config 全部配置
|
||
*
|
||
* @return array
|
||
*/
|
||
protected static function values()
|
||
{
|
||
$result = [];
|
||
try {
|
||
foreach (Db::name('fc_config')->select() as $row) {
|
||
$result[$row['key']] = $row['value'];
|
||
}
|
||
} catch (\Exception $e) {
|
||
// 业务表尚未初始化时,系统配置页仍可正常打开(使用默认值)
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 写入单项配置
|
||
*
|
||
* @param string $key
|
||
* @param mixed $value
|
||
* @param string $group
|
||
* @param string $remark
|
||
* @return void
|
||
*/
|
||
protected static function put($key, $value, $group, $remark)
|
||
{
|
||
$now = time();
|
||
$value = is_scalar($value) || $value === null ? (string)$value : json_encode($value, JSON_UNESCAPED_UNICODE);
|
||
$exist = Db::name('fc_config')->where('key', $key)->find();
|
||
if ($exist) {
|
||
Db::name('fc_config')->where('key', $key)->update([
|
||
'value' => $value,
|
||
'group_key' => $group,
|
||
'remark' => $remark,
|
||
'updatetime' => $now,
|
||
]);
|
||
} else {
|
||
Db::name('fc_config')->insert([
|
||
'key' => $key,
|
||
'value' => $value,
|
||
'group_key' => $group,
|
||
'remark' => $remark,
|
||
'createtime' => $now,
|
||
'updatetime' => $now,
|
||
]);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 构造系统配置页的单个配置项
|
||
*
|
||
* @param string $name
|
||
* @param string $title
|
||
* @param string $type
|
||
* @param mixed $value
|
||
* @param string $tip
|
||
* @param string $rule
|
||
* @param array $content
|
||
* @return array
|
||
*/
|
||
protected static function item($name, $title, $type, $value, $tip = '', $rule = '', array $content = [])
|
||
{
|
||
return [
|
||
'name' => $name,
|
||
'title' => $title,
|
||
'type' => $type,
|
||
'value' => $value,
|
||
'content' => $content,
|
||
'tip' => $tip,
|
||
'rule' => $rule,
|
||
'extend' => '',
|
||
'extend_html' => '',
|
||
'setting' => [],
|
||
'visible' => '',
|
||
'id' => 0,
|
||
];
|
||
}
|
||
}
|