feat: add article and production modules

This commit is contained in:
2026-08-31 13:16:46 +08:00
parent ac3da246b2
commit 5c586a1a95
334 changed files with 34419 additions and 1 deletions
+2
View File
@@ -21,6 +21,8 @@ public/*
plugin/*
!plugin/example/
!plugin/demo/
!plugin/produce/
!plugin/article/
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace plugin\article;
use core\plugin\PluginInstall as BasePluginInstall;
/**
* 插件安装类
* 继承 PluginInstall 基类,自动处理:
* - 数据库迁移 (resource/database/migrations/)
* - 种子数据 (resource/database/seeds/)
*
* 菜单需要按需导入或卸载;模板在安装时导入,物理删除由删除插件阶段统一处理:
* - $this->importPluginMenus() - 导入菜单
* - $this->importTemplates() - 复制模板
* - $this->uninstallPluginMenus() - 卸载菜单
*/
class Install extends BasePluginInstall
{
/**
* 安装前回调
*/
protected function beforeInstall(string $version): void
{
$this->output(" 🔄 Running beforeInstall...\n");
}
/**
* 安装后回调 - 按需导入菜单和模板
*/
protected function afterInstall(string $version): void
{
$this->output(" 🔄 Running afterInstall...\n");
// 导入菜单
$this->importPluginMenus();
// 复制模板资源
$this->importTemplates();
}
/**
* 卸载前回调
*/
protected function beforeUninstall(string $version): void
{
$this->output(" 🔄 Running beforeUninstall...\n");
}
/**
* 卸载后回调 - 清理菜单
*/
protected function afterUninstall(string $version): void
{
$this->output(" 🔄 Running afterUninstall...\n");
// 卸载菜单
$this->uninstallPluginMenus();
}
}
@@ -0,0 +1,231 @@
<?php
/**
* Article 控制器
*/
namespace plugin\article\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\CurrentUser;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\model\system\Admin;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\article\app\model\Content;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ArticleController
{
protected CurrentUser $currentUser;
public function __construct(CurrentUser $currentUser)
{
$this->currentUser = $currentUser;
}
#[OA\Get(
path: '/article/legacy/index',
summary: '旧版内容列表示例',
tags: ['article-legacy']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'article:legacy:index', description: '旧版内容列表示例')]
public function index(Request $request): Response
{
return Json::success('获取成功', [
[
'title' => '欢迎使用文章管理插件',
'category' => '默认分类',
'tags' => '插件, 演示',
'status' => '已发布',
'updated_at' => '2026-07-25 17:50:00',
],
]);
}
#[OA\Get(
path: '/article/legacy/category',
summary: '旧版内容分类列表示例',
tags: ['article-legacy']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'article:legacy:category', description: '旧版内容分类列表示例')]
public function category(Request $request): Response
{
return Json::success('获取成功', [
[
'name' => '默认分类',
'slug' => 'default-category',
'article_count' => 1,
'sort' => 100,
'updated_at' => '2026-07-25 17:50:00',
],
]);
}
#[OA\Get(
path: '/article/legacy/tag',
summary: '旧版标签列表示例',
tags: ['article-legacy']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'article:legacy:tag', description: '旧版标签列表示例')]
public function tag(Request $request): Response
{
return Json::success('获取成功', [
[
'name' => '插件演示',
'slug' => 'plugin-demo',
'article_count' => 1,
'heat' => 88,
'updated_at' => '2026-07-25 17:50:00',
],
]);
}
#[OA\Get(
path: '/article/recycle',
summary: '内容回收站列表',
tags: ['article-legacy']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'article:recycle:index', description: '内容回收站权限')]
public function recycle(Request $request): Response
{
$keyword = trim((string) $request->input('LIKE_title', ''));
$items = Content::query()
->onlyTrashed()
->with(['category'])
->when($keyword !== '', function ($query) use ($keyword) {
$query->where('title', 'like', "%{$keyword}%");
})
->orderByDesc('deleted_at')
->get();
$operatorIds = $items->pluck('updated_by')
->filter(static fn($id) => !empty($id))
->unique()
->values()
->all();
$operators = empty($operatorIds)
? collect()
: Admin::query()->whereIn('id', $operatorIds)->get(['id', 'real_name']);
$operatorMap = $operators->pluck('real_name', 'id');
$rows = $items->map(function (Content $item) use ($operatorMap) {
$deletedAt = $item->deleted_at;
return [
'id' => $item->id,
'title' => $item->title,
'category' => $item->category?->name ?? '',
'deleted_by' => $operatorMap[$item->updated_by] ?? '',
'deleted_at' => $deletedAt ? date('Y-m-d H:i:s', (int) $deletedAt) : '',
'expire_at' => $deletedAt ? date('Y-m-d H:i:s', (int) $deletedAt + 30 * 24 * 60 * 60) : '',
];
})->values()->all();
return Json::success('获取成功', $rows);
}
#[OA\Put(
path: '/article/recycle/{id}/restore',
summary: '恢复内容',
tags: ['article-legacy']
)]
#[SimpleResponse(description: '恢复成功')]
#[Permission(code: 'article:recycle:restore', description: '恢复回收站内容')]
public function restore(Request $request): Response
{
try {
$id = $request->route?->param('id') ?: $request->input('id');
$item = Content::withTrashed()->find($id);
if (!$item) {
return Json::fail('内容不存在');
}
$item->restore();
return Json::success('恢复成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(
path: '/article/recycle/{id}',
summary: '彻底删除内容',
tags: ['article-legacy']
)]
#[SimpleResponse(description: '删除成功')]
#[Permission(code: 'article:recycle:delete', description: '彻底删除回收站内容')]
public function destroyRecycle(Request $request): Response
{
try {
$id = $request->route?->param('id') ?: $request->input('id');
$item = Content::withTrashed()->find($id);
if (!$item) {
return Json::fail('内容不存在');
}
$item->forceDelete();
return Json::success('删除成功');
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Post(
path: '/article/legacy/create',
summary: '旧版创建示例',
tags: ['article-legacy']
)]
#[SimpleResponse(description: '创建成功')]
#[Permission(code: 'article:legacy:create', description: '旧版创建示例')]
public function create(Request $request): Response
{
return Json::success('创建成功');
}
#[OA\Get(
path: '/article/legacy/info',
summary: '旧版详情示例',
tags: ['article-legacy']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'article:legacy:info', description: '旧版详情示例')]
public function info(Request $request): Response
{
return Json::success('获取成功', []);
}
#[OA\Post(
path: '/article/legacy/update',
summary: '旧版更新示例',
tags: ['article-legacy']
)]
#[SimpleResponse(description: '更新成功')]
#[Permission(code: 'article:legacy:update', description: '旧版更新示例')]
public function update(Request $request): Response
{
return Json::success('更新成功');
}
#[OA\Post(
path: '/article/legacy/delete',
summary: '旧版删除示例',
tags: ['article-legacy']
)]
#[SimpleResponse(description: '删除成功')]
#[Permission(code: 'article:legacy:delete', description: '旧版删除示例')]
public function delete(Request $request): Response
{
return Json::success('删除成功');
}
}
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\article\app\service\admin\CategoryService;
use plugin\article\app\validate\CategoryValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class CategoryController extends Crud
{
public function __construct(CategoryService $service, CategoryValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/article/category',
summary: '内容分类列表',
tags: ['article-category']
)]
#[Permission(code: 'article:category:list', description: '内容分类列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/article/category/{id}',
summary: '内容分类详情',
tags: ['article-category'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:category:read', description: '内容分类详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/article/category',
summary: '创建内容分类',
tags: ['article-category']
)]
#[Permission(code: 'article:category:create', description: '创建内容分类')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/article/category/{id}',
summary: '更新内容分类',
tags: ['article-category'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:category:update', description: '更新内容分类')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/article/category/export',
summary: '导出内容分类',
tags: ['article-category']
)]
#[Permission(code: 'article:category:list', description: '导出内容分类')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/article/category/{id}',
summary: '删除内容分类',
tags: ['article-category'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:category:delete', description: '删除内容分类')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/article/category',
summary: '批量删除内容分类',
tags: ['article-category'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'article:category:delete', description: '批量删除内容分类')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\CurrentUser;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\article\app\model\Content;
use plugin\article\app\service\admin\ContentService;
use plugin\article\app\validate\ContentValidate;
use support\annotation\Middleware;
use support\Container;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ContentController extends Crud
{
protected CurrentUser $currentUser;
public function __construct(ContentService $service, ContentValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
$this->currentUser = Container::make(CurrentUser::class);
}
#[OA\Get(
path: '/article/index',
summary: '内容中心列表',
tags: ['article-content']
)]
#[Permission(code: 'article:list', description: '内容中心列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/article/index/{id}',
summary: '内容中心详情',
tags: ['article-content'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:read', description: '内容中心详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/article/index',
summary: '创建内容',
tags: ['article-content']
)]
#[Permission(code: 'article:create', description: '创建内容')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/article/index/{id}',
summary: '更新内容',
tags: ['article-content'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:update', description: '更新内容')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/article/index/export',
summary: '导出内容',
tags: ['article-content']
)]
#[Permission(code: 'article:list', description: '导出内容')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
protected function selectInput(Request $request, ?array $config = null): array
{
$result = parent::selectInput($request, $config);
if (empty($result[4])) {
$result[4] = 'published_at desc,id desc';
}
return $result;
}
protected function insertInput(Request $request): array
{
return $this->inputFilter($request->all(), ['tag_ids']);
}
#[OA\Delete(
path: '/article/index/{id}',
summary: '删除内容',
tags: ['article-content'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:delete', description: '删除内容')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return $this->deleteContents($request);
}
#[OA\Delete(
path: '/article/index',
summary: '批量删除内容',
tags: ['article-content'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'article:delete', description: '批量删除内容')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return $this->deleteContents($request);
}
protected function deleteContents(Request $request): \support\Response
{
try {
$ids = $this->getDeleteIds($request);
if (empty($ids)) {
throw new \Exception('删除参数不能为空');
}
$result = $this->service->transaction(function () use ($ids) {
$data = is_array($ids) ? $ids : explode(',', $ids);
$deletedIds = [];
$currentUserId = $this->currentUser->id();
foreach ($data as $id) {
$item = Content::query()->find($id);
if (!$item) {
continue;
}
if ($currentUserId && $item->isFillable('updated_by')) {
$item->setAttribute('updated_by', $currentUserId);
$item->save();
}
$item->delete();
$deletedIds[] = $item->id;
}
return $deletedIds;
});
return Json::success('ok', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
}
@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\article\app\service\admin\TagService;
use plugin\article\app\validate\TagValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class TagController extends Crud
{
public function __construct(TagService $service, TagValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(
path: '/article/tag',
summary: '标签管理列表',
tags: ['article-tag']
)]
#[Permission(code: 'article:tag:list', description: '标签管理列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/article/tag/{id}',
summary: '标签详情',
tags: ['article-tag'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:tag:read', description: '标签详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(
path: '/article/tag',
summary: '创建标签',
tags: ['article-tag']
)]
#[Permission(code: 'article:tag:create', description: '创建标签')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/article/tag/{id}',
summary: '更新标签',
tags: ['article-tag'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:tag:update', description: '更新标签')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(
path: '/article/tag/export',
summary: '导出标签',
tags: ['article-tag']
)]
#[Permission(code: 'article:tag:list', description: '导出标签')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/article/tag/{id}',
summary: '删除标签',
tags: ['article-tag'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'article:tag:delete', description: '删除标签')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/article/tag',
summary: '批量删除标签',
tags: ['article-tag'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'article:tag:delete', description: '批量删除标签')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\dao;
use core\base\BaseDao;
use plugin\article\app\model\Category;
class CategoryDao extends BaseDao
{
protected function setModel(): string
{
return Category::class;
}
protected function getKeywordFields(): array
{
return ['name', 'slug', 'description'];
}
public function selectModel(
array $where,
array|string $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Pagination\LengthAwarePaginator|null {
$query = parent::selectModel($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
if ($query instanceof \Illuminate\Database\Eloquent\Builder) {
$query->withCount(['contents as article_count']);
}
return $query;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\dao;
use core\base\BaseDao;
use plugin\article\app\model\Content;
class ContentDao extends BaseDao
{
protected function setModel(): string
{
return Content::class;
}
protected function getKeywordFields(): array
{
return ['title', 'slug', 'summary', 'content'];
}
public function get($id, ?array $field = null, ?array $with = ['category', 'tags', 'createds', 'updateds'], string $order = '', ?array $withoutScopes = null): ?\Illuminate\Database\Eloquent\Model
{
return parent::get($id, $field, $with, $order, $withoutScopes);
}
}
+40
View File
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\dao;
use core\base\BaseDao;
use plugin\article\app\model\Tag;
class TagDao extends BaseDao
{
protected function setModel(): string
{
return Tag::class;
}
protected function getKeywordFields(): array
{
return ['name', 'slug', 'description'];
}
public function selectModel(
array $where,
array|string $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder|\Illuminate\Pagination\LengthAwarePaginator|null {
$query = parent::selectModel($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
if ($query instanceof \Illuminate\Database\Eloquent\Builder) {
$query->withCount(['contents as article_count']);
}
return $query;
}
}
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\model;
use app\model\system\Admin;
use core\base\BaseModel as CoreBaseModel;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\SoftDeletes;
abstract class BaseModel extends CoreBaseModel
{
use SoftDeletes;
protected $appends = ['created_date', 'updated_date', 'created_name', 'updated_name'];
public function createds(): HasOne
{
return $this->hasOne(Admin::class, 'id', 'created_by')->select('id', 'real_name as created_name');
}
public function updateds(): HasOne
{
return $this->hasOne(Admin::class, 'id', 'updated_by')->select('id', 'real_name as updated_name');
}
public function getCreatedNameAttribute(): string
{
return $this->createds?->created_name ?? '';
}
public function getUpdatedNameAttribute(): string
{
return $this->updateds?->updated_name ?? '';
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Category extends BaseModel
{
protected $table = 'article_category';
protected $fillable = [
'id',
'name',
'slug',
'parent_id',
'sort',
'enabled',
'description',
'created_by',
'updated_by',
'created_at',
'updated_at',
'deleted_at',
];
protected $casts = [
'parent_id' => 'integer',
'sort' => 'integer',
'enabled' => 'integer',
'created_at' => 'integer',
'updated_at' => 'integer',
'deleted_at' => 'integer',
];
public function contents(): HasMany
{
return $this->hasMany(Content::class, 'category_id', 'id');
}
}
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Content extends BaseModel
{
protected $table = 'article_content';
protected $fillable = [
'id',
'title',
'content_type',
'slug',
'category_id',
'channel',
'visibility',
'author',
'source',
'cover',
'seo_keywords',
'seo_title',
'seo_description',
'summary',
'content',
'status',
'is_top',
'is_recommend',
'allow_comment',
'sort',
'view_count',
'like_count',
'comment_count',
'favorite_count',
'published_at',
'offline_at',
'created_by',
'updated_by',
'created_at',
'updated_at',
'deleted_at',
];
protected $casts = [
'category_id' => 'integer',
'visibility' => 'integer',
'status' => 'integer',
'is_top' => 'integer',
'is_recommend' => 'integer',
'allow_comment' => 'integer',
'sort' => 'integer',
'view_count' => 'integer',
'like_count' => 'integer',
'comment_count' => 'integer',
'favorite_count' => 'integer',
'published_at' => 'integer',
'offline_at' => 'integer',
'created_at' => 'integer',
'updated_at' => 'integer',
'deleted_at' => 'integer',
];
protected $appends = ['tag_names', 'tag_ids', 'category_name', 'created_date', 'updated_date', 'created_name', 'updated_name'];
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'category_id', 'id');
}
public function tags(): BelongsToMany
{
return $this->belongsToMany(Tag::class, 'article_content_tag', 'content_id', 'tag_id');
}
public function getCategoryNameAttribute(): string
{
return $this->category?->name ?? '';
}
public function getTagNamesAttribute(): string
{
return $this->tags->pluck('name')->implode('、');
}
public function getTagIdsAttribute(): array
{
return $this->tags->pluck('id')->map(static fn($id) => is_numeric($id) ? (int)$id : $id)->values()->all();
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Tag extends BaseModel
{
protected $table = 'article_tag';
protected $fillable = [
'id',
'name',
'slug',
'sort',
'heat',
'enabled',
'description',
'created_by',
'updated_by',
'created_at',
'updated_at',
'deleted_at',
];
protected $casts = [
'sort' => 'integer',
'heat' => 'integer',
'enabled' => 'integer',
'created_at' => 'integer',
'updated_at' => 'integer',
'deleted_at' => 'integer',
];
public function contents(): BelongsToMany
{
return $this->belongsToMany(Content::class, 'article_content_tag', 'tag_id', 'content_id');
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\service\admin;
use core\base\BaseService;
use plugin\article\app\dao\CategoryDao;
class CategoryService extends BaseService
{
public function __construct(CategoryDao $dao)
{
$this->dao = $dao;
}
}
@@ -0,0 +1,129 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\service\admin;
use core\exception\handler\AdminException;
use madong\helper\PropertyCopier;
use core\base\BaseService;
use core\uuid\Snowflake;
use Illuminate\Support\Str;
use plugin\article\app\dao\ContentDao;
use plugin\article\app\model\Content;
use support\Db;
class ContentService extends BaseService
{
public function __construct(ContentDao $dao)
{
$this->dao = $dao;
}
public function save(array $data): ?\plugin\article\app\model\Content
{
try {
return $this->transaction(function () use ($data) {
$payload = $this->normalizePayload($data);
$tagIds = $payload['tag_ids'] ?? [];
unset($payload['tag_ids']);
$model = new Content();
PropertyCopier::copyProperties((object) $payload, $model);
$model->save();
$this->syncTags($model, $tagIds);
return $model->refresh()?->load(['category', 'tags', 'createds', 'updateds']);
});
} catch (\Throwable $e) {
throw new AdminException($e->getMessage());
}
}
public function update($id, $data): void
{
try {
$this->transaction(function () use ($id, $data) {
$model = $this->dao->get($id);
if (!$model) {
throw new AdminException('内容不存在');
}
$payload = $this->normalizePayload($data, false);
$tagIds = $payload['tag_ids'] ?? null;
unset($payload['tag_ids']);
PropertyCopier::copyProperties((object) $payload, $model);
$model->save();
if ($tagIds !== null) {
$this->syncTags($model, $tagIds);
}
});
} catch (\Throwable $e) {
throw new AdminException($e->getMessage());
}
}
protected function normalizePayload(array $data, bool $isCreate = true): array
{
$payload = $data;
$payload['content_type'] = $payload['content_type'] ?? 'article';
$payload['channel'] = $payload['channel'] ?? 'official';
$payload['visibility'] = isset($payload['visibility']) ? (int) $payload['visibility'] : 1;
$payload['source'] = $payload['source'] ?? 'original';
$payload['status'] = isset($payload['status']) ? (int) $payload['status'] : 0;
$payload['is_top'] = isset($payload['is_top']) ? (int) $payload['is_top'] : 0;
$payload['is_recommend'] = isset($payload['is_recommend']) ? (int) $payload['is_recommend'] : 0;
$payload['allow_comment'] = isset($payload['allow_comment']) ? (int) $payload['allow_comment'] : 1;
$payload['sort'] = isset($payload['sort']) ? (int) $payload['sort'] : 100;
$payload['view_count'] = isset($payload['view_count']) ? (int) $payload['view_count'] : 0;
$payload['like_count'] = isset($payload['like_count']) ? (int) $payload['like_count'] : 0;
$payload['comment_count'] = isset($payload['comment_count']) ? (int) $payload['comment_count'] : 0;
$payload['favorite_count'] = isset($payload['favorite_count']) ? (int) $payload['favorite_count'] : 0;
$payload['category_id'] = isset($payload['category_id']) ? (int) $payload['category_id'] : 0;
if (!empty($payload['slug'])) {
$payload['slug'] = trim((string) $payload['slug']);
} elseif ($isCreate && !empty($payload['title'])) {
$payload['slug'] = Str::slug((string) $payload['title']);
}
if (array_key_exists('tag_ids', $payload)) {
$payload['tag_ids'] = collect($payload['tag_ids'] ?? [])
->filter(static fn($id) => $id !== null && $id !== '' && $id !== 0 && $id !== '0')
->map(static fn($id) => (int) $id)
->unique()
->values()
->all();
}
return $payload;
}
protected function syncTags(Content $model, array $tagIds): void
{
$tableName = $model->tags()->getTable();
Db::table($tableName)->where('content_id', $model->id)->delete();
if (empty($tagIds)) {
return;
}
$now = time();
$rows = [];
foreach ($tagIds as $tagId) {
$rows[] = [
'id' => (string) Snowflake::generate(),
'content_id' => $model->id,
'tag_id' => (int) $tagId,
'created_at' => $now,
];
}
Db::table($tableName)->insert($rows);
}
}
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\service\admin;
use core\base\BaseService;
use plugin\article\app\dao\TagDao;
class TagService extends BaseService
{
public function __construct(TagDao $dao)
{
$this->dao = $dao;
}
}
@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\validate;
use core\base\BaseValidate;
use Illuminate\Validation\Rule;
use plugin\article\app\model\Category;
class CategoryValidate extends BaseValidate
{
public function rules(): array
{
$id = request()->route?->param('id');
return [
'name' => ['required', 'max:100', Rule::unique(Category::class, 'name')->ignore($id, 'id')],
'slug' => ['nullable', 'max:100', Rule::unique(Category::class, 'slug')->ignore($id, 'id')],
'parent_id' => ['nullable', 'integer'],
'sort' => ['nullable', 'integer', 'min:0'],
'enabled' => ['nullable', 'integer', 'in:0,1'],
'description' => ['nullable', 'max:65535'],
];
}
protected array $message = [
'name.required' => '分类名称不能为空',
'name.max' => '分类名称不能超过100个字符',
'name.unique' => '分类名称已存在',
'slug.max' => '分类标识不能超过100个字符',
'slug.unique' => '分类标识已存在',
'parent_id.integer' => '父级分类格式不正确',
'sort.integer' => '排序必须为整数',
'sort.min' => '排序不能小于0',
'enabled.integer' => '状态格式不正确',
'enabled.in' => '状态值不正确',
'description.max' => '分类描述内容过长',
];
protected array $scene = [
'store' => ['name', 'slug', 'parent_id', 'sort', 'enabled', 'description'],
'update' => ['name', 'slug', 'parent_id', 'sort', 'enabled', 'description'],
];
}
@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\validate;
use core\base\BaseValidate;
use Illuminate\Validation\Rule;
use plugin\article\app\model\Content;
class ContentValidate extends BaseValidate
{
public function rules(): array
{
$id = request()->route?->param('id');
return [
'title' => ['required', 'max:200', Rule::unique(Content::class, 'title')->ignore($id, 'id')],
'content_type' => ['nullable', 'max:50'],
'slug' => ['nullable', 'max:200', Rule::unique(Content::class, 'slug')->ignore($id, 'id')],
'category_id' => ['required', 'integer'],
'channel' => ['nullable', 'max:100'],
'visibility' => ['nullable', 'integer', 'in:1,2,3'],
'author' => ['nullable', 'max:100'],
'source' => ['nullable', 'max:100'],
'cover' => ['nullable', 'max:255'],
'seo_keywords' => ['nullable', 'max:255'],
'seo_title' => ['nullable', 'max:200'],
'seo_description' => ['nullable', 'max:500'],
'summary' => ['nullable', 'max:500'],
'content' => ['nullable'],
'status' => ['nullable', 'integer', 'in:0,1,2'],
'is_top' => ['nullable', 'integer', 'in:0,1'],
'is_recommend' => ['nullable', 'integer', 'in:0,1'],
'allow_comment' => ['nullable', 'integer', 'in:0,1'],
'sort' => ['nullable', 'integer', 'min:0'],
'view_count' => ['nullable', 'integer', 'min:0'],
'like_count' => ['nullable', 'integer', 'min:0'],
'comment_count' => ['nullable', 'integer', 'min:0'],
'favorite_count' => ['nullable', 'integer', 'min:0'],
'published_at' => ['nullable', 'integer'],
'offline_at' => ['nullable', 'integer'],
'tag_ids' => ['nullable', 'array'],
];
}
protected array $message = [
'title.required' => '文章标题不能为空',
'title.max' => '文章标题不能超过200个字符',
'title.unique' => '文章标题已存在',
'content_type.max' => '内容类型不能超过50个字符',
'slug.max' => '文章标识不能超过200个字符',
'slug.unique' => '文章标识已存在',
'category_id.required' => '请选择内容分类',
'category_id.integer' => '内容分类格式不正确',
'channel.max' => '所属频道不能超过100个字符',
'visibility.integer' => '可见范围格式不正确',
'visibility.in' => '可见范围值不正确',
'author.max' => '作者不能超过100个字符',
'source.max' => '内容来源不能超过100个字符',
'cover.max' => '封面地址不能超过255个字符',
'seo_keywords.max' => 'SEO关键词不能超过255个字符',
'seo_title.max' => 'SEO标题不能超过200个字符',
'seo_description.max' => 'SEO描述不能超过500个字符',
'summary.max' => '摘要不能超过500个字符',
'status.integer' => '状态格式不正确',
'status.in' => '状态值不正确',
'is_top.integer' => '置顶状态格式不正确',
'is_top.in' => '置顶状态值不正确',
'is_recommend.integer' => '推荐状态格式不正确',
'is_recommend.in' => '推荐状态值不正确',
'allow_comment.integer' => '评论设置格式不正确',
'allow_comment.in' => '评论设置值不正确',
'sort.integer' => '排序必须为整数',
'sort.min' => '排序不能小于0',
'view_count.integer' => '浏览量必须为整数',
'view_count.min' => '浏览量不能小于0',
'like_count.integer' => '点赞量必须为整数',
'like_count.min' => '点赞量不能小于0',
'comment_count.integer' => '评论量必须为整数',
'comment_count.min' => '评论量不能小于0',
'favorite_count.integer' => '收藏量必须为整数',
'favorite_count.min' => '收藏量不能小于0',
'published_at.integer' => '发布时间格式不正确',
'offline_at.integer' => '下线时间格式不正确',
'tag_ids.array' => '标签数据格式不正确',
];
protected array $scene = [
'store' => ['title', 'content_type', 'slug', 'category_id', 'channel', 'visibility', 'author', 'source', 'cover', 'seo_keywords', 'seo_title', 'seo_description', 'summary', 'content', 'status', 'is_top', 'is_recommend', 'allow_comment', 'sort', 'view_count', 'like_count', 'comment_count', 'favorite_count', 'published_at', 'offline_at', 'tag_ids'],
'update' => ['title', 'content_type', 'slug', 'category_id', 'channel', 'visibility', 'author', 'source', 'cover', 'seo_keywords', 'seo_title', 'seo_description', 'summary', 'content', 'status', 'is_top', 'is_recommend', 'allow_comment', 'sort', 'view_count', 'like_count', 'comment_count', 'favorite_count', 'published_at', 'offline_at', 'tag_ids'],
];
}
@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace plugin\article\app\validate;
use core\base\BaseValidate;
use Illuminate\Validation\Rule;
use plugin\article\app\model\Tag;
class TagValidate extends BaseValidate
{
public function rules(): array
{
$id = request()->route?->param('id');
return [
'name' => ['required', 'max:100', Rule::unique(Tag::class, 'name')->ignore($id, 'id')],
'slug' => ['nullable', 'max:100', Rule::unique(Tag::class, 'slug')->ignore($id, 'id')],
'sort' => ['nullable', 'integer', 'min:0'],
'heat' => ['nullable', 'integer', 'min:0'],
'enabled' => ['nullable', 'integer', 'in:0,1'],
'description' => ['nullable', 'max:65535'],
];
}
protected array $message = [
'name.required' => '标签名称不能为空',
'name.max' => '标签名称不能超过100个字符',
'name.unique' => '标签名称已存在',
'slug.max' => '标签标识不能超过100个字符',
'slug.unique' => '标签标识已存在',
'sort.integer' => '排序必须为整数',
'sort.min' => '排序不能小于0',
'heat.integer' => '热度必须为整数',
'heat.min' => '热度不能小于0',
'enabled.integer' => '状态格式不正确',
'enabled.in' => '状态值不正确',
'description.max' => '标签描述内容过长',
];
protected array $scene = [
'store' => ['name', 'slug', 'sort', 'heat', 'enabled', 'description'],
'update' => ['name', 'slug', 'sort', 'heat', 'enabled', 'description'],
];
}
+9
View File
@@ -0,0 +1,9 @@
<?php
return [
'enable' => true,
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => false,
'version' => '1.0.0',
];
+20
View File
@@ -0,0 +1,20 @@
<?php
/**
* 插件信息配置
*/
return [
'name' => 'article',
'identifier' => 'article',
'type' => 'madong:plugin',
'version' => '1.0.0',
'description' => '',
'author' => 'mrlee',
'author_email' => '',
'website' => '',
'uninstall' => [
'drop_tables' => false,
'remove_dependencies' => false,
],
];
@@ -0,0 +1,4 @@
<?php return array (
'version' => '1.0.0',
'installed_at' => '2026-07-26 13:45:43',
);
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* article 插件路由配置
* 使用 Swagger 注解自动生成路由
*/
use Webman\Route;
use WebmanTech\Swagger\Swagger;
use OpenApi\Annotations as OA;
/**
* 注册 API 路由
*/
Route::group('/api', function () {
Swagger::create()->registerRoute([
'route_prefix' => '/article/openapi',
'register_route' => true,
'openapi_doc' => [
'scan_path' => [
base_path('plugin/article/app/api'),
],
'modify' => function (OA\OpenApi $openapi) {
$openapi->info->title = '文章管理 API';
$openapi->info->version = '1.0.0';
$openapi->servers = [
new OA\Server(
[
'url' => '/api',
'description' => request()->host(),
]
),
];
/** @phpstan-ignore-next-line */
if (!$openapi->components instanceof OA\Components) {
$openapi->components = new OA\Components([]);
}
$openapi->components->securitySchemes = [
new OA\SecurityScheme([
'securityScheme' => 'api_key',
'type' => 'apiKey',
'name' => config('core.jwt.app.token_name', 'Authorization'),
'in' => 'header',
]),
];
},
],
]);
});
/**
* 注册 AdminAPI 路由
*/
Route::group('/adminapi', function () {
Swagger::create()->registerRoute([
'route_prefix' => '/article/openapi',
'register_route' => true,
'openapi_doc' => [
'scan_path' => [
base_path('plugin/article/app/adminapi'),
],
'modify' => function (OA\OpenApi $openapi) {
$openapi->info->title = '文章管理 API';
$openapi->info->version = '1.0.0';
$openapi->servers = [
new OA\Server(
[
'url' => '/adminapi',
'description' => request()->host(),
]
),
];
/** @phpstan-ignore-next-line */
if (!$openapi->components instanceof OA\Components) {
$openapi->components = new OA\Components([]);
}
$openapi->components->securitySchemes = [
new OA\SecurityScheme([
'securityScheme' => 'api_key',
'type' => 'apiKey',
'name' => config('core.jwt.app.token_name', 'Authorization'),
'in' => 'header',
]),
];
},
],
]);
});
@@ -0,0 +1,7 @@
<?php
return [
'locale' => config('app.lang'),
'fallback_locale' => ['zh_CN', 'en'],
'path' => base_path() . '/plugin/article/resource/translations',
];
Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 KiB

@@ -0,0 +1,94 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return new class {
public function up(Builder $schema): void
{
if (!$schema->hasTable('article_category')) {
$schema->create('article_category', function (Blueprint $table) {
$table->bigInteger('id')->primary()->comment('主键');
$table->string('name', 100)->comment('分类名称');
$table->string('slug', 100)->nullable()->comment('分类标识');
$table->bigInteger('parent_id')->default(0)->comment('父级分类ID');
$table->integer('sort')->default(100)->comment('排序');
$table->tinyInteger('enabled')->default(1)->comment('状态: 1启用 0禁用');
$table->longText('description')->nullable()->comment('分类描述');
$table->bigInteger('created_by')->nullable()->comment('创建者');
$table->bigInteger('updated_by')->nullable()->comment('更新者');
$table->bigInteger('created_at')->nullable()->comment('创建时间');
$table->bigInteger('updated_at')->nullable()->comment('更新时间');
$table->bigInteger('deleted_at')->nullable()->comment('删除时间');
$table->index('parent_id');
$table->index('slug');
$table->index('enabled');
});
}
if (!$schema->hasTable('article_tag')) {
$schema->create('article_tag', function (Blueprint $table) {
$table->bigInteger('id')->primary()->comment('主键');
$table->string('name', 100)->comment('标签名称');
$table->string('slug', 100)->nullable()->comment('标签标识');
$table->integer('sort')->default(100)->comment('排序');
$table->integer('heat')->default(0)->comment('热度');
$table->tinyInteger('enabled')->default(1)->comment('状态: 1启用 0禁用');
$table->longText('description')->nullable()->comment('标签描述');
$table->bigInteger('created_by')->nullable()->comment('创建者');
$table->bigInteger('updated_by')->nullable()->comment('更新者');
$table->bigInteger('created_at')->nullable()->comment('创建时间');
$table->bigInteger('updated_at')->nullable()->comment('更新时间');
$table->bigInteger('deleted_at')->nullable()->comment('删除时间');
$table->index('slug');
$table->index('enabled');
$table->index('heat');
});
}
if (!$schema->hasTable('article_content')) {
$schema->create('article_content', function (Blueprint $table) {
$table->bigInteger('id')->primary()->comment('主键');
$table->string('title', 200)->comment('文章标题');
$table->string('slug', 200)->nullable()->comment('文章标识');
$table->bigInteger('category_id')->default(0)->comment('分类ID');
$table->string('cover', 255)->nullable()->comment('封面图');
$table->string('summary', 500)->nullable()->comment('摘要');
$table->longText('content')->nullable()->comment('文章内容');
$table->tinyInteger('status')->default(1)->comment('状态: 0草稿 1发布 2下架');
$table->tinyInteger('is_top')->default(0)->comment('是否置顶');
$table->integer('sort')->default(100)->comment('排序');
$table->integer('view_count')->default(0)->comment('浏览量');
$table->bigInteger('published_at')->nullable()->comment('发布时间');
$table->bigInteger('created_by')->nullable()->comment('创建者');
$table->bigInteger('updated_by')->nullable()->comment('更新者');
$table->bigInteger('created_at')->nullable()->comment('创建时间');
$table->bigInteger('updated_at')->nullable()->comment('更新时间');
$table->bigInteger('deleted_at')->nullable()->comment('删除时间');
$table->index('category_id');
$table->index('status');
$table->index('is_top');
$table->index('published_at');
});
}
if (!$schema->hasTable('article_content_tag')) {
$schema->create('article_content_tag', function (Blueprint $table) {
$table->bigInteger('id')->primary()->comment('主键');
$table->bigInteger('content_id')->comment('文章ID');
$table->bigInteger('tag_id')->comment('标签ID');
$table->bigInteger('created_at')->nullable()->comment('创建时间');
$table->index('content_id');
$table->index('tag_id');
});
}
}
public function down(Builder $schema): void
{
$schema->dropIfExists('article_content_tag');
$schema->dropIfExists('article_content');
$schema->dropIfExists('article_tag');
$schema->dropIfExists('article_category');
}
};
@@ -0,0 +1,93 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;
return new class {
public function up(Builder $schema): void
{
if (!$schema->hasTable('article_content')) {
return;
}
$schema->table('article_content', function (Blueprint $table) use ($schema) {
if (!$schema->hasColumn('article_content', 'content_type')) {
$table->string('content_type', 50)->default('article')->comment('内容类型')->after('title');
}
if (!$schema->hasColumn('article_content', 'channel')) {
$table->string('channel', 100)->default('official')->comment('所属频道')->after('category_id');
}
if (!$schema->hasColumn('article_content', 'visibility')) {
$table->tinyInteger('visibility')->default(1)->comment('可见范围: 1公开 2登录可见 3私密')->after('channel');
}
if (!$schema->hasColumn('article_content', 'author')) {
$table->string('author', 100)->nullable()->comment('作者')->after('visibility');
}
if (!$schema->hasColumn('article_content', 'source')) {
$table->string('source', 100)->default('original')->comment('内容来源')->after('author');
}
if (!$schema->hasColumn('article_content', 'offline_at')) {
$table->bigInteger('offline_at')->nullable()->comment('下线时间')->after('published_at');
}
if (!$schema->hasColumn('article_content', 'seo_keywords')) {
$table->string('seo_keywords', 255)->nullable()->comment('SEO关键词')->after('source');
}
if (!$schema->hasColumn('article_content', 'seo_title')) {
$table->string('seo_title', 200)->nullable()->comment('SEO标题')->after('seo_keywords');
}
if (!$schema->hasColumn('article_content', 'seo_description')) {
$table->string('seo_description', 500)->nullable()->comment('SEO描述')->after('seo_title');
}
if (!$schema->hasColumn('article_content', 'like_count')) {
$table->integer('like_count')->default(0)->comment('点赞量')->after('view_count');
}
if (!$schema->hasColumn('article_content', 'comment_count')) {
$table->integer('comment_count')->default(0)->comment('评论量')->after('like_count');
}
if (!$schema->hasColumn('article_content', 'favorite_count')) {
$table->integer('favorite_count')->default(0)->comment('收藏量')->after('comment_count');
}
if (!$schema->hasColumn('article_content', 'is_recommend')) {
$table->tinyInteger('is_recommend')->default(0)->comment('是否推荐')->after('is_top');
}
if (!$schema->hasColumn('article_content', 'allow_comment')) {
$table->tinyInteger('allow_comment')->default(1)->comment('是否允许评论')->after('is_recommend');
}
});
}
public function down(Builder $schema): void
{
if (!$schema->hasTable('article_content')) {
return;
}
$dropColumns = [];
foreach ([
'content_type',
'channel',
'visibility',
'author',
'source',
'offline_at',
'seo_keywords',
'seo_title',
'seo_description',
'like_count',
'comment_count',
'favorite_count',
'is_recommend',
'allow_comment',
] as $column) {
if ($schema->hasColumn('article_content', $column)) {
$dropColumns[] = $column;
}
}
if (!empty($dropColumns)) {
$schema->table('article_content', function (Blueprint $table) use ($dropColumns) {
$table->dropColumn($dropColumns);
});
}
}
};
@@ -0,0 +1,46 @@
<?php
/**
* 后台菜单配置
* 安装时会自动加载
*/
return [
[
'name' => '文章管理',
'code' => 'article',
'app' => 'article',
'path' => '/article',
'icon' => 'plugin',
'children' => [
[
'name' => '内容中心',
'code' => 'article:index',
'app' => 'article',
'path' => '/article/index',
'component' => '/article/content/index',
],
[
'name' => '内容分类',
'code' => 'article:category:index',
'app' => 'article',
'path' => '/article/category',
'component' => '/article/category/index',
],
[
'name' => '标签管理',
'code' => 'article:tag:index',
'app' => 'article',
'path' => '/article/tag',
'component' => '/article/tag/index',
],
[
'name' => '内容回收站',
'code' => 'article:recycle:index',
'app' => 'article',
'path' => '/article/recycle',
'component' => '/article/recycle/index',
],
],
],
];
@@ -0,0 +1,9 @@
<?php
/**
* 前端菜单配置
*/
return [
// 前端菜单配置
];
@@ -0,0 +1,9 @@
<?php
/**
* Web 菜单配置
*/
return [
// Web 菜单配置
];
@@ -0,0 +1,118 @@
import BaseService from '@/api/core/base'
import request from '@/utils/http'
export interface ArticleCategoryRow {
id?: string | number
name: string
slug?: string
parent_id?: string | number
article_count?: number
sort?: number
enabled?: number
description?: string
created_date?: string
updated_date?: string
}
export interface ArticleTagRow {
id?: string | number
name: string
slug?: string
article_count?: number
sort?: number
heat?: number
enabled?: number
description?: string
created_date?: string
updated_date?: string
}
export interface ArticleContentRow {
id?: string | number
title: string
content_type?: string
slug?: string
category_id?: string | number
category_name?: string
channel?: string
visibility?: number
author?: string
source?: string
cover?: string
seo_keywords?: string
seo_title?: string
seo_description?: string
summary?: string
content?: string
status?: number
is_top?: number
is_recommend?: number
allow_comment?: number
sort?: number
view_count?: number
like_count?: number
comment_count?: number
favorite_count?: number
published_at?: number | string
offline_at?: number | string
tag_ids?: Array<string | number>
tag_names?: string
created_date?: string
updated_date?: string
}
export const ArticleContentService = {
...BaseService<ArticleContentRow>({ baseUrl: '/article/index' })
}
export const ArticleCategoryService = {
...BaseService<ArticleCategoryRow>({ baseUrl: '/article/category' })
}
export const ArticleTagService = {
...BaseService<ArticleTagRow>({ baseUrl: '/article/tag' })
}
const pluginApi = {
async test(params = {}) {
return request.get<{ msg?: string; data?: unknown }>({
url: '/article/index',
params
})
},
contentList(params = {}) {
return ArticleContentService.list(params as Record<string, any>)
},
categoryList(params = {}) {
return ArticleCategoryService.list(params as Record<string, any>)
},
tagList(params = {}) {
return ArticleTagService.list(params as Record<string, any>)
},
async recycleList(params = {}) {
return request.get<{ code: number; msg: string; data: Record<string, unknown>[] }>({
url: '/article/recycle',
params
})
},
async recycleRestore(id: string | number) {
return request.put({
url: `/article/recycle/${id}/restore`,
params: { id }
})
},
async recycleDelete(id: string | number) {
return request.del({
url: `/article/recycle/${id}`,
params: { id }
})
}
}
export default pluginApi
@@ -0,0 +1,136 @@
{
"article": {
"title": "Article Management",
"description": "Article content operation and management plugin",
"card_title": "Article Plugin Data",
"refresh_btn": "Refresh",
"common": {
"list_mode": "List Mode",
"columns": {
"index": "No."
}
},
"modules": {
"content": {
"title": "Content Center",
"description": "Manage articles, publish states, and content archives.",
"search": {
"keywords": "Keywords",
"keywords_placeholder": "Please enter article title, category, or tags"
},
"form": {
"title": "Content Title",
"title_placeholder": "Please enter article title",
"slug": "Content Slug",
"slug_placeholder": "Please enter article slug",
"content_type": "Content Type",
"category_id": "Category",
"category_id_placeholder": "Please select category",
"channel": "Channel",
"visibility": "Visibility",
"author": "Author",
"author_placeholder": "Please enter author",
"source": "Source",
"cover": "Cover",
"cover_placeholder": "Please enter cover URL",
"summary": "Summary",
"summary_placeholder": "Please enter summary",
"content": "Content",
"content_placeholder": "Please enter content",
"status": "Status",
"is_top": "Pinned",
"is_recommend": "Recommend",
"allow_comment": "Comment Setting",
"sort": "Sort Order",
"sort_placeholder": "Please enter sort order",
"view_count": "View Count",
"like_count": "Like Count",
"comment_count": "Comment Count",
"favorite_count": "Favorite Count",
"published_at": "Published At",
"offline_at": "Offline At",
"tag_ids": "Content Tags",
"tag_ids_placeholder": "Please select content tags",
"seo_keywords": "SEO Keywords",
"seo_keywords_placeholder": "For search engine optimization",
"seo_title": "SEO Title",
"seo_title_placeholder": "Optional, defaults to content title",
"seo_description": "SEO Description",
"seo_description_placeholder": "Optional, highlight value and search keywords"
},
"columns": {
"title": "Article Title",
"content_info": "Content Info",
"category": "Category",
"tags": "Tags",
"status": "Status",
"business_data": "Business Data",
"time_info": "Time",
"is_top": "Pinned",
"updated_at": "Last Updated"
}
},
"category": {
"title": "Content Category",
"description": "Manage article category structure and ordering.",
"search": {
"keywords": "Keywords",
"keywords_placeholder": "Please enter category name or slug"
},
"form": {
"name_placeholder": "Please enter category name",
"slug_placeholder": "Please enter category slug",
"parent_id": "Parent Category",
"parent_top": "Top Category",
"description": "Category Description",
"description_placeholder": "Please enter category description"
},
"columns": {
"name": "Category Name",
"slug": "Category Slug",
"article_count": "Content Count",
"enabled": "Status",
"sort": "Sort",
"updated_at": "Last Updated"
}
},
"tag": {
"title": "Tag Management",
"description": "Maintain article tags, popularity, and related article counts.",
"search": {
"keywords": "Keywords",
"keywords_placeholder": "Please enter tag name or slug"
},
"form": {
"name_placeholder": "Please enter tag name",
"slug_placeholder": "Please enter tag slug",
"description": "Tag Description",
"description_placeholder": "Please enter tag description"
},
"columns": {
"name": "Tag Name",
"slug": "Tag Slug",
"article_count": "Usage Count",
"enabled": "Status",
"heat": "Heat",
"updated_at": "Last Updated"
}
},
"recycle": {
"title": "Content Recycle Bin",
"description": "Manage deleted articles for later recovery or cleanup.",
"search": {
"keywords": "Keywords",
"keywords_placeholder": "Please enter article title or deleter"
},
"columns": {
"title": "Article Title",
"category": "Original Category",
"deleted_by": "Deleted By",
"deleted_at": "Deleted At",
"expire_at": "Expire At"
}
}
}
}
}
@@ -0,0 +1,8 @@
{
"article": {
"title": "Article Management",
"description": "Article plugin demo page",
"card_title": "Article Plugin Data",
"refresh_btn": "Refresh"
}
}
@@ -0,0 +1,8 @@
{
"article": {
"title": "文章管理",
"description": "文章插件示例页面",
"card_title": "文章插件数据",
"refresh_btn": "刷新"
}
}
@@ -0,0 +1,136 @@
{
"article": {
"title": "文章管理",
"description": "文章内容运营与管理插件",
"card_title": "文章插件数据",
"refresh_btn": "刷新",
"common": {
"list_mode": "列表模式",
"columns": {
"index": "序号"
}
},
"modules": {
"content": {
"title": "内容中心",
"description": "管理文章内容、发布状态与内容归档。",
"search": {
"keywords": "关键字",
"keywords_placeholder": "请输入文章标题、分类或标签"
},
"form": {
"title": "内容标题",
"title_placeholder": "请输入文章标题",
"slug": "内容别名",
"slug_placeholder": "请输入文章标识",
"content_type": "内容类型",
"category_id": "内容分类",
"category_id_placeholder": "请选择内容分类",
"channel": "所属频道",
"visibility": "可见范围",
"author": "作者",
"author_placeholder": "请输入作者",
"source": "内容来源",
"cover": "封面图",
"cover_placeholder": "请输入封面图地址",
"summary": "摘要",
"summary_placeholder": "请输入摘要",
"content": "文章内容",
"content_placeholder": "请输入文章内容",
"status": "状态",
"is_top": "是否置顶",
"is_recommend": "推荐",
"allow_comment": "评论设置",
"sort": "内容排序",
"sort_placeholder": "请输入排序",
"view_count": "浏览量",
"like_count": "点赞量",
"comment_count": "评论量",
"favorite_count": "收藏量",
"published_at": "发布时间",
"offline_at": "下线时间",
"tag_ids": "内容标签",
"tag_ids_placeholder": "请选择内容标签",
"seo_keywords": "SEO 关键词",
"seo_keywords_placeholder": "用于搜索引擎优化",
"seo_title": "SEO 标题",
"seo_title_placeholder": "可选,默认继承内容标题",
"seo_description": "SEO 描述",
"seo_description_placeholder": "可选,建议突出商业价值与检索关键词"
},
"columns": {
"title": "文章标题",
"content_info": "内容信息",
"category": "内容分类",
"tags": "标签",
"status": "状态",
"business_data": "经营数据",
"time_info": "时间",
"is_top": "置顶",
"updated_at": "最后更新"
}
},
"category": {
"title": "内容分类",
"description": "管理文章分类结构与分类排序。",
"search": {
"keywords": "关键字",
"keywords_placeholder": "请输入分类名称或分类标识"
},
"form": {
"name_placeholder": "请输入分类名称",
"slug_placeholder": "请输入分类标识",
"parent_id": "父级分类",
"parent_top": "顶级分类",
"description": "分类描述",
"description_placeholder": "请输入分类描述"
},
"columns": {
"name": "分类名称",
"slug": "分类标识",
"article_count": "内容数",
"enabled": "状态",
"sort": "排序",
"updated_at": "最后更新"
}
},
"tag": {
"title": "标签管理",
"description": "维护文章标签、热度与关联内容数量。",
"search": {
"keywords": "关键字",
"keywords_placeholder": "请输入标签名称或标签标识"
},
"form": {
"name_placeholder": "请输入标签名称",
"slug_placeholder": "请输入标签标识",
"description": "标签描述",
"description_placeholder": "请输入标签描述"
},
"columns": {
"name": "标签名称",
"slug": "标签标识",
"article_count": "使用次数",
"enabled": "状态",
"heat": "热度",
"updated_at": "最后更新"
}
},
"recycle": {
"title": "内容回收站",
"description": "管理已删除文章,支持后续恢复或清理。",
"search": {
"keywords": "关键字",
"keywords_placeholder": "请输入文章标题或删除人"
},
"columns": {
"title": "文章标题",
"category": "原分类",
"deleted_by": "删除人",
"deleted_at": "删除时间",
"expire_at": "过期清理时间"
}
}
}
}
}
@@ -0,0 +1,59 @@
import { AppRouteRecord } from '@/types/router'
import { RoutesAlias } from '@/router/routesAlias'
export default {
path: '/article',
name: 'Article',
component: RoutesAlias.Layout,
meta: {
title: '文章管理',
icon: 'ri:plugin-line',
module: 'article'
},
children: [
{
path: 'index',
name: 'ArticleIndex',
component: '/article/content/index',
meta: {
title: '内容中心',
icon: 'ri:article-line',
keepAlive: false,
module: 'article'
}
},
{
path: 'category',
name: 'ArticleCategory',
component: '/article/category/index',
meta: {
title: '内容分类',
icon: 'ri:folder-2-line',
keepAlive: false,
module: 'article'
}
},
{
path: 'tag',
name: 'ArticleTag',
component: '/article/tag/index',
meta: {
title: '标签管理',
icon: 'ri:price-tag-3-line',
keepAlive: false,
module: 'article'
}
},
{
path: 'recycle',
name: 'ArticleRecycle',
component: '/article/recycle/index',
meta: {
title: '内容回收站',
icon: 'ri:delete-bin-6-line',
keepAlive: false,
module: 'article'
}
}
]
} as AppRouteRecord
@@ -0,0 +1,26 @@
<template>
<div class="article-category-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
defineOptions({ name: 'ArticleCategoryList' })
const [BasicCrud] = useCrud({
...crudSchema(),
toolbarActions: [],
dropDownToolbarActions: [],
tableActions: [],
dropDownActions: [],
})
</script>
<style scoped>
.article-category-page {
height: 100%;
}
</style>
@@ -0,0 +1,189 @@
import { CrudSchema } from '@/components/crud'
import { DictEnum } from '@/enums'
import { $t } from '@/locales'
import { ArticleCategoryService } from '../../../../api/index'
export const crudSchema = (): CrudSchema => {
return {
api: ArticleCategoryService.list,
dialogTitle: $t('article.modules.category.title'),
crudApi: {
add: ArticleCategoryService.create,
edit: ArticleCategoryService.update,
remove: ArticleCategoryService.delete,
view: ArticleCategoryService.get,
},
useCrud: true,
hasAdd: true,
hasEdit: true,
hasRemove: true,
hasView: true,
addAuth: 'article:category:create',
editAuth: 'article:category:update',
removeAuth: 'article:category:delete',
viewAuth: 'article:category:read',
rowKey: 'id',
searchFormSchema: {
labelWidth: '100px',
schema: [
{
prop: 'LIKE_name',
label: $t('article.modules.category.search.keywords'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('article.modules.category.search.keywords_placeholder'),
},
colSpan: 6,
},
],
},
columns: [
{
type: 'selection',
},
{
type: 'index',
label: $t('article.common.columns.index'),
width: 70,
},
{
prop: 'name',
label: $t('article.modules.category.columns.name'),
minWidth: 220,
},
{
prop: 'slug',
label: $t('article.modules.category.columns.slug'),
minWidth: 180,
},
{
prop: 'article_count',
label: $t('article.modules.category.columns.article_count'),
width: 120,
},
{
prop: 'enabled',
label: $t('article.modules.category.columns.enabled'),
width: 120,
component: 'ApiDict',
componentProps: {
code: DictEnum.SYS_ENABLED_STATUS,
},
},
{
prop: 'sort',
label: $t('article.modules.category.columns.sort'),
width: 120,
},
{
prop: 'updated_date',
label: $t('article.modules.category.columns.updated_at'),
minWidth: 180,
},
],
dialogProps: {
width: '600px',
},
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
{
component: 'Input',
show: false,
ifDetail: true,
colSpan: 24,
prop: 'id',
label: 'ID',
},
{
component: 'Input',
prop: 'name',
label: $t('article.modules.category.columns.name'),
componentProps: {
placeholder: $t('article.modules.category.form.name_placeholder'),
maxlength: 100,
},
rules: [{ required: true, message: $t('article.modules.category.form.name_placeholder') }],
colSpan: 12,
},
{
component: 'Input',
prop: 'slug',
label: $t('article.modules.category.columns.slug'),
componentProps: {
placeholder: $t('article.modules.category.form.slug_placeholder'),
maxlength: 100,
},
colSpan: 12,
},
{
component: 'ApiTreeSelect',
prop: 'parent_id',
label: $t('article.modules.category.form.parent_id'),
defaultValue: 0,
componentProps: {
api: '/article/category',
requestMethod: 'get',
params: {
format: 'tree',
},
afterFetch: async (res: any) => {
if (!Array.isArray(res)) {
return res
}
return [
{
id: 0,
name: $t('article.modules.category.form.parent_top'),
},
...res,
]
},
clearable: true,
checkStrictly: true,
class: 'w-full',
labelField: 'name',
valueField: 'id',
},
colSpan: 12,
},
{
component: 'InputNumber',
prop: 'sort',
label: $t('article.modules.category.columns.sort'),
defaultValue: 100,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'ApiDict',
prop: 'enabled',
label: $t('article.modules.category.columns.enabled'),
defaultValue: 1,
componentProps: {
code: DictEnum.SYS_ENABLED_STATUS,
isBtn: true,
renderType: 'RadioGroup',
},
colSpan: 24,
},
{
component: 'Input',
prop: 'description',
label: $t('article.modules.category.form.description'),
componentProps: {
type: 'textarea',
rows: 4,
placeholder: $t('article.modules.category.form.description_placeholder'),
},
colSpan: 24,
},
],
},
}
}
@@ -0,0 +1,26 @@
<template>
<div class="article-content-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
defineOptions({ name: 'ArticleContentList' })
const [BasicCrud] = useCrud({
...crudSchema(),
toolbarActions: [],
dropDownToolbarActions: [],
tableActions: [],
dropDownActions: [],
})
</script>
<style scoped>
.article-content-page {
height: 100%;
}
</style>
@@ -0,0 +1,544 @@
import { CrudSchema } from '@/components/crud'
import { DictEnum } from '@/enums'
import { $t } from '@/locales'
import { ArticleContentService } from '../../../../api/index'
import { h } from 'vue'
const contentTypeOptions = [
{ label: '文章', value: 'article' },
{ label: '图文', value: 'news' },
{ label: '公告', value: 'notice' },
]
const channelOptions = [
{ label: '官网内容', value: 'official' },
{ label: '专题内容', value: 'topic' },
{ label: '活动内容', value: 'activity' },
]
const visibilityOptions = [
{ label: '公开', value: 1 },
{ label: '登录可见', value: 2 },
{ label: '私密', value: 3 },
]
const sourceOptions = [
{ label: '原创', value: 'original' },
{ label: '转载', value: 'repost' },
{ label: '投稿', value: 'contribution' },
]
const statusOptions = [
{ label: '草稿', value: 0 },
{ label: '发布', value: 1 },
{ label: '下线', value: 2 },
]
const getOptionLabel = (options: Array<{ label: string; value: string | number }>, value: string | number) => {
return options.find((item) => item.value === value)?.label || '-'
}
const getStatusTagClass = (status: number) => {
if (status === 1) {
return 'inline-flex rounded-full bg-[#ecfdf5] px-2 py-[2px] text-xs text-[#10b981]'
}
if (status === 2) {
return 'inline-flex rounded-full bg-[#fef2f2] px-2 py-[2px] text-xs text-[#ef4444]'
}
return 'inline-flex rounded-full bg-[#f4f4f5] px-2 py-[2px] text-xs text-[#909399]'
}
export const crudSchema = (): CrudSchema => {
return {
api: ArticleContentService.list,
dialogTitle: $t('article.modules.content.title'),
exportFileName: '内容中心',
exportFields: [
{ field: 'title', title: '标题' },
{ field: 'content_type', title: '内容类型' },
{ field: 'category_name', title: '分类' },
{ field: 'tag_names', title: '标签' },
{ field: 'author', title: '作者' },
{ field: 'channel', title: '频道' },
{ field: 'visibility', title: '可见范围' },
{ field: 'source', title: '来源' },
{ field: 'status', title: '状态' },
{ field: 'is_top', title: '置顶' },
{ field: 'is_recommend', title: '推荐' },
{ field: 'view_count', title: '浏览数' },
{ field: 'like_count', title: '点赞数' },
{ field: 'comment_count', title: '评论数' },
{ field: 'favorite_count', title: '收藏数' },
{ field: 'published_at', title: '发布时间' },
{ field: 'updated_date', title: '更新时间' },
{ field: 'summary', title: '摘要' }
],
crudApi: {
add: ArticleContentService.create,
edit: ArticleContentService.update,
remove: ArticleContentService.remove,
view: ArticleContentService.get,
},
useCrud: true,
hasAdd: true,
hasEdit: true,
hasRemove: true,
hasView: true,
addAuth: 'article:create',
editAuth: 'article:update',
removeAuth: 'article:delete',
viewAuth: 'article:read',
rowKey: 'id',
searchFormSchema: {
labelWidth: '100px',
schema: [
{
prop: 'LIKE_title',
label: $t('article.modules.content.search.keywords'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('article.modules.content.search.keywords_placeholder'),
},
colSpan: 6,
},
],
},
columns: [
{
type: 'index',
label: $t('article.common.columns.index'),
width: 70,
},
{
prop: 'content_info',
label: $t('article.modules.content.columns.content_info'),
minWidth: 380,
formatter: (row: any) => {
const metaItems = [
getOptionLabel(contentTypeOptions, row.content_type),
row.author || '管理员',
row.category_name,
row.tag_names,
].filter(Boolean)
const badgeNodes = [
Number(row.is_top) === 1
? h('span', { class: 'inline-flex items-center rounded bg-[#fff7e6] px-1.5 py-[1px] text-[10px] text-[#fa8c16]' }, '置顶')
: null,
Number(row.is_recommend) === 1
? h('span', { class: 'inline-flex items-center rounded bg-[#ecfdf5] px-1.5 py-[1px] text-[10px] text-[#10b981]' }, '推荐')
: null,
].filter(Boolean)
return h('div', { class: 'flex gap-3 py-2' }, [
row.cover
? h('img', {
src: row.cover,
class: 'mt-1 h-14 w-20 flex-none rounded bg-[#f5f7fa] object-cover border border-[#f0f0f0]'
})
: h(
'div',
{
class: 'mt-1 h-14 w-20 flex-none rounded border border-[#f0f0f0] bg-[#fafafa] text-[10px] text-[#c0c4cc] flex items-center justify-center'
},
'无上传封面'
),
h('div', { class: 'min-w-0 flex-1 leading-6' }, [
h('div', { class: 'flex items-center gap-2 text-sm font-medium text-[#1f2937]' }, [
h('span', { class: 'line-clamp-1' }, row.title || '-'),
...badgeNodes
]),
h(
'div',
{ class: 'mt-1 flex flex-wrap gap-x-3 gap-y-1 text-xs text-[#909399]' },
metaItems.length > 0 ? metaItems.map((item) => h('span', item)) : [h('span', '-')]
),
h('div', { class: 'mt-1 line-clamp-2 text-xs text-[#606266]' }, row.summary || row.slug || '-')
])
])
}
},
{
prop: 'status_info',
label: $t('article.modules.content.columns.status'),
width: 140,
align: 'center',
formatter: (row: any) => {
const statusText = getOptionLabel(statusOptions, row.status)
const visibilityText = getOptionLabel(visibilityOptions, row.visibility)
const sourceText = getOptionLabel(sourceOptions, row.source)
return h('div', { class: 'flex flex-col items-center py-2 text-sm leading-6' }, [
h('div', [
h('span', { class: getStatusTagClass(Number(row.status)) }, statusText)
]),
h('div', { class: 'mt-1 text-xs text-[#606266]' }, visibilityText),
h('div', { class: 'mt-1 text-xs text-[#909399]' }, sourceText)
])
}
},
{
prop: 'business_data',
label: $t('article.modules.content.columns.business_data'),
minWidth: 250,
formatter: (row: any) => {
const metricItems = [
{ label: '浏览', value: row.view_count ?? 0 },
{ label: '点赞', value: row.like_count ?? 0 },
{ label: '评论', value: row.comment_count ?? 0 },
{ label: '收藏', value: row.favorite_count ?? 0 },
]
return h('div', { class: 'grid grid-cols-2 gap-2 py-2 text-xs text-[#606266]' }, [
...metricItems.map((item) =>
h('div', { class: 'flex items-center justify-between rounded border border-[#e5e7eb] bg-[#eef2f6] px-3 py-1.5 transition-colors hover:bg-[#e5e7eb]' }, [
h('span', { class: 'text-[#909399]' }, item.label),
h('span', item.value)
])
)
])
}
},
{
prop: 'time_info',
label: $t('article.modules.content.columns.time_info'),
minWidth: 190,
formatter: (row: any) => {
return h('div', { class: 'py-2 text-xs leading-6 text-[#606266]' }, [
h('div', [
h('span', { class: 'text-[#909399]' }, '更新 '),
h('span', row.updated_date || '-')
]),
h('div', { class: 'mt-1' }, [
h('span', { class: 'text-[#909399]' }, '发布 '),
h('span', row.created_date || '-')
])
])
}
},
],
tableActionColumn: {
width: 170,
},
dialogProps: {
width: '70%',
},
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
{
component: 'Input',
show: false,
ifDetail: true,
colSpan: 24,
prop: 'id',
label: 'ID',
},
{
component: 'Input',
prop: 'title',
label: $t('article.modules.content.columns.title'),
componentProps: {
placeholder: $t('article.modules.content.form.title_placeholder'),
maxlength: 200,
},
rules: [{ required: true, message: $t('article.modules.content.form.title_placeholder') }],
colSpan: 12,
},
{
component: 'Input',
prop: 'slug',
label: $t('article.modules.content.form.slug'),
componentProps: {
placeholder: $t('article.modules.content.form.slug_placeholder'),
maxlength: 200,
},
colSpan: 12,
},
{
component: 'Select',
prop: 'content_type',
label: $t('article.modules.content.form.content_type'),
defaultValue: 'article',
componentProps: {
options: contentTypeOptions,
class: 'w-full',
},
rules: [{ required: true, message: $t('article.modules.content.form.content_type') }],
colSpan: 12,
},
{
component: 'ApiTreeSelect',
prop: 'category_id',
label: $t('article.modules.content.form.category_id'),
componentProps: {
api: '/article/category',
requestMethod: 'get',
params: {
format: 'tree',
},
teleported: true,
clearable: true,
checkStrictly: true,
class: 'w-full',
labelField: 'name',
valueField: 'id',
placeholder: $t('article.modules.content.form.category_id_placeholder'),
},
rules: [{ required: true, message: $t('article.modules.content.form.category_id') }],
colSpan: 12,
},
{
component: 'Select',
prop: 'channel',
label: $t('article.modules.content.form.channel'),
defaultValue: 'official',
componentProps: {
options: channelOptions,
class: 'w-full',
},
rules: [{ required: true, message: $t('article.modules.content.form.channel') }],
colSpan: 12,
},
{
component: 'Input',
prop: 'author',
label: $t('article.modules.content.form.author'),
componentProps: {
placeholder: $t('article.modules.content.form.author_placeholder'),
maxlength: 100,
},
rules: [{ required: true, message: $t('article.modules.content.form.author_placeholder') }],
colSpan: 12,
},
{
component: 'Select',
prop: 'visibility',
label: $t('article.modules.content.form.visibility'),
defaultValue: 1,
componentProps: {
options: visibilityOptions,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'Select',
prop: 'status',
label: $t('article.modules.content.form.status'),
defaultValue: 0,
componentProps: {
options: statusOptions,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'Select',
prop: 'source',
label: $t('article.modules.content.form.source'),
defaultValue: 'original',
componentProps: {
options: sourceOptions,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'Avatar',
prop: 'cover',
label: $t('article.modules.content.form.cover'),
componentProps: {
shape: 'square',
},
colSpan: 24,
},
{
component: 'Input',
prop: 'summary',
label: $t('article.modules.content.form.summary'),
componentProps: {
type: 'textarea',
rows: 4,
maxlength: 500,
placeholder: $t('article.modules.content.form.summary_placeholder'),
},
colSpan: 24,
},
{
component: 'ApiSelect',
prop: 'tag_ids',
label: $t('article.modules.content.form.tag_ids'),
componentProps: {
api: '/article/tag',
requestMethod: 'get',
params: {
format: 'select',
},
labelField: 'label',
valueField: 'value',
multiple: true,
clearable: true,
filterable: true,
class: 'w-full',
placeholder: $t('article.modules.content.form.tag_ids_placeholder'),
},
colSpan: 12,
},
{
component: 'Input',
prop: 'seo_keywords',
label: $t('article.modules.content.form.seo_keywords'),
componentProps: {
placeholder: $t('article.modules.content.form.seo_keywords_placeholder'),
maxlength: 255,
},
colSpan: 12,
},
{
component: 'Input',
prop: 'seo_title',
label: $t('article.modules.content.form.seo_title'),
componentProps: {
placeholder: $t('article.modules.content.form.seo_title_placeholder'),
maxlength: 200,
},
colSpan: 12,
},
{
component: 'Input',
prop: 'seo_description',
label: $t('article.modules.content.form.seo_description'),
componentProps: {
placeholder: $t('article.modules.content.form.seo_description_placeholder'),
maxlength: 500,
},
colSpan: 12,
},
{
component: 'InputNumber',
prop: 'sort',
label: $t('article.modules.content.form.sort'),
defaultValue: 100,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 8,
},
{
component: 'DatePicker',
prop: 'published_at',
label: $t('article.modules.content.form.published_at'),
componentProps: {
type: 'datetime',
valueFormat: 'X',
class: 'w-full',
},
colSpan: 8,
},
{
component: 'DatePicker',
prop: 'offline_at',
label: $t('article.modules.content.form.offline_at'),
componentProps: {
type: 'datetime',
valueFormat: 'X',
class: 'w-full',
},
colSpan: 8,
},
{
component: 'InputNumber',
prop: 'view_count',
label: $t('article.modules.content.form.view_count'),
defaultValue: 0,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 6,
},
{
component: 'InputNumber',
prop: 'like_count',
label: $t('article.modules.content.form.like_count'),
defaultValue: 0,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 6,
},
{
component: 'InputNumber',
prop: 'comment_count',
label: $t('article.modules.content.form.comment_count'),
defaultValue: 0,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 6,
},
{
component: 'InputNumber',
prop: 'favorite_count',
label: $t('article.modules.content.form.favorite_count'),
defaultValue: 0,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 6,
},
{
component: 'ApiDict',
prop: 'is_top',
label: $t('article.modules.content.form.is_top'),
defaultValue: 0,
componentProps: {
code: DictEnum.SYS_YES_NO,
isBtn: true,
renderType: 'RadioGroup',
},
colSpan: 8,
},
{
component: 'ApiDict',
prop: 'is_recommend',
label: $t('article.modules.content.form.is_recommend'),
defaultValue: 0,
componentProps: {
code: DictEnum.SYS_YES_NO,
isBtn: true,
renderType: 'RadioGroup',
},
colSpan: 8,
},
{
component: 'ApiDict',
prop: 'allow_comment',
label: $t('article.modules.content.form.allow_comment'),
defaultValue: 1,
componentProps: {
code: DictEnum.SYS_YES_NO,
isBtn: true,
renderType: 'RadioGroup',
},
colSpan: 8,
},
{
component: 'Editor',
prop: 'content',
label: $t('article.modules.content.form.content'),
componentProps: {
placeholder: $t('article.modules.content.form.content_placeholder'),
},
colSpan: 24,
},
],
},
}
}
@@ -0,0 +1,147 @@
<template>
<div class="article-container">
<div class="plugin-header">
<h1>{{ $t('article.title') }}</h1>
<p>{{ $t('article.description') }}</p>
</div>
<div class="plugin-content">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>{{ $t('article.card_title') }}</span>
<el-button type="primary" @click="fetchTestData">
{{ $t('article.refresh_btn') }}
</el-button>
</div>
</template>
<div v-loading="loading" class="card-body">
<div v-if="error" class="error-message">
<el-alert
:title="error"
type="error"
show-icon
/>
</div>
<div v-else-if="testData" class="plugin-data">
<el-descriptions :column="1" border>
<el-descriptions-item label="测试消息">
{{ testData.msg }}
</el-descriptions-item>
<el-descriptions-item label="数据">
{{ testData.data }}
</el-descriptions-item>
</el-descriptions>
</div>
<div v-else class="empty-state">
<el-empty description="暂无数据" />
</div>
</div>
</el-card>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import pluginApi from '../../../api/index';
export default defineComponent({
name: 'ArticleView',
setup() {
const loading = ref(false);
const error = ref<string | null>(null);
const testData = ref<{ msg?: string; data?: unknown } | null>(null);
const fetchTestData = async () => {
loading.value = true;
error.value = null;
try {
const response = await pluginApi.test();
testData.value = response;
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : '网络错误';
} finally {
loading.value = false;
}
};
// 初始加载数据
fetchTestData();
return {
loading,
error,
testData,
fetchTestData
}
}
});
</script>
<style scoped>
.article-container {
padding: 20px;
min-height: 80vh;
}
.plugin-header {
margin-bottom: 30px;
text-align: center;
}
.plugin-header h1 {
font-size: 28px;
font-weight: 600;
margin-bottom: 10px;
color: #303133;
}
.plugin-header p {
font-size: 16px;
color: #606266;
}
.plugin-content {
max-width: 800px;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px 0;
}
.error-message {
margin-bottom: 20px;
}
.plugin-data {
margin-top: 20px;
}
.plugin-data pre {
background-color: #f5f7fa;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
}
.empty-state {
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
@@ -0,0 +1,59 @@
<template>
<div class="article-recycle-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { ElMessage, ElMessageBox } from 'element-plus'
import { $t } from '@/locales'
import pluginApi from '../../../api/index'
import { crudSchema } from './schemas'
defineOptions({ name: 'ArticleRecycleList' })
const [BasicCrud, crudApi] = useCrud({
...crudSchema(),
toolbarActions: [],
dropDownToolbarActions: [],
tableActions: [
{
label: '恢复',
type: 'primary',
link: true,
icon: 'ant-design:swap-outlined',
auth: 'article:recycle:restore',
onClick: async (e: Event, row: any) => {
await pluginApi.recycleRestore(row.id)
ElMessage.success($t('common.operation_success'))
crudApi.reload()
}
},
{
label: '彻底删除',
type: 'danger',
link: true,
icon: 'ant-design:delete-outlined',
auth: 'article:recycle:delete',
onClick: async (e: Event, row: any) => {
await ElMessageBox.confirm('确定彻底删除该内容吗?删除后不可恢复。', '二次确认', {
type: 'warning',
confirmButtonText: '确定删除',
cancelButtonText: '取消'
})
await pluginApi.recycleDelete(row.id)
ElMessage.success($t('common.operation_success'))
crudApi.reload()
}
}
],
dropDownActions: [],
})
</script>
<style scoped>
.article-recycle-page {
height: 100%;
}
</style>
@@ -0,0 +1,65 @@
import { CrudSchema } from '@/components/crud'
import { $t } from '@/locales'
import pluginApi from '../../../../api/index'
export const crudSchema = (): CrudSchema => {
return {
api: async (params: any) => {
const response = await pluginApi.recycleList(params)
const rows = Array.isArray(response) ? response : []
return {
recordCount: rows.length,
totalPage: 1,
rows,
}
},
useCrud: false,
searchFormSchema: {
labelWidth: '100px',
schema: [
{
prop: 'LIKE_title',
label: $t('article.modules.recycle.search.keywords'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('article.modules.recycle.search.keywords_placeholder'),
},
colSpan: 6,
},
],
},
columns: [
{
type: 'index',
label: $t('article.common.columns.index'),
width: 70,
},
{
prop: 'title',
label: $t('article.modules.recycle.columns.title'),
minWidth: 220,
},
{
prop: 'category',
label: $t('article.modules.recycle.columns.category'),
minWidth: 160,
},
{
prop: 'deleted_by',
label: $t('article.modules.recycle.columns.deleted_by'),
width: 140,
},
{
prop: 'deleted_at',
label: $t('article.modules.recycle.columns.deleted_at'),
minWidth: 180,
},
{
prop: 'expire_at',
label: $t('article.modules.recycle.columns.expire_at'),
minWidth: 180,
},
],
}
}
@@ -0,0 +1,26 @@
<template>
<div class="article-tag-page art-full-height">
<BasicCrud />
</div>
</template>
<script setup lang="tsx">
import { useCrud } from '@/components/crud'
import { crudSchema } from './schemas'
defineOptions({ name: 'ArticleTagList' })
const [BasicCrud] = useCrud({
...crudSchema(),
toolbarActions: [],
dropDownToolbarActions: [],
tableActions: [],
dropDownActions: [],
})
</script>
<style scoped>
.article-tag-page {
height: 100%;
}
</style>
@@ -0,0 +1,169 @@
import { CrudSchema } from '@/components/crud'
import { DictEnum } from '@/enums'
import { $t } from '@/locales'
import { ArticleTagService } from '../../../../api/index'
export const crudSchema = (): CrudSchema => {
return {
api: ArticleTagService.list,
dialogTitle: $t('article.modules.tag.title'),
crudApi: {
add: ArticleTagService.create,
edit: ArticleTagService.update,
remove: ArticleTagService.delete,
view: ArticleTagService.get,
},
useCrud: true,
hasAdd: true,
hasEdit: true,
hasRemove: true,
hasView: true,
addAuth: 'article:tag:create',
editAuth: 'article:tag:update',
removeAuth: 'article:tag:delete',
viewAuth: 'article:tag:read',
rowKey: 'id',
searchFormSchema: {
labelWidth: '100px',
schema: [
{
prop: 'LIKE_name',
label: $t('article.modules.tag.search.keywords'),
component: 'Input',
componentProps: {
clearable: true,
placeholder: $t('article.modules.tag.search.keywords_placeholder'),
},
colSpan: 6,
},
],
},
columns: [
{
type: 'selection',
},
{
type: 'index',
label: $t('article.common.columns.index'),
width: 70,
},
{
prop: 'name',
label: $t('article.modules.tag.columns.name'),
minWidth: 220,
},
{
prop: 'slug',
label: $t('article.modules.tag.columns.slug'),
minWidth: 180,
},
{
prop: 'article_count',
label: $t('article.modules.tag.columns.article_count'),
width: 120,
},
{
prop: 'enabled',
label: $t('article.modules.tag.columns.enabled'),
width: 120,
component: 'ApiDict',
componentProps: {
code: DictEnum.SYS_ENABLED_STATUS,
},
},
{
prop: 'heat',
label: $t('article.modules.tag.columns.heat'),
width: 120,
},
{
prop: 'updated_date',
label: $t('article.modules.tag.columns.updated_at'),
minWidth: 180,
},
],
dialogProps: {
width: '600px',
},
formSchema: {
showDefaultActions: false,
labelWidth: '100px',
schema: [
{
component: 'Input',
show: false,
ifDetail: true,
colSpan: 24,
prop: 'id',
label: 'ID',
},
{
component: 'Input',
prop: 'name',
label: $t('article.modules.tag.columns.name'),
componentProps: {
placeholder: $t('article.modules.tag.form.name_placeholder'),
maxlength: 100,
},
rules: [{ required: true, message: $t('article.modules.tag.form.name_placeholder') }],
colSpan: 12,
},
{
component: 'Input',
prop: 'slug',
label: $t('article.modules.tag.columns.slug'),
componentProps: {
placeholder: $t('article.modules.tag.form.slug_placeholder'),
maxlength: 100,
},
colSpan: 12,
},
{
component: 'InputNumber',
prop: 'sort',
label: $t('article.modules.tag.columns.heat'),
defaultValue: 100,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'InputNumber',
prop: 'heat',
label: $t('article.modules.tag.columns.heat'),
defaultValue: 0,
componentProps: {
min: 0,
class: 'w-full',
},
colSpan: 12,
},
{
component: 'ApiDict',
prop: 'enabled',
label: $t('article.modules.tag.columns.enabled'),
defaultValue: 1,
componentProps: {
code: DictEnum.SYS_ENABLED_STATUS,
isBtn: true,
renderType: 'RadioGroup',
},
colSpan: 24,
},
{
component: 'Input',
prop: 'description',
label: $t('article.modules.tag.form.description'),
componentProps: {
type: 'textarea',
rows: 4,
placeholder: $t('article.modules.tag.form.description_placeholder'),
},
colSpan: 24,
},
],
},
}
}
@@ -0,0 +1,147 @@
<template>
<div class="article-container">
<div class="plugin-header">
<h1>{{ $t('article.title') }}</h1>
<p>{{ $t('article.description') }}</p>
</div>
<div class="plugin-content">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<span>{{ $t('article.card_title') }}</span>
<el-button type="primary" @click="fetchTestData">
{{ $t('article.refresh_btn') }}
</el-button>
</div>
</template>
<div v-loading="loading" class="card-body">
<div v-if="error" class="error-message">
<el-alert
:title="error"
type="error"
show-icon
/>
</div>
<div v-else-if="testData" class="plugin-data">
<el-descriptions :column="1" border>
<el-descriptions-item label="测试消息">
{{ testData.msg }}
</el-descriptions-item>
<el-descriptions-item label="数据">
{{ testData.data }}
</el-descriptions-item>
</el-descriptions>
</div>
<div v-else class="empty-state">
<el-empty description="暂无数据" />
</div>
</div>
</el-card>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import pluginApi from '../../api/index';
export default defineComponent({
name: 'ArticleView',
setup() {
const loading = ref(false);
const error = ref<string | null>(null);
const testData = ref<{ msg?: string; data?: unknown } | null>(null);
const fetchTestData = async () => {
loading.value = true;
error.value = null;
try {
const response = await pluginApi.test();
testData.value = response;
} catch (err: unknown) {
error.value = err instanceof Error ? err.message : '网络错误';
} finally {
loading.value = false;
}
};
// 初始加载数据
fetchTestData();
return {
loading,
error,
testData,
fetchTestData
}
}
});
</script>
<style scoped>
.article-container {
padding: 20px;
min-height: 80vh;
}
.plugin-header {
margin-bottom: 30px;
text-align: center;
}
.plugin-header h1 {
font-size: 28px;
font-weight: 600;
margin-bottom: 10px;
color: #303133;
}
.plugin-header p {
font-size: 16px;
color: #606266;
}
.plugin-content {
max-width: 800px;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-body {
padding: 20px 0;
}
.error-message {
margin-bottom: 20px;
}
.plugin-data {
margin-top: 20px;
}
.plugin-data pre {
background-color: #f5f7fa;
padding: 15px;
border-radius: 4px;
overflow-x: auto;
font-size: 14px;
line-height: 1.5;
}
.empty-state {
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
</style>
+59
View File
@@ -0,0 +1,59 @@
<?php
namespace plugin\produce;
use core\plugin\PluginInstall as BasePluginInstall;
/**
* 插件安装类
* 继承 PluginInstall 基类,自动处理:
* - 数据库迁移 (resource/database/migrations/)
* - 种子数据 (resource/database/seeds/)
*
* 菜单需要按需导入或卸载;模板在安装时导入,物理删除由删除插件阶段统一处理:
* - $this->importPluginMenus() - 导入菜单
* - $this->importTemplates() - 复制模板
* - $this->uninstallPluginMenus() - 卸载菜单
*/
class Install extends BasePluginInstall
{
/**
* 安装前回调
*/
protected function beforeInstall(string $version): void
{
$this->output(" 🔄 Running beforeInstall...\n");
}
/**
* 安装后回调 - 按需导入菜单和模板
*/
protected function afterInstall(string $version): void
{
$this->output(" 🔄 Running afterInstall...\n");
// 导入菜单
$this->importPluginMenus();
// 复制模板资源
$this->importTemplates();
}
/**
* 卸载前回调
*/
protected function beforeUninstall(string $version): void
{
$this->output(" 🔄 Running beforeUninstall...\n");
}
/**
* 卸载后回调 - 清理菜单
*/
protected function afterUninstall(string $version): void
{
$this->output(" 🔄 Running afterUninstall...\n");
// 卸载菜单
$this->uninstallPluginMenus();
}
}
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ExceptionAnalysisService;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ExceptionAnalysisController
{
public function __construct(protected ExceptionAnalysisService $service)
{
}
#[OA\Get(path: '/produce/exception-analysis/overview', summary: '异常数据分析', tags: ['produce-exception-analysis'])]
#[DataResponse(description: '获取异常数据分析成功')]
#[Permission(code: 'produce:exception-analysis:read', description: '查看异常数据分析')]
public function overview(Request $request): Response
{
return Json::success('获取成功', $this->service->overview($request->all()));
}
}
@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ExceptionRecordService;
use plugin\produce\app\validate\ExceptionRecordValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ExceptionRecordController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['order_no', 'user_code', 'reporter', 'type_name', 'reason_name', 'issue_name', 'remark'],
];
public function __construct(ExceptionRecordService $service, ExceptionRecordValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/exception-record', summary: '异常数据列表', tags: ['produce-exception-record'])]
#[Permission(code: 'produce:exception-data:list', description: '异常数据列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(path: '/produce/exception-record/{id}', summary: '异常数据详情', tags: ['produce-exception-record'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-data:read', description: '异常数据详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/exception-record', summary: '创建异常数据', tags: ['produce-exception-record'])]
#[Permission(code: 'produce:exception-data:create', description: '创建异常数据')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(path: '/produce/exception-record/{id}', summary: '更新异常数据', tags: ['produce-exception-record'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-data:update', description: '更新异常数据')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/exception-record/export', summary: '导出异常数据', tags: ['produce-exception-record'])]
#[Permission(code: 'produce:exception-data:list', description: '导出异常数据')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(path: '/produce/exception-record/{id}', summary: '删除异常数据', tags: ['produce-exception-record'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-data:delete', description: '删除异常数据')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(path: '/produce/exception-record', summary: '批量删除异常数据', tags: ['produce-exception-record'], x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
])]
#[Permission(code: 'produce:exception-data:delete', description: '批量删除异常数据')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ExceptionSettingService;
use plugin\produce\app\validate\ExceptionSettingValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ExceptionSettingController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['code', 'name', 'remark'],
];
public function __construct(ExceptionSettingService $service, ExceptionSettingValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/exception-setting', summary: '异常设置列表', tags: ['produce-exception-setting'])]
#[Permission(code: 'produce:exception-setting:list', description: '异常设置列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(path: '/produce/exception-setting/{id}', summary: '异常设置详情', tags: ['produce-exception-setting'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-setting:read', description: '异常设置详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/exception-setting', summary: '创建异常设置', tags: ['produce-exception-setting'])]
#[Permission(code: 'produce:exception-setting:create', description: '创建异常设置')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(path: '/produce/exception-setting/{id}', summary: '更新异常设置', tags: ['produce-exception-setting'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-setting:update', description: '更新异常设置')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Delete(path: '/produce/exception-setting/{id}', summary: '删除异常设置', tags: ['produce-exception-setting'], x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
])]
#[Permission(code: 'produce:exception-setting:delete', description: '删除异常设置')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(path: '/produce/exception-setting', summary: '批量删除异常设置', tags: ['produce-exception-setting'], x: [
SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class,
])]
#[Permission(code: 'produce:exception-setting:delete', description: '批量删除异常设置')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\HardwareFaultDescriptionService;
use plugin\produce\app\validate\HardwareFaultDescriptionValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class HardwareFaultDescriptionController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['description', 'remark'],
];
public function __construct(HardwareFaultDescriptionService $service, HardwareFaultDescriptionValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/hardware-fault-description', summary: '硬件故障描述列表', tags: ['produce-hardware-fault-description'])]
#[Permission(code: 'produce:hardware-fault-description:list', description: '硬件故障描述列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response { return parent::index($request); }
#[OA\Post(path: '/produce/hardware-fault-description/import', summary: '导入硬件故障描述', tags: ['produce-hardware-fault-description'])]
#[Permission(code: 'produce:hardware-fault-description:import', description: '导入硬件故障描述')]
#[SimpleResponse(schema: [], example: [])]
public function import(Request $request): \support\Response
{
try {
$file = $request->file('file');
if (!$file) {
return Json::fail('请选择Excel文件');
}
if ($file->getSize() > 5 * 1024 * 1024) {
return Json::fail('Excel文件不能超过5MB');
}
$extension = strtolower((string) $file->getUploadExtension());
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
return Json::fail('只支持xlsx、xls或csv格式文件');
}
$result = $this->service->importFromExcel($file->getPathname());
return Json::success('导入成功', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(path: '/produce/hardware-fault-description/{id}', summary: '硬件故障描述详情', tags: ['produce-hardware-fault-description'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:hardware-fault-description:read', description: '硬件故障描述详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response { return parent::show($request); }
#[OA\Post(path: '/produce/hardware-fault-description', summary: '创建硬件故障描述', tags: ['produce-hardware-fault-description'])]
#[Permission(code: 'produce:hardware-fault-description:create', description: '创建硬件故障描述')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response { return parent::store($request); }
#[OA\Put(path: '/produce/hardware-fault-description/{id}', summary: '更新硬件故障描述', tags: ['produce-hardware-fault-description'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:hardware-fault-description:update', description: '更新硬件故障描述')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response { return parent::update($request); }
#[OA\Delete(path: '/produce/hardware-fault-description/{id}', summary: '删除硬件故障描述', tags: ['produce-hardware-fault-description'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:hardware-fault-description:delete', description: '删除硬件故障描述')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response { return parent::destroy($request); }
#[OA\Delete(path: '/produce/hardware-fault-description', summary: '批量删除硬件故障描述', tags: ['produce-hardware-fault-description'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'produce:hardware-fault-description:delete', description: '批量删除硬件故障描述')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response { return parent::destroy($request); }
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\MaterialCategoryService;
use plugin\produce\app\validate\MaterialCategoryValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class MaterialCategoryController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['category_name'],
];
public function __construct(MaterialCategoryService $service, MaterialCategoryValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/material-category', summary: '物料分类列表', tags: ['produce-material-category'])]
#[Permission(code: 'produce:material-category:list', description: '物料分类列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response { return parent::index($request); }
#[OA\Get(path: '/produce/material-category/{id}', summary: '物料分类详情', tags: ['produce-material-category'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material-category:read', description: '物料分类详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response { return parent::show($request); }
#[OA\Post(path: '/produce/material-category', summary: '创建物料分类', tags: ['produce-material-category'])]
#[Permission(code: 'produce:material-category:create', description: '创建物料分类')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response { return parent::store($request); }
#[OA\Put(path: '/produce/material-category/{id}', summary: '更新物料分类', tags: ['produce-material-category'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material-category:update', description: '更新物料分类')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response { return parent::update($request); }
#[OA\Delete(path: '/produce/material-category/{id}', summary: '删除物料分类', tags: ['produce-material-category'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material-category:delete', description: '删除物料分类')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response { return parent::destroy($request); }
#[OA\Delete(path: '/produce/material-category', summary: '批量删除物料分类', tags: ['produce-material-category'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'produce:material-category:delete', description: '批量删除物料分类')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response { return parent::destroy($request); }
}
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\MaterialService;
use plugin\produce\app\validate\MaterialValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class MaterialController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['product_code', 'product_name'],
];
public function __construct(MaterialService $service, MaterialValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/material', summary: '物料列表', tags: ['produce-material'])]
#[Permission(code: 'produce:material:list', description: '物料列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response { return parent::index($request); }
#[OA\Post(path: '/produce/material/import', summary: '导入物料', tags: ['produce-material'])]
#[Permission(code: 'produce:material:import', description: '导入物料')]
#[SimpleResponse(schema: [], example: [])]
public function import(Request $request): \support\Response
{
try {
$file = $request->file('file');
if (!$file) {
return Json::fail('请选择Excel文件');
}
if ($file->getSize() > 5 * 1024 * 1024) {
return Json::fail('Excel文件不能超过5MB');
}
$extension = strtolower((string) $file->getUploadExtension());
if (!in_array($extension, ['xlsx', 'xls', 'csv'], true)) {
return Json::fail('只支持xlsx、xls或csv格式文件');
}
$result = $this->service->importFromExcel($file->getPathname());
return Json::success('导入成功', $result);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(path: '/produce/material/{id}', summary: '物料详情', tags: ['produce-material'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material:read', description: '物料详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response { return parent::show($request); }
#[OA\Post(path: '/produce/material', summary: '创建物料', tags: ['produce-material'])]
#[Permission(code: 'produce:material:create', description: '创建物料')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response { return parent::store($request); }
#[OA\Put(path: '/produce/material/{id}', summary: '更新物料', tags: ['produce-material'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material:update', description: '更新物料')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response { return parent::update($request); }
#[OA\Post(path: '/produce/material/export', summary: '导出物料', tags: ['produce-material'])]
#[Permission(code: 'produce:material:list', description: '导出物料')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response { return parent::export($request); }
#[OA\Delete(path: '/produce/material/{id}', summary: '删除物料', tags: ['produce-material'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:material:delete', description: '删除物料')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response { return parent::destroy($request); }
#[OA\Delete(path: '/produce/material', summary: '批量删除物料', tags: ['produce-material'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'produce:material:delete', description: '批量删除物料')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response { return parent::destroy($request); }
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ProcessService;
use plugin\produce\app\validate\ProcessValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ProcessController extends Crud
{
public function __construct(ProcessService $service, ProcessValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/process', summary: '工序管理列表', tags: ['produce-process'])]
#[Permission(code: 'produce:process:list', description: '工序管理列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/process/{id}',
summary: '工序管理详情',
tags: ['produce-process'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process:read', description: '工序管理详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/process', summary: '创建工序', tags: ['produce-process'])]
#[Permission(code: 'produce:process:create', description: '创建工序')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/process/{id}',
summary: '更新工序',
tags: ['produce-process'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process:update', description: '更新工序')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/process/export', summary: '导出工序', tags: ['produce-process'])]
#[Permission(code: 'produce:process:list', description: '导出工序')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/process/{id}',
summary: '删除工序',
tags: ['produce-process'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process:delete', description: '删除工序')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/process',
summary: '批量删除工序',
tags: ['produce-process'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:process:delete', description: '批量删除工序')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ProcessStepService;
use plugin\produce\app\validate\ProcessStepValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ProcessStepController extends Crud
{
public function __construct(ProcessStepService $service, ProcessStepValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/process-step', summary: '工序步骤列表', tags: ['produce-process-step'])]
#[Permission(code: 'produce:process-step:list', description: '工序步骤列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/process-step/{id}',
summary: '工序步骤详情',
tags: ['produce-process-step'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process-step:read', description: '工序步骤详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/process-step', summary: '创建工序步骤', tags: ['produce-process-step'])]
#[Permission(code: 'produce:process-step:create', description: '创建工序步骤')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/process-step/{id}',
summary: '更新工序步骤',
tags: ['produce-process-step'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process-step:update', description: '更新工序步骤')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/process-step/export', summary: '导出工序步骤', tags: ['produce-process-step'])]
#[Permission(code: 'produce:process-step:list', description: '导出工序步骤')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/process-step/{id}',
summary: '删除工序步骤',
tags: ['produce-process-step'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:process-step:delete', description: '删除工序步骤')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/process-step',
summary: '批量删除工序步骤',
tags: ['produce-process-step'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:process-step:delete', description: '批量删除工序步骤')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ProduceConfigService;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ProduceConfigController
{
public function __construct(protected ProduceConfigService $service)
{
}
#[OA\Get(path: '/produce/config/info', summary: '获取生产配置', tags: ['produce-config'])]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'produce:config:read', description: '查看生产配置')]
public function info(Request $request): Response
{
$groupCode = (string) $request->input('group_code', '');
if ($groupCode === '') {
return Json::fail('配置组代码不能为空');
}
return Json::success('获取成功', $this->service->getConfigByGroup($groupCode));
}
#[OA\Post(path: '/produce/config/save', summary: '保存生产配置', tags: ['produce-config'])]
#[SimpleResponse(description: '保存成功')]
#[Permission(code: 'produce:config:update', description: '保存生产配置')]
public function save(Request $request): Response
{
$payload = $request->post();
$groupCode = (string) ($payload['group_code'] ?? '');
$configs = $payload['configs'] ?? [];
if ($groupCode === '') {
return Json::fail('配置组代码不能为空');
}
if (!is_array($configs) || $configs === []) {
return Json::fail('配置数据不能为空');
}
$this->service->saveConfig($groupCode, $configs);
return Json::success('保存成功');
}
}
@@ -0,0 +1,82 @@
<?php
/**
* Produce 控制器
*/
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use madong\swagger\attribute\AllowAnonymous;
use OpenApi\Attributes as OA;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ProduceController
{
#[OA\Get(
path: '/produce/index',
summary: '获取列表',
tags: ['produce']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'produce:index', description: '列表权限')]
public function index(Request $request): Response
{
return Json::success('获取成功', []);
}
#[OA\Post(
path: '/produce/create',
summary: '创建',
tags: ['produce']
)]
#[SimpleResponse(description: '创建成功')]
#[Permission(code: 'produce:create', description: '创建权限')]
public function create(Request $request): Response
{
return Json::success('创建成功');
}
#[OA\Get(
path: '/produce/info',
summary: '详情',
tags: ['produce']
)]
#[DataResponse(description: '获取成功')]
#[Permission(code: 'produce:info', description: '详情权限')]
public function info(Request $request): Response
{
return Json::success('获取成功', []);
}
#[OA\Post(
path: '/produce/update',
summary: '更新',
tags: ['produce']
)]
#[SimpleResponse(description: '更新成功')]
#[Permission(code: 'produce:update', description: '更新权限')]
public function update(Request $request): Response
{
return Json::success('更新成功');
}
#[OA\Post(
path: '/produce/delete',
summary: '删除',
tags: ['produce']
)]
#[SimpleResponse(description: '删除成功')]
#[Permission(code: 'produce:delete', description: '删除权限')]
public function delete(Request $request): Response
{
return Json::success('删除成功');
}
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ProductRecordService;
use plugin\produce\app\validate\ProductRecordValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ProductRecordController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['process_order_no', 'product_code', 'operator', 'remark'],
];
public function __construct(ProductRecordService $service, ProductRecordValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/product-record', summary: '整机数据列表', tags: ['produce-product-record'])]
#[Permission(code: 'produce:product-record:list', description: '整机数据列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/product-record/{id}',
summary: '整机数据详情',
tags: ['produce-product-record'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:product-record:read', description: '整机数据详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/product-record', summary: '创建整机数据', tags: ['produce-product-record'])]
#[Permission(code: 'produce:product-record:create', description: '创建整机数据')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/product-record/{id}',
summary: '更新整机数据',
tags: ['produce-product-record'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:product-record:update', description: '更新整机数据')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/product-record/export', summary: '导出整机数据', tags: ['produce-product-record'])]
#[Permission(code: 'produce:product-record:list', description: '导出整机数据')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/product-record/{id}',
summary: '删除整机数据',
tags: ['produce-product-record'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:product-record:delete', description: '删除整机数据')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/product-record',
summary: '批量删除整机数据',
tags: ['produce-product-record'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:product-record:delete', description: '批量删除整机数据')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use core\tool\Json;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\RepairRecordService;
use plugin\produce\app\validate\RepairRecordValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class RepairRecordController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['repair_no', 'order_no', 'product_code', 'product_name', 'fault_description', 'operator', 'remark'],
];
public function __construct(RepairRecordService $service, RepairRecordValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/repair-record', summary: '维修数据列表', tags: ['produce-repair-record'])]
#[Permission(code: 'produce:repair-record:list', description: '维修数据列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
try {
[$where, $format, $limit, $field, $order, $page] = $this->selectInput($request);
$methods = [
'select' => 'formatSelect',
'tree' => 'formatTree',
'table_tree' => 'formatTableTree',
'normal' => 'formatNormal',
];
$order = $order ?: 'repaired_at DESC, id DESC';
$total = $this->service->getCount($where);
$list = $this->service->selectList($where, $field, $page, $limit, $order, [], false);
return call_user_func([$this, $methods[$format] ?? 'formatNormal'], $list, $total);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Get(path: '/produce/repair-record/{id}', summary: '维修数据详情', tags: ['produce-repair-record'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-record:read', description: '维修数据详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response { return parent::show($request); }
#[OA\Post(path: '/produce/repair-record', summary: '创建维修数据', tags: ['produce-repair-record'])]
#[Permission(code: 'produce:repair-record:create', description: '创建维修数据')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response { return parent::store($request); }
#[OA\Put(path: '/produce/repair-record/{id}', summary: '更新维修数据', tags: ['produce-repair-record'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-record:update', description: '更新维修数据')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response { return parent::update($request); }
protected function expandExportRows(iterable $items): iterable
{
foreach ($items as $item) {
$record = $item->toArray();
$parts = is_array($record['parts'] ?? null) ? $record['parts'] : [];
if (!$parts) {
yield $record;
continue;
}
foreach ($parts as $part) {
$faultDescriptions = is_array($part['fault_descriptions'] ?? null) ? $part['fault_descriptions'] : [];
$record['order_product'] = implode('/', array_filter([(string) ($record['order_no'] ?? ''), (string) ($record['product_code'] ?? '')]));
$record['part_category'] = (string) ($part['category_name'] ?? '-');
$record['part_code'] = (string) ($part['product_code'] ?? '-');
$record['part_name'] = (string) ($part['product_name'] ?? '-');
$record['part_fault'] = implode('、', array_filter(array_map(
static fn (array $fault): string => (string) ($fault['description'] ?? ''),
$faultDescriptions,
))) ?: '-';
$record['part_quantity'] = (string) ($part['quantity'] ?? 1);
$record['part_source'] = match ($part['source'] ?? '') {
'new' => '新料',
'old', 'after_sale', 'after_sales', 'after-sale' => '售后',
'repair', 'refurbished' => '返修',
default => (string) ($part['source'] ?? '-'),
};
yield $record;
}
}
}
#[OA\Post(path: '/produce/repair-record/export', summary: '导出维修数据', tags: ['produce-repair-record'])]
#[Permission(code: 'produce:repair-record:list', description: '导出维修数据')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(path: '/produce/repair-record/{id}', summary: '删除维修数据', tags: ['produce-repair-record'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-record:delete', description: '删除维修数据')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
try {
$ids = $this->getDeleteIds($request);
if (!$ids) {
return Json::fail('删除参数不能为空');
}
$this->service->transaction(function () use ($ids) {
foreach ($ids as $id) {
$this->service->deleteRecord($id);
}
});
return Json::success('ok', $ids);
} catch (\Throwable $e) {
return Json::fail($e->getMessage());
}
}
#[OA\Delete(path: '/produce/repair-record', summary: '批量删除维修数据', tags: ['produce-repair-record'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'produce:repair-record:delete', description: '批量删除维修数据')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response { return $this->destroy($request); }
}
@@ -0,0 +1,65 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\RepairSettingService;
use plugin\produce\app\validate\RepairSettingValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class RepairSettingController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['code', 'name', 'remark'],
];
public function __construct(RepairSettingService $service, RepairSettingValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/repair-setting', summary: '维修设置列表', tags: ['produce-repair-setting'])]
#[Permission(code: 'produce:repair-setting:list', description: '维修设置列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response { return parent::index($request); }
#[OA\Get(path: '/produce/repair-setting/{id}', summary: '维修设置详情', tags: ['produce-repair-setting'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-setting:read', description: '维修设置详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response { return parent::show($request); }
#[OA\Post(path: '/produce/repair-setting', summary: '创建维修设置', tags: ['produce-repair-setting'])]
#[Permission(code: 'produce:repair-setting:create', description: '创建维修设置')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response { return parent::store($request); }
#[OA\Put(path: '/produce/repair-setting/{id}', summary: '更新维修设置', tags: ['produce-repair-setting'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-setting:update', description: '更新维修设置')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response { return parent::update($request); }
#[OA\Delete(path: '/produce/repair-setting/{id}', summary: '删除维修设置', tags: ['produce-repair-setting'], x: [SchemaConstants::X_PROPERTY_IN => 'id', SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class])]
#[Permission(code: 'produce:repair-setting:delete', description: '删除维修设置')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response { return parent::destroy($request); }
#[OA\Delete(path: '/produce/repair-setting', summary: '批量删除维修设置', tags: ['produce-repair-setting'], x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class])]
#[Permission(code: 'produce:repair-setting:delete', description: '批量删除维修设置')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response { return parent::destroy($request); }
}
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\ScanDataService;
use plugin\produce\app\validate\ScanDataValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class ScanDataController extends Crud
{
protected ?array $selectInputConfig = [
'enable_keyword_search' => true,
'keyword_fields' => ['order_no', 'user_code', 'operator', 'remark'],
];
public function __construct(ScanDataService $service, ScanDataValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/scan-data', summary: '扫码数据列表', tags: ['produce-scan-data'])]
#[Permission(code: 'produce:scan-data:list', description: '扫码数据列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/scan-data/{id}',
summary: '扫码数据详情',
tags: ['produce-scan-data'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:scan-data:read', description: '扫码数据详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/scan-data', summary: '创建扫码数据', tags: ['produce-scan-data'])]
#[Permission(code: 'produce:scan-data:create', description: '创建扫码数据')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/scan-data/{id}',
summary: '更新扫码数据',
tags: ['produce-scan-data'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:scan-data:update', description: '更新扫码数据')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/scan-data/export', summary: '导出扫码数据', tags: ['produce-scan-data'])]
#[Permission(code: 'produce:scan-data:list', description: '导出扫码数据')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/scan-data/{id}',
summary: '删除扫码数据',
tags: ['produce-scan-data'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:scan-data:delete', description: '删除扫码数据')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/scan-data',
summary: '批量删除扫码数据',
tags: ['produce-scan-data'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:scan-data:delete', description: '批量删除扫码数据')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\SpecificRuleService;
use plugin\produce\app\validate\SpecificRuleValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class SpecificRuleController extends Crud
{
public function __construct(SpecificRuleService $service, SpecificRuleValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/specific-rule', summary: '特定规则列表', tags: ['produce-specific-rule'])]
#[Permission(code: 'produce:specific-rule:list', description: '特定规则列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/specific-rule/{id}',
summary: '特定规则详情',
tags: ['produce-specific-rule'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:specific-rule:read', description: '特定规则详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/specific-rule', summary: '创建特定规则', tags: ['produce-specific-rule'])]
#[Permission(code: 'produce:specific-rule:create', description: '创建特定规则')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/specific-rule/{id}',
summary: '更新特定规则',
tags: ['produce-specific-rule'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:specific-rule:update', description: '更新特定规则')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/specific-rule/export', summary: '导出特定规则', tags: ['produce-specific-rule'])]
#[Permission(code: 'produce:specific-rule:list', description: '导出特定规则')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/specific-rule/{id}',
summary: '删除特定规则',
tags: ['produce-specific-rule'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:specific-rule:delete', description: '删除特定规则')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/specific-rule',
summary: '批量删除特定规则',
tags: ['produce-specific-rule'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:specific-rule:delete', description: '批量删除特定规则')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\StatusTransitionService;
use plugin\produce\app\validate\StatusTransitionValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class StatusTransitionController extends Crud
{
public function __construct(StatusTransitionService $service, StatusTransitionValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/status-transition', summary: '状态转换规则列表', tags: ['produce-status-transition'])]
#[Permission(code: 'produce:status-transition:list', description: '状态转换规则列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/status-transition/{id}',
summary: '状态转换规则详情',
tags: ['produce-status-transition'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:status-transition:read', description: '状态转换规则详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/status-transition', summary: '创建状态转换规则', tags: ['produce-status-transition'])]
#[Permission(code: 'produce:status-transition:create', description: '创建状态转换规则')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/status-transition/{id}',
summary: '更新状态转换规则',
tags: ['produce-status-transition'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:status-transition:update', description: '更新状态转换规则')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/status-transition/export', summary: '导出状态转换规则', tags: ['produce-status-transition'])]
#[Permission(code: 'produce:status-transition:list', description: '导出状态转换规则')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/status-transition/{id}',
summary: '删除状态转换规则',
tags: ['produce-status-transition'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:status-transition:delete', description: '删除状态转换规则')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/status-transition',
summary: '批量删除状态转换规则',
tags: ['produce-status-transition'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:status-transition:delete', description: '批量删除状态转换规则')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\StepRelationService;
use plugin\produce\app\validate\StepRelationValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class StepRelationController extends Crud
{
public function __construct(StepRelationService $service, StepRelationValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/step-relation', summary: '步骤关系列表', tags: ['produce-step-relation'])]
#[Permission(code: 'produce:step-relation:list', description: '步骤关系列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/step-relation/{id}',
summary: '步骤关系详情',
tags: ['produce-step-relation'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-relation:read', description: '步骤关系详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/step-relation', summary: '创建步骤关系', tags: ['produce-step-relation'])]
#[Permission(code: 'produce:step-relation:create', description: '创建步骤关系')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/step-relation/{id}',
summary: '更新步骤关系',
tags: ['produce-step-relation'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-relation:update', description: '更新步骤关系')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/step-relation/export', summary: '导出步骤关系', tags: ['produce-step-relation'])]
#[Permission(code: 'produce:step-relation:list', description: '导出步骤关系')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/step-relation/{id}',
summary: '删除步骤关系',
tags: ['produce-step-relation'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-relation:delete', description: '删除步骤关系')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/step-relation',
summary: '批量删除步骤关系',
tags: ['produce-step-relation'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:step-relation:delete', description: '批量删除步骤关系')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\BatchDeleteRequest;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\StepStatusService;
use plugin\produce\app\validate\StepStatusValidate;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class StepStatusController extends Crud
{
public function __construct(StepStatusService $service, StepStatusValidate $validate)
{
$this->service = $service;
$this->validate = $validate;
}
#[OA\Get(path: '/produce/step-status', summary: '步骤状态列表', tags: ['produce-step-status'])]
#[Permission(code: 'produce:step-status:list', description: '步骤状态列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/step-status/{id}',
summary: '步骤状态详情',
tags: ['produce-step-status'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-status:read', description: '步骤状态详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/step-status', summary: '创建步骤状态', tags: ['produce-step-status'])]
#[Permission(code: 'produce:step-status:create', description: '创建步骤状态')]
#[SimpleResponse(schema: [], example: [])]
public function store(Request $request): \support\Response
{
return parent::store($request);
}
#[OA\Put(
path: '/produce/step-status/{id}',
summary: '更新步骤状态',
tags: ['produce-step-status'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-status:update', description: '更新步骤状态')]
#[SimpleResponse(schema: [], example: [])]
public function update(Request $request): \support\Response
{
return parent::update($request);
}
#[OA\Post(path: '/produce/step-status/export', summary: '导出步骤状态', tags: ['produce-step-status'])]
#[Permission(code: 'produce:step-status:list', description: '导出步骤状态')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
#[OA\Delete(
path: '/produce/step-status/{id}',
summary: '删除步骤状态',
tags: ['produce-step-status'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:step-status:delete', description: '删除步骤状态')]
#[SimpleResponse(schema: [], example: [])]
public function destroy(Request $request): \support\Response
{
return parent::destroy($request);
}
#[OA\Delete(
path: '/produce/step-status',
summary: '批量删除步骤状态',
tags: ['produce-step-status'],
x: [SchemaConstants::X_SCHEMA_REQUEST => BatchDeleteRequest::class]
)]
#[Permission(code: 'produce:step-status:delete', description: '批量删除步骤状态')]
#[SimpleResponse(schema: [], example: [])]
public function batchDelete(Request $request): \support\Response
{
return parent::destroy($request);
}
}
@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\controller\Crud;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use app\schema\request\IdRequest;
use madong\swagger\annotation\response\PageResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\TransitionLogService;
use support\annotation\Middleware;
use support\Request;
use WebmanTech\Swagger\DTO\SchemaConstants;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class TransitionLogController extends Crud
{
public function __construct(TransitionLogService $service)
{
$this->service = $service;
}
#[OA\Get(path: '/produce/transition-log', summary: '转换日志列表', tags: ['produce-transition-log'])]
#[Permission(code: 'produce:transition-log:list', description: '转换日志列表')]
#[PageResponse(schema: [], example: [])]
public function index(Request $request): \support\Response
{
return parent::index($request);
}
#[OA\Get(
path: '/produce/transition-log/{id}',
summary: '转换日志详情',
tags: ['produce-transition-log'],
x: [
SchemaConstants::X_PROPERTY_IN => 'id',
SchemaConstants::X_SCHEMA_REQUEST => IdRequest::class,
]
)]
#[Permission(code: 'produce:transition-log:read', description: '转换日志详情')]
#[SimpleResponse(schema: [], example: [])]
public function show(Request $request): \support\Response
{
return parent::show($request);
}
#[OA\Post(path: '/produce/transition-log/export', summary: '导出转换日志', tags: ['produce-transition-log'])]
#[Permission(code: 'produce:transition-log:list', description: '导出转换日志')]
#[SimpleResponse(schema: [], example: [])]
public function export(Request $request): \support\Response
{
return parent::export($request);
}
}
@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\adminapi\controller;
use app\adminapi\middleware\OperationMiddleware;
use app\adminapi\middleware\PermissionMiddleware;
use core\tool\Json;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\attribute\Permission;
use OpenApi\Attributes as OA;
use plugin\produce\app\service\admin\WorkStatisticsService;
use support\annotation\Middleware;
use support\Request;
use support\Response;
#[Middleware(OperationMiddleware::class, PermissionMiddleware::class)]
class WorkStatisticsController
{
public function __construct(protected WorkStatisticsService $service)
{
}
#[OA\Get(path: '/produce/statistics/monthly', summary: '月工作统计', tags: ['produce-statistics'])]
#[DataResponse(description: '获取月工作统计成功')]
#[Permission(code: 'produce:statistics:monthly', description: '月工作统计')]
public function monthly(Request $request): Response
{
return Json::success('获取成功', [
'columns' => $this->monthlyColumns((string) $request->input('month', date('Y-m'))),
'items' => $this->service->monthly($request->all()),
]);
}
#[OA\Get(path: '/produce/statistics/daily', summary: '日工作统计', tags: ['produce-statistics'])]
#[DataResponse(description: '获取日工作统计成功')]
#[Permission(code: 'produce:statistics:daily', description: '日工作统计')]
public function daily(Request $request): Response
{
return Json::success('获取成功', [
'columns' => $this->dailyColumns(),
'items' => $this->service->daily($request->all()),
]);
}
private function monthlyColumns(string $month): array
{
$month = preg_match('/^\d{4}-\d{2}$/', $month) ? $month : date('Y-m');
$days = (int) date('t', strtotime($month . '-01'));
return array_map(static fn (int $day): array => [
'key' => (string) $day,
'label' => str_pad((string) $day, 2, '0', STR_PAD_LEFT),
], range(1, $days));
}
private function dailyColumns(): array
{
return array_map(static fn (int $hour): array => [
'key' => str_pad((string) $hour, 2, '0', STR_PAD_LEFT) . ':00',
'label' => str_pad((string) $hour, 2, '0', STR_PAD_LEFT) . ':00',
], range(0, 23));
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,227 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\appapi\controller;
use madong\swagger\annotation\response\DataResponse;
use madong\swagger\annotation\response\SimpleResponse;
use madong\swagger\attribute\AllowAnonymous;
use OpenApi\Attributes as OA;
use plugin\produce\app\api\controller\DataEntryController as WebDataEntryController;
use plugin\produce\app\service\admin\RepairRecordService;
use support\Request;
use support\Response;
#[OA\Tag(name: 'produce-app')]
final class DataEntryController
{
public function __construct(private WebDataEntryController $delegate)
{
}
#[OA\Get(path: '/produce/process', summary: 'APP 获取工序下拉', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function processOptions(Request $request): Response
{
return $this->delegate->processOptions($request);
}
#[OA\Get(path: '/produce/process-step', summary: 'APP 获取步骤下拉', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function stepOptions(Request $request): Response
{
return $this->delegate->stepOptions($request);
}
#[OA\Get(path: '/produce/step-status', summary: 'APP 获取状态下拉', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function statusOptions(Request $request): Response
{
return $this->delegate->statusOptions($request);
}
#[OA\Get(path: '/produce/material/lookup', summary: 'APP 按商品编码查询物料', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function materialLookup(Request $request): Response
{
return $this->delegate->materialLookup($request);
}
#[OA\Get(path: '/produce/scanapp/material-category', summary: 'APP 获取物料分类选项', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function scanAppMaterialCategory(Request $request): Response
{
return $this->delegate->scanAppMaterialCategory($request);
}
#[OA\Get(path: '/produce/hardware-fault-description/options', summary: 'APP 按物料分类获取硬件故障描述', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function hardwareFaultDescriptionOptions(Request $request): Response
{
return $this->delegate->hardwareFaultDescriptionOptions($request);
}
#[OA\Get(path: '/produce/config/data-entry', summary: 'APP 获取扫码输入页配置', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function dataEntryConfig(Request $request): Response
{
return $this->delegate->dataEntryConfig($request);
}
#[OA\Get(path: '/produce/member-user/check', summary: 'APP 校验用户编码对应会员', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function checkMemberUser(Request $request): Response
{
return $this->delegate->checkMemberUser($request);
}
#[OA\Get(path: '/produce/query/records', summary: 'APP 查询生产记录', tags: ['produce-app'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function queryRecords(Request $request): Response
{
return $this->delegate->queryRecords($request);
}
#[OA\Get(path: '/produce/query/timeline', summary: 'APP 查询订单生产时间线', tags: ['produce-app'])]
#[DataResponse(description: '查询成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function queryTimeline(Request $request): Response
{
return $this->delegate->queryTimeline($request);
}
#[OA\Get(path: '/produce/scan-data', summary: 'APP 最近订单扫码记录', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function recentRecords(Request $request): Response
{
return $this->delegate->recentRecords($request);
}
#[OA\Get(path: '/produce/scan-data/statistics', summary: 'APP 订单扫码统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function statistics(Request $request): Response
{
return $this->delegate->statistics($request);
}
#[OA\Get(path: '/produce/scan-data/user-code-stats', summary: 'APP 用户扫码统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function userCodeStats(Request $request): Response
{
return $this->delegate->userCodeStats($request);
}
#[OA\Post(path: '/produce/scan-data/record', summary: 'APP 提交订单扫码记录', tags: ['produce-app'])]
#[SimpleResponse(description: '录入成功')]
public function record(Request $request): Response
{
return $this->delegate->record($request);
}
#[OA\Get(path: '/produce/exception/settings', summary: 'APP 获取异常设置', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function exceptionSettings(Request $request): Response
{
return $this->delegate->exceptionSettings($request);
}
#[OA\Get(path: '/produce/exception/statistics', summary: 'APP 获取异常统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function exceptionStatistics(Request $request): Response
{
return $this->delegate->exceptionStatistics($request);
}
#[OA\Get(path: '/produce/exception/recent', summary: 'APP 获取最近异常记录', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function recentExceptions(Request $request): Response
{
return $this->delegate->recentExceptions($request);
}
#[OA\Get(path: '/produce/exception/order-check', summary: 'APP 校验异常提报单号', tags: ['produce-app'])]
#[DataResponse(description: '校验成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function checkExceptionOrder(Request $request): Response
{
return $this->delegate->checkExceptionOrder($request);
}
#[OA\Get(path: '/produce/repair-record/tasks', summary: 'APP 获取待维修任务列表', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function repairTasks(Request $request): Response
{
return $this->delegate->repairTasks($request);
}
#[OA\Get(path: '/produce/repair-record/statistics', summary: 'APP 获取维修统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function repairStatistics(Request $request): Response
{
return $this->delegate->repairStatistics($request);
}
#[OA\Get(path: '/produce/exception/repair-task', summary: 'APP 获取维修异常任务', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function repairTask(Request $request): Response
{
return $this->delegate->repairTask($request);
}
#[OA\Post(path: '/produce/repair-record/record', summary: 'APP 提交维修记录', tags: ['produce-app'])]
#[SimpleResponse(description: '提交成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function repairRecord(Request $request, RepairRecordService $repairRecordService): Response
{
return $this->delegate->repairRecord($request, $repairRecordService);
}
#[OA\Post(path: '/produce/exception/record', summary: 'APP 提交异常记录', tags: ['produce-app'])]
#[SimpleResponse(description: '提交成功')]
#[AllowAnonymous(requireToken: false, requirePermission: false, description: 'APP 公共接口')]
public function exceptionRecord(Request $request): Response
{
return $this->delegate->exceptionRecord($request);
}
#[OA\Get(path: '/produce/product-record/recent', summary: 'APP 最近整机记录', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function productRecentRecords(Request $request): Response
{
return $this->delegate->productRecentRecords($request);
}
#[OA\Get(path: '/produce/product-record/statistics', summary: 'APP 整机统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function productStatistics(Request $request): Response
{
return $this->delegate->productStatistics($request);
}
#[OA\Get(path: '/produce/product-record/user-code-stats', summary: 'APP 整机用户统计', tags: ['produce-app'])]
#[DataResponse(description: '获取成功')]
public function productUserCodeStats(Request $request): Response
{
return $this->delegate->productUserCodeStats($request);
}
#[OA\Post(path: '/produce/product-record/record', summary: 'APP 提交整机记录', tags: ['produce-app'])]
#[SimpleResponse(description: '录入成功')]
public function productRecord(Request $request): Response
{
return $this->delegate->productRecord($request);
}
}
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\ExceptionRecord;
class ExceptionRecordDao extends BaseDao
{
protected function setModel(): string
{
return ExceptionRecord::class;
}
protected function getKeywordFields(): array
{
return ['order_no', 'user_code', 'reporter', 'type_name', 'reason_name', 'issue_name', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['createds']));
$order = $order ?: 'reported_at desc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$keyword = '%' . $where['keyword'] . '%';
$query->where(function (Builder $builder) use ($keyword) {
$builder->where('order_no', 'like', $keyword)
->orWhere('user_code', 'like', $keyword)
->orWhere('reporter', 'like', $keyword)
->orWhere('type_name', 'like', $keyword)
->orWhere('reason_name', 'like', $keyword)
->orWhere('issue_name', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
foreach (['type_code', 'reason_code', 'issue_code', 'status'] as $field) {
if (($where[$field] ?? '') !== '') {
$query->where($field, $where[$field]);
}
}
if (!empty($where['reported_at_range']) && is_array($where['reported_at_range']) && count($where['reported_at_range']) === 2) {
$query->whereBetween('reported_at', [
strtotime((string) $where['reported_at_range'][0] . ' 00:00:00'),
strtotime((string) $where['reported_at_range'][1] . ' 23:59:59'),
]);
}
return $query;
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use plugin\produce\app\model\ExceptionSetting;
class ExceptionSettingDao extends BaseDao
{
protected function setModel(): string
{
return ExceptionSetting::class;
}
protected function getKeywordFields(): array
{
return ['code', 'name', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['parent', 'createds']));
$order = $order ?: 'setting_type asc, parent_id asc, sort asc, id asc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function get($id, ?array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model
{
$with = array_unique(array_merge($with ?? [], ['parent', 'createds']));
return parent::get($id, $field, $with, $order, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$keyword = '%' . $where['keyword'] . '%';
$query->where(function (Builder $builder) use ($keyword) {
$builder->where('code', 'like', $keyword)
->orWhere('name', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
foreach (['setting_type', 'parent_id', 'is_enabled'] as $field) {
if (($where[$field] ?? '') !== '') {
$query->where($field, $where[$field]);
}
}
return $query;
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use plugin\produce\app\model\HardwareFaultDescription;
class HardwareFaultDescriptionDao extends BaseDao
{
protected function setModel(): string
{
return HardwareFaultDescription::class;
}
protected function getKeywordFields(): array
{
return ['description', 'remark'];
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use plugin\produce\app\model\MaterialCategory;
class MaterialCategoryDao extends BaseDao
{
protected function setModel(): string
{
return MaterialCategory::class;
}
protected function getKeywordFields(): array
{
return ['category_name'];
}
}
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\Material;
class MaterialDao extends BaseDao
{
protected function setModel(): string
{
return Material::class;
}
protected function getKeywordFields(): array
{
return ['product_code', 'product_name'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
return parent::selectList($where, $field, $page, $limit, $order ?: 'id desc', array_unique(array_merge($with, ['category'])), $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$keyword = '%' . $where['keyword'] . '%';
$query->where(function (Builder $builder) use ($keyword) {
$builder->where('product_code', 'like', $keyword)
->orWhere('product_name', 'like', $keyword);
});
}
if (($where['category_id'] ?? '') !== '') {
$query->where('category_id', $where['category_id']);
}
return $query;
}
}
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\Process;
class ProcessDao extends BaseDao
{
protected function setModel(): string
{
return Process::class;
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$query = $this->selectModel($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
$query->withCount(['steps as step_count']);
if ($page > 0 && $limit > 0) {
return $query->paginate($limit, ['*'], 'page', $page)->getCollection();
}
return $query->get();
}
protected function getKeywordFields(): array
{
return ['process_code', 'process_name', 'description'];
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\ProcessStep;
class ProcessStepDao extends BaseDao
{
protected function setModel(): string
{
return ProcessStep::class;
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
return parent::selectList($where, $field, $page, $limit, $order, ['process'], $search, $withoutScopes);
}
protected function getKeywordFields(): array
{
return ['step_code', 'step_name', 'description'];
}
}
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use plugin\produce\app\model\ProduceConfig;
class ProduceConfigDao extends BaseDao
{
protected function setModel(): string
{
return ProduceConfig::class;
}
protected function getKeywordFields(): array
{
return ['group_code', 'code', 'name', 'remark'];
}
public function getByGroupCode(string $groupCode): array
{
$items = $this->getModel()::query()
->where('group_code', $groupCode)
->orderBy('sort')
->orderBy('id')
->get();
$result = [];
foreach ($items as $item) {
$result[$item->code] = $item->content;
}
return $result;
}
public function batchSave(string $groupCode, array $configs): bool
{
$timestamp = time();
foreach ($configs as $config) {
$this->getModel()::query()->updateOrCreate(
[
'group_code' => $groupCode,
'code' => (string) ($config['code'] ?? ''),
],
[
'name' => (string) ($config['name'] ?? ''),
'content' => is_scalar($config['content'] ?? null) || $config['content'] === null
? ($config['content'] ?? null)
: json_encode($config['content'], JSON_UNESCAPED_UNICODE),
'sort' => (int) ($config['sort'] ?? 0),
'remark' => (string) ($config['remark'] ?? ''),
'updated_at' => $timestamp,
]
);
}
return true;
}
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\ProductRecord;
class ProductRecordDao extends BaseDao
{
protected function setModel(): string
{
return ProductRecord::class;
}
protected function getKeywordFields(): array
{
return ['process_order_no', 'product_code', 'operator', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['process', 'step', 'status']));
$order = $order ?: 'created_at desc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$query->where(function (Builder $builder) use ($where) {
$keyword = '%' . $where['keyword'] . '%';
$builder->where('process_order_no', 'like', $keyword)
->orWhere('product_code', 'like', $keyword)
->orWhere('operator', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
if (($where['process_id'] ?? '') !== '') {
$query->where('process_id', $where['process_id']);
}
if (($where['step_id'] ?? '') !== '') {
$query->where('step_id', $where['step_id']);
}
if (($where['status_id'] ?? '') !== '') {
$query->where('status_id', $where['status_id']);
}
if (($where['scan_result'] ?? '') !== '') {
$query->where('scan_result', $where['scan_result']);
}
if (!empty($where['created_time_range']) && is_array($where['created_time_range']) && count($where['created_time_range']) === 2) {
$query->whereBetween('created_at', [strtotime((string) $where['created_time_range'][0]), strtotime((string) $where['created_time_range'][1])]);
}
return $query;
}
}
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use plugin\produce\app\model\RepairRecord;
class RepairRecordDao extends BaseDao
{
protected function setModel(): string
{
return RepairRecord::class;
}
protected function getKeywordFields(): array
{
return ['repair_no', 'order_no', 'product_code', 'product_name', 'fault_description', 'operator', 'remark'];
}
}
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use plugin\produce\app\model\RepairSetting;
class RepairSettingDao extends BaseDao
{
protected function setModel(): string
{
return RepairSetting::class;
}
protected function getKeywordFields(): array
{
return ['code', 'name', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['parent', 'createds']));
$order = $order ?: 'setting_type asc, parent_id asc, sort asc, id asc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function get($id, ?array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model
{
$with = array_unique(array_merge($with ?? [], ['parent', 'createds']));
return parent::get($id, $field, $with, $order, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$keyword = '%' . $where['keyword'] . '%';
$query->where(function (Builder $builder) use ($keyword) {
$builder->where('code', 'like', $keyword)
->orWhere('name', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
foreach (['setting_type', 'parent_id', 'is_enabled'] as $field) {
if (($where[$field] ?? '') !== '') {
$query->where($field, $where[$field]);
}
}
return $query;
}
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\ScanData;
class ScanDataDao extends BaseDao
{
protected function setModel(): string
{
return ScanData::class;
}
protected function getKeywordFields(): array
{
return ['order_no', 'user_code', 'operator', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['process', 'step', 'status']));
$order = $order ?: 'scan_time desc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$query->where(function (Builder $builder) use ($where) {
$keyword = '%' . $where['keyword'] . '%';
$builder->where('order_no', 'like', $keyword)
->orWhere('user_code', 'like', $keyword)
->orWhere('operator', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
if (($where['process_id'] ?? '') !== '') {
$query->where('process_id', $where['process_id']);
}
if (($where['step_id'] ?? '') !== '') {
$query->where('step_id', $where['step_id']);
}
if (($where['status_id'] ?? '') !== '') {
$query->where('status_id', $where['status_id']);
}
if (($where['scan_result'] ?? '') !== '') {
$query->where('scan_result', $where['scan_result']);
}
if (!empty($where['scan_time_range']) && is_array($where['scan_time_range']) && count($where['scan_time_range']) === 2) {
$query->whereBetween('scan_time', [strtotime((string) $where['scan_time_range'][0]), strtotime((string) $where['scan_time_range'][1])]);
}
return $query;
}
}
@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use plugin\produce\app\model\SpecificRule;
class SpecificRuleDao extends BaseDao
{
protected function setModel(): string
{
return SpecificRule::class;
}
protected function getKeywordFields(): array
{
return ['rule_name', 'rule_pattern', 'remark'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['createds']));
$order = $order ?: 'sort asc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function get($id, ?array $field = null, ?array $with = [], string $order = '', ?array $withoutScopes = null): ?Model
{
$with = array_unique(array_merge($with ?? [], ['createds']));
return parent::get($id, $field, $with, $order, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = $this->query();
if (($where['keyword'] ?? '') !== '') {
$query->where(function (Builder $builder) use ($where) {
$keyword = '%' . $where['keyword'] . '%';
$builder->where('rule_name', 'like', $keyword)
->orWhere('rule_pattern', 'like', $keyword)
->orWhere('remark', 'like', $keyword);
});
}
if (($where['rule_type'] ?? '') !== '') {
$query->where('rule_type', $where['rule_type']);
}
if (($where['is_enabled'] ?? '') !== '') {
$query->where('is_enabled', $where['is_enabled']);
}
return $query;
}
}
@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Builder;
use plugin\produce\app\model\StatusTransition;
class StatusTransitionDao extends BaseDao
{
protected function setModel(): string
{
return StatusTransition::class;
}
protected function getKeywordFields(): array
{
return ['trigger_event', 'description'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['step', 'fromStatus', 'toStatus']));
$order = $order ?: 'priority desc, sort asc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = parent::handleSearch($where, $isPage);
if (($where['step_id'] ?? '') !== '') {
$query->where('step_id', $where['step_id']);
}
if (($where['from_status_id'] ?? '') !== '') {
$query->where('from_status_id', $where['from_status_id']);
}
if (($where['to_status_id'] ?? '') !== '') {
$query->where('to_status_id', $where['to_status_id']);
}
if (($where['trigger_event'] ?? '') !== '') {
$query->where('trigger_event', $where['trigger_event']);
}
return $query;
}
public function getAutoTransitionsByStep(int|string $stepId, int|string $fromStatusId): array
{
return $this->getModel()::query()
->where('step_id', $stepId)
->where('from_status_id', $fromStatusId)
->where('auto_trigger', 1)
->where('status', 1)
->orderBy('priority', 'desc')
->orderBy('sort', 'asc')
->with(['toStatus:id,status_name'])
->get()
->map(function ($item) {
return [
'id' => $item->id,
'step_id' => $item->step_id,
'from_status_id' => $item->from_status_id,
'to_status_id' => $item->to_status_id,
'to_status_name' => $item->toStatus->status_name ?? '',
'trigger_event' => $item->trigger_event,
'event_config' => $item->event_config,
'priority' => $item->priority,
];
})
->toArray();
}
public function getAutoTransitions(int|string $stepId, int|string $fromStatusId, string $triggerEvent): array
{
return $this->getModel()::query()
->where('step_id', $stepId)
->where('from_status_id', $fromStatusId)
->where('trigger_event', $triggerEvent)
->where('auto_trigger', 1)
->where('status', 1)
->orderBy('priority', 'desc')
->orderBy('sort', 'asc')
->with(['toStatus:id,status_name'])
->get()
->map(function ($item) {
return [
'id' => $item->id,
'step_id' => $item->step_id,
'from_status_id' => $item->from_status_id,
'to_status_id' => $item->to_status_id,
'to_status_name' => $item->toStatus->status_name ?? '',
'trigger_event' => $item->trigger_event,
'event_config' => $item->event_config,
'priority' => $item->priority,
];
})
->toArray();
}
}
@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Builder;
use plugin\produce\app\model\StepRelation;
class StepRelationDao extends BaseDao
{
protected function setModel(): string
{
return StepRelation::class;
}
protected function getKeywordFields(): array
{
return ['relation_type', 'description'];
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
$with = array_unique(array_merge($with, ['fromStep', 'toStep']));
$order = $order ?: 'priority asc, sort asc, id desc';
return parent::selectList($where, $field, $page, $limit, $order, $with, $search, $withoutScopes);
}
public function handleSearch(array $where, bool $isPage = true): Builder
{
$query = parent::handleSearch($where, $isPage);
if (($where['from_step_id'] ?? '') !== '') {
$query->where('from_step_id', $where['from_step_id']);
}
if (($where['to_step_id'] ?? '') !== '') {
$query->where('to_step_id', $where['to_step_id']);
}
if (($where['relation_type'] ?? '') !== '') {
$query->where('relation_type', $where['relation_type']);
}
return $query;
}
public function getPreviousSteps(int|string $currentStepId): array
{
return $this->getModel()::query()
->where('to_step_id', $currentStepId)
->where('status', 1)
->orderBy('priority', 'asc')
->orderBy('sort', 'asc')
->get(['id', 'from_step_id', 'to_step_id', 'relation_type'])
->toArray();
}
}
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace plugin\produce\app\dao;
use core\base\BaseDao;
use Illuminate\Database\Eloquent\Collection;
use plugin\produce\app\model\StepStatus;
class StepStatusDao extends BaseDao
{
protected function setModel(): string
{
return StepStatus::class;
}
public function selectList(
array $where,
string|array $field = '*',
int $page = 0,
int $limit = 0,
string $order = '',
array $with = [],
bool $search = false,
?array $withoutScopes = null
): ?Collection {
return parent::selectList($where, $field, $page, $limit, $order, ['step'], $search, $withoutScopes);
}
protected function getKeywordFields(): array
{
return ['status_code', 'status_name', 'description'];
}
}

Some files were not shown because too many files have changed in this diff Show More