This commit is contained in:
ll
2026-02-06 22:24:50 +08:00
4 changed files with 472 additions and 110 deletions

View File

@@ -0,0 +1,44 @@
import request from '@/utils/request'
// 查询VIEW列表
export function listPedigree(query) {
return request({
url: '/system/pedigree/list',
method: 'get',
params: query
})
}
// 查询VIEW详细
export function getPedigree(id) {
return request({
url: '/system/pedigree/' + id,
method: 'get'
})
}
// 新增VIEW
export function addPedigree(data) {
return request({
url: '/system/pedigree',
method: 'post',
data: data
})
}
// 修改VIEW
export function updatePedigree(data) {
return request({
url: '/system/pedigree',
method: 'put',
data: data
})
}
// 删除VIEW
export function delPedigree(id) {
return request({
url: '/system/pedigree/' + id,
method: 'delete'
})
}

View File

@@ -0,0 +1,428 @@
<template>
<div class="app-container">
<!-- 查询+导出区域 -->
<el-form :model="queryParams" ref="queryRef" :inline="true" label-width="68px">
<el-form-item label="管理耳号" prop="bsManageTags">
<el-select
v-model="queryParams.bsManageTags"
placeholder="请选择管理耳号"
clearable
@change="handleQuery"
style="width: 200px"
>
<el-option
v-for="item in tagOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">查询</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
<el-button
type="warning"
plain
icon="Download"
@click="handleExport"
v-hasPermi="['system:pedigree:export']"
style="margin-left: 10px"
>导出</el-button>
</el-form-item>
</el-form>
<!-- 横向系谱图优化横向布局 -->
<div class="pedigree-container" v-loading="loading">
<div class="empty-tip" v-if="!pedigreeData.id">
请选择管理耳号查询系谱数据
</div>
<div class="pedigree-tree" v-else>
<!-- 第一层核心羊左侧 -->
<div class="pedigree-level level-1">
<div class="pedigree-node" @click="handleNodeClick(pedigreeData.bsManageTags)">
<div class="sheep-icon core-sheep">🐑</div>
<span class="node-text">{{ pedigreeData.bsManageTags }}-{{ getGenderText(pedigreeData.gender) }}</span>
</div>
</div>
<!-- 第一层与第二层连线 -->
<div class="pedigree-line line-1-2"></div>
<!-- 第二层/母羊中间 -->
<div class="pedigree-level level-2">
<!-- 母本 -->
<div class="pedigree-node" v-if="pedigreeData.motherData" @click="handleNodeClick(pedigreeData.motherData.bsManageTags || pedigreeData.motherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">母亲耳号:{{ pedigreeData.motherData.bsManageTags || pedigreeData.motherManageTags }}</span>
</div>
<!-- 父本 -->
<div class="pedigree-node" v-if="pedigreeData.fatherData" @click="handleNodeClick(pedigreeData.fatherData.bsManageTags || pedigreeData.fatherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">父亲耳号:{{ pedigreeData.fatherData.bsManageTags || pedigreeData.fatherManageTags }}</span>
</div>
</div>
<!-- 第二层与第三层连线 -->
<div class="pedigree-line line-2-3"></div>
<!-- 第三层祖父母/外祖父母右侧 -->
<div class="pedigree-level level-3">
<!-- 外祖母上1 -->
<div class="pedigree-node" v-if="pedigreeData.maternalGrandmotherData" @click="handleNodeClick(pedigreeData.maternalGrandmotherData.bsManageTags || pedigreeData.maternalGrandmotherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">外祖母耳号:{{ pedigreeData.maternalGrandmotherData.bsManageTags || pedigreeData.maternalGrandmotherManageTags }}</span>
</div>
<!-- 外祖父上2 -->
<div class="pedigree-node" v-if="pedigreeData.maternalGrandfatherData" @click="handleNodeClick(pedigreeData.maternalGrandfatherData.bsManageTags || pedigreeData.maternalGrandfatherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">外祖父耳号:{{ pedigreeData.maternalGrandfatherData.bsManageTags || pedigreeData.maternalGrandfatherManageTags }}</span>
</div>
<!-- 祖母下1 -->
<div class="pedigree-node" v-if="pedigreeData.paternalGrandmotherData" @click="handleNodeClick(pedigreeData.paternalGrandmotherData.bsManageTags || pedigreeData.grandmotherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">祖母耳号:{{ pedigreeData.paternalGrandmotherData.bsManageTags || pedigreeData.grandmotherManageTags }}</span>
</div>
<!-- 祖父下2 -->
<div class="pedigree-node" v-if="pedigreeData.paternalGrandfatherData" @click="handleNodeClick(pedigreeData.paternalGrandfatherData.bsManageTags || pedigreeData.grandfatherManageTags)">
<div class="sheep-icon normal-sheep">🐑</div>
<span class="node-text">祖父耳号:{{ pedigreeData.paternalGrandfatherData.bsManageTags || pedigreeData.grandfatherManageTags }}</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup name="Pedigree">
import { ref, reactive, toRefs, getCurrentInstance } from 'vue'
import { listPedigree, getPedigree } from "@/api/fileManagement/pedigree"
const { proxy } = getCurrentInstance()
// 管理耳号下拉选项
const tagOptions = ref([])
// 系谱核心数据
const pedigreeData = ref({})
const loading = ref(false)
// 查询参数
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
bsManageTags: null
}
})
const { queryParams } = toRefs(data)
/** 性别转换:数字转文字 */
function getGenderText(gender) {
if (gender === 1) return '母'
if (gender === 2) return '公'
if (gender === 3) return '阉羊'
if (gender === 4) return '兼性'
return '未知'
}
/** 初始化耳号下拉 */
function initTagOptions() {
listPedigree({ pageSize: 9999 }).then(response => {
tagOptions.value = response.rows.map(item => ({
label: item.bsManageTags,
value: item.bsManageTags
}))
})
}
/** 根据耳号查询单只羊数据 */
async function getSheepByTag(tag) {
const listRes = await listPedigree({ bsManageTags: tag })
return listRes.rows[0] || {}
}
/** 构建横向系谱数据 */
async function buildPedigreeData(baseTag) {
const baseSheep = await getSheepByTag(baseTag)
if (!baseSheep.id) {
pedigreeData.value = {}
return
}
const pedigree = { ...baseSheep }
// 获取母本+外祖父母数据
if (baseSheep.bsMotherId) {
try {
const motherRes = await getPedigree(baseSheep.bsMotherId)
pedigree.motherData = motherRes.data || {}
if (pedigree.motherData.bsFatherId) {
const mgfRes = await getPedigree(pedigree.motherData.bsFatherId)
pedigree.maternalGrandfatherData = mgfRes.data || {}
}
if (pedigree.motherData.bsMotherId) {
const mgmRes = await getPedigree(pedigree.motherData.bsMotherId)
pedigree.maternalGrandmotherData = mgmRes.data || {}
}
} catch (e) {
console.error("获取母本系谱失败:", e)
}
}
// 获取父本+祖父母数据
if (baseSheep.bsFatherId) {
try {
const fatherRes = await getPedigree(baseSheep.bsFatherId)
pedigree.fatherData = fatherRes.data || {}
if (pedigree.fatherData.bsFatherId) {
const pgfRes = await getPedigree(pedigree.fatherData.bsFatherId)
pedigree.paternalGrandfatherData = pgfRes.data || {}
}
if (pedigree.fatherData.bsMotherId) {
const pgfRes = await getPedigree(pedigree.fatherData.bsMotherId)
pedigree.paternalGrandmotherData = pgfRes.data || {}
}
} catch (e) {
console.error("获取父本系谱失败:", e)
}
}
pedigreeData.value = pedigree
}
/** 查询系谱数据 */
async function getList() {
if (!queryParams.value.bsManageTags) {
proxy.$modal.msgWarning("请选择管理耳号后查询")
pedigreeData.value = {}
return
}
loading.value = true
try {
await buildPedigreeData(queryParams.value.bsManageTags)
} catch (e) {
console.error("查询系谱失败:", e)
pedigreeData.value = {}
} finally {
loading.value = false
}
}
/** 点击节点切换系谱树 */
async function handleNodeClick(tag) {
if (!tag) {
proxy.$modal.msgWarning("该羊只暂无有效耳号,无法查询系谱")
return
}
// 更新查询参数为点击节点的耳号
queryParams.value.bsManageTags = tag
// 重新查询并渲染该羊的系谱树
await getList()
}
/** 搜索/重置/导出 */
function handleQuery() {
getList()
}
function resetQuery() {
proxy.resetForm("queryRef")
queryParams.value.bsManageTags = null
pedigreeData.value = {}
}
function handleExport() {
if (!queryParams.value.bsManageTags) {
proxy.$modal.msgWarning("请选择管理耳号后导出")
return
}
proxy.download('system/pedigree/export', {
...queryParams.value
}, `pedigree_${queryParams.value.bsManageTags}_${new Date().getTime()}.xlsx`)
}
// 初始化
initTagOptions()
</script>
<style scoped>
/* 系谱容器整体样式 */
.pedigree-container {
margin-top: 20px;
padding: 30px 20px;
background: #fff;
border-radius: 8px;
min-height: 500px;
position: relative;
overflow-x: auto; /* 适配小屏幕横向滚动 */
}
/* 空状态提示 */
.empty-tip {
text-align: center;
line-height: 500px;
color: #999;
font-size: 14px;
}
/* 系谱树核心布局 - 横向排列 */
.pedigree-tree {
display: flex;
flex-direction: row; /* 横向布局核心 */
align-items: center;
justify-content: flex-start;
min-width: 1000px; /* 保证横向布局基础宽度 */
height: 450px;
position: relative;
margin: 0 auto;
}
/* 层级通用样式 */
.pedigree-level {
display: flex;
flex-direction: column; /* 每个层级内部垂直排列 */
align-items: center;
justify-content: center;
height: 100%;
flex: 0 0 200px; /* 每个层级固定宽度 */
gap: 40px; /* 层级内节点间距 */
}
/* 第一层(核心羊)- 最左侧 */
.level-1 {
position: absolute;
left: 50px;
top: 50%;
transform: translateY(-50%);
gap: 0; /* 核心羊只有一个节点 */
}
/* 第二层(父母)- 中间 */
.level-2 {
position: absolute;
left: 300px;
top: 50%;
transform: translateY(-50%);
}
/* 第三层(祖父母/外祖父母)- 最右侧 */
.level-3 {
position: absolute;
left: 550px;
top: 50%;
transform: translateY(-50%);
gap: 20px; /* 四层节点缩小间距 */
}
/* 节点样式优化 */
.pedigree-node {
display: flex;
align-items: center;
flex-direction: column;
gap: 10px;
padding: 10px;
border-radius: 8px;
transition: all 0.2s ease;
cursor: pointer; /* 鼠标悬浮显示手型,提示可点击 */
}
.pedigree-node:hover {
background: #e8f4ff; /* 悬浮浅蓝背景,增强交互提示 */
transform: scale(1.05); /* 悬浮放大效果 */
}
/* 羊图标优化 */
.sheep-icon {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
color: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.15); /* 阴影提升质感 */
}
/* 核心羊图标(蓝色渐变) */
.core-sheep {
background: linear-gradient(135deg, #409eff, #66b1ff);
}
/* 普通羊图标(灰色渐变) */
.normal-sheep {
background: linear-gradient(135deg, #e5e6eb, #c9c9c9);
color: #666;
}
/* 节点文字优化 */
.node-text {
font-size: 13px;
color: #333;
text-align: center;
white-space: nowrap;
padding: 2px 8px;
border-radius: 4px;
background: #f9f9f9;
}
/* 连线样式 - 核心羊到父母(横向连线) */
.line-1-2 {
position: absolute;
left: 100px;
top: 50%;
width: 180px;
height: 2px;
background: #409eff; /* 主色调连线 */
transform: translateY(-50%);
}
/* 核心羊到父母的垂直分支线 */
.line-1-2::before {
content: '';
position: absolute;
left: 180px;
top: -60px;
width: 2px;
height: 120px;
background: #409eff;
}
/* 连线样式 - 父母到祖父母(横向连线) */
.line-2-3 {
position: absolute;
left: 350px;
top: 50%;
width: 180px;
height: 2px;
background: #409eff;
transform: translateY(-50%);
}
/* 父母到祖父母的垂直分支线 */
.line-2-3::before {
content: '';
position: absolute;
left: 180px;
top: -100px;
width: 2px;
height: 200px;
background: #409eff;
}
/* 适配小屏幕 */
@media (max-width: 1200px) {
.pedigree-container {
padding: 20px 10px;
}
.pedigree-tree {
min-width: 800px;
}
.level-1 { left: 20px; }
.level-2 { left: 250px; }
.level-3 { left: 480px; }
.line-1-2 { width: 210px; }
.line-2-3 { width: 210px; left: 300px; }
}
</style>

View File

@@ -1,110 +0,0 @@
<!-- src/views/fileManagement/sheep_pedigree/index.vue -->
<template>
<div class="app-container">
<!-- 诊断信息 -->
<div style="background:#f0f9ff; padding:20px; margin-bottom:20px; border:1px solid #bae0ff;">
<h3>页面加载诊断</h3>
<p><strong>路由路径</strong> {{ $route.path }}</p>
<p><strong>路由参数</strong> {{ $route.params }}</p>
<p><strong>查询参数</strong> {{ $route.query }}</p>
<p><strong>组件路径</strong> {{ $route.meta?.component }}</p>
<p><strong>菜单配置</strong> {{ menuConfig }}</p>
<el-button @click="reloadPage" type="primary">重新加载</el-button>
<el-button @click="goBack" type="info">返回</el-button>
</div>
<!-- 测试内容 -->
<div style="text-align:center; padding:50px;">
<h1 style="color:#1890ff;">羊只系谱管理页面</h1>
<p v-if="isFrameIssue" style="color:red; font-weight:bold;">
检测到 isFrame=1 问题请修改菜单配置为"是否外链="
</p>
<p>这是一个测试页面如果看到此内容说明组件加载成功</p>
<el-button type="success" @click="testClick">测试按钮</el-button>
</div>
</div>
</template>
<script>
export default {
name: "SheepPedigree",
data() {
return {
menuConfig: {},
isFrameIssue: false
};
},
created() {
console.log("=== 羊只系谱页面创建 ===");
console.log("1. 路由信息:", this.$route);
console.log("2. 路由匹配:", this.$router.currentRoute);
console.log("3. 权限信息:", this.$store.state.user.permissions);
// 检测是否是isFrame问题
const route = this.$route;
if (route.meta && route.meta.isFrame === '1') {
this.isFrameIssue = true;
console.error("检测到isFrame=1会导致页面显示问题");
}
// 获取当前路由对应的菜单配置
this.getMenuConfig();
// 测试API
this.testApiConnection();
},
mounted() {
console.log("页面已挂载到DOM");
console.log("当前URL", window.location.href);
},
methods: {
getMenuConfig() {
// 从Vuex中查找当前路由对应的菜单
const routes = this.$store.state.permission.routes || [];
this.findMenuConfig(routes, this.$route.path);
},
findMenuConfig(menus, path) {
for (const menu of menus) {
if (menu.path === path) {
this.menuConfig = menu;
console.log("找到菜单配置:", menu);
return;
}
if (menu.children) {
this.findMenuConfig(menu.children, path);
}
}
},
async testApiConnection() {
try {
// 测试调用API使用已有的API
const response = await this.$axios.get('/system/dict/data/type/sheep_type');
console.log("API测试成功", response.data);
} catch (error) {
console.warn("API测试失败", error.response || error.message);
}
},
testClick() {
this.$message.success("按钮点击成功!页面功能正常");
},
reloadPage() {
window.location.reload();
},
goBack() {
this.$router.back();
}
}
};
</script>
<style scoped>
.app-container {
min-height: 500px;
padding: 20px;
}
</style>