文件
fenleixinxi/application/admin/command/FcInstall.php
linda d452be1d60 feat(api): v1.0.10 免费发布条数(周期 每日/每月/累计)+ 计费顺序(套餐额度 → 免费条数 → 单条付费)
需求:免费发布不能是「要么全免费、要么一开付费就是硬付费墙」,需要可配「前 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 无语法错误。
2026-10-17 11:45:36 +08:00

1025 行
54 KiB
PHP

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

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

<?php
namespace app\admin\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\console\input\Option;
use think\Db;
/**
* 同城分类信息 · 业务表安装器
*
* 用法:
* php think fc:install 建表 + 首次注入种子数据(已存在则跳过种子)+ 轮播底色与春绿主题对齐
* php think fc:install --reset 建表 + 强制重置种子数据
*
* 说明:全部使用 CREATE TABLE IF NOT EXISTS,幂等安全,不影响 FastAdmin 原有表。
* 不加 --reset 时只补建表结构、对齐内置轮播底色,业务数据(发布/订单/会员)一律不动。
*/
class FcInstall extends Command
{
/**
* 内置轮播(标题 / 跳转 / 底色)——种子数据与「底色升级」共用同一份定义。
* color 取前端 uni.scss 的春绿主题(v4):105deg 亮绿(左)→ 深绿(右),
* 文字区落在亮绿端(小程序用墨绿字 #04301E),白色插画落在右侧深绿端;
* image 留空时小程序用「亮绿底 + 墨绿字 + 白色插画」内置配图渲染。
*/
const BANNERS = [
['同城好物节 · 二手直降', '/pages/post/list?categoryId=3', 'linear-gradient(105deg, #00e070 0%, #00c860 42%, #046b3b 100%)'],
['招聘旺季 · 免费发布', '/pages/post/create', 'linear-gradient(105deg, #00d96c 0%, #00b85c 40%, #023a21 100%)'],
['本地服务 · 上门更省心', '/pages/post/list?categoryId=5', 'linear-gradient(105deg, #00e878 0%, #049e52 45%, #045c34 100%)'],
];
/**
* 需要被主题升级覆盖的历史底色特征:
* ① 旧暖橙纯色(#FF8A5B 一族);② v1.2 生态绿(#0e7c66 一族);
* 命中任一即按标题覆盖为上面的春绿渐变(只动 color 字段,不动业务数据)。
*/
const LEGACY_COLOR_KEYS = [
'18a587', '0e7c66', '0a5d4d', '04302a', '16897f', '0e6b62', '07443e',
'ff8a5b', 'ffb86b', 'ff6f61',
];
protected function configure()
{
$this->setName('fc:install')
->addOption('reset', null, Option::VALUE_NONE, '强制重置种子数据')
->setDescription('同城分类信息业务表安装 + 种子数据');
}
protected function execute(Input $input, Output $output)
{
$prefix = \think\Config::get('database.prefix');
$reset = $input->hasOption('reset') && $input->getOption('reset');
$output->writeln('==> 建表(前缀 ' . $prefix . ')');
$tables = $this->tables();
foreach ($tables as $name => $sql) {
// 占位符 #P# 位于反引号内(形如 `#P#config`),故按裸串替换
Db::execute(str_replace('#P#', $prefix . 'fc_', $sql));
$output->writeln(' - ' . $prefix . 'fc_' . $name);
}
$seeded = Db::name('fc_config')->count();
if ($reset) {
$this->truncateAll();
$seeded = 0;
}
if (!$seeded) {
$output->writeln('==> 注入种子数据');
$this->seed($output);
} else {
$output->writeln('==> 已有数据,跳过种子(如需重置请加 --reset)');
}
$output->writeln('==> 轮播底色与春绿主题对齐');
$this->upgradeBanners($output);
$output->writeln('==> 发布模式与发布套餐升级');
$this->upgradePublish($output);
$output->writeln('==> 初始化后台菜单');
$this->menu($output);
$output->writeln('==> 完成');
}
/**
* 轮播底色升级(幂等,可重复执行)
*
* 主题迭代史:v1.0.x 暖橙纯色(#FF8A5B 一族)→ v1.2 生态绿(#0e7c66 一族)→ v1.3 春绿(本次)。
* 线上库若还是历史值,这里按「标题」精确匹配把内置 3 条轮播的底色覆盖为春绿渐变;
* 只动 color 字段,不新增/删除记录,也不影响发布、订单等业务数据。
* 判定规则:color 为空/非渐变(旧纯色),或命中 LEGACY_COLOR_KEYS(上一版主题色)→ 覆盖;
* 自定义过标题、或已是春绿渐变(#00e070 一族)的轮播不会被改动(幂等)。
*/
private function upgradeBanners(Output $output)
{
// ① 先确保 color 列容得下 CSS 渐变字符串:旧库是 varchar(16),只够存 #RRGGBB,
// 直接写 linear-gradient(...) 会被截断(严格模式还会报 1406 Data too long)。
$table = \think\Config::get('database.prefix') . 'fc_banner';
$cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE 'color'");
$row = isset($cols[0]) ? $cols[0] : [];
$type = isset($row['Type']) ? (string)$row['Type'] : (isset($row['type']) ? (string)$row['type'] : '');
if (preg_match('/^varchar\((\d+)\)$/i', $type, $m) && (int)$m[1] < 255) {
Db::execute("ALTER TABLE `{$table}` MODIFY COLUMN `color` varchar(255) NOT NULL DEFAULT ''"
. " COMMENT '底色:CSS 渐变 linear-gradient(...),留空则用内置春绿渐变'");
$output->writeln(' - 轮播底色列 color 已由 ' . $type . ' 扩容为 varchar(255)(存 CSS 渐变)');
}
// ② 历史底色(旧纯色 / 上一版生态绿)→ 春绿渐变
$upgraded = 0;
foreach (self::BANNERS as $b) {
$row = Db::name('fc_banner')->where('title', $b[0])->find();
if (!$row) {
continue;
}
$color = strtolower((string)$row['color']);
$isGradient = strpos($color, 'gradient(') !== false;
$isLegacy = false;
foreach (self::LEGACY_COLOR_KEYS as $k) {
if (strpos($color, $k) !== false) {
$isLegacy = true;
break;
}
}
if (!$isGradient || $isLegacy) {
Db::name('fc_banner')->where('id', $row['id'])
->update(['color' => $b[2], 'updatetime' => time()]);
$upgraded++;
}
}
$output->writeln($upgraded
? ' - 已把 ' . $upgraded . ' 条内置轮播底色升级为春绿渐变(105deg 亮绿→深绿)'
: ' - 轮播底色已是春绿渐变或非内置轮播,无需处理');
}
/**
* 发布模式与发布套餐升级(幂等,可重复执行)
*
* 背景:v1.0.7 新增「免费发布 / 付费发布」开关与付费发布的三档定价
* · 单条价格 post.singlePrice
* · 免费发布条数 post.freeQuota / post.freeQuotaCycle(v1.0.10:达到条数后按单条价格计费)
* · 按月(价格 + 含条数)post.monthPrice / post.monthQuota
* · 按年(价格 + 含条数)post.yearPrice / post.yearQuota
* 老库缺列/缺配置时补齐,**不覆盖运营已改过的值**:
* ① fc_user_ext 补 post_quota_left / post_quota_expire(剩余发布条数 + 有效期)
* ② fc_plan 补 post_quota(套餐含发布条数,月/年卡赠送额度)
* ③ fc_config 补 post.* 八项(已存在则跳过)
* ④ 月卡/年卡的价格与条数以配置为准做一次同步(配置缺失时反向用套餐值填配置)
*/
private function upgradePublish(Output $output)
{
$prefix = \think\Config::get('database.prefix');
$changed = 0;
// ① 用户扩展表:发布额度
$extTable = $prefix . 'fc_user_ext';
if (!$this->hasColumn($extTable, 'post_quota_left')) {
Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `post_quota_left` int(10) NOT NULL DEFAULT 0"
. " COMMENT '剩余发布条数(月/年套餐赠送)' AFTER `free_view_used`");
$output->writeln(' - ' . $extTable . ' 新增列 post_quota_left');
$changed++;
}
if (!$this->hasColumn($extTable, 'post_quota_expire')) {
Db::execute("ALTER TABLE `{$extTable}` ADD COLUMN `post_quota_expire` int(10) NULL"
. " COMMENT '发布额度有效期(=会员到期时间)' AFTER `post_quota_left`");
$output->writeln(' - ' . $extTable . ' 新增列 post_quota_expire');
$changed++;
}
// ② 会员套餐:含发布条数
$planTable = $prefix . 'fc_plan';
if (!$this->hasColumn($planTable, 'post_quota')) {
Db::execute("ALTER TABLE `{$planTable}` ADD COLUMN `post_quota` int(10) NOT NULL DEFAULT 0"
. " COMMENT '含发布条数(月/年套餐赠送额度)' AFTER `days`");
$output->writeln(' - ' . $planTable . ' 新增列 post_quota');
$changed++;
}
// ③ 配置项(只补缺失的,不覆盖运营已改的值)
$defaults = [
['post.mode', 'free', 'post', '发布模式:free 免费发布 / paid 付费发布'],
['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'],
['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'],
['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'],
['post.monthPrice', '19.9', 'post', '按月发布价格(元)'],
['post.monthQuota', '30', 'post', '按月含发布条数'],
['post.yearPrice', '168', 'post', '按年发布价格(元)'],
['post.yearQuota', '500', 'post', '按年含发布条数'],
];
$now = time();
$added = [];
foreach ($defaults as $d) {
if (Db::name('fc_config')->where('key', $d[0])->find()) {
continue;
}
Db::name('fc_config')->insert([
'key' => $d[0], 'value' => $d[1], 'group_key' => $d[2], 'remark' => $d[3],
'createtime' => $now, 'updatetime' => $now,
]);
$added[] = $d[0];
}
if ($added) {
$output->writeln(' - 新增配置 ' . count($added) . ' 项:' . implode(' / ', $added));
$changed++;
}
// ④ 月/年卡「价格 + 含发布条数」与配置对齐
$map = ['M1' => ['post.monthPrice', 'post.monthQuota'], 'M12' => ['post.yearPrice', 'post.yearQuota']];
$synced = 0;
foreach ($map as $code => $keys) {
$plan = Db::name('fc_plan')->where('plan_code', $code)->find();
if (!$plan) {
continue;
}
$price = round((float)$this->cfgValue($keys[0], $plan['price']), 2);
$quota = (int)$this->cfgValue($keys[1], $plan['post_quota']);
if (round((float)$plan['price'], 2) !== $price || (int)$plan['post_quota'] !== $quota) {
Db::name('fc_plan')->where('id', $plan['id'])->update([
'price' => $price,
'post_quota' => $quota,
'updatetime' => time(),
]);
$synced++;
}
}
if ($synced) {
$output->writeln(' - 会员套餐已与发布配置对齐 ' . $synced . ' 条(月/年卡价格与含条数)');
$changed++;
}
$output->writeln($changed
? ' - 完成(发布模式 ' . $this->cfgValue('post.mode', 'free') . ')'
: ' - 发布模式与套餐配置已是最新,无需处理');
}
/** 表是否存在某列 */
private function hasColumn($table, $column)
{
$cols = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '" . addslashes($column) . "'");
return !empty($cols);
}
/** 读配置默认值(不依赖 api 基类的 cfg 缓存) */
private function cfgValue($key, $default = null)
{
$row = Db::name('fc_config')->where('key', $key)->find();
if (!$row || $row['value'] === '' || $row['value'] === null) {
return $default;
}
return $row['value'];
}
/**
* 初始化 FastAdmin 后台菜单(幂等)
*
* 结构:
* 同城业务 (fc)
* ├─ 内容管理 分类管理 / 轮播管理 / 信息管理 / 评价管理 / 举报处理
* ├─ 用户与商家 商家管理 / 会员管理 / 消息管理
* ├─ 交易管理 订单管理 / 会员套餐 / 资金流水 / 佣金明细 / 付费查看记录
* ├─ 分销管理 分销关系 / 提现审核
* └─ 基础数据 服务区域 / 草稿箱
*/
protected function menu(Output $output)
{
$now = time();
$ruleTable = Db::name('auth_rule');
// 1) 顶级菜单
$top = $ruleTable->where('name', 'fc')->find();
$topData = [
'type' => 'file', 'title' => '同城业务', 'icon' => 'fa fa-map-marker',
'ismenu' => 1, 'menutype' => 'addtabs', 'weigh' => 95,
'status' => 'normal', 'updatetime' => $now,
];
if ($top) {
$ruleTable->where('id', $top['id'])->update($topData);
$topId = (int)$top['id'];
} else {
$topData['pid'] = 0;
$topData['name'] = 'fc';
$topData['url'] = '';
$topData['createtime'] = $now;
$topId = (int)$ruleTable->insertGetId($topData);
}
// 2) 分组菜单
$groups = [
['fc/content', '内容管理', 'fa fa-file-text-o', 90],
['fc/user', '用户与商家', 'fa fa-users', 80],
['fc/trade', '交易管理', 'fa fa-shopping-cart', 70],
['fc/dist', '分销管理', 'fa fa-share-alt', 60],
['fc/base', '基础数据', 'fa fa-database', 50],
];
$groupId = [];
foreach ($groups as $g) {
$row = $ruleTable->where('name', $g[0])->find();
$data = [
'type' => 'menu', 'pid' => $topId, 'title' => $g[1], 'icon' => $g[2],
'url' => '', 'ismenu' => 1, 'menutype' => 'addtabs',
'weigh' => $g[3], 'status' => 'normal', 'updatetime' => $now,
];
if ($row) {
$ruleTable->where('id', $row['id'])->update($data);
$groupId[$g[0]] = (int)$row['id'];
} else {
$data['name'] = $g[0];
$data['createtime'] = $now;
$groupId[$g[0]] = (int)$ruleTable->insertGetId($data);
}
}
// 3) 各业务控制器归组 + 中文标题 + 图标 + 排序
$items = [
'fc/category' => ['fc/content', '分类管理', 'fa fa-list', 95],
'fc/banner' => ['fc/content', '轮播管理', 'fa fa-image', 90],
'fc/post' => ['fc/content', '信息管理', 'fa fa-file-text', 85],
'fc/comment' => ['fc/content', '评价管理', 'fa fa-star', 80],
'fc/report' => ['fc/content', '举报处理', 'fa fa-flag', 75],
'fc/merchant' => ['fc/user', '商家管理', 'fa fa-building', 95],
'fc/userext' => ['fc/user', '会员管理', 'fa fa-user-circle', 90],
'fc/message' => ['fc/user', '消息管理', 'fa fa-bell', 85],
'fc/order' => ['fc/trade', '订单管理', 'fa fa-file-text-o', 95],
'fc/plan' => ['fc/trade', '会员套餐', 'fa fa-diamond', 90],
'fc/financeflow' => ['fc/trade', '资金流水', 'fa fa-money', 85],
'fc/commission' => ['fc/trade', '佣金明细', 'fa fa-cny', 80],
'fc/payview' => ['fc/trade', '付费查看记录', 'fa fa-eye', 75],
'fc/distrelation' => ['fc/dist', '分销关系', 'fa fa-sitemap', 95],
'fc/withdraw' => ['fc/dist', '提现审核', 'fa fa-credit-card', 90],
'fc/area' => ['fc/base', '服务区域', 'fa fa-map-o', 95],
'fc/publish' => ['fc/base', '发布与费用配置', 'fa fa-cny', 85],
'fc/draft' => ['fc/base', '草稿箱', 'fa fa-folder-o', 90],
];
$done = 0;
foreach ($items as $name => $m) {
if (!isset($groupId[$m[0]])) {
continue;
}
$aff = $ruleTable->where('name', $name)->update([
'pid' => $groupId[$m[0]],
'title' => $m[1],
'icon' => $m[2],
'weigh' => $m[3],
'ismenu' => 1,
'status' => 'normal',
'updatetime' => $now,
]);
$done += $aff > 0 ? 1 : 1;
}
$output->writeln(' - 顶级 1 / 分组 ' . count($groups) . ' / 功能项 ' . $done);
}
/** 清空业务表(保留表结构) */
protected function truncateAll()
{
foreach (array_keys($this->tables()) as $name) {
Db::execute('TRUNCATE TABLE `' . \think\Config::get('database.prefix') . 'fc_' . $name . '`');
}
}
/** 表结构定义:key 为表名后缀,SQL 中 `#P#` 会被替换为 prefix . 'fc_' */
protected function tables()
{
return [
'config' => "CREATE TABLE IF NOT EXISTS `#P#config` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`key` varchar(64) NOT NULL DEFAULT '' COMMENT '配置键',
`value` text COMMENT '配置值',
`group_key` varchar(32) NOT NULL DEFAULT '' COMMENT '分组',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '说明',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `key` (`key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-业务配置';",
'category' => "CREATE TABLE IF NOT EXISTS `#P#category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL DEFAULT '' COMMENT '分类名',
`icon_key` varchar(32) NOT NULL DEFAULT '' COMMENT '图标key',
`color` varchar(16) NOT NULL DEFAULT '' COMMENT '主题色',
`sort` int(10) NOT NULL DEFAULT '0' COMMENT '排序',
`enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
`post_count` int(10) NOT NULL DEFAULT '0' COMMENT '信息量',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分类';",
'banner' => "CREATE TABLE IF NOT EXISTS `#P#banner` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(64) NOT NULL DEFAULT '',
`image` varchar(255) NOT NULL DEFAULT '' COMMENT '图片,空则用渐变占位',
`link` varchar(255) NOT NULL DEFAULT '' COMMENT '跳转路径',
`color` varchar(255) NOT NULL DEFAULT '' COMMENT '底色:CSS 渐变 linear-gradient(...),留空则用内置春绿渐变',
`sort` int(10) NOT NULL DEFAULT '0',
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-轮播广告位';",
'area' => "CREATE TABLE IF NOT EXISTS `#P#area` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(32) NOT NULL DEFAULT '',
`sort` int(10) NOT NULL DEFAULT '0',
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-服务区域';",
'post' => "CREATE TABLE IF NOT EXISTS `#P#post` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '发布者',
`category_id` int(10) NOT NULL DEFAULT '0',
`title` varchar(80) NOT NULL DEFAULT '',
`content` text,
`cover` varchar(255) NOT NULL DEFAULT '',
`media` text COMMENT '媒体JSON数组',
`price` decimal(10,2) NOT NULL DEFAULT '0.00',
`price_unit` varchar(16) NOT NULL DEFAULT '',
`area` varchar(32) NOT NULL DEFAULT '',
`address` varchar(128) NOT NULL DEFAULT '',
`contact` varchar(32) NOT NULL DEFAULT '' COMMENT '联系人',
`phone` varchar(20) NOT NULL DEFAULT '' COMMENT '联系电话(完整)',
`wechat` varchar(64) NOT NULL DEFAULT '',
`tags` varchar(128) NOT NULL DEFAULT '' COMMENT '标签,逗号分隔',
`is_paid` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否付费发布',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '支付金额',
`pay_type` varchar(16) NOT NULL DEFAULT '',
`audit_status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0待审1通过2驳回',
`audit_reason` varchar(255) NOT NULL DEFAULT '',
`views` int(10) NOT NULL DEFAULT '0',
`likes` int(10) NOT NULL DEFAULT '0',
`comment_count` int(10) NOT NULL DEFAULT '0',
`status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1正常0下架',
`is_top` tinyint(1) NOT NULL DEFAULT '0',
`distance` int(10) NOT NULL DEFAULT '0' COMMENT '距离(米)',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
`deletetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`),
KEY `user_id` (`user_id`),
KEY `audit_status` (`audit_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-信息';",
'draft' => "CREATE TABLE IF NOT EXISTS `#P#draft` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`category_id` int(10) NOT NULL DEFAULT '0',
`title` varchar(80) NOT NULL DEFAULT '',
`content` text,
`media` text,
`contact` varchar(32) NOT NULL DEFAULT '',
`phone` varchar(20) NOT NULL DEFAULT '',
`address` varchar(128) NOT NULL DEFAULT '',
`savedat` int(10) DEFAULT NULL,
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-草稿箱';",
'merchant' => "CREATE TABLE IF NOT EXISTS `#P#merchant` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`name` varchar(64) NOT NULL DEFAULT '',
`logo` varchar(255) NOT NULL DEFAULT '',
`contact` varchar(32) NOT NULL DEFAULT '',
`category` varchar(32) NOT NULL DEFAULT '' COMMENT '经营类目',
`license` varchar(255) NOT NULL DEFAULT '' COMMENT '营业执照',
`audit_status` tinyint(1) NOT NULL DEFAULT '0',
`audit_reason` varchar(255) NOT NULL DEFAULT '',
`shop_status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'normal/pending/banned',
`violations` int(10) NOT NULL DEFAULT '0',
`post_count` int(10) NOT NULL DEFAULT '0',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-商家资质';",
'comment' => "CREATE TABLE IF NOT EXISTS `#P#comment` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`post_id` int(10) unsigned NOT NULL DEFAULT '0',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`score` tinyint(1) NOT NULL DEFAULT '5',
`content` varchar(500) NOT NULL DEFAULT '',
`images` text,
`reply` varchar(500) NOT NULL DEFAULT '',
`replytime` int(10) DEFAULT NULL,
`top` tinyint(1) NOT NULL DEFAULT '0',
`status` tinyint(1) NOT NULL DEFAULT '1',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `post_id` (`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-评价';",
'report' => "CREATE TABLE IF NOT EXISTS `#P#report` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`target_type` varchar(16) NOT NULL DEFAULT 'post' COMMENT 'post/comment',
`target_id` int(10) NOT NULL DEFAULT '0',
`reason` varchar(255) NOT NULL DEFAULT '',
`reporter_id` int(10) unsigned NOT NULL DEFAULT '0',
`status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/handled',
`remark` varchar(255) NOT NULL DEFAULT '',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-举报';",
'order' => "CREATE TABLE IF NOT EXISTS `#P#order` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL DEFAULT '',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`type` varchar(16) NOT NULL DEFAULT 'post' COMMENT 'post/member/recharge',
`title` varchar(64) NOT NULL DEFAULT '',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`pay_type` varchar(16) NOT NULL DEFAULT '',
`status` varchar(16) NOT NULL DEFAULT 'pending' COMMENT 'pending/paid/refunded/closed',
`paytime` int(10) DEFAULT NULL,
`refund_amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`refund_reason` varchar(255) NOT NULL DEFAULT '',
`related_id` int(10) NOT NULL DEFAULT '0' COMMENT '关联业务ID',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `order_no` (`order_no`),
KEY `user_id` (`user_id`),
KEY `type` (`type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-订单';",
'plan' => "CREATE TABLE IF NOT EXISTS `#P#plan` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`plan_code` varchar(16) NOT NULL DEFAULT '' COMMENT 'M1/M3/M12',
`name` varchar(32) NOT NULL DEFAULT '',
`price` decimal(10,2) NOT NULL DEFAULT '0.00',
`origin_price` decimal(10,2) NOT NULL DEFAULT '0.00',
`days` int(10) NOT NULL DEFAULT '30',
`post_quota` int(10) NOT NULL DEFAULT '0' COMMENT '含发布条数(月/年套餐赠送额度)',
`tag` varchar(16) NOT NULL DEFAULT '',
`sort` int(10) NOT NULL DEFAULT '0',
`enabled` tinyint(1) NOT NULL DEFAULT '1',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `plan_code` (`plan_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-会员套餐';",
'user_ext' => "CREATE TABLE IF NOT EXISTS `#P#user_ext` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`openid` varchar(64) NOT NULL DEFAULT '' COMMENT '微信openid',
`user_type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1个人2商家',
`realname_status` tinyint(1) NOT NULL DEFAULT '0',
`is_member` tinyint(1) NOT NULL DEFAULT '0',
`member_level` varchar(32) NOT NULL DEFAULT '',
`member_expiretime` int(10) DEFAULT NULL,
`invite_code` varchar(16) NOT NULL DEFAULT '',
`inviter_id` int(10) unsigned NOT NULL DEFAULT '0',
`free_view_date` varchar(10) NOT NULL DEFAULT '' COMMENT '免费查看额度基准日',
`free_view_used` int(10) NOT NULL DEFAULT '0',
`post_quota_left` int(10) NOT NULL DEFAULT '0' COMMENT '剩余发布条数(月/年套餐赠送)',
`post_quota_expire` int(10) DEFAULT NULL COMMENT '发布额度有效期(=会员到期时间)',
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id` (`user_id`),
KEY `invite_code` (`invite_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-用户扩展';",
'dist_relation' => "CREATE TABLE IF NOT EXISTS `#P#dist_relation` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '下线用户',
`parent_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '上级用户',
`level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1一级2二级',
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_level` (`user_id`,`level`),
KEY `parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分销关系';",
'commission' => "CREATE TABLE IF NOT EXISTS `#P#commission` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '获得佣金的人',
`from_user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '产生佣金的下线',
`order_id` int(10) unsigned NOT NULL DEFAULT '0',
`order_no` varchar(32) NOT NULL DEFAULT '',
`order_type` varchar(16) NOT NULL DEFAULT '',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '佣金金额',
`rate` decimal(5,2) NOT NULL DEFAULT '0.00' COMMENT '分佣比例%',
`level` tinyint(1) NOT NULL DEFAULT '1',
`status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '0未结算1已结算',
`settletime` int(10) DEFAULT NULL,
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-分销佣金';",
'withdraw' => "CREATE TABLE IF NOT EXISTS `#P#withdraw` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`withdraw_no` varchar(32) NOT NULL DEFAULT '',
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`channel` varchar(16) NOT NULL DEFAULT 'wxpay',
`status` varchar(16) NOT NULL DEFAULT 'auditing' COMMENT 'auditing/paid/rejected',
`reason` varchar(255) NOT NULL DEFAULT '' COMMENT '驳回原因',
`paytime` int(10) DEFAULT NULL,
`createtime` int(10) DEFAULT NULL,
`updatetime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `withdraw_no` (`withdraw_no`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-提现';",
'finance_flow' => "CREATE TABLE IF NOT EXISTS `#P#finance_flow` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`flow_no` varchar(32) NOT NULL DEFAULT '',
`type` varchar(16) NOT NULL DEFAULT 'income' COMMENT 'income/expense',
`scene` varchar(32) NOT NULL DEFAULT '' COMMENT '业务场景',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`balance` decimal(12,2) NOT NULL DEFAULT '0.00' COMMENT '账后余额',
`related_id` int(10) NOT NULL DEFAULT '0',
`remark` varchar(255) NOT NULL DEFAULT '',
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `flow_no` (`flow_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-资金流水';",
'message' => "CREATE TABLE IF NOT EXISTS `#P#message` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '0=全体',
`type` varchar(16) NOT NULL DEFAULT 'system' COMMENT 'system/interact',
`title` varchar(64) NOT NULL DEFAULT '',
`content` varchar(500) NOT NULL DEFAULT '',
`isread` tinyint(1) NOT NULL DEFAULT '0',
`readtime` int(10) DEFAULT NULL,
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-消息';",
'pay_view' => "CREATE TABLE IF NOT EXISTS `#P#pay_view` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL DEFAULT '0',
`post_id` int(10) unsigned NOT NULL DEFAULT '0',
`pay_type` varchar(16) NOT NULL DEFAULT '',
`amount` decimal(10,2) NOT NULL DEFAULT '0.00',
`order_id` int(10) unsigned NOT NULL DEFAULT '0',
`createtime` int(10) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `user_post` (`user_id`,`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='同城-付费查看记录';",
];
}
/**
* 注入种子数据
* 说明:与小程序端 common/mock/data.js 的数据集逐字段对齐,
* 保证「前端离线 Mock」与「线上真实接口」返回同一套数据,便于两端对拍。
*/
protected function seed(Output $output)
{
$now = time();
$day = 86400;
$t = function ($offsetDays) use ($now, $day) {
return (int)($now - $offsetDays * $day);
};
/* ---------- 业务配置 ---------- */
$configs = [
['base.appName', '同城分类信息', 'base', '小程序名称'],
['base.logo', '', 'base', 'LOGO地址'],
['base.servicePhone', '400-000-0000', 'base', '客服电话'],
['base.aboutUs', '本地同城分类信息服务平台,招聘/房产/二手/服务一站直达。', 'base', '关于我们'],
['base.shareTitle', '同城分类信息 · 身边的好帮手', 'base', '分享标题'],
['wx.appid', 'wxbfcb778f3019cff8', 'wx', '小程序AppID(jscode2session 用)'],
['wx.secret', 'b023726713eebe3dad3876f5cea8ee6a', 'wx', '小程序AppSecret(仅服务端持有,不下发前端)'],
['pay.mchId', '', 'pay', '微信支付商户号'],
['pay.apiKey', '', 'pay', '微信支付密钥'],
['fee.singlePost', '5', 'fee', '单条发布价格(元)'],
['fee.freePostPerDay', '2', 'fee', '每日免费发布条数'],
['fee.memberMonth', '19.9', 'fee', '月卡价格'],
['fee.memberQuarter', '49.9', 'fee', '季卡价格'],
['fee.memberYear', '168', 'fee', '年卡价格'],
// 发布模式与发布套餐(后台「发布与费用配置」页维护;真源=配置,保存时同步 fc_plan)
['post.mode', 'free', 'post', '发布模式:free 免费发布 / paid 付费发布(默认免费,避免上线即撞付费墙)'],
['post.singlePrice', '5', 'post', '单条发布价格(元)=付费发布标准价'],
['post.freeQuota', '0', 'post', '免费发布条数(0 = 不限制/免费到底;达到条数后按单条发布价格计费)'],
['post.freeQuotaCycle', 'day', 'post', '免费条数统计周期:day 每日 / month 每月 / total 累计'],
['post.monthPrice', '19.9', 'post', '按月发布价格(元)'],
['post.monthQuota', '30', 'post', '按月含发布条数'],
['post.yearPrice', '168', 'post', '按年发布价格(元)'],
['post.yearQuota', '500', 'post', '按年含发布条数'],
['dist.enabled', '1', 'dist', '是否开启分销'],
['dist.level', '2', 'dist', '分佣层级'],
['dist.level1Rate', '20', 'dist', '一级分佣比例%'],
['dist.level2Rate', '10', 'dist', '二级分佣比例%'],
['dist.settleCycle', 'T+7', 'dist', '结算周期'],
['dist.minWithdraw', '50', 'dist', '最低提现金额'],
['member.benefit', json_encode([
'freeView' => 20,
'postDiscount' => '8 折',
'distRate' => '一级 20% / 二级 10%',
'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '分销佣金加速结算'],
], JSON_UNESCAPED_UNICODE), 'member', '会员权益配置'],
['ui.siteName', '同城分类信息', 'ui', '站点名称'],
['ui.logo', '', 'ui', '自定义LOGO'],
['ui.icons', '{}', 'ui', '图标覆盖 {key:url}'],
['ui.updatedAt', date('c'), 'ui', '最后更新时间'],
];
foreach ($configs as $c) {
Db::name('fc_config')->insert([
'key' => $c[0], 'value' => $c[1], 'group_key' => $c[2], 'remark' => $c[3],
'createtime' => $now, 'updatetime' => $now,
]);
}
$output->writeln(' - 配置 ' . count($configs) . ' 项');
/* ---------- 分类 ---------- */
$categories = [
[1, '招聘求职', 'cat-job', '#FF7A59', 1280],
[2, '房产租售', 'cat-house', '#FF9F45', 860],
[3, '二手闲置', 'cat-goods', '#FFB570', 2340],
[4, '车辆买卖', 'cat-car', '#F2994A', 430],
[5, '本地服务', 'cat-service', '#E8845F', 760],
[6, '教育培训', 'cat-education', '#F4A261', 320],
[7, '家政保洁', 'cat-clean', '#FFA07A', 210],
[8, '同城交友', 'cat-friend', '#FF8C69', 190],
];
foreach ($categories as $i => $c) {
Db::name('fc_category')->insert([
'id' => $c[0], 'name' => $c[1], 'icon_key' => $c[2], 'color' => $c[3],
'sort' => $i + 1, 'enabled' => 1, 'post_count' => $c[4],
'createtime' => $now, 'updatetime' => $now,
]);
}
/* ---------- 轮播 ---------- */
$banners = self::BANNERS;
foreach ($banners as $i => $b) {
Db::name('fc_banner')->insert([
'title' => $b[0], 'image' => '', 'link' => $b[1], 'color' => $b[2],
'sort' => $i + 1, 'enabled' => 1, 'createtime' => $now, 'updatetime' => $now,
]);
}
/* ---------- 服务区域 ---------- */
$areas = [['云岩区', 1, 1], ['南明区', 2, 1], ['观山湖区', 3, 1], ['花溪区', 4, 1], ['白云区', 5, 0]];
foreach ($areas as $a) {
Db::name('fc_area')->insert([
'name' => $a[0], 'sort' => $a[1], 'enabled' => $a[2],
'createtime' => $now, 'updatetime' => $now,
]);
}
/* ---------- 会员套餐 ---------- */
$plans = [
['M1', '月卡', 19.9, 29.9, 30, '尝鲜', 30],
['M3', '季卡', 49.9, 89.7, 90, '最划算', 90],
['M12', '年卡', 168, 358.8, 365, '省心', 500],
];
foreach ($plans as $i => $p) {
Db::name('fc_plan')->insert([
'plan_code' => $p[0], 'name' => $p[1], 'price' => $p[2], 'origin_price' => $p[3],
'days' => $p[4], 'tag' => $p[5], 'sort' => $i + 1, 'enabled' => 1,
'post_quota' => $p[6],
'createtime' => $now, 'updatetime' => $now,
]);
}
$output->writeln(' - 分类8 / 轮播3 / 区域5 / 套餐3');
/* ---------- 演示用户(显式ID,与前端 mock 数据集一致) ---------- */
$salt = 'fcseed';
$pwd = md5(md5('123456') . $salt);
$users = [
[1001, '同城小助手', '13800001111', 128.50, 320],
[1002, '小王同学', '15033443344', 12.00, 40],
[1003, '洁净到家', '13156785678', 60.00, 120],
[1004, '违规用户A', '13000000000', 0.00, 0],
[2001, '顺捷物流', '13988888888', 300.00, 210],
[2002, '安家房产', '13566666666', 520.00, 380],
[2003, '骑行爱好者', '13712341234', 8.00, 15],
[2004, '车行天下', '13800000000', 90.00, 66],
[2005, '洁净到家', '13156785678', 45.00, 88],
[2006, '小王同学', '15033443344', 6.00, 10],
[2007, '优选二手车', '13700000000', 0.00, 0],
];
// 幂等:先清理同 ID 的演示用户(fa_user 属核心表,不在 truncateAll 范围内)
$demoUserIds = array_column($users, 0);
Db::name('user')->where('id', 'in', $demoUserIds)->delete();
Db::name('fc_user_ext')->where('user_id', 'in', $demoUserIds)->delete();
foreach ($users as $u) {
Db::name('user')->insert([
'id' => $u[0], 'group_id' => 1, 'username' => 'fc' . $u[0], 'nickname' => $u[1],
'password' => $pwd, 'salt' => $salt, 'email' => $u[0] . '@fenleixinxi.local',
'mobile' => $u[2], 'avatar' => '', 'level' => 1, 'gender' => 0,
'money' => $u[3], 'score' => $u[4], 'status' => ($u[0] === 1004 ? 'hidden' : 'normal'),
'jointime' => $t(180), 'joinip' => '127.0.0.1', 'logintime' => $t(0.1),
'loginip' => '127.0.0.1', 'createtime' => $t(180), 'updatetime' => $t(0.1),
]);
}
/* 用户扩展(会员状态 / 身份 / 邀请码) */
$exts = [
[1001, 1, 1, '年卡会员', 300, 'TC8A9K', 0, 1],
[1002, 1, 0, '', 0, 'TC2B7M', 1001, 1],
[1003, 2, 1, '月卡会员', 25, 'TC3C1P', 1001, 2],
[1004, 1, 0, '', 0, 'TC4D5Q', 0, 0],
[2001, 2, 1, '季卡会员', 60, 'TC5E8R', 0, 2],
[2002, 2, 1, '年卡会员', 200, 'TC6F2S', 0, 2],
[2003, 1, 0, '', 0, 'TC7G9T', 1001, 1],
[2004, 2, 0, '', 0, 'TC8H3U', 1002, 2],
[2005, 2, 1, '月卡会员', 20, 'TC9J6V', 1001, 2],
[2006, 1, 0, '', 0, 'TC2K4W', 1002, 1],
[2007, 2, 0, '', 0, 'TC3L7X', 0, 2],
];
foreach ($exts as $e) {
Db::name('fc_user_ext')->insert([
'user_id' => $e[0], 'openid' => 'openid_' . $e[0], 'user_type' => $e[1],
'realname_status' => 1, 'is_member' => $e[2], 'member_level' => $e[3],
'member_expiretime' => $e[4] > 0 ? $t(-$e[4]) : null,
'invite_code' => $e[5], 'inviter_id' => $e[6],
'free_view_date' => date('Y-m-d'), 'free_view_used' => 0,
'createtime' => $t(180), 'updatetime' => $t(0.1),
]);
}
$output->writeln(' - 用户 ' . count($users) . ' 个 + 扩展');
/* ---------- 信息(覆盖待审/已通过/已驳回三种状态) ---------- */
$posts = [
[101, 2001, 1, '急招快递分拣员(日结)', '站点直招,日结 180-220 元,包工作餐,当日结算。有意者电话联系。',
0, '面议', '云岩区', '贵阳北站物流园', 1892, 32, 0, 1, 12, 1200, '日结,包吃', 0],
[102, 2002, 2, '整租·未来方舟精装一室一厅', '家电齐全,拎包入住,近地铁 2 号线,押一付一,可短租。',
1800, '元/月', '南明区', '未来方舟 B 区', 3204, 88, 1, 1, 26, 3400, '精装,近地铁', 0],
[103, 2003, 3, '九成新山地自行车 转让', '骑行不到 300 公里,27 速,原价 1599,现 680 出,可小刀,同城自提。',
680, '元', '观山湖区', '世纪城', 640, 21, 0, 1, 4, 820, '自提,9成新', 0],
[104, 2004, 4, '2019 款家用轿车 一手车', '车况精品,无事故无泡水,4S 店保养记录齐全,支持第三方检测。',
68000, '元', '云岩区', '贵阳市云岩区', 1120, 40, 1, 0, 0, 5100, '无事故,可检测', 0],
[105, 2005, 5, '专业空调清洗 / 加氟上门', '10 年老师傅,上门服务,清洗 60 元/台,加氟 80 元起,不满意不收费。',
60, '元/台', '南明区', '花果园', 2310, 66, 0, 0, 18, 2600, '上门,不满意不收费', 0],
[106, 2006, 3, 'iPad 2021 64G 深空灰', '学生自用,无磕碰,配件齐全,支持当面验机。',
1450, '元', '观山湖区', '贵阳奥体中心', 480, 15, 0, 2, 1, 1500, '验机', 0],
];
$offsets = [0.2, 0.5, 1, 0.1, 0.05, 2];
$phones = [101 => '13988888888', 102 => '13566666666', 103 => '13712341234',
104 => '13800000000', 105 => '13156785678', 106 => '15033443344'];
$paids = [101 => 0, 102 => 1, 103 => 0, 104 => 1, 105 => 0, 106 => 0];
foreach ($posts as $i => $p) {
$id = $p[0];
Db::name('fc_post')->insert([
'id' => $id, 'user_id' => $p[1], 'category_id' => $p[2], 'title' => $p[3], 'content' => $p[4],
'cover' => '', 'media' => json_encode([], JSON_UNESCAPED_UNICODE),
'price' => $p[5], 'price_unit' => $p[6], 'area' => $p[7], 'address' => $p[8],
'contact' => '联系人', 'phone' => $phones[$id], 'wechat' => 'tongcheng_' . $id,
'tags' => $p[15],
'is_paid' => $paids[$id], 'amount' => $paids[$id] ? 5 : 0, 'pay_type' => $paids[$id] ? 'wxpay' : '',
'audit_status' => $p[12], 'audit_reason' => $p[12] == 2 ? '信息涉嫌重复发布' : '',
'views' => $p[9], 'likes' => $p[10], 'comment_count' => $p[13],
'status' => 1, 'is_top' => 0, 'distance' => $p[14],
'createtime' => $t($offsets[$i]), 'updatetime' => $t($offsets[$i]),
]);
}
$output->writeln(' - 信息 ' . count($posts) . ' 条');
/* ---------- 草稿 ---------- */
Db::name('fc_draft')->insert([
'user_id' => 1001, 'category_id' => 2, 'title' => '转租单间(草稿)', 'content' => '还未填完……',
'media' => json_encode([], JSON_UNESCAPED_UNICODE), 'savedat' => $t(0.1),
'createtime' => $t(0.1), 'updatetime' => $t(0.1),
]);
/* ---------- 商家资质 ---------- */
$merchants = [
[2001, '顺捷物流', '13988888888', '物流服务', 1, 'normal', 0, 32, 120],
[2002, '安家房产', '13566666666', '房产中介', 1, 'normal', 1, 68, 200],
[2005, '洁净到家', '13156785678', '家政服务', 0, 'pending', 0, 12, 3],
[2007, '优选二手车', '13700000000', '汽车服务', 2, 'banned', 3, 0, 60],
];
foreach ($merchants as $m) {
Db::name('fc_merchant')->insert([
'user_id' => $m[0], 'name' => $m[1], 'logo' => '', 'contact' => $m[2], 'category' => $m[3],
'license' => $m[4] == 2 ? '' : '营业执照.jpg',
'audit_status' => $m[4], 'audit_reason' => $m[4] == 2 ? '营业执照不清晰' : '',
'shop_status' => $m[5], 'violations' => $m[6], 'post_count' => $m[7],
'createtime' => $t($m[8]), 'updatetime' => $t($m[8]),
]);
}
/* ---------- 评价 ---------- */
$comments = [
[5001, 101, 1002, 5, '联系很及时,当天就上岗了。', '', 0, 0.5],
[5002, 102, 1003, 4, '房子不错,房东好说话。', '感谢支持,欢迎续租~', 1, 2],
[5003, 103, 1004, 1, '广告信息,疑似虚假。', '', 0, 0.2],
];
foreach ($comments as $c) {
Db::name('fc_comment')->insert([
'id' => $c[0], 'post_id' => $c[1], 'user_id' => $c[2], 'score' => $c[3], 'content' => $c[4],
'images' => json_encode([], JSON_UNESCAPED_UNICODE), 'reply' => $c[5],
'replytime' => $c[5] ? $t($c[7]) : null, 'top' => $c[6], 'status' => 1,
'createtime' => $t($c[7]), 'updatetime' => $t($c[7]),
]);
}
/* ---------- 举报 ---------- */
Db::name('fc_report')->insert([
'target_type' => 'comment', 'target_id' => 5003, 'reason' => '疑似虚假信息',
'reporter_id' => 1002, 'status' => 'pending', 'remark' => '',
'createtime' => $t(0.2), 'updatetime' => $t(0.2),
]);
Db::name('fc_report')->insert([
'target_type' => 'post', 'target_id' => 106, 'reason' => '重复发布',
'reporter_id' => 1004, 'status' => 'handled', 'remark' => '已下架处理',
'createtime' => $t(4), 'updatetime' => $t(4),
]);
$output->writeln(' - 商家4 / 评价3 / 举报2');
/* ---------- 订单(发布 / 会员 / 充值 三类) ---------- */
$orders = [
[3001, 'PO20240101001', 1001, 'post', '单条付费发布', 5, 'wxpay', 'paid', 1],
[3002, 'MO20240102002', 1001, 'member', '年卡会员', 168, 'wxpay', 'paid', 2],
[3003, 'RO20240103003', 1001, 'recharge', '余额充值', 100, 'wxpay', 'paid', 3],
[3004, 'PO20240104004', 1001, 'post', '单条付费发布', 5, 'balance', 'pending', 0.5],
];
foreach ($orders as $o) {
Db::name('fc_order')->insert([
'id' => $o[0], 'order_no' => $o[1], 'user_id' => $o[2], 'type' => $o[3], 'title' => $o[4],
'amount' => $o[5], 'pay_type' => $o[6], 'status' => $o[7],
'paytime' => $o[7] === 'paid' ? $t($o[8]) : null,
'related_id' => 0, 'createtime' => $t($o[8]), 'updatetime' => $t($o[8]),
]);
}
/* ---------- 消息中心 ---------- */
$messages = [
[1001, 'system', '审核通过', '您发布的信息「急招快递分拣员(日结)」已审核通过。', 0, 0.2],
[1001, 'system', '会员到期提醒', '您的年卡会员将于 30 天后到期,续费可享 9 折。', 0, 1],
[1001, 'interact', '收到新评价', '用户「小王同学」评价了您的信息:服务很到位,五星好评!', 1, 2],
[1001, 'interact', '点赞提醒', '您发布的信息获得 5 个赞。', 1, 3],
];
foreach ($messages as $m) {
Db::name('fc_message')->insert([
'user_id' => $m[0], 'type' => $m[1], 'title' => $m[2], 'content' => $m[3],
'isread' => $m[4], 'readtime' => $m[4] ? $t($m[5]) : null,
'createtime' => $t($m[5]),
]);
}
/* ---------- 分销关系(1001 的一/二级下线) ---------- */
$rels = [[1001, 1], [1002, 1], [2003, 1], [1003, 2], [2004, 2], [2005, 2], [2006, 2]];
foreach ($rels as $r) {
Db::name('fc_dist_relation')->insert([
'user_id' => $r[0], 'parent_id' => 1001, 'level' => $r[1], 'createtime' => $t(10),
]);
}
/* ---------- 佣金(1001 的收益明细) ----------
* 数值与小程序 mock 的 distOverview 对齐:
* totalCommission 1286.4 = settled 336.4 + unsettled 950
* 已申请提现 150(auditing 50 + paid 100)
* → withdrawable = 336.4 - 150 = 186.4
*/
$commissions = [
[1002, 3002, 'MO20240102002', 'member', 900.0, 20, 1, 0, 2],
[2003, 3001, 'PO20240101001', 'post', 50.0, 20, 1, 0, 0],
[2004, 3003, 'RO20240103003', 'recharge', 1.0, 20, 1, 1, 3],
[1003, 3002, 'MO20240102002', 'member', 300.0, 10, 2, 1, 5],
[2005, 3003, 'RO20240103003', 'recharge', 35.4, 10, 2, 1, 6],
];
foreach ($commissions as $c) {
Db::name('fc_commission')->insert([
'user_id' => 1001,
'from_user_id' => $c[0],
'order_id' => $c[1],
'order_no' => $c[2],
'order_type' => $c[3],
'amount' => $c[4],
'rate' => $c[5],
'level' => $c[6],
'status' => $c[7],
'settletime' => $c[7] ? $t($c[8]) : null,
'createtime' => $t($c[8]),
]);
}
/* ---------- 提现(含待审/已打款) ---------- */
$withdraws = [
[1, 'WD20231220001', 1001, 100, 'paid', '', 25],
[2, 'WD20240105002', 1001, 50, 'auditing', '', 1],
[3, 'WD20240106003', 1002, 200, 'auditing', '', 0.4],
];
foreach ($withdraws as $w) {
Db::name('fc_withdraw')->insert([
'id' => $w[0], 'withdraw_no' => $w[1], 'user_id' => $w[2], 'amount' => $w[3],
'channel' => 'wxpay', 'status' => $w[4], 'reason' => $w[5],
'paytime' => $w[4] === 'paid' ? $t($w[6]) : null,
'createtime' => $t($w[6]), 'updatetime' => $t($w[6]),
]);
}
/* ---------- 资金流水 ---------- */
$flows = [
['F20240107001', 'income', '发布付费', 5, 2864.5, 0.2],
['F20240107002', 'income', '会员开通', 168, 2859.5, 0.5],
['F20240106003', 'expense', '佣金提现', 100, 2691.5, 1],
['F20240105004', 'income', '余额充值', 100, 2791.5, 2],
];
foreach ($flows as $f) {
Db::name('fc_finance_flow')->insert([
'flow_no' => $f[0], 'type' => $f[1], 'scene' => $f[2], 'amount' => $f[3],
'balance' => $f[4], 'related_id' => 0, 'remark' => '',
'createtime' => $t($f[5]),
]);
}
$output->writeln(' - 订单4 / 消息4 / 分销7 / 佣金2 / 提现3 / 流水4');
}
}