xsh_1997 il y a 1 semaine
Parent
commit
e635125a93

+ 104 - 0
doc/产业数据模型及服务/农村三资数据填报/农村三资数据填报前端技术方案.md

@@ -0,0 +1,104 @@
1
+# 农村三资数据填报 — 前端技术方案
2
+
3
+> 依据:同目录功能需求、技术方案;村级唯一 CRUD;**无**导入导出;字段 **小驼峰**。
4
+
5
+---
6
+
7
+## 1. 页面与路由
8
+
9
+| 项 | 说明 |
10
+| --- | --- |
11
+| **Vue 路径** | `ruoyi-ui/src/views/dataModel/ruralThreeAssets/index.vue` |
12
+| **组件名** | `RuralThreeAssets` |
13
+| **菜单配置** | 组件路径:`dataModel/ruralThreeAssets/index` |
14
+| **权限前缀** | `dataModel:ruralThreeAssets:list\|query\|add\|edit\|remove` |
15
+
16
+---
17
+
18
+## 2. API 模块
19
+
20
+| 项 | 说明 |
21
+| --- | --- |
22
+| **文件** | `ruoyi-ui/src/api/dataModel/ruralThreeAssets.js` |
23
+| **Base** | `/dataModel/ruralThreeAssets` |
24
+
25
+| 方法 | 路径 | 说明 |
26
+| --- | --- | --- |
27
+| `listRuralThreeAssets` | GET `/list` | 分页;筛选乡镇、村 |
28
+| `getRuralThreeAssets` | GET `/{id}` | 详情(含派生固定资产) |
29
+| `addRuralThreeAssets` | POST `/` | 新增 |
30
+| `updateRuralThreeAssets` | PUT `/` | 修改 |
31
+| `delRuralThreeAssets` | DELETE `/{ids}` | 删除 |
32
+| `listRuralThreeAssetsTownOptions` | GET `/townOptions` | 乡镇下拉 |
33
+| `listRuralThreeAssetsVillageTree` | GET `/villageTree` | 县→乡镇→村树 |
34
+
35
+本期 **无** `yearOptions` / 导入 / 导出。
36
+
37
+---
38
+
39
+## 3. 页面结构
40
+
41
+1. **筛选**:乡镇、村(可搜索)。  
42
+2. **列表**:乡镇、村、经营性固定资产、非经营性固定资产、固定资产、操作。  
43
+3. **新增/编辑**:只选村(标签「乡镇 / 村」);金额单位「万元」;固定资产只读预览。  
44
+4. **详情**:含乡镇;派生固定资产展示。
45
+
46
+### 表单 / 查看分区
47
+
48
+| 分区 | 内容 |
49
+| --- | --- |
50
+| 基本信息 | 村(查看含乡镇) |
51
+| 三资情况 | 经营性固定资产、非经营性固定资产;只读固定资产;备注 |
52
+
53
+### 字段映射
54
+
55
+| 界面 | 字段 |
56
+| --- | --- |
57
+| 经营性固定资产 | `operatingFixedAssets` |
58
+| 非经营性固定资产 | `nonOperatingFixedAssets` |
59
+| 固定资产(派生) | `fixedAssets` |
60
+
61
+---
62
+
63
+## 4. 业务规则
64
+
65
+| 规则 | 实现 |
66
+| --- | --- |
67
+| 唯一键 | 村(提交只传 `villageDeptId`;乡镇由后端反推) |
68
+| 必填 | 村 |
69
+| 金额 | 经营性/非经营性可选;≥0;单位「万元」;精度 2 位 |
70
+| 固定资产预览 | 二者皆空 →「—」;否则 `(经营性\|\|0)+(非经营性\|\|0)`,保留 2 位 |
71
+| 落库 | 后端 `RuralThreeAssetsDerivedSupport` 计算并写入 |
72
+
73
+---
74
+
75
+## 5. 国际化
76
+
77
+`dataModel.ruralThreeAssets`(`lang/zh|bo/dataModel.js`);页面 `dmNs = 'ruralThreeAssets'`,复用 `dataModelLocaleMixin`。查看表单使用 `dm-view-form`。
78
+
79
+---
80
+
81
+## 6. 联调说明
82
+
83
+1. 执行 `sql/biz_rural_three_assets.sql`。  
84
+2. 菜单组件 `dataModel/ruralThreeAssets/index`,权限见 §1。  
85
+3. 验证:同村不可重复新增、固定资产预览与保存后一致、无导入导出入口。
86
+
87
+---
88
+
89
+## 7. 文件清单
90
+
91
+| 类型 | 路径 |
92
+| --- | --- |
93
+| 页面 | `ruoyi-ui/src/views/dataModel/ruralThreeAssets/index.vue` |
94
+| API | `ruoyi-ui/src/api/dataModel/ruralThreeAssets.js` |
95
+| i18n | `lang/zh/dataModel.js`、`lang/bo/dataModel.js` → `ruralThreeAssets` |
96
+| 前端技术方案 | 本文档 |
97
+
98
+---
99
+
100
+## 8. 修订记录
101
+
102
+| 版本 | 说明 |
103
+| --- | --- |
104
+| 1.0 | 初版:村级唯一 CRUD;固定资产只读预览;无导入导出 |

+ 8 - 1
doc/产业数据模型及服务/农村三资数据填报/农村三资数据填报功能需求.md

@@ -37,8 +37,15 @@
37 37
 
38 38
 ---
39 39
 
40
-## 5. 修订记录
40
+## 5. 相关文档
41
+
42
+- [农村三资数据填报技术方案.md](./农村三资数据填报技术方案.md)
43
+- [农村三资数据填报前端技术方案.md](./农村三资数据填报前端技术方案.md)
44
+- [农村三资数据填报测试用例.md](./农村三资数据填报测试用例.md)
45
+
46
+## 6. 修订记录
41 47
 
42 48
 | 版本 | 说明 |
43 49
 | --- | --- |
44 50
 | 1.0 | 初版:村级 CRUD + 固定资产派生 |
51
+| 1.1 | 补充前端技术方案链接 |

+ 3 - 0
doc/产业数据模型及服务/农村三资数据填报/农村三资数据填报技术方案.md

@@ -31,6 +31,8 @@ DDL:`sql/biz_rural_three_assets.sql`
31 31
 
32 32
 ## 4. 前端
33 33
 
34
+详见同目录 **[农村三资数据填报前端技术方案.md](./农村三资数据填报前端技术方案.md)**。
35
+
34 36
 - 页面:`ruoyi-ui/src/views/dataModel/ruralThreeAssets/index.vue`
35 37
 - API:`ruoyi-ui/src/api/dataModel/ruralThreeAssets.js`
36 38
 - 表单只选村;固定资产只读预览;列表筛乡镇/村
@@ -40,3 +42,4 @@ DDL:`sql/biz_rural_three_assets.sql`
40 42
 | 版本 | 说明 |
41 43
 | --- | --- |
42 44
 | 1.0 | 初版 |
45
+| 1.1 | 补充前端技术方案链接 |

+ 117 - 0
doc/产业数据模型及服务/有无畜户数据填报/有无畜户数据填报前端技术方案.md

@@ -0,0 +1,117 @@
1
+# 有无畜户数据填报 — 前端技术方案
2
+
3
+> 依据:同目录功能需求、技术方案;交互对齐疫苗接种等村×年月填报;字段 **小驼峰**。
4
+
5
+---
6
+
7
+## 1. 页面与路由
8
+
9
+| 项 | 说明 |
10
+| --- | --- |
11
+| **Vue 路径** | `ruoyi-ui/src/views/dataModel/livestockOwnershipHousehold/index.vue` |
12
+| **组件名** | `LivestockOwnershipHousehold` |
13
+| **菜单配置** | 组件路径:`dataModel/livestockOwnershipHousehold/index` |
14
+| **权限前缀** | `dataModel:livestockOwnershipHousehold:list\|query\|add\|edit\|remove\|import\|export` |
15
+
16
+---
17
+
18
+## 2. API 模块
19
+
20
+| 项 | 说明 |
21
+| --- | --- |
22
+| **文件** | `ruoyi-ui/src/api/dataModel/livestockOwnershipHousehold.js` |
23
+| **Base** | `/dataModel/livestockOwnershipHousehold` |
24
+
25
+CRUD、`yearOptions` / `monthOptions` / `townOptions` / `villageTree`、导入 `importData`、模板 `importTemplate`、导出 `export`(可选 `townDeptId`)与同类村级填报模块同构;模板/导出均为 **GET + blob**。
26
+
27
+| 方法 | 路径 | 说明 |
28
+| --- | --- | --- |
29
+| `listLivestockOwnershipHousehold` | GET `/list` | 分页;筛选年、月、乡镇、村 |
30
+| `getLivestockOwnershipHousehold` | GET `/{id}` | 详情 |
31
+| `addLivestockOwnershipHousehold` | POST `/` | 新增 |
32
+| `updateLivestockOwnershipHousehold` | PUT `/` | 修改 |
33
+| `delLivestockOwnershipHousehold` | DELETE `/{ids}` | 删除 |
34
+| `listLivestockOwnershipHouseholdYearOptions` | GET `/yearOptions` | 年份下拉 |
35
+| `listLivestockOwnershipHouseholdMonthOptions` | GET `/monthOptions` | 月份下拉 |
36
+| `listLivestockOwnershipHouseholdTownOptions` | GET `/townOptions` | 乡镇下拉 |
37
+| `listLivestockOwnershipHouseholdVillageTree` | GET `/villageTree` | 县→乡镇→村树 |
38
+| `importLivestockOwnershipHousehold` | POST `/importData` | FormData:`file` + `statYear` + `statMonth` |
39
+| `downloadLivestockOwnershipHouseholdTemplate` | GET `/importTemplate` | 模板 blob |
40
+| `exportLivestockOwnershipHousehold` | GET `/export` | 参数:`statYear`、`statMonth`、可选 `townDeptId` |
41
+
42
+---
43
+
44
+## 3. 页面结构
45
+
46
+1. **筛选**:年、月、乡镇、村(村可搜索)。  
47
+2. **列表**:年、月、乡镇、村;有畜户(户数/人数/有劳力/牲畜数);无畜户(户数/人数/有劳力);操作。  
48
+3. **新增/编辑**:选村(标签「乡镇 / 村」);新增默认本年本月;计数字段选填、非负整数。  
49
+4. **详情**:与表单同分区;查看含乡镇;数值带单位。  
50
+5. **导入**:选年月 → upsert 摘要;任一行失败整批不写(后端)。  
51
+6. **导出**:选年月;乡镇打开时带入列表当前筛选,清空=全部。
52
+
53
+### 表单 / 查看分区
54
+
55
+| 分区 | 内容 |
56
+| --- | --- |
57
+| 基本信息 | 年月、村(查看含乡镇) |
58
+| 有畜户 | 户数(户)、人数(人)、有劳力(人)、牲畜数(头只) |
59
+| 无畜户 | 户数(户)、人数(人)、有劳力(人) |
60
+| — | 备注 |
61
+
62
+### 字段映射
63
+
64
+| 界面 | 字段 |
65
+| --- | --- |
66
+| 有畜户户数 | `withLivestockHouseholdCount` |
67
+| 有畜户人数 | `withLivestockPopulationCount` |
68
+| 有畜户有劳力 | `withLivestockLaborCount` |
69
+| 有畜户牲畜数 | `withLivestockAnimalCount` |
70
+| 无畜户户数 | `withoutLivestockHouseholdCount` |
71
+| 无畜户人数 | `withoutLivestockPopulationCount` |
72
+| 无畜户有劳力 | `withoutLivestockLaborCount` |
73
+
74
+---
75
+
76
+## 4. 业务规则
77
+
78
+| 规则 | 实现 |
79
+| --- | --- |
80
+| 唯一键 | 年 + 月 + 村(提交只传 `villageDeptId`;乡镇由后端反推) |
81
+| 必填 | 年、月、村 |
82
+| 计数 | 七项可选;非负整数 |
83
+| 不可未来月 | 表单 / 导入 / 导出校验 |
84
+| 导入 | 同年同月同村 upsert |
85
+
86
+---
87
+
88
+## 5. 国际化
89
+
90
+`dataModel.livestockOwnershipHousehold`(`lang/zh|bo/dataModel.js`);页面 `dmNs = 'livestockOwnershipHousehold'`,复用 `dataModelLocaleMixin`。查看表单使用 `dm-view-form`。
91
+
92
+---
93
+
94
+## 6. 联调说明
95
+
96
+1. 执行 `sql/biz_livestock_ownership_household.sql`。  
97
+2. 菜单组件 `dataModel/livestockOwnershipHousehold/index`,权限见 §1。  
98
+3. 验证:新增默认年月、分区填报、导入 upsert、导出乡镇带入列表筛选。
99
+
100
+---
101
+
102
+## 7. 文件清单
103
+
104
+| 类型 | 路径 |
105
+| --- | --- |
106
+| 页面 | `ruoyi-ui/src/views/dataModel/livestockOwnershipHousehold/index.vue` |
107
+| API | `ruoyi-ui/src/api/dataModel/livestockOwnershipHousehold.js` |
108
+| i18n | `lang/zh/dataModel.js`、`lang/bo/dataModel.js` → `livestockOwnershipHousehold` |
109
+| 前端技术方案 | 本文档 |
110
+
111
+---
112
+
113
+## 8. 修订记录
114
+
115
+| 版本 | 说明 |
116
+| --- | --- |
117
+| 1.0 | 初版:村×年月;CRUD + upsert 导入 + 导出;有畜户/无畜户分区 |

+ 8 - 1
doc/产业数据模型及服务/有无畜户数据填报/有无畜户数据填报功能需求.md

@@ -40,8 +40,15 @@
40 40
 
41 41
 ---
42 42
 
43
-## 5. 修订记录
43
+## 5. 相关文档
44
+
45
+- [有无畜户数据填报技术方案.md](./有无畜户数据填报技术方案.md)
46
+- [有无畜户数据填报前端技术方案.md](./有无畜户数据填报前端技术方案.md)
47
+- [有无畜户数据填报测试用例.md](./有无畜户数据填报测试用例.md)
48
+
49
+## 6. 修订记录
44 50
 
45 51
 | 版本 | 说明 |
46 52
 | --- | --- |
47 53
 | 1.0 | 初版:对齐牦牛出栏骨架,村×年月填报 |
54
+| 1.1 | 补充前端技术方案链接 |

+ 11 - 1
doc/产业数据模型及服务/有无畜户数据填报/有无畜户数据填报技术方案.md

@@ -33,8 +33,18 @@ Controller `/dataModel/livestockOwnershipHousehold` → Service → Mapper;Exc
33 33
 - 导入:系统选年月 → 解析 → 乡镇/村识别 → 非负校验 → upsert。
34 34
 - 导出:改标题年月、填表人/时间;写村行;末行「合计」(合并序号~村列)。
35 35
 
36
-## 5. 修订记录
36
+## 5. 前端
37
+
38
+详见同目录 **[有无畜户数据填报前端技术方案.md](./有无畜户数据填报前端技术方案.md)**。
39
+
40
+- 页面:`ruoyi-ui/src/views/dataModel/livestockOwnershipHousehold/index.vue`
41
+- API:`ruoyi-ui/src/api/dataModel/livestockOwnershipHousehold.js`
42
+- 菜单组件:`dataModel/livestockOwnershipHousehold/index`
43
+- i18n:`dataModel.livestockOwnershipHousehold`(zh/bo)
44
+
45
+## 6. 修订记录
37 46
 
38 47
 | 版本 | 说明 |
39 48
 | --- | --- |
40 49
 | 1.0 | 初版 |
50
+| 1.1 | 补充前端技术方案链接 |

+ 120 - 0
ruoyi-ui/src/api/dataModel/livestockOwnershipHousehold.js

@@ -0,0 +1,120 @@
1
+import request from "@/utils/request"
2
+
3
+/** 分页列表 */
4
+export function listLivestockOwnershipHousehold(query) {
5
+  return request({
6
+    url: "/dataModel/livestockOwnershipHousehold/list",
7
+    method: "get",
8
+    params: query
9
+  })
10
+}
11
+
12
+/** 详情 */
13
+export function getLivestockOwnershipHousehold(id) {
14
+  return request({
15
+    url: "/dataModel/livestockOwnershipHousehold/" + id,
16
+    method: "get"
17
+  })
18
+}
19
+
20
+/** 新增 */
21
+export function addLivestockOwnershipHousehold(data) {
22
+  return request({
23
+    url: "/dataModel/livestockOwnershipHousehold",
24
+    method: "post",
25
+    data
26
+  })
27
+}
28
+
29
+/** 修改 */
30
+export function updateLivestockOwnershipHousehold(data) {
31
+  return request({
32
+    url: "/dataModel/livestockOwnershipHousehold",
33
+    method: "put",
34
+    data
35
+  })
36
+}
37
+
38
+/** 删除 */
39
+export function delLivestockOwnershipHousehold(ids) {
40
+  return request({
41
+    url: "/dataModel/livestockOwnershipHousehold/" + ids,
42
+    method: "delete"
43
+  })
44
+}
45
+
46
+/** 可选年份 */
47
+export function listLivestockOwnershipHouseholdYearOptions() {
48
+  return request({
49
+    url: "/dataModel/livestockOwnershipHousehold/yearOptions",
50
+    method: "get"
51
+  })
52
+}
53
+
54
+/** 可选月份(1~12) */
55
+export function listLivestockOwnershipHouseholdMonthOptions() {
56
+  return request({
57
+    url: "/dataModel/livestockOwnershipHousehold/monthOptions",
58
+    method: "get"
59
+  })
60
+}
61
+
62
+/** 乡镇下拉 */
63
+export function listLivestockOwnershipHouseholdTownOptions() {
64
+  return request({
65
+    url: "/dataModel/livestockOwnershipHousehold/townOptions",
66
+    method: "get"
67
+  })
68
+}
69
+
70
+/** 县→乡镇→村树 */
71
+export function listLivestockOwnershipHouseholdVillageTree() {
72
+  return request({
73
+    url: "/dataModel/livestockOwnershipHousehold/villageTree",
74
+    method: "get"
75
+  })
76
+}
77
+
78
+/** Excel 导入 */
79
+export function importLivestockOwnershipHousehold(file, statYear, statMonth) {
80
+  const formData = new FormData()
81
+  formData.append("file", file, file.name)
82
+  formData.append("statYear", String(statYear))
83
+  formData.append("statMonth", String(statMonth))
84
+  return request({
85
+    url: "/dataModel/livestockOwnershipHousehold/importData",
86
+    method: "post",
87
+    headers: {
88
+      repeatSubmit: false
89
+    },
90
+    data: formData
91
+  })
92
+}
93
+
94
+/** 下载导入模板(GET) */
95
+export function downloadLivestockOwnershipHouseholdTemplate() {
96
+  return request({
97
+    url: "/dataModel/livestockOwnershipHousehold/importTemplate",
98
+    method: "get",
99
+    responseType: "blob"
100
+  })
101
+}
102
+
103
+/**
104
+ * 按年月导出(GET)
105
+ * @param {number} statYear
106
+ * @param {number} statMonth
107
+ * @param {number} [townDeptId] 可选乡镇
108
+ */
109
+export function exportLivestockOwnershipHousehold(statYear, statMonth, townDeptId) {
110
+  const params = { statYear, statMonth }
111
+  if (townDeptId != null && townDeptId !== "") {
112
+    params.townDeptId = townDeptId
113
+  }
114
+  return request({
115
+    url: "/dataModel/livestockOwnershipHousehold/export",
116
+    method: "get",
117
+    params,
118
+    responseType: "blob"
119
+  })
120
+}

+ 60 - 0
ruoyi-ui/src/api/dataModel/ruralThreeAssets.js

@@ -0,0 +1,60 @@
1
+import request from "@/utils/request"
2
+
3
+/** 分页列表 */
4
+export function listRuralThreeAssets(query) {
5
+  return request({
6
+    url: "/dataModel/ruralThreeAssets/list",
7
+    method: "get",
8
+    params: query
9
+  })
10
+}
11
+
12
+/** 详情 */
13
+export function getRuralThreeAssets(id) {
14
+  return request({
15
+    url: "/dataModel/ruralThreeAssets/" + id,
16
+    method: "get"
17
+  })
18
+}
19
+
20
+/** 新增 */
21
+export function addRuralThreeAssets(data) {
22
+  return request({
23
+    url: "/dataModel/ruralThreeAssets",
24
+    method: "post",
25
+    data
26
+  })
27
+}
28
+
29
+/** 修改 */
30
+export function updateRuralThreeAssets(data) {
31
+  return request({
32
+    url: "/dataModel/ruralThreeAssets",
33
+    method: "put",
34
+    data
35
+  })
36
+}
37
+
38
+/** 删除 */
39
+export function delRuralThreeAssets(ids) {
40
+  return request({
41
+    url: "/dataModel/ruralThreeAssets/" + ids,
42
+    method: "delete"
43
+  })
44
+}
45
+
46
+/** 乡镇下拉 */
47
+export function listRuralThreeAssetsTownOptions() {
48
+  return request({
49
+    url: "/dataModel/ruralThreeAssets/townOptions",
50
+    method: "get"
51
+  })
52
+}
53
+
54
+/** 县→乡镇→村树 */
55
+export function listRuralThreeAssetsVillageTree() {
56
+  return request({
57
+    url: "/dataModel/ruralThreeAssets/villageTree",
58
+    method: "get"
59
+  })
60
+}

+ 95 - 0
ruoyi-ui/src/lang/bo/dataModel.js

@@ -1055,5 +1055,100 @@ export default {
1055 1055
     unitSheepUnit: "ལུག་རིམ།",
1056 1056
     dateRangeInvalid: "འགོ་ཚེས་མཇུག་ཚེས་ལས་ཕྱི་མི་ཆོག",
1057 1057
     emptyList: "ཉེན་བརྡ་གཞི་གྲངས་མེད།"
1058
+  },
1059
+  livestockOwnershipHousehold: {
1060
+    queryStatYear: "གཏོགས་ལོ།",
1061
+    queryStatMonth: "གཏོགས་ཟླ།",
1062
+    queryTown: "ཤང་གྲོང།",
1063
+    queryVillage: "གྲོང་ཚོ།",
1064
+    phVillageSearch: "འདེམས་པའམ་འཚོལ་འཇུག་རོགས།",
1065
+    phRemark: "འདེམས་འབྲི,ཡིག་ཆ་500ལས་མི་ཆོག",
1066
+    colStatYear: "གཏོགས་ལོ།",
1067
+    colStatMonth: "གཏོགས་ཟླ།",
1068
+    statMonthLabel: "{month}ཟླ།",
1069
+    colTownName: "ཤང་གྲོང།",
1070
+    colVillageName: "གྲོང་ཚོ།",
1071
+    colWithHousehold: "ཕྱུགས་ཡོད་ཁྱིམ་གྲངས།",
1072
+    colWithPopulation: "ཕྱུགས་ཡོད་མི་གྲངས།",
1073
+    colWithLabor: "ཕྱུགས་ཡོད་ངལ་རྩོལ།",
1074
+    colWithAnimal: "ཕྱུགས་ཡོད་ཕྱུགས་གྲངས།",
1075
+    colWithoutHousehold: "ཕྱུགས་མེད་ཁྱིམ་གྲངས།",
1076
+    colWithoutPopulation: "ཕྱུགས་མེད་མི་གྲངས།",
1077
+    colWithoutLabor: "ཕྱུགས་མེད་ངལ་རྩོལ།",
1078
+    formStatYear: "གཏོགས་ལོ།",
1079
+    formStatMonth: "གཏོགས་ཟླ།",
1080
+    formTown: "ཤང་གྲོང།",
1081
+    formVillage: "གྲོང་ཚོ།",
1082
+    formHouseholdCount: "ཁྱིམ་གྲངས།",
1083
+    formPopulationCount: "མི་གྲངས།",
1084
+    formLaborCount: "ངལ་རྩོལ་ཡོད།",
1085
+    formAnimalCount: "ཕྱུགས་གྲངས།",
1086
+    formRemark: "ཟིན་བྲིས།",
1087
+    sectionBasic: "གཞི་རྩའི་གནས་ཚུལ།",
1088
+    sectionWith: "ཕྱུགས་ཡོད་ཁྱིམ།",
1089
+    sectionWithout: "ཕྱུགས་མེད་ཁྱིམ།",
1090
+    btnImport: "ནང་འདྲེན།",
1091
+    btnDownloadTemplate: "དཔེ་ཡིག་ཕབ་ལེན།",
1092
+    exportTitle: "Excel ཕྱིར་འདྲེན།",
1093
+    exportStatYear: "གཏོགས་ལོ།",
1094
+    exportStatYearPh: "གཏོགས་ལོ་འདེམས་རོགས།",
1095
+    exportStatMonth: "གཏོགས་ཟླ།",
1096
+    exportStatMonthPh: "གཏོགས་ཟླ་འདེམས་རོགས།",
1097
+    exportTown: "ཤང་གྲོང།",
1098
+    exportTownPh: "མ་འདེམས་ན་ལོ་ཟླ་དེའི་ཤང་གྲོང་ཚང་མ་ཕྱིར་འདྲེན།",
1099
+    exportStatYearRequired: "གཏོགས་ལོ་འདེམས་རོགས།",
1100
+    exportStatMonthRequired: "གཏོགས་ཟླ་འདེམས་རོགས།",
1101
+    exportLoading: "ཕྱིར་འདྲེན་བཞིན... སྒུག་རོགས།",
1102
+    exportFail: "ཕྱིར་འདྲེན་ཕམ།",
1103
+    importTitle: "Excel ནང་འདྲེན།",
1104
+    importStatYear: "འབྲི་ལོ།",
1105
+    importStatYearPh: "འབྲི་ལོ་འདེམས་རོགས།",
1106
+    importStatMonth: "འབྲི་ཟླ།",
1107
+    importStatMonthPh: "འབྲི་ཟླ་འདེམས་རོགས།",
1108
+    importTip: "སྔོན་ལ་འབྲི་ལོ་ཟླ་འདེམས,དེ་ནས་དཔེ་ཡིག་དང་མཐུན་པའི .xlsx ཡར་འཇུག;ལོ་ཟླ་གྲོང་ཚོ་གཅིག་པ་ཡོད་ན་རྩོམ་སྒྲིག",
1109
+    importLoading: "ནང་འདྲེན་བཞིན... སྒུག་རོགས།",
1110
+    importSummary: "ནང་འདྲེན་ཟིན:གསར་{insert} རྩོམ་{update} ཕམ་{fail}",
1111
+    importWarnTitle: "ནང་འདྲེན་ཉེན་བརྡ།",
1112
+    importFailTitle: "ནང་འདྲེན་ཕམ་ཞིབ།",
1113
+    dialogAdd: "[གསར་སྣོན]ཕྱུགས་ཡོད་མེད་ཁྱིམ་གཞི་གྲངས།",
1114
+    dialogEdit: "[རྩོམ་སྒྲིག]ཕྱུགས་ཡོད་མེད་ཁྱིམ་གཞི་གྲངས།",
1115
+    confirmDelete: "ཐོ་གཞུང་འདི་སུབ་རྒྱུ་གཏན་ཁེལ་ལམ?",
1116
+    emptyList: "འབྲི་གཞི་གྲངས་མེད།",
1117
+    unitHousehold: "ཁྱིམ།",
1118
+    unitPerson: "མི།",
1119
+    unitHeadPiece: "མགོ་ཐོ།",
1120
+    ruleStatYearRequired: "གཏོགས་ལོ་འདེམས་རོགས།",
1121
+    ruleStatMonthRequired: "གཏོགས་ཟླ་འདེམས་རོགས།",
1122
+    ruleStatMonthFuture: "མ་འོངས་ཟླ་འབྲི་མི་ཆོག",
1123
+    ruleVillageRequired: "གྲོང་ཚོ་འདེམས་རོགས།",
1124
+    ruleCountNonNegative: "0་ཡན་གྱི་ཧྲིལ་གྲངས་དགོས།",
1125
+    villageTreeEmpty: "གྲོང་ཚོའི་སྒྲིག་འཛུགས་མ་ལོན,ལས་ཁུངས་ནང་「ཤང་གྲོང→གྲོང་ཚོ」ཡོད་མེད་ཞིབ་རོགས།"
1126
+  },
1127
+  ruralThreeAssets: {
1128
+    queryTown: "ཤང་གྲོང།",
1129
+    queryVillage: "གྲོང་ཚོ།",
1130
+    phVillageSearch: "འདེམས་པའམ་འཚོལ་འཇུག་རོགས།",
1131
+    phRemark: "འདེམས་འབྲི,ཡིག་ཆ་500ལས་མི་ཆོག",
1132
+    colTownName: "ཤང་གྲོང།",
1133
+    colVillageName: "གྲོང་ཚོ།",
1134
+    colOperatingFixedAssets: "གཉེར་སྐྱོང་རང་གཏན་རྒྱུ་ནོར།",
1135
+    colNonOperatingFixedAssets: "གཉེར་སྐྱོང་མིན་པའི་རང་གཏན་རྒྱུ་ནོར།",
1136
+    colFixedAssets: "རང་གཏན་རྒྱུ་ནོར།",
1137
+    formTown: "ཤང་གྲོང།",
1138
+    formVillage: "གྲོང་ཚོ།",
1139
+    formOperatingFixedAssets: "གཉེར་སྐྱོང་རང་གཏན་རྒྱུ་ནོར།",
1140
+    formNonOperatingFixedAssets: "གཉེར་སྐྱོང་མིན་པའི་རང་གཏན་རྒྱུ་ནོར།",
1141
+    formFixedAssets: "རང་གཏན་རྒྱུ་ནོར།",
1142
+    formRemark: "ཟིན་བྲིས།",
1143
+    sectionBasic: "གཞི་རྩའི་གནས་ཚུལ།",
1144
+    sectionAssets: "རྒྱུ་ནོར་གསུམ་གནས་ཚུལ།",
1145
+    dialogAdd: "[གསར་སྣོན]གྲོང་གསེབ་རྒྱུ་ནོར་གསུམ་གཞི་གྲངས།",
1146
+    dialogEdit: "[རྩོམ་སྒྲིག]གྲོང་གསེབ་རྒྱུ་ནོར་གསུམ་གཞི་གྲངས།",
1147
+    confirmDelete: "ཐོ་གཞུང་འདི་སུབ་རྒྱུ་གཏན་ཁེལ་ལམ?",
1148
+    emptyList: "འབྲི་གཞི་གྲངས་མེད།",
1149
+    unitWanYuan: "ཁྲི་སྒོར།",
1150
+    ruleVillageRequired: "གྲོང་ཚོ་འདེམས་རོགས།",
1151
+    ruleMoneyNonNegative: "0་ཡན་གྱི་གྲངས་དགོས།",
1152
+    villageTreeEmpty: "གྲོང་ཚོའི་སྒྲིག་འཛུགས་མ་ལོན,ལས་ཁུངས་ནང་「ཤང་གྲོང→གྲོང་ཚོ」ཡོད་མེད་ཞིབ་རོགས།"
1058 1153
   }
1059 1154
 }

+ 95 - 0
ruoyi-ui/src/lang/zh/dataModel.js

@@ -1055,5 +1055,100 @@ export default {
1055 1055
     unitSheepUnit: "绵羊单位",
1056 1056
     dateRangeInvalid: "开始日期不能晚于结束日期",
1057 1057
     emptyList: "暂无预警数据"
1058
+  },
1059
+  livestockOwnershipHousehold: {
1060
+    queryStatYear: "所属年份",
1061
+    queryStatMonth: "所属月份",
1062
+    queryTown: "所属乡镇",
1063
+    queryVillage: "所属村",
1064
+    phVillageSearch: "请选择或输入搜索",
1065
+    phRemark: "选填,不超过 500 字",
1066
+    colStatYear: "所属年份",
1067
+    colStatMonth: "所属月份",
1068
+    statMonthLabel: "{month}月",
1069
+    colTownName: "所属乡镇",
1070
+    colVillageName: "所属村",
1071
+    colWithHousehold: "有畜户户数",
1072
+    colWithPopulation: "有畜户人数",
1073
+    colWithLabor: "有畜户有劳力",
1074
+    colWithAnimal: "有畜户牲畜数",
1075
+    colWithoutHousehold: "无畜户户数",
1076
+    colWithoutPopulation: "无畜户人数",
1077
+    colWithoutLabor: "无畜户有劳力",
1078
+    formStatYear: "所属年份",
1079
+    formStatMonth: "所属月份",
1080
+    formTown: "所属乡镇",
1081
+    formVillage: "所属村",
1082
+    formHouseholdCount: "户数",
1083
+    formPopulationCount: "人数",
1084
+    formLaborCount: "有劳力",
1085
+    formAnimalCount: "牲畜数",
1086
+    formRemark: "备注",
1087
+    sectionBasic: "基本信息",
1088
+    sectionWith: "有畜户",
1089
+    sectionWithout: "无畜户",
1090
+    btnImport: "导入",
1091
+    btnDownloadTemplate: "下载模板",
1092
+    exportTitle: "Excel 导出",
1093
+    exportStatYear: "所属年份",
1094
+    exportStatYearPh: "请选择所属年份",
1095
+    exportStatMonth: "所属月份",
1096
+    exportStatMonthPh: "请选择所属月份",
1097
+    exportTown: "所属乡镇",
1098
+    exportTownPh: "不选则导出该年月全部乡镇",
1099
+    exportStatYearRequired: "请选择所属年份",
1100
+    exportStatMonthRequired: "请选择所属月份",
1101
+    exportLoading: "正在导出,请稍候...",
1102
+    exportFail: "导出失败",
1103
+    importTitle: "Excel 导入",
1104
+    importStatYear: "填报年份",
1105
+    importStatYearPh: "请选择填报年份",
1106
+    importStatMonth: "填报月份",
1107
+    importStatMonthPh: "请选择填报月份",
1108
+    importTip: "请先选择填报年月,再上传与模板格式一致的 .xlsx 文件;同年同月同村已存在则更新",
1109
+    importLoading: "正在导入,请稍候...",
1110
+    importSummary: "导入完成:新增 {insert} 条,更新 {update} 条,失败 {fail} 条",
1111
+    importWarnTitle: "导入警告",
1112
+    importFailTitle: "导入失败明细",
1113
+    dialogAdd: "[新增]有无畜户数据",
1114
+    dialogEdit: "[编辑]有无畜户数据",
1115
+    confirmDelete: "是否确认删除这条数据?",
1116
+    emptyList: "暂无填报数据",
1117
+    unitHousehold: "户",
1118
+    unitPerson: "人",
1119
+    unitHeadPiece: "头只",
1120
+    ruleStatYearRequired: "请选择所属年份",
1121
+    ruleStatMonthRequired: "请选择所属月份",
1122
+    ruleStatMonthFuture: "不可填报未来月份",
1123
+    ruleVillageRequired: "请选择所属村",
1124
+    ruleCountNonNegative: "须为大于等于 0 的整数",
1125
+    villageTreeEmpty: "未加载到村级组织数据,请确认系统部门已维护「乡镇→村」"
1126
+  },
1127
+  ruralThreeAssets: {
1128
+    queryTown: "所属乡镇",
1129
+    queryVillage: "所属村",
1130
+    phVillageSearch: "请选择或输入搜索",
1131
+    phRemark: "选填,不超过 500 字",
1132
+    colTownName: "所属乡镇",
1133
+    colVillageName: "所属村",
1134
+    colOperatingFixedAssets: "经营性固定资产",
1135
+    colNonOperatingFixedAssets: "非经营性固定资产",
1136
+    colFixedAssets: "固定资产",
1137
+    formTown: "所属乡镇",
1138
+    formVillage: "所属村",
1139
+    formOperatingFixedAssets: "经营性固定资产",
1140
+    formNonOperatingFixedAssets: "非经营性固定资产",
1141
+    formFixedAssets: "固定资产",
1142
+    formRemark: "备注",
1143
+    sectionBasic: "基本信息",
1144
+    sectionAssets: "三资情况",
1145
+    dialogAdd: "[新增]农村三资数据",
1146
+    dialogEdit: "[编辑]农村三资数据",
1147
+    confirmDelete: "是否确认删除这条数据?",
1148
+    emptyList: "暂无填报数据",
1149
+    unitWanYuan: "万元",
1150
+    ruleVillageRequired: "请选择所属村",
1151
+    ruleMoneyNonNegative: "须为大于等于 0 的数字",
1152
+    villageTreeEmpty: "未加载到村级组织数据,请确认系统部门已维护「乡镇→村」"
1058 1153
   }
1059 1154
 }

+ 2 - 1
ruoyi-ui/src/views/dataModel/farmerHousehold/index.vue

@@ -969,12 +969,13 @@ export default {
969 969
           return
970 970
         }
971 971
         this.exporting = true
972
+        const filename = `${this.exportForm.statYear}年${this.exportForm.statMonth}月农牧户数据.xlsx`
972 973
         this.$modal.loading(this.dmT("exportLoading"))
973 974
         exportFarmerHousehold(this.exportForm.statYear, this.exportForm.statMonth, this.exportForm.townDeptId)
974 975
           .then(async (data) => {
975 976
             const isBlob = blobValidate(data)
976 977
             if (isBlob) {
977
-              saveAs(data, `farmer_household_${this.exportForm.statYear}_${this.exportForm.statMonth}_${new Date().getTime()}.xlsx`)
978
+              saveAs(data, filename)
978 979
               this.exportOpen = false
979 980
             } else {
980 981
               this.$modal.msgError(this.dmT("exportFail"))

+ 844 - 0
ruoyi-ui/src/views/dataModel/livestockOwnershipHousehold/index.vue

@@ -0,0 +1,844 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-card shadow="never">
4
+      <el-form ref="queryForm" :model="queryParams" size="small" :inline="true">
5
+        <el-form-item prop="statYear">
6
+          <template slot="label">{{ dmT("queryStatYear") }}</template>
7
+          <el-select v-model="queryParams.statYear" :placeholder="dmCommon('pleaseSelect')" clearable style="width: 120px">
8
+            <el-option v-for="y in yearOptions" :key="y" :label="String(y)" :value="y" />
9
+          </el-select>
10
+        </el-form-item>
11
+        <el-form-item prop="statMonth">
12
+          <template slot="label">{{ dmT("queryStatMonth") }}</template>
13
+          <el-select v-model="queryParams.statMonth" :placeholder="dmCommon('pleaseSelect')" clearable style="width: 120px">
14
+            <el-option v-for="m in monthOptions" :key="'q-' + m" :label="formatStatMonth(m)" :value="m" />
15
+          </el-select>
16
+        </el-form-item>
17
+        <el-form-item prop="townDeptId">
18
+          <template slot="label">{{ dmT("queryTown") }}</template>
19
+          <el-select v-model="queryParams.townDeptId" :placeholder="dmCommon('pleaseSelect')" clearable filterable style="width: 180px">
20
+            <el-option v-for="item in townOptions" :key="item.deptId" :label="item.deptName" :value="item.deptId" />
21
+          </el-select>
22
+        </el-form-item>
23
+        <el-form-item prop="villageDeptId">
24
+          <template slot="label">{{ dmT("queryVillage") }}</template>
25
+          <el-select
26
+            v-model="queryParams.villageDeptId"
27
+            :placeholder="dmT('phVillageSearch')"
28
+            clearable
29
+            filterable
30
+            style="width: 220px"
31
+          >
32
+            <el-option
33
+              v-for="item in villageOptions"
34
+              :key="'q-v-' + item.deptId"
35
+              :label="item.label"
36
+              :value="item.deptId"
37
+            />
38
+          </el-select>
39
+        </el-form-item>
40
+        <el-form-item>
41
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ dmCommon("search") }}</el-button>
42
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ dmCommon("reset") }}</el-button>
43
+          <el-button
44
+            v-hasPermi="['dataModel:livestockOwnershipHousehold:add']"
45
+            type="primary"
46
+            plain
47
+            icon="el-icon-plus"
48
+            size="mini"
49
+            @click="handleAdd"
50
+          >{{ dmCommon("add") }}</el-button>
51
+          <el-button
52
+            v-hasPermi="['dataModel:livestockOwnershipHousehold:import']"
53
+            type="info"
54
+            plain
55
+            icon="el-icon-upload2"
56
+            size="mini"
57
+            @click="handleImportOpen"
58
+          >{{ dmT("btnImport") }}</el-button>
59
+          <el-button
60
+            v-hasPermi="['dataModel:livestockOwnershipHousehold:export']"
61
+            type="warning"
62
+            plain
63
+            icon="el-icon-download"
64
+            size="mini"
65
+            @click="handleExportOpen"
66
+          >{{ dmCommon("export") }}</el-button>
67
+        </el-form-item>
68
+      </el-form>
69
+    </el-card>
70
+
71
+    <br />
72
+
73
+    <el-card shadow="never">
74
+      <el-table v-loading="loading" :data="tableList" border>
75
+        <template slot="empty">
76
+          <span>{{ dmT("emptyList") }}</span>
77
+        </template>
78
+        <el-table-column :label="dmT('colStatYear')" prop="statYear" align="center" width="90" fixed />
79
+        <el-table-column :label="dmT('colStatMonth')" align="center" width="80" fixed>
80
+          <template slot-scope="scope">{{ formatStatMonth(scope.row.statMonth) }}</template>
81
+        </el-table-column>
82
+        <el-table-column :label="dmT('colTownName')" prop="townName" align="center" min-width="100" :show-overflow-tooltip="true" />
83
+        <el-table-column :label="dmT('colVillageName')" prop="villageName" align="center" min-width="100" :show-overflow-tooltip="true" />
84
+        <el-table-column :label="dmT('colWithHousehold')" align="center" width="100">
85
+          <template slot-scope="scope">{{ formatCount(scope.row.withLivestockHouseholdCount) }}</template>
86
+        </el-table-column>
87
+        <el-table-column :label="dmT('colWithPopulation')" align="center" width="100">
88
+          <template slot-scope="scope">{{ formatCount(scope.row.withLivestockPopulationCount) }}</template>
89
+        </el-table-column>
90
+        <el-table-column :label="dmT('colWithLabor')" align="center" width="100">
91
+          <template slot-scope="scope">{{ formatCount(scope.row.withLivestockLaborCount) }}</template>
92
+        </el-table-column>
93
+        <el-table-column :label="dmT('colWithAnimal')" align="center" width="110">
94
+          <template slot-scope="scope">{{ formatCount(scope.row.withLivestockAnimalCount) }}</template>
95
+        </el-table-column>
96
+        <el-table-column :label="dmT('colWithoutHousehold')" align="center" width="100">
97
+          <template slot-scope="scope">{{ formatCount(scope.row.withoutLivestockHouseholdCount) }}</template>
98
+        </el-table-column>
99
+        <el-table-column :label="dmT('colWithoutPopulation')" align="center" width="100">
100
+          <template slot-scope="scope">{{ formatCount(scope.row.withoutLivestockPopulationCount) }}</template>
101
+        </el-table-column>
102
+        <el-table-column :label="dmT('colWithoutLabor')" align="center" width="100">
103
+          <template slot-scope="scope">{{ formatCount(scope.row.withoutLivestockLaborCount) }}</template>
104
+        </el-table-column>
105
+        <el-table-column :label="dmCommon('colOp')" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
106
+          <template slot-scope="scope">
107
+            <el-button v-hasPermi="['dataModel:livestockOwnershipHousehold:query']" type="text" size="mini" @click="handleView(scope.row)">{{ dmCommon("view") }}</el-button>
108
+            <el-button v-hasPermi="['dataModel:livestockOwnershipHousehold:edit']" type="text" size="mini" @click="handleEdit(scope.row)">{{ dmCommon("edit") }}</el-button>
109
+            <el-button v-hasPermi="['dataModel:livestockOwnershipHousehold:remove']" type="text" size="mini" @click="handleDelete(scope.row)">{{ dmCommon("delete") }}</el-button>
110
+          </template>
111
+        </el-table-column>
112
+      </el-table>
113
+      <pagination
114
+        v-show="total > 0"
115
+        :total="total"
116
+        :page.sync="queryParams.pageNum"
117
+        :limit.sync="queryParams.pageSize"
118
+        @pagination="getList"
119
+      />
120
+    </el-card>
121
+
122
+    <el-dialog :title="dialogTitle" :visible.sync="open" width="680px" append-to-body @close="cancel">
123
+      <el-form :key="formKey" ref="form" :model="form" class="loh-form" label-width="120px" size="small">
124
+        <div class="loh-section-title">{{ dmT("sectionBasic") }}</div>
125
+        <el-form-item prop="statYear" :rules="formRules.statYear">
126
+          <template slot="label">{{ dmT("formStatYear") }}</template>
127
+          <el-select v-model="form.statYear" :placeholder="dmCommon('pleaseSelect')" style="width: 100%">
128
+            <el-option v-for="y in yearOptions" :key="'f-' + y" :label="String(y)" :value="y" />
129
+          </el-select>
130
+        </el-form-item>
131
+        <el-form-item prop="statMonth" :rules="formRules.statMonth">
132
+          <template slot="label">{{ dmT("formStatMonth") }}</template>
133
+          <el-select v-model="form.statMonth" :placeholder="dmCommon('pleaseSelect')" style="width: 100%">
134
+            <el-option v-for="m in monthOptions" :key="'fm-' + m" :label="formatStatMonth(m)" :value="m" />
135
+          </el-select>
136
+        </el-form-item>
137
+        <el-form-item prop="villageDeptId" :rules="formRules.villageDeptId">
138
+          <template slot="label">{{ dmT("formVillage") }}</template>
139
+          <el-select
140
+            v-model="form.villageDeptId"
141
+            :placeholder="dmT('phVillageSearch')"
142
+            filterable
143
+            clearable
144
+            style="width: 100%"
145
+          >
146
+            <el-option
147
+              v-for="item in villageOptions"
148
+              :key="'f-v-' + item.deptId"
149
+              :label="item.label"
150
+              :value="item.deptId"
151
+            />
152
+          </el-select>
153
+        </el-form-item>
154
+
155
+        <div class="loh-section-title">{{ dmT("sectionWith") }}</div>
156
+        <el-form-item prop="withLivestockHouseholdCount" :rules="formRules.count">
157
+          <template slot="label">{{ dmT("formHouseholdCount") }}</template>
158
+          <div class="dm-input-with-unit">
159
+            <el-input-number v-model="form.withLivestockHouseholdCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
160
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitHousehold") }}</span>
161
+          </div>
162
+        </el-form-item>
163
+        <el-form-item prop="withLivestockPopulationCount" :rules="formRules.count">
164
+          <template slot="label">{{ dmT("formPopulationCount") }}</template>
165
+          <div class="dm-input-with-unit">
166
+            <el-input-number v-model="form.withLivestockPopulationCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
167
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitPerson") }}</span>
168
+          </div>
169
+        </el-form-item>
170
+        <el-form-item prop="withLivestockLaborCount" :rules="formRules.count">
171
+          <template slot="label">{{ dmT("formLaborCount") }}</template>
172
+          <div class="dm-input-with-unit">
173
+            <el-input-number v-model="form.withLivestockLaborCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
174
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitPerson") }}</span>
175
+          </div>
176
+        </el-form-item>
177
+        <el-form-item prop="withLivestockAnimalCount" :rules="formRules.count">
178
+          <template slot="label">{{ dmT("formAnimalCount") }}</template>
179
+          <div class="dm-input-with-unit">
180
+            <el-input-number v-model="form.withLivestockAnimalCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
181
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitHeadPiece") }}</span>
182
+          </div>
183
+        </el-form-item>
184
+
185
+        <div class="loh-section-title">{{ dmT("sectionWithout") }}</div>
186
+        <el-form-item prop="withoutLivestockHouseholdCount" :rules="formRules.count">
187
+          <template slot="label">{{ dmT("formHouseholdCount") }}</template>
188
+          <div class="dm-input-with-unit">
189
+            <el-input-number v-model="form.withoutLivestockHouseholdCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
190
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitHousehold") }}</span>
191
+          </div>
192
+        </el-form-item>
193
+        <el-form-item prop="withoutLivestockPopulationCount" :rules="formRules.count">
194
+          <template slot="label">{{ dmT("formPopulationCount") }}</template>
195
+          <div class="dm-input-with-unit">
196
+            <el-input-number v-model="form.withoutLivestockPopulationCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
197
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitPerson") }}</span>
198
+          </div>
199
+        </el-form-item>
200
+        <el-form-item prop="withoutLivestockLaborCount" :rules="formRules.count">
201
+          <template slot="label">{{ dmT("formLaborCount") }}</template>
202
+          <div class="dm-input-with-unit">
203
+            <el-input-number v-model="form.withoutLivestockLaborCount" :min="0" :precision="0" controls-position="right" class="dm-input-with-unit__input" />
204
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitPerson") }}</span>
205
+          </div>
206
+        </el-form-item>
207
+
208
+        <el-form-item prop="remark" :rules="formRules.remark">
209
+          <template slot="label">{{ dmT("formRemark") }}</template>
210
+          <el-input v-model="form.remark" type="textarea" :rows="2" :placeholder="dmT('phRemark')" maxlength="500" show-word-limit />
211
+        </el-form-item>
212
+      </el-form>
213
+      <div slot="footer" class="dialog-footer">
214
+        <el-button type="primary" @click="submitForm">{{ dmCommon("ok") }}</el-button>
215
+        <el-button @click="cancel">{{ dmCommon("cancel") }}</el-button>
216
+      </div>
217
+    </el-dialog>
218
+
219
+    <el-dialog :title="dmCommon('viewTitle')" :visible.sync="viewOpen" width="680px" append-to-body>
220
+      <el-form class="dm-view-form loh-form" label-width="120px" size="small">
221
+        <div class="loh-section-title">{{ dmT("sectionBasic") }}</div>
222
+        <el-form-item :label="dmT('formStatYear')"><span>{{ viewRow.statYear || dash }}</span></el-form-item>
223
+        <el-form-item :label="dmT('formStatMonth')"><span>{{ formatStatMonth(viewRow.statMonth) }}</span></el-form-item>
224
+        <el-form-item :label="dmT('formTown')"><span>{{ viewRow.townName || dash }}</span></el-form-item>
225
+        <el-form-item :label="dmT('formVillage')"><span>{{ viewRow.villageName || dash }}</span></el-form-item>
226
+
227
+        <div class="loh-section-title">{{ dmT("sectionWith") }}</div>
228
+        <el-form-item :label="dmT('formHouseholdCount')">
229
+          <span>{{ formatWithUnit(formatCount(viewRow.withLivestockHouseholdCount), "unitHousehold") }}</span>
230
+        </el-form-item>
231
+        <el-form-item :label="dmT('formPopulationCount')">
232
+          <span>{{ formatWithUnit(formatCount(viewRow.withLivestockPopulationCount), "unitPerson") }}</span>
233
+        </el-form-item>
234
+        <el-form-item :label="dmT('formLaborCount')">
235
+          <span>{{ formatWithUnit(formatCount(viewRow.withLivestockLaborCount), "unitPerson") }}</span>
236
+        </el-form-item>
237
+        <el-form-item :label="dmT('formAnimalCount')">
238
+          <span>{{ formatWithUnit(formatCount(viewRow.withLivestockAnimalCount), "unitHeadPiece") }}</span>
239
+        </el-form-item>
240
+
241
+        <div class="loh-section-title">{{ dmT("sectionWithout") }}</div>
242
+        <el-form-item :label="dmT('formHouseholdCount')">
243
+          <span>{{ formatWithUnit(formatCount(viewRow.withoutLivestockHouseholdCount), "unitHousehold") }}</span>
244
+        </el-form-item>
245
+        <el-form-item :label="dmT('formPopulationCount')">
246
+          <span>{{ formatWithUnit(formatCount(viewRow.withoutLivestockPopulationCount), "unitPerson") }}</span>
247
+        </el-form-item>
248
+        <el-form-item :label="dmT('formLaborCount')">
249
+          <span>{{ formatWithUnit(formatCount(viewRow.withoutLivestockLaborCount), "unitPerson") }}</span>
250
+        </el-form-item>
251
+
252
+        <el-form-item :label="dmT('formRemark')">
253
+          <span class="loh-view-text">{{ viewRow.remark || dash }}</span>
254
+        </el-form-item>
255
+      </el-form>
256
+      <div slot="footer" class="dialog-footer">
257
+        <el-button v-hasPermi="['dataModel:livestockOwnershipHousehold:edit']" type="primary" @click="handleEditFromView">{{ dmCommon("edit") }}</el-button>
258
+        <el-button @click="viewOpen = false">{{ dmCommon("close") }}</el-button>
259
+      </div>
260
+    </el-dialog>
261
+
262
+    <el-dialog :title="dmT('importTitle')" :visible.sync="importOpen" width="480px" append-to-body @close="resetImport">
263
+      <el-form ref="importFormRef" :model="importForm" label-width="100px" size="small">
264
+        <el-form-item prop="statYear" :rules="importRules.statYear">
265
+          <template slot="label">{{ dmT("importStatYear") }}</template>
266
+          <el-select v-model="importForm.statYear" :placeholder="dmT('importStatYearPh')" style="width: 100%">
267
+            <el-option v-for="y in yearOptions" :key="'iy-' + y" :label="String(y)" :value="y" />
268
+          </el-select>
269
+        </el-form-item>
270
+        <el-form-item prop="statMonth" :rules="importRules.statMonth">
271
+          <template slot="label">{{ dmT("importStatMonth") }}</template>
272
+          <el-select v-model="importForm.statMonth" :placeholder="dmT('importStatMonthPh')" style="width: 100%">
273
+            <el-option v-for="m in monthOptions" :key="'im-' + m" :label="formatStatMonth(m)" :value="m" />
274
+          </el-select>
275
+        </el-form-item>
276
+        <p class="loh-import-tip">{{ dmT("importTip") }}</p>
277
+        <el-upload ref="importUpload" drag :limit="1" accept=".xlsx,.xls" :auto-upload="false" action="#">
278
+          <i class="el-icon-upload" />
279
+          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
280
+          <div slot="tip" class="el-upload__tip">
281
+            <el-link type="primary" :underline="false" @click="handleDownloadTemplate">{{ dmT("btnDownloadTemplate") }}</el-link>
282
+          </div>
283
+        </el-upload>
284
+      </el-form>
285
+      <div slot="footer" class="dialog-footer">
286
+        <el-button type="primary" :loading="importing" @click="handleImportSubmit">{{ dmCommon("ok") }}</el-button>
287
+        <el-button @click="importOpen = false">{{ dmCommon("cancel") }}</el-button>
288
+      </div>
289
+    </el-dialog>
290
+
291
+    <el-dialog :title="dmT('exportTitle')" :visible.sync="exportOpen" width="480px" append-to-body @close="resetExport">
292
+      <el-form ref="exportFormRef" :model="exportForm" label-width="100px" size="small">
293
+        <el-form-item prop="statYear" :rules="exportRules.statYear">
294
+          <template slot="label">{{ dmT("exportStatYear") }}</template>
295
+          <el-select v-model="exportForm.statYear" :placeholder="dmT('exportStatYearPh')" style="width: 100%">
296
+            <el-option v-for="y in yearOptions" :key="'ey-' + y" :label="String(y)" :value="y" />
297
+          </el-select>
298
+        </el-form-item>
299
+        <el-form-item prop="statMonth" :rules="exportRules.statMonth">
300
+          <template slot="label">{{ dmT("exportStatMonth") }}</template>
301
+          <el-select v-model="exportForm.statMonth" :placeholder="dmT('exportStatMonthPh')" style="width: 100%">
302
+            <el-option v-for="m in monthOptions" :key="'em-' + m" :label="formatStatMonth(m)" :value="m" />
303
+          </el-select>
304
+        </el-form-item>
305
+        <el-form-item>
306
+          <template slot="label">{{ dmT("exportTown") }}</template>
307
+          <el-select
308
+            v-model="exportForm.townDeptId"
309
+            :placeholder="dmT('exportTownPh')"
310
+            clearable
311
+            filterable
312
+            style="width: 100%"
313
+          >
314
+            <el-option v-for="item in townOptions" :key="'et-' + item.deptId" :label="item.deptName" :value="item.deptId" />
315
+          </el-select>
316
+        </el-form-item>
317
+      </el-form>
318
+      <div slot="footer" class="dialog-footer">
319
+        <el-button type="primary" :loading="exporting" @click="handleExportSubmit">{{ dmCommon("ok") }}</el-button>
320
+        <el-button @click="exportOpen = false">{{ dmCommon("cancel") }}</el-button>
321
+      </div>
322
+    </el-dialog>
323
+  </div>
324
+</template>
325
+
326
+<script>
327
+import dataModelLocaleMixin from "@/mixins/dataModelLocaleMixin"
328
+import {
329
+  listLivestockOwnershipHousehold,
330
+  getLivestockOwnershipHousehold,
331
+  addLivestockOwnershipHousehold,
332
+  updateLivestockOwnershipHousehold,
333
+  delLivestockOwnershipHousehold,
334
+  listLivestockOwnershipHouseholdYearOptions,
335
+  listLivestockOwnershipHouseholdMonthOptions,
336
+  listLivestockOwnershipHouseholdTownOptions,
337
+  listLivestockOwnershipHouseholdVillageTree,
338
+  importLivestockOwnershipHousehold,
339
+  downloadLivestockOwnershipHouseholdTemplate,
340
+  exportLivestockOwnershipHousehold
341
+} from "@/api/dataModel/livestockOwnershipHousehold"
342
+import { blobValidate } from "@/utils/ruoyi"
343
+import { saveAs } from "file-saver"
344
+
345
+const COUNT_PROPS = [
346
+  "withLivestockHouseholdCount",
347
+  "withLivestockPopulationCount",
348
+  "withLivestockLaborCount",
349
+  "withLivestockAnimalCount",
350
+  "withoutLivestockHouseholdCount",
351
+  "withoutLivestockPopulationCount",
352
+  "withoutLivestockLaborCount"
353
+]
354
+
355
+function getCurrentYearMonth() {
356
+  const now = new Date()
357
+  return { statYear: now.getFullYear(), statMonth: now.getMonth() + 1 }
358
+}
359
+
360
+function createEmptyForm() {
361
+  const form = {
362
+    id: undefined,
363
+    statYear: undefined,
364
+    statMonth: undefined,
365
+    villageDeptId: undefined,
366
+    remark: undefined
367
+  }
368
+  COUNT_PROPS.forEach((prop) => {
369
+    form[prop] = undefined
370
+  })
371
+  return form
372
+}
373
+
374
+function normalizeForm(data) {
375
+  const row = Object.assign(createEmptyForm(), data || {})
376
+  COUNT_PROPS.forEach((prop) => {
377
+    const val = row[prop]
378
+    row[prop] = val == null || val === "" ? undefined : Number(val)
379
+  })
380
+  return row
381
+}
382
+
383
+function flattenVillageOptions(tree) {
384
+  const list = []
385
+  ;(tree || []).forEach((county) => {
386
+    ;(county.children || []).forEach((town) => {
387
+      ;(town.children || []).forEach((village) => {
388
+        if (village == null || village.id == null) {
389
+          return
390
+        }
391
+        const townName = town.label || ""
392
+        const villageName = village.label || ""
393
+        list.push({
394
+          deptId: village.id,
395
+          label: townName ? `${townName} / ${villageName}` : villageName
396
+        })
397
+      })
398
+    })
399
+  })
400
+  return list
401
+}
402
+
403
+export default {
404
+  name: "LivestockOwnershipHousehold",
405
+  mixins: [dataModelLocaleMixin],
406
+  data() {
407
+    return {
408
+      dmNs: "livestockOwnershipHousehold",
409
+      loading: false,
410
+      importing: false,
411
+      exporting: false,
412
+      open: false,
413
+      viewOpen: false,
414
+      importOpen: false,
415
+      exportOpen: false,
416
+      dialogEdit: false,
417
+      formKey: 0,
418
+      total: 0,
419
+      tableList: [],
420
+      yearOptions: [],
421
+      monthOptions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
422
+      townOptions: [],
423
+      villageOptions: [],
424
+      viewRow: {},
425
+      importForm: { statYear: undefined, statMonth: undefined },
426
+      exportForm: { statYear: undefined, statMonth: undefined, townDeptId: undefined },
427
+      queryParams: {
428
+        pageNum: 1,
429
+        pageSize: 20,
430
+        statYear: undefined,
431
+        statMonth: undefined,
432
+        townDeptId: undefined,
433
+        villageDeptId: undefined
434
+      },
435
+      form: createEmptyForm()
436
+    }
437
+  },
438
+  computed: {
439
+    dash() {
440
+      return this.$t("dataModel.status.dash")
441
+    },
442
+    dialogTitle() {
443
+      return this.dialogEdit ? this.dmT("dialogEdit") : this.dmT("dialogAdd")
444
+    },
445
+    formRules() {
446
+      return {
447
+        statYear: [{ required: true, message: this.dmT("ruleStatYearRequired"), trigger: "change" }],
448
+        statMonth: [
449
+          { required: true, message: this.dmT("ruleStatMonthRequired"), trigger: "change" },
450
+          { validator: this.validateFormStatMonth, trigger: "change" }
451
+        ],
452
+        villageDeptId: [{ required: true, message: this.dmT("ruleVillageRequired"), trigger: "change" }],
453
+        count: [{ validator: this.validateOptionalCount, trigger: "change" }],
454
+        remark: [{ max: 500, message: this.dmCommon("ruleLen500"), trigger: "blur" }]
455
+      }
456
+    },
457
+    importRules() {
458
+      return {
459
+        statYear: [{ required: true, message: this.dmT("ruleStatYearRequired"), trigger: "change" }],
460
+        statMonth: [
461
+          { required: true, message: this.dmT("ruleStatMonthRequired"), trigger: "change" },
462
+          { validator: this.validateImportStatMonth, trigger: "change" }
463
+        ]
464
+      }
465
+    },
466
+    exportRules() {
467
+      return {
468
+        statYear: [{ required: true, message: this.dmT("exportStatYearRequired"), trigger: "change" }],
469
+        statMonth: [
470
+          { required: true, message: this.dmT("exportStatMonthRequired"), trigger: "change" },
471
+          { validator: this.validateExportStatMonth, trigger: "change" }
472
+        ]
473
+      }
474
+    }
475
+  },
476
+  created() {
477
+    this.loadOptions()
478
+    this.getList()
479
+  },
480
+  methods: {
481
+    loadOptions() {
482
+      listLivestockOwnershipHouseholdYearOptions().then((res) => {
483
+        this.yearOptions = Array.isArray(res.data) ? res.data : []
484
+      })
485
+      listLivestockOwnershipHouseholdMonthOptions().then((res) => {
486
+        if (Array.isArray(res.data) && res.data.length) {
487
+          this.monthOptions = res.data
488
+        }
489
+      })
490
+      listLivestockOwnershipHouseholdTownOptions().then((res) => {
491
+        this.townOptions = Array.isArray(res.data) ? res.data : []
492
+      })
493
+      this.loadVillageTree()
494
+    },
495
+    loadVillageTree() {
496
+      return listLivestockOwnershipHouseholdVillageTree()
497
+        .then((res) => {
498
+          const raw = res && res.data !== undefined ? res.data : res
499
+          this.villageOptions = flattenVillageOptions(Array.isArray(raw) ? raw : [])
500
+          if (!this.villageOptions.length) {
501
+            this.$modal.msgWarning(this.dmT("villageTreeEmpty"))
502
+          }
503
+        })
504
+        .catch(() => {
505
+          this.villageOptions = []
506
+        })
507
+    },
508
+    formatStatMonth(month) {
509
+      if (month == null || month === "") {
510
+        return this.dash
511
+      }
512
+      return this.dmT("statMonthLabel", { month })
513
+    },
514
+    formatCount(val) {
515
+      return val == null || val === "" ? this.dash : val
516
+    },
517
+    isFutureStatPeriod(statYear, statMonth) {
518
+      if (statYear == null || statMonth == null) {
519
+        return false
520
+      }
521
+      const now = new Date()
522
+      const y = now.getFullYear()
523
+      const m = now.getMonth() + 1
524
+      return statYear > y || (statYear === y && statMonth > m)
525
+    },
526
+    validateFormStatMonth(rule, value, callback) {
527
+      if (this.isFutureStatPeriod(this.form.statYear, value)) {
528
+        callback(new Error(this.dmT("ruleStatMonthFuture")))
529
+        return
530
+      }
531
+      callback()
532
+    },
533
+    validateImportStatMonth(rule, value, callback) {
534
+      if (this.isFutureStatPeriod(this.importForm.statYear, value)) {
535
+        callback(new Error(this.dmT("ruleStatMonthFuture")))
536
+        return
537
+      }
538
+      callback()
539
+    },
540
+    validateExportStatMonth(rule, value, callback) {
541
+      if (this.isFutureStatPeriod(this.exportForm.statYear, value)) {
542
+        callback(new Error(this.dmT("ruleStatMonthFuture")))
543
+        return
544
+      }
545
+      callback()
546
+    },
547
+    validateOptionalCount(rule, value, callback) {
548
+      if (value == null || value === "") {
549
+        callback()
550
+        return
551
+      }
552
+      if (typeof value !== "number" || Number.isNaN(value) || value < 0 || !Number.isInteger(value)) {
553
+        callback(new Error(this.dmT("ruleCountNonNegative")))
554
+        return
555
+      }
556
+      callback()
557
+    },
558
+    buildListQuery() {
559
+      const q = {
560
+        pageNum: this.queryParams.pageNum,
561
+        pageSize: this.queryParams.pageSize
562
+      }
563
+      if (this.queryParams.statYear != null) {
564
+        q.statYear = this.queryParams.statYear
565
+      }
566
+      if (this.queryParams.statMonth != null) {
567
+        q.statMonth = this.queryParams.statMonth
568
+      }
569
+      if (this.queryParams.townDeptId != null) {
570
+        q.townDeptId = this.queryParams.townDeptId
571
+      }
572
+      if (this.queryParams.villageDeptId != null) {
573
+        q.villageDeptId = this.queryParams.villageDeptId
574
+      }
575
+      return q
576
+    },
577
+    getList() {
578
+      this.loading = true
579
+      listLivestockOwnershipHousehold(this.buildListQuery())
580
+        .then((res) => {
581
+          this.tableList = res.rows || []
582
+          this.total = res.total || 0
583
+        })
584
+        .finally(() => {
585
+          this.loading = false
586
+        })
587
+    },
588
+    handleQuery() {
589
+      this.queryParams.pageNum = 1
590
+      this.getList()
591
+    },
592
+    resetQuery() {
593
+      this.queryParams = {
594
+        pageNum: 1,
595
+        pageSize: 20,
596
+        statYear: undefined,
597
+        statMonth: undefined,
598
+        townDeptId: undefined,
599
+        villageDeptId: undefined
600
+      }
601
+      this.getList()
602
+    },
603
+    handleAdd() {
604
+      this.dialogEdit = false
605
+      this.formKey += 1
606
+      this.form = createEmptyForm()
607
+      const ym = getCurrentYearMonth()
608
+      this.form.statYear = ym.statYear
609
+      this.form.statMonth = ym.statMonth
610
+      this.open = true
611
+      this.$nextTick(() => {
612
+        if (this.$refs.form) {
613
+          this.$refs.form.clearValidate()
614
+        }
615
+      })
616
+    },
617
+    handleEdit(row) {
618
+      getLivestockOwnershipHousehold(row.id).then((res) => {
619
+        this.dialogEdit = true
620
+        this.formKey += 1
621
+        this.form = normalizeForm(res.data)
622
+        this.open = true
623
+        this.viewOpen = false
624
+        this.$nextTick(() => {
625
+          if (this.$refs.form) {
626
+            this.$refs.form.clearValidate()
627
+          }
628
+        })
629
+      })
630
+    },
631
+    handleView(row) {
632
+      getLivestockOwnershipHousehold(row.id).then((res) => {
633
+        this.viewRow = res.data || {}
634
+        this.viewOpen = true
635
+      })
636
+    },
637
+    handleEditFromView() {
638
+      if (!this.viewRow || this.viewRow.id == null) {
639
+        return
640
+      }
641
+      this.handleEdit(this.viewRow)
642
+    },
643
+    handleDelete(row) {
644
+      this.$modal
645
+        .confirm(this.dmT("confirmDelete"))
646
+        .then(() => delLivestockOwnershipHousehold(row.id))
647
+        .then(() => {
648
+          this.$modal.msgSuccess(this.dmCommon("msgDelOk"))
649
+          this.getList()
650
+        })
651
+        .catch(() => {})
652
+    },
653
+    normalizeCount(val) {
654
+      return val == null || val === "" ? null : val
655
+    },
656
+    buildSubmitPayload() {
657
+      const payload = {
658
+        id: this.form.id,
659
+        statYear: this.form.statYear,
660
+        statMonth: this.form.statMonth,
661
+        villageDeptId: this.form.villageDeptId,
662
+        remark: this.form.remark
663
+      }
664
+      COUNT_PROPS.forEach((prop) => {
665
+        payload[prop] = this.normalizeCount(this.form[prop])
666
+      })
667
+      return payload
668
+    },
669
+    submitForm() {
670
+      this.$refs.form.validate((valid) => {
671
+        if (!valid) {
672
+          return
673
+        }
674
+        const payload = this.buildSubmitPayload()
675
+        if (this.dialogEdit) {
676
+          updateLivestockOwnershipHousehold(payload).then(() => {
677
+            this.$modal.msgSuccess(this.dmCommon("msgEditOk"))
678
+            this.open = false
679
+            this.getList()
680
+          })
681
+        } else {
682
+          addLivestockOwnershipHousehold(payload).then(() => {
683
+            this.$modal.msgSuccess(this.dmCommon("msgAddOk"))
684
+            this.open = false
685
+            this.getList()
686
+          })
687
+        }
688
+      })
689
+    },
690
+    cancel() {
691
+      this.open = false
692
+      this.form = createEmptyForm()
693
+    },
694
+    handleImportOpen() {
695
+      const ym = getCurrentYearMonth()
696
+      this.importForm = {
697
+        statYear: this.queryParams.statYear != null ? this.queryParams.statYear : ym.statYear,
698
+        statMonth: this.queryParams.statMonth != null ? this.queryParams.statMonth : ym.statMonth
699
+      }
700
+      this.importOpen = true
701
+      this.$nextTick(() => {
702
+        if (this.$refs.importFormRef) {
703
+          this.$refs.importFormRef.clearValidate()
704
+        }
705
+        if (this.$refs.importUpload) {
706
+          this.$refs.importUpload.clearFiles()
707
+        }
708
+      })
709
+    },
710
+    resetImport() {
711
+      this.importForm = { statYear: undefined, statMonth: undefined }
712
+      if (this.$refs.importUpload) {
713
+        this.$refs.importUpload.clearFiles()
714
+      }
715
+    },
716
+    handleDownloadTemplate() {
717
+      downloadLivestockOwnershipHouseholdTemplate().then(async (data) => {
718
+        const isBlob = blobValidate(data)
719
+        if (isBlob) {
720
+          saveAs(data, `livestock_ownership_household_import_template_${new Date().getTime()}.xlsx`)
721
+        } else {
722
+          this.$modal.msgError(this.dmT("exportFail"))
723
+        }
724
+      })
725
+    },
726
+    handleImportSubmit() {
727
+      this.$refs.importFormRef.validate((valid) => {
728
+        if (!valid) {
729
+          return
730
+        }
731
+        const files = this.$refs.importUpload ? this.$refs.importUpload.uploadFiles : []
732
+        if (!files || !files.length) {
733
+          this.$modal.msgError(this.dmCommon("pleaseSelect"))
734
+          return
735
+        }
736
+        const raw = files[0].raw
737
+        this.importing = true
738
+        this.$modal.loading(this.dmT("importLoading"))
739
+        importLivestockOwnershipHousehold(raw, this.importForm.statYear, this.importForm.statMonth)
740
+          .then((res) => {
741
+            const data = res.data || {}
742
+            const msg = this.dmT("importSummary", {
743
+              insert: data.insertCount != null ? data.insertCount : 0,
744
+              update: data.updateCount != null ? data.updateCount : 0,
745
+              fail: data.failCount != null ? data.failCount : 0
746
+            })
747
+            this.$modal.msgSuccess(msg)
748
+            if (data.warnMessages && data.warnMessages.length) {
749
+              this.$alert(data.warnMessages.join("<br/>"), this.dmT("importWarnTitle"), { dangerouslyUseHTMLString: true })
750
+            }
751
+            if (data.failMessages && data.failMessages.length) {
752
+              this.$alert(data.failMessages.join("<br/>"), this.dmT("importFailTitle"), { dangerouslyUseHTMLString: true })
753
+            }
754
+            this.importOpen = false
755
+            this.getList()
756
+          })
757
+          .finally(() => {
758
+            this.importing = false
759
+            this.$modal.closeLoading()
760
+          })
761
+      })
762
+    },
763
+    handleExportOpen() {
764
+      const ym = getCurrentYearMonth()
765
+      this.exportForm = {
766
+        statYear: this.queryParams.statYear != null ? this.queryParams.statYear : ym.statYear,
767
+        statMonth: this.queryParams.statMonth != null ? this.queryParams.statMonth : ym.statMonth,
768
+        townDeptId: this.queryParams.townDeptId
769
+      }
770
+      this.exportOpen = true
771
+      this.$nextTick(() => {
772
+        if (this.$refs.exportFormRef) {
773
+          this.$refs.exportFormRef.clearValidate()
774
+        }
775
+      })
776
+    },
777
+    resetExport() {
778
+      this.exportForm = { statYear: undefined, statMonth: undefined, townDeptId: undefined }
779
+    },
780
+    handleExportSubmit() {
781
+      this.$refs.exportFormRef.validate((valid) => {
782
+        if (!valid) {
783
+          return
784
+        }
785
+        this.exporting = true
786
+        const filename = `${this.exportForm.statYear}年${this.exportForm.statMonth}月有无畜户数据.xlsx`
787
+        this.$modal.loading(this.dmT("exportLoading"))
788
+        exportLivestockOwnershipHousehold(this.exportForm.statYear, this.exportForm.statMonth, this.exportForm.townDeptId)
789
+          .then(async (data) => {
790
+            const isBlob = blobValidate(data)
791
+            if (isBlob) {
792
+              saveAs(
793
+                data,
794
+                filename
795
+              )
796
+              this.exportOpen = false
797
+            } else {
798
+              this.$modal.msgError(this.dmT("exportFail"))
799
+            }
800
+          })
801
+          .finally(() => {
802
+            this.exporting = false
803
+            this.$modal.closeLoading()
804
+          })
805
+      })
806
+    }
807
+  }
808
+}
809
+</script>
810
+
811
+<style scoped>
812
+.loh-section-title {
813
+  margin: 4px 0 12px;
814
+  font-weight: 600;
815
+  color: #303133;
816
+}
817
+.loh-import-tip {
818
+  margin: 0 0 12px;
819
+  color: #909399;
820
+  font-size: 12px;
821
+  line-height: 1.5;
822
+}
823
+.loh-view-text {
824
+  white-space: pre-wrap;
825
+  word-break: break-all;
826
+}
827
+.dm-input-with-unit {
828
+  display: flex;
829
+  align-items: center;
830
+  width: 100%;
831
+}
832
+.dm-input-with-unit__input {
833
+  flex: 1;
834
+  width: 100%;
835
+}
836
+.dm-input-with-unit__suffix {
837
+  margin-left: 8px;
838
+  color: #909399;
839
+  white-space: nowrap;
840
+}
841
+.dm-input-with-unit >>> .el-input-number {
842
+  width: 100%;
843
+}
844
+</style>

+ 476 - 0
ruoyi-ui/src/views/dataModel/ruralThreeAssets/index.vue

@@ -0,0 +1,476 @@
1
+<template>
2
+  <div class="app-container">
3
+    <el-card shadow="never">
4
+      <el-form ref="queryForm" :model="queryParams" size="small" :inline="true">
5
+        <el-form-item prop="townDeptId">
6
+          <template slot="label">{{ dmT("queryTown") }}</template>
7
+          <el-select v-model="queryParams.townDeptId" :placeholder="dmCommon('pleaseSelect')" clearable filterable style="width: 180px">
8
+            <el-option v-for="item in townOptions" :key="item.deptId" :label="item.deptName" :value="item.deptId" />
9
+          </el-select>
10
+        </el-form-item>
11
+        <el-form-item prop="villageDeptId">
12
+          <template slot="label">{{ dmT("queryVillage") }}</template>
13
+          <el-select
14
+            v-model="queryParams.villageDeptId"
15
+            :placeholder="dmT('phVillageSearch')"
16
+            clearable
17
+            filterable
18
+            style="width: 220px"
19
+          >
20
+            <el-option
21
+              v-for="item in villageOptions"
22
+              :key="'q-v-' + item.deptId"
23
+              :label="item.label"
24
+              :value="item.deptId"
25
+            />
26
+          </el-select>
27
+        </el-form-item>
28
+        <el-form-item>
29
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">{{ dmCommon("search") }}</el-button>
30
+          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">{{ dmCommon("reset") }}</el-button>
31
+          <el-button
32
+            v-hasPermi="['dataModel:ruralThreeAssets:add']"
33
+            type="primary"
34
+            plain
35
+            icon="el-icon-plus"
36
+            size="mini"
37
+            @click="handleAdd"
38
+          >{{ dmCommon("add") }}</el-button>
39
+        </el-form-item>
40
+      </el-form>
41
+    </el-card>
42
+
43
+    <br />
44
+
45
+    <el-card shadow="never">
46
+      <el-table v-loading="loading" :data="tableList" border>
47
+        <template slot="empty">
48
+          <span>{{ dmT("emptyList") }}</span>
49
+        </template>
50
+        <el-table-column :label="dmT('colTownName')" prop="townName" align="center" min-width="110" :show-overflow-tooltip="true" />
51
+        <el-table-column :label="dmT('colVillageName')" prop="villageName" align="center" min-width="110" :show-overflow-tooltip="true" />
52
+        <el-table-column :label="dmT('colOperatingFixedAssets')" align="center" min-width="140">
53
+          <template slot-scope="scope">{{ formatMoney(scope.row.operatingFixedAssets) }}</template>
54
+        </el-table-column>
55
+        <el-table-column :label="dmT('colNonOperatingFixedAssets')" align="center" min-width="150">
56
+          <template slot-scope="scope">{{ formatMoney(scope.row.nonOperatingFixedAssets) }}</template>
57
+        </el-table-column>
58
+        <el-table-column :label="dmT('colFixedAssets')" align="center" min-width="120">
59
+          <template slot-scope="scope">{{ formatMoney(scope.row.fixedAssets) }}</template>
60
+        </el-table-column>
61
+        <el-table-column :label="dmCommon('colOp')" align="center" class-name="small-padding fixed-width" width="180" fixed="right">
62
+          <template slot-scope="scope">
63
+            <el-button v-hasPermi="['dataModel:ruralThreeAssets:query']" type="text" size="mini" @click="handleView(scope.row)">{{ dmCommon("view") }}</el-button>
64
+            <el-button v-hasPermi="['dataModel:ruralThreeAssets:edit']" type="text" size="mini" @click="handleEdit(scope.row)">{{ dmCommon("edit") }}</el-button>
65
+            <el-button v-hasPermi="['dataModel:ruralThreeAssets:remove']" type="text" size="mini" @click="handleDelete(scope.row)">{{ dmCommon("delete") }}</el-button>
66
+          </template>
67
+        </el-table-column>
68
+      </el-table>
69
+      <pagination
70
+        v-show="total > 0"
71
+        :total="total"
72
+        :page.sync="queryParams.pageNum"
73
+        :limit.sync="queryParams.pageSize"
74
+        @pagination="getList"
75
+      />
76
+    </el-card>
77
+
78
+    <el-dialog :title="dialogTitle" :visible.sync="open" width="640px" append-to-body @close="cancel">
79
+      <el-form :key="formKey" ref="form" :model="form" class="rta-form" label-width="160px" size="small">
80
+        <div class="rta-section-title">{{ dmT("sectionBasic") }}</div>
81
+        <el-form-item prop="villageDeptId" :rules="formRules.villageDeptId">
82
+          <template slot="label">{{ dmT("formVillage") }}</template>
83
+          <el-select
84
+            v-model="form.villageDeptId"
85
+            :placeholder="dmT('phVillageSearch')"
86
+            filterable
87
+            clearable
88
+            style="width: 100%"
89
+          >
90
+            <el-option
91
+              v-for="item in villageOptions"
92
+              :key="'f-v-' + item.deptId"
93
+              :label="item.label"
94
+              :value="item.deptId"
95
+            />
96
+          </el-select>
97
+        </el-form-item>
98
+
99
+        <div class="rta-section-title">{{ dmT("sectionAssets") }}</div>
100
+        <el-form-item prop="operatingFixedAssets" :rules="formRules.money">
101
+          <template slot="label">{{ dmT("formOperatingFixedAssets") }}</template>
102
+          <div class="dm-input-with-unit">
103
+            <el-input-number
104
+              v-model="form.operatingFixedAssets"
105
+              :min="0"
106
+              :precision="2"
107
+              controls-position="right"
108
+              class="dm-input-with-unit__input"
109
+            />
110
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitWanYuan") }}</span>
111
+          </div>
112
+        </el-form-item>
113
+        <el-form-item prop="nonOperatingFixedAssets" :rules="formRules.money">
114
+          <template slot="label">{{ dmT("formNonOperatingFixedAssets") }}</template>
115
+          <div class="dm-input-with-unit">
116
+            <el-input-number
117
+              v-model="form.nonOperatingFixedAssets"
118
+              :min="0"
119
+              :precision="2"
120
+              controls-position="right"
121
+              class="dm-input-with-unit__input"
122
+            />
123
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitWanYuan") }}</span>
124
+          </div>
125
+        </el-form-item>
126
+        <el-form-item>
127
+          <template slot="label">{{ dmT("formFixedAssets") }}</template>
128
+          <div class="dm-input-with-unit">
129
+            <el-input :value="fixedAssetsDisplay" disabled class="dm-input-with-unit__input" />
130
+            <span class="dm-input-with-unit__suffix">{{ dmT("unitWanYuan") }}</span>
131
+          </div>
132
+        </el-form-item>
133
+        <el-form-item prop="remark" :rules="formRules.remark">
134
+          <template slot="label">{{ dmT("formRemark") }}</template>
135
+          <el-input v-model="form.remark" type="textarea" :rows="2" :placeholder="dmT('phRemark')" maxlength="500" show-word-limit />
136
+        </el-form-item>
137
+      </el-form>
138
+      <div slot="footer" class="dialog-footer">
139
+        <el-button type="primary" @click="submitForm">{{ dmCommon("ok") }}</el-button>
140
+        <el-button @click="cancel">{{ dmCommon("cancel") }}</el-button>
141
+      </div>
142
+    </el-dialog>
143
+
144
+    <el-dialog :title="dmCommon('viewTitle')" :visible.sync="viewOpen" width="640px" append-to-body>
145
+      <el-form class="dm-view-form rta-form" label-width="160px" size="small">
146
+        <div class="rta-section-title">{{ dmT("sectionBasic") }}</div>
147
+        <el-form-item :label="dmT('formTown')"><span>{{ viewRow.townName || dash }}</span></el-form-item>
148
+        <el-form-item :label="dmT('formVillage')"><span>{{ viewRow.villageName || dash }}</span></el-form-item>
149
+
150
+        <div class="rta-section-title">{{ dmT("sectionAssets") }}</div>
151
+        <el-form-item :label="dmT('formOperatingFixedAssets')">
152
+          <span>{{ formatWithUnit(formatMoney(viewRow.operatingFixedAssets), "unitWanYuan") }}</span>
153
+        </el-form-item>
154
+        <el-form-item :label="dmT('formNonOperatingFixedAssets')">
155
+          <span>{{ formatWithUnit(formatMoney(viewRow.nonOperatingFixedAssets), "unitWanYuan") }}</span>
156
+        </el-form-item>
157
+        <el-form-item :label="dmT('formFixedAssets')">
158
+          <span>{{ formatWithUnit(formatMoney(viewRow.fixedAssets), "unitWanYuan") }}</span>
159
+        </el-form-item>
160
+        <el-form-item :label="dmT('formRemark')">
161
+          <span class="rta-view-text">{{ viewRow.remark || dash }}</span>
162
+        </el-form-item>
163
+      </el-form>
164
+      <div slot="footer" class="dialog-footer">
165
+        <el-button v-hasPermi="['dataModel:ruralThreeAssets:edit']" type="primary" @click="handleEditFromView">{{ dmCommon("edit") }}</el-button>
166
+        <el-button @click="viewOpen = false">{{ dmCommon("close") }}</el-button>
167
+      </div>
168
+    </el-dialog>
169
+  </div>
170
+</template>
171
+
172
+<script>
173
+import dataModelLocaleMixin from "@/mixins/dataModelLocaleMixin"
174
+import {
175
+  listRuralThreeAssets,
176
+  getRuralThreeAssets,
177
+  addRuralThreeAssets,
178
+  updateRuralThreeAssets,
179
+  delRuralThreeAssets,
180
+  listRuralThreeAssetsTownOptions,
181
+  listRuralThreeAssetsVillageTree
182
+} from "@/api/dataModel/ruralThreeAssets"
183
+
184
+function createEmptyForm() {
185
+  return {
186
+    id: undefined,
187
+    villageDeptId: undefined,
188
+    operatingFixedAssets: undefined,
189
+    nonOperatingFixedAssets: undefined,
190
+    remark: undefined
191
+  }
192
+}
193
+
194
+function normalizeForm(data) {
195
+  const row = Object.assign(createEmptyForm(), data || {})
196
+  ;["operatingFixedAssets", "nonOperatingFixedAssets"].forEach((prop) => {
197
+    const val = row[prop]
198
+    row[prop] = val == null || val === "" ? undefined : Number(val)
199
+  })
200
+  return row
201
+}
202
+
203
+function flattenVillageOptions(tree) {
204
+  const list = []
205
+  ;(tree || []).forEach((county) => {
206
+    ;(county.children || []).forEach((town) => {
207
+      ;(town.children || []).forEach((village) => {
208
+        if (village == null || village.id == null) {
209
+          return
210
+        }
211
+        const townName = town.label || ""
212
+        const villageName = village.label || ""
213
+        list.push({
214
+          deptId: village.id,
215
+          label: townName ? `${townName} / ${villageName}` : villageName
216
+        })
217
+      })
218
+    })
219
+  })
220
+  return list
221
+}
222
+
223
+function calcFixedAssets(operating, nonOperating) {
224
+  if ((operating == null || operating === "") && (nonOperating == null || nonOperating === "")) {
225
+    return null
226
+  }
227
+  const a = operating == null || operating === "" ? 0 : Number(operating)
228
+  const b = nonOperating == null || nonOperating === "" ? 0 : Number(nonOperating)
229
+  if (!Number.isFinite(a) || !Number.isFinite(b)) {
230
+    return null
231
+  }
232
+  return Math.round((a + b) * 100) / 100
233
+}
234
+
235
+export default {
236
+  name: "RuralThreeAssets",
237
+  mixins: [dataModelLocaleMixin],
238
+  data() {
239
+    return {
240
+      dmNs: "ruralThreeAssets",
241
+      loading: false,
242
+      open: false,
243
+      viewOpen: false,
244
+      dialogEdit: false,
245
+      formKey: 0,
246
+      total: 0,
247
+      tableList: [],
248
+      townOptions: [],
249
+      villageOptions: [],
250
+      viewRow: {},
251
+      queryParams: {
252
+        pageNum: 1,
253
+        pageSize: 20,
254
+        townDeptId: undefined,
255
+        villageDeptId: undefined
256
+      },
257
+      form: createEmptyForm()
258
+    }
259
+  },
260
+  computed: {
261
+    dash() {
262
+      return this.$t("dataModel.status.dash")
263
+    },
264
+    dialogTitle() {
265
+      return this.dialogEdit ? this.dmT("dialogEdit") : this.dmT("dialogAdd")
266
+    },
267
+    fixedAssetsDisplay() {
268
+      const sum = calcFixedAssets(this.form.operatingFixedAssets, this.form.nonOperatingFixedAssets)
269
+      return sum == null ? this.dash : sum.toFixed(2)
270
+    },
271
+    formRules() {
272
+      return {
273
+        villageDeptId: [{ required: true, message: this.dmT("ruleVillageRequired"), trigger: "change" }],
274
+        money: [{ validator: this.validateOptionalMoney, trigger: "change" }],
275
+        remark: [{ max: 500, message: this.dmCommon("ruleLen500"), trigger: "blur" }]
276
+      }
277
+    }
278
+  },
279
+  created() {
280
+    this.loadOptions()
281
+    this.getList()
282
+  },
283
+  methods: {
284
+    loadOptions() {
285
+      listRuralThreeAssetsTownOptions().then((res) => {
286
+        this.townOptions = Array.isArray(res.data) ? res.data : []
287
+      })
288
+      this.loadVillageTree()
289
+    },
290
+    loadVillageTree() {
291
+      return listRuralThreeAssetsVillageTree()
292
+        .then((res) => {
293
+          const raw = res && res.data !== undefined ? res.data : res
294
+          this.villageOptions = flattenVillageOptions(Array.isArray(raw) ? raw : [])
295
+          if (!this.villageOptions.length) {
296
+            this.$modal.msgWarning(this.dmT("villageTreeEmpty"))
297
+          }
298
+        })
299
+        .catch(() => {
300
+          this.villageOptions = []
301
+        })
302
+    },
303
+    formatMoney(val) {
304
+      if (val == null || val === "") {
305
+        return this.dash
306
+      }
307
+      const n = Number(val)
308
+      if (!Number.isFinite(n)) {
309
+        return this.dash
310
+      }
311
+      return n.toFixed(2)
312
+    },
313
+    validateOptionalMoney(rule, value, callback) {
314
+      if (value == null || value === "") {
315
+        callback()
316
+        return
317
+      }
318
+      if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
319
+        callback(new Error(this.dmT("ruleMoneyNonNegative")))
320
+        return
321
+      }
322
+      callback()
323
+    },
324
+    buildListQuery() {
325
+      const q = {
326
+        pageNum: this.queryParams.pageNum,
327
+        pageSize: this.queryParams.pageSize
328
+      }
329
+      if (this.queryParams.townDeptId != null) {
330
+        q.townDeptId = this.queryParams.townDeptId
331
+      }
332
+      if (this.queryParams.villageDeptId != null) {
333
+        q.villageDeptId = this.queryParams.villageDeptId
334
+      }
335
+      return q
336
+    },
337
+    getList() {
338
+      this.loading = true
339
+      listRuralThreeAssets(this.buildListQuery())
340
+        .then((res) => {
341
+          this.tableList = res.rows || []
342
+          this.total = res.total || 0
343
+        })
344
+        .finally(() => {
345
+          this.loading = false
346
+        })
347
+    },
348
+    handleQuery() {
349
+      this.queryParams.pageNum = 1
350
+      this.getList()
351
+    },
352
+    resetQuery() {
353
+      this.queryParams = {
354
+        pageNum: 1,
355
+        pageSize: 20,
356
+        townDeptId: undefined,
357
+        villageDeptId: undefined
358
+      }
359
+      this.getList()
360
+    },
361
+    handleAdd() {
362
+      this.dialogEdit = false
363
+      this.formKey += 1
364
+      this.form = createEmptyForm()
365
+      this.open = true
366
+      this.$nextTick(() => {
367
+        if (this.$refs.form) {
368
+          this.$refs.form.clearValidate()
369
+        }
370
+      })
371
+    },
372
+    handleEdit(row) {
373
+      getRuralThreeAssets(row.id).then((res) => {
374
+        this.dialogEdit = true
375
+        this.formKey += 1
376
+        this.form = normalizeForm(res.data)
377
+        this.open = true
378
+        this.viewOpen = false
379
+        this.$nextTick(() => {
380
+          if (this.$refs.form) {
381
+            this.$refs.form.clearValidate()
382
+          }
383
+        })
384
+      })
385
+    },
386
+    handleView(row) {
387
+      getRuralThreeAssets(row.id).then((res) => {
388
+        this.viewRow = res.data || {}
389
+        this.viewOpen = true
390
+      })
391
+    },
392
+    handleEditFromView() {
393
+      if (!this.viewRow || this.viewRow.id == null) {
394
+        return
395
+      }
396
+      this.handleEdit(this.viewRow)
397
+    },
398
+    handleDelete(row) {
399
+      this.$modal
400
+        .confirm(this.dmT("confirmDelete"))
401
+        .then(() => delRuralThreeAssets(row.id))
402
+        .then(() => {
403
+          this.$modal.msgSuccess(this.dmCommon("msgDelOk"))
404
+          this.getList()
405
+        })
406
+        .catch(() => {})
407
+    },
408
+    normalizeMoney(val) {
409
+      return val == null || val === "" ? null : val
410
+    },
411
+    buildSubmitPayload() {
412
+      return {
413
+        id: this.form.id,
414
+        villageDeptId: this.form.villageDeptId,
415
+        operatingFixedAssets: this.normalizeMoney(this.form.operatingFixedAssets),
416
+        nonOperatingFixedAssets: this.normalizeMoney(this.form.nonOperatingFixedAssets),
417
+        remark: this.form.remark
418
+      }
419
+    },
420
+    submitForm() {
421
+      this.$refs.form.validate((valid) => {
422
+        if (!valid) {
423
+          return
424
+        }
425
+        const payload = this.buildSubmitPayload()
426
+        if (this.dialogEdit) {
427
+          updateRuralThreeAssets(payload).then(() => {
428
+            this.$modal.msgSuccess(this.dmCommon("msgEditOk"))
429
+            this.open = false
430
+            this.getList()
431
+          })
432
+        } else {
433
+          addRuralThreeAssets(payload).then(() => {
434
+            this.$modal.msgSuccess(this.dmCommon("msgAddOk"))
435
+            this.open = false
436
+            this.getList()
437
+          })
438
+        }
439
+      })
440
+    },
441
+    cancel() {
442
+      this.open = false
443
+      this.form = createEmptyForm()
444
+    }
445
+  }
446
+}
447
+</script>
448
+
449
+<style scoped>
450
+.rta-section-title {
451
+  margin: 4px 0 12px;
452
+  font-weight: 600;
453
+  color: #303133;
454
+}
455
+.rta-view-text {
456
+  white-space: pre-wrap;
457
+  word-break: break-all;
458
+}
459
+.dm-input-with-unit {
460
+  display: flex;
461
+  align-items: center;
462
+  width: 100%;
463
+}
464
+.dm-input-with-unit__input {
465
+  flex: 1;
466
+  width: 100%;
467
+}
468
+.dm-input-with-unit__suffix {
469
+  margin-left: 8px;
470
+  color: #909399;
471
+  white-space: nowrap;
472
+}
473
+.dm-input-with-unit >>> .el-input-number {
474
+  width: 100%;
475
+}
476
+</style>

+ 2 - 1
ruoyi-ui/src/views/dataModel/vaccinationData/index.vue

@@ -774,6 +774,7 @@ export default {
774 774
           return
775 775
         }
776 776
         this.exporting = true
777
+        const filename = `${this.exportForm.statYear}年${this.exportForm.statMonth}月疫苗接种数据.xlsx`
777 778
         this.$modal.loading(this.dmT("exportLoading"))
778 779
         exportVaccinationData(this.exportForm.statYear, this.exportForm.statMonth, this.exportForm.townDeptId)
779 780
           .then(async (data) => {
@@ -781,7 +782,7 @@ export default {
781 782
             if (isBlob) {
782 783
               saveAs(
783 784
                 data,
784
-                `vaccination_data_${this.exportForm.statYear}_${this.exportForm.statMonth}_${new Date().getTime()}.xlsx`
785
+                filename
785 786
               )
786 787
               this.exportOpen = false
787 788
             } else {