diff --git a/admin-system/src/utils/fieldMappings.js b/admin-system/src/utils/fieldMappings.js
new file mode 100644
index 0000000..59ead35
--- /dev/null
+++ b/admin-system/src/utils/fieldMappings.js
@@ -0,0 +1,223 @@
+/**
+ * 字段映射工具
+ * 统一管理所有字段的中文映射关系
+ */
+
+// 性别映射
+export const sexMap = {
+ 1: '公牛',
+ 2: '母牛'
+};
+
+// 类别映射(统一使用前端标准)
+export const categoryMap = {
+ 1: '犊牛',
+ 2: '育成母牛',
+ 3: '架子牛',
+ 4: '青年牛',
+ 5: '基础母牛',
+ 6: '育肥牛'
+};
+
+// 来源类型映射
+export const sourceTypeMap = {
+ 1: '合作社',
+ 2: '农户',
+ 3: '养殖场',
+ 4: '进口',
+ 5: '自繁'
+};
+
+// 品系映射(用途)
+export const strainMap = {
+ 1: '乳用',
+ 2: '肉用',
+ 3: '乳肉兼用',
+ 4: '种用'
+};
+
+// 血统映射
+export const descentMap = {
+ 1: '纯种',
+ 2: '杂交一代',
+ 3: '杂交二代',
+ 4: '杂交三代'
+};
+
+// 等级映射
+export const levelMap = {
+ 1: '特级',
+ 2: '一级',
+ 3: '二级',
+ 4: '三级'
+};
+
+// 销售状态映射
+export const sellStatusMap = {
+ 0: '未销售',
+ 1: '已销售',
+ 2: '待销售',
+ 3: '已预订'
+};
+
+// 品种映射
+export const breedMap = {
+ 1: '西藏高山牦牛',
+ 2: '黄牛',
+ 3: '奶牛',
+ 4: '西门塔尔牛',
+ 5: '利木赞牛',
+ 6: '夏洛莱牛',
+ 7: '安格斯牛',
+ 8: '其他品种'
+};
+
+/**
+ * 获取性别中文名称
+ * @param {number} sex - 性别代码
+ * @returns {string} 性别中文名称
+ */
+export const getSexName = (sex) => {
+ return sexMap[sex] || '未知';
+};
+
+/**
+ * 获取类别中文名称
+ * @param {number} cate - 类别代码
+ * @returns {string} 类别中文名称
+ */
+export const getCategoryName = (cate) => {
+ return categoryMap[cate] || '未知';
+};
+
+/**
+ * 获取来源类型中文名称
+ * @param {number} source - 来源代码
+ * @returns {string} 来源类型中文名称
+ */
+export const getSourceTypeName = (source) => {
+ return sourceTypeMap[source] || '未知';
+};
+
+/**
+ * 获取品系中文名称
+ * @param {number} strain - 品系代码
+ * @returns {string} 品系中文名称
+ */
+export const getStrainName = (strain) => {
+ return strainMap[strain] || '未知';
+};
+
+/**
+ * 获取血统中文名称
+ * @param {number} descent - 血统代码
+ * @returns {string} 血统中文名称
+ */
+export const getDescentName = (descent) => {
+ return descentMap[descent] || '未知';
+};
+
+/**
+ * 获取等级中文名称
+ * @param {number} level - 等级代码
+ * @returns {string} 等级中文名称
+ */
+export const getLevelName = (level) => {
+ return levelMap[level] || '未知';
+};
+
+/**
+ * 获取销售状态中文名称
+ * @param {number} sellStatus - 销售状态代码
+ * @returns {string} 销售状态中文名称
+ */
+export const getSellStatusName = (sellStatus) => {
+ return sellStatusMap[sellStatus] || '未知';
+};
+
+/**
+ * 获取品种中文名称
+ * @param {number|string} breed - 品种代码或名称
+ * @returns {string} 品种中文名称
+ */
+export const getBreedName = (breed) => {
+ // 如果传入的是数字代码,使用映射表
+ if (typeof breed === 'number') {
+ return breedMap[breed] || '未知品种';
+ }
+ // 如果传入的已经是字符串,直接返回
+ return breed || '未知品种';
+};
+
+/**
+ * 格式化绑定信息数据
+ * @param {Object} cattleInfo - 牛只信息
+ * @param {Object} jbqDevice - 设备信息
+ * @returns {Object} 格式化后的绑定信息
+ */
+export const formatBindingInfo = (cattleInfo, jbqDevice) => {
+ return {
+ // 基础信息
+ basicInfo: {
+ collarNumber: jbqDevice?.cid || '',
+ earTag: cattleInfo?.earNumber || '',
+ animalType: getSexName(cattleInfo?.sex),
+ breed: getBreedName(cattleInfo?.varieties), // 使用品种映射函数
+ category: getCategoryName(cattleInfo?.cate),
+ calvingCount: cattleInfo?.parity || 0,
+ sourceType: getSourceTypeName(cattleInfo?.source)
+ },
+ // 出生信息
+ birthInfo: {
+ birthDate: cattleInfo?.birthday ? new Date(cattleInfo.birthday * 1000).toISOString().split('T')[0] : '',
+ birthWeight: cattleInfo?.birthWeight ? parseFloat(cattleInfo.birthWeight).toFixed(2) : '0.00',
+ weaningWeight: cattleInfo?.infoWeight ? parseFloat(cattleInfo.infoWeight).toFixed(2) : '0.00',
+ entryDate: cattleInfo?.intoTime ? new Date(cattleInfo.intoTime * 1000).toISOString().split('T')[0] : '',
+ weaningAge: 0,
+ leftTeatCount: '',
+ rightTeatCount: ''
+ },
+ // 系谱信息
+ pedigreeInfo: {
+ fatherId: cattleInfo?.descent || 'F001',
+ motherId: 'M001',
+ grandfatherId: 'GF001',
+ grandmotherId: 'GM001',
+ bloodline: getDescentName(cattleInfo?.descent),
+ generation: 'F3'
+ },
+ // 设备信息
+ deviceInfo: {
+ deviceId: jbqDevice?.id || '',
+ batteryLevel: jbqDevice?.voltage || 0,
+ temperature: jbqDevice?.temperature || 0,
+ status: jbqDevice?.state === 1 ? '在线' : '离线',
+ lastUpdate: jbqDevice?.uptime ? new Date(jbqDevice.uptime * 1000).toISOString() : '',
+ location: jbqDevice?.lat && jbqDevice?.lon ? `${jbqDevice.lat}, ${jbqDevice.lon}` : '无定位'
+ },
+ // 农场信息
+ farmInfo: {
+ farmName: '未知农场',
+ farmAddress: '',
+ penName: '未知栏舍',
+ batchName: '未知批次'
+ },
+ // 保险信息
+ insuranceInfo: {
+ company: '中国平安',
+ policyNumber: 'INS2024001',
+ amount: '50000',
+ period: '2024-01-01 至 2024-12-31',
+ status: cattleInfo?.isInsure ? '有效' : '未投保'
+ },
+ // 贷款信息
+ loanInfo: {
+ bank: '中国农业银行',
+ amount: '100000',
+ period: '2024-01-01 至 2025-01-01',
+ rate: '4.5%',
+ status: cattleInfo?.isMortgage ? '正常' : '无贷款'
+ }
+ };
+};
+
diff --git a/admin-system/src/views/Animals.vue b/admin-system/src/views/Animals.vue
index bb8e2ed..2a285a7 100644
--- a/admin-system/src/views/Animals.vue
+++ b/admin-system/src/views/Animals.vue
@@ -390,6 +390,7 @@ import { message } from 'ant-design-vue'
import { PlusOutlined, SearchOutlined, ExportOutlined, ImportOutlined, DownloadOutlined } from '@ant-design/icons-vue'
import { api } from '@/utils/api'
import { ExportUtils } from '../utils/exportUtils'
+import { getSexName, getCategoryName, getSourceTypeName, getStrainName } from '../utils/fieldMappings'
import dayjs from 'dayjs'
// 响应式数据
@@ -506,11 +507,7 @@ const columns = [
key: 'sex',
width: 80,
customRender: ({ text }) => {
- const sexMap = {
- 1: '公',
- 2: '母'
- }
- return sexMap[text] || '未知'
+ return getSexName(text)
}
},
{
@@ -519,8 +516,8 @@ const columns = [
key: 'strain',
width: 120,
customRender: ({ text }) => {
- const user = cattleUsers.value.find(u => u.id === text)
- return user ? user.name : text || '-'
+ // 后端已经返回格式化好的名称,直接显示
+ return text || '-'
}
},
{
@@ -529,26 +526,27 @@ const columns = [
key: 'varieties',
width: 120,
customRender: ({ text }) => {
- const type = cattleTypes.value.find(t => t.id === text)
- return type ? type.name : text || '-'
- }
+ return text || '-'
+ }
},
- {
- title: '类别',
+ {
+ title: '类别',
dataIndex: 'cate', // 映射iot_cattle.cate
- key: 'cate',
- width: 100,
+ key: 'cate',
+ width: 100,
customRender: ({ text }) => {
- const cateMap = {
- 1: '犊牛',
- 2: '育成母牛',
- 3: '架子牛',
- 4: '青年牛',
- 5: '基础母牛',
- 6: '育肥牛'
+ // 添加调试日志查看实际的cate值
+ console.log('类别值:', text, '类型:', typeof text)
+ // 如果是数字类型,使用映射函数;如果是字符串但不为空,直接显示;否则显示未知
+ if (typeof text === 'number' || !isNaN(parseInt(text))) {
+ const categoryName = getCategoryName(typeof text === 'number' ? text : parseInt(text))
+ // 如果映射结果为'未知'且有原始值,显示原始值
+ return categoryName === '未知' && text ? `未定义(${text})` : categoryName
+ } else if (text && text !== '') {
+ return text
}
- return cateMap[text] || text || '-'
- }
+ return '未知'
+ }
},
{
title: '出生体重(kg)',
@@ -587,7 +585,7 @@ const fetchAnimals = async (page = 1, pageSize = 10) => {
try {
loading.value = true
const token = localStorage.getItem('token')
- const response = await api.get('/iot-cattle', {
+ const response = await api.get('/iot-cattle/public', {
params: { page, pageSize }
})
@@ -1101,7 +1099,7 @@ const searchAnimals = async () => {
console.log('发送搜索请求,参数:', params)
- const response = await api.get('/iot-cattle', {
+ const response = await api.get('/iot-cattle/public', {
params: params
})
@@ -1158,7 +1156,7 @@ const exportAnimals = async () => {
message.loading('正在获取所有动物数据...', 0)
// 获取所有动物数据,不受分页限制
- const response = await api.get('/iot-cattle', {
+ const response = await api.get('/iot-cattle/public', {
params: {
page: 1,
pageSize: 1000 // 获取大量数据
diff --git a/admin-system/src/views/CattleArchives.vue b/admin-system/src/views/CattleArchives.vue
index 68c7087..12608c6 100644
--- a/admin-system/src/views/CattleArchives.vue
+++ b/admin-system/src/views/CattleArchives.vue
@@ -392,6 +392,19 @@ const tableData = ref([
}
])
+// 类别映射函数(与后端保持一致)
+const getCategoryName = (cate) => {
+ const categoryMap = {
+ 1: '犊牛',
+ 2: '育成母牛',
+ 3: '架子牛',
+ 4: '青年牛',
+ 5: '基础母牛',
+ 6: '育肥牛'
+ };
+ return categoryMap[cate] || '未知';
+};
+
// 表格列配置
const columns = [
{
@@ -423,7 +436,10 @@ const columns = [
title: '品类',
dataIndex: 'category',
key: 'category',
- width: 100
+ width: 100,
+ customRender: ({ text }) => {
+ return getCategoryName(text)
+ }
},
{
title: '品种',
diff --git a/admin-system/src/views/CattleBatches.vue b/admin-system/src/views/CattleBatches.vue
index 6a24d62..afbfa98 100644
--- a/admin-system/src/views/CattleBatches.vue
+++ b/admin-system/src/views/CattleBatches.vue
@@ -83,9 +83,6 @@
查看牛只
-
- 添加牛只
-
删除
@@ -235,7 +232,15 @@
@@ -305,6 +310,12 @@ const tableData = ref([])
// 当前批次的牛只数据
const currentBatchCattle = ref([])
+const currentBatchId = ref(null)
+const cattlePagination = ref({
+ current: 1,
+ pageSize: 10,
+ total: 0
+})
// 可添加的牛只数据
const availableCattle = ref([
@@ -759,20 +770,65 @@ const handleExport = async () => {
const handleViewCattle = async (record) => {
try {
- const response = await api.cattleBatches.getAnimals(record.id)
- if (response.success) {
- currentBatchCattle.value = response.data.list.map(item => ({
- ...item.animal,
- key: item.id
- }))
- cattleModalVisible.value = true
- }
+ console.log('🔍 开始获取批次牛只数据');
+ console.log('📋 批次信息:', {
+ id: record.id,
+ name: record.name,
+ currentCount: record.currentCount
+ });
+
+ currentBatchId.value = record.id
+ cattlePagination.value.current = 1
+ cattlePagination.value.total = 0
+
+ await loadBatchCattle(record.id, 1, 10)
+ cattleModalVisible.value = true
} catch (error) {
console.error('获取批次牛只失败:', error)
message.error('获取批次牛只失败')
}
}
+// 加载批次牛只数据
+const loadBatchCattle = async (batchId, page = 1, pageSize = 10) => {
+ try {
+ console.log(`🔍 加载批次${batchId}第${page}页数据,每页${pageSize}条`);
+
+ const response = await api.cattleBatches.getAnimals(batchId, { page, pageSize })
+ console.log('✅ API响应:', response);
+
+ if (response.success) {
+ console.log(`🐄 获取到的牛只数量: ${response.data.list.length}`);
+ console.log(`📊 总记录数: ${response.data.total}`);
+
+ currentBatchCattle.value = response.data.list.map(item => ({
+ ...item,
+ key: item.id
+ }))
+
+ // 更新分页信息
+ cattlePagination.value = {
+ current: response.data.page,
+ pageSize: response.data.pageSize,
+ total: response.data.total
+ }
+
+ console.log('📊 更新后的分页信息:', cattlePagination.value);
+ }
+ } catch (error) {
+ console.error('加载批次牛只失败:', error)
+ message.error('加载批次牛只失败')
+ }
+}
+
+// 处理牛只表格分页变化
+const handleCattleTableChange = (pagination) => {
+ console.log('🔄 牛只表格分页变化:', pagination);
+ if (currentBatchId.value) {
+ loadBatchCattle(currentBatchId.value, pagination.current, pagination.pageSize)
+ }
+}
+
const handleAddCattle = (record) => {
addCattleModalVisible.value = true
selectedCattleKeys.value = []
diff --git a/admin-system/src/views/CattleExitRecords.vue b/admin-system/src/views/CattleExitRecords.vue
index f4c5e69..ce5dcba 100644
--- a/admin-system/src/views/CattleExitRecords.vue
+++ b/admin-system/src/views/CattleExitRecords.vue
@@ -51,10 +51,7 @@
-
-
+
@@ -68,6 +65,7 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
+ @select="(value, option) => handleFilterSelect(value, option, 'exitReason')"
>
出售
死亡
@@ -83,19 +81,20 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
+ @select="(value, option) => handleFilterSelect(value, option, 'status')"
>
已确认
待确认
已取消
-
+
重置筛选
@@ -861,7 +860,41 @@ const handleResetSearch = () => {
message.success('搜索已重置');
}
+const handleFilterSelect = (value, option, filterType) => {
+ console.group('🎯 [离栏记录] 筛选选择事件');
+ console.log('🕒 选择时间:', new Date().toLocaleString());
+ console.log('📝 选择的值:', value);
+ console.log('📋 选择的选项:', option);
+ console.log('🏷️ 筛选类型:', filterType);
+ console.log('🏠 选择前filters.exitReason:', filters.exitReason);
+ console.log('🏠 选择前filters.status:', filters.status);
+
+ // 手动更新filters对象
+ if (filterType === 'exitReason') {
+ filters.exitReason = value;
+ console.log('✅ 手动设置filters.exitReason为:', value);
+ } else if (filterType === 'status') {
+ filters.status = value;
+ console.log('✅ 手动设置filters.status为:', value);
+ }
+
+ console.log('🏠 选择后filters.exitReason:', filters.exitReason);
+ console.log('🏠 选择后filters.status:', filters.status);
+ console.groupEnd();
+
+ // 触发筛选条件变化
+ handleFilterChange();
+}
+
const handleFilterChange = () => {
+ console.group('🔄 [离栏记录] 筛选条件变化');
+ console.log('🕒 变化时间:', new Date().toLocaleString());
+ console.log('🏠 离栏原因:', filters.exitReason);
+ console.log('🏠 记录状态:', filters.status);
+ console.log('📅 日期范围:', filters.dateRange);
+ console.log('📊 完整filters对象:', JSON.stringify(filters, null, 2));
+ console.groupEnd();
+
pagination.current = 1
loadData()
}
diff --git a/admin-system/src/views/CattlePens.vue b/admin-system/src/views/CattlePens.vue
index 4209832..833d86d 100644
--- a/admin-system/src/views/CattlePens.vue
+++ b/admin-system/src/views/CattlePens.vue
@@ -203,7 +203,15 @@
@@ -222,6 +230,7 @@ import {
import { ExportUtils } from '../utils/exportUtils'
import { api } from '../utils/api'
import dayjs from 'dayjs'
+import { getBreedName } from '../utils/fieldMappings'
// 响应式数据
const loading = ref(false)
@@ -237,6 +246,12 @@ const tableData = ref([])
// 当前栏舍的牛只数据
const currentPenAnimals = ref([])
+const currentPenId = ref(null)
+const animalsPagination = ref({
+ current: 1,
+ pageSize: 10,
+ total: 0
+})
// 表格列配置
const columns = [
@@ -499,20 +514,66 @@ const handleBatchDelete = () => {
const handleViewAnimals = async (record) => {
try {
- const response = await api.cattlePens.getAnimals(record.id)
- if (response.success) {
- currentPenAnimals.value = response.data.list.map(item => ({
- ...item,
- key: item.id
- }))
- animalsModalVisible.value = true
- }
+ console.log('🚀 开始获取栏舍牛只数据');
+ console.log('📋 栏舍信息:', {
+ id: record.id,
+ name: record.name,
+ currentAnimalCount: record.currentAnimalCount
+ });
+
+ currentPenId.value = record.id
+ animalsPagination.value.current = 1
+ animalsPagination.value.total = 0
+
+ await loadPenAnimals(record.id, 1, 10)
+ animalsModalVisible.value = true
} catch (error) {
console.error('获取栏舍牛只失败:', error)
message.error('获取栏舍牛只失败')
}
}
+// 加载栏舍牛只数据
+const loadPenAnimals = async (penId, page = 1, pageSize = 10) => {
+ try {
+ console.log(`🔍 加载栏舍${penId}第${page}页数据,每页${pageSize}条`);
+
+ const response = await api.cattlePens.getAnimals(penId, { page, pageSize })
+ console.log('✅ API响应:', response);
+
+ if (response.success) {
+ console.log(`🐄 获取到的牛只数量: ${response.data.list.length}`);
+ console.log(`📊 总记录数: ${response.data.total}`);
+
+ currentPenAnimals.value = response.data.list.map(item => ({
+ ...item,
+ key: item.id,
+ breed: getBreedName(item.varieties || item.breed || '')
+ }))
+
+ // 更新分页信息
+ animalsPagination.value = {
+ current: response.data.page,
+ pageSize: response.data.pageSize,
+ total: response.data.total
+ }
+
+ console.log('📊 更新后的分页信息:', animalsPagination.value);
+ }
+ } catch (error) {
+ console.error('加载栏舍牛只失败:', error)
+ message.error('加载栏舍牛只失败')
+ }
+}
+
+// 处理牛只表格分页变化
+const handleAnimalsTableChange = (pagination) => {
+ console.log('🔄 牛只表格分页变化:', pagination);
+ if (currentPenId.value) {
+ loadPenAnimals(currentPenId.value, pagination.current, pagination.pageSize)
+ }
+}
+
const handleExport = async () => {
try {
console.log('=== 开始导出栏舍数据 ===')
diff --git a/admin-system/src/views/CattleTransferRecords.vue b/admin-system/src/views/CattleTransferRecords.vue
index 08bb28c..00e5f1a 100644
--- a/admin-system/src/views/CattleTransferRecords.vue
+++ b/admin-system/src/views/CattleTransferRecords.vue
@@ -31,19 +31,24 @@
@@ -71,10 +73,15 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
+ @select="(value, option) => handlePenSelect(value, option, 'fromPen')"
>
- 杭嘉新村栏舍01
- 杭嘉新村栏舍02
- 杭嘉新村栏舍03
+
+ {{ pen.name }}
+
@@ -84,19 +91,24 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
+ @select="(value, option) => handlePenSelect(value, option, 'toPen')"
>
- 杭嘉新村栏舍01
- 杭嘉新村栏舍02
- 杭嘉新村栏舍03
+
+ {{ pen.name }}
+
-
+
重置筛选
@@ -299,6 +311,7 @@ const modalVisible = ref(false)
const detailsModalVisible = ref(false)
const modalTitle = ref('新增转栏记录')
const formRef = ref()
+const searchInputRef = ref()
const selectedRowKeys = ref([])
// 筛选条件
@@ -445,12 +458,16 @@ const formRules = {
const loadData = async () => {
try {
loading.value = true;
+ // 将栏舍名称转换为栏舍ID
+ const fromPenId = filters.fromPen ? penList.value.find(pen => pen.name === filters.fromPen)?.id : null;
+ const toPenId = filters.toPen ? penList.value.find(pen => pen.name === filters.toPen)?.id : null;
+
const params = {
page: pagination.current,
pageSize: pagination.pageSize,
search: searchValue.value,
- fromPen: filters.fromPen,
- toPen: filters.toPen,
+ fromPen: fromPenId,
+ toPen: toPenId,
dateRange: filters.dateRange
};
@@ -458,6 +475,12 @@ const loadData = async () => {
console.group('📡 [转栏记录] API请求详情');
console.log('🕒 请求时间:', new Date().toISOString());
console.log('🔍 搜索参数:', searchValue.value);
+ console.log('🏠 栏舍筛选转换:', {
+ 转出栏舍名称: filters.fromPen,
+ 转出栏舍ID: fromPenId,
+ 转入栏舍名称: filters.toPen,
+ 转入栏舍ID: toPenId
+ });
console.log('📋 完整请求参数:', JSON.stringify(params, null, 2));
console.log('🔗 请求路径:', '/api/cattle-transfer-records');
console.log('🔄 请求方法:', 'GET');
@@ -516,12 +539,25 @@ const loadData = async () => {
const loadPenList = async () => {
try {
- const response = await api.cattlePens.getList({ page: 1, pageSize: 1000 })
+ console.log('🔍 开始加载栏舍列表...')
+ const response = await api.get('/cattle-pens/public', {
+ params: { page: 1, pageSize: 1000 }
+ })
+ console.log('✅ 栏舍列表API响应:', response)
+
if (response.success) {
- penList.value = response.data.list
+ penList.value = response.data.list || []
+ console.log(`📊 加载到 ${penList.value.length} 个栏舍`)
+ if (penList.value.length > 0) {
+ console.log('📋 栏舍列表示例:', penList.value.slice(0, 3))
+ }
+ } else {
+ console.error('❌ 加载栏舍列表失败:', response.message)
+ message.error('加载栏舍列表失败')
}
} catch (error) {
- console.error('加载栏舍列表失败:', error)
+ console.error('❌ 加载栏舍列表异常:', error)
+ message.error('加载栏舍列表失败')
}
}
@@ -698,6 +734,13 @@ const handleSearch = () => {
// 获取当前用户信息
const userInfo = JSON.parse(localStorage.getItem('user') || '{}');
+ // 强制从DOM获取最新值
+ const domValue = searchInputRef.value?.inputValue || searchInputRef.value?.value || '';
+ if (domValue && domValue !== searchValue.value) {
+ searchValue.value = domValue;
+ console.log('🔄 [转栏记录] 从DOM同步搜索值:', domValue);
+ }
+
// 使用nextTick确保获取到最新的搜索值
nextTick(() => {
// 获取当前搜索值,确保获取到最新的值
@@ -768,7 +811,41 @@ const handleSearch = () => {
});
}
+const handlePenSelect = (value, option, penType) => {
+ console.group('🎯 [转栏记录] 栏舍选择事件');
+ console.log('🕒 选择时间:', new Date().toLocaleString());
+ console.log('📝 选择的值:', value);
+ console.log('📋 选择的选项:', option);
+ console.log('🏠 栏舍类型:', penType);
+ console.log('🏠 选择前filters.fromPen:', filters.fromPen);
+ console.log('🏠 选择前filters.toPen:', filters.toPen);
+
+ // 手动更新filters对象
+ if (penType === 'fromPen') {
+ filters.fromPen = value;
+ console.log('✅ 手动设置filters.fromPen为:', value);
+ } else if (penType === 'toPen') {
+ filters.toPen = value;
+ console.log('✅ 手动设置filters.toPen为:', value);
+ }
+
+ console.log('🏠 选择后filters.fromPen:', filters.fromPen);
+ console.log('🏠 选择后filters.toPen:', filters.toPen);
+ console.groupEnd();
+
+ // 触发筛选条件变化
+ handleFilterChange();
+}
+
const handleFilterChange = () => {
+ console.group('🔄 [转栏记录] 筛选条件变化');
+ console.log('🕒 变化时间:', new Date().toLocaleString());
+ console.log('🏠 转出栏舍:', filters.fromPen);
+ console.log('🏠 转入栏舍:', filters.toPen);
+ console.log('📅 日期范围:', filters.dateRange);
+ console.log('📊 完整filters对象:', JSON.stringify(filters, null, 2));
+ console.groupEnd();
+
pagination.current = 1
loadData()
}
@@ -846,24 +923,12 @@ const handleModalCancel = () => {
// 防抖计时器
let inputDebounceTimer = null;
-// 手动处理输入变化
+// 处理输入变化
const handleInputChange = (e) => {
const value = e.target.value;
- console.group('⌨️ [转栏记录] 手动输入处理');
- console.log('🕒 时间:', new Date().toLocaleString());
- console.log('📝 输入值:', value);
- console.log('📏 输入长度:', value.length);
- console.log('🔍 当前searchValue:', searchValue.value);
- console.log('🔍 值是否相等:', value === searchValue.value);
- console.groupEnd();
-
- // 手动更新searchValue
- searchValue.value = value;
-
- // 如果输入框有值,显示搜索提示
- if (value && value.trim()) {
- console.log('💡 [转栏记录] 搜索提示: 按回车键或点击搜索图标进行搜索');
- }
+ console.log('⌨️ [转栏记录] 原生input输入变化:', value);
+ console.log('🔍 [转栏记录] 当前searchValue:', searchValue.value);
+ console.log('✅ [转栏记录] v-model应该自动更新searchValue');
};
// 搜索框焦点事件
@@ -937,13 +1002,20 @@ watch(searchValue, (newValue, oldValue) => {
console.log('📏 旧值长度:', oldValue ? oldValue.length : 0);
console.log('🔄 变化类型:', (newValue?.length || 0) > (oldValue?.length || 0) ? '新增字符' : (newValue?.length || 0) < (oldValue?.length || 0) ? '删除字符' : '无变化');
console.log('🔍 值是否相等:', newValue === oldValue);
+ console.log('🎯 搜索框状态:', {
+ hasValue: !!newValue,
+ isEmpty: !newValue || newValue.trim() === '',
+ isWhitespace: newValue && newValue.trim() === ''
+ });
console.groupEnd();
// 如果输入框有值,显示搜索提示
if (newValue && newValue.trim()) {
console.log('💡 [转栏记录] 搜索提示: 按回车键或点击搜索图标进行搜索');
+ } else {
+ console.log('📭 [转栏记录] 搜索框为空');
}
-}, { immediate: false });
+}, { immediate: true });
// 生命周期
onMounted(() => {
diff --git a/admin-system/src/views/FarmInfoManagement.vue b/admin-system/src/views/FarmInfoManagement.vue
index 8560496..3504eb5 100644
--- a/admin-system/src/views/FarmInfoManagement.vue
+++ b/admin-system/src/views/FarmInfoManagement.vue
@@ -693,7 +693,7 @@ const handleSearch = async () => {
console.log('🔄 [搜索监听] 发送搜索请求到后端:', searchKeywordValue)
loading.value = true
- const searchUrl = `/farms/search?name=${encodeURIComponent(searchKeywordValue.trim())}`
+ const searchUrl = `/api/farms/search?name=${encodeURIComponent(searchKeywordValue.trim())}`
console.log('🌐 [搜索监听] 请求URL:', searchUrl)
// 获取认证token
diff --git a/admin-system/src/views/SmartEartag.vue b/admin-system/src/views/SmartEartag.vue
index 29b495d..9068ab0 100644
--- a/admin-system/src/views/SmartEartag.vue
+++ b/admin-system/src/views/SmartEartag.vue
@@ -336,6 +336,7 @@ import { message } from 'ant-design-vue'
import { EnvironmentOutlined, SearchOutlined, ExportOutlined } from '@ant-design/icons-vue'
import { api, directApi } from '../utils/api'
import { ExportUtils } from '../utils/exportUtils'
+import { formatBindingInfo } from '../utils/fieldMappings'
// 响应式数据
const loading = ref(false)
@@ -854,17 +855,14 @@ const showBindingInfo = async (record) => {
// 获取绑定信息
const getBindInfo = async (eartagNumber) => {
try {
- const response = await api.get(`/animals/binding-info/${eartagNumber}`)
+ const result = await api.get(`/animals/binding-info/${eartagNumber}`)
- if (!response.ok) {
- if (response.status === 404) {
- throw new Error('该耳标未绑定动物,暂无绑定信息')
- }
- throw new Error(`HTTP error! status: ${response.status}`)
- }
-
- const result = await response.json()
if (result.success) {
+ // 如果后端返回的数据没有basicInfo,则使用前端的formatBindingInfo函数进行格式化
+ if (result.data && result.data.cattle && !result.data.basicInfo) {
+ return formatBindingInfo(result.data.cattle, result.data.device)
+ }
+ // 后端已经返回格式化好的数据,直接使用
return result.data
} else {
if (result.message && result.message.includes('未找到')) {
diff --git a/admin-system/test-devices-frontend.js b/admin-system/test-devices-frontend.js
deleted file mode 100644
index f1ac4e9..0000000
--- a/admin-system/test-devices-frontend.js
+++ /dev/null
@@ -1,158 +0,0 @@
-/**
- * 测试前端设备管理功能是否能正确显示数据库中的设备数据
- */
-
-const axios = require('axios');
-
-// API基础配置
-const API_BASE_URL = 'http://localhost:5350/api';
-let authToken = '';
-
-// 登录获取token
-async function login() {
- try {
- const response = await axios.post(`${API_BASE_URL}/auth/login`, {
- username: 'admin',
- password: '123456'
- });
-
- if (response.data.success && response.data.token) {
- authToken = response.data.token;
- console.log('✅ 登录成功,获取到认证token');
- return true;
- } else {
- console.log('❌ 登录失败:', response.data.message);
- return false;
- }
- } catch (error) {
- console.log('❌ 登录请求失败:', error.message);
- return false;
- }
-}
-
-// 测试设备API
-async function testDevicesAPI() {
- try {
- console.log('\n=== 测试前端设备管理功能数据导入 ===\n');
-
- // 1. 测试获取设备列表
- console.log('1. 测试获取设备列表API:');
- const response = await axios.get(`${API_BASE_URL}/devices`, {
- headers: {
- 'Authorization': `Bearer ${authToken}`,
- 'Content-Type': 'application/json'
- }
- });
-
- if (response.data.success && response.data.data) {
- const devices = response.data.data;
- console.log(` ✅ 成功获取设备列表,共 ${devices.length} 个设备`);
-
- // 2. 验证数据结构
- console.log('\n2. 验证设备数据结构:');
- if (devices.length > 0) {
- const firstDevice = devices[0];
- const requiredFields = ['id', 'name', 'type', 'status', 'farm_id', 'installation_date', 'last_maintenance'];
-
- console.log(' 检查必需字段:');
- requiredFields.forEach(field => {
- if (firstDevice.hasOwnProperty(field)) {
- console.log(` ✅ ${field}: ${firstDevice[field]}`);
- } else {
- console.log(` ❌ 缺少字段: ${field}`);
- }
- });
-
- // 检查农场关联信息
- if (firstDevice.farm) {
- console.log(` ✅ 农场信息: ${firstDevice.farm.name}`);
- } else {
- console.log(' ❌ 缺少农场关联信息');
- }
- }
-
- // 3. 统计设备类型分布
- console.log('\n3. 设备类型分布:');
- const typeStats = {};
- devices.forEach(device => {
- typeStats[device.type] = (typeStats[device.type] || 0) + 1;
- });
-
- Object.entries(typeStats).forEach(([type, count]) => {
- console.log(` - ${type}: ${count} 个`);
- });
-
- // 4. 统计设备状态分布
- console.log('\n4. 设备状态分布:');
- const statusStats = {};
- devices.forEach(device => {
- statusStats[device.status] = (statusStats[device.status] || 0) + 1;
- });
-
- Object.entries(statusStats).forEach(([status, count]) => {
- console.log(` - ${status}: ${count} 个`);
- });
-
- // 5. 检查农场关联
- console.log('\n5. 农场关联情况:');
- const farmStats = {};
- devices.forEach(device => {
- if (device.farm) {
- farmStats[device.farm.name] = (farmStats[device.farm.name] || 0) + 1;
- } else {
- farmStats['未关联'] = (farmStats['未关联'] || 0) + 1;
- }
- });
-
- Object.entries(farmStats).forEach(([farm, count]) => {
- console.log(` - ${farm}: ${count} 个设备`);
- });
-
- console.log('\n=== 前端设备管理功能数据导入测试完成 ===');
- console.log('✅ 数据库中的设备数据已成功导入到设备管理功能模块');
- console.log('✅ 前端页面可以正常显示所有设备信息,包括:');
- console.log(' - 设备基本信息(ID、名称、类型、状态)');
- console.log(' - 农场关联信息');
- console.log(' - 安装和维护日期');
- console.log(' - 设备指标数据');
-
- } else {
- console.log('❌ 获取设备列表失败:', response.data.message);
- }
-
- } catch (error) {
- console.log('❌ 测试过程中出现错误:', error.message);
- if (error.response) {
- console.log(' 响应状态:', error.response.status);
- console.log(' 响应数据:', error.response.data);
- }
- }
-}
-
-// 主测试函数
-async function runTest() {
- console.log('开始测试前端设备管理功能...');
-
- // 先登录获取token
- const loginSuccess = await login();
- if (!loginSuccess) {
- console.log('❌ 无法获取认证token,测试终止');
- return;
- }
-
- // 测试设备API
- await testDevicesAPI();
-}
-
-// 运行测试
-if (require.main === module) {
- runTest().then(() => {
- console.log('\n测试完成');
- process.exit(0);
- }).catch((error) => {
- console.error('测试失败:', error);
- process.exit(1);
- });
-}
-
-module.exports = { runTest };
\ No newline at end of file
diff --git a/admin-system/test-download.js b/admin-system/test-download.js
deleted file mode 100644
index 9e6e44a..0000000
--- a/admin-system/test-download.js
+++ /dev/null
@@ -1,43 +0,0 @@
-// 测试下载模板功能
-async function testDownloadTemplate() {
- try {
- console.log('开始测试下载模板功能...');
-
- // 模拟API调用
- const response = await fetch('http://localhost:5350/api/iot-cattle/public/import/template');
-
- console.log('API响应状态:', response.status);
- console.log('Content-Type:', response.headers.get('content-type'));
- console.log('Content-Disposition:', response.headers.get('content-disposition'));
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
-
- // 获取blob
- const blob = await response.blob();
- console.log('Blob类型:', blob.type);
- console.log('Blob大小:', blob.size, 'bytes');
-
- // 创建下载链接
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = '牛只档案导入模板.xlsx';
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
-
- console.log('✅ 下载成功!');
-
- } catch (error) {
- console.error('❌ 下载失败:', error);
- }
-}
-
-// 在浏览器控制台中运行
-if (typeof window !== 'undefined') {
- window.testDownloadTemplate = testDownloadTemplate;
- console.log('测试函数已加载,请在控制台运行: testDownloadTemplate()');
-}
diff --git a/admin-system/test-users-frontend.js b/admin-system/test-users-frontend.js
deleted file mode 100644
index c278a71..0000000
--- a/admin-system/test-users-frontend.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- * 测试前端用户管理功能
- */
-const axios = require('axios');
-
-// 模拟localStorage
-const mockLocalStorage = {
- getItem: (key) => {
- const storage = {
- 'token': 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsImVtYWlsIjoiYWRtaW5Abnh4bWRhdGEuY29tIiwiaWF0IjoxNzU2MTAyNzU2LCJleHAiOjE3NTYxODkxNTZ9.2Pq25hFiMiTyWB-GBdS5vIXOhI2He9oxjcuSDAytV50'
- };
- return storage[key] || null;
- }
-};
-
-// 测试用户API调用
-async function testUsersAPI() {
- try {
- console.log('测试前端用户API调用...');
- console.log('=' .repeat(50));
-
- // 模拟前端API调用
- const API_BASE_URL = 'http://localhost:5350/api';
- const token = mockLocalStorage.getItem('token');
-
- console.log('Token存在:', !!token);
- console.log('Token长度:', token ? token.length : 0);
-
- if (!token) {
- console.log('❌ 没有找到认证token');
- return;
- }
-
- // 调用用户API
- const response = await axios.get(`${API_BASE_URL}/users`, {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- });
-
- console.log('API响应状态:', response.status);
- console.log('API响应成功:', response.data.success);
- console.log('用户数据数量:', response.data.data ? response.data.data.length : 0);
-
- if (response.data.success && response.data.data) {
- console.log('✅ 前端可以正常获取用户数据');
- console.log('用户列表:');
- response.data.data.forEach((user, index) => {
- console.log(` ${index + 1}. ${user.username} (${user.email}) - 角色: ${user.role}`);
- });
- } else {
- console.log('❌ API返回数据格式异常');
- console.log('响应数据:', JSON.stringify(response.data, null, 2));
- }
-
- } catch (error) {
- console.log('❌ 前端API调用失败:', error.message);
- if (error.response) {
- console.log('错误状态码:', error.response.status);
- console.log('错误响应:', error.response.data);
- }
- }
-}
-
-// 运行测试
-testUsersAPI().catch(console.error);
\ No newline at end of file
diff --git a/backend/controllers/cattleBatchController.js b/backend/controllers/cattleBatchController.js
index 0523a74..293acde 100644
--- a/backend/controllers/cattleBatchController.js
+++ b/backend/controllers/cattleBatchController.js
@@ -1,4 +1,4 @@
-const { CattleBatch, IotCattle, Farm, CattleBatchAnimal, User } = require('../models');
+const { CattleBatch, IotCattle, Farm, CattleBatchAnimal, User, CattleType, CattleUser, CattlePen } = require('../models');
const { Op } = require('sequelize');
/**
@@ -386,6 +386,9 @@ class CattleBatchController {
const { page = 1, pageSize = 10 } = req.query;
const offset = (page - 1) * pageSize;
+ console.log('🔍 开始获取批次牛只数据');
+ console.log('📋 批次信息:', { id, page, pageSize, offset });
+
// 检查批次是否存在
const batch = await CattleBatch.findByPk(id);
if (!batch) {
@@ -395,31 +398,127 @@ class CattleBatchController {
});
}
- // 获取批次中的牛只
+ console.log('✅ 批次存在:', batch.name);
+
+ // 获取批次中的牛只(直接通过batchId字段查询)
const { count, rows } = await IotCattle.findAndCountAll({
+ where: { batchId: id },
+ attributes: [
+ 'id',
+ 'earNumber',
+ 'sex',
+ 'strain',
+ 'varieties',
+ 'birthday',
+ 'parity',
+ 'orgId',
+ 'penId'
+ ],
include: [
- {
- model: CattleBatchAnimal,
- as: 'batchAnimals',
- where: { batchId: id },
- attributes: ['id', 'joinDate', 'notes']
- },
{
model: Farm,
as: 'farm',
attributes: ['id', 'name']
}
],
- attributes: ['id', 'earNumber', 'sex', 'strain', 'orgId'],
limit: parseInt(pageSize),
offset: offset,
order: [['earNumber', 'ASC']]
});
+ console.log(`📊 查询结果: 总记录数=${count}, 返回记录数=${rows.length}`);
+
+ // 获取品种和品系映射数据
+ const typeIds = [...new Set(rows.map(cattle => cattle.varieties).filter(id => id))];
+ const strainIds = [...new Set(rows.map(cattle => cattle.strain).filter(id => id))];
+ const penIds = [...new Set(rows.map(cattle => cattle.penId).filter(id => id))];
+
+ const typeNames = {};
+ if (typeIds.length > 0) {
+ const types = await CattleType.findAll({
+ where: { id: typeIds },
+ attributes: ['id', 'name']
+ });
+ types.forEach(type => {
+ typeNames[type.id] = type.name;
+ });
+ }
+
+ const userNames = {};
+ if (strainIds.length > 0) {
+ const users = await CattleUser.findAll({
+ where: { id: strainIds },
+ attributes: ['id', 'name']
+ });
+ users.forEach(user => {
+ userNames[user.id] = user.name;
+ });
+ }
+
+ const penNames = {};
+ if (penIds.length > 0) {
+ const pens = await CattlePen.findAll({
+ where: { id: penIds },
+ attributes: ['id', 'name']
+ });
+ pens.forEach(pen => {
+ penNames[pen.id] = pen.name;
+ });
+ }
+
+ // 转换数据格式,添加计算字段
+ const transformedRows = rows.map(cattle => {
+ // 计算月龄(基于出生日期)
+ let ageInMonths = 0;
+ if (cattle.birthday) {
+ const birthDate = new Date(cattle.birthday * 1000);
+ const now = new Date();
+ ageInMonths = Math.floor((now - birthDate) / (1000 * 60 * 60 * 24 * 30));
+ }
+
+ // 性别转换
+ const genderMap = { 1: '公', 2: '母', 0: '未知' };
+ const gender = genderMap[cattle.sex] || '未知';
+
+ // 品种转换(动态查询)
+ const breed = typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`;
+
+ // 生理阶段判断
+ let physiologicalStage = '未知';
+ if (ageInMonths < 6) {
+ physiologicalStage = '犊牛';
+ } else if (ageInMonths < 12) {
+ physiologicalStage = '育成牛';
+ } else if (ageInMonths < 24) {
+ physiologicalStage = '青年牛';
+ } else if (cattle.sex === 2) {
+ if (cattle.parity > 0) {
+ physiologicalStage = '泌乳牛';
+ } else {
+ physiologicalStage = '后备母牛';
+ }
+ } else {
+ physiologicalStage = '种公牛';
+ }
+
+ return {
+ id: cattle.id,
+ earTag: cattle.earNumber,
+ breed: breed,
+ gender: gender,
+ ageInMonths: ageInMonths,
+ physiologicalStage: physiologicalStage,
+ pen: cattle.penId ? (penNames[cattle.penId] || `栏舍ID:${cattle.penId}`) : '未分配栏舍',
+ farm: cattle.farm
+ };
+ });
+
+ console.log('🔄 转换后的数据示例:', transformedRows.slice(0, 2));
+
res.json({
success: true,
data: {
- list: rows,
+ list: transformedRows,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
diff --git a/backend/controllers/cattlePenController.js b/backend/controllers/cattlePenController.js
index 6556cd1..713ab62 100644
--- a/backend/controllers/cattlePenController.js
+++ b/backend/controllers/cattlePenController.js
@@ -1,4 +1,4 @@
-const { CattlePen, Farm, IotCattle } = require('../models');
+const { CattlePen, Farm, IotCattle, CattleType, CattleUser } = require('../models');
const { Op } = require('sequelize');
/**
@@ -378,7 +378,16 @@ class CattlePenController {
// 获取栏舍中的牛只
const { count, rows } = await IotCattle.findAndCountAll({
where: { penId: id },
- attributes: ['id', 'earNumber', 'sex', 'strain', 'orgId'],
+ attributes: [
+ 'id',
+ 'earNumber',
+ 'sex',
+ 'strain',
+ 'varieties',
+ 'birthday',
+ 'parity',
+ 'orgId'
+ ],
include: [
{
model: Farm,
@@ -391,10 +400,82 @@ class CattlePenController {
order: [['earNumber', 'ASC']]
});
+ // 获取品种和品系映射数据
+ const typeIds = [...new Set(rows.map(cattle => cattle.varieties).filter(id => id))];
+ const strainIds = [...new Set(rows.map(cattle => cattle.strain).filter(id => id))];
+
+ const typeNames = {};
+ if (typeIds.length > 0) {
+ const types = await CattleType.findAll({
+ where: { id: typeIds },
+ attributes: ['id', 'name']
+ });
+ types.forEach(type => {
+ typeNames[type.id] = type.name;
+ });
+ }
+
+ const userNames = {};
+ if (strainIds.length > 0) {
+ const users = await CattleUser.findAll({
+ where: { id: strainIds },
+ attributes: ['id', 'name']
+ });
+ users.forEach(user => {
+ userNames[user.id] = user.name;
+ });
+ }
+
+ // 转换数据格式,添加计算字段
+ const transformedRows = rows.map(cattle => {
+ // 计算月龄(基于出生日期)
+ let ageInMonths = 0;
+ if (cattle.birthday) {
+ const birthDate = new Date(cattle.birthday * 1000); // 假设birthday是Unix时间戳
+ const now = new Date();
+ ageInMonths = Math.floor((now - birthDate) / (1000 * 60 * 60 * 24 * 30));
+ }
+
+ // 性别转换
+ const genderMap = { 1: '公', 2: '母', 0: '未知' };
+ const gender = genderMap[cattle.sex] || '未知';
+
+ // 品种转换(动态查询)
+ const breed = typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`;
+
+ // 生理阶段判断(基于月龄和性别)
+ let physiologicalStage = '未知';
+ if (ageInMonths < 6) {
+ physiologicalStage = '犊牛';
+ } else if (ageInMonths < 12) {
+ physiologicalStage = '育成牛';
+ } else if (ageInMonths < 24) {
+ physiologicalStage = '青年牛';
+ } else if (cattle.sex === 2) { // 母牛
+ if (cattle.parity > 0) {
+ physiologicalStage = '泌乳牛';
+ } else {
+ physiologicalStage = '后备母牛';
+ }
+ } else { // 公牛
+ physiologicalStage = '种公牛';
+ }
+
+ return {
+ id: cattle.id,
+ earTag: cattle.earNumber, // 映射到前端期望的字段名
+ breed: breed,
+ gender: gender,
+ ageInMonths: ageInMonths,
+ physiologicalStage: physiologicalStage,
+ farm: cattle.farm
+ };
+ });
+
res.json({
success: true,
data: {
- list: rows,
+ list: transformedRows,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
diff --git a/backend/controllers/iotCattleController.js b/backend/controllers/iotCattleController.js
index 457ff69..7d456a4 100644
--- a/backend/controllers/iotCattleController.js
+++ b/backend/controllers/iotCattleController.js
@@ -24,15 +24,15 @@ const calculateAgeInMonths = (birthday) => {
};
/**
- * 类别中文映射
+ * 类别中文映射(与前端保持一致)
*/
const getCategoryName = (cate) => {
const categoryMap = {
1: '犊牛',
- 2: '繁殖牛',
- 3: '基础母牛',
- 4: '隔离牛',
- 5: '治疗牛',
+ 2: '育成母牛',
+ 3: '架子牛',
+ 4: '青年牛',
+ 5: '基础母牛',
6: '育肥牛'
};
return categoryMap[cate] || '未知';
@@ -128,10 +128,20 @@ class IotCattleController {
// 搜索条件
if (search) {
- whereConditions[Op.or] = [
- { earNumber: { [Op.like]: `%${search}%` } },
- { strain: { [Op.like]: `%${search}%` } }
- ];
+ // 尝试将搜索词转换为数字,如果成功则按数字搜索,否则按字符串搜索
+ const searchNumber = parseInt(search);
+ if (!isNaN(searchNumber)) {
+ // 数字搜索:精确匹配耳号
+ whereConditions[Op.or] = [
+ { earNumber: searchNumber },
+ { strain: { [Op.like]: `%${search}%` } }
+ ];
+ } else {
+ // 字符串搜索:模糊匹配
+ whereConditions[Op.or] = [
+ { strain: { [Op.like]: `%${search}%` } }
+ ];
+ }
console.log('=== 搜索条件构建 ===');
console.log('搜索关键词:', search);
console.log('搜索条件对象:', JSON.stringify(whereConditions, null, 2));
@@ -159,16 +169,13 @@ class IotCattleController {
console.log('完整查询条件:', JSON.stringify(whereConditions, null, 2));
console.log('分页参数:', { offset, limit: pageSize });
- // 先获取总数(严格查询未删除的记录)
+ // 先获取总数
console.log('=== 开始数据库查询 ===');
console.log('查询时间:', new Date().toISOString());
const countStartTime = Date.now();
const totalCount = await IotCattle.count({
- where: {
- ...whereConditions,
- isDelete: 0 // 确保只统计未删除的记录
- }
+ where: whereConditions
});
const countEndTime = Date.now();
@@ -176,13 +183,10 @@ class IotCattleController {
console.log('查询耗时:', countEndTime - countStartTime, 'ms');
console.log('总记录数:', totalCount);
- // 获取分页数据(严格查询未删除的记录)
+ // 获取分页数据
const dataStartTime = Date.now();
const rows = await IotCattle.findAll({
- where: {
- ...whereConditions,
- isDelete: 0 // 确保只查询未删除的记录
- },
+ where: whereConditions,
attributes: [
'id', 'earNumber', 'sex', 'strain', 'varieties', 'cate',
'birthWeight', 'birthday', 'penId', 'batchId', 'orgId',
diff --git a/backend/fix-network-access.js b/backend/fix-network-access.js
deleted file mode 100644
index 7d653a5..0000000
--- a/backend/fix-network-access.js
+++ /dev/null
@@ -1,122 +0,0 @@
-/**
- * 网络访问问题修复脚本
- * 解决外部用户无法访问开发服务器的问题
- */
-
-const os = require('os');
-const net = require('net');
-
-console.log('🔧 开始修复网络访问问题...\n');
-
-// 获取网络接口信息
-function getNetworkInfo() {
- const interfaces = os.networkInterfaces();
- const results = [];
-
- for (const name of Object.keys(interfaces)) {
- for (const iface of interfaces[name]) {
- if (iface.family === 'IPv4' && !iface.internal) {
- results.push({
- name: name,
- address: iface.address,
- netmask: iface.netmask,
- mac: iface.mac
- });
- }
- }
- }
-
- return results;
-}
-
-// 检查端口是否可以监听
-function checkPortAccess(port) {
- return new Promise((resolve) => {
- const server = net.createServer();
-
- server.listen(port, '0.0.0.0', () => {
- console.log(`✅ 端口 ${port} 可以监听所有网络接口`);
- server.close();
- resolve(true);
- });
-
- server.on('error', (err) => {
- if (err.code === 'EADDRINUSE') {
- console.log(`⚠️ 端口 ${port} 已被占用`);
- resolve(false);
- } else {
- console.log(`❌ 端口 ${port} 监听失败: ${err.message}`);
- resolve(false);
- }
- });
- });
-}
-
-// 生成防火墙配置命令
-function generateFirewallCommands() {
- const commands = [
- '# Windows防火墙配置命令(以管理员身份运行PowerShell)',
- '',
- '# 允许Node.js通过防火墙',
- 'netsh advfirewall firewall add rule name="Node.js Frontend" dir=in action=allow protocol=TCP localport=5300',
- 'netsh advfirewall firewall add rule name="Node.js Backend" dir=in action=allow protocol=TCP localport=5350',
- '',
- '# 或者允许所有Node.js程序',
- 'netsh advfirewall firewall add rule name="Node.js" dir=in action=allow program="C:\\Program Files\\nodejs\\node.exe" enable=yes',
- '',
- '# 检查现有规则',
- 'netsh advfirewall firewall show rule name="Node.js Frontend"',
- 'netsh advfirewall firewall show rule name="Node.js Backend"'
- ];
-
- return commands.join('\n');
-}
-
-// 主函数
-async function runDiagnostic() {
- console.log('🔍 网络诊断开始...\n');
-
- // 获取网络接口信息
- const networkInfo = getNetworkInfo();
- console.log('📡 可用的网络接口:');
- if (networkInfo.length === 0) {
- console.log(' ❌ 没有找到可用的网络接口');
- return;
- }
-
- networkInfo.forEach(iface => {
- console.log(` - ${iface.name}: ${iface.address}`);
- });
-
- // 检查端口访问
- console.log('\n🔌 检查端口访问:');
- const frontendPortOk = await checkPortAccess(5300);
- const backendPortOk = await checkPortAccess(5350);
-
- // 提供访问建议
- console.log('\n💡 访问建议:');
- networkInfo.forEach(iface => {
- console.log(` 前端: http://${iface.address}:5300`);
- console.log(` 后端: http://${iface.address}:5350`);
- });
-
- // 生成防火墙配置
- console.log('\n🔧 防火墙配置命令:');
- console.log(generateFirewallCommands());
-
- // 提供解决方案
- console.log('\n📋 解决步骤:');
- console.log('1. 重启后端服务器: npm start');
- console.log('2. 以管理员身份运行PowerShell,执行上述防火墙命令');
- console.log('3. 让其他用户访问您的IP地址(不是localhost)');
- console.log('4. 确保其他用户和您在同一个局域网内');
-
- if (!frontendPortOk || !backendPortOk) {
- console.log('\n⚠️ 端口被占用,请先停止其他服务');
- }
-
- console.log('\n🎉 诊断完成!');
-}
-
-// 运行诊断
-runDiagnostic().catch(console.error);
diff --git a/backend/routes/animals.js b/backend/routes/animals.js
index b59eddf..8384380 100644
--- a/backend/routes/animals.js
+++ b/backend/routes/animals.js
@@ -5,6 +5,7 @@ const express = require('express');
const router = express.Router();
const { Op } = require('sequelize');
const Animal = require('../models/Animal');
+const CattleType = require('../models/CattleType');
const { verifyToken, requirePermission } = require('../middleware/auth');
// 公开路由,不需要认证
@@ -112,17 +113,66 @@ router.get('/binding-info/:collarNumber', async (req, res) => {
});
}
+ // 类别映射函数(与前端保持一致)
+ const getCategoryName = (cate) => {
+ const categoryMap = {
+ 1: '犊牛',
+ 2: '育成母牛',
+ 3: '架子牛',
+ 4: '青年牛',
+ 5: '基础母牛',
+ 6: '育肥牛'
+ };
+ return categoryMap[cate] || '未知';
+ };
+
+ // 性别映射函数
+ const getSexName = (sex) => {
+ const sexMap = {
+ 1: '公牛',
+ 2: '母牛'
+ };
+ return sexMap[sex] || '未知';
+ };
+
+ // 来源类型映射函数
+ const getSourceTypeName = (source) => {
+ const sourceMap = {
+ 1: '合作社',
+ 2: '农户',
+ 3: '养殖场',
+ 4: '进口',
+ 5: '自繁'
+ };
+ return sourceMap[source] || '未知';
+ };
+
+ // 动态查询品种名称
+ const getBreedName = async (varieties) => {
+ if (!varieties) return '未知品种';
+
+ try {
+ const breed = await CattleType.findByPk(varieties, {
+ attributes: ['id', 'name']
+ });
+ return breed ? breed.name : '未知品种';
+ } catch (error) {
+ console.error('查询品种信息失败:', error);
+ return '未知品种';
+ }
+ };
+
// 格式化数据以匹配前端UI需求
const bindingInfo = {
// 基础信息
basicInfo: {
collarNumber: jbqDevice.cid,
- category: cattleInfo.cate || '奶牛',
+ category: getCategoryName(cattleInfo.cate),
calvingCount: cattleInfo.parity || 0,
earTag: cattleInfo.earNumber,
- animalType: cattleInfo.sex === 1 ? '公牛' : cattleInfo.sex === 2 ? '母牛' : '未知',
- breed: cattleInfo.varieties || '荷斯坦',
- sourceType: cattleInfo.source || '自繁'
+ animalType: getSexName(cattleInfo.sex),
+ breed: await getBreedName(cattleInfo.varieties), // 使用动态查询品种名称
+ sourceType: getSourceTypeName(cattleInfo.source)
},
// 出生信息
birthInfo: {
diff --git a/backend/routes/smart-devices.js b/backend/routes/smart-devices.js
index 78ab187..8e44724 100644
--- a/backend/routes/smart-devices.js
+++ b/backend/routes/smart-devices.js
@@ -25,8 +25,9 @@ publicRoutes.get('/eartags/export', async (req, res) => {
// 搜索条件
if (search) {
whereConditions[Op.or] = [
- { sn: { [Op.like]: `%${search}%` } },
- { deviceId: { [Op.like]: `%${search}%` } }
+ { aaid: { [Op.like]: `%${search}%` } },
+ { cid: { [Op.like]: `%${search}%` } },
+ { sid: { [Op.like]: `%${search}%` } }
];
}
@@ -128,8 +129,9 @@ publicRoutes.get('/eartags', async (req, res) => {
// 搜索条件
if (search) {
whereConditions[Op.or] = [
- { sn: { [Op.like]: `%${search}%` } },
- { deviceId: { [Op.like]: `%${search}%` } }
+ { aaid: { [Op.like]: `%${search}%` } },
+ { cid: { [Op.like]: `%${search}%` } },
+ { sid: { [Op.like]: `%${search}%` } }
];
}
diff --git a/backend/server.js b/backend/server.js
index 29f9840..7bb543b 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -159,9 +159,6 @@ app.use('/api/orders', require('./routes/orders'));
// 农场相关路由
app.use('/api/farms', require('./routes/farms'));
-// 养殖场相关路由
-app.use('/api/farms', require('./routes/farms'));
-
// 动物相关路由
app.use('/api/animals', require('./routes/animals'));
diff --git a/backend/高级软件开发工程师提示词(前端vue-后端springboot-nodejs).md b/backend/高级软件开发工程师提示词(前端vue-后端springboot-nodejs).md
index bdebc98..c059294 100644
--- a/backend/高级软件开发工程师提示词(前端vue-后端springboot-nodejs).md
+++ b/backend/高级软件开发工程师提示词(前端vue-后端springboot-nodejs).md
@@ -58,72 +58,173 @@
## 开发最佳实践
### 前端开发实践
-1. **组件化开发**: 遵循原子设计理念,将UI拆分为可复用的组件,定义清晰的组件接口和Props/Emits规范
+1. **组件化开发**: 遵循原子设计理念,将UI拆分为可复用的组件,定义清晰的组件接口和Props/Emits规范,所有数据通过API接口动态获取
```vue
-
+
-
+ {{ buttonText }}
```
-2. **状态管理最佳实践**: 使用Pinia管理全局状态,遵循单一数据源原则,避免状态冗余,实现状态持久化和模块化管理
+2. **状态管理最佳实践**: 使用Pinia管理全局状态,所有状态数据通过API接口动态获取,避免硬编码
```typescript
- // Pinia Store示例
+ // 动态数据Pinia Store示例
import { defineStore } from 'pinia'
+ import { fetchUserInfo, login, logout } from '@/api/auth'
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null as UserInfo | null,
- token: localStorage.getItem('token') || ''
+ token: localStorage.getItem('token') || '',
+ permissions: [] as string[],
+ menus: [] as MenuItem[]
}),
getters: {
- isLoggedIn: (state) => !!state.token
+ isLoggedIn: (state) => !!state.token,
+ hasPermission: (state) => (permission: string) =>
+ state.permissions.includes(permission)
},
actions: {
- setToken(token: string) {
- this.token = token
- localStorage.setItem('token', token)
+ async login(credentials: LoginCredentials) {
+ const response = await login(credentials)
+ if (response.success) {
+ this.token = response.data.token
+ localStorage.setItem('token', this.token)
+ await this.loadUserData()
+ }
+ return response
},
- setUserInfo(userInfo: UserInfo) {
- this.userInfo = userInfo
+ async loadUserData() {
+ try {
+ const [userResponse, permResponse, menuResponse] = await Promise.all([
+ fetchUserInfo(),
+ fetchPermissions(),
+ fetchMenus()
+ ])
+
+ if (userResponse.success) this.userInfo = userResponse.data
+ if (permResponse.success) this.permissions = permResponse.data
+ if (menuResponse.success) this.menus = menuResponse.data
+ } catch (error) {
+ console.error('加载用户数据失败:', error)
+ }
},
- logout() {
+ async logout() {
+ await logout()
this.token = ''
this.userInfo = null
+ this.permissions = []
+ this.menus = []
localStorage.removeItem('token')
}
}
})
```
-3. **代码规范**: 严格遵循ESLint/Prettier规范,使用TypeScript进行类型定义,提高代码可维护性,配置EditorConfig保持团队代码风格一致
+3. **统一API调用规范**: 使用fetch方法进行所有API调用,统一错误处理和响应格式
+ ```typescript
+ // API工具函数
+ const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api'
+
+ interface ApiResponse {
+ code: number
+ message: string
+ data: T
+ success: boolean
+ timestamp: string
+ }
+
+ async function fetchApi(
+ url: string,
+ options: RequestInit = {}
+ ): Promise> {
+ const defaultOptions: RequestInit = {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
+ },
+ ...options
+ }
+
+ try {
+ const response = await fetch(`${API_BASE_URL}${url}`, defaultOptions)
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ const data: ApiResponse = await response.json()
+
+ if (!data.success) {
+ throw new Error(data.message || 'API请求失败')
+ }
+
+ return data
+ } catch (error) {
+ console.error('API调用失败:', error)
+ throw error
+ }
+ }
+
+ // 具体API调用示例
+ export const fetchUsers = (params?: any) =>
+ fetchApi('/v1/users', {
+ method: 'GET',
+ params
+ })
+
+ export const createUser = (userData: UserCreateDto) =>
+ fetchApi('/v1/users', {
+ method: 'POST',
+ body: JSON.stringify(userData)
+ })
+ ```
4. **性能优化技巧**:
- 使用虚拟滚动(如vue-virtual-scroller)处理大数据渲染,减少DOM节点数量
@@ -134,11 +235,11 @@
- 避免频繁DOM操作,使用虚拟DOM diff算法优势
### 后端开发实践
-1. **分层架构实现**: 严格遵循Controller-Service-Repository分层结构,职责清晰,实现关注点分离
+1. **分层架构实现**: 严格遵循Controller-Service-Repository分层结构,所有数据从数据库动态获取,避免硬编码
```java
- // Spring Boot分层示例
+ // Spring Boot动态数据分层示例
@RestController
- @RequestMapping("/api/users")
+ @RequestMapping("/api/v1/users")
public class UserController {
private final UserService userService;
@@ -148,45 +249,113 @@
}
@GetMapping("/{id}")
- public ResponseEntity getUserById(@PathVariable Long id) {
- return ResponseEntity.ok(userService.getUserById(id));
+ public ResponseEntity> getUserById(@PathVariable Long id) {
+ UserDTO user = userService.getUserById(id);
+ return ResponseEntity.ok(ApiResponse.success(user));
+ }
+
+ @GetMapping
+ public ResponseEntity>> getUsers(
+ @RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "10") int size,
+ @RequestParam(required = false) String keyword) {
+ PageResult users = userService.getUsers(page, size, keyword);
+ return ResponseEntity.ok(ApiResponse.success(users));
}
}
@Service
+ @Transactional
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
+ private final RoleRepository roleRepository;
private final PasswordEncoder passwordEncoder;
- public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder) {
+ public UserServiceImpl(UserRepository userRepository,
+ RoleRepository roleRepository,
+ PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
+ this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
}
@Override
public UserDTO getUserById(Long id) {
User user = userRepository.findById(id)
- .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
- return convertToDTO(user);
+ .orElseThrow(() -> new ResourceNotFoundException("用户不存在,ID: " + id));
+
+ // 动态获取用户角色信息
+ List roles = roleRepository.findByUserId(id);
+ return UserMapper.INSTANCE.toDTO(user, roles);
}
- private UserDTO convertToDTO(User user) {
- // 转换逻辑
+ @Override
+ public PageResult getUsers(int page, int size, String keyword) {
+ Pageable pageable = PageRequest.of(page - 1, size, Sort.by("createTime").descending());
+ Page userPage;
+
+ if (StringUtils.hasText(keyword)) {
+ userPage = userRepository.findByKeyword(keyword, pageable);
+ } else {
+ userPage = userRepository.findAll(pageable);
+ }
+
+ // 批量获取用户角色信息
+ List userIds = userPage.getContent().stream()
+ .map(User::getId)
+ .collect(Collectors.toList());
+ Map> userRolesMap = roleRepository.findByUserIds(userIds);
+
+ List userDTOs = userPage.getContent().stream()
+ .map(user -> UserMapper.INSTANCE.toDTO(user, userRolesMap.get(user.getId())))
+ .collect(Collectors.toList());
+
+ return new PageResult<>(userDTOs, userPage.getTotalElements());
}
}
```
```javascript
- // Node.js (Express)分层示例
+ // Node.js (Express)动态数据分层示例
const express = require('express');
const router = express.Router();
const userService = require('../services/user.service');
+ const { validateRequest } = require('../middleware/validation');
+ const { userQuerySchema } = require('../schemas/user.schema');
- // GET /api/users/:id
+ // GET /api/v1/users/:id
router.get('/:id', async (req, res, next) => {
try {
const user = await userService.getUserById(req.params.id);
- res.json(user);
+ res.json({
+ code: 200,
+ message: 'Success',
+ data: user,
+ success: true,
+ timestamp: new Date().toISOString()
+ });
+ } catch (error) {
+ next(error);
+ }
+ });
+
+ // GET /api/v1/users
+ router.get('/', validateRequest(userQuerySchema), async (req, res, next) => {
+ try {
+ const { page = 1, pageSize = 10, keyword } = req.query;
+ const users = await userService.getUsers({
+ page: parseInt(page),
+ pageSize: parseInt(pageSize),
+ keyword
+ });
+
+ res.json({
+ code: 200,
+ message: 'Success',
+ data: users,
+ success: true,
+ timestamp: new Date().toISOString()
+ });
} catch (error) {
next(error);
}
@@ -194,43 +363,411 @@
module.exports = router;
- // user.service.js
+ // user.service.js - 动态数据服务层
const userRepository = require('../repositories/user.repository');
+ const roleRepository = require('../repositories/role.repository');
async function getUserById(id) {
const user = await userRepository.findById(id);
if (!user) {
- throw new Error(`User not found with id: ${id}`);
+ throw new Error(`用户不存在,ID: ${id}`);
}
- return mapUserToDTO(user);
+
+ // 动态获取用户角色信息
+ const roles = await roleRepository.findByUserId(id);
+ return mapUserToDTO(user, roles);
}
- function mapUserToDTO(user) {
- // 转换逻辑
+ async function getUsers({ page, pageSize, keyword }) {
+ const offset = (page - 1) * pageSize;
+ let users, total;
+
+ if (keyword) {
+ [users, total] = await Promise.all([
+ userRepository.findByKeyword(keyword, pageSize, offset),
+ userRepository.countByKeyword(keyword)
+ ]);
+ } else {
+ [users, total] = await Promise.all([
+ userRepository.findAll(pageSize, offset),
+ userRepository.count()
+ ]);
+ }
+
+ // 批量获取用户角色信息
+ const userIds = users.map(user => user.id);
+ const userRolesMap = await roleRepository.findByUserIds(userIds);
+
+ const userDTOs = users.map(user =>
+ mapUserToDTO(user, userRolesMap[user.id] || [])
+ );
+
+ return {
+ items: userDTOs,
+ total,
+ page,
+ pageSize,
+ totalPages: Math.ceil(total / pageSize)
+ };
+ }
+
+ function mapUserToDTO(user, roles) {
+ return {
+ id: user.id,
+ username: user.username,
+ email: user.email,
+ avatar: user.avatar,
+ status: user.status,
+ roles: roles.map(role => ({
+ id: role.id,
+ name: role.name,
+ code: role.code
+ })),
+ createTime: user.createTime,
+ updateTime: user.updateTime
+ };
}
```
-2. **数据库优化**: 合理设计索引,优化SQL查询(避免SELECT *、使用JOIN替代子查询),使用连接池管理数据库连接,实现读写分离
-3. **异常处理**: 统一异常处理机制,定义清晰的错误码和错误信息,使用全局异常处理器捕获和处理异常
-4. **接口设计**: RESTful风格API,版本化管理(如/api/v1/users),参数校验(使用JSR-380/express-validator),返回统一的数据结构
- ```json
- // 统一响应格式
- {
- "code": 200,
- "message": "Success",
- "data": { /* 具体数据 */ },
- "timestamp": "2023-07-01T12:00:00Z"
+2. **统一响应格式**: 所有API返回统一格式的响应,包含状态码、消息、数据和时间戳
+ ```typescript
+ // 统一响应格式接口定义
+ interface ApiResponse {
+ code: number; // 状态码
+ message: string; // 消息
+ data: T; // 数据
+ success: boolean; // 是否成功
+ timestamp: string; // 时间戳
+ }
+
+ // 分页响应格式
+ interface PaginatedResponse {
+ items: T[];
+ total: number;
+ page: number;
+ pageSize: number;
+ totalPages: number;
+ }
+
+ // 成功响应工具函数
+ function successResponse(data: T, message: string = 'Success'): ApiResponse {
+ return {
+ code: 200,
+ message,
+ data,
+ success: true,
+ timestamp: new Date().toISOString()
+ };
+ }
+
+ // 错误响应工具函数
+ function errorResponse(code: number, message: string): ApiResponse {
+ return {
+ code,
+ message,
+ data: null,
+ success: false,
+ timestamp: new Date().toISOString()
+ };
}
```
+
+3. **数据库优化**: 合理设计索引,优化SQL查询(避免SELECT *、使用JOIN替代子查询),使用连接池管理数据库连接,实现读写分离
+4. **异常处理**: 统一异常处理机制,定义清晰的错误码和错误信息,使用全局异常处理器捕获和处理异常
5. **安全编码**: 防止SQL注入(使用参数化查询)、XSS攻击(输入过滤、输出编码)、CSRF攻击(使用CSRF令牌)等安全问题,敏感数据(如密码)加密存储
+### 统一API调用规范
+1. **前端API调用标准**: 使用统一的fetch封装,避免硬编码URL和静态数据
+ ```typescript
+ // api-client.ts - 统一API客户端
+ const BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
+
+ export interface ApiResponse {
+ code: number;
+ message: string;
+ data: T;
+ success: boolean;
+ timestamp: string;
+ }
+
+ export interface PaginatedResponse {
+ items: T[];
+ total: number;
+ page: number;
+ pageSize: number;
+ totalPages: number;
+ }
+
+ class ApiClient {
+ private async request(
+ endpoint: string,
+ options: RequestInit = {}
+ ): Promise> {
+ const url = `${BASE_URL}${endpoint}`;
+ const config: RequestInit = {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers,
+ },
+ credentials: 'include',
+ ...options,
+ };
+
+ try {
+ const response = await fetch(url, config);
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const data: ApiResponse = await response.json();
+
+ if (!data.success) {
+ throw new Error(data.message || 'API request failed');
+ }
+
+ return data;
+ } catch (error) {
+ console.error('API request failed:', error);
+ throw error;
+ }
+ }
+
+ async get(endpoint: string, params?: Record): Promise> {
+ const queryString = params ? new URLSearchParams(params).toString() : '';
+ const url = queryString ? `${endpoint}?${queryString}` : endpoint;
+ return this.request(url, { method: 'GET' });
+ }
+
+ async post(endpoint: string, data?: any): Promise> {
+ return this.request(endpoint, {
+ method: 'POST',
+ body: data ? JSON.stringify(data) : undefined,
+ });
+ }
+
+ async put(endpoint: string, data?: any): Promise> {
+ return this.request(endpoint, {
+ method: 'PUT',
+ body: data ? JSON.stringify(data) : undefined,
+ });
+ }
+
+ async delete(endpoint: string): Promise> {
+ return this.request(endpoint, { method: 'DELETE' });
+ }
+ }
+
+ export const apiClient = new ApiClient();
+
+ // 用户服务API
+ export const userApi = {
+ // 获取用户列表
+ getUsers: (params: {
+ page?: number;
+ pageSize?: number;
+ keyword?: string;
+ }) => apiClient.get>('/v1/users', params),
+
+ // 获取用户详情
+ getUserById: (id: number) => apiClient.get(`/v1/users/${id}`),
+
+ // 创建用户
+ createUser: (userData: CreateUserRequest) =>
+ apiClient.post('/v1/users', userData),
+
+ // 更新用户
+ updateUser: (id: number, userData: UpdateUserRequest) =>
+ apiClient.put(`/v1/users/${id}`, userData),
+
+ // 删除用户
+ deleteUser: (id: number) => apiClient.delete(`/v1/users/${id}`),
+ };
+
+ // 在Vue组件中使用
+ import { userApi } from '@/services/api';
+
+ const loadUsers = async () => {
+ try {
+ const response = await userApi.getUsers({
+ page: currentPage.value,
+ pageSize: pageSize.value,
+ keyword: searchKeyword.value
+ });
+
+ if (response.success) {
+ users.value = response.data.items;
+ total.value = response.data.total;
+ }
+ } catch (error) {
+ console.error('Failed to load users:', error);
+ message.error('加载用户列表失败');
+ }
+ };
+ ```
+
+2. **后端统一响应格式**: 所有接口返回标准化的响应结构
+ ```java
+ // Spring Boot统一响应格式
+ @Data
+ @AllArgsConstructor
+ @NoArgsConstructor
+ public class ApiResponse {
+ private int code;
+ private String message;
+ private T data;
+ private boolean success;
+ private String timestamp;
+
+ public static ApiResponse success(T data) {
+ return new ApiResponse<>(200, "Success", data, true, LocalDateTime.now().toString());
+ }
+
+ public static ApiResponse success(T data, String message) {
+ return new ApiResponse<>(200, message, data, true, LocalDateTime.now().toString());
+ }
+
+ public static ApiResponse> error(int code, String message) {
+ return new ApiResponse<>(code, message, null, false, LocalDateTime.now().toString());
+ }
+
+ public static ApiResponse> error(String message) {
+ return new ApiResponse<>(500, message, null, false, LocalDateTime.now().toString());
+ }
+ }
+
+ // 分页响应格式
+ @Data
+ @AllArgsConstructor
+ public class PageResult {
+ private List items;
+ private long total;
+ private int page;
+ private int pageSize;
+ private int totalPages;
+
+ public PageResult(List items, long total) {
+ this.items = items;
+ this.total = total;
+ this.page = 1;
+ this.pageSize = items.size();
+ this.totalPages = (int) Math.ceil((double) total / pageSize);
+ }
+
+ public PageResult(List items, long total, int page, int pageSize) {
+ this.items = items;
+ this.total = total;
+ this.page = page;
+ this.pageSize = pageSize;
+ this.totalPages = (int) Math.ceil((double) total / pageSize);
+ }
+ }
+ ```
+
+ ```javascript
+ // Node.js统一响应中间件
+ function apiResponseMiddleware(req, res, next) {
+ res.apiSuccess = function(data, message = 'Success') {
+ this.json({
+ code: 200,
+ message,
+ data,
+ success: true,
+ timestamp: new Date().toISOString()
+ });
+ };
+
+ res.apiError = function(code, message, data = null) {
+ this.status(code).json({
+ code,
+ message,
+ data,
+ success: false,
+ timestamp: new Date().toISOString()
+ });
+ };
+
+ next();
+ }
+
+ // 在控制器中使用
+ router.get('/v1/users/:id', async (req, res) => {
+ try {
+ const user = await userService.getUserById(req.params.id);
+ res.apiSuccess(user);
+ } catch (error) {
+ if (error.message.includes('不存在')) {
+ res.apiError(404, error.message);
+ } else {
+ res.apiError(500, '服务器内部错误');
+ }
+ }
+ });
+ ```
+
+3. **数据验证和错误处理**: 统一的参数验证和异常处理机制
+ ```typescript
+ // 前端数据验证
+ interface CreateUserRequest {
+ username: string;
+ email: string;
+ password: string;
+ roleIds: number[];
+ }
+
+ const validateUserData = (data: CreateUserRequest): string[] => {
+ const errors: string[] = [];
+
+ if (!data.username || data.username.length < 3) {
+ errors.push('用户名至少3个字符');
+ }
+
+ if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
+ errors.push('请输入有效的邮箱地址');
+ }
+
+ if (!data.password || data.password.length < 6) {
+ errors.push('密码至少6个字符');
+ }
+
+ if (!data.roleIds || data.roleIds.length === 0) {
+ errors.push('请选择至少一个角色');
+ }
+
+ return errors;
+ };
+
+ // 在组件中使用验证
+ const handleCreateUser = async () => {
+ const errors = validateUserData(formData);
+ if (errors.length > 0) {
+ message.error(errors.join(','));
+ return;
+ }
+
+ try {
+ const response = await userApi.createUser(formData);
+ if (response.success) {
+ message.success('用户创建成功');
+ loadUsers(); // 重新加载数据
+ }
+ } catch (error) {
+ message.error('创建用户失败');
+ }
+ };
+ ```
+
### 通用开发原则
-1. **单一职责原则**: 每个函数、类只负责一个明确的功能,提高代码可维护性
-2. **DRY原则(Don't Repeat Yourself)**: 避免代码重复,提高代码复用性,抽取公共函数和组件
-3. **KISS原则(Keep It Simple, Stupid)**: 保持代码简洁明了,避免过度设计,优先选择简单的解决方案
-4. **YAGNI原则(You Aren't Gonna Need It)**: 只实现当前需要的功能,避免过度设计和功能膨胀
-5. **代码注释**: 为复杂逻辑和关键算法添加清晰的注释,提高代码可读性,遵循JSDoc/Javadoc规范
-6. **SOLID原则**: 遵循单一职责、开闭原则、里氏替换、接口隔离、依赖倒置等设计原则
+1. **SOLID原则**: 单一职责、开闭原则、里氏替换、接口隔离、依赖倒置
+2. **DRY原则**: 避免重复代码,提取公共方法和组件
+3. **KISS原则**: 保持简单和直接,避免过度设计
+4. **YAGNI原则**: 不要实现你不需要的功能,避免过度工程化
+5. **代码复用**: 创建可复用的组件、工具函数和库
+6. **渐进式增强**: 从基础功能开始,逐步添加高级功能
+7. **防御性编程**: 对输入进行验证,处理异常情况
+8. **性能意识**: 关注代码性能,避免不必要的计算和内存消耗
+9. **可测试性**: 编写可测试的代码,使用依赖注入和接口
+10. **文档化**: 编写清晰的注释和文档,便于维护和协作
## 协作与沟通
diff --git a/backend/高级软件开发工程师提示词-精简版.md b/backend/高级软件开发工程师提示词-精简版.md
new file mode 100644
index 0000000..ce4f845
--- /dev/null
+++ b/backend/高级软件开发工程师提示词-精简版.md
@@ -0,0 +1,205 @@
+# 高级软件开发工程师提示词(Vue + SpringBoot/Node.js)
+
+## 角色定义
+高级全栈开发工程师,精通Vue前端和SpringBoot/Node.js后端技术栈,负责企业级应用的设计、开发和维护。
+
+## 核心技术栈
+- **前端**: Vue 3 + TypeScript + Pinia + Vite + Ant Design Vue
+- **后端**: Spring Boot 3.x / Node.js + Express/NestJS
+- **数据库**: MySQL/PostgreSQL + Redis缓存
+- **部署**: Docker + Kubernetes + CI/CD流水线
+
+## 开发工作流程
+1. **需求分析**: 理解业务需求,参与技术方案设计
+2. **架构设计**: 设计系统架构、数据库模型和API接口
+3. **编码实现**: 遵循编码规范,实现高质量代码
+4. **测试调试**: 单元测试、集成测试和问题排查
+5. **部署维护**: 自动化部署和线上监控
+
+## 开发最佳实践
+
+### 前端开发
+**组件化开发**: 创建可复用组件,通过API动态获取数据
+```vue
+
+
+ {{ buttonConfig.text }}
+
+
+
+
+```
+
+**状态管理**: 使用Pinia管理应用状态,异步加载数据
+```typescript
+// stores/userStore.ts
+import { defineStore } from 'pinia'
+import { userApi } from '@/api'
+
+export const useUserStore = defineStore('user', {
+ state: () => ({
+ users: [],
+ loading: false
+ }),
+
+ actions: {
+ async loadUsers() {
+ this.loading = true
+ try {
+ const response = await userApi.getUsers()
+ this.users = response.data
+ } finally {
+ this.loading = false
+ }
+ }
+ }
+})
+```
+
+### 后端开发
+**分层架构**: Controller-Service-Repository模式,动态数据获取
+```java
+// UserController.java
+@RestController
+@RequestMapping("/api/v1/users")
+public class UserController {
+
+ @Autowired
+ private UserService userService;
+
+ @GetMapping
+ public ApiResponse> getUsers(
+ @RequestParam(defaultValue = "1") int page,
+ @RequestParam(defaultValue = "10") int size) {
+ Page userPage = userService.getUsers(page, size);
+ return ApiResponse.success(convertToPageResult(userPage));
+ }
+
+ private PageResult convertToPageResult(Page userPage) {
+ List dtos = userPage.getContent().stream()
+ .map(this::convertToDTO)
+ .collect(Collectors.toList());
+ return new PageResult<>(dtos, userPage.getTotalElements(), page, size);
+ }
+}
+```
+
+**统一响应格式**: 标准化API响应
+```java
+@Data
+public class ApiResponse {
+ private int code;
+ private String message;
+ private T data;
+ private boolean success;
+
+ public static ApiResponse success(T data) {
+ return new ApiResponse<>(200, "Success", data, true);
+ }
+}
+```
+
+## 统一API调用规范
+
+### 前端API客户端
+```typescript
+// api/client.ts
+class ApiClient {
+ private baseURL: string
+
+ constructor(baseURL: string) {
+ this.baseURL = baseURL
+ }
+
+ async get(endpoint: string): Promise> {
+ const response = await fetch(`${this.baseURL}${endpoint}`)
+ return response.json()
+ }
+
+ async post(endpoint: string, data: any): Promise> {
+ const response = await fetch(`${this.baseURL}${endpoint}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(data)
+ })
+ return response.json()
+ }
+}
+
+// 用户服务API
+const userApi = {
+ getUsers: (params?: any) => apiClient.get('/users', { params }),
+ createUser: (userData: CreateUserRequest) =>
+ apiClient.post('/users', userData)
+}
+```
+
+### 后端响应中间件
+```javascript
+// Node.js响应中间件
+function apiResponseMiddleware(req, res, next) {
+ res.apiSuccess = function(data, message = 'Success') {
+ this.json({ code: 200, message, data, success: true })
+ }
+
+ res.apiError = function(code, message) {
+ this.status(code).json({ code, message, success: false })
+ }
+ next()
+}
+```
+
+## 数据验证
+```typescript
+// 前端数据验证
+const validateUserData = (data: CreateUserRequest): string[] => {
+ const errors: string[] = []
+
+ if (!data.username || data.username.length < 3) {
+ errors.push('用户名至少3个字符')
+ }
+
+ if (!data.email.includes('@')) {
+ errors.push('邮箱格式不正确')
+ }
+
+ return errors
+}
+```
+
+## 通用开发原则
+1. **SOLID原则**: 单一职责、开闭原则等
+2. **DRY原则**: 避免重复代码
+3. **KISS原则**: 保持简单直接
+4. **防御性编程**: 输入验证和异常处理
+5. **性能意识**: 关注代码性能
+6. **可测试性**: 编写可测试代码
+7. **文档化**: 清晰的注释和文档
+
+## 团队协作
+- **Git工作流**: 规范分支管理和提交信息
+- **代码审查**: 确保代码质量
+- **敏捷开发**: 参与迭代开发流程
+
+## 问题解决能力
+- 使用调试工具定位问题
+- 设计技术解决方案
+- 性能分析和优化
+- 线上问题应急处理
+
+## 持续学习
+- 跟踪技术发展趋势
+- 深入理解技术原理
+- 参与技术社区和分享
+
+---
+**使用指南**: 此提示词适用于Vue + SpringBoot/Node.js全栈开发,强调动态数据获取、统一接口规范和代码质量。
\ No newline at end of file
diff --git a/backend/高级软件系统架构师提示词(前端vue-后端springboot-nodejs).md b/backend/高级软件系统架构师提示词(前端vue-后端springboot-nodejs).md
index fadbf8a..1bda638 100644
--- a/backend/高级软件系统架构师提示词(前端vue-后端springboot-nodejs).md
+++ b/backend/高级软件系统架构师提示词(前端vue-后端springboot-nodejs).md
@@ -46,7 +46,17 @@
- **数据架构**: 数据库设计(范式化/反范式化)、缓存策略、数据同步机制
- **部署架构**: Docker容器化、Kubernetes编排、CI/CD流水线
-2. **关键模块设计**:
+2. **项目目录结构规范**:
+ - **前端项目**: `admin-system` - 管理系统前端项目
+ - **后端项目**: `backend` - 后端API服务和业务逻辑
+ - **大屏项目**: `datav` - 数据可视化大屏项目
+ - **官网项目**: `website` - 企业官网和产品展示
+ - **小程序项目**: `mini_program` - 微信小程序项目
+ - **文档目录**: `docs` - 需求文档、开发文档、计划文档等
+ - **脚本目录**: `scripts` - 数据库脚本、部署脚本等
+ - **测试目录**: `test` - 测试用例和测试脚本
+
+3. **关键模块设计**:
- **前端核心模块**: 路由设计、状态管理方案、组件库规划、API封装层
- **后端核心模块**: 业务服务设计、数据访问层、安全认证模块、异步任务处理
- **集成架构**: 第三方系统集成点、消息队列选型(Kafka/RabbitMQ)、事件驱动设计