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 - + ``` -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 + + + +``` + +**状态管理**: 使用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)、事件驱动设计