# 大屏 — 首页统计 — 技术方案 > 依据:同目录 `大屏首页功能需求.md`;数据来自养殖产业、流通交易、服务预约、补贴发放、养殖标准五类子表、科技资源与实战培训等**既有表**。本模块为**大屏只读看板**,无业务台账 CRUD。 --- ## 1. 技术架构 | 项 | 说明 | | --- | --- | | **后端** | RuoYi **v3.9.2**(**springboot2** 分支):JDK 8、Spring Boot 2.x、Spring MVC、MyBatis、Druid | | **数据库** | MySQL **5.7.39**,InnoDB,`utf8mb4` | | **前端** | 大屏独立首页;**切换 `statYear`** 拉看板主数据,养殖标准**分页**单独请求 | | **代码包** | `com.ruoyi.web.modules.screen`(`HomeScreenController` / `IHomeScreenService` / `vo` / `support`) | **分层**:`Controller` → `IHomeScreenService`(口径编排、按 **Y** 计算统计日)→ 扩展 Mapper / 复用 `BreedingIndustryStatsMapper` + `BreedingIndustryCalculator`(只读)。 | 场景 | 行为 | | --- | --- | | 进入 / 切换 `statYear` | 调用 **§3.1** + **§3.2**(`pageNum=1`) | | 标准列表翻页 / 切 Tab | 仅 **§3.2** | | 数据权限 | **全县**聚合:牦牛、牧场取全部 `biz_status=1` 且 `del_flag=0` 的牧场 ID(与疫病风险大屏一致,**不按**登录用户牧场过滤);草场全县 `del_flag=0` | | 统计日 | `Y = 当前年` → `statDate = 今日`;`Y` 为历史年 → `statDate = Y-12-31`(时点、月末还原、年出栏上界均据此) | | 主数据三项 | 供应商 / 承销商 / 交易市场**实时快照**,**不**随 `statYear` 变 | | 按月图表 | 横轴固定 **12** 月;缺月、当前年未来月均为 **0** | | 性能 | 默认实时聚合;可选 **§2.3** 缓存表 | **依赖表(只读)** | 表 | 用途 | | --- | --- | | `biz_yak_asset` / `biz_pasture` / `biz_grassland` | 产业总览、存栏趋势、年龄结构、出栏、草场 | | `biz_supplier` / `biz_distributor` / `biz_trade_market` | 流通主数据条数(实时) | | `biz_service_appointment` | 线下服务预约 | | `biz_subsidy_grant_record` | 惠农补贴 | | `biz_breeding_standard` 等 5 张标准表 | 养殖标准分页列表 | | `biz_tech_resource` | 视频课程(`004008`) | | `biz_practical_training` | 实战培训场次与报名趋势 | | `biz_yak_herd_inventory` | 地图标注 — 乡镇牦牛存栏(**上一年** `yak_total`) | | `biz_yak_outbound_report` | 地图标注 — 乡镇牦牛出栏(**当年** `yak_outbound_count`) | | `sys_dept` | 地图标注 — 乡镇列表(`parent_id=100` 且 `order_num>12`) | --- ## 2. 数据库设计 ### 2.1 本模块业务表 **无新增业务台账表。** DDL 以各业务 `sql/*.sql` 为准。 ### 2.2 公共参数 记 `statYear = Y`,`statDate` 见 **§1**。 | 边界 | 表达式 | | --- | --- | | 年区间起 | `Y-01-01 00:00:00` | | 年区间止(年内累计) | `Y` 为当前年:`statDate 23:59:59`;历史年:`Y-12-31 23:59:59` | | 按月分组键 | `MONTH(日期字段)`,应用层补全 1~12 | ### 2.3 可选表 `biz_home_screen_cache`(性能优化) **非本期必建。** | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | `bigint(20)` | 主键 | | `stat_year` | `int(11)` | 统计年份 | | `payload_json` | `mediumtext` | **§3.1** 响应 JSON(不含标准分页列表) | | `expire_time` | `datetime` | 过期时间 | | `create_time` | `datetime` | 写入时间 | **索引**:`UNIQUE uk_stat_year (stat_year)`。TTL 建议 **1~5 分钟**。 ### 2.4 查询口径(按表) #### 养殖产业(对齐《养殖产业数据技术方案》§2.2,按 **Y** + `statDate`) 实现复用 `BreedingIndustryStatsMapper`、`BreedingIndustryCalculator`;大屏传入**全县** `allowedPastureIds`(全部正常牧场 ID)。 | 指标 | 要点 | | --- | --- | | 存栏 / 公母牛 / 年龄结构 | `statDate` 时点;`asset_status=1`,`pasture_id` 非空且在可见牧场内 | | 年出栏 | `asset_status=4`,`status_change_date` 在 **§2.2 年区间** 且非空 | | 按月存栏趋势 | 每月末 `M`(当前月用 `statDate`);**公牛**、**母牛**两系列(`gender` 公/母),非总量一条线 | | 按月出栏 | `status_change_date` 落在该自然月 | | 年龄分档 | `M1_6` / `M7_12` / `M13_PLUS`(与养殖产业看板编码一致,对应需求 1~6 / 7~12 / 12 月龄以上) | | 牧场数 | `del_flag=0` AND `biz_status=1` | | 草场 | `del_flag=0`;面积 `SUM(area_mu)`;可用/使用中/退化分组同养殖产业 | > 首页**不含**预警牦牛;不查 `biz_yak_disease_warning`。 #### 流通主数据(**实时**,与 **Y** 无关) | 表 | 指标 | 条件 | | --- | --- | --- | | `biz_supplier` | 供应商数 | `del_flag='0'` | | `biz_distributor` | 承销商数 | `del_flag='0'` | | `biz_trade_market` | 交易市场数 | `del_flag='0'` AND `operate_status=1` | #### 服务预约 `biz_service_appointment` | 项 | 条件 | | --- | --- | | 按年 | `appoint_date >= Y-01-01` AND `appoint_date <= Y-12-31` | | 状态 | `status NOT IN (2,3)`(排除已拒绝、已取消);表无 `del_flag` | | 分类 | `provider_type`:`1` 兽医 → 展示「兽医人员」;`3` 专家 →「专家人员」;`2` 机构 →「诊疗机构」 | | 统计 | 每条记录计 **1** 人次;年度卡片为三类合计;按月 `GROUP BY MONTH(appoint_date), provider_type` | #### 补贴发放 `biz_subsidy_grant_record` | 项 | 条件 | | --- | --- | | 按年 | `grant_date` 在 **Y** 年内(含起止日) | | 分组 | `subsidy_type`:`1` 农业生产、`2` 住房基建、`3` 民生保障 | | 指标 | `SUM(subsidy_amount)`;服务端换算 **万元**(÷10000,保留 2 位小数) | #### 农技课堂 | 指标 | 表 | 条件 | | --- | --- | --- | | 课程视频数 | `biz_tech_resource` | `del_flag='0'` AND `publish_status=1` AND `resource_type='004008'` AND `publish_time` 落在 **Y** 年 | | 实战培训场次 | `biz_practical_training` | `del_flag='0'` AND `publish_status=1` AND `publish_time` 落在 **Y** 年 | | 培训内容合计 | — | 视频数 + 场次数(应用层相加) | #### 实战培训报名趋势 `biz_practical_training` | 系列 | 条件 | | --- | --- | | 活动数/月 | `del_flag='0'` AND `publish_status=1` AND `YEAR(training_time)=Y` AND `MONTH(training_time)=m` → `COUNT(*)` | | 报名人数/月 | 同上月的培训行,`SUM(actual_enrolled_count)` | | 无培训 | `hasTrainingData=false`(**§3.1**),前端走空态,**不**强行全 0 曲线 | #### 养殖标准(分页,**§3.2**) **纳入**:`publish_status=1`;`DATE(create_time)` 落在 **Y** 年内(与功能需求 **§2.9** 一致)。 **Tab → 物理表与 `standard_type` 白名单** | `category`(请求枚举) | 表 | `standard_type IN` | | --- | --- | --- | | `BREEDING_MGMT` | `biz_breeding_standard` | `001001`,`001002`,`001003`,`001004` | | `FEEDING` | `biz_feeding_standard` | `001005`,`001006` | | `EQUIPMENT` | `biz_equipment_work_standard` | `001007`,`001008`,`001009`,`001010` | | `TREATMENT` | `biz_epidemic_treatment_standard` | `001013` | | `GROWTH` | `biz_growth_slaughter_standard` | `001011`,`001012` | 排序:`create_time DESC, id DESC`。 ### 2.5 索引建议(既有表) | 表 | 建议 | | --- | --- | | `biz_yak_asset` | `(pasture_id, del_flag, asset_status)`、`status_change_date` | | `biz_service_appointment` | 沿用 `idx_appoint_date`、`idx_provider` | | `biz_subsidy_grant_record` | 沿用 `idx_grant_date`、`idx_subsidy_type` | | `biz_tech_resource` | `(resource_type, publish_status, publish_time)` | | `biz_practical_training` | 沿用 `idx_training_time`、`idx_publish` | | 各 `biz_*_standard` | 沿用 `idx_create_time`、`idx_type` | ### 2.6 Mapper 扩展(建议) | Mapper | 方法 | 说明 | | --- | --- | --- | | `BreedingIndustryStatsMapper` | (复用 + 可选新增) | `countMonthlyOutboundByMonth`、`selectYakSnapshotsInScope` 等;按月公/母牛趋势在 Service 用快照 + `BreedingIndustryCalculator` | | `BizSupplierMapper` / `BizDistributorMapper` / `BizTradeMarketMapper` | `countForScreen()` | 主数据条数 | | `BizServiceAppointmentMapper` | `selectStatsByYear(int y)` | 返回 appoint_date、provider_type 明细或按月聚合行 | | `BizSubsidyGrantRecordMapper` | `sumAmountByTypeForYear(int y)` | 三类金额 | | `BizTechResourceMapper` | `countPublishedVideoByYear(int y)` | `004008` | | `BizPracticalTrainingMapper` | `countPublishedByYear` / `selectPublishedByYear` | 场次与按月趋势 | | `HomeScreenStandardMapper` | `selectListForScreen(category, y, ...)` | 按 **§2.4** Tab 路由到对应表 | > **禁止**大屏单独实现一套牦牛月末还原公式;须调用 `BreedingIndustryCalculator`。 --- ## 3. 接口设计 **统一响应**:`AjaxResult`(`code` / `msg` / `data`);标准列表用 `TableDataInfo`(`rows` / `total`)。 **权限标识(示例)**:`bigScreen:home:query` **Base Path**:`/bigScreen/home` | # | 说明 | Method | URI | 权限 | | --- | --- | --- | --- | --- | | 3.1 | 看板主数据(除标准分页外全部区块) | GET | `/bigScreen/home/dashboard` | `bigScreen:home:query` | | 3.2 | 养殖标准分页列表 | GET | `/bigScreen/home/standards` | 同上 | | 3.3 | 地图标注(乡镇存栏/出栏) | GET | `/bigScreen/home/townMapMarkers` | 同上 | 前端:切换 `statYear` → **3.1** + **3.2**(`pageNum=1`);仅翻页 / 切 Tab → **3.2**;进入首页 / 地图刷新 → **3.3**(**不**随 `statYear` 参数变化,按服务器当前自然年计算)。 --- ### 3.1 看板主数据 #### Query | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `statYear` | string | Y | 四位年份;非法 → 业务异常(复用 `AchievementReportValidation.parseStatYear`) | | `forceRefresh` | boolean | N | 默认 `false`;`true` 时跳过 **§2.3** 缓存 | #### 响应 `data`(顶层) | 字段 | 类型 | 说明 | | --- | --- | --- | | `statYear` | int | 回显 | | `statDate` | string | 统计时点 `yyyy-MM-dd` | | `availableYears` | int[] | 各业务表出现过的年份并集 + 当前年,降序 | | `industryOverview` | object | 产业总览八项 **§3.1.1** | | `inventoryChange` | object | 存栏变动 **§3.1.2** | | `ageStructure` | array | 年龄结构 **§3.1.3** | | `outboundMonthly` | array | 出栏按月 **§3.1.4** | | `grassland` | object | 草场 **§3.1.5** | | `appointment` | object | 预约 **§3.1.6** | | `subsidy` | object | 补贴 **§3.1.7** | | `agriClassroom` | object | 农技课堂 **§3.1.8** | | `trainingTrend` | object | 实战培训报名 **§3.1.9** | #### 3.1.1 `industryOverview` | 字段 | 说明 | | --- | --- | | `inventoryTotal` | 牦牛存栏(头),`biz_yak_herd_inventory` 全县 `yak_total` 合计;年份 = 当前自然年 **− 1**;**字段名不变** | | `annualOutbound` | 年出栏(头),`biz_yak_outbound_report` 全县 `yak_outbound_count` 合计;年份 = 当前自然年;**字段名不变** | | `pastureCount` | 农牧户(户),`biz_yak_outbound_report` 全县 `farmer_household_count` 合计;年份 = 当前自然年;**字段名不变** | | `grasslandCount` / `grasslandAreaMu` | 草场、面积 | | `supplierCount` / `distributorCount` / `tradeMarketCount` | 流通三项**实时**快照 | #### 3.1.2 `inventoryChange` | 字段 | 说明 | | --- | --- | | `bullCount` / `cowCount` / `unknownGenderCount` | **statDate** 时点公/母/未标注 | | `monthlyTrend` | 固定 **12** 条 | `monthlyTrend[]`:`month`(1~12)、`bullCount`、`cowCount`(该月末还原;**Y** 为当前年时,`month > statDate.month` 均为 0)。 #### 3.1.3 `ageStructure[]` | 字段 | 说明 | | --- | --- | | `bandCode` | `M1_6` / `M7_12` / `M13_PLUS` | | `bandLabel` | 展示名 | | `count` / `ratio` | 头数;占比 **0~100** 数值(如 `37.5` 表示 37.5%) | 三档固定返回;无存栏时 `count=0`,`ratio=0`。 #### 3.1.4 `outboundMonthly[]` | 字段 | 说明 | | --- | --- | | `month` | 1~12 | | `outboundCount` | 该月出栏头数 | 固定 12 条;未来月规则同 **§1**。 #### 3.1.5 `grassland` | 字段 | 说明 | | --- | --- | | `availableCount` / `inUseCount` | 可用 / 使用中(个) | | `degradationStats[]` | `degradationLevel`(1~5)、`degradationLabel`、`count`、`ratio`(% 数值) | 五档固定返回。 #### 3.1.6 `appointment` | 字段 | 说明 | | --- | --- | | `annualVetCount` / `annualExpertCount` / `annualOrgCount` | 三类年度合计 | | `monthlyStacked[]` | 12 条:`month`、`vetCount`、`expertCount`、`orgCount` | #### 3.1.7 `subsidy` | 字段 | 说明 | | --- | --- | | `typeStats[]` | 固定 3 条:`subsidyType`(1~3)、`subsidyTypeName`、`amountWan`(万元) | | `hasSubsidyData` | **Y** 年是否存在任一条发放记录 | #### 3.1.8 `agriClassroom` | 字段 | 说明 | | --- | --- | | `videoCourseCount` | 视频课程数 | | `practicalTrainingCount` | 实战培训场次 | | `trainingContentTotal` | 二者之和 | | `hasAgriData` | 两项是否均为 0 | #### 3.1.9 `trainingTrend` | 字段 | 说明 | | --- | --- | | `hasTrainingData` | **Y** 年是否存在已发布培训 | | `monthly[]` | 12 条:`month`、`activityCount`、`enrolledCount` | `hasTrainingData=false` 时 `monthly` 可为 `[]` 或全 0(前端以 `hasTrainingData` 走空态)。 #### 响应示例(节选) ```json { "statYear": 2026, "statDate": "2026-05-20", "availableYears": [2026, 2025], "industryOverview": { "inventoryTotal": 120, "annualOutbound": 8, "pastureCount": 12, "grasslandCount": 80, "grasslandAreaMu": 12500.50, "supplierCount": 5, "distributorCount": 3, "tradeMarketCount": 2 }, "inventoryChange": { "bullCount": 45, "cowCount": 60, "unknownGenderCount": 15, "monthlyTrend": [ { "month": 1, "bullCount": 40, "cowCount": 55 }, { "month": 12, "bullCount": 0, "cowCount": 0 } ] }, "appointment": { "annualVetCount": 10, "annualExpertCount": 5, "annualOrgCount": 8, "monthlyStacked": [{ "month": 1, "vetCount": 2, "expertCount": 1, "orgCount": 0 }] }, "trainingTrend": { "hasTrainingData": true, "monthly": [{ "month": 3, "activityCount": 1, "enrolledCount": 20 }] } } ``` #### 服务端逻辑(摘要) ``` 1. year = parseStatYear(statYear); statDate = resolveStatDate(year) 2. allowedPastureIds = selectAllActivePastureIds() // 全县 3. industry + inventory + age + outbound + grassland = breeding 聚合(statDate, year, allowedPastureIds) 4. tradeCounts = supplier/distributor/market countForScreen() // 与 year 无关 5. appointment = appointmentMapper stats by year 6. subsidy = grantMapper sum by type; hasSubsidyData 7. agriClassroom = tech video count + training publish count by year 8. trainingTrend = training by month; hasTrainingData 9. availableYears = union(distinct years from各源表, current year) 10. industryOverview 填报合计填入既有字段:`inventoryTotal` / `annualOutbound` / `pastureCount`(农牧户);原牧场/快照口径在 Service 内暂保留 11. return AjaxResult.success(vo) ``` --- ### 3.2 养殖标准分页列表 #### Query | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `statYear` | string | Y | 同 **3.1** | | `category` | string | Y | `BREEDING_MGMT` / `FEEDING` / `EQUIPMENT` / `TREATMENT` / `GROWTH` | | `pageNum` | int | N | 默认 `1` | | `pageSize` | int | N | 默认 `10`;建议 **5~10** | #### 响应 `TableDataInfo`:`rows` 为 **§3.2.1** 数组,`total` 为当前 Tab 下符合条件的总数。 **不提供**大屏专用 `GET /standards/{id}`;行内详情字段一次返回(对齐共同富裕列表约定)。 #### 3.2.1 `rows[]` | 字段 | 类型 | 说明 | | --- | --- | --- | | `id` | long | 标准 ID | | `standardName` | string | 标准名称 | | `standardType` | string | 类型编码 | | `standardTypeName` | string | 类型展示名(字典/资讯类别) | | `introduction` | string | 简介 | | `contentFileUrl` | string | 正文附件 URL | | `coverFileUrl` | string | 封面 URL,可空 | | `createTime` | string | 创建时间 | | `publishTime` | string | 发布时间 | 默认排序:`create_time DESC, id DESC`。 --- ### 3.3 地图标注 #### Query 无参数。统计年份由服务器当前自然年 `Y` 推导: | 字段 | 年份 | | --- | --- | | `herdStatYear` | `Y - 1`(存栏取 `biz_yak_herd_inventory.stat_year`) | | `outboundStatYear` | `Y`(出栏取 `biz_yak_outbound_report.stat_year`) | #### 响应 `data` | 字段 | 类型 | 说明 | | --- | --- | --- | | `herdStatYear` | int | 存栏数据年份(上一年) | | `outboundStatYear` | int | 出栏数据年份(当年) | | `markers` | array | 乡镇标注列表,见下表 | `markers[]`: | 字段 | 类型 | 说明 | | --- | --- | --- | | `townDeptId` | long | 所属乡镇 `dept_id`(`sys_dept`) | | `townName` | string | 乡镇名称 | | `yakInventoryCount` | int | 牦牛存栏数(头),`biz_yak_herd_inventory.yak_total`;无填报为 **0** | | `yakOutboundCount` | int | 牦牛出栏数(头),`biz_yak_outbound_report.yak_outbound_count`;无填报为 **0** | #### 乡镇范围 与牦牛存栏/出栏填报模块一致:`sys_dept` 中 `parent_id = 100`、`order_num > 12`、`del_flag = '0'`、`status = '0'`,按 `order_num`、`dept_id` 升序。 #### 服务端逻辑(摘要) ``` 1. outboundStatYear = Year.now(); herdStatYear = outboundStatYear - 1 2. markers = HomeScreenStatsMapper.selectTownMapMarkers(herdStatYear, outboundStatYear) 3. return AjaxResult.success(vo) ``` --- ## 4. 菜单与权限(示例) | 类型 | 名称 | 权限标识 | | --- | --- | --- | | 菜单 | 大屏首页 | `bigScreen:home:query` | SQL 示例:`sql/big_screen_home_perm.sql`(挂载「大屏」父菜单下)。 --- ## 5. 交付清单 - [ ] `HomeScreenController` + `IHomeScreenService` + VO + `HomeScreenSupport` - [ ] 复用 `BreedingIndustryStatsMapper` / `BreedingIndustryCalculator`(按 **Y** 与全县牧场) - [ ] 预约、补贴、标准、科技资源、实战培训 Mapper 扩展(**§2.6**) - [ ] 单元测试:历史年 `statDate`、12 月补 0、主数据不随 **Y** 变、补贴万元、无培训空态、标准 Tab 路由 - [ ] 接口测试:`/bigScreen/home/dashboard`、`/standards`、`/townMapMarkers` - [ ] 菜单权限 SQL;依赖各业务表 DDL 已执行 --- ## 6. 修订记录 | 版本 | 日期 | 说明 | | --- | --- | --- | | 1.0 | 2026-05-20 | 初稿:无新表;双接口;对齐 `大屏首页功能需求.md` v1.0 |