殖系統(tǒng):從數(shù)據(jù)對(duì)不上到全鏈路跑通)
簡(jiǎn)介這是一套基于SpringBoot與Vue實(shí)現(xiàn)的家禽養(yǎng)殖管理系統(tǒng)源碼面向計(jì)算機(jī)專業(yè)學(xué)生、課程設(shè)計(jì)開發(fā)者及需要企業(yè)級(jí)管理后臺(tái)參考的Java學(xué)習(xí)者。系統(tǒng)圍繞養(yǎng)殖場(chǎng)日常運(yùn)營(yíng)展開劃分管理員、普通員工與物資管理員三類角色管理員負(fù)責(zé)賬戶創(chuàng)建、權(quán)限分配與注冊(cè)申請(qǐng)審核普通員工完成家禽信息、飼養(yǎng)記錄、生長(zhǎng)健康狀態(tài)等日數(shù)據(jù)導(dǎo)入并執(zhí)行喂養(yǎng)與環(huán)境清潔任務(wù)物資管理員則管理飼料、藥品、醫(yī)療器械的采購(gòu)與庫(kù)存監(jiān)控維護(hù)供應(yīng)商列表以保障物資質(zhì)量。資源包共449個(gè)文件以72個(gè)Java后端源碼、21個(gè)Vue組件、22個(gè)JavaScript腳本及22個(gè)XML配置為主另含SQL建表腳本、YAML配置與少量圖片資源壓縮包約11.03MB前后端結(jié)構(gòu)完整。目前已有176人學(xué)習(xí)下載適合作為畢業(yè)設(shè)計(jì)、課程作業(yè)或全棧練手項(xiàng)目幫助讀者快速理解角色權(quán)限設(shè)計(jì)與養(yǎng)殖業(yè)務(wù)模塊的實(shí)現(xiàn)思路。1. 家禽養(yǎng)殖系統(tǒng)為什么總在“數(shù)據(jù)對(duì)不上”這一步翻車養(yǎng)了三年雞的老張最近很煩。他那個(gè)存欄兩萬(wàn)只的養(yǎng)殖場(chǎng)每天喂了多少料、死了多少只、產(chǎn)了多少蛋全靠三本手寫臺(tái)賬。月底一算賬飼料消耗比理論值高了百分之八死淘率對(duì)不上獸醫(yī)記錄產(chǎn)蛋數(shù)跟收購(gòu)單差了兩百多枚。問(wèn)題出在哪不是他不會(huì)養(yǎng)雞是數(shù)據(jù)在雞舍、倉(cāng)庫(kù)、財(cái)務(wù)三個(gè)環(huán)節(jié)各記各的中間全靠人腦和微信語(yǔ)音傳遞。基于SpringBootVue的家禽養(yǎng)殖系統(tǒng)要解決的就是這個(gè)“數(shù)據(jù)對(duì)不上”的問(wèn)題。它把進(jìn)雛、飼喂、免疫、用藥、產(chǎn)蛋、死淘、出欄、庫(kù)存、成本核算串成一條線讓每個(gè)環(huán)節(jié)的數(shù)據(jù)從哪來(lái)到哪去都有跡可循。適合誰(shuí)用中小型養(yǎng)殖場(chǎng)的場(chǎng)長(zhǎng)、技術(shù)員以及想用一套完整項(xiàng)目練手全棧開發(fā)的學(xué)生或初級(jí)工程師。這篇文章不講空泛的架構(gòu)圖只講怎么把這條數(shù)據(jù)鏈跑通、參數(shù)怎么設(shè)、哪里容易翻車。2. 從雞舍到數(shù)據(jù)庫(kù)家禽養(yǎng)殖系統(tǒng)的領(lǐng)域建模與表結(jié)構(gòu)設(shè)計(jì)2.1 先搞清楚“一批雞”在系統(tǒng)里怎么表示家禽養(yǎng)殖的核心對(duì)象是“批次”。一批雞從進(jìn)雛到出欄所有生產(chǎn)數(shù)據(jù)都掛在批次下面。常見(jiàn)做法是建三張主表批次表、日記錄表、事件表。批次表存進(jìn)雛日期、品種、初始數(shù)量、當(dāng)前存欄、雞舍編號(hào)日記錄表存每天的采食量、飲水量、產(chǎn)蛋數(shù)、死淘數(shù)、平均體重事件表存免疫、用藥、消毒、轉(zhuǎn)群這些非每日發(fā)生但必須留痕的操作。為什么不用一張大寬表因?yàn)槿沼涗浭歉哳l寫入事件是低頻但字段差異大。分開之后日記錄表可以按批次和日期做聯(lián)合索引查詢“某批次最近七天采食趨勢(shì)”時(shí)不會(huì)掃全表。事件表用類型字段區(qū)分免疫記錄存疫苗名稱和劑量用藥記錄存藥品名稱和休藥期字段用JSON擴(kuò)展列存避免頻繁改表結(jié)構(gòu)。-- 批次表一批雞的身份證 CREATE TABLE batch ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_no VARCHAR(32) NOT NULL UNIQUE COMMENT 批次編號(hào)如2024-B01, breed VARCHAR(64) COMMENT 品種如白羽肉雞、海蘭褐, house_no VARCHAR(16) COMMENT 雞舍編號(hào), initial_count INT NOT NULL COMMENT 進(jìn)雛數(shù)量, current_count INT NOT NULL COMMENT 當(dāng)前存欄, start_date DATE NOT NULL COMMENT 進(jìn)雛日期, status TINYINT DEFAULT 1 COMMENT 1在養(yǎng) 2已出欄 3已清欄, INDEX idx_start_date (start_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 日記錄表每天一條高頻寫入 CREATE TABLE daily_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, batch_id BIGINT NOT NULL, record_date DATE NOT NULL, feed_kg DECIMAL(10,2) COMMENT 采食量公斤, water_l DECIMAL(10,2) COMMENT 飲水量升, egg_count INT DEFAULT 0 COMMENT 產(chǎn)蛋數(shù), dead_count INT DEFAULT 0 COMMENT 死淘數(shù), avg_weight_g INT COMMENT 平均體重克, UNIQUE KEY uk_batch_date (batch_id, record_date), INDEX idx_batch (batch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;參數(shù)說(shuō)明batch_no用業(yè)務(wù)編號(hào)而不是自增ID對(duì)外方便場(chǎng)里人溝通。current_count不通過(guò)觸發(fā)器自動(dòng)減而是由服務(wù)層在死淘事件里更新因?yàn)樗捞钥赡苎a(bǔ)錄觸發(fā)器容易算錯(cuò)。uk_batch_date唯一索引防止同一天重復(fù)錄入這是血淚經(jīng)驗(yàn)——不加上這個(gè)約束月底對(duì)賬時(shí)你會(huì)發(fā)現(xiàn)同一天有三條記錄分別是技術(shù)員、場(chǎng)長(zhǎng)、財(cái)務(wù)各錄了一遍。2.2 存欄數(shù)為什么不能只靠“初始減死淘”新手最容易踩的坑以為當(dāng)前存欄 初始數(shù)量 - 累計(jì)死淘。實(shí)際養(yǎng)殖中還有淘汰、轉(zhuǎn)群、出售、調(diào)撥。比如一批雞養(yǎng)到中期把兩千只轉(zhuǎn)到另一個(gè)雞舍這批的存欄要減那批的要加。如果只記死淘轉(zhuǎn)群之后兩邊數(shù)據(jù)全亂。常見(jiàn)做法是引入“存欄變動(dòng)流水表”每次數(shù)量變化都插一條記錄字段包括批次ID、變動(dòng)類型死淘/轉(zhuǎn)群出/轉(zhuǎn)群入/出售/淘汰、變動(dòng)數(shù)量、變動(dòng)后存欄、操作時(shí)間。當(dāng)前存欄直接讀批次表的冗余字段但每天凌晨跑一次對(duì)賬任務(wù)用流水表重算存欄和冗余字段比對(duì)不一致就告警。這樣既保證查詢快又保證數(shù)據(jù)可追溯。// 存欄變動(dòng)服務(wù)每次變動(dòng)都寫流水并更新冗余 Service public class StockService { Transactional public void changeStock(Long batchId, String type, int delta, String remark) { Batch batch batchMapper.selectById(batchId); int newCount batch.getCurrentCount() delta; if (newCount 0) { throw new BizException(存欄不能為負(fù)當(dāng)前 batch.getCurrentCount() 變動(dòng) delta); } // 寫流水 StockFlow flow new StockFlow(); flow.setBatchId(batchId); flow.setChangeType(type); flow.setDelta(delta); flow.setAfterCount(newCount); flow.setRemark(remark); stockFlowMapper.insert(flow); // 更新冗余 batch.setCurrentCount(newCount); batchMapper.updateById(batch); } }邏輯說(shuō)明delta用正負(fù)表示增減轉(zhuǎn)群出傳負(fù)數(shù)轉(zhuǎn)群入傳正數(shù)。newCount 0校驗(yàn)必須做否則一次誤操作就能把存欄搞成負(fù)數(shù)后面所有報(bào)表全廢。對(duì)賬任務(wù)用SELECT batch_id, SUM(delta) FROM stock_flow GROUP BY batch_id和批次表比對(duì)差異超過(guò)閾值就發(fā)通知。2.3 免疫和用藥記錄怎么和休藥期聯(lián)動(dòng)家禽養(yǎng)殖繞不開用藥但用藥之后有休藥期休藥期內(nèi)產(chǎn)的蛋不能賣。系統(tǒng)如果不做聯(lián)動(dòng)全靠人記遲早出事。設(shè)計(jì)上用藥事件表加兩個(gè)字段drug_name和withdraw_days。當(dāng)錄入一條用藥記錄時(shí)自動(dòng)計(jì)算withdraw_end_date 用藥日期 withdraw_days并寫入批次表的lock_until字段。產(chǎn)蛋記錄提交時(shí)如果當(dāng)前日期小于lock_until彈窗提示“該批次處于休藥期產(chǎn)蛋不可銷售”但允許記錄為“非銷售蛋”。這個(gè)邏輯不復(fù)雜但參數(shù)要留活口。不同藥品休藥期不同同一藥品不同給藥方式休藥期也不同。常見(jiàn)做法是建一個(gè)藥品字典表存藥品名、默認(rèn)休藥期、給藥方式錄入時(shí)帶出默認(rèn)值允許手動(dòng)改。改的時(shí)候要填原因留審計(jì)痕跡。3. 用SpringBoot把生產(chǎn)數(shù)據(jù)接口跑通從錄入到校驗(yàn)的完整鏈路3.1 日記錄錄入接口的防重與校驗(yàn)日記錄是系統(tǒng)里寫入最頻繁的接口一天可能幾百條。設(shè)計(jì)不好要么重復(fù)錄入要么并發(fā)寫沖突。常見(jiàn)做法是用batch_id record_date做唯一鍵插入時(shí)用INSERT ... ON DUPLICATE KEY UPDATE但這樣會(huì)覆蓋之前的數(shù)據(jù)不適合需要留痕的場(chǎng)景。更穩(wěn)的做法是先查后插查的時(shí)候加行鎖。PostMapping(/daily) public Result saveDaily(RequestBody Valid DailyRecordDTO dto) { // 1. 校驗(yàn)批次是否存在且在養(yǎng) Batch batch batchMapper.selectById(dto.getBatchId()); if (batch null || batch.getStatus() ! 1) { return Result.fail(批次不存在或已結(jié)束); } // 2. 校驗(yàn)日期不能晚于今天 if (dto.getRecordDate().isAfter(LocalDate.now())) { return Result.fail(不能錄入未來(lái)日期); } // 3. 查重加鎖防并發(fā) DailyRecord exist dailyMapper.selectForUpdate(dto.getBatchId(), dto.getRecordDate()); if (exist ! null) { return Result.fail(該批次當(dāng)日記錄已存在請(qǐng)勿重復(fù)錄入); } // 4. 死淘數(shù)不能超過(guò)當(dāng)前存欄 if (dto.getDeadCount() batch.getCurrentCount()) { return Result.fail(死淘數(shù)超過(guò)當(dāng)前存欄); } // 5. 保存并更新存欄 DailyRecord record new DailyRecord(); BeanUtils.copyProperties(dto, record); dailyMapper.insert(record); if (dto.getDeadCount() 0) { stockService.changeStock(dto.getBatchId(), DEAD, -dto.getDeadCount(), 日記錄死淘); } return Result.ok(); }參數(shù)說(shuō)明selectForUpdate在Mapper里寫SELECT * FROM daily_record WHERE batch_id? AND record_date? FOR UPDATE利用InnoDB行鎖防止兩個(gè)請(qǐng)求同時(shí)查到空然后都插入。deadCount校驗(yàn)用而不是因?yàn)樗捞缘扔诖鏅谑窃试S的意味著清欄。死淘更新存欄走StockService保證流水和冗余一致。3.2 產(chǎn)蛋數(shù)據(jù)的異常值過(guò)濾產(chǎn)蛋數(shù)受品種、日齡、季節(jié)、光照影響波動(dòng)正常但突然翻倍或減半大概率是錄入錯(cuò)誤。系統(tǒng)里加一層軟校驗(yàn)根據(jù)批次品種和日齡查標(biāo)準(zhǔn)產(chǎn)蛋曲線實(shí)際值偏離標(biāo)準(zhǔn)值超過(guò)百分之三十時(shí)標(biāo)記為“待確認(rèn)”不直接拒絕但推送給技術(shù)員復(fù)核。// 產(chǎn)蛋異常檢測(cè)偏離標(biāo)準(zhǔn)曲線30%以上標(biāo)記 public boolean isEggCountAbnormal(Batch batch, int eggCount, LocalDate date) { int ageDays (int) ChronoUnit.DAYS.between(batch.getStartDate(), date); // 標(biāo)準(zhǔn)曲線從配置表讀按品種和日齡查 StandardCurve curve curveMapper.selectByBreedAndAge(batch.getBreed(), ageDays); if (curve null) { return false; // 沒(méi)有標(biāo)準(zhǔn)曲線就不校驗(yàn) } double expected curve.getEggRate() * batch.getCurrentCount(); if (expected 0) { return false; } double deviation Math.abs(eggCount - expected) / expected; return deviation 0.3; }邏輯說(shuō)明eggRate是產(chǎn)蛋率從標(biāo)準(zhǔn)曲線表讀比如海蘭褐在200日齡產(chǎn)蛋率約0.92。expected是理論產(chǎn)蛋數(shù)。偏離超過(guò)百分之三十標(biāo)記異常前端顯示黃色感嘆號(hào)技術(shù)員點(diǎn)確認(rèn)后才進(jìn)入統(tǒng)計(jì)。這個(gè)閾值可以配置不同品種、不同季節(jié)可以調(diào)。注意不要用硬編碼否則換品種就得改代碼。3.3 批次成本核算的定時(shí)任務(wù)養(yǎng)殖場(chǎng)最關(guān)心的是這批雞到底賺沒(méi)賺錢。成本包括雞雛、飼料、藥品、疫苗、人工、水電、折舊。前四項(xiàng)能從系統(tǒng)數(shù)據(jù)算后三項(xiàng)通常按批次分?jǐn)?。常?jiàn)做法是每天凌晨跑一個(gè)定時(shí)任務(wù)匯總當(dāng)天各批次的飼料消耗、藥品消耗寫入成本日匯總表。出欄時(shí)再算總成本。Scheduled(cron 0 30 1 * * ?) // 每天凌晨1:30執(zhí)行 public void calcDailyCost() { ListBatch activeBatches batchMapper.selectActive(); for (Batch batch : activeBatches) { LocalDate yesterday LocalDate.now().minusDays(1); // 飼料成本從日記錄讀采食量乘以飼料單價(jià) DailyRecord record dailyMapper.selectByBatchAndDate(batch.getId(), yesterday); BigDecimal feedCost BigDecimal.ZERO; if (record ! null record.getFeedKg() ! null) { BigDecimal price feedPriceService.getPrice(batch.getBreed(), yesterday); feedCost record.getFeedKg().multiply(price); } // 藥品成本從事件表讀當(dāng)天用藥記錄 BigDecimal drugCost eventMapper.sumDrugCost(batch.getId(), yesterday); // 寫入成本匯總 CostSummary summary new CostSummary(); summary.setBatchId(batch.getId()); summary.setCostDate(yesterday); summary.setFeedCost(feedCost); summary.setDrugCost(drugCost); costSummaryMapper.insert(summary); } }參數(shù)說(shuō)明cron表達(dá)式按服務(wù)器時(shí)區(qū)如果服務(wù)器在UTC要改成對(duì)應(yīng)時(shí)間。feedPriceService.getPrice按品種和日期查因?yàn)轱暳蟽r(jià)格會(huì)波動(dòng)不能用一個(gè)固定值算全程。sumDrugCost在事件表里按類型過(guò)濾只算用藥和疫苗不算消毒。這個(gè)任務(wù)要加失敗重試某天沒(méi)跑成功第二天要補(bǔ)算否則成本缺一天。4. Vue前端怎么把“雞舍現(xiàn)場(chǎng)”搬進(jìn)瀏覽器4.1 批次看板一屏看清所有在養(yǎng)批次場(chǎng)長(zhǎng)最??吹捻?yè)面是批次看板。需求很簡(jiǎn)單每個(gè)在養(yǎng)批次一張卡片顯示批次號(hào)、品種、日齡、當(dāng)前存欄、今日采食、今日產(chǎn)蛋、死淘率。數(shù)據(jù)從/api/batch/overview一次拉回前端用v-for渲染。關(guān)鍵點(diǎn)是日齡計(jì)算和死淘率格式化。// 批次看板組件核心邏輯 export default { data() { return { batches: [], loading: false }; }, methods: { async fetchOverview() { this.loading true; try { const res await axios.get(/api/batch/overview); this.batches res.data.map(item ({ ...item, // 日齡從進(jìn)雛日期到今天 ageDays: dayjs().diff(dayjs(item.startDate), day), // 死淘率累計(jì)死淘/初始數(shù)量 deadRate: item.initialCount 0 ? ((item.initialCount - item.currentCount) / item.initialCount * 100).toFixed(2) : 0.00 })); } finally { this.loading false; } } }, mounted() { this.fetchOverview(); // 每5分鐘刷新一次 this.timer setInterval(this.fetchOverview, 5 * 60 * 1000); }, beforeDestroy() { clearInterval(this.timer); } };邏輯說(shuō)明dayjs().diff算日齡注意時(shí)區(qū)問(wèn)題如果服務(wù)器返回的是UTC日期要轉(zhuǎn)成本地再算。deadRate用累計(jì)死淘除以初始數(shù)量不是除以當(dāng)前存欄否則分母越來(lái)越小死淘率會(huì)虛高。定時(shí)刷新用setInterval組件銷毀時(shí)要clearInterval否則內(nèi)存泄漏。這個(gè)頁(yè)面在手機(jī)瀏覽器上也要能看卡片布局用flex換行不要寫死寬度。4.2 日記錄表單的聯(lián)動(dòng)校驗(yàn)錄入日記錄時(shí)前端要做幾項(xiàng)即時(shí)校驗(yàn)日期不能選未來(lái)、死淘數(shù)不能超過(guò)當(dāng)前存欄、采食量不能為負(fù)。這些校驗(yàn)后端也會(huì)做但前端先攔一道減少無(wú)效請(qǐng)求。用Element UI的el-form規(guī)則加上自定義校驗(yàn)函數(shù)。// 日記錄表單校驗(yàn)規(guī)則 rules: { recordDate: [ { required: true, message: 請(qǐng)選擇日期, trigger: change }, { validator: (rule, value, callback) { if (value dayjs(value).isAfter(dayjs(), day)) { callback(new Error(不能錄入未來(lái)日期)); } else { callback(); } }, trigger: change } ], deadCount: [ { required: true, message: 請(qǐng)輸入死淘數(shù), trigger: blur }, { validator: (rule, value, callback) { if (value this.currentBatch.currentCount) { callback(new Error(死淘數(shù)不能超過(guò)當(dāng)前存欄 this.currentBatch.currentCount)); } else { callback(); } }, trigger: blur } ], feedKg: [ { type: number, min: 0, message: 采食量不能為負(fù), trigger: blur } ] }參數(shù)說(shuō)明dayjs(value).isAfter(dayjs(), day)比較到天同一天允許。this.currentBatch從父組件傳入切換批次時(shí)要重置表單。feedKg用type: number而不是type: integer因?yàn)椴墒沉靠赡苡行?shù)。校驗(yàn)觸發(fā)用blur而不是change避免輸入過(guò)程中頻繁報(bào)錯(cuò)。4.3 用ECharts畫產(chǎn)蛋曲線和采食趨勢(shì)技術(shù)員需要看趨勢(shì)判斷雞群是否正常。產(chǎn)蛋曲線和采食趨勢(shì)用ECharts折線圖數(shù)據(jù)從/api/report/trend?batchIdxdays30拉。關(guān)鍵點(diǎn)是橫軸日期要連續(xù)不能因?yàn)槟程鞗](méi)記錄就斷掉。// 產(chǎn)蛋趨勢(shì)圖配置 initEggChart(data) { const dates []; const eggCounts []; const expectedCounts []; // 補(bǔ)齊缺失日期 const start dayjs().subtract(29, day); for (let i 0; i 30; i) { const d start.add(i, day).format(YYYY-MM-DD); dates.push(d); const found data.find(item item.recordDate d); eggCounts.push(found ? found.eggCount : null); expectedCounts.push(found ? found.expectedCount : null); } this.chart.setOption({ xAxis: { type: category, data: dates }, yAxis: { type: value, name: 產(chǎn)蛋數(shù) }, series: [ { name: 實(shí)際產(chǎn)蛋, type: line, data: eggCounts, connectNulls: false }, { name: 標(biāo)準(zhǔn)曲線, type: line, data: expectedCounts, lineStyle: { type: dashed } } ], tooltip: { trigger: axis } }); }邏輯說(shuō)明connectNulls: false讓缺失日期斷開一眼能看出哪天沒(méi)錄。標(biāo)準(zhǔn)曲線用虛線實(shí)際用實(shí)線偏離大時(shí)技術(shù)員能直接看到。expectedCount從后端算好返回前端不做業(yè)務(wù)計(jì)算。圖表容器要設(shè)固定高度否則ECharts在flex布局里可能高度為0。5. 避坑與排查家禽養(yǎng)殖系統(tǒng)落地時(shí)最容易踩的五個(gè)坑5.1 坑一存欄數(shù)越用越亂最后沒(méi)人信系統(tǒng)現(xiàn)象用了兩個(gè)月場(chǎng)里人發(fā)現(xiàn)系統(tǒng)顯示的存欄和實(shí)際對(duì)不上差幾十只于是又回去用手寫臺(tái)賬。原因死淘錄入不及時(shí)或者轉(zhuǎn)群沒(méi)錄或者補(bǔ)錄時(shí)只改了日記錄沒(méi)走存欄變動(dòng)。解決第一日記錄提交時(shí)死淘必須走StockService不允許直接改批次表第二每天凌晨對(duì)賬任務(wù)跑一遍差異超過(guò)千分之五就推送告警給場(chǎng)長(zhǎng)第三轉(zhuǎn)群操作做成獨(dú)立功能必須選轉(zhuǎn)出批次和轉(zhuǎn)入批次兩邊同時(shí)更新。關(guān)鍵是讓場(chǎng)里人知道系統(tǒng)數(shù)據(jù)比手寫準(zhǔn)他們才會(huì)用。5.2 坑二休藥期沒(méi)鎖住產(chǎn)蛋記錄照樣能提交現(xiàn)象技術(shù)員錄了用藥記錄但產(chǎn)蛋錄入時(shí)沒(méi)提示休藥期雞蛋照樣標(biāo)記為可銷售。原因用藥事件和產(chǎn)蛋記錄之間沒(méi)有聯(lián)動(dòng)或者lock_until字段沒(méi)寫進(jìn)批次表。解決用藥保存時(shí)同步更新批次表的lock_until產(chǎn)蛋提交時(shí)查這個(gè)字段在休藥期內(nèi)強(qiáng)制標(biāo)記為“非銷售蛋”并且前端用紅色橫幅提示。如果業(yè)務(wù)允許手動(dòng)解鎖必須填解鎖原因并記錄操作人。5.3 坑三日記錄重復(fù)錄入月底對(duì)賬發(fā)現(xiàn)同一天三條現(xiàn)象技術(shù)員錄了一次場(chǎng)長(zhǎng)又錄了一次財(cái)務(wù)補(bǔ)錄一次同一天三條記錄統(tǒng)計(jì)時(shí)不知道用哪條。原因沒(méi)有唯一約束或者前端沒(méi)做防重提交。解決數(shù)據(jù)庫(kù)加uk_batch_date唯一索引接口層先查后插加行鎖前端提交按鈕點(diǎn)擊后置灰三秒。如果確實(shí)需要修改走編輯接口保留修改日志。不要用INSERT ON DUPLICATE KEY UPDATE那樣會(huì)靜默覆蓋出了事查不到。5.4 坑四成本核算漏算飼料價(jià)格波動(dòng)算出來(lái)全是錯(cuò)的現(xiàn)象出欄一算每只雞成本比實(shí)際低了兩塊場(chǎng)長(zhǎng)覺(jué)得系統(tǒng)在瞎算。原因飼料價(jià)格用了固定值但實(shí)際飼料價(jià)格每月都在變。解決建飼料價(jià)格表按品種和生效日期存價(jià)格成本核算時(shí)按日記錄日期查對(duì)應(yīng)價(jià)格。如果價(jià)格表沒(méi)維護(hù)成本任務(wù)要告警而不是用默認(rèn)值硬算。另外人工水電這些分?jǐn)傢?xiàng)要允許手動(dòng)錄入不能全指望自動(dòng)算。5.5 坑五前端圖表在手機(jī)上一片空白現(xiàn)象場(chǎng)長(zhǎng)用手機(jī)看產(chǎn)蛋趨勢(shì)圖表區(qū)域白的什么都沒(méi)有。原因ECharts容器高度用了百分比父元素沒(méi)有固定高度算出來(lái)是0。解決圖表容器設(shè)固定高度比如height: 300px或者用vh單位。另外移動(dòng)端要監(jiān)聽resize事件調(diào)chart.resize()否則橫豎屏切換后圖表變形。如果還不行檢查數(shù)據(jù)是不是空數(shù)組空數(shù)據(jù)時(shí)ECharts不報(bào)錯(cuò)但也不顯示。6. 把系統(tǒng)真正用起來(lái)從演示到日常的最后一公里很多養(yǎng)殖系統(tǒng)死在演示之后。演示時(shí)數(shù)據(jù)漂亮真用起來(lái)場(chǎng)里人嫌麻煩錄兩天就不錄了。我自己的習(xí)慣是上線第一周只開一個(gè)功能日記錄錄入。其他功能全隱藏。等日記錄錄順了再開產(chǎn)蛋和死淘。再一周開免疫用藥。再一周開報(bào)表。每次只加一個(gè)功能場(chǎng)里人不會(huì)覺(jué)得一下子多了很多活。驗(yàn)證系統(tǒng)有沒(méi)有真正用起來(lái)不看登錄次數(shù)看一個(gè)指標(biāo)日記錄連續(xù)錄入天數(shù)。連續(xù)三十天不斷說(shuō)明流程跑通了。斷一天就要問(wèn)為什么。常見(jiàn)原因是那天太忙忘了或者手機(jī)沒(méi)信號(hào)。如果是忘了加個(gè)每天下午五點(diǎn)的提醒如果是信號(hào)問(wèn)題允許離線暫存有網(wǎng)再同步。最后一個(gè)技巧把批次看板做成大屏掛在辦公室墻上。場(chǎng)長(zhǎng)每天路過(guò)看一眼存欄、采食、產(chǎn)蛋、死淘率一目了然。數(shù)據(jù)不準(zhǔn)他自己就會(huì)來(lái)找你。這比任何培訓(xùn)都管用。我見(jiàn)過(guò)太多系統(tǒng)功能做了一堆最后沒(méi)人看就是因?yàn)閿?shù)據(jù)沒(méi)展示在最顯眼的地方。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取