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->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 亮绿→深绿)' : ' - 轮播底色已是春绿渐变或非内置轮播,无需处理'); } /** * 初始化 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(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', `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', '分享标题'], ['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', '年卡价格'], ['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, '尝鲜'], ['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'); } }