From 254823e30897b65221c8d58574ec012d822d4fd7 Mon Sep 17 00:00:00 2001 From: liu <2302563948@qq.com> Date: Wed, 17 Jun 2026 09:13:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=A1=E5=9B=AD=E5=95=86=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bootstrap/providers.php | 2 + .../2026_05_30_000002_create_forum_tables.php | 17 +- ...6_05_30_000004_create_part_time_tables.php | 37 +--- database/seeders/SysUserSeeder.php | 85 +++++++++ .../MerchantCategoryController.php | 73 +++++++ .../Controllers/MerchantProductController.php | 76 ++++++++ .../Controllers/MerchantStoreController.php | 74 +++++++ .../Http/Controllers/PrintTaskController.php | 47 +++++ .../Http/Controllers/PrinterController.php | 74 +++++++ .../Controllers/ProductCategoryController.php | 74 +++++++ .../Requests/MerchantCategoryFormRequest.php | 48 +++++ .../Requests/MerchantProductFormRequest.php | 37 ++++ .../Requests/MerchantStoreFormRequest.php | 42 ++++ .../Http/Requests/PrinterFormRequest.php | 32 ++++ .../Requests/ProductCategoryFormRequest.php | 33 ++++ .../Merchant/Models/MerchantCategoryModel.php | 24 +++ .../Merchant/Models/MerchantProductModel.php | 42 ++++ .../Merchant/Models/MerchantStoreModel.php | 42 ++++ modules/Merchant/Models/PrintTaskModel.php | 28 +++ modules/Merchant/Models/PrinterModel.php | 27 +++ .../Merchant/Models/ProductCategoryModel.php | 28 +++ .../Providers/MerchantServiceProvider.php | 14 ++ web/domain/iMerchantCategory.ts | 11 ++ web/domain/iMerchantProduct.ts | 19 ++ web/domain/iMerchantStore.ts | 25 +++ web/domain/iPrintTask.ts | 12 ++ web/domain/iPrinter.ts | 13 ++ web/domain/iProductCategory.ts | 13 ++ web/locales/en_US/index.ts | 13 ++ web/locales/en_US/menu.ts | 7 + web/locales/en_US/merchant/category.ts | 17 ++ web/locales/en_US/merchant/print-task.ts | 18 ++ web/locales/en_US/merchant/printer.ts | 20 ++ .../en_US/merchant/product-category.ts | 20 ++ web/locales/en_US/merchant/product.ts | 27 +++ web/locales/en_US/merchant/store.ts | 31 +++ web/locales/zh_CN/index.ts | 13 ++ web/locales/zh_CN/menu.ts | 7 + web/locales/zh_CN/merchant/category.ts | 17 ++ web/locales/zh_CN/merchant/print-task.ts | 18 ++ web/locales/zh_CN/merchant/printer.ts | 20 ++ .../zh_CN/merchant/product-category.ts | 20 ++ web/locales/zh_CN/merchant/product.ts | 27 +++ web/locales/zh_CN/merchant/store.ts | 31 +++ web/pages/merchant/category/index.tsx | 121 ++++++++++++ web/pages/merchant/print-task/index.tsx | 135 +++++++++++++ web/pages/merchant/printer/index.tsx | 139 ++++++++++++++ web/pages/merchant/product-category/index.tsx | 131 +++++++++++++ web/pages/merchant/product/index.tsx | 180 ++++++++++++++++++ web/pages/merchant/store/index.tsx | 165 ++++++++++++++++ 50 files changed, 2186 insertions(+), 40 deletions(-) create mode 100644 modules/Merchant/Http/Controllers/MerchantCategoryController.php create mode 100644 modules/Merchant/Http/Controllers/MerchantProductController.php create mode 100644 modules/Merchant/Http/Controllers/MerchantStoreController.php create mode 100644 modules/Merchant/Http/Controllers/PrintTaskController.php create mode 100644 modules/Merchant/Http/Controllers/PrinterController.php create mode 100644 modules/Merchant/Http/Controllers/ProductCategoryController.php create mode 100644 modules/Merchant/Http/Requests/MerchantCategoryFormRequest.php create mode 100644 modules/Merchant/Http/Requests/MerchantProductFormRequest.php create mode 100644 modules/Merchant/Http/Requests/MerchantStoreFormRequest.php create mode 100644 modules/Merchant/Http/Requests/PrinterFormRequest.php create mode 100644 modules/Merchant/Http/Requests/ProductCategoryFormRequest.php create mode 100644 modules/Merchant/Models/MerchantCategoryModel.php create mode 100644 modules/Merchant/Models/MerchantProductModel.php create mode 100644 modules/Merchant/Models/MerchantStoreModel.php create mode 100644 modules/Merchant/Models/PrintTaskModel.php create mode 100644 modules/Merchant/Models/PrinterModel.php create mode 100644 modules/Merchant/Models/ProductCategoryModel.php create mode 100644 modules/Merchant/Providers/MerchantServiceProvider.php create mode 100644 web/domain/iMerchantCategory.ts create mode 100644 web/domain/iMerchantProduct.ts create mode 100644 web/domain/iMerchantStore.ts create mode 100644 web/domain/iPrintTask.ts create mode 100644 web/domain/iPrinter.ts create mode 100644 web/domain/iProductCategory.ts create mode 100644 web/locales/en_US/merchant/category.ts create mode 100644 web/locales/en_US/merchant/print-task.ts create mode 100644 web/locales/en_US/merchant/printer.ts create mode 100644 web/locales/en_US/merchant/product-category.ts create mode 100644 web/locales/en_US/merchant/product.ts create mode 100644 web/locales/en_US/merchant/store.ts create mode 100644 web/locales/zh_CN/merchant/category.ts create mode 100644 web/locales/zh_CN/merchant/print-task.ts create mode 100644 web/locales/zh_CN/merchant/printer.ts create mode 100644 web/locales/zh_CN/merchant/product-category.ts create mode 100644 web/locales/zh_CN/merchant/product.ts create mode 100644 web/locales/zh_CN/merchant/store.ts create mode 100644 web/pages/merchant/category/index.tsx create mode 100644 web/pages/merchant/print-task/index.tsx create mode 100644 web/pages/merchant/printer/index.tsx create mode 100644 web/pages/merchant/product-category/index.tsx create mode 100644 web/pages/merchant/product/index.tsx create mode 100644 web/pages/merchant/store/index.tsx diff --git a/bootstrap/providers.php b/bootstrap/providers.php index fa02973..8d06191 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -8,6 +8,7 @@ use Modules\SystemTool\Providers\SystemToolServiceProvider; use Modules\SystemUser\Providers\SystemUserServiceProvider; use Modules\Member\Providers\MemberServiceProvider; use Modules\Forum\Providers\ForumServiceProvider; +use Modules\Merchant\Providers\MerchantServiceProvider; return [ AppServiceProvider::class, @@ -18,4 +19,5 @@ return [ SystemToolServiceProvider::class, MemberServiceProvider::class, ForumServiceProvider::class, + MerchantServiceProvider::class, ]; diff --git a/database/migrations/2026_05_30_000002_create_forum_tables.php b/database/migrations/2026_05_30_000002_create_forum_tables.php index 1422f63..cb34f8f 100644 --- a/database/migrations/2026_05_30_000002_create_forum_tables.php +++ b/database/migrations/2026_05_30_000002_create_forum_tables.php @@ -8,20 +8,19 @@ return new class extends Migration { public function up(): void { - // 话题分类 + // 论坛板块 if (! Schema::hasTable('forum_categories')) { Schema::create('forum_categories', function (Blueprint $table) { $table->id(); - $table->unsignedInteger('parent_id')->default(0)->comment('父级ID'); - $table->string('name', 50)->comment('分类名称'); - $table->string('slug', 50)->comment('标识'); - $table->string('description', 255)->default('')->comment('描述'); + $table->string('name', 50)->comment('板块名称'); + $table->string('description', 255)->default('')->comment('板块描述'); $table->string('icon', 255)->default('')->comment('图标'); $table->integer('sort')->default(0)->comment('排序'); $table->tinyInteger('status')->default(1)->comment('状态:0=禁用,1=正常'); + $table->tinyInteger('review')->default(1)->comment('帖子是否审核:0=无需审核,1=需要审核'); + $table->tinyInteger('comment_review')->default(1)->comment('评论是否审核:0=无需审核,1=需要审核'); $table->timestamps(); - $table->index('slug'); - $table->comment('话题分类'); + $table->comment('帖子板块'); }); } @@ -30,7 +29,7 @@ return new class extends Migration Schema::create('forum_posts', function (Blueprint $table) { $table->id(); $table->unsignedInteger('user_id')->comment('发布用户ID'); - $table->unsignedBigInteger('category_id')->nullable()->comment('分类ID'); + $table->unsignedBigInteger('category_id')->nullable()->comment('帖子板块ID'); $table->string('title', 100)->comment('帖子标题'); $table->text('content')->comment('帖子内容'); $table->json('images')->nullable()->comment('图片列表'); @@ -40,7 +39,7 @@ return new class extends Migration $table->integer('favorite_count')->default(0)->comment('收藏数'); $table->tinyInteger('is_hot')->default(0)->comment('是否热门:0=否,1=是'); $table->tinyInteger('is_top')->default(0)->comment('是否置顶:0=否,1=是'); - $table->tinyInteger('status')->default(1)->comment('状态:0=草稿,1=发布,2=隐藏'); + $table->tinyInteger('status')->default(1)->comment('状态:1=发布,2=隐藏'); $table->timestamps(); $table->softDeletes(); $table->index('user_id'); diff --git a/database/migrations/2026_05_30_000004_create_part_time_tables.php b/database/migrations/2026_05_30_000004_create_part_time_tables.php index 5a8af88..74eb428 100644 --- a/database/migrations/2026_05_30_000004_create_part_time_tables.php +++ b/database/migrations/2026_05_30_000004_create_part_time_tables.php @@ -8,45 +8,21 @@ return new class extends Migration { public function up(): void { - // 兼职岗位 - if (! Schema::hasTable('part_time_jobs')) { - Schema::create('part_time_jobs', function (Blueprint $table) { - $table->id(); - $table->unsignedInteger('merchant_id')->nullable()->comment('发布商户ID'); - $table->string('title', 100)->comment('岗位标题'); - $table->text('description')->nullable()->comment('岗位描述'); - $table->text('requirements')->nullable()->comment('任职要求'); - $table->decimal('salary', 10, 2)->default(0)->comment('薪资'); - $table->tinyInteger('salary_type')->default(1)->comment('薪资类型:1=时薪,2=日薪,3=月薪'); - $table->string('location', 255)->default('')->comment('工作地点'); - $table->string('contact_name', 30)->default('')->comment('联系人'); - $table->string('contact_mobile', 20)->default('')->comment('联系电话'); - $table->integer('quota')->default(1)->comment('招聘人数'); - $table->integer('applied_count')->default(0)->comment('已申请人数'); - $table->tinyInteger('status')->default(1)->comment('状态:0=草稿,1=发布,2=已关闭'); - $table->date('start_date')->nullable()->comment('开始日期'); - $table->date('end_date')->nullable()->comment('截止日期'); - $table->timestamps(); - $table->index('merchant_id'); - $table->index('status'); - $table->comment('兼职岗位'); - }); - } - // 兼职申请/报名 if (! Schema::hasTable('part_time_applications')) { Schema::create('part_time_applications', function (Blueprint $table) { $table->id(); - $table->unsignedBigInteger('job_id')->comment('岗位ID'); $table->unsignedInteger('user_id')->comment('申请用户ID'); $table->string('real_name', 30)->comment('真实姓名'); $table->string('mobile', 20)->comment('联系电话'); $table->string('resume', 500)->default('')->comment('个人简介/简历'); + $table->string('id_card', 18)->comment('身份证号'); + $table->string('id_card_front', 255)->default('')->comment('身份证正面照'); + $table->string('id_card_back', 255)->default('')->comment('身份证背面照'); $table->tinyInteger('status')->default(0)->comment('状态:0=待审核,1=已通过,2=已拒绝'); $table->string('review_remark', 255)->default('')->comment('审核备注'); $table->timestamp('reviewed_at')->nullable()->comment('审核时间'); $table->timestamps(); - $table->index('job_id'); $table->index('user_id'); $table->comment('兼职申请'); }); @@ -60,12 +36,11 @@ return new class extends Migration $table->string('real_name', 30)->comment('真实姓名'); $table->string('mobile', 20)->comment('联系电话'); $table->string('id_card', 18)->comment('身份证号'); - $table->string('id_card_front', 255)->default('')->comment('身份证正面照'); - $table->string('id_card_back', 255)->default('')->comment('身份证背面照'); - $table->decimal('balance', 10, 2)->default(0)->comment('佣金余额'); $table->decimal('total_income', 10, 2)->default(0)->comment('累计收入'); - $table->tinyInteger('status')->default(0)->comment('状态:0=待认证,1=已认证,2=已拒绝'); + $table->integer('total_order')->default(0)->comment('累计订单'); + $table->decimal('credit_score', 10, 2)->default(100)->comment('信誉分'); $table->timestamp('verified_at')->nullable()->comment('认证时间'); + $table->tinyInteger('status')->default(1)->comment('状态:0=禁止接单,1=正常'); $table->timestamps(); $table->index('user_id'); $table->index('mobile'); diff --git a/database/seeders/SysUserSeeder.php b/database/seeders/SysUserSeeder.php index 2991830..ece49d5 100644 --- a/database/seeders/SysUserSeeder.php +++ b/database/seeders/SysUserSeeder.php @@ -477,6 +477,91 @@ class SysUserSeeder extends Seeder ], ], ], + [ + 'type' => 'menu', + 'name' => '校园商户', + 'local' => 'menu.merchant', + 'icon' => 'ShopOutlined', + 'key' => 'merchant', + 'children' => [ + [ + 'type' => 'route', + 'name' => '商户分类', + 'key' => 'merchant.category', + 'path' => '/merchant/category', + 'local' => 'menu.merchant.category', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.category.query'], + ['type' => 'rule', 'name' => '新增分类', 'key' => 'merchant.category.create'], + ['type' => 'rule', 'name' => '修改分类', 'key' => 'merchant.category.update'], + ['type' => 'rule', 'name' => '删除分类', 'key' => 'merchant.category.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '商户管理', + 'key' => 'merchant.store', + 'path' => '/merchant/store', + 'local' => 'menu.merchant.store', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.store.query'], + ['type' => 'rule', 'name' => '新增商户', 'key' => 'merchant.store.create'], + ['type' => 'rule', 'name' => '修改商户', 'key' => 'merchant.store.update'], + ['type' => 'rule', 'name' => '删除商户', 'key' => 'merchant.store.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '商品分类', + 'key' => 'merchant.product-category', + 'path' => '/merchant/product-category', + 'local' => 'menu.merchant.product-category', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.product-category.query'], + ['type' => 'rule', 'name' => '新增分类', 'key' => 'merchant.product-category.create'], + ['type' => 'rule', 'name' => '修改分类', 'key' => 'merchant.product-category.update'], + ['type' => 'rule', 'name' => '删除分类', 'key' => 'merchant.product-category.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '商品管理', + 'key' => 'merchant.product', + 'path' => '/merchant/product', + 'local' => 'menu.merchant.product', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.product.query'], + ['type' => 'rule', 'name' => '新增商品', 'key' => 'merchant.product.create'], + ['type' => 'rule', 'name' => '修改商品', 'key' => 'merchant.product.update'], + ['type' => 'rule', 'name' => '删除商品', 'key' => 'merchant.product.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '打印机管理', + 'key' => 'merchant.printer', + 'path' => '/merchant/printer', + 'local' => 'menu.merchant.printer', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.printer.query'], + ['type' => 'rule', 'name' => '新增打印机', 'key' => 'merchant.printer.create'], + ['type' => 'rule', 'name' => '修改打印机', 'key' => 'merchant.printer.update'], + ['type' => 'rule', 'name' => '删除打印机', 'key' => 'merchant.printer.delete'], + ], + ], + [ + 'type' => 'route', + 'name' => '打印任务', + 'key' => 'merchant.print-task', + 'path' => '/merchant/print-task', + 'local' => 'menu.merchant.print-task', + 'children' => [ + ['type' => 'rule', 'name' => '查询列表', 'key' => 'merchant.print-task.query'], + ['type' => 'rule', 'name' => '删除任务', 'key' => 'merchant.print-task.delete'], + ], + ], + ], + ], [ 'type' => 'route', 'name' => 'XinAdmin', diff --git a/modules/Merchant/Http/Controllers/MerchantCategoryController.php b/modules/Merchant/Http/Controllers/MerchantCategoryController.php new file mode 100644 index 0000000..9139328 --- /dev/null +++ b/modules/Merchant/Http/Controllers/MerchantCategoryController.php @@ -0,0 +1,73 @@ + '=', + ]; + + protected array $quickSearchField = ['name', 'slug']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = MerchantCategoryModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(MerchantCategoryFormRequest $request): JsonResponse + { + MerchantCategoryModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, MerchantCategoryFormRequest $request): JsonResponse + { + $model = MerchantCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = MerchantCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Controllers/MerchantProductController.php b/modules/Merchant/Http/Controllers/MerchantProductController.php new file mode 100644 index 0000000..cdb7af7 --- /dev/null +++ b/modules/Merchant/Http/Controllers/MerchantProductController.php @@ -0,0 +1,76 @@ + '=', + 'is_recommend' => '=', + 'merchant_id' => '=', + 'category_id' => '=', + ]; + + protected array $quickSearchField = ['name']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = MerchantProductModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(MerchantProductFormRequest $request): JsonResponse + { + MerchantProductModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, MerchantProductFormRequest $request): JsonResponse + { + $model = MerchantProductModel::find($id); + if (empty($model)) { + return $this->error('商品不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = MerchantProductModel::find($id); + if (empty($model)) { + return $this->error('商品不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Controllers/MerchantStoreController.php b/modules/Merchant/Http/Controllers/MerchantStoreController.php new file mode 100644 index 0000000..da71953 --- /dev/null +++ b/modules/Merchant/Http/Controllers/MerchantStoreController.php @@ -0,0 +1,74 @@ + '=', + 'category_id' => '=', + ]; + + protected array $quickSearchField = ['name', 'contact_name', 'contact_mobile']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = MerchantStoreModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(MerchantStoreFormRequest $request): JsonResponse + { + MerchantStoreModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, MerchantStoreFormRequest $request): JsonResponse + { + $model = MerchantStoreModel::find($id); + if (empty($model)) { + return $this->error('商户不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = MerchantStoreModel::find($id); + if (empty($model)) { + return $this->error('商户不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Controllers/PrintTaskController.php b/modules/Merchant/Http/Controllers/PrintTaskController.php new file mode 100644 index 0000000..e188245 --- /dev/null +++ b/modules/Merchant/Http/Controllers/PrintTaskController.php @@ -0,0 +1,47 @@ + '=', + 'printer_id' => '=', + ]; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = PrintTaskModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = PrintTaskModel::find($id); + if (empty($model)) { + return $this->error('打印任务不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Controllers/PrinterController.php b/modules/Merchant/Http/Controllers/PrinterController.php new file mode 100644 index 0000000..2f8d398 --- /dev/null +++ b/modules/Merchant/Http/Controllers/PrinterController.php @@ -0,0 +1,74 @@ + '=', + 'merchant_id' => '=', + ]; + + protected array $quickSearchField = ['name', 'brand', 'model']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = PrinterModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(PrinterFormRequest $request): JsonResponse + { + PrinterModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, PrinterFormRequest $request): JsonResponse + { + $model = PrinterModel::find($id); + if (empty($model)) { + return $this->error('打印机不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = PrinterModel::find($id); + if (empty($model)) { + return $this->error('打印机不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Controllers/ProductCategoryController.php b/modules/Merchant/Http/Controllers/ProductCategoryController.php new file mode 100644 index 0000000..a041d60 --- /dev/null +++ b/modules/Merchant/Http/Controllers/ProductCategoryController.php @@ -0,0 +1,74 @@ + '=', + 'merchant_id' => '=', + ]; + + protected array $quickSearchField = ['name', 'slug']; + + #[GetRoute(authorize: 'query')] + public function query(Request $request): JsonResponse + { + $params = $request->all(); + $pageSize = $params['pageSize'] ?? 10; + $query = ProductCategoryModel::query(); + $data = $this->buildSearch($params, $query) + ->paginate($pageSize) + ->toArray(); + return $this->success($data); + } + + #[PostRoute(authorize: 'create')] + public function create(ProductCategoryFormRequest $request): JsonResponse + { + ProductCategoryModel::create($request->validated()); + return $this->success(); + } + + #[PutRoute( + route: '/{id}', + authorize: 'update', + where: ['id' => '[0-9]+'] + )] + public function update(int $id, ProductCategoryFormRequest $request): JsonResponse + { + $model = ProductCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->update($request->validated()); + return $this->success(); + } + + #[DeleteRoute( + route: '/{id}', + authorize: 'delete', + where: ['id' => '[0-9]+'] + )] + public function delete(int $id): JsonResponse + { + $model = ProductCategoryModel::find($id); + if (empty($model)) { + return $this->error('分类不存在'); + } + $model->delete(); + return $this->success(); + } +} diff --git a/modules/Merchant/Http/Requests/MerchantCategoryFormRequest.php b/modules/Merchant/Http/Requests/MerchantCategoryFormRequest.php new file mode 100644 index 0000000..208f8a7 --- /dev/null +++ b/modules/Merchant/Http/Requests/MerchantCategoryFormRequest.php @@ -0,0 +1,48 @@ +isUpdate()) { + return [ + 'name' => 'required|max:50', + 'slug' => 'required|max:50|unique:merchant_categories,slug', + 'description' => 'nullable|max:255', + 'icon' => 'nullable|max:255', + 'sort' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + $id = $this->route('id'); + return [ + 'name' => 'required|max:50', + 'slug' => [ + 'required', + 'max:50', + Rule::unique('merchant_categories', 'slug')->ignore($id), + ], + 'description' => 'nullable|max:255', + 'icon' => 'nullable|max:255', + 'sort' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '分类名称不能为空', + 'slug.required' => '分类标识不能为空', + 'slug.unique' => '分类标识已存在', + ]; + } +} diff --git a/modules/Merchant/Http/Requests/MerchantProductFormRequest.php b/modules/Merchant/Http/Requests/MerchantProductFormRequest.php new file mode 100644 index 0000000..2612505 --- /dev/null +++ b/modules/Merchant/Http/Requests/MerchantProductFormRequest.php @@ -0,0 +1,37 @@ + 'required|integer', + 'category_id' => 'nullable|integer', + 'name' => 'required|max:100', + 'description' => 'nullable', + 'images' => 'nullable|array', + 'price' => 'required|numeric', + 'original_price' => 'nullable|numeric', + 'stock' => 'nullable|integer', + 'unit' => 'nullable|max:20', + 'status' => 'nullable|integer|in:0,1', + 'is_recommend' => 'nullable|integer|in:0,1', + 'sort' => 'nullable|integer', + ]; + } + + public function messages(): array + { + return [ + 'merchant_id.required' => '所属商户不能为空', + 'name.required' => '商品名称不能为空', + 'price.required' => '售价不能为空', + ]; + } +} diff --git a/modules/Merchant/Http/Requests/MerchantStoreFormRequest.php b/modules/Merchant/Http/Requests/MerchantStoreFormRequest.php new file mode 100644 index 0000000..68efd4c --- /dev/null +++ b/modules/Merchant/Http/Requests/MerchantStoreFormRequest.php @@ -0,0 +1,42 @@ + 'required|integer', + 'category_id' => 'nullable|integer', + 'name' => 'required|max:50', + 'logo' => 'nullable|max:255', + 'banner' => 'nullable|max:255', + 'description' => 'nullable', + 'contact_name' => 'nullable|max:30', + 'contact_mobile' => 'nullable|max:20', + 'province' => 'nullable|max:30', + 'city' => 'nullable|max:30', + 'district' => 'nullable|max:30', + 'address' => 'nullable|max:255', + 'latitude' => 'nullable|max:20', + 'longitude' => 'nullable|max:20', + 'business_hours' => 'nullable|max:100', + 'min_price' => 'nullable|numeric', + 'status' => 'nullable|integer|in:0,1,2,3', + 'reject_reason' => 'nullable|max:255', + ]; + } + + public function messages(): array + { + return [ + 'name.required' => '店铺名称不能为空', + 'user_id.required' => '所属用户不能为空', + ]; + } +} diff --git a/modules/Merchant/Http/Requests/PrinterFormRequest.php b/modules/Merchant/Http/Requests/PrinterFormRequest.php new file mode 100644 index 0000000..e1bc54e --- /dev/null +++ b/modules/Merchant/Http/Requests/PrinterFormRequest.php @@ -0,0 +1,32 @@ + 'required|integer', + 'name' => 'required|max:50', + 'brand' => 'nullable|max:50', + 'model' => 'nullable|max:50', + 'device_no' => 'nullable|max:100', + 'secret_key' => 'nullable|max:255', + 'status' => 'nullable|integer|in:0,1,2', + 'config' => 'nullable|array', + ]; + } + + public function messages(): array + { + return [ + 'merchant_id.required' => '所属商户不能为空', + 'name.required' => '打印机名称不能为空', + ]; + } +} diff --git a/modules/Merchant/Http/Requests/ProductCategoryFormRequest.php b/modules/Merchant/Http/Requests/ProductCategoryFormRequest.php new file mode 100644 index 0000000..c6654be --- /dev/null +++ b/modules/Merchant/Http/Requests/ProductCategoryFormRequest.php @@ -0,0 +1,33 @@ + 'required|integer', + 'parent_id' => 'nullable|integer', + 'name' => 'required|max:50', + 'slug' => 'required|max:50', + 'description' => 'nullable|max:255', + 'icon' => 'nullable|max:255', + 'sort' => 'nullable|integer', + 'status' => 'nullable|integer|in:0,1', + ]; + } + + public function messages(): array + { + return [ + 'merchant_id.required' => '所属商户不能为空', + 'name.required' => '分类名称不能为空', + 'slug.required' => '分类标识不能为空', + ]; + } +} diff --git a/modules/Merchant/Models/MerchantCategoryModel.php b/modules/Merchant/Models/MerchantCategoryModel.php new file mode 100644 index 0000000..88b1d9c --- /dev/null +++ b/modules/Merchant/Models/MerchantCategoryModel.php @@ -0,0 +1,24 @@ + 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Merchant/Models/MerchantProductModel.php b/modules/Merchant/Models/MerchantProductModel.php new file mode 100644 index 0000000..79457a1 --- /dev/null +++ b/modules/Merchant/Models/MerchantProductModel.php @@ -0,0 +1,42 @@ + 'integer', + 'category_id' => 'integer', + 'images' => 'array', + 'price' => 'decimal:2', + 'original_price' => 'decimal:2', + 'stock' => 'integer', + 'sales' => 'integer', + 'status' => 'integer', + 'is_recommend' => 'integer', + 'sort' => 'integer', + ]; +} diff --git a/modules/Merchant/Models/MerchantStoreModel.php b/modules/Merchant/Models/MerchantStoreModel.php new file mode 100644 index 0000000..587de78 --- /dev/null +++ b/modules/Merchant/Models/MerchantStoreModel.php @@ -0,0 +1,42 @@ + 'integer', + 'category_id' => 'integer', + 'balance' => 'decimal:2', + 'total_income' => 'decimal:2', + 'min_price' => 'decimal:2', + 'status' => 'integer', + ]; +} diff --git a/modules/Merchant/Models/PrintTaskModel.php b/modules/Merchant/Models/PrintTaskModel.php new file mode 100644 index 0000000..b18238c --- /dev/null +++ b/modules/Merchant/Models/PrintTaskModel.php @@ -0,0 +1,28 @@ + 'integer', + 'order_id' => 'integer', + 'copies' => 'integer', + 'status' => 'integer', + 'printed_at' => 'datetime', + ]; +} diff --git a/modules/Merchant/Models/PrinterModel.php b/modules/Merchant/Models/PrinterModel.php new file mode 100644 index 0000000..9807480 --- /dev/null +++ b/modules/Merchant/Models/PrinterModel.php @@ -0,0 +1,27 @@ + 'integer', + 'status' => 'integer', + 'config' => 'array', + ]; +} diff --git a/modules/Merchant/Models/ProductCategoryModel.php b/modules/Merchant/Models/ProductCategoryModel.php new file mode 100644 index 0000000..982d9fc --- /dev/null +++ b/modules/Merchant/Models/ProductCategoryModel.php @@ -0,0 +1,28 @@ + 'integer', + 'parent_id' => 'integer', + 'sort' => 'integer', + 'status' => 'integer', + ]; +} diff --git a/modules/Merchant/Providers/MerchantServiceProvider.php b/modules/Merchant/Providers/MerchantServiceProvider.php new file mode 100644 index 0000000..551d041 --- /dev/null +++ b/modules/Merchant/Providers/MerchantServiceProvider.php @@ -0,0 +1,14 @@ +register(base_path('modules/Merchant/Http/Controllers')); + } +} diff --git a/web/domain/iMerchantCategory.ts b/web/domain/iMerchantCategory.ts new file mode 100644 index 0000000..2fca570 --- /dev/null +++ b/web/domain/iMerchantCategory.ts @@ -0,0 +1,11 @@ +export default interface IMerchantCategory { + id?: number; + name?: string; + slug?: string; + description?: string; + icon?: string; + sort?: number; + status?: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iMerchantProduct.ts b/web/domain/iMerchantProduct.ts new file mode 100644 index 0000000..ab0e8ec --- /dev/null +++ b/web/domain/iMerchantProduct.ts @@ -0,0 +1,19 @@ +export default interface IMerchantProduct { + id?: number; + merchant_id?: number; + category_id?: number; + name?: string; + description?: string; + images?: string[]; + price?: number; + original_price?: number; + stock?: number; + unit?: string; + sales?: number; + status?: number; + is_recommend?: number; + sort?: number; + created_at?: string; + updated_at?: string; + deleted_at?: string; +} diff --git a/web/domain/iMerchantStore.ts b/web/domain/iMerchantStore.ts new file mode 100644 index 0000000..1241f9d --- /dev/null +++ b/web/domain/iMerchantStore.ts @@ -0,0 +1,25 @@ +export default interface IMerchantStore { + id?: number; + user_id?: number; + category_id?: number; + name?: string; + logo?: string; + banner?: string; + description?: string; + contact_name?: string; + contact_mobile?: string; + province?: string; + city?: string; + district?: string; + address?: string; + latitude?: string; + longitude?: string; + business_hours?: string; + balance?: number; + total_income?: number; + min_price?: number; + status?: number; + reject_reason?: string; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iPrintTask.ts b/web/domain/iPrintTask.ts new file mode 100644 index 0000000..a7228a9 --- /dev/null +++ b/web/domain/iPrintTask.ts @@ -0,0 +1,12 @@ +export default interface IPrintTask { + id?: number; + printer_id?: number; + order_id?: number; + content?: string; + copies?: number; + status?: number; + error_msg?: string; + printed_at?: string; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iPrinter.ts b/web/domain/iPrinter.ts new file mode 100644 index 0000000..657df0a --- /dev/null +++ b/web/domain/iPrinter.ts @@ -0,0 +1,13 @@ +export default interface IPrinter { + id?: number; + merchant_id?: number; + name?: string; + brand?: string; + model?: string; + device_no?: string; + secret_key?: string; + status?: number; + config?: Record; + created_at?: string; + updated_at?: string; +} diff --git a/web/domain/iProductCategory.ts b/web/domain/iProductCategory.ts new file mode 100644 index 0000000..d819fd3 --- /dev/null +++ b/web/domain/iProductCategory.ts @@ -0,0 +1,13 @@ +export default interface IProductCategory { + id?: number; + merchant_id?: number; + parent_id?: number; + name?: string; + slug?: string; + description?: string; + icon?: string; + sort?: number; + status?: number; + created_at?: string; + updated_at?: string; +} diff --git a/web/locales/en_US/index.ts b/web/locales/en_US/index.ts index 5e134f6..e48faec 100644 --- a/web/locales/en_US/index.ts +++ b/web/locales/en_US/index.ts @@ -31,6 +31,13 @@ import forumSensitiveWord from "./forum/sensitive-word"; import forumPostLike from "./forum/post-like"; import forumPostFavorite from "./forum/post-favorite"; +import merchantCategory from "./merchant/category"; +import merchantStore from "./merchant/store"; +import merchantProductCategory from "./merchant/product-category"; +import merchantProduct from "./merchant/product"; +import merchantPrinter from "./merchant/printer"; +import merchantPrintTask from "./merchant/print-task"; + import userProfile from "./user/profile"; import xinForm from "./components/xin-form"; @@ -67,6 +74,12 @@ export default { ...forumSensitiveWord, ...forumPostLike, ...forumPostFavorite, + ...merchantCategory, + ...merchantStore, + ...merchantProductCategory, + ...merchantProduct, + ...merchantPrinter, + ...merchantPrintTask, ...userProfile, ...xinForm, ...xinTable, diff --git a/web/locales/en_US/menu.ts b/web/locales/en_US/menu.ts index 8a60733..b2a4429 100644 --- a/web/locales/en_US/menu.ts +++ b/web/locales/en_US/menu.ts @@ -54,5 +54,12 @@ export default { "menu.forum.sensitive-word": "Sensitive Words", "menu.forum.post-like": "Like Records", "menu.forum.post-favorite": "Favorite Records", + "menu.merchant": "Campus Merchant", + "menu.merchant.category": "Categories", + "menu.merchant.store": "Merchants", + "menu.merchant.product-category": "Product Categories", + "menu.merchant.product": "Products", + "menu.merchant.printer": "Printers", + "menu.merchant.print-task": "Print Tasks", "menu.xin-admin": "XinAdmin", } diff --git a/web/locales/en_US/merchant/category.ts b/web/locales/en_US/merchant/category.ts new file mode 100644 index 0000000..0a9f42d --- /dev/null +++ b/web/locales/en_US/merchant/category.ts @@ -0,0 +1,17 @@ +export default { + "merchant.category.page.title": "Merchant Categories", + "merchant.category.page.description": "Manage campus merchant categories for grouping", + "merchant.category.id": "ID", + "merchant.category.name": "Name", + "merchant.category.name.required": "Category name is required", + "merchant.category.slug": "Slug", + "merchant.category.slug.required": "Category slug is required", + "merchant.category.description": "Description", + "merchant.category.icon": "Icon", + "merchant.category.sort": "Sort", + "merchant.category.status": "Status", + "merchant.category.status.0": "Disabled", + "merchant.category.status.1": "Active", + "merchant.category.created_at": "Created At", + "merchant.category.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/merchant/print-task.ts b/web/locales/en_US/merchant/print-task.ts new file mode 100644 index 0000000..8bedb24 --- /dev/null +++ b/web/locales/en_US/merchant/print-task.ts @@ -0,0 +1,18 @@ +export default { + "merchant.print-task.page.title": "Print Tasks", + "merchant.print-task.page.description": "View and manage merchant printer task records", + "merchant.print-task.id": "ID", + "merchant.print-task.printer_id": "Printer", + "merchant.print-task.order_id": "Order", + "merchant.print-task.content": "Content", + "merchant.print-task.copies": "Copies", + "merchant.print-task.status": "Status", + "merchant.print-task.status.0": "Pending", + "merchant.print-task.status.1": "Printing", + "merchant.print-task.status.2": "Completed", + "merchant.print-task.status.3": "Failed", + "merchant.print-task.error_msg": "Error", + "merchant.print-task.printed_at": "Printed At", + "merchant.print-task.created_at": "Created At", + "merchant.print-task.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/merchant/printer.ts b/web/locales/en_US/merchant/printer.ts new file mode 100644 index 0000000..6a4eb40 --- /dev/null +++ b/web/locales/en_US/merchant/printer.ts @@ -0,0 +1,20 @@ +export default { + "merchant.printer.page.title": "Printer Management", + "merchant.printer.page.description": "Manage merchant printer devices with online/offline status monitoring", + "merchant.printer.id": "ID", + "merchant.printer.merchant_id": "Merchant", + "merchant.printer.merchant_id.required": "Merchant is required", + "merchant.printer.name": "Name", + "merchant.printer.name.required": "Printer name is required", + "merchant.printer.brand": "Brand", + "merchant.printer.model": "Model", + "merchant.printer.device_no": "Device No", + "merchant.printer.secret_key": "Secret Key", + "merchant.printer.status": "Status", + "merchant.printer.status.0": "Offline", + "merchant.printer.status.1": "Online", + "merchant.printer.status.2": "Fault", + "merchant.printer.config": "Config", + "merchant.printer.created_at": "Created At", + "merchant.printer.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/merchant/product-category.ts b/web/locales/en_US/merchant/product-category.ts new file mode 100644 index 0000000..fe226d6 --- /dev/null +++ b/web/locales/en_US/merchant/product-category.ts @@ -0,0 +1,20 @@ +export default { + "merchant.product-category.page.title": "Product Categories", + "merchant.product-category.page.description": "Manage product categories under merchants with parent-child structure", + "merchant.product-category.id": "ID", + "merchant.product-category.merchant_id": "Merchant", + "merchant.product-category.merchant_id.required": "Merchant is required", + "merchant.product-category.parent_id": "Parent", + "merchant.product-category.name": "Name", + "merchant.product-category.name.required": "Category name is required", + "merchant.product-category.slug": "Slug", + "merchant.product-category.slug.required": "Category slug is required", + "merchant.product-category.description": "Description", + "merchant.product-category.icon": "Icon", + "merchant.product-category.sort": "Sort", + "merchant.product-category.status": "Status", + "merchant.product-category.status.0": "Disabled", + "merchant.product-category.status.1": "Active", + "merchant.product-category.created_at": "Created At", + "merchant.product-category.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/merchant/product.ts b/web/locales/en_US/merchant/product.ts new file mode 100644 index 0000000..d768264 --- /dev/null +++ b/web/locales/en_US/merchant/product.ts @@ -0,0 +1,27 @@ +export default { + "merchant.product.page.title": "Product Management", + "merchant.product.page.description": "Manage merchant products, support listing, delisting, recommend and more", + "merchant.product.id": "ID", + "merchant.product.merchant_id": "Merchant", + "merchant.product.merchant_id.required": "Merchant is required", + "merchant.product.category_id": "Category", + "merchant.product.name": "Name", + "merchant.product.name.required": "Product name is required", + "merchant.product.description": "Description", + "merchant.product.images": "Images", + "merchant.product.price": "Price", + "merchant.product.price.required": "Price is required", + "merchant.product.original_price": "Original Price", + "merchant.product.stock": "Stock", + "merchant.product.unit": "Unit", + "merchant.product.sales": "Sales", + "merchant.product.status": "Status", + "merchant.product.status.0": "Delisted", + "merchant.product.status.1": "Listed", + "merchant.product.is_recommend": "Recommended", + "merchant.product.is_recommend.0": "No", + "merchant.product.is_recommend.1": "Yes", + "merchant.product.sort": "Sort", + "merchant.product.created_at": "Created At", + "merchant.product.updated_at": "Updated At", +}; diff --git a/web/locales/en_US/merchant/store.ts b/web/locales/en_US/merchant/store.ts new file mode 100644 index 0000000..97fb170 --- /dev/null +++ b/web/locales/en_US/merchant/store.ts @@ -0,0 +1,31 @@ +export default { + "merchant.store.page.title": "Merchant Management", + "merchant.store.page.description": "Manage campus merchants/stores, support review, enable, disable and more", + "merchant.store.id": "ID", + "merchant.store.user_id": "Owner", + "merchant.store.user_id.required": "Owner is required", + "merchant.store.category_id": "Category", + "merchant.store.name": "Store Name", + "merchant.store.name.required": "Store name is required", + "merchant.store.logo": "Logo", + "merchant.store.banner": "Banner", + "merchant.store.description": "Description", + "merchant.store.contact_name": "Contact", + "merchant.store.contact_mobile": "Phone", + "merchant.store.province": "Province", + "merchant.store.city": "City", + "merchant.store.district": "District", + "merchant.store.address": "Address", + "merchant.store.business_hours": "Business Hours", + "merchant.store.balance": "Balance", + "merchant.store.total_income": "Total Income", + "merchant.store.min_price": "Min Price", + "merchant.store.status": "Status", + "merchant.store.status.0": "Pending", + "merchant.store.status.1": "Active", + "merchant.store.status.2": "Disabled", + "merchant.store.status.3": "Closed", + "merchant.store.reject_reason": "Reject Reason", + "merchant.store.created_at": "Joined At", + "merchant.store.updated_at": "Updated At", +}; diff --git a/web/locales/zh_CN/index.ts b/web/locales/zh_CN/index.ts index 5e134f6..e48faec 100644 --- a/web/locales/zh_CN/index.ts +++ b/web/locales/zh_CN/index.ts @@ -31,6 +31,13 @@ import forumSensitiveWord from "./forum/sensitive-word"; import forumPostLike from "./forum/post-like"; import forumPostFavorite from "./forum/post-favorite"; +import merchantCategory from "./merchant/category"; +import merchantStore from "./merchant/store"; +import merchantProductCategory from "./merchant/product-category"; +import merchantProduct from "./merchant/product"; +import merchantPrinter from "./merchant/printer"; +import merchantPrintTask from "./merchant/print-task"; + import userProfile from "./user/profile"; import xinForm from "./components/xin-form"; @@ -67,6 +74,12 @@ export default { ...forumSensitiveWord, ...forumPostLike, ...forumPostFavorite, + ...merchantCategory, + ...merchantStore, + ...merchantProductCategory, + ...merchantProduct, + ...merchantPrinter, + ...merchantPrintTask, ...userProfile, ...xinForm, ...xinTable, diff --git a/web/locales/zh_CN/menu.ts b/web/locales/zh_CN/menu.ts index 4b4e9bd..e95366c 100644 --- a/web/locales/zh_CN/menu.ts +++ b/web/locales/zh_CN/menu.ts @@ -54,5 +54,12 @@ export default { "menu.forum.sensitive-word": "敏感词管理", "menu.forum.post-like": "点赞记录", "menu.forum.post-favorite": "收藏记录", + "menu.merchant": "校园商户", + "menu.merchant.category": "商户分类", + "menu.merchant.store": "商户管理", + "menu.merchant.product-category": "商品分类", + "menu.merchant.product": "商品管理", + "menu.merchant.printer": "打印机管理", + "menu.merchant.print-task": "打印任务", "menu.xin-admin": "XinAdmin", }; diff --git a/web/locales/zh_CN/merchant/category.ts b/web/locales/zh_CN/merchant/category.ts new file mode 100644 index 0000000..d5be866 --- /dev/null +++ b/web/locales/zh_CN/merchant/category.ts @@ -0,0 +1,17 @@ +export default { + "merchant.category.page.title": "商户分类", + "merchant.category.page.description": "管理校园商户分类,方便对商户进行归类管理", + "merchant.category.id": "ID", + "merchant.category.name": "分类名称", + "merchant.category.name.required": "分类名称不能为空", + "merchant.category.slug": "标识", + "merchant.category.slug.required": "分类标识不能为空", + "merchant.category.description": "描述", + "merchant.category.icon": "图标", + "merchant.category.sort": "排序", + "merchant.category.status": "状态", + "merchant.category.status.0": "禁用", + "merchant.category.status.1": "正常", + "merchant.category.created_at": "创建时间", + "merchant.category.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/merchant/print-task.ts b/web/locales/zh_CN/merchant/print-task.ts new file mode 100644 index 0000000..d885e12 --- /dev/null +++ b/web/locales/zh_CN/merchant/print-task.ts @@ -0,0 +1,18 @@ +export default { + "merchant.print-task.page.title": "打印任务", + "merchant.print-task.page.description": "查看和管理商户打印机任务记录", + "merchant.print-task.id": "ID", + "merchant.print-task.printer_id": "打印机", + "merchant.print-task.order_id": "关联订单", + "merchant.print-task.content": "打印内容", + "merchant.print-task.copies": "打印份数", + "merchant.print-task.status": "状态", + "merchant.print-task.status.0": "待打印", + "merchant.print-task.status.1": "打印中", + "merchant.print-task.status.2": "已完成", + "merchant.print-task.status.3": "失败", + "merchant.print-task.error_msg": "错误信息", + "merchant.print-task.printed_at": "打印时间", + "merchant.print-task.created_at": "创建时间", + "merchant.print-task.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/merchant/printer.ts b/web/locales/zh_CN/merchant/printer.ts new file mode 100644 index 0000000..0bc55a9 --- /dev/null +++ b/web/locales/zh_CN/merchant/printer.ts @@ -0,0 +1,20 @@ +export default { + "merchant.printer.page.title": "打印机管理", + "merchant.printer.page.description": "管理商户打印机设备,支持在线、离线状态监控", + "merchant.printer.id": "ID", + "merchant.printer.merchant_id": "所属商户", + "merchant.printer.merchant_id.required": "所属商户不能为空", + "merchant.printer.name": "打印机名称", + "merchant.printer.name.required": "打印机名称不能为空", + "merchant.printer.brand": "品牌", + "merchant.printer.model": "型号", + "merchant.printer.device_no": "设备编号", + "merchant.printer.secret_key": "密钥", + "merchant.printer.status": "状态", + "merchant.printer.status.0": "离线", + "merchant.printer.status.1": "在线", + "merchant.printer.status.2": "故障", + "merchant.printer.config": "打印配置", + "merchant.printer.created_at": "创建时间", + "merchant.printer.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/merchant/product-category.ts b/web/locales/zh_CN/merchant/product-category.ts new file mode 100644 index 0000000..b6e8a05 --- /dev/null +++ b/web/locales/zh_CN/merchant/product-category.ts @@ -0,0 +1,20 @@ +export default { + "merchant.product-category.page.title": "商品分类", + "merchant.product-category.page.description": "管理商户下的商品分类,支持父子级分类结构", + "merchant.product-category.id": "ID", + "merchant.product-category.merchant_id": "所属商户", + "merchant.product-category.merchant_id.required": "所属商户不能为空", + "merchant.product-category.parent_id": "父级分类", + "merchant.product-category.name": "分类名称", + "merchant.product-category.name.required": "分类名称不能为空", + "merchant.product-category.slug": "标识", + "merchant.product-category.slug.required": "分类标识不能为空", + "merchant.product-category.description": "描述", + "merchant.product-category.icon": "图标", + "merchant.product-category.sort": "排序", + "merchant.product-category.status": "状态", + "merchant.product-category.status.0": "禁用", + "merchant.product-category.status.1": "正常", + "merchant.product-category.created_at": "创建时间", + "merchant.product-category.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/merchant/product.ts b/web/locales/zh_CN/merchant/product.ts new file mode 100644 index 0000000..2b225d4 --- /dev/null +++ b/web/locales/zh_CN/merchant/product.ts @@ -0,0 +1,27 @@ +export default { + "merchant.product.page.title": "商品管理", + "merchant.product.page.description": "管理商户商品,支持上架、下架、推荐等操作", + "merchant.product.id": "ID", + "merchant.product.merchant_id": "所属商户", + "merchant.product.merchant_id.required": "所属商户不能为空", + "merchant.product.category_id": "商品分类", + "merchant.product.name": "商品名称", + "merchant.product.name.required": "商品名称不能为空", + "merchant.product.description": "商品描述", + "merchant.product.images": "商品图片", + "merchant.product.price": "售价", + "merchant.product.price.required": "售价不能为空", + "merchant.product.original_price": "原价", + "merchant.product.stock": "库存", + "merchant.product.unit": "单位", + "merchant.product.sales": "销量", + "merchant.product.status": "状态", + "merchant.product.status.0": "下架", + "merchant.product.status.1": "上架", + "merchant.product.is_recommend": "推荐", + "merchant.product.is_recommend.0": "否", + "merchant.product.is_recommend.1": "是", + "merchant.product.sort": "排序", + "merchant.product.created_at": "创建时间", + "merchant.product.updated_at": "更新时间", +}; diff --git a/web/locales/zh_CN/merchant/store.ts b/web/locales/zh_CN/merchant/store.ts new file mode 100644 index 0000000..e1ec552 --- /dev/null +++ b/web/locales/zh_CN/merchant/store.ts @@ -0,0 +1,31 @@ +export default { + "merchant.store.page.title": "商户管理", + "merchant.store.page.description": "管理校园商户/店铺,支持审核、启用、停用等操作", + "merchant.store.id": "ID", + "merchant.store.user_id": "所属用户", + "merchant.store.user_id.required": "所属用户不能为空", + "merchant.store.category_id": "商户分类", + "merchant.store.name": "店铺名称", + "merchant.store.name.required": "店铺名称不能为空", + "merchant.store.logo": "Logo", + "merchant.store.banner": "横幅", + "merchant.store.description": "店铺简介", + "merchant.store.contact_name": "联系人", + "merchant.store.contact_mobile": "联系电话", + "merchant.store.province": "省", + "merchant.store.city": "市", + "merchant.store.district": "区", + "merchant.store.address": "详细地址", + "merchant.store.business_hours": "营业时间", + "merchant.store.balance": "账户余额", + "merchant.store.total_income": "累计收入", + "merchant.store.min_price": "起送价", + "merchant.store.status": "状态", + "merchant.store.status.0": "待审核", + "merchant.store.status.1": "正常", + "merchant.store.status.2": "停用", + "merchant.store.status.3": "已关闭", + "merchant.store.reject_reason": "拒绝原因", + "merchant.store.created_at": "入驻时间", + "merchant.store.updated_at": "更新时间", +}; diff --git a/web/pages/merchant/category/index.tsx b/web/pages/merchant/category/index.tsx new file mode 100644 index 0000000..644bdfb --- /dev/null +++ b/web/pages/merchant/category/index.tsx @@ -0,0 +1,121 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IMerchantCategory from '@/domain/iMerchantCategory'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.category.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.category.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.category.name.required') }], + }, + { + title: t('merchant.category.slug'), + dataIndex: 'slug', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.category.slug.required') }], + }, + { + title: t('merchant.category.description'), + dataIndex: 'description', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.category.sort'), + dataIndex: 'sort', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.category.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.category.status.0') }, + { value: 1, label: t('merchant.category.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('merchant.category.status.1')} + : {t('merchant.category.status.0')}; + }, + filters: [ + { text: t('merchant.category.status.0'), value: 0 }, + { text: t('merchant.category.status.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('merchant.category.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('merchant.category.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/category', + columns, + rowKey: 'id', + accessName: 'merchant.category', + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 600 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.category.page.title')} + {t('merchant.category.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/merchant/print-task/index.tsx b/web/pages/merchant/print-task/index.tsx new file mode 100644 index 0000000..220dab5 --- /dev/null +++ b/web/pages/merchant/print-task/index.tsx @@ -0,0 +1,135 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IPrintTask from '@/domain/iPrintTask'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.print-task.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.print-task.printer_id'), + dataIndex: 'printer_id', + valueType: 'text', + align: 'center', + }, + { + title: t('merchant.print-task.order_id'), + dataIndex: 'order_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.print-task.content'), + dataIndex: 'content', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + }, + { + title: t('merchant.print-task.copies'), + dataIndex: 'copies', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.print-task.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.print-task.status.0') }, + { value: 1, label: t('merchant.print-task.status.1') }, + { value: 2, label: t('merchant.print-task.status.2') }, + { value: 3, label: t('merchant.print-task.status.3') }, + ], + }, + render: (value: number) => { + const colorMap: Record = { 0: 'default', 1: 'processing', 2: 'success', 3: 'error' }; + const labelMap: Record = { + 0: t('merchant.print-task.status.0'), 1: t('merchant.print-task.status.1'), + 2: t('merchant.print-task.status.2'), 3: t('merchant.print-task.status.3'), + }; + return {labelMap[value] || '-'}; + }, + filters: [ + { text: t('merchant.print-task.status.0'), value: 0 }, + { text: t('merchant.print-task.status.1'), value: 1 }, + { text: t('merchant.print-task.status.2'), value: 2 }, + { text: t('merchant.print-task.status.3'), value: 3 }, + ], + align: 'center', + }, + { + title: t('merchant.print-task.error_msg'), + dataIndex: 'error_msg', + valueType: 'text', + hideInSearch: true, + align: 'center', + }, + { + title: t('merchant.print-task.printed_at'), + dataIndex: 'printed_at', + hideInForm: true, + hideInSearch: true, + align: 'center', + render: (value: string) => (value ? dayjs(value).format('YYYY-MM-DD HH:mm:ss') : '-'), + }, + { + title: t('merchant.print-task.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/print-task', + columns, + rowKey: 'id', + accessName: 'merchant.print-task', + createShow: false, + editShow: false, + scroll: { x: 1200 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 600 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.print-task.page.title')} + {t('merchant.print-task.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/merchant/printer/index.tsx b/web/pages/merchant/printer/index.tsx new file mode 100644 index 0000000..eeaf054 --- /dev/null +++ b/web/pages/merchant/printer/index.tsx @@ -0,0 +1,139 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IPrinter from '@/domain/iPrinter'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.printer.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.printer.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.printer.name.required') }], + }, + { + title: t('merchant.printer.merchant_id'), + dataIndex: 'merchant_id', + valueType: 'text', + align: 'center', + required: true, + }, + { + title: t('merchant.printer.brand'), + dataIndex: 'brand', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.printer.model'), + dataIndex: 'model', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.printer.device_no'), + dataIndex: 'device_no', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.printer.secret_key'), + dataIndex: 'secret_key', + valueType: 'password', + hideInTable: true, + hideInSearch: true, + }, + { + title: t('merchant.printer.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.printer.status.0') }, + { value: 1, label: t('merchant.printer.status.1') }, + { value: 2, label: t('merchant.printer.status.2') }, + ], + }, + render: (value: number) => { + const colorMap: Record = { 0: 'default', 1: 'success', 2: 'error' }; + const labelMap: Record = { + 0: t('merchant.printer.status.0'), 1: t('merchant.printer.status.1'), 2: t('merchant.printer.status.2'), + }; + return {labelMap[value] || '-'}; + }, + filters: [ + { text: t('merchant.printer.status.0'), value: 0 }, + { text: t('merchant.printer.status.1'), value: 1 }, + { text: t('merchant.printer.status.2'), value: 2 }, + ], + align: 'center', + }, + { + title: t('merchant.printer.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('merchant.printer.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/printer', + columns, + rowKey: 'id', + accessName: 'merchant.printer', + scroll: { x: 1200 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 700 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.printer.page.title')} + {t('merchant.printer.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/merchant/product-category/index.tsx b/web/pages/merchant/product-category/index.tsx new file mode 100644 index 0000000..e89b909 --- /dev/null +++ b/web/pages/merchant/product-category/index.tsx @@ -0,0 +1,131 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IProductCategory from '@/domain/iProductCategory'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.product-category.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.product-category.merchant_id'), + dataIndex: 'merchant_id', + valueType: 'text', + align: 'center', + required: true, + }, + { + title: t('merchant.product-category.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.product-category.name.required') }], + }, + { + title: t('merchant.product-category.slug'), + dataIndex: 'slug', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.product-category.slug.required') }], + }, + { + title: t('merchant.product-category.parent_id'), + dataIndex: 'parent_id', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product-category.description'), + dataIndex: 'description', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product-category.sort'), + dataIndex: 'sort', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product-category.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.product-category.status.0') }, + { value: 1, label: t('merchant.product-category.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('merchant.product-category.status.1')} + : {t('merchant.product-category.status.0')}; + }, + align: 'center', + }, + { + title: t('merchant.product-category.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('merchant.product-category.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/product-category', + columns, + rowKey: 'id', + accessName: 'merchant.product-category', + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 600 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.product-category.page.title')} + {t('merchant.product-category.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/merchant/product/index.tsx b/web/pages/merchant/product/index.tsx new file mode 100644 index 0000000..5d7a777 --- /dev/null +++ b/web/pages/merchant/product/index.tsx @@ -0,0 +1,180 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IMerchantProduct from '@/domain/iMerchantProduct'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.product.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.product.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.product.name.required') }], + }, + { + title: t('merchant.product.merchant_id'), + dataIndex: 'merchant_id', + valueType: 'text', + align: 'center', + required: true, + }, + { + title: t('merchant.product.category_id'), + dataIndex: 'category_id', + valueType: 'text', + align: 'center', + }, + { + title: t('merchant.product.price'), + dataIndex: 'price', + valueType: 'text', + align: 'center', + render: (value: number) => `¥${value || '0.00'}`, + }, + { + title: t('merchant.product.original_price'), + dataIndex: 'original_price', + valueType: 'text', + align: 'center', + hideInSearch: true, + render: (value: number) => value ? `¥${value}` : '-', + }, + { + title: t('merchant.product.stock'), + dataIndex: 'stock', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product.sales'), + dataIndex: 'sales', + hideInForm: true, + hideInSearch: true, + align: 'center', + }, + { + title: t('merchant.product.unit'), + dataIndex: 'unit', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product.description'), + dataIndex: 'description', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + }, + { + title: t('merchant.product.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.product.status.0') }, + { value: 1, label: t('merchant.product.status.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('merchant.product.status.1')} + : {t('merchant.product.status.0')}; + }, + filters: [ + { text: t('merchant.product.status.0'), value: 0 }, + { text: t('merchant.product.status.1'), value: 1 }, + ], + align: 'center', + }, + { + title: t('merchant.product.is_recommend'), + dataIndex: 'is_recommend', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.product.is_recommend.0') }, + { value: 1, label: t('merchant.product.is_recommend.1') }, + ], + }, + render: (value: number) => { + return value === 1 + ? {t('merchant.product.is_recommend.1')} + : {t('merchant.product.is_recommend.0')}; + }, + align: 'center', + }, + { + title: t('merchant.product.sort'), + dataIndex: 'sort', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.product.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('merchant.product.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/product', + columns, + rowKey: 'id', + accessName: 'merchant.product', + scroll: { x: 1600 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 800 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.product.page.title')} + {t('merchant.product.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table; diff --git a/web/pages/merchant/store/index.tsx b/web/pages/merchant/store/index.tsx new file mode 100644 index 0000000..79a75a2 --- /dev/null +++ b/web/pages/merchant/store/index.tsx @@ -0,0 +1,165 @@ +import { Tag, Typography } from 'antd'; +import React from 'react'; +import XinTable from '@/components/XinTable'; +import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings'; +import type IMerchantStore from '@/domain/iMerchantStore'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +const { Title, Text } = Typography; +dayjs.extend(relativeTime); + +const Table: React.FC = () => { + const { t } = useTranslation(); + + const columns: XinTableColumn[] = [ + { + title: t('merchant.store.id'), + dataIndex: 'id', + hideInForm: true, + sorter: true, + align: 'center', + width: 80, + }, + { + title: t('merchant.store.name'), + dataIndex: 'name', + valueType: 'text', + align: 'center', + required: true, + rules: [{ required: true, message: t('merchant.store.name.required') }], + }, + { + title: t('merchant.store.user_id'), + dataIndex: 'user_id', + valueType: 'text', + align: 'center', + required: true, + hideInSearch: true, + }, + { + title: t('merchant.store.category_id'), + dataIndex: 'category_id', + valueType: 'text', + align: 'center', + }, + { + title: t('merchant.store.contact_name'), + dataIndex: 'contact_name', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.store.contact_mobile'), + dataIndex: 'contact_mobile', + valueType: 'text', + align: 'center', + hideInSearch: true, + }, + { + title: t('merchant.store.address'), + dataIndex: 'address', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + }, + { + title: t('merchant.store.description'), + dataIndex: 'description', + valueType: 'text', + hideInTable: true, + hideInSearch: true, + }, + { + title: t('merchant.store.balance'), + dataIndex: 'balance', + hideInForm: true, + hideInSearch: true, + align: 'center', + render: (value: number) => `¥${value || '0.00'}`, + }, + { + title: t('merchant.store.min_price'), + dataIndex: 'min_price', + valueType: 'text', + align: 'center', + hideInSearch: true, + render: (value: number) => `¥${value || '0.00'}`, + }, + { + title: t('merchant.store.status'), + dataIndex: 'status', + valueType: 'radioButton', + fieldProps: { + options: [ + { value: 0, label: t('merchant.store.status.0') }, + { value: 1, label: t('merchant.store.status.1') }, + { value: 2, label: t('merchant.store.status.2') }, + { value: 3, label: t('merchant.store.status.3') }, + ], + }, + render: (value: number) => { + const colorMap: Record = { 0: 'processing', 1: 'success', 2: 'warning', 3: 'error' }; + const labelMap: Record = { + 0: t('merchant.store.status.0'), 1: t('merchant.store.status.1'), + 2: t('merchant.store.status.2'), 3: t('merchant.store.status.3'), + }; + return {labelMap[value] || '-'}; + }, + filters: [ + { text: t('merchant.store.status.0'), value: 0 }, + { text: t('merchant.store.status.1'), value: 1 }, + { text: t('merchant.store.status.2'), value: 2 }, + { text: t('merchant.store.status.3'), value: 3 }, + ], + align: 'center', + }, + { + title: t('merchant.store.created_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'created_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + { + title: t('merchant.store.updated_at'), + hideInForm: true, + hideInSearch: true, + dataIndex: 'updated_at', + align: 'center', + render: (value: string) => (value ? dayjs(value).fromNow() : '-'), + }, + ]; + + const tableProps: XinTableProps = { + api: '/merchant/store', + columns, + rowKey: 'id', + accessName: 'merchant.store', + scroll: { x: 1400 }, + formProps: { + grid: true, + colProps: { span: 12 }, + rowProps: { gutter: [30, 0] }, + layout: 'vertical', + }, + modalProps: { width: 800 }, + cardProps: { variant: 'borderless' }, + pagination: { size: 'small', style: { marginBottom: 0 } }, + }; + + return ( + <> +
+ {t('merchant.store.page.title')} + {t('merchant.store.page.description')} +
+ {...tableProps} /> + + ); +}; + +export default Table;