优化项目bug

This commit is contained in:
xuqiuyun
2025-09-16 16:07:32 +08:00
parent 89bc6e8ce2
commit b67f22fd79
22 changed files with 1650 additions and 598 deletions

View File

@@ -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 ? '正常' : '无贷款'
}
};
};

View File

@@ -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 // 获取大量数据

View File

@@ -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: '品种',

View File

@@ -83,9 +83,6 @@
<a-button type="link" size="small" @click="handleViewCattle(record)">
查看牛只
</a-button>
<a-button type="link" size="small" @click="handleAddCattle(record)">
添加牛只
</a-button>
<a-button type="link" size="small" danger @click="handleDelete(record)">
删除
</a-button>
@@ -235,7 +232,15 @@
<a-table
:columns="cattleColumns"
:data-source="currentBatchCattle"
:pagination="{ pageSize: 10 }"
:pagination="{
current: cattlePagination.current,
pageSize: cattlePagination.pageSize,
total: cattlePagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
}"
@change="handleCattleTableChange"
size="small"
/>
</a-modal>
@@ -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 = []

View File

@@ -51,10 +51,7 @@
</a-space>
</template>
</a-input>
<!-- 搜索提示 -->
<!-- <div v-if="searchValue && searchValue.trim()" class="search-hint">
💡 按回车键或点击搜索图标进行搜索
</div> -->
</div>
</div>
@@ -68,6 +65,7 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
@select="(value, option) => handleFilterSelect(value, option, 'exitReason')"
>
<a-select-option value="出售">出售</a-select-option>
<a-select-option value="死亡">死亡</a-select-option>
@@ -83,19 +81,20 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
@select="(value, option) => handleFilterSelect(value, option, 'status')"
>
<a-select-option value="已确认">已确认</a-select-option>
<a-select-option value="待确认">待确认</a-select-option>
<a-select-option value="已取消">已取消</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<!-- <a-col :span="6">
<a-range-picker
v-model="filters.dateRange"
style="width: 100%"
@change="handleFilterChange"
/>
</a-col>
</a-col> -->
<a-col :span="6">
<a-button @click="handleResetFilters">重置筛选</a-button>
</a-col>
@@ -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()
}

View File

@@ -203,7 +203,15 @@
<a-table
:columns="animalColumns"
:data-source="currentPenAnimals"
:pagination="{ pageSize: 5 }"
:pagination="{
current: animalsPagination.current,
pageSize: animalsPagination.pageSize,
total: animalsPagination.total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条,共 ${total} 条`
}"
@change="handleAnimalsTableChange"
size="small"
/>
</a-modal>
@@ -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('=== 开始导出栏舍数据 ===')

View File

@@ -31,19 +31,24 @@
<!-- 搜索区域 -->
<div class="search-container">
<div class="search-input-group">
<a-input
v-model="searchValue"
placeholder="请输入耳号搜索"
class="search-input"
@pressEnter="handleSearch"
@input="handleInputChange"
@focus="handleSearchFocus"
@blur="handleSearchBlur"
>
<template #suffix>
<SearchOutlined @click="handleSearch" style="cursor: pointer;" />
</template>
</a-input>
<div style="position: relative; flex: 1;">
<input
ref="searchInputRef"
v-model="searchValue"
placeholder="请输入耳号搜索"
class="search-input"
@keyup.enter="handleSearch"
@input="handleInputChange"
@change="handleInputChange"
@focus="handleSearchFocus"
@blur="handleSearchBlur"
style="width: 100%; height: 40px; padding: 8px 40px 8px 12px; border: 1px solid #d9d9d9; border-radius: 6px; font-size: 14px; outline: none;"
/>
<SearchOutlined
@click="handleSearch"
style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); cursor: pointer; color: #999;"
/>
</div>
<a-button
class="search-reset-btn"
@click="handleResetSearch"
@@ -54,10 +59,7 @@
重置
</a-button>
</div>
<!-- 搜索提示 -->
<!-- <div v-if="searchValue && searchValue.trim()" class="search-hint">
💡 按回车键或点击搜索图标进行搜索
</div> -->
</div>
</div>
@@ -71,10 +73,15 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
@select="(value, option) => handlePenSelect(value, option, 'fromPen')"
>
<a-select-option value="杭嘉新村栏舍01">杭嘉新村栏舍01</a-select-option>
<a-select-option value="杭嘉新村栏舍02">杭嘉新村栏舍02</a-select-option>
<a-select-option value="杭嘉新村栏舍03">杭嘉新村栏舍03</a-select-option>
<a-select-option
v-for="pen in penList"
:key="pen.id"
:value="pen.name"
>
{{ pen.name }}
</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
@@ -84,19 +91,24 @@
allowClear
style="width: 100%"
@change="handleFilterChange"
@select="(value, option) => handlePenSelect(value, option, 'toPen')"
>
<a-select-option value="杭嘉新村栏舍01">杭嘉新村栏舍01</a-select-option>
<a-select-option value="杭嘉新村栏舍02">杭嘉新村栏舍02</a-select-option>
<a-select-option value="杭嘉新村栏舍03">杭嘉新村栏舍03</a-select-option>
<a-select-option
v-for="pen in penList"
:key="pen.id"
:value="pen.name"
>
{{ pen.name }}
</a-select-option>
</a-select>
</a-col>
<a-col :span="6">
<!-- <a-col :span="6">
<a-range-picker
v-model="filters.dateRange"
style="width: 100%"
@change="handleFilterChange"
/>
</a-col>
</a-col> -->
<a-col :span="6">
<a-button @click="handleResetFilters">重置筛选</a-button>
</a-col>
@@ -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(() => {

View File

@@ -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

View File

@@ -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('未找到')) {

View File

@@ -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 };

View File

@@ -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()');
}

View File

@@ -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);