文件
fenleixinxi/application/admin/command/FcInstall.php
linda 1ae3109058 feat(admin): 补齐后台管理端全量业务模块(FastAdmin 原生)
一、问题
35 个后台接口此前无任何可用界面:FastAdmin PC 后台 110 条菜单里 0 条业务菜单,
小程序端仅有 1 个 LOGO/图标设置页 → 后台功能实际上无处可操作。

二、方案(FastAdmin 原生机制)
- 用 `php think crud` 为 17 张业务表生成管理模块:
  控制器 application/admin/controller/fc/*.php(继承 Backend)
  模型 application/admin/model/Fc*.php
  视图 application/admin/view/fc/*/{index,add,edit}.html
  后台 JS public/assets/js/backend/fc/*.js
  语言包 application/admin/lang/zh-cn/fc/*.php
- 菜单结构固化进安装器 application/admin/command/FcInstall.php 的 menu()(幂等):
  同城业务 ├─ 内容管理   分类管理/轮播管理/信息管理/评价管理/举报处理
          ├─ 用户与商家 商家管理/会员管理/消息管理
          ├─ 交易管理   订单管理/会员套餐/资金流水/佣金明细/付费查看记录
          ├─ 分销管理   分销关系/提现审核
          └─ 基础数据   服务区域/草稿箱

三、业务动作(一键操作,含列表页按钮)
- fc/post:    audit(审核通过/驳回 + 分类信息量 + 站内信) / offline(下架)
- fc/merchant: audit(资质审核 + 同步用户身份 + 店铺状态)
- fc/withdraw: pay(打款/驳回 + 资金流水 + 拒绝须填原因)
- fc/order:   refund(退款) / supplement(按订单号补单 + 流水)
- fc/comment: reply(回复) / top(置顶)
- fc/report:  handle(标记已处理)

四、验证
- 后台登录后业务页 HTTP 200,controllername/jsname 正确解析
- 列表数据真实加载:分类 8 / 信息 6 / 订单 4 / 提现 3 / 商家 4
- 侧边栏 5 个中文分组菜单正常渲染
2026-10-15 15:20:44 +08:00

816 行
43 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 原有表。
*/
class FcInstall extends Command
{
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->menu($output);
$output->writeln('==> 完成');
}
/**
* 初始化 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/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(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`)
) 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',
`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',
`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', '分享标题'],
['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', '年卡价格'],
['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 = [
['同城好物节 · 二手直降', '/pages/post/list?categoryId=3', '#FF8A5B'],
['招聘旺季 · 免费发布', '/pages/post/create', '#FFB86B'],
['本地服务 · 上门更省心', '/pages/post/list?categoryId=5', '#FF6F61'],
];
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, '尝鲜'],
['M3', '季卡', 49.9, 89.7, 90, '最划算'],
['M12', '年卡', 168, 358.8, 365, '省心'],
];
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,
'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');
}
}