feat(produce): 完善统计、菜单与扫码搜索

This commit is contained in:
2026-09-01 20:45:40 +08:00
parent 32f1486f5e
commit 793f140a45
26 changed files with 591 additions and 41 deletions
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Schema\Builder;
return new class {
public function up(Builder $schema): void
{
if (!$schema->hasTable('sys_menu')) {
return;
}
$icons = [
'article' => 'ant-design:read-outlined',
'article:index' => 'ant-design:file-text-outlined',
'article:category:index' => 'ant-design:folder-open-outlined',
'article:tag:index' => 'ant-design:tags-outlined',
'article:recycle:index' => 'ant-design:delete-outlined',
];
foreach ($icons as $code => $icon) {
$schema->getConnection()->table('sys_menu')->where('app', 'article')->where('code', $code)->update([
'icon' => $icon,
'updated_at' => time(),
]);
}
}
public function down(Builder $schema): void
{
}
};
@@ -11,7 +11,7 @@ return [
'code' => 'article', 'code' => 'article',
'app' => 'article', 'app' => 'article',
'path' => '/article', 'path' => '/article',
'icon' => 'plugin', 'icon' => 'ant-design:read-outlined',
'children' => [ 'children' => [
[ [
'name' => '内容中心', 'name' => '内容中心',
@@ -19,6 +19,7 @@ return [
'app' => 'article', 'app' => 'article',
'path' => '/article/index', 'path' => '/article/index',
'component' => '/article/content/index', 'component' => '/article/content/index',
'icon' => 'ant-design:file-text-outlined',
], ],
[ [
'name' => '内容分类', 'name' => '内容分类',
@@ -26,6 +27,7 @@ return [
'app' => 'article', 'app' => 'article',
'path' => '/article/category', 'path' => '/article/category',
'component' => '/article/category/index', 'component' => '/article/category/index',
'icon' => 'ant-design:folder-open-outlined',
], ],
[ [
'name' => '标签管理', 'name' => '标签管理',
@@ -33,6 +35,7 @@ return [
'app' => 'article', 'app' => 'article',
'path' => '/article/tag', 'path' => '/article/tag',
'component' => '/article/tag/index', 'component' => '/article/tag/index',
'icon' => 'ant-design:tags-outlined',
], ],
[ [
'name' => '内容回收站', 'name' => '内容回收站',
@@ -40,6 +43,7 @@ return [
'app' => 'article', 'app' => 'article',
'path' => '/article/recycle', 'path' => '/article/recycle',
'component' => '/article/recycle/index', 'component' => '/article/recycle/index',
'icon' => 'ant-design:delete-outlined',
], ],
], ],
], ],
@@ -41,14 +41,13 @@ class ScanDataDao extends BaseDao
{ {
$query = $this->query(); $query = $this->query();
if (($where['keyword'] ?? '') !== '') { foreach (['order_no', 'user_code', 'operator', 'remark'] as $field) {
$query->where(function (Builder $builder) use ($where) { $value = trim((string) ($where['LIKE_' . $field] ?? ''));
$keyword = '%' . $where['keyword'] . '%'; if ($value === '') {
$builder->where('order_no', 'like', $keyword) continue;
->orWhere('user_code', 'like', $keyword) }
->orWhere('operator', 'like', $keyword)
->orWhere('remark', 'like', $keyword); $query->where($field, 'like', '%' . $value . '%');
});
} }
if (($where['process_id'] ?? '') !== '') { if (($where['process_id'] ?? '') !== '') {
@@ -5,11 +5,18 @@ declare(strict_types=1);
namespace plugin\produce\app\service\admin; namespace plugin\produce\app\service\admin;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use plugin\produce\app\model\ExceptionRecord;
use plugin\produce\app\model\Process;
use plugin\produce\app\model\ProcessStep;
use plugin\produce\app\model\ProductRecord; use plugin\produce\app\model\ProductRecord;
use plugin\produce\app\model\ProduceConfig;
use plugin\produce\app\model\RepairRecord;
use plugin\produce\app\model\ScanData; use plugin\produce\app\model\ScanData;
class WorkStatisticsService class WorkStatisticsService
{ {
private ?array $repairFlow = null;
public function monthly(array $params): array public function monthly(array $params): array
{ {
$month = $this->normalizeMonth($params['month'] ?? null); $month = $this->normalizeMonth($params['month'] ?? null);
@@ -44,9 +51,70 @@ class WorkStatisticsService
$rows = array_merge($rows, $this->queryProductRecord($params, $start, $end, $periodExpression)->get()->all()); $rows = array_merge($rows, $this->queryProductRecord($params, $start, $end, $periodExpression)->get()->all());
} }
if ($dataType === 'all' || $dataType === 'exception') {
$rows = array_merge($rows, $this->queryExceptionRecord($params, $start, $end, $periodExpression)->get()->all());
}
if ($dataType === 'all' || $dataType === 'repair') {
$rows = array_merge($rows, $this->queryRepairRecord($params, $start, $end, $periodExpression)->get()->all());
}
return $rows; return $rows;
} }
private function queryExceptionRecord(array $params, int $start, int $end, callable $periodExpression): Builder
{
$model = new ExceptionRecord();
$connection = $model->getConnection();
$table = $model->getTable();
$processTable = 'produce_process';
$stepTable = 'produce_process_step';
$query = $connection->table($table)
->leftJoin($processTable, $processTable . '.id', '=', $table . '.process_id')
->leftJoin($stepTable, $stepTable . '.id', '=', $table . '.step_id')
->whereBetween($table . '.reported_at', [$start, $end])
->whereNull($table . '.deleted_at');
$this->applyStatisticsFilters($query, $params, $table, $processTable, $stepTable, 'reporter');
return $query->select([$table . '.user_code', $table . '.reporter as operator'])
->selectRaw($periodExpression($connection->getTablePrefix() . $table . '.reported_at') . ' as period')
->selectRaw($connection->getTablePrefix() . $processTable . '.process_name as process_name')
->selectRaw($connection->getTablePrefix() . $stepTable . '.step_name as step_name')
->selectRaw('COUNT(*) as amount')
->groupBy('user_code', 'operator', 'process_name', 'step_name', 'period');
}
private function queryRepairRecord(array $params, int $start, int $end, callable $periodExpression): Builder
{
$model = new RepairRecord();
$exceptionModel = new ExceptionRecord();
$connection = $model->getConnection();
$table = $model->getTable();
$exceptionTable = $exceptionModel->getTable();
$flow = $this->getRepairFlow();
$query = $connection->table($table)
->leftJoin($exceptionTable, $exceptionTable . '.id', '=', $table . '.exception_id')
->whereBetween($table . '.repaired_at', [$start, $end])
->whereNull($table . '.deleted_at');
if (!$this->matchesRepairFlow($params, $flow)) {
$query->whereRaw('1 = 0');
}
if (($params['user_code'] ?? '') !== '') {
$query->where($exceptionTable . '.user_code', 'like', '%' . $params['user_code'] . '%');
}
if (($params['operator'] ?? '') !== '') {
$query->where($table . '.operator', 'like', '%' . $params['operator'] . '%');
}
return $query->select([$exceptionTable . '.user_code', $table . '.operator'])
->selectRaw($periodExpression($connection->getTablePrefix() . $table . '.repaired_at') . ' as period')
->selectRaw('? as process_name', [$flow['process_name']])
->selectRaw('? as step_name', [$flow['step_name']])
->selectRaw('COUNT(*) as amount')
->groupBy('user_code', 'operator', 'process_name', 'step_name', 'period');
}
private function queryScanData(array $params, int $start, int $end, callable $periodExpression): Builder private function queryScanData(array $params, int $start, int $end, callable $periodExpression): Builder
{ {
$scanDataModel = new ScanData(); $scanDataModel = new ScanData();
@@ -61,7 +129,7 @@ class WorkStatisticsService
->whereBetween($scanDataTable . '.scan_time', [$start, $end]) ->whereBetween($scanDataTable . '.scan_time', [$start, $end])
->whereNull($scanDataTable . '.deleted_at'); ->whereNull($scanDataTable . '.deleted_at');
foreach (['process_id', 'step_id', 'scan_result'] as $field) { foreach (['process_id', 'step_id'] as $field) {
if (($params[$field] ?? '') !== '') { if (($params[$field] ?? '') !== '') {
$query->where($scanDataTable . '.' . $field, $params[$field]); $query->where($scanDataTable . '.' . $field, $params[$field]);
} }
@@ -139,6 +207,65 @@ class WorkStatisticsService
->orderBy('step_name'); ->orderBy('step_name');
} }
private function applyStatisticsFilters(Builder $query, array $params, string $table, string $processTable, string $stepTable, string $operatorField): void
{
foreach (['process_id', 'step_id', 'scan_result'] as $field) {
if (($params[$field] ?? '') !== '') {
$query->where($table . '.' . $field, $params[$field]);
}
}
if (($params['user_code'] ?? '') !== '') {
$query->where($table . '.user_code', 'like', '%' . $params['user_code'] . '%');
}
if (($params['operator'] ?? '') !== '') {
$query->where($table . '.' . $operatorField, 'like', '%' . $params['operator'] . '%');
}
foreach (['process_name' => $processTable . '.process_name', 'step_name' => $stepTable . '.step_name'] as $field => $column) {
if (($params[$field] ?? '') !== '') {
$query->where($column, 'like', '%' . $params[$field] . '%');
}
}
}
private function matchesRepairFlow(array $params, array $flow): bool
{
if (($params['process_id'] ?? '') !== '' && (string) $params['process_id'] !== (string) $flow['process_id']) {
return false;
}
if (($params['step_id'] ?? '') !== '' && (string) $params['step_id'] !== (string) $flow['step_id']) {
return false;
}
if (($params['process_name'] ?? '') !== '' && !str_contains($flow['process_name'], (string) $params['process_name'])) {
return false;
}
return ($params['step_name'] ?? '') === '' || str_contains($flow['step_name'], (string) $params['step_name']);
}
private function getRepairFlow(): array
{
if ($this->repairFlow !== null) {
return $this->repairFlow;
}
$configs = ProduceConfig::query()
->whereIn('group_code', ['basic_setting', 'repair_flow'])
->whereIn('code', ['repair_process_id', 'repair_step_id'])
->get(['group_code', 'code', 'content'])
->groupBy('group_code');
$basic = $configs->get('basic_setting', collect())->pluck('content', 'code');
$flow = $configs->get('repair_flow', collect())->pluck('content', 'code');
$processId = (int) (($basic['repair_process_id'] ?? '') ?: ($flow['repair_process_id'] ?? 0));
$stepId = (int) (($basic['repair_step_id'] ?? '') ?: ($flow['repair_step_id'] ?? 0));
$process = $processId > 0 ? Process::query()->withTrashed()->find($processId) : null;
$step = $stepId > 0 ? ProcessStep::query()->withTrashed()->find($stepId) : null;
return $this->repairFlow = [
'process_id' => $processId,
'step_id' => $stepId,
'process_name' => (string) ($process?->process_name ?? '维修'),
'step_name' => (string) ($step?->step_name ?? '维修'),
];
}
private function formatRows(iterable $rows, int $periodCount, callable $periodLabel): array private function formatRows(iterable $rows, int $periodCount, callable $periodLabel): array
{ {
$grouped = []; $grouped = [];
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Schema\Builder;
return new class {
public function up(Builder $schema): void
{
if (!$schema->hasTable('sys_menu')) {
return;
}
$icons = [
'produce' => 'ant-design:dashboard-outlined',
'produce:material-category:index' => 'ant-design:tags-outlined',
'produce:material:index' => 'ant-design:database-outlined',
'produce:config:index' => 'ant-design:setting-outlined',
'produce:process:index' => 'ant-design:apartment-outlined',
'produce:process-step:index' => 'ant-design:ordered-list-outlined',
'produce:step-status:index' => 'ant-design:flag-outlined',
'produce:step-relation:index' => 'ant-design:share-alt-outlined',
'produce:status-transition:index' => 'ant-design:swap-outlined',
'produce:product-record:index' => 'ant-design:desktop-outlined',
'produce:scan-data:index' => 'ant-design:qrcode-outlined',
'produce:transition-log:index' => 'ant-design:swap-right-outlined',
'produce:exception:index' => 'ant-design:warning-outlined',
'produce:exception-data:list' => 'ant-design:exception-outlined',
'produce:exception-setting:list' => 'ant-design:setting-outlined',
'produce:exception-analysis:read' => 'ant-design:pie-chart-outlined',
'produce:repair:index' => 'ant-design:tool-outlined',
'produce:repair-record:list' => 'ant-design:tool-outlined',
'produce:repair-setting:list' => 'ant-design:setting-outlined',
'produce:hardware-fault-description:list' => 'ant-design:bug-outlined',
'produce:statistics:index' => 'ant-design:bar-chart-outlined',
'produce:statistics:monthly' => 'ant-design:calendar-outlined',
'produce:statistics:daily' => 'ant-design:clock-circle-outlined',
];
foreach ($icons as $code => $icon) {
$schema->getConnection()->table('sys_menu')->where('app', 'produce')->where('code', $code)->update([
'icon' => $icon,
'updated_at' => time(),
]);
}
}
public function down(Builder $schema): void
{
}
};
+23 -4
View File
@@ -11,7 +11,7 @@ return [
'code' => 'produce', 'code' => 'produce',
'app' => 'produce', 'app' => 'produce',
'path' => '/produce', 'path' => '/produce',
'icon' => 'plugin', 'icon' => 'ant-design:dashboard-outlined',
'children' => [ 'children' => [
[ [
'name' => '物料分类', 'name' => '物料分类',
@@ -19,6 +19,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/material-category', 'path' => '/produce/material-category',
'component' => '/produce/material-category/index', 'component' => '/produce/material-category/index',
'icon' => 'ant-design:tags-outlined',
], ],
[ [
'name' => '物料库', 'name' => '物料库',
@@ -26,6 +27,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/material', 'path' => '/produce/material',
'component' => '/produce/material/index', 'component' => '/produce/material/index',
'icon' => 'ant-design:database-outlined',
], ],
[ [
'name' => '生产配置', 'name' => '生产配置',
@@ -33,6 +35,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/config', 'path' => '/produce/config',
'component' => '/produce/config/index', 'component' => '/produce/config/index',
'icon' => 'ant-design:setting-outlined',
], ],
[ [
'name' => '工序管理', 'name' => '工序管理',
@@ -40,6 +43,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/process', 'path' => '/produce/process',
'component' => '/produce/process/index', 'component' => '/produce/process/index',
'icon' => 'ant-design:apartment-outlined',
], ],
[ [
'name' => '工序步骤', 'name' => '工序步骤',
@@ -47,6 +51,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/process-step', 'path' => '/produce/process-step',
'component' => '/produce/process-step/index', 'component' => '/produce/process-step/index',
'icon' => 'ant-design:ordered-list-outlined',
], ],
[ [
'name' => '步骤状态', 'name' => '步骤状态',
@@ -54,6 +59,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/step-status', 'path' => '/produce/step-status',
'component' => '/produce/step-status/index', 'component' => '/produce/step-status/index',
'icon' => 'ant-design:flag-outlined',
], ],
[ [
'name' => '步骤关系', 'name' => '步骤关系',
@@ -61,6 +67,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/step-relation', 'path' => '/produce/step-relation',
'component' => '/produce/step-relation/index', 'component' => '/produce/step-relation/index',
'icon' => 'ant-design:share-alt-outlined',
], ],
[ [
'name' => '状态转换', 'name' => '状态转换',
@@ -68,6 +75,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/status-transition', 'path' => '/produce/status-transition',
'component' => '/produce/status-transition/index', 'component' => '/produce/status-transition/index',
'icon' => 'ant-design:swap-outlined',
], ],
[ [
'name' => '整机数据', 'name' => '整机数据',
@@ -75,6 +83,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/product-record', 'path' => '/produce/product-record',
'component' => '/produce/product-record/index', 'component' => '/produce/product-record/index',
'icon' => 'ant-design:desktop-outlined',
], ],
[ [
'name' => '扫码数据', 'name' => '扫码数据',
@@ -82,6 +91,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/scan-data', 'path' => '/produce/scan-data',
'component' => '/produce/scan-data/index', 'component' => '/produce/scan-data/index',
'icon' => 'ant-design:qrcode-outlined',
], ],
[ [
'name' => '转换日志', 'name' => '转换日志',
@@ -89,13 +99,14 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/transition-log', 'path' => '/produce/transition-log',
'component' => '/produce/transition-log/index', 'component' => '/produce/transition-log/index',
'icon' => 'ant-design:swap-right-outlined',
], ],
[ [
'name' => '异常管理', 'name' => '异常管理',
'code' => 'produce:exception:index', 'code' => 'produce:exception:index',
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/exception', 'path' => '/produce/exception',
'icon' => 'warning', 'icon' => 'ant-design:warning-outlined',
'children' => [ 'children' => [
[ [
'name' => '异常数据', 'name' => '异常数据',
@@ -103,6 +114,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/exception-data', 'path' => '/produce/exception-data',
'component' => '/produce/exception/data/index', 'component' => '/produce/exception/data/index',
'icon' => 'ant-design:exception-outlined',
], ],
[ [
'name' => '异常设置', 'name' => '异常设置',
@@ -110,6 +122,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/exception-setting', 'path' => '/produce/exception-setting',
'component' => '/produce/exception/setting/index', 'component' => '/produce/exception/setting/index',
'icon' => 'ant-design:setting-outlined',
], ],
[ [
'name' => '数据分析', 'name' => '数据分析',
@@ -117,6 +130,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/exception-analysis', 'path' => '/produce/exception-analysis',
'component' => '/produce/exception/analysis/index', 'component' => '/produce/exception/analysis/index',
'icon' => 'ant-design:pie-chart-outlined',
], ],
], ],
], ],
@@ -125,7 +139,7 @@ return [
'code' => 'produce:repair:index', 'code' => 'produce:repair:index',
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/repair', 'path' => '/produce/repair',
'icon' => 'tools', 'icon' => 'ant-design:tool-outlined',
'children' => [ 'children' => [
[ [
'name' => '维修数据', 'name' => '维修数据',
@@ -133,6 +147,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/repair/repair-data', 'path' => '/produce/repair/repair-data',
'component' => '/produce/repair/repair-data/index', 'component' => '/produce/repair/repair-data/index',
'icon' => 'ant-design:tool-outlined',
], ],
[ [
'name' => '维修设置', 'name' => '维修设置',
@@ -140,6 +155,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/repair/repair-setting', 'path' => '/produce/repair/repair-setting',
'component' => '/produce/repair/repair-setting/index', 'component' => '/produce/repair/repair-setting/index',
'icon' => 'ant-design:setting-outlined',
], ],
[ [
'name' => '硬件故障描述', 'name' => '硬件故障描述',
@@ -147,6 +163,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/repair/hardware-fault-description', 'path' => '/produce/repair/hardware-fault-description',
'component' => '/produce/repair/hardware-fault-description/index', 'component' => '/produce/repair/hardware-fault-description/index',
'icon' => 'ant-design:bug-outlined',
], ],
], ],
], ],
@@ -155,7 +172,7 @@ return [
'code' => 'produce:statistics:index', 'code' => 'produce:statistics:index',
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/statistics', 'path' => '/produce/statistics',
'icon' => 'bar-chart-2', 'icon' => 'ant-design:bar-chart-outlined',
'children' => [ 'children' => [
[ [
'name' => '月工作统计', 'name' => '月工作统计',
@@ -163,6 +180,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/statistics/monthly', 'path' => '/produce/statistics/monthly',
'component' => '/produce/statistics/monthly/index', 'component' => '/produce/statistics/monthly/index',
'icon' => 'ant-design:calendar-outlined',
], ],
[ [
'name' => '日工作统计', 'name' => '日工作统计',
@@ -170,6 +188,7 @@ return [
'app' => 'produce', 'app' => 'produce',
'path' => '/produce/statistics/daily', 'path' => '/produce/statistics/daily',
'component' => '/produce/statistics/daily/index', 'component' => '/produce/statistics/daily/index',
'icon' => 'ant-design:clock-circle-outlined',
], ],
], ],
], ],
@@ -18,5 +18,52 @@ const [BasicCrud] = useCrud({
<style scoped> <style scoped>
.produce-scan-data-page { .produce-scan-data-page {
height: 100%; height: 100%;
padding: 16px;
box-sizing: border-box;
background: var(--el-bg-color-page);
}
.produce-scan-data-page :deep(.art-search-bar) {
padding: 14px 16px;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
box-shadow: none;
}
.produce-scan-data-page :deep(.art-search-bar .el-form-item) {
margin-bottom: 0;
}
.produce-scan-data-page :deep(.art-search-bar .el-row) {
gap: 12px;
}
.produce-scan-data-page :deep(.art-search-bar .el-col:not(.action-column)) {
flex: 0 0 var(--search-item-width);
min-width: 0;
max-width: var(--search-item-width);
}
.produce-scan-data-page :deep(.art-search-bar .action-column) {
flex: 0 0 auto;
max-width: none;
}
.produce-scan-data-page :deep(.art-search-bar .el-input),
.produce-scan-data-page :deep(.art-search-bar .el-select),
.produce-scan-data-page :deep(.art-search-bar .el-date-editor) {
width: 100%;
}
.produce-scan-data-page :deep(.art-table-card) {
margin-top: 12px;
}
@media (max-width: 640px) {
.produce-scan-data-page :deep(.art-search-bar .el-col:not(.action-column)) {
flex-basis: 100%;
min-width: 0;
max-width: 100%;
}
} }
</style> </style>
@@ -22,21 +22,49 @@ export const crudSchema = (): CrudSchema => {
viewAuth: 'produce:scan-data:read', viewAuth: 'produce:scan-data:read',
rowKey: 'id', rowKey: 'id',
searchFormSchema: { searchFormSchema: {
labelWidth: '100px', labelWidth: 0,
gutter: 0,
span: 6,
isExpand: true,
showExpandButton: false,
schema: [ schema: [
{ {
prop: 'keyword', prop: 'LIKE_order_no',
label: '关键字', label: '',
searchWidth: '170px',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
clearable: true, clearable: true,
placeholder: '请输入订单号/用户编码/操作人员/备注', placeholder: '请输入订单号',
},
colSpan: 6,
},
{
prop: 'LIKE_user_code',
label: '',
searchWidth: '160px',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入用户编码',
},
colSpan: 6,
},
{
prop: 'LIKE_operator',
label: '',
searchWidth: '180px',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入操作人员',
}, },
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'process_id', prop: 'process_id',
label: '工序名称', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/process', api: '/produce/process',
@@ -49,7 +77,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'step_id', prop: 'step_id',
label: '步骤名称', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/process-step', api: '/produce/process-step',
@@ -62,7 +91,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'status_id', prop: 'status_id',
label: '步骤状态', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/step-status', api: '/produce/step-status',
@@ -74,18 +104,20 @@ export const crudSchema = (): CrudSchema => {
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'LIKE_operator', prop: 'LIKE_remark',
label: '操作人员', label: '',
searchWidth: '160px',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
clearable: true, clearable: true,
placeholder: '请输入操作人员', placeholder: '请输入备注',
}, },
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'scan_result', prop: 'scan_result',
label: '扫码结果', label: '',
searchWidth: '170px',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
clearable: true, clearable: true,
@@ -99,7 +131,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'scan_time_range', prop: 'scan_time_range',
label: '扫码时间', label: '',
searchWidth: '280px',
component: 'DatePicker', component: 'DatePicker',
componentProps: { componentProps: {
type: 'daterange', type: 'daterange',
@@ -108,7 +141,7 @@ export const crudSchema = (): CrudSchema => {
startPlaceholder: '开始日期', startPlaceholder: '开始日期',
endPlaceholder: '结束日期', endPlaceholder: '结束日期',
}, },
colSpan: 12, colSpan: 6,
}, },
], ],
}, },
@@ -12,6 +12,8 @@
<el-option label="全部数据" value="all" /> <el-option label="全部数据" value="all" />
<el-option label="订单数据" value="order" /> <el-option label="订单数据" value="order" />
<el-option label="整机数据" value="machine" /> <el-option label="整机数据" value="machine" />
<el-option label="异常提报" value="exception" />
<el-option label="维修记录" value="repair" />
</el-select> </el-select>
<el-input <el-input
v-model="filters.user_code" v-model="filters.user_code"
@@ -12,6 +12,8 @@
<el-option label="全部数据" value="all" /> <el-option label="全部数据" value="all" />
<el-option label="订单数据" value="order" /> <el-option label="订单数据" value="order" />
<el-option label="整机数据" value="machine" /> <el-option label="整机数据" value="machine" />
<el-option label="异常提报" value="exception" />
<el-option label="维修记录" value="repair" />
</el-select> </el-select>
<el-input <el-input
v-model="filters.user_code" v-model="filters.user_code"
@@ -0,0 +1,21 @@
# 异常维修工序步骤统计
## 问题修复
日工作统计与月工作统计此前仅聚合订单扫码和整机录入,未包含异常提报、维修记录,导致这两类数据无法按工序步骤显示。
## 调整内容
- 异常提报按记录保存的工序、步骤及提报时间统计,每条异常记录计 1 次。
- 维修记录按后台设置的维修工序、步骤及维修时间统计,每条维修记录计 1 次。
- 统计数据类型增加“异常提报”和“维修记录”;选择“全部数据”时一并汇总。
- 工序、步骤筛选条件同样作用于新增的两类统计数据。
## 文件同步
- Admin 模板源 `backend/plugin/produce/resource/template/admin/views/produce/statistics/` 已修改。
- Admin 运行目录 `frontend/admin/src/apps/produce/views/produce/statistics/` 已同步。
## 验证
- `php -l backend/plugin/produce/app/service/admin/WorkStatisticsService.php` 通过。
@@ -0,0 +1,13 @@
# 生产菜单图标修复
## 调整内容
- 为生产管理的根菜单、一级菜单和展开后的子菜单补齐图标。
- 图标统一使用项目图标选择器支持的 `ant-design:*` 格式。
- 修正旧的无前缀图标值,避免图标库无法解析。
- 新增幂等迁移,将已安装插件的 `sys_menu` 菜单图标同步更新;新安装插件则直接读取菜单模板的最新图标。
## 验证
- 所有选用的 Ant Design 图标已在 Admin 图标库中确认存在。
- 菜单模板与图标更新迁移均通过 PHP 语法检查。
@@ -0,0 +1,12 @@
# 文章菜单图标修复
## 调整内容
- 为文章管理、内容中心、内容分类、标签管理、内容回收站补齐 Ant Design 图标。
- 新增幂等迁移,同步已安装文章插件的 `sys_menu` 图标数据。
- 已单独执行该迁移并核验当前菜单图标。
## 验证
- 文章插件菜单共 5 条,空图标 0 条,无效图标 0 条。
- 菜单配置与迁移文件均通过 PHP 语法检查。
@@ -0,0 +1,14 @@
# 扫码数据搜索区域样式统一
## 调整内容
- 以月度统计页面的搜索工具栏为基准,将扫码数据页面改为无字段标签、白底描边的横向筛选布局。
- 保留原有关键字、工序、步骤、状态、操作人员、扫码结果和扫码时间筛选条件及请求参数。
- 搜索条件默认完整展开,小屏幕时按栅格自动换行。
- 为通用 CRUD 搜索栏补充栅格宽度和始终展开配置的传递能力。
- 已同步生产插件模板源与 Admin 运行目录的扫码数据页面文件。
## 验证
- 模板源与运行目录的 `index.vue`、`schemas/index.tsx` 内容一致。
- 前端类型检查待在完整 Admin 依赖环境中执行。
@@ -0,0 +1,12 @@
# 扫码数据搜索控件宽度调整
## 调整内容
- 搜索控件由等分撑满整行改为固定 180px 宽度。
- 筛选项之间保持 12px 间距,查询与重置按钮紧随筛选项排列。
- 样式与生产统计月报的搜索区域保持一致,小屏幕时自动换行。
- 已同步生产插件模板源与 Admin 运行目录。
## 验证
- 模板源与运行目录的扫码数据页面及筛选配置内容一致。
@@ -0,0 +1,11 @@
# 扫码数据搜索控件自适应
## 调整内容
- 搜索控件宽度改为自适应:最小 180px、最大 240px,并可在可用空间内自动伸缩。
- 视口宽度小于 640px 时,每个筛选项占满一行,避免横向滚动或内容挤压。
- 已同步生产插件模板源与 Admin 运行目录。
## 验证
- 模板源与运行目录扫码数据页面的自适应样式内容一致。
@@ -0,0 +1,13 @@
# 扫码数据搜索按字段宽度调整
## 调整内容
- 不再对所有搜索控件使用统一宽度。
- 按提示文字及控件内容分别设置紧凑宽度:关键字 320px、工序/步骤/状态 160px、人员 180px、扫码结果 170px、日期范围 280px。
- 通用搜索栏新增 `searchWidth` 配置,以 CSS 变量把指定宽度应用到对应搜索项,不影响控件参数传递。
- 窄屏下仍自动改为单列展示。
- 已同步生产插件模板源与 Admin 运行目录。
## 验证
- 模板源与运行目录的扫码数据页面及筛选配置内容一致。
@@ -0,0 +1,13 @@
# 扫码数据独立搜索条件
## 调整内容
- 将原有“订单号/用户编码/操作人员/备注”聚合搜索拆分为订单号、用户编码、操作人员、备注四个独立输入框。
- 前端独立提交 `LIKE_order_no`、`LIKE_user_code`、`LIKE_operator`、`LIKE_remark` 参数。
- 后端按每个独立条件执行模糊查询;多个条件同时填写时按交集过滤。
- 已同步生产插件模板源与 Admin 运行目录。
## 验证
- 已检查前端模板源与运行目录搜索配置一致。
- 已对扫码数据 DAO 执行 PHP 语法检查。
@@ -0,0 +1,11 @@
# 扫码数据用户编码操作人员相邻
## 调整内容
- 将扫码数据搜索栏中的“操作人员”移动到“用户编码”之后。
- 订单号、用户编码、操作人员、工序等其他查询字段及参数保持不变。
- 已同步生产插件模板源与 Admin 运行目录。
## 验证
- 模板源与运行目录的搜索项顺序一致。
@@ -18,5 +18,52 @@ const [BasicCrud] = useCrud({
<style scoped> <style scoped>
.produce-scan-data-page { .produce-scan-data-page {
height: 100%; height: 100%;
padding: 16px;
box-sizing: border-box;
background: var(--el-bg-color-page);
}
.produce-scan-data-page :deep(.art-search-bar) {
padding: 14px 16px;
background: var(--el-bg-color);
border: 1px solid var(--el-border-color-light);
box-shadow: none;
}
.produce-scan-data-page :deep(.art-search-bar .el-form-item) {
margin-bottom: 0;
}
.produce-scan-data-page :deep(.art-search-bar .el-row) {
gap: 12px;
}
.produce-scan-data-page :deep(.art-search-bar .el-col:not(.action-column)) {
flex: 0 0 var(--search-item-width);
min-width: 0;
max-width: var(--search-item-width);
}
.produce-scan-data-page :deep(.art-search-bar .action-column) {
flex: 0 0 auto;
max-width: none;
}
.produce-scan-data-page :deep(.art-search-bar .el-input),
.produce-scan-data-page :deep(.art-search-bar .el-select),
.produce-scan-data-page :deep(.art-search-bar .el-date-editor) {
width: 100%;
}
.produce-scan-data-page :deep(.art-table-card) {
margin-top: 12px;
}
@media (max-width: 640px) {
.produce-scan-data-page :deep(.art-search-bar .el-col:not(.action-column)) {
flex-basis: 100%;
min-width: 0;
max-width: 100%;
}
} }
</style> </style>
@@ -22,21 +22,49 @@ export const crudSchema = (): CrudSchema => {
viewAuth: 'produce:scan-data:read', viewAuth: 'produce:scan-data:read',
rowKey: 'id', rowKey: 'id',
searchFormSchema: { searchFormSchema: {
labelWidth: '100px', labelWidth: 0,
gutter: 0,
span: 6,
isExpand: true,
showExpandButton: false,
schema: [ schema: [
{ {
prop: 'keyword', prop: 'LIKE_order_no',
label: '关键字', label: '',
searchWidth: '170px',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
clearable: true, clearable: true,
placeholder: '请输入订单号/用户编码/操作人员/备注', placeholder: '请输入订单号',
},
colSpan: 6,
},
{
prop: 'LIKE_user_code',
label: '',
searchWidth: '160px',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入用户编码',
},
colSpan: 6,
},
{
prop: 'LIKE_operator',
label: '',
searchWidth: '180px',
component: 'Input',
componentProps: {
clearable: true,
placeholder: '请输入操作人员',
}, },
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'process_id', prop: 'process_id',
label: '工序名称', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/process', api: '/produce/process',
@@ -49,7 +77,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'step_id', prop: 'step_id',
label: '步骤名称', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/process-step', api: '/produce/process-step',
@@ -62,7 +91,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'status_id', prop: 'status_id',
label: '步骤状态', label: '',
searchWidth: '160px',
component: 'ApiSelect', component: 'ApiSelect',
componentProps: { componentProps: {
api: '/produce/step-status', api: '/produce/step-status',
@@ -74,18 +104,20 @@ export const crudSchema = (): CrudSchema => {
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'LIKE_operator', prop: 'LIKE_remark',
label: '操作人员', label: '',
searchWidth: '160px',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
clearable: true, clearable: true,
placeholder: '请输入操作人员', placeholder: '请输入备注',
}, },
colSpan: 6, colSpan: 6,
}, },
{ {
prop: 'scan_result', prop: 'scan_result',
label: '扫码结果', label: '',
searchWidth: '170px',
component: 'Select', component: 'Select',
componentProps: { componentProps: {
clearable: true, clearable: true,
@@ -99,7 +131,8 @@ export const crudSchema = (): CrudSchema => {
}, },
{ {
prop: 'scan_time_range', prop: 'scan_time_range',
label: '扫码时间', label: '',
searchWidth: '280px',
component: 'DatePicker', component: 'DatePicker',
componentProps: { componentProps: {
type: 'daterange', type: 'daterange',
@@ -108,7 +141,7 @@ export const crudSchema = (): CrudSchema => {
startPlaceholder: '开始日期', startPlaceholder: '开始日期',
endPlaceholder: '结束日期', endPlaceholder: '结束日期',
}, },
colSpan: 12, colSpan: 6,
}, },
], ],
}, },
@@ -12,6 +12,8 @@
<el-option label="全部数据" value="all" /> <el-option label="全部数据" value="all" />
<el-option label="订单数据" value="order" /> <el-option label="订单数据" value="order" />
<el-option label="整机数据" value="machine" /> <el-option label="整机数据" value="machine" />
<el-option label="异常提报" value="exception" />
<el-option label="维修记录" value="repair" />
</el-select> </el-select>
<el-input <el-input
v-model="filters.user_code" v-model="filters.user_code"
@@ -12,6 +12,8 @@
<el-option label="全部数据" value="all" /> <el-option label="全部数据" value="all" />
<el-option label="订单数据" value="order" /> <el-option label="订单数据" value="order" />
<el-option label="整机数据" value="machine" /> <el-option label="整机数据" value="machine" />
<el-option label="异常提报" value="exception" />
<el-option label="维修记录" value="repair" />
</el-select> </el-select>
<el-input <el-input
v-model="filters.user_code" v-model="filters.user_code"
@@ -18,6 +18,7 @@
:md="getColSpan(item.span, 'md')" :md="getColSpan(item.span, 'md')"
:lg="getColSpan(item.span, 'lg')" :lg="getColSpan(item.span, 'lg')"
:xl="getColSpan(item.span, 'xl')" :xl="getColSpan(item.span, 'xl')"
:style="getItemStyle(item)"
> >
<ElFormItem <ElFormItem
:prop="item.key" :prop="item.key"
@@ -188,6 +189,8 @@
items: SearchFormItem[] items: SearchFormItem[]
/** 每列的宽度(基于 24 格布局) */ /** 每列的宽度(基于 24 格布局) */
span?: number span?: number
/** 搜索项的紧凑宽度 */
width?: string
/** 表单控件间隙 */ /** 表单控件间隙 */
gutter?: number gutter?: number
/** 展开/收起 */ /** 展开/收起 */
@@ -239,7 +242,17 @@
*/ */
const isExpanded = ref(props.defaultExpanded) const isExpanded = ref(props.defaultExpanded)
const rootProps = ['label', 'labelWidth', 'key', 'type', 'hidden', 'span', 'slots'] const rootProps = [
'label',
'labelWidth',
'key',
'type',
'hidden',
'span',
'width',
'searchWidth',
'slots'
]
const getProps = (item: SearchFormItem) => { const getProps = (item: SearchFormItem) => {
if (item.props) return item.props if (item.props) return item.props
@@ -248,6 +261,12 @@
return props return props
} }
const getItemStyle = (item: SearchFormItem) => {
if (!item.width) return undefined
return { '--search-item-width': item.width }
}
// 获取插槽 // 获取插槽
const getSlots = (item: SearchFormItem) => { const getSlots = (item: SearchFormItem) => {
if (!item.slots) return {} if (!item.slots) return {}
+6
View File
@@ -4,6 +4,10 @@ export interface FormSchema {
labelPosition?: 'left' | 'right' | 'top' labelPosition?: 'left' | 'right' | 'top'
gutter?: number // 栅格间隔 gutter?: number // 栅格间隔
labelWidth?: string | number labelWidth?: string | number
// 每列的栅格宽度
span?: number
// 是否始终展开所有搜索条件
isExpand?: boolean
schema?: FormItemSchema[] schema?: FormItemSchema[]
// 显示默认操作按钮 // 显示默认操作按钮
showDefaultActions?: boolean showDefaultActions?: boolean
@@ -24,6 +28,8 @@ export interface FormSchema {
} }
export interface FormItemSchema { export interface FormItemSchema {
labelWidth?: string | number labelWidth?: string | number
// 搜索栏中按内容设置的控件宽度
searchWidth?: string
// 表单项标签 // 表单项标签
label: string label: string
// 表单项属性名 // 表单项属性名
@@ -4,6 +4,11 @@
:show-reset="formSchemaRef.showResetButton" :show-reset="formSchemaRef.showResetButton"
:show-search="formSchemaRef.showSubmitButton" :show-search="formSchemaRef.showSubmitButton"
:show-expand="formSchemaRef.showExpandButton" :show-expand="formSchemaRef.showExpandButton"
:label-position="formSchemaRef.labelPosition"
:label-width="formSchemaRef.labelWidth"
:gutter="formSchemaRef.gutter"
:span="formSchemaRef.span"
:is-expand="formSchemaRef.isExpand"
:items="cItems" :items="cItems"
v-model="searchFormState" v-model="searchFormState"
@search="handleSearch" @search="handleSearch"
@@ -63,6 +68,7 @@
key: item.prop, key: item.prop,
type: item.component, type: item.component,
span: item.colSpan, span: item.colSpan,
width: item.searchWidth,
props: item.componentProps props: item.componentProps
} }
}) || [] }) || []