diff --git a/application/admin/controller/Dashboard.php b/application/admin/controller/Dashboard.php index d19c44b..b0f0557 100644 --- a/application/admin/controller/Dashboard.php +++ b/application/admin/controller/Dashboard.php @@ -124,10 +124,19 @@ class Dashboard extends Backend public function getInitData(TestingModel $testingModel) { $granaryList = Db::name('granary') - ->field(['id', 'name']) + ->field(['id', 'name', 'varieties']) ->select(); + $defaultType = 'rapeseed'; + if (!empty($granaryList)) { + foreach ($granaryList as &$g) { + $vInfo = getVarietiesInfo($g['varieties'] ?? ''); + $g['type'] = $vInfo['type'] ?? 'rapeseed'; + } + unset($g); + $defaultType = $granaryList[0]['type'] ?? 'rapeseed'; + } $typeList = $testingModel->getTypeList(); - $lineCharts = $this->paramsQuarterLineChartsData(!empty($granaryList) ? $granaryList[0]['id'] : 0, date('Y'), 'rapeseed'); + $lineCharts = $this->paramsQuarterLineChartsData(!empty($granaryList) ? $granaryList[0]['id'] : 0, date('Y'), $defaultType); return json([ 'code' => 200, @@ -135,6 +144,7 @@ class Dashboard extends Backend 'data' => [ 'granaryList' => $granaryList, 'typeList' => $typeList, + 'defaultType' => $defaultType, 'lineCharts' => $lineCharts, 'year' => date('Y') ] diff --git a/application/admin/view/dashboard/index.html b/application/admin/view/dashboard/index.html index b3e42ed..bdfcec0 100644 --- a/application/admin/view/dashboard/index.html +++ b/application/admin/view/dashboard/index.html @@ -97,7 +97,7 @@
- +
- + {{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];