feat: 完善仓库建筑绑定、质检预警规则与大屏展示
这个提交包含在:
@@ -99,6 +99,26 @@ class Granary extends Backend
|
||||
{
|
||||
$data = $this->request->post();
|
||||
if (!empty($data)) {
|
||||
$content = isset($data['content']) && is_array($data['content']) ? $data['content'] : [];
|
||||
$position = isset($content['screenPosition']) ? $content['screenPosition'] : '';
|
||||
$positions = \app\common\library\GranaryScreen::extraPositions();
|
||||
if ($position !== '' && !isset($positions[$position])) {
|
||||
return json(['code' => 400, 'msg' => '请选择有效的大屏建筑位置']);
|
||||
}
|
||||
if ($position !== '') {
|
||||
if (strpos($data['storage'], '习水') !== false) {
|
||||
return json(['code' => 400, 'msg' => '此建筑位置属于中心库区']);
|
||||
}
|
||||
foreach ($this->model->field('id,content')->select() as $existing) {
|
||||
$saved = $existing['content'];
|
||||
if (!is_array($saved)) $saved = json_decode($saved, true);
|
||||
if ((string)$existing['id'] !== (string)(isset($data['id']) ? $data['id'] : 0)
|
||||
&& isset($saved['screenPosition']) && $saved['screenPosition'] === $position) {
|
||||
return json(['code' => 400, 'msg' => '此建筑已绑定仓库,请编辑对应仓库或选择其他位置']);
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['content'] = $content;
|
||||
$id = $this->model->where(['num' => $data['num']])->value('id');
|
||||
if (isset($data['granary_type']) && in_array(intval($data['granary_type']), [1, 2])) {
|
||||
$data['granary_type'] = intval($data['granary_type']);
|
||||
|
||||
@@ -113,6 +113,10 @@ class Testing extends Backend
|
||||
if (!empty($data)) {
|
||||
$content = $data['content'];
|
||||
$date = $data['record_date'];
|
||||
$ruleError = \app\common\library\QualityRule::validate($content);
|
||||
if ($ruleError !== '') {
|
||||
return json(['code' => 400, 'msg' => $ruleError]);
|
||||
}
|
||||
|
||||
// 添加
|
||||
if (empty($data['id'])) {
|
||||
|
||||
@@ -270,6 +270,14 @@
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="大屏建筑位置:">
|
||||
<el-select v-model="formData.screenPosition" style="width: 440px;">
|
||||
<el-option label="保留现有建筑位置" value=""></el-option>
|
||||
<el-option label="中心库 · 左上新增仓库(42号仓左侧)" value="center_upper_left"></el-option>
|
||||
<el-option label="中心库 · 右下新增仓库(41号仓左侧)" value="center_lower_right"></el-option>
|
||||
</el-select>
|
||||
<div style="color: #909399;">新仓库请选择对应建筑;现有标记可直接修改仓号,保存后大屏自动同步。</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="仓房类型:" prop="granary_type">
|
||||
<el-radio-group v-model="formData.granary_type" @change="changeGranaryType" size="small">
|
||||
<el-radio-button :label="1">立式油罐 (油类)</el-radio-button>
|
||||
|
||||
@@ -35,6 +35,16 @@
|
||||
.search-select .el-input--suffix .el-input__inner {
|
||||
width: 130px;
|
||||
}
|
||||
|
||||
.quality-direction-toggle {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.quality-direction-toggle .el-button {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
</style>
|
||||
<div id="vue" v-cloak>
|
||||
<div class="screen-container">
|
||||
@@ -214,39 +224,63 @@
|
||||
<el-input type="number" placeholder="排序" style="width: 330px;" v-model="formData.sort"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="检测参数:" prop="content">
|
||||
<p>数值指标请选择“越大越好”或“越小越好”。绿色为达标、黄色为预警、红色为警告;等于阈值时归入较低一级,保存后对大屏生效。</p>
|
||||
<el-table :data="typeList[formData.type]['content']" border highlight-current-row>
|
||||
<el-table-column prop="num" width="60" label="序号" show-overflow-tooltip
|
||||
align="center"></el-table-column>
|
||||
<el-table-column prop="name" label="检测项目" show-overflow-tooltip
|
||||
align="center"></el-table-column>
|
||||
<el-table-column prop="unit" width="160" label="单位" show-overflow-tooltip
|
||||
<el-table-column prop="unit" width="110" label="单位" show-overflow-tooltip
|
||||
align="center">
|
||||
<template scope="scope">
|
||||
<el-input style="width: 130px;" placeholder="单位" size="small" maxlength="15" show-word-limit
|
||||
<el-input style="width: 85px;" placeholder="单位" size="small" maxlength="15" show-word-limit
|
||||
v-model="typeList[formData.type]['content'][scope.$index].unit"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="val" width="200" label="值" show-overflow-tooltip
|
||||
<el-table-column prop="val" width="130" label="值" show-overflow-tooltip
|
||||
align="center">
|
||||
<template scope="scope">
|
||||
<el-input style="width: 150px;" placeholder="请您输入值" size="small" maxlength="50" show-word-limit
|
||||
<el-input style="width: 110px;" placeholder="请您输入值" size="small" maxlength="50" show-word-limit
|
||||
v-model="typeList[formData.type]['content'][scope.$index].val"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="warning" width="230" label="预警值" show-overflow-tooltip
|
||||
<el-table-column prop="warning" width="140" label="预警值" show-overflow-tooltip
|
||||
align="center">
|
||||
<template scope="scope">
|
||||
<el-input style="width: 170px;" placeholder="请您输入预警值" size="small" maxlength="50" show-word-limit
|
||||
<el-input style="width: 120px;" placeholder="请您输入预警值" size="small" maxlength="50" show-word-limit
|
||||
v-model="typeList[formData.type]['content'][scope.$index].warning"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="alert" width="230" label="警戒值" show-overflow-tooltip
|
||||
<el-table-column prop="alert" width="140" label="警告值" show-overflow-tooltip
|
||||
align="center">
|
||||
<template scope="scope">
|
||||
<el-input style="width: 170px;" placeholder="请您输入警戒值" size="small" maxlength="50" show-word-limit
|
||||
<el-input style="width: 120px;" placeholder="请您输入警告值" size="small" maxlength="50" show-word-limit
|
||||
v-model="typeList[formData.type]['content'][scope.$index].alert"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column width="270" fixed="right" label="指标偏好与颜色规则" align="left">
|
||||
<template scope="scope">
|
||||
<template v-if="isNumericRule(scope.row)">
|
||||
<el-button-group class="quality-direction-toggle" :aria-label="'选择' + scope.row.name + '的指标偏好'">
|
||||
<el-button size="mini" :type="ruleDirection(scope.row) === 'lower' ? 'success' : 'default'"
|
||||
:plain="ruleDirection(scope.row) !== 'lower'"
|
||||
:aria-pressed="ruleDirection(scope.row) === 'lower'"
|
||||
@click="setRuleDirection(scope.row, 'lower')">越大越好</el-button>
|
||||
<el-button size="mini" :type="ruleDirection(scope.row) === 'upper' ? 'success' : 'default'"
|
||||
:plain="ruleDirection(scope.row) !== 'upper'"
|
||||
:aria-pressed="ruleDirection(scope.row) === 'upper'"
|
||||
@click="setRuleDirection(scope.row, 'upper')">越小越好</el-button>
|
||||
</el-button-group>
|
||||
<div v-if="ruleError(scope.row)" style="color: #c0392b; margin-top: 6px;">{{ruleError(scope.row)}}</div>
|
||||
<div v-else style="font-size: 12px; line-height: 22px; margin-top: 6px;">
|
||||
<div style="color: #16844a;">绿 · {{ruleRanges(scope.row)[0]}}</div>
|
||||
<div style="color: #946200;">黄 · {{ruleRanges(scope.row)[1]}}</div>
|
||||
<div style="color: #c0392b;">红 · {{ruleRanges(scope.row)[2]}}</div>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else>文字指标,按检测结果判定</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="alert" width="100" label="图表" show-overflow-tooltip
|
||||
align="center">
|
||||
<template scope="scope">
|
||||
@@ -274,7 +308,7 @@
|
||||
width="98%"
|
||||
>
|
||||
<div class="form">
|
||||
<el-form ref="form" :model="formData" label-width="110px">
|
||||
<el-form ref="detailForm" :model="formData" label-width="110px">
|
||||
<el-form-item label="所属仓库:" prop="gid">
|
||||
<div>{{detail.granary.name}}</div>
|
||||
</el-form-item>
|
||||
@@ -308,9 +342,20 @@
|
||||
<el-table-column prop="warning" width="230" label="预警值" show-overflow-tooltip
|
||||
align="center">
|
||||
</el-table-column>
|
||||
<el-table-column prop="alert" width="230" label="警戒值" show-overflow-tooltip
|
||||
<el-table-column prop="alert" width="230" label="警告值" show-overflow-tooltip
|
||||
align="center">
|
||||
</el-table-column>
|
||||
<el-table-column width="250" label="指标偏好与颜色规则" align="left">
|
||||
<template scope="scope">
|
||||
<template v-if="isNumericRule(scope.row)">
|
||||
<div>{{ruleDirection(scope.row) === 'lower' ? '● 越大越好' : '● 越小越好'}}</div>
|
||||
<div style="color: #16844a;">绿 · {{ruleRanges(scope.row)[0]}}</div>
|
||||
<div style="color: #946200;">黄 · {{ruleRanges(scope.row)[1]}}</div>
|
||||
<div style="color: #c0392b;">红 · {{ruleRanges(scope.row)[2]}}</div>
|
||||
</template>
|
||||
<span v-else>文字指标,按检测结果判定</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -326,4 +371,4 @@
|
||||
<script src="__CDN__/assets/element-ui-2.15.14/js/index.js"></script>
|
||||
<script src="__CDN__/assets/tool/libs/moment.js"></script>
|
||||
<script src="/assets/js/sweetalert.min.js"></script>
|
||||
<script src="__CDN__/assets/js/jquery-2.1.1.min.js"></script>
|
||||
<script src="__CDN__/assets/js/jquery-2.1.1.min.js"></script>
|
||||
|
||||
@@ -52,8 +52,23 @@ class Index extends Api
|
||||
if (!empty($list) && count($list) > 0) {
|
||||
$list = collection($list)->toArray();
|
||||
|
||||
// Load saved rules in two queries rather than one query per granary.
|
||||
$tests = TestingModel::with(['testingParams'])
|
||||
->where('gid', 'in', array_column($list, 'id'))
|
||||
->order('test_date DESC, id DESC')->select();
|
||||
$latestTests = [];
|
||||
foreach ($tests as $test) {
|
||||
$test = $test->toArray();
|
||||
$testKey = $test['gid'] . ':' . $test['type'];
|
||||
if (!isset($latestTests[$testKey])) {
|
||||
$latestTests[$testKey] = $test;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($list as &$v) {
|
||||
$v['varieties'] = getVarietiesInfo($v['varieties']);
|
||||
$testKey = $v['id'] . ':' . $v['varieties']['type'];
|
||||
$v['quality_test'] = isset($latestTests[$testKey]) ? $latestTests[$testKey] : null;
|
||||
$v['harvest_year'] = is_numeric($v['harvest_year']) ? date('Y-m-d', $v['harvest_year']) : $v['harvest_year'];
|
||||
$v['enter_time'] = is_numeric($v['enter_time']) ? date('Y-m-d', $v['enter_time']) : $v['enter_time'];
|
||||
$v['create_time'] = is_numeric($v['create_time']) ? date('Y-m-d', $v['create_time']) : $v['create_time'];
|
||||
@@ -72,6 +87,12 @@ class Index extends Api
|
||||
}
|
||||
|
||||
$coords = $this->getSiteCoords($site);
|
||||
if ($site !== 'xishui' && is_array($list)) {
|
||||
foreach ($list as &$granary) {
|
||||
$granary['screen_coord'] = \app\common\library\GranaryScreen::resolve($granary, $coords);
|
||||
}
|
||||
unset($granary);
|
||||
}
|
||||
|
||||
$this->success('获取成功', [
|
||||
'list' => $list,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
/** 建筑位置独立于仓号,修改仓号不会移动大屏标记。 */
|
||||
class GranaryScreen
|
||||
{
|
||||
public static function extraPositions()
|
||||
{
|
||||
return [
|
||||
'center_upper_left' => ['x' => 46.8, 'y' => 28.4],
|
||||
'center_lower_right' => ['x' => 58.8, 'y' => 34.1],
|
||||
];
|
||||
}
|
||||
|
||||
public static function resolve($item, $coords)
|
||||
{
|
||||
$content = is_array($item['content']) ? $item['content'] : json_decode($item['content'], true);
|
||||
$position = isset($content['screenPosition']) ? $content['screenPosition'] : '';
|
||||
$extra = self::extraPositions();
|
||||
if (isset($extra[$position])) {
|
||||
return $extra[$position];
|
||||
}
|
||||
// 历史记录按数据库 id 锚定原建筑,避免改号后套用另一栋仓库的位置。
|
||||
$originalNumbers = [27 => 30, 29 => 31, 30 => 32, 31 => 10, 33 => 42,
|
||||
34 => 33, 36 => 35, 37 => 36, 38 => 12, 39 => 39, 40 => 40,
|
||||
42 => 41, 43 => 14, 44 => 37, 45 => 15, 46 => 44, 47 => 16,
|
||||
48 => 17, 49 => 18, 50 => 38, 51 => 43];
|
||||
$num = isset($originalNumbers[$item['id']]) ? $originalNumbers[$item['id']] : null;
|
||||
return $num !== null && isset($coords[$num]) ? $coords[$num] : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace app\common\library;
|
||||
|
||||
/** Numeric warning rules shared by the editor and persistence layer. */
|
||||
class QualityRule
|
||||
{
|
||||
public static function direction($item)
|
||||
{
|
||||
if (isset($item['direction']) && in_array($item['direction'], ['upper', 'lower'], true)) {
|
||||
return $item['direction'];
|
||||
}
|
||||
$warning = isset($item['warning']) ? $item['warning'] : null;
|
||||
$alert = isset($item['alert']) ? $item['alert'] : null;
|
||||
return is_numeric($warning) && is_numeric($alert) && $warning > $alert ? 'lower' : 'upper';
|
||||
}
|
||||
|
||||
public static function validate($content)
|
||||
{
|
||||
if (!is_array($content) || empty($content)) {
|
||||
return '请填写检测参数';
|
||||
}
|
||||
foreach ($content as $item) {
|
||||
if (!is_array($item)) {
|
||||
return '检测参数格式无效';
|
||||
}
|
||||
if (isset($item['direction']) && !in_array($item['direction'], ['upper', 'lower'], true)) {
|
||||
return '预警方向无效';
|
||||
}
|
||||
if (!isset($item['warning'], $item['alert'])) {
|
||||
return '请填写预警值和警告值';
|
||||
}
|
||||
if (is_numeric($item['warning']) && is_numeric($item['alert'])) {
|
||||
$upper = self::direction($item) === 'upper';
|
||||
if (($upper && $item['warning'] > $item['alert'])
|
||||
|| (!$upper && $item['warning'] < $item['alert'])) {
|
||||
return $item['name'] . ($upper
|
||||
? ':越小越好指标要求预警值 ≤ 警告值(高值预警)'
|
||||
: ':越大越好指标要求预警值 ≥ 警告值(低值预警)');
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -799,6 +799,13 @@ class TestingModel extends Model
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($typeArr as &$type) {
|
||||
foreach ($type['content'] as &$item) {
|
||||
$item['direction'] = \app\common\library\QualityRule::direction($item);
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
unset($type);
|
||||
return $typeArr;
|
||||
}
|
||||
|
||||
@@ -914,6 +921,7 @@ class TestingModel extends Model
|
||||
'val' => $v['val'],
|
||||
'warning' => $v['warning'],
|
||||
'alert' => $v['alert'],
|
||||
'direction' => \app\common\library\QualityRule::direction($v),
|
||||
'test_date' => $data['test_date'],
|
||||
'year' => $data['year'],
|
||||
'season' => $data['season'],
|
||||
@@ -956,6 +964,7 @@ class TestingModel extends Model
|
||||
'val' => $v['val'],
|
||||
'warning' => $v['warning'],
|
||||
'alert' => $v['alert'],
|
||||
'direction' => \app\common\library\QualityRule::direction($v),
|
||||
'test_date' => $data['test_date'],
|
||||
'year' => $data['year'],
|
||||
'season' => $data['season'],
|
||||
@@ -1013,4 +1022,4 @@ class TestingModel extends Model
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户