-
+
{{item.name}}
diff --git a/application/api/controller/Index.php b/application/api/controller/Index.php
index 7b5aaf9..9c2d257 100644
--- a/application/api/controller/Index.php
+++ b/application/api/controller/Index.php
@@ -322,7 +322,12 @@ class Index extends Api
public function forecast()
{
$granaryName = $this->request->get('granaryName', '');
- $variety = $this->request->get('variety', '小麦');
+ $variety = $this->request->get('variety', $this->request->get('commodity', $this->request->get('type', '小麦')));
+ if (empty($variety) || $variety === '小麦') {
+ if (strpos($granaryName, '30') !== false || strpos($granaryName, '稻') !== false) {
+ $variety = '稻谷';
+ }
+ }
$harvestYear = $this->request->get('harvestYear', '2020');
$origin = $this->request->get('origin', '河南');
$daysStr = $this->request->get('days', '7,15,30,60');
@@ -366,15 +371,16 @@ class Index extends Api
if (!empty($remoteData)) {
$targets = isset($remoteData['targets']) ? $remoteData['targets'] : [];
+ $isRice = ($grainType === 'rice');
$resultList[] = [
'day' => $day,
'target_date' => $targetDate,
'target_month' => $targetMonth,
'target_day' => $targetDay,
- 'forecast_water' => isset($targets['wheat_moisture']) ? $targets['wheat_moisture'] : (isset($targets['paddy_moisture']) ? $targets['paddy_moisture'] : 12.0),
- 'forecast_impurity' => 0.6,
- 'forecast_weight' => isset($targets['wheat_GWAC']) ? $targets['wheat_GWAC'] : 790,
- 'forecast_imperfect' => isset($targets['wheat_DON']) ? round($targets['wheat_DON'] / 100, 2) : 3.5,
+ 'forecast_water' => isset($targets['paddy_moisture']) ? $targets['paddy_moisture'] : (isset($targets['wheat_moisture']) ? $targets['wheat_moisture'] : 11.9),
+ 'forecast_impurity' => 0.4,
+ 'forecast_weight' => isset($targets['paddy_fatacid']) ? $targets['paddy_fatacid'] : (isset($targets['wheat_GWAC']) ? $targets['wheat_GWAC'] : 790),
+ 'forecast_imperfect' => isset($targets['paddy_AFB1']) ? $targets['paddy_AFB1'] : (isset($targets['wheat_DON']) ? round($targets['wheat_DON'] / 100, 2) : 3.5),
'forecast_taste' => isset($targets['wheat_ZEN']) ? 82.0 : 80.0,
'targets' => $targets,
'status' => 'normal'
@@ -382,16 +388,28 @@ class Index extends Api
} else {
$decayFactor = $day / 60.0;
$tempPenalty = max(0, ($tm_i - 15.0) * 0.015 * $decayFactor);
+ $isRice = ($grainType === 'rice');
+ $fallbackTargets = $isRice ? [
+ 'paddy_moisture' => round(11.7 - $day * 0.005, 4),
+ 'paddy_fatacid' => round(25.5 + $day * 0.06, 4),
+ 'paddy_AFB1' => round(2.4 + $day * 0.008, 4),
+ ] : [
+ 'wheat_moisture' => round(11.9 - $day * 0.006, 4),
+ 'wheat_GWAC' => round(201.2 - $day * 0.08, 4),
+ 'wheat_DON' => round(135.0 + $day * 0.15, 4),
+ 'wheat_ZEN' => round(7.0 + $day * 0.012, 4),
+ ];
$resultList[] = [
'day' => $day,
'target_date' => $targetDate,
'target_month' => $targetMonth,
'target_day' => $targetDay,
- 'forecast_water' => round(12.2 + 0.3 * $decayFactor + $tempPenalty, 2),
- 'forecast_impurity' => 0.6,
- 'forecast_weight' => round(790 - 4.0 * $decayFactor, 1),
- 'forecast_imperfect' => round(3.5 + 0.8 * $decayFactor, 2),
+ 'forecast_water' => $isRice ? round(11.7 - 0.2 * $decayFactor, 2) : round(12.2 + 0.3 * $decayFactor + $tempPenalty, 2),
+ 'forecast_impurity' => 0.4,
+ 'forecast_weight' => $isRice ? round(25.5 + 2.0 * $decayFactor, 1) : round(790 - 4.0 * $decayFactor, 1),
+ 'forecast_imperfect' => $isRice ? round(2.4 + 0.5 * $decayFactor, 2) : round(3.5 + 0.8 * $decayFactor, 2),
'forecast_taste' => round(82 - 3.5 * $decayFactor, 1),
+ 'targets' => $fallbackTargets,
'status' => 'normal'
];
}
@@ -399,7 +417,7 @@ class Index extends Api
$this->success('获取成功', [
'granaryName' => $granaryName,
- 'variety' => $variety,
+ 'variety' => ($grainType === 'rice' ? '稻谷' : '小麦'),
'list' => $resultList
]);
}
diff --git a/application/extra/site.php b/application/extra/site.php
index d5b1246..40f1959 100644
--- a/application/extra/site.php
+++ b/application/extra/site.php
@@ -4,7 +4,7 @@ return array (
'name' => '中央储备粮遵义直属库有限公司',
'beian' => '',
'cdnurl' => '',
- 'version' => '20261102_v2',
+ 'version' => '20261102_v3',
'timezone' => 'Asia/Shanghai',
'forbiddenip' => '',
'languages' =>
diff --git a/public/assets/js/backend/dashboard.js b/public/assets/js/backend/dashboard.js
index 432fcbe..be24455 100644
--- a/public/assets/js/backend/dashboard.js
+++ b/public/assets/js/backend/dashboard.js
@@ -18,6 +18,18 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
this.getInitData();
},
methods: {
+ // 仓库切换智能联动其品种并刷新折线图
+ granaryChange(gid) {
+ let cur = this.granaryList.find(function (item) { return item.id === gid; });
+ if (cur && cur.type) {
+ this.search.type = cur.type;
+ }
+ this.getParamsLineCharts();
+ },
+ // 品种切换自动刷新折线图
+ typeChange(type) {
+ this.getParamsLineCharts();
+ },
// 获取初始化数据
getInitData() {
let loading = this.$loading({
@@ -37,9 +49,15 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
this.granaryList = res.data.granaryList;
this.typeList = res.data.typeList;
this.search.year = res.data.year;
+ if (res.data.defaultType) {
+ this.search.type = res.data.defaultType;
+ }
if (this.granaryList.length) {
this.search.gid = this.granaryList[0]['id'];
+ if (this.granaryList[0]['type']) {
+ this.search.type = this.granaryList[0]['type'];
+ }
}
this.lineCharts = res.data.lineCharts;
@@ -80,34 +98,47 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
},
// 折线图
paramsLineCharts() {
- let series = this.lineCharts;
+ let series = this.lineCharts || {};
let lineChart = Echarts.init(document.getElementById('main-chart'));
let legendData = [];
+ let hasData = false;
if (series.seriesData && series.seriesData.length) {
- // 数据处理
- for (item of series.seriesData) {
+ for (let i = 0; i < series.seriesData.length; i++) {
+ let item = series.seriesData[i];
item.type = 'line';
item.areaStyle = {
focus: 'series'
};
-
legendData.push(item.name);
+ if (item.data && item.data.length && item.data.some(function (v) { return v !== null && v !== undefined && v !== ''; })) {
+ hasData = true;
+ }
}
- } else {
- console.log('aaasd');
- series.seriesData = [{
- name: '暂无检测数据',
- type: 'line',
- areaStyle: {
- focus: 'series'
- },
- data: []
- }];
}
+ let curGid = this.search.gid;
+ let curGranary = this.granaryList.find(function (g) { return g.id === curGid; });
+ let granaryName = curGranary ? curGranary.name : '所选仓库';
+ let typeName = this.typeList && this.typeList[this.search.type] ? this.typeList[this.search.type].name : this.search.type;
+ let yearText = this.search.year || '';
+
let option = {
- title: {
+ title: !hasData ? {
+ text: '暂无检测数据',
+ subtext: (yearText ? yearText + '年 ' : '') + granaryName + ' 暂无【' + typeName + '】检测报告,可切换年份或对应品种查看',
+ left: 'center',
+ top: 'middle',
+ textStyle: {
+ color: '#909399',
+ fontSize: 16,
+ fontWeight: 'normal'
+ },
+ subtextStyle: {
+ color: '#c0c4cc',
+ fontSize: 13
+ }
+ } : {
text: ''
},
tooltip: {
@@ -120,7 +151,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
}
},
legend: {
- data: legendData
+ data: hasData ? legendData : []
},
toolbox: {
feature: {
@@ -137,7 +168,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
{
type: 'category',
boundaryGap: false,
- data: series.list
+ data: hasData ? (series.list || []) : []
}
],
yAxis: [
@@ -145,7 +176,7 @@ define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echart
type: 'value'
}
],
- series: series.seriesData
+ series: hasData ? series.seriesData : []
};
lineChart.setOption(option, true);
diff --git a/public/screen/js/app.js b/public/screen/js/app.js
index 7c884b5..51092e0 100644
--- a/public/screen/js/app.js
+++ b/public/screen/js/app.js
@@ -767,7 +767,9 @@ function setupForecastEnvControls() {
const month = parseInt(document.getElementById('input-forecast-month')?.value, 10) || 7;
const day = parseInt(document.getElementById('input-forecast-day')?.value, 10) || 13;
- STATE.forecastEnv = { tm_i, hm_i, tm_o, hm_o, month, day };
+ const currentYear = new Date().getFullYear();
+ const baseDate = currentYear + '-' + String(month).padStart(2, '0') + '-' + String(day).padStart(2, '0');
+ STATE.forecastEnv = { tm_i, hm_i, tm_o, hm_o, month, day, baseDate };
delete STATE.forecastCache[STATE.activeId];
const currentItem = STATE.list.find(i => i.id === STATE.activeId) || STATE.list[0];
@@ -778,13 +780,15 @@ function setupForecastEnvControls() {
if (btnReset && !btnReset.dataset.bound) {
btnReset.dataset.bound = 'true';
btnReset.addEventListener('click', async () => {
+ const currentYear = new Date().getFullYear();
STATE.forecastEnv = {
tm_i: 18.5,
tm_o: 32.5,
hm_i: 72.4,
hm_o: 54.2,
month: 7,
- day: 13
+ day: 13,
+ baseDate: currentYear + '-07-13'
};
delete STATE.forecastCache[STATE.activeId];
diff --git a/public/screen/js/grain_monitor_core.js b/public/screen/js/grain_monitor_core.js
index 1c3208f..fc10e48 100644
--- a/public/screen/js/grain_monitor_core.js
+++ b/public/screen/js/grain_monitor_core.js
@@ -1200,9 +1200,14 @@ export function buildForecastQueryParams(granaryItem, options = {}) {
let month;
let day;
let targetInfo = null;
+ let baseDate = options.baseDate || null;
+ if (!baseDate && options.month && options.day) {
+ const currentYear = new Date().getFullYear();
+ baseDate = currentYear + '-' + String(options.month).padStart(2, '0') + '-' + String(options.day).padStart(2, '0');
+ }
if (options.horizon !== undefined) {
- targetInfo = calculateForecastTargetDate(options.baseDate, options.horizon);
+ targetInfo = calculateForecastTargetDate(baseDate, options.horizon);
month = targetInfo.month;
day = targetInfo.day;
} else if (options.day !== undefined && options.day > 31) {
@@ -1424,7 +1429,8 @@ export function generateForecastSeries(grainType, currentReport, forecastList =
const unit = saved?.unit || cfg.unit;
const canonicalUnit = value => String(value).replace(/[μµ]/g, 'u').replace(/\s/g, '').toLowerCase();
const concentrations = {'ug/kg': 1, 'ng/g': 1, 'ug/g': 1000, 'mg/kg': 1000, 'mg/g': 1000000};
- const from = canonicalUnit(cfg.unit), to = canonicalUnit(unit);
+ const normalizePercent = u => (u === '%' || u === 'g/100g' || u === 'g/100 g') ? '%' : u;
+ const from = normalizePercent(canonicalUnit(cfg.unit)), to = normalizePercent(canonicalUnit(unit));
const factor = from === to ? 1 : concentrations[from] && concentrations[to] ? concentrations[from] / concentrations[to] : null;
const curVal = findCurrentVal(cfg.key, factor === null ? null : cfg.fallback * factor);
const rules = saved && Number.isFinite(qualityNumber(saved.warn)) && Number.isFinite(qualityNumber(saved.alert))
@@ -1628,7 +1634,11 @@ export async function fetchGranaryForecast(granaryItem, days = [7, 15, 30, 60],
const grainType = mapVarietyToForecastType(variety);
if (!grainType) return [];
- const baseDate = envOptions.baseDate || null;
+ let baseDate = envOptions.baseDate || null;
+ if (!baseDate && envOptions.month && envOptions.day) {
+ const currentYear = new Date().getFullYear();
+ baseDate = currentYear + '-' + String(envOptions.month).padStart(2, '0') + '-' + String(envOptions.day).padStart(2, '0');
+ }
const promises = days.map(async (day) => {
const targetInfo = calculateForecastTargetDate(baseDate, day);
const params = buildForecastQueryParams(granaryItem, { ...envOptions, horizon: day, baseDate });
@@ -1643,7 +1653,7 @@ export async function fetchGranaryForecast(granaryItem, days = [7, 15, 30, 60],
for (const fUrl of forecastUrls) {
try {
- const res = await fetch(fUrl, { signal: AbortSignal.timeout(600) });
+ const res = await fetch(fUrl, { signal: AbortSignal.timeout(2500) });
if (!res.ok) continue;
const json = await res.json();
if (json && json.code === '0' && json.output?.targets) {
@@ -1652,11 +1662,17 @@ export async function fetchGranaryForecast(granaryItem, days = [7, 15, 30, 60],
} else if (json && json.code === 1 && json.data && Array.isArray(json.data.list)) {
const matched = json.data.list.find(i => i.day === day);
if (matched) {
+ const remoteTargets = matched.targets || {};
+ const defaultFatacid = 19.0 + (day / 60.0) * 8.0;
+ const defaultAFB1 = 2.5 + (day / 60.0) * 0.4;
apiData = {
output: {
targets: {
+ ...remoteTargets,
water: matched.forecast_water,
[grainType === 'wheat' ? 'wheat_moisture' : 'paddy_moisture']: matched.forecast_water,
+ paddy_fatacid: remoteTargets.paddy_fatacid !== undefined ? remoteTargets.paddy_fatacid : defaultFatacid,
+ paddy_AFB1: remoteTargets.paddy_AFB1 !== undefined ? remoteTargets.paddy_AFB1 : defaultAFB1,
impurity: matched.forecast_impurity,
weight: matched.forecast_weight,
wheat_unitweight: matched.forecast_weight,
diff --git a/public/screen/js/xishui_app.js b/public/screen/js/xishui_app.js
index 3aa7cc8..a3366c5 100644
--- a/public/screen/js/xishui_app.js
+++ b/public/screen/js/xishui_app.js
@@ -731,7 +731,9 @@ function setupForecastEnvControls() {
const month = parseInt(document.getElementById('input-forecast-month')?.value, 10) || 7;
const day = parseInt(document.getElementById('input-forecast-day')?.value, 10) || 13;
- STATE.forecastEnv = { tm_i, hm_i, tm_o, hm_o, month, day };
+ const currentYear = new Date().getFullYear();
+ const baseDate = currentYear + '-' + String(month).padStart(2, '0') + '-' + String(day).padStart(2, '0');
+ STATE.forecastEnv = { tm_i, hm_i, tm_o, hm_o, month, day, baseDate };
delete STATE.forecastCache[STATE.activeId];
const currentItem = STATE.list.find(i => i.id === STATE.activeId) || STATE.list[0];
@@ -742,13 +744,15 @@ function setupForecastEnvControls() {
if (btnReset && !btnReset.dataset.bound) {
btnReset.dataset.bound = 'true';
btnReset.addEventListener('click', async () => {
+ const currentYear = new Date().getFullYear();
STATE.forecastEnv = {
tm_i: 18.5,
tm_o: 32.5,
hm_i: 72.4,
hm_o: 54.2,
month: 7,
- day: 13
+ day: 13,
+ baseDate: currentYear + '-07-13'
};
delete STATE.forecastCache[STATE.activeId];