修改bug。新增牛只,超链接

This commit is contained in:
xuqiuyun
2025-09-15 18:18:41 +08:00
parent be32363412
commit 89bc6e8ce2
18 changed files with 2183 additions and 76 deletions

View File

@@ -129,32 +129,61 @@
<a-row :gutter="16">
<a-col :span="8">
<a-form-item label="品系" name="strain">
<a-input
<a-select
v-model:value="formData.strain"
placeholder="请输入品系"
@input="handleFieldChange('strain', $event.target.value)"
@change="handleFieldChange('strain', $event.target.value)"
/>
placeholder="请选择品系"
:loading="cattleUsersLoading"
@change="handleFieldChange('strain', $event)"
show-search
:filter-option="filterOption"
>
<a-select-option
v-for="user in cattleUsers"
:key="user.id"
:value="user.id"
>
{{ user.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="品种" name="varieties">
<a-input
<a-select
v-model:value="formData.varieties"
placeholder="请输入品种"
@input="handleFieldChange('varieties', $event.target.value)"
@change="handleFieldChange('varieties', $event.target.value)"
/>
placeholder="请选择品种"
:loading="cattleTypesLoading"
@change="handleFieldChange('varieties', $event)"
show-search
:filter-option="filterOption"
>
<a-select-option
v-for="type in cattleTypes"
:key="type.id"
:value="type.id"
>
{{ type.name }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="8">
<a-form-item label="类别" name="cate">
<a-input
<a-select
v-model:value="formData.cate"
placeholder="请输入类别"
@input="handleFieldChange('cate', $event.target.value)"
@change="handleFieldChange('cate', $event.target.value)"
/>
placeholder="请选择类别"
@change="handleFieldChange('cate', $event)"
show-search
:filter-option="filterCateOption"
>
<a-select-option
v-for="(name, value) in cateOptions"
:key="value"
:value="parseInt(value)"
>
{{ name }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
</a-row>
@@ -368,15 +397,29 @@ const animals = ref([])
const farms = ref([])
const pens = ref([])
const batches = ref([])
const cattleUsers = ref([]) // 品系数据cattle_user表
const cattleTypes = ref([]) // 品种数据cattle_type表
const loading = ref(false)
const farmsLoading = ref(false)
const pensLoading = ref(false)
const batchesLoading = ref(false)
const cattleUsersLoading = ref(false)
const cattleTypesLoading = ref(false)
const modalVisible = ref(false)
const submitLoading = ref(false)
const isEdit = ref(false)
const formRef = ref()
// 类别选项(预定义映射)
const cateOptions = ref({
1: '犊牛',
2: '育成母牛',
3: '架子牛',
4: '青年牛',
5: '基础母牛',
6: '育肥牛'
})
// 分页相关
const pagination = ref({
current: 1,
@@ -474,19 +517,38 @@ const columns = [
title: '品系',
dataIndex: 'strain', // 映射iot_cattle.strain显示用途名称
key: 'strain',
width: 120
width: 120,
customRender: ({ text }) => {
const user = cattleUsers.value.find(u => u.id === text)
return user ? user.name : text || '-'
}
},
{
title: '品种',
dataIndex: 'varieties', // 映射iot_cattle.varieties
key: 'varieties',
width: 120
width: 120,
customRender: ({ text }) => {
const type = cattleTypes.value.find(t => t.id === text)
return type ? type.name : text || '-'
}
},
{
title: '类别',
dataIndex: 'cate', // 映射iot_cattle.cate
key: 'cate',
width: 100
width: 100,
customRender: ({ text }) => {
const cateMap = {
1: '犊牛',
2: '育成母牛',
3: '架子牛',
4: '青年牛',
5: '基础母牛',
6: '育肥牛'
}
return cateMap[text] || text || '-'
}
},
{
title: '出生体重(kg)',
@@ -611,6 +673,38 @@ const fetchBatches = async (farmId = null) => {
}
}
// 获取品系列表cattle_user表
const fetchCattleUsers = async () => {
try {
cattleUsersLoading.value = true
const response = await api.get('/cattle-user')
if (response.success) {
cattleUsers.value = response.data
console.log('获取品系列表成功:', cattleUsers.value.length, '条记录')
}
} catch (error) {
console.error('获取品系列表失败:', error)
} finally {
cattleUsersLoading.value = false
}
}
// 获取品种和类别列表cattle_type表
const fetchCattleTypes = async () => {
try {
cattleTypesLoading.value = true
const response = await api.get('/cattle-type')
if (response.success) {
cattleTypes.value = response.data
console.log('获取品种列表成功:', cattleTypes.value.length, '条记录')
}
} catch (error) {
console.error('获取品种列表失败:', error)
} finally {
cattleTypesLoading.value = false
}
}
// 字段变化监听函数
const handleFieldChange = (fieldName, value) => {
console.log(`字段 ${fieldName} 变化:`, value)
@@ -625,6 +719,54 @@ const handleFieldChange = (fieldName, value) => {
formData[fieldName] = value
}
// 下拉框过滤选项方法
const filterOption = (input, option) => {
return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
// 类别下拉框过滤选项方法
const filterCateOption = (input, option) => {
return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
// 绑定耳标到iot_jbq_client表
const bindEarTag = async (earNumber, cattleId) => {
try {
console.log('开始绑定耳标:', earNumber, '到牛只ID:', cattleId)
// 查找对应的耳标设备
const response = await api.get('/iot-jbq-client', {
params: { cid: earNumber }
})
if (response.success && response.data && response.data.length > 0) {
const device = response.data[0]
console.log('找到耳标设备:', device)
// 更新设备的绑定状态
const updateResponse = await api.put(`/iot-jbq-client/${device.id}`, {
bandge_status: 1, // 1表示已绑定
bound_cattle_id: cattleId,
bound_ear_number: earNumber
})
if (updateResponse.success) {
console.log('耳标绑定成功')
message.success('耳标绑定成功')
} else {
console.error('耳标绑定失败:', updateResponse.message)
message.warning('牛只档案创建成功,但耳标绑定失败')
}
} else {
console.warn('未找到对应的耳标设备:', earNumber)
message.warning('牛只档案创建成功,但未找到对应的耳标设备')
}
} catch (error) {
console.error('耳标绑定失败:', error)
message.warning('牛只档案创建成功,但耳标绑定失败')
}
}
// 处理动物类型选择变化
const handleTypeChange = (value) => {
console.log('=== 动物类型变化 ===')
@@ -775,6 +917,8 @@ const openModal = () => {
// 加载必需数据
const loadRequiredData = () => {
fetchFarms()
fetchCattleUsers()
fetchCattleTypes()
}
// 提交表单使用iot_cattle API
@@ -789,11 +933,20 @@ const handleSubmit = async () => {
console.log('是否编辑模式:', isEdit.value);
console.log('原始表单数据:', JSON.parse(JSON.stringify(formData)));
// 处理日期格式
// 检查必填字段
const requiredFields = ['earNumber', 'sex', 'strain', 'varieties', 'cate', 'birthWeight', 'birthday', 'orgId'];
const missingFields = requiredFields.filter(field => !formData[field] && formData[field] !== 0);
if (missingFields.length > 0) {
console.error('缺少必填字段:', missingFields);
message.error(`缺少必填字段: ${missingFields.join(', ')}`);
return;
}
// 处理日期格式 - 转换为时间戳(秒)
const submitData = {
...formData,
birthday: formData.birthday ? formData.birthday.format('YYYY-MM-DD') : null,
weightCalculateTime: formData.weightCalculateTime ? formData.weightCalculateTime.format('YYYY-MM-DD') : null
birthday: formData.birthday ? Math.floor(formData.birthday.valueOf() / 1000) : null,
weightCalculateTime: formData.weightCalculateTime ? Math.floor(formData.weightCalculateTime.valueOf() / 1000) : null
}
// 如果是新增操作移除id字段
@@ -815,6 +968,11 @@ const handleSubmit = async () => {
console.log('服务器响应:', response);
if (response.success) {
// 如果创建成功且有耳标号,尝试绑定耳标
if (!isEdit.value && formData.earNumber) {
await bindEarTag(formData.earNumber, response.data.id)
}
message.success(isEdit.value ? '更新牛只档案成功' : '创建牛只档案成功')
modalVisible.value = false
await fetchAnimals()
@@ -1210,6 +1368,8 @@ onMounted(() => {
fetchFarms()
fetchPens()
fetchBatches()
fetchCattleUsers()
fetchCattleTypes()
})
</script>

View File

@@ -119,18 +119,32 @@
</template>
</a-input>
<a-select
v-model="searchType"
placeholder="围栏类型"
class="type-filter"
@change="handleSearch"
v-model="selectedFenceName"
placeholder="选择围栏名称"
class="fence-name-select"
@change="handleFenceNameSelect"
allowClear
show-search
:filter-option="filterFenceNameOption"
:loading="fenceNamesLoading"
>
<a-select-option value="">全部类型</a-select-option>
<a-select-option value="grazing">放牧区</a-select-option>
<a-select-option value="safety">安全区</a-select-option>
<a-select-option value="restricted">限制区</a-select-option>
<a-select-option value="collector">收集区</a-select-option>
<a-select-option
v-for="fence in fenceList"
:key="fence.id"
:value="fence.name"
>
{{ fence.name }}
</a-select-option>
</a-select>
<a-button
type="default"
@click="clearAllSearch"
class="clear-search-btn"
:disabled="!searchValue && !selectedFenceName && !searchType"
>
清除搜索
</a-button>
</div>
<div class="fence-stats">
<a-statistic
@@ -264,6 +278,8 @@ const submitting = ref(false)
const fenceModalVisible = ref(false)
const searchValue = ref('')
const searchType = ref('')
const selectedFenceName = ref('')
const fenceNamesLoading = ref(false)
const selectedFence = ref(null)
const editingFence = ref(null)
@@ -306,8 +322,13 @@ let allMarkers = []
const filteredFenceList = computed(() => {
let filtered = fenceList.value
// 按名称和描述搜索
if (searchValue.value) {
// 按围栏名称选择过滤
if (selectedFenceName.value) {
filtered = filtered.filter(fence => fence.name === selectedFenceName.value)
}
// 按名称和描述搜索(当没有选择具体围栏名称时)
if (searchValue.value && !selectedFenceName.value) {
const searchLower = searchValue.value.toLowerCase()
filtered = filtered.filter(fence =>
fence.name.toLowerCase().includes(searchLower) ||
@@ -700,7 +721,7 @@ const handleFenceSubmit = async () => {
// 保存围栏
const result = await api.electronicFence.createFence(fenceData)
if (result && result.id) {
if (result && result.data && result.data.id) {
// 保存坐标点
const pointsData = drawingState.currentPoints.map((point, index) => ({
lng: point.lng,
@@ -709,10 +730,16 @@ const handleFenceSubmit = async () => {
description: `围栏点${index + 1}`
}))
await api.electronicFencePoints.createPoints({
fence_id: result.id,
points: pointsData
})
try {
await api.electronicFencePoints.createPoints({
fence_id: result.data.id,
points: pointsData
})
console.log('坐标点创建成功')
} catch (pointError) {
console.error('坐标点创建失败:', pointError)
message.warning('围栏创建成功,但坐标点保存失败: ' + pointError.message)
}
console.log('围栏创建成功:', result)
message.success('围栏创建成功')
@@ -1090,6 +1117,27 @@ const handleSearch = () => {
// 搜索逻辑在计算属性中处理
}
// 处理围栏名称选择
const handleFenceNameSelect = (value) => {
selectedFenceName.value = value
// 清空文本搜索,避免冲突
if (value) {
searchValue.value = ''
}
}
// 过滤围栏名称选项
const filterFenceNameOption = (input, option) => {
return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
// 清除所有搜索条件
const clearAllSearch = () => {
searchValue.value = ''
selectedFenceName.value = ''
searchType.value = ''
}
// 初始化
onMounted(() => {
console.log('电子围栏组件已挂载')
@@ -1233,15 +1281,26 @@ onMounted(() => {
.search-section {
margin-bottom: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.search-input {
margin-bottom: 10px;
width: 100%;
}
.fence-name-select {
width: 100%;
}
.type-filter {
width: 100%;
margin-bottom: 10px;
}
.clear-search-btn {
width: 100%;
margin-top: 5px;
}
.fence-stats {