|
|
@@ -1,557 +0,0 @@
|
|
|
-<?php
|
|
|
-
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace SixShop\System\Command;
|
|
|
-
|
|
|
-use think\console\Command;
|
|
|
-use think\console\Input;
|
|
|
-use think\console\input\Argument;
|
|
|
-use think\console\input\Option;
|
|
|
-use think\console\Output;
|
|
|
-
|
|
|
-use function SixShop\Core\extension_path;
|
|
|
-
|
|
|
-class ExtensionScaffoldMakeCommand extends Command
|
|
|
-{
|
|
|
- protected function configure(): void
|
|
|
- {
|
|
|
- $this->setName('extension:make')
|
|
|
- ->setDescription('生成扩展脚手架骨架(后端+前端,可选 Service/Entity/FFI/Frontend)')
|
|
|
- ->addArgument('module', Argument::REQUIRED, '扩展模块名(目录名,建议小写下划线)')
|
|
|
- ->addOption('with-api', null, Option::VALUE_NONE, '生成 API 路由与控制器')
|
|
|
- ->addOption('with-admin', null, Option::VALUE_NONE, '生成 Admin 路由与控制器')
|
|
|
- ->addOption('with-service', null, Option::VALUE_NONE, '生成 Service 层')
|
|
|
- ->addOption('with-entity', null, Option::VALUE_NONE, '生成 Entity 层')
|
|
|
- ->addOption('with-migration', null, Option::VALUE_NONE, '生成迁移与安装/卸载 SQL 样板')
|
|
|
- ->addOption('with-frontend', null, Option::VALUE_NONE, '生成前端 Admin 模板')
|
|
|
- ->addOption('with-ffi', null, Option::VALUE_NONE, '生成 FFI 目录与构建脚本样板')
|
|
|
- ->addOption('adapter', null, Option::VALUE_REQUIRED, '服务适配默认实现 php|ffi|auto', 'php')
|
|
|
- ->addOption('desc', null, Option::VALUE_REQUIRED, 'info.php 描述', '')
|
|
|
- ->addOption('author', null, Option::VALUE_REQUIRED, '作者', 'yourname')
|
|
|
- ->addOption('dry-run', null, Option::VALUE_NONE, '仅预览将要创建的目录与文件,不实际写入')
|
|
|
- ->addOption('force', null, Option::VALUE_NONE, '允许在已存在的模块目录内覆盖写入文件');
|
|
|
- }
|
|
|
-
|
|
|
- protected function execute(Input $input, Output $output): int
|
|
|
- {
|
|
|
- $module = (string)$input->getArgument('module');
|
|
|
- $withApi = (bool)$input->getOption('with-api');
|
|
|
- $withAdmin = (bool)$input->getOption('with-admin');
|
|
|
- $withService = (bool)$input->getOption('with-service');
|
|
|
- $withEntity = (bool)$input->getOption('with-entity');
|
|
|
- $withMigration = (bool)$input->getOption('with-migration');
|
|
|
- $withFrontend = (bool)$input->getOption('with-frontend');
|
|
|
- $withFFI = (bool)$input->getOption('with-ffi');
|
|
|
- $adapter = (string)$input->getOption('adapter');
|
|
|
- $desc = (string)$input->getOption('desc');
|
|
|
- $author = (string)$input->getOption('author');
|
|
|
- $dryRun = (bool)$input->getOption('dry-run');
|
|
|
- $force = (bool)$input->getOption('force');
|
|
|
-
|
|
|
- if (!$module) {
|
|
|
- $output->error('模块名不能为空');
|
|
|
- return 1;
|
|
|
- }
|
|
|
-
|
|
|
- // 默认行为:如果用户未显式指定任何 with-* 选项,则默认生成“完整插件”(除 FFI)
|
|
|
- $anySpecified = $withApi || $withAdmin || $withService || $withEntity || $withMigration || $withFrontend || $withFFI;
|
|
|
- if (!$anySpecified) {
|
|
|
- $withApi = $withAdmin = $withService = $withEntity = $withMigration = $withFrontend = true;
|
|
|
- // $withFFI 默认为 false,避免环境未开启 FFI 导致构建失败
|
|
|
- }
|
|
|
-
|
|
|
- $base = rtrim(extension_path($module), '/');
|
|
|
- if (is_dir($base) && !$force) {
|
|
|
- $output->error("扩展 {$module} 已存在:{$base},可使用 --force 覆盖写入");
|
|
|
- return 1;
|
|
|
- }
|
|
|
-
|
|
|
- // 目录结构
|
|
|
- $dirs = [
|
|
|
- "$base/src/Controller/Api",
|
|
|
- "$base/src/Controller/Admin",
|
|
|
- "$base/src/Service",
|
|
|
- "$base/src/Entity",
|
|
|
- "$base/src/Hook",
|
|
|
- "$base/database/migrations",
|
|
|
- "$base/database/seeds",
|
|
|
- "$base/route",
|
|
|
- "$base/config",
|
|
|
- ];
|
|
|
- // 计划文件(用于 dry-run 展示)
|
|
|
- $ns = "SixShop\\\\Extension\\\\{$module}";
|
|
|
- $studly = str_replace(['-', '_'], '', ucwords($module, '-_'));
|
|
|
- $planFiles = [
|
|
|
- "$base/info.php",
|
|
|
- "$base/config.php",
|
|
|
- "$base/README.md",
|
|
|
- "$base/src/Extension.php",
|
|
|
- "$base/src/Hook/{$studly}Hook.php",
|
|
|
- ];
|
|
|
- if ($withApi) {
|
|
|
- $planFiles[] = "$base/route/api.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Api/HelloController.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Api/ItemController.php";
|
|
|
- }
|
|
|
- if ($withAdmin) {
|
|
|
- $planFiles[] = "$base/route/admin.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Admin/DashboardController.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Admin/ManageController.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Admin/ItemController.php";
|
|
|
- $planFiles[] = "$base/src/Controller/Admin/UploadController.php";
|
|
|
- }
|
|
|
- if ($withService) {
|
|
|
- $planFiles[] = "$base/src/Service/{$studly}Service.php";
|
|
|
- }
|
|
|
- if ($withEntity) {
|
|
|
- $planFiles[] = "$base/src/Entity/{$studly}.php";
|
|
|
- }
|
|
|
- if ($withMigration) {
|
|
|
- $planFiles[] = "$base/config/install.sql";
|
|
|
- $planFiles[] = "$base/config/uninstall.sql";
|
|
|
- }
|
|
|
-
|
|
|
- if ($dryRun) {
|
|
|
- $output->writeln("[DRY-RUN] 将创建以下目录:");
|
|
|
- foreach ($dirs as $d) {
|
|
|
- $output->writeln(" - $d");
|
|
|
- }
|
|
|
- $output->writeln("[DRY-RUN] 将创建以下关键文件(部分):");
|
|
|
- foreach ($planFiles as $f) {
|
|
|
- $output->writeln(" - $f");
|
|
|
- }
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- foreach ($dirs as $d) {
|
|
|
- @mkdir($d, 0777, true);
|
|
|
- }
|
|
|
-
|
|
|
- // info.php
|
|
|
- // 生成完整 info.php(参考 guimi)
|
|
|
- $info = [
|
|
|
- 'id' => $module,
|
|
|
- 'name' => $module,
|
|
|
- // 分类:core|content|shop|other|custom,默认 custom
|
|
|
- 'category' => 'custom',
|
|
|
- 'description' => $desc ?: ($module . ' 扩展模块'),
|
|
|
- 'version' => '0.1.0',
|
|
|
- 'core_version' => '^1.0',
|
|
|
- 'author' => $author ?: 'sixshop',
|
|
|
- 'email' => '',
|
|
|
- 'website' => '',
|
|
|
- 'image' => '',
|
|
|
- 'license' => 'MIT',
|
|
|
- 'keywords' => [],
|
|
|
- 'dependencies' => [],
|
|
|
- 'conflicts' => [],
|
|
|
- 'requires' => [
|
|
|
- 'php' => '>=8.0.0',
|
|
|
- 'extensions' => ['json', 'pdo'],
|
|
|
- ],
|
|
|
- ];
|
|
|
- $infoExport = var_export($info, true);
|
|
|
- $infoExport = str_replace(['array (', ')'], ['[', ']'], $infoExport);
|
|
|
- file_put_contents("$base/info.php", "<?php\ndeclare(strict_types=1);\n\nreturn " . $infoExport . ";\n");
|
|
|
-
|
|
|
- // Extension.php(使用 Nowdoc + sprintf 注入命名空间)
|
|
|
- $ns = 'SixShop\\Extension\\' . $module;
|
|
|
- $extClass = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s;
|
|
|
-
|
|
|
-use SixShop\Core\ExtensionAbstract;
|
|
|
-use think\facade\Db;
|
|
|
-
|
|
|
-class Extension extends ExtensionAbstract
|
|
|
-{
|
|
|
- protected function getBaseDir(): string
|
|
|
- {
|
|
|
- return dirname(__DIR__);
|
|
|
- }
|
|
|
-
|
|
|
- public function install(): void
|
|
|
- {
|
|
|
- $sqlFile = __DIR__ . '/../config/install.sql';
|
|
|
- if (is_file($sqlFile)) {
|
|
|
- $sql = file_get_contents($sqlFile);
|
|
|
- if ($sql) { Db::execute($sql); }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function uninstall(): void
|
|
|
- {
|
|
|
- $sqlFile = __DIR__ . '/../config/uninstall.sql';
|
|
|
- if (is_file($sqlFile)) {
|
|
|
- $sql = file_get_contents($sqlFile);
|
|
|
- if ($sql) { Db::execute($sql); }
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Extension.php", $extClass);
|
|
|
-
|
|
|
- // config.php(form-create 占位)
|
|
|
- $configPhp = <<<PHP
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-return [
|
|
|
- 'form' => [
|
|
|
- [
|
|
|
- 'type' => 'input',
|
|
|
- 'field' => 'title',
|
|
|
- 'title' => '标题',
|
|
|
- 'value' => '',
|
|
|
- 'props' => ['placeholder' => '请输入标题'],
|
|
|
- ],
|
|
|
- ],
|
|
|
-];
|
|
|
-PHP;
|
|
|
- file_put_contents("$base/config.php", $configPhp);
|
|
|
-
|
|
|
- // README
|
|
|
- file_put_contents("$base/README.md", "# {$module}\n\n自动生成的扩展骨架。\n");
|
|
|
-
|
|
|
- // 上面已生成 Extension.php,这里不再重复生成
|
|
|
-
|
|
|
- // Hook 占位(下面统一生成一次)
|
|
|
-
|
|
|
- // 安装/卸载 SQL 样板
|
|
|
- if ($withMigration) {
|
|
|
- $install = "-- 安装 SQL 示例\n";
|
|
|
- $uninstall = "-- 卸载 SQL 示例\n";
|
|
|
- file_put_contents("$base/config/install.sql", $install);
|
|
|
- file_put_contents("$base/config/uninstall.sql", $uninstall);
|
|
|
- }
|
|
|
-
|
|
|
- // 路由(注意:系统会自动加 /api/{$module} 或 /admin/{$module} 前缀,这里不需要再包一层模块分组)
|
|
|
- $apiRoute = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-use SixShop\Extension\%s\Controller\Api\ItemController;
|
|
|
-use think\facade\Route;
|
|
|
-
|
|
|
-// 注意:前缀由系统自动添加,这里只写相对路径
|
|
|
-
|
|
|
-// 健康检查
|
|
|
-Route::get('ping', fn() => json(['code' => 0, 'msg' => 'ok', 'data' => ['pong' => true]]))->middleware(['auth']);
|
|
|
-
|
|
|
-// 示例:业务分组-具体动作(放在资源路由之前,避免 :id 冲突)
|
|
|
-Route::group('item', function () {
|
|
|
- Route::get('info', [ItemController::class, 'info']);
|
|
|
- Route::post('check', [ItemController::class, 'check']);
|
|
|
-})->middleware(['auth']);
|
|
|
-
|
|
|
-PHP, $module);
|
|
|
- $adminRoute = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-use think\facade\Route;
|
|
|
-use SixShop\Extension\%s\Controller\Admin\DashboardController;
|
|
|
-use SixShop\Extension\%s\Controller\Admin\ItemController;
|
|
|
-use SixShop\Extension\%s\Controller\Admin\UploadController;
|
|
|
-
|
|
|
-// 首页/仪表盘控制器路由(对齐 guimi 写法)
|
|
|
-Route::get('dashboard/stats', [DashboardController::class, 'stats'])->middleware(['auth']);
|
|
|
-// 可按需继续追加:relation-trend / verification-trend / redemption-trend / latest 等
|
|
|
-
|
|
|
-// 通用上传
|
|
|
-Route::post('upload', [UploadController::class, 'handle'])->middleware(['auth']);
|
|
|
-PHP, $module, $module, $module);
|
|
|
- if ($withApi) {
|
|
|
- file_put_contents("$base/route/api.php", $apiRoute);
|
|
|
- }
|
|
|
- if ($withAdmin) {
|
|
|
- file_put_contents("$base/route/admin.php", $adminRoute);
|
|
|
- }
|
|
|
-
|
|
|
- // 控制器样板
|
|
|
- if ($withApi) {
|
|
|
- $apiCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Api;
|
|
|
-
|
|
|
-use think\Request; use think\Response;
|
|
|
-
|
|
|
-class HelloController
|
|
|
-{
|
|
|
- private function uid(Request $r): ?int { return $r->userID ?? ($r->adminID ?? null); }
|
|
|
- public function index(Request $r): Response
|
|
|
- {
|
|
|
- if (!$this->uid($r)) return json(['code'=>401,'msg'=>'未登录']);
|
|
|
- return json(['code'=>0,'msg'=>'ok','data'=>['now'=>date('c')]]);
|
|
|
- }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Api/HelloController.php", $apiCtrl);
|
|
|
-
|
|
|
- // API 资源控制器
|
|
|
- $apiItemCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Api;
|
|
|
-
|
|
|
-use think\Request; use think\Response;
|
|
|
-
|
|
|
-class ItemController
|
|
|
-{
|
|
|
- private function uid(Request $r): ?int { return $r->userID ?? ($r->adminID ?? null); }
|
|
|
-
|
|
|
- public function index(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]); }
|
|
|
- public function read(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>['id'=>$id]]); }
|
|
|
- public function save(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
- public function update(int $id, Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
- public function delete(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
-
|
|
|
- // 具体动作示例(与路由匹配)
|
|
|
- public function info(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>['info'=>[]]]); }
|
|
|
- public function check(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Api/ItemController.php", $apiItemCtrl);
|
|
|
- }
|
|
|
- if ($withAdmin) {
|
|
|
- $adminCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Admin;
|
|
|
-
|
|
|
-use think\Request; use think\Response;
|
|
|
-
|
|
|
-class ManageController
|
|
|
-{
|
|
|
- public function list(Request $r): Response
|
|
|
- {
|
|
|
- return json(['code'=>0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]);
|
|
|
- }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Admin/ManageController.php", $adminCtrl);
|
|
|
-
|
|
|
- // 首页/仪表盘控制器(对齐 guimi:dashboard/*)
|
|
|
- $dashboardCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Admin;
|
|
|
-
|
|
|
-use think\Response;
|
|
|
-
|
|
|
-class DashboardController
|
|
|
-{
|
|
|
- public function stats(): Response
|
|
|
- {
|
|
|
- // 首页统计占位:可返回卡片统计与趋势入口
|
|
|
- return json(['code' => 0, 'msg' => 'ok', 'data' => [
|
|
|
- 'cards' => [
|
|
|
- ['title' => '总数', 'value' => 0],
|
|
|
- ],
|
|
|
- ]]);
|
|
|
- }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Admin/DashboardController.php", $dashboardCtrl);
|
|
|
-
|
|
|
- // Admin 资源控制器
|
|
|
- $adminItemCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Admin;
|
|
|
-
|
|
|
-use think\Request; use think\Response;
|
|
|
-
|
|
|
-class ItemController
|
|
|
-{
|
|
|
- public function index(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>['list'=>[], 'total'=>0]]); }
|
|
|
- public function read(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>['id'=>$id]]); }
|
|
|
- public function save(Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
- public function update(int $id, Request $r): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
- public function delete(int $id): Response { return json(['code'=>0,'msg'=>'ok','data'=>true]); }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Admin/ItemController.php", $adminItemCtrl);
|
|
|
-
|
|
|
- // Admin 上传控制器
|
|
|
- $uploadCtrl = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Controller\Admin;
|
|
|
-
|
|
|
-use think\Request; use think\Response;
|
|
|
-
|
|
|
-class UploadController
|
|
|
-{
|
|
|
- public function handle(Request $r): Response
|
|
|
- {
|
|
|
- // TODO: 接入实际存储逻辑,返回 { url, name }
|
|
|
- return json(['code'=>0,'msg'=>'ok','data'=>['url'=>'','name'=>'']]);
|
|
|
- }
|
|
|
-}
|
|
|
-PHP, $ns);
|
|
|
- file_put_contents("$base/src/Controller/Admin/UploadController.php", $uploadCtrl);
|
|
|
- }
|
|
|
-
|
|
|
- // Service / Entity 占位
|
|
|
- if ($withService) {
|
|
|
- $svc = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Service;
|
|
|
-
|
|
|
-class %sService
|
|
|
-{
|
|
|
- public function ping(): array { return ['pong' => true]; }
|
|
|
-}
|
|
|
-PHP, $ns, $studly);
|
|
|
- file_put_contents("$base/src/Service/{$studly}Service.php", $svc);
|
|
|
- }
|
|
|
- if ($withEntity) {
|
|
|
- $ent = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Entity;
|
|
|
-
|
|
|
-class %sEntity
|
|
|
-{
|
|
|
- public const TABLE = 'extension_%s_item';
|
|
|
-}
|
|
|
-PHP, $ns, $studly, $module);
|
|
|
- file_put_contents("$base/src/Entity/{$studly}Entity.php", $ent);
|
|
|
- }
|
|
|
-
|
|
|
- // 迁移 & 安装/卸载 SQL
|
|
|
- if ($withMigration) {
|
|
|
- $install = "-- 安装 SQL 示例\n" .
|
|
|
- "CREATE TABLE IF NOT EXISTS `extension_{$module}_item`(\n" .
|
|
|
- " `id` int unsigned NOT NULL AUTO_INCREMENT,\n" .
|
|
|
- " `title` varchar(255) NOT NULL DEFAULT '',\n" .
|
|
|
- " `created_at` int unsigned NOT NULL DEFAULT 0,\n" .
|
|
|
- " PRIMARY KEY (`id`)\n" .
|
|
|
- ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n";
|
|
|
- $uninstall = "DROP TABLE IF EXISTS `extension_{$module}_item`;\n";
|
|
|
- file_put_contents("$base/config/install.sql", $install);
|
|
|
- file_put_contents("$base/config/uninstall.sql", $uninstall);
|
|
|
- }
|
|
|
-
|
|
|
- // Hook 示例(统一在此生成一次)
|
|
|
- $hook = sprintf(<<<'PHP'
|
|
|
-<?php
|
|
|
-declare(strict_types=1);
|
|
|
-
|
|
|
-namespace %s\Hook;
|
|
|
-
|
|
|
-class %sHook
|
|
|
-{
|
|
|
- /** 示例:用户登录后 */
|
|
|
- public function onUserLogin(array $payload): void {}
|
|
|
-}
|
|
|
-PHP, $ns, $studly);
|
|
|
- file_put_contents("$base/src/Hook/{$studly}Hook.php", $hook);
|
|
|
-
|
|
|
- // FFI 样板
|
|
|
- if ($withFFI) {
|
|
|
- @mkdir("$base/ffi/model", 0777, true);
|
|
|
- $gomod = sprintf("module %s\n\ngo 1.21\n", $module);
|
|
|
- file_put_contents("$base/ffi/go.mod", $gomod);
|
|
|
- $mainGo = <<<'GO'
|
|
|
-package main
|
|
|
-
|
|
|
-// TODO: 实现导出方法
|
|
|
-func main() {}
|
|
|
-GO;
|
|
|
- file_put_contents("$base/ffi/main.go", $mainGo);
|
|
|
- $mk = sprintf(<<<'MK'
|
|
|
-.PHONY: build
|
|
|
-build:
|
|
|
- go build -buildmode=c-shared -o lib_%s.so main.go
|
|
|
-MK, $module);
|
|
|
- file_put_contents("$base/ffi/Makefile", $mk);
|
|
|
- $buildSh = <<<'SH'
|
|
|
-#!/usr/bin/env bash
|
|
|
-set -euo pipefail
|
|
|
-cd "$(dirname "$0")/ffi"
|
|
|
-make build
|
|
|
-cd ..
|
|
|
-echo "[提示] 如使用 FFI,请重启 PHP-FPM 并在 Service Adapter 中切换实现"
|
|
|
-SH;
|
|
|
- file_put_contents("$base/build.sh", $buildSh);
|
|
|
- @chmod("$base/build.sh", 0755);
|
|
|
- }
|
|
|
-
|
|
|
- // 前端 Admin 模板
|
|
|
- if ($withFrontend) {
|
|
|
- // 注意:root_path() 指向 backend/ 应用根;我们需要仓库根目录
|
|
|
- $projectRoot = rtrim(dirname(root_path()), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
|
|
|
- // 视图目录名使用连字符(kebab-case),避免下划线
|
|
|
- $feViewName = str_replace('_', '-', $module);
|
|
|
- $feBase = $projectRoot . 'frontend/admin/src/views/' . $feViewName;
|
|
|
- @mkdir($feBase . '/components', 0777, true);
|
|
|
- @mkdir($feBase . '/composables', 0777, true);
|
|
|
- $indexVue = sprintf(<<<'VUE'
|
|
|
-<template>
|
|
|
- <div class="%s-page">
|
|
|
- <a-tabs v-model:activeKey="tab">
|
|
|
- <a-tab-pane key="dashboard" tab="仪表盘" />
|
|
|
- <a-tab-pane key="list" tab="列表" />
|
|
|
- <a-tab-pane key="settings" tab="设置" />
|
|
|
- </a-tabs>
|
|
|
- <component :is="currentComp" />
|
|
|
- </div>
|
|
|
-</template>
|
|
|
-<script setup lang="ts">
|
|
|
-import { ref, computed } from 'vue'
|
|
|
-const tab = ref('dashboard')
|
|
|
-const currentComp = computed(() => {
|
|
|
- return tab.value === 'list' ? 'ListPanel' : (tab.value === 'settings' ? 'SettingsPanel' : 'DashboardPanel')
|
|
|
-})
|
|
|
-</script>
|
|
|
-VUE, $feViewName);
|
|
|
- file_put_contents($feBase . '/index.vue', $indexVue);
|
|
|
- $dash = "<template><div>DashboardPanel - {$feViewName}</div></template>\n";
|
|
|
- $list = "<template><div>ListPanel - {$feViewName}</div></template>\n";
|
|
|
- $settings = "<template><div>SettingsPanel - {$feViewName}</div></template>\n";
|
|
|
- file_put_contents($feBase . '/components/DashboardPanel.vue', $dash);
|
|
|
- file_put_contents($feBase . '/components/ListPanel.vue', $list);
|
|
|
- file_put_contents($feBase . '/components/SettingsPanel.vue', $settings);
|
|
|
- $useApi = sprintf(<<<'TS'
|
|
|
-import request from '@/utils/request'
|
|
|
-
|
|
|
-export function apiGet(url: string, params?: any) { return request.get(url, { params }) }
|
|
|
-export function apiPost(url: string, data?: any) { return request.post(url, data) }
|
|
|
-export const %sApi = {
|
|
|
- ping: () => apiGet(`/api/%s/ping`),
|
|
|
-}
|
|
|
-TS, $module, $module);
|
|
|
- file_put_contents($feBase . '/composables/useApi.ts', $useApi);
|
|
|
- }
|
|
|
-
|
|
|
- // 提示
|
|
|
- $output->writeln("<info>扩展骨架已生成:</info> $base");
|
|
|
- if ($withFrontend) {
|
|
|
- $output->writeln("<comment>前端模板:</comment> frontend/admin/src/views/{$module}");
|
|
|
- }
|
|
|
- $output->writeln("<comment>下一步:</comment> 1) 根据业务完善 Service/Entity 2) 配置路由与菜单 3) 如需 FFI 执行 {$module}/build.sh");
|
|
|
- return 0;
|
|
|
- }
|
|
|
-
|
|
|
- private function studly(string $value): string
|
|
|
- {
|
|
|
- $value = str_replace(['-', '_'], ' ', $value);
|
|
|
- $value = ucwords($value);
|
|
|
- return str_replace(' ', '', $value);
|
|
|
- }
|
|
|
-}
|