明树Git Lab

Commit 24964840 authored by zhanghan's avatar zhanghan

增加电梯导航

parent 2ce8e606
Pipeline #109163 passed with stage
in 21 seconds
# CollapseNavigation 组件使用指南
## 功能说明
这是一个基于 Element Plus `el-collapse` 的粘性导航组件,可以自动为折叠面板生成右侧导航,支持:
- 自动获取所有折叠面板项
- 点击导航快速定位到对应面板
- 滚动时自动高亮当前可见的面板
- 粘性定位,始终可见
- 响应式设计
## 快速使用
### 1. 基础用法
```vue
<template>
<div class="page-container">
<div class="content-wrapper">
<!-- 主内容区 -->
<div class="main-content">
<el-collapse v-model="activeCollapse" v-collapse-nav="navigationItems">
<el-collapse-item title="项目基本信息" name="项目基本信息">
<!-- 内容 -->
</el-collapse-item>
<el-collapse-item title="投资信息" name="投资信息">
<!-- 内容 -->
</el-collapse-item>
</el-collapse>
</div>
<!-- 右侧导航 -->
<div class="nav-wrapper">
<CollapseNavigation
:nav-items="navigationItems"
:active-item="activeCollapse"
@nav-click="handleNavClick"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import CollapseNavigation from '@/components/CollapseNavigation/index.vue';
const activeCollapse = ref(['项目基本信息']);
const navigationItems = ref([]); // 由指令自动填充
const handleNavClick = (item) => {
// 点击导航时自动展开对应面板
if (!activeCollapse.value.includes(item.name)) {
activeCollapse.value.push(item.name);
}
};
</script>
<style scoped>
.content-wrapper {
display: flex;
gap: 20px;
}
.main-content {
flex: 1;
min-width: 0;
}
.nav-wrapper {
flex-shrink: 0;
width: 200px;
}
</style>
```
## 组件 Props
| 参数 | 说明 | 类型 | 默认值 |
|------|------|------|--------|
| navItems | 导航项列表 | Array | [] |
| activeItem | 当前激活的项 | String/Array | '' |
| width | 导航宽度 | String | '200px' |
| title | 导航标题 | String | '快速导航' |
| scrollContainer | 滚动容器选择器 | String | '' (window) |
| offset | 滚动偏移量 | Number | 0 |
## 组件事件
| 事件名 | 说明 | 参数 |
|--------|------|------|
| navClick | 导航项被点击 | item: 导航项对象 |
| update:activeItem | 激活项变化时触发 | name: 导航项名称 |
## 自定义指令
### v-collapse-nav
自动获取 `el-collapse` 的所有子项并生成导航数据。
**用法:**
```vue
<el-collapse v-collapse-nav="navigationItems">
<!-- 折叠面板项 -->
</el-collapse>
```
**参数:**
- `navigationItems`: 响应式数组,用于接收生成的导航数据
## 高级配置
### 自定义滚动容器
如果页面有自定义滚动容器,可以指定容器选择器:
```vue
<CollapseNavigation
:nav-items="navigationItems"
:active-item="activeCollapse"
scroll-container=".custom-scroll-container"
/>
```
### 调整偏移量
如果页面有固定头部,可以设置偏移量:
```vue
<CollapseNavigation
:nav-items="navigationItems"
:active-item="activeCollapse"
:offset="80"
/>
```
### 自定义导航标题
```vue
<CollapseNavigation
:nav-items="navigationItems"
:active-item="activeCollapse"
title="目录导航"
/>
```
## 样式定制
组件使用了 less,你可以通过全局样式覆盖来定制外观:
```less
// 修改导航项悬停背景色
.collapse-navigation .nav-item:hover {
background: #f0f0f0;
}
// 修改激活项颜色
.collapse-navigation .nav-item.active {
color: #custom-color;
}
```
## 注意事项
1. 确保 `el-collapse-item` 有唯一的 `name` 属性
2. 导航组件需要粘性定位的父容器
3. 如果内容是动态加载的,指令会自动更新导航项
4. 建议配合 `activeCollapse` 使用,点击导航时自动展开对应面板
## 完整示例
参考文件:`src/views/everydayPage/annualAdd.vue`
<template>
<div class="collapse-navigation" :style="{ width: width }">
<!-- <div class="nav-header" v-if="title">
{{ title }}
</div> -->
<div class="nav-list">
<div
v-for="item in navItems"
:key="item.name"
class="nav-item"
:class="{ active: currentItem === item.name }"
@click="scrollToItem(item)"
>
<span class="nav-dot"></span>
<span class="nav-text">{{ item.label || item.name }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from "vue";
const props = defineProps({
// 导航项列表
navItems: {
type: Array,
default: () => [],
},
// 当前激活的项
activeItem: {
type: [String, Array],
default: "",
},
// 导航宽度
width: {
type: String,
default: "200px",
},
// 导航标题
title: {
type: String,
default: "快速导航",
},
// 滚动容器选择器(默认为 window)
scrollContainer: {
type: String,
default: "",
},
// 偏移量(用于修正滚动位置)
offset: {
type: Number,
default: 0,
},
});
const emit = defineEmits(["update:activeItem", "navClick"]);
const currentItem = ref("");
const scrollContainerEl = ref(null);
// 初始化滚动容器
const initScrollContainer = () => {
if (props.scrollContainer) {
scrollContainerEl.value = document.querySelector(props.scrollContainer);
} else {
scrollContainerEl.value = window;
}
};
// 滚动到指定项
const scrollToItem = (item) => {
const targetId = `collapse-${item.name}`;
const targetElement = document.getElementById(targetId);
console.log("CollapseNavigation - Scrolling to:", {
targetId,
item,
found: !!targetElement,
allIds: Array.from(document.querySelectorAll('[id^="collapse-"]')).map(
(el) => el.id,
),
});
if (targetElement) {
// 使用更简单的滚动方式
targetElement.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
// 如果有偏移量,再进行微调
if (props.offset !== 0) {
setTimeout(() => {
const elementPosition = targetElement.getBoundingClientRect().top;
const offsetPosition =
elementPosition + window.pageYOffset - props.offset;
window.scrollTo({
top: offsetPosition,
behavior: "smooth",
});
}, 100);
}
currentItem.value = item.name;
emit("update:activeItem", item.name);
emit("navClick", item);
} else {
console.error("CollapseNavigation - Target element not found:", targetId);
}
};
// 检测当前可见的折叠面板
const detectCurrentItem = () => {
const container = scrollContainerEl.value;
const containerRect =
container === window
? { top: 0, bottom: window.innerHeight }
: container.getBoundingClientRect();
let maxVisibility = 0;
let bestItem = "";
props.navItems.forEach((item) => {
const targetId = `collapse-${item.name}`;
const targetElement = document.getElementById(targetId);
if (targetElement) {
const rect = targetElement.getBoundingClientRect();
const visibleTop = Math.max(rect.top, containerRect.top);
const visibleBottom = Math.min(rect.bottom, containerRect.bottom);
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
const visibility = visibleHeight / rect.height;
if (visibility > maxVisibility && visibility > 0.3) {
maxVisibility = visibility;
bestItem = item.name;
}
}
});
if (bestItem && bestItem !== currentItem.value) {
currentItem.value = bestItem;
emit("update:activeItem", bestItem);
}
};
// 防抖函数
const debounce = (fn, delay) => {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
};
const debouncedDetect = debounce(detectCurrentItem, 100);
// 监听滚动事件
const handleScroll = () => {
debouncedDetect();
};
// 监听 activeItem 变化
watch(
() => props.activeItem,
(newVal) => {
if (Array.isArray(newVal)) {
currentItem.value = newVal[0] || "";
} else {
currentItem.value = newVal;
}
},
{ immediate: true },
);
onMounted(() => {
initScrollContainer();
if (scrollContainerEl.value) {
scrollContainerEl.value.addEventListener("scroll", handleScroll, {
passive: true,
});
// 延迟检测,等待 DOM 渲染完成
setTimeout(detectCurrentItem, 300);
}
});
onBeforeUnmount(() => {
if (scrollContainerEl.value) {
scrollContainerEl.value.removeEventListener("scroll", handleScroll);
}
});
</script>
<style lang="less" scoped>
.collapse-navigation {
position: sticky;
top: 20px;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
max-height: calc(100vh - 40px);
overflow-y: auto;
z-index: 100;
.nav-header {
padding: 12px 16px;
font-size: 14px;
font-weight: 600;
color: #303133;
border-bottom: 1px solid #ebeef5;
background: #f5f7fa;
}
.nav-list {
padding: 8px 0;
}
.nav-item {
display: flex;
align-items: center;
padding: 10px 16px;
cursor: pointer;
transition: all 0.3s;
position: relative;
&:hover {
background: #f5f7fa;
.nav-dot {
background: #409eff;
transform: scale(1.2);
}
}
&.active {
background: #ecf5ff;
color: #409eff;
font-weight: 500;
.nav-dot {
background: #409eff;
transform: scale(1.3);
}
&::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: #409eff;
}
}
.nav-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #c0c4cc;
margin-right: 8px;
transition: all 0.3s;
flex-shrink: 0;
}
.nav-text {
flex: 1;
font-size: 13px;
color: #606266;
word-break: break-all;
}
}
}
// 滚动条样式
.collapse-navigation::-webkit-scrollbar {
width: 4px;
}
.collapse-navigation::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 2px;
}
.collapse-navigation::-webkit-scrollbar-thumb:hover {
background: #c0c4cc;
}
</style>
/**
* 折叠面板导航指令
* 用法:v-collapse-nav="navItems"
* 自动获取 el-collapse 的所有子项信息,生成导航数据
*/
export default {
mounted(el, binding) {
const updateNavItems = () => {
// 使用 nextTick 确保 DOM 完全渲染
setTimeout(() => {
// 获取所有 el-collapse-item 元素
const collapseItems = el.querySelectorAll('.el-collapse-item');
const navItems = [];
collapseItems.forEach((item, index) => {
// 获取标题
const titleEl = item.querySelector('.el-collapse-item__header');
const title = titleEl ? titleEl.textContent.trim() : `项目${index + 1}`;
// 尝试从多个来源获取 name
let name = '';
// 1. 优先从已有的 id 属性获取
const existingId = item.getAttribute('id');
if (existingId && existingId.startsWith('collapse-')) {
name = existingId.replace('collapse-', '');
} else {
// 2. 尝试从 data-name 属性获取(Element Plus 可能会将 name 存储在这里)
const dataName = item.getAttribute('data-name');
if (dataName) {
name = dataName;
} else {
// 3. 尝试从 name 属性获取
const nameAttr = item.getAttribute('name');
if (nameAttr) {
name = nameAttr;
} else {
// 4. 使用标题作为 name
name = title;
}
}
// 设置 id
item.setAttribute('id', `collapse-${name}`);
}
console.log('Navigation item:', {
name,
title,
id: item.getAttribute('id'),
allAttrs: Array.from(item.attributes).map(attr => `${attr.name}="${attr.value}"`)
});
navItems.push({
name,
label: title,
index
});
});
console.log('All navigation items:', navItems);
// 更新绑定值
if (binding.value) {
if (typeof binding.value === 'function') {
binding.value(navItems);
} else if (Array.isArray(binding.value)) {
// 先清空数组,再添加新元素
binding.value.length = 0;
navItems.forEach(navItem => {
binding.value.push(navItem);
});
}
}
}, 100);
};
// 初始化时更新
updateNavItems();
// 使用 MutationObserver 监听 DOM 变化(处理动态内容)
const observer = new MutationObserver((mutations) => {
// 只在节点变化时更新,避免无限循环
const hasRelevantMutation = mutations.some(mutation =>
mutation.type === 'childList' && mutation.addedNodes.length > 0
);
if (hasRelevantMutation) {
updateNavItems();
}
});
observer.observe(el, {
childList: true,
subtree: false
});
// 保存 observer 到元素上,方便后续清理
el._collapseNavObserver = observer;
},
unmounted(el) {
// 清理 observer
if (el._collapseNavObserver) {
el._collapseNavObserver.disconnect();
delete el._collapseNavObserver;
}
}
};
/**
* 在 Vue 应用中注册指令的示例:
* import collapseNav from '@/directives/collapseNav';
* app.directive('collapse-nav', collapseNav);
*/
import delayRender from "./delayRender"; import delayRender from "./delayRender";
import collapseNav from "./collapseNav";
export default { export default {
"delay-render": delayRender, // 指令名与指令实现映射 "delay-render": delayRender, // 指令名与指令实现映射
"collapse-nav": collapseNav, // 折叠面板导航指令
}; };
...@@ -3,12 +3,16 @@ ...@@ -3,12 +3,16 @@
<div class="add-project-content" v-loading="loading"> <div class="add-project-content" v-loading="loading">
<routerBack /> <routerBack />
<div class="tabs-content"> <div class="tabs-content">
<div class="project-tab-content"> <div class="project-tab-content-wrapper">
<div class="tab-content"> <div class="tab-content">
<el-form label-width="150" :model="formData" :disabled="isPreview"> <el-form label-width="150" :model="formData" :disabled="isPreview">
<el-collapse v-model="activeCollapse"> <el-collapse v-model="activeCollapse">
<!-- 项目基本信息:字段完全对齐数据库 --> <!-- 项目基本信息:字段完全对齐数据库 -->
<el-collapse-item title="项目基本信息" name="项目基本信息"> <el-collapse-item
title="项目基本信息"
name="项目基本信息"
id="collapse-项目基本信息"
>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="项目信息" required> <el-form-item label="项目信息" required>
...@@ -150,6 +154,7 @@ ...@@ -150,6 +154,7 @@
<el-collapse-item <el-collapse-item
title="国资委中央企业投资信息" title="国资委中央企业投资信息"
name="国资委中央企业投资信息" name="国资委中央企业投资信息"
id="collapse-国资委中央企业投资信息"
> >
<div class="tzxx"> <div class="tzxx">
<!-- 战略类A:按数据库字段补充所有表单项 --> <!-- 战略类A:按数据库字段补充所有表单项 -->
...@@ -489,7 +494,11 @@ ...@@ -489,7 +494,11 @@
</div> </div>
</el-collapse-item> </el-collapse-item>
<!-- 最终分类情况:补充长文本输入框 --> <!-- 最终分类情况:补充长文本输入框 -->
<el-collapse-item title="最终分类情况" name="最终分类情况"> <el-collapse-item
title="最终分类情况"
name="最终分类情况"
id="collapse-最终分类情况"
>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24">
<el-form-item label="最终分类情况说明"> <el-form-item label="最终分类情况说明">
...@@ -508,6 +517,7 @@ ...@@ -508,6 +517,7 @@
<el-collapse-item <el-collapse-item
title="可研/决策信息(单位:万元)" title="可研/决策信息(单位:万元)"
name="可研/决策信息(单位:万元)" name="可研/决策信息(单位:万元)"
id="collapse-可研/决策信息(单位:万元)"
> >
<el-table <el-table
:data="financialIndicators" :data="financialIndicators"
...@@ -572,7 +582,11 @@ ...@@ -572,7 +582,11 @@
</el-collapse-item> </el-collapse-item>
<!-- 年度投资计划:基础信息 --> <!-- 年度投资计划:基础信息 -->
<el-collapse-item title="年度投资计划" name="年度投资计划"> <el-collapse-item
title="年度投资计划"
name="年度投资计划"
id="collapse-年度投资计划"
>
<el-row :gutter="20"> <el-row :gutter="20">
<!-- 基础短字段:span12分栏 --> <!-- 基础短字段:span12分栏 -->
<el-col :span="8"> <el-col :span="8">
...@@ -773,6 +787,7 @@ ...@@ -773,6 +787,7 @@
<el-collapse-item <el-collapse-item
title="项目年度计划表格(单位:万元)" title="项目年度计划表格(单位:万元)"
name="项目年度计划表格(单位:万元)" name="项目年度计划表格(单位:万元)"
id="collapse-项目年度计划表格(单位:万元)"
> >
<div class="annualPlans"> <div class="annualPlans">
<annualPlan <annualPlan
...@@ -788,6 +803,7 @@ ...@@ -788,6 +803,7 @@
<el-collapse-item <el-collapse-item
title="项目年度计划(资金支付口径)" title="项目年度计划(资金支付口径)"
name="项目年度计划(资金支付口径)" name="项目年度计划(资金支付口径)"
id="collapse-项目年度计划(资金支付口径)"
> >
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="6"> <el-col :span="6">
...@@ -911,6 +927,7 @@ ...@@ -911,6 +927,7 @@
<el-collapse-item <el-collapse-item
title="2026年参股单位出资情况修正(单位:万元)" title="2026年参股单位出资情况修正(单位:万元)"
name="2026年参股单位出资情况修正(单位:万元)" name="2026年参股单位出资情况修正(单位:万元)"
id="collapse-2026年参股单位出资情况修正(单位:万元)"
> >
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="24"> <el-col :span="24">
...@@ -932,6 +949,15 @@ ...@@ -932,6 +949,15 @@
</el-collapse> </el-collapse>
</el-form> </el-form>
</div> </div>
<div class="navigation-wrapper">
<CollapseNavigation
:nav-items="navigationItems"
:active-item="activeCollapse"
title="目录导航"
width="auto"
@nav-click="handleNavClick"
/>
</div>
</div> </div>
</div> </div>
...@@ -955,6 +981,7 @@ import { ElMessage } from "element-plus"; ...@@ -955,6 +981,7 @@ import { ElMessage } from "element-plus";
import annualPlan from "./annualPlan.vue"; import annualPlan from "./annualPlan.vue";
import DynamicTable from "@/components/FormDynamicTable/index.vue"; import DynamicTable from "@/components/FormDynamicTable/index.vue";
import routerBack from "@/components/common/routerBack.vue"; import routerBack from "@/components/common/routerBack.vue";
import CollapseNavigation from "@/components/CollapseNavigation/index.vue";
// ========== 年度计划子组件专属时间列表【核心:适配任意年份,父子唯一统一】 ========== // ========== 年度计划子组件专属时间列表【核心:适配任意年份,父子唯一统一】 ==========
const annualDynamicTimeList = ref([]); const annualDynamicTimeList = ref([]);
...@@ -1025,6 +1052,77 @@ const router = useRouter(); ...@@ -1025,6 +1052,77 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
// ========== 导航配置 ==========
// 定义所有折叠面板的导航信息
const getNavigationItems = () => {
return [
{ name: "项目基本信息", label: "项目基本信息" },
{ name: "国资委中央企业投资信息", label: "国资委中央企业投资信息" },
{ name: "最终分类情况", label: "最终分类情况" },
{ name: "可研/决策信息(单位:万元)", label: "可研/决策信息" },
{ name: "年度投资计划", label: "年度投资计划" },
{ name: "项目年度计划表格(单位:万元)", label: "项目年度计划表格" },
{ name: "项目年度计划(资金支付口径)", label: "资金支付口径" },
{
name: "2026年参股单位出资情况修正(单位:万元)",
label: "参股单位出资修正",
},
];
};
const navigationItems = ref(getNavigationItems());
// 导航点击处理
const handleNavClick = (item) => {
console.log("Navigation clicked:", item);
// 点击导航时自动展开对应的折叠面板
if (!activeCollapse.value.includes(item.name)) {
activeCollapse.value.push(item.name);
}
// 等待 DOM 更新后滚动
nextTick(() => {
setTimeout(() => {
const targetId = `collapse-${item.name}`;
const targetElement = document.getElementById(targetId);
console.log("Scrolling to element:", {
targetId,
found: !!targetElement,
element: targetElement,
});
if (targetElement) {
// 使用 scrollIntoView 获得更平滑的效果
targetElement.scrollIntoView({
behavior: "smooth",
block: "start",
inline: "nearest",
});
// 可选:添加额外的偏移量调整
setTimeout(() => {
const elementPosition = targetElement.getBoundingClientRect().top;
if (elementPosition < 80) {
// 如果距离顶部太近,向上滚动一点
window.scrollBy({
top: elementPosition - 80,
behavior: "smooth",
});
}
}, 300);
} else {
console.error("Element not found:", targetId);
console.log(
"Available collapse elements:",
document.querySelectorAll('[id^="collapse-"]'),
);
}
}, 300); // 增加延迟确保动画完成
});
};
// ========== 基础配置:两个表格共用(仅时间列表,无其他关联) ========== // ========== 基础配置:两个表格共用(仅时间列表,无其他关联) ==========
const activeCollapse = ref([ const activeCollapse = ref([
"项目基本信息", "项目基本信息",
...@@ -1372,7 +1470,7 @@ const initAnnualPlanTable = () => { ...@@ -1372,7 +1470,7 @@ const initAnnualPlanTable = () => {
total: 0, total: 0,
...annualDynamicTimeList.value.reduce( ...annualDynamicTimeList.value.reduce(
(obj, time) => ({ ...obj, [time]: 0 }), (obj, time) => ({ ...obj, [time]: 0 }),
{} {},
), ),
})); }));
formData.xmndjh = tableData; formData.xmndjh = tableData;
...@@ -1393,7 +1491,7 @@ const updateAllFinancialTotalRow = () => { ...@@ -1393,7 +1491,7 @@ const updateAllFinancialTotalRow = () => {
dynamicTimeList.value.forEach((time) => (totalRow[time] = 0)); dynamicTimeList.value.forEach((time) => (totalRow[time] = 0));
totalRow.parentCode.forEach((code) => { totalRow.parentCode.forEach((code) => {
const childRow = financialIndicators.value.find( const childRow = financialIndicators.value.find(
(item) => item.serialNumber === code (item) => item.serialNumber === code,
); );
if (childRow) { if (childRow) {
totalRow.total += Number(childRow.total) || 0; totalRow.total += Number(childRow.total) || 0;
...@@ -1409,7 +1507,7 @@ const updateAllFinancialTotalRow = () => { ...@@ -1409,7 +1507,7 @@ const updateAllFinancialTotalRow = () => {
const initFinancialRowTotal = () => { const initFinancialRowTotal = () => {
// 仅计算可研表格的非合计行 // 仅计算可研表格的非合计行
financialIndicators.value.forEach( financialIndicators.value.forEach(
(row) => !row.isTotal && updateFinancialRowTotal(row) (row) => !row.isTotal && updateFinancialRowTotal(row),
); );
// 仅更新可研表格的合计行 // 仅更新可研表格的合计行
updateAllFinancialTotalRow(); updateAllFinancialTotalRow();
...@@ -1462,7 +1560,7 @@ const fillFinancialTable = (backfillData) => { ...@@ -1462,7 +1560,7 @@ const fillFinancialTable = (backfillData) => {
const backfillRow = backfillData.find( const backfillRow = backfillData.find(
(item) => (item) =>
item.serialNumber === frontRow.serialNumber && item.serialNumber === frontRow.serialNumber &&
item.indicatorName === frontRow.indicatorName item.indicatorName === frontRow.indicatorName,
); );
if (!backfillRow) return; if (!backfillRow) return;
frontRow.total = Number(backfillRow.total) || 0; frontRow.total = Number(backfillRow.total) || 0;
...@@ -1544,7 +1642,7 @@ const getJsqtzjcDetail = () => { ...@@ -1544,7 +1642,7 @@ const getJsqtzjcDetail = () => {
// 2. 可研表格:初始化时间+回填(与年度计划无关) // 2. 可研表格:初始化时间+回填(与年度计划无关)
if (data.kyjcxx && Array.isArray(data.kyjcxx) && data.kyjcxx.length > 0) { if (data.kyjcxx && Array.isArray(data.kyjcxx) && data.kyjcxx.length > 0) {
dynamicTimeList.value = Object.keys(data.kyjcxx[0]).filter((key) => dynamicTimeList.value = Object.keys(data.kyjcxx[0]).filter((key) =>
/^\d{4}(-\d{2})?$/.test(key) /^\d{4}(-\d{2})?$/.test(key),
); );
} else { } else {
generateDynamicTime(); generateDynamicTime();
...@@ -1615,7 +1713,7 @@ const backClick = () => router.back(-1); ...@@ -1615,7 +1713,7 @@ const backClick = () => router.back(-1);
// 表格单元格样式 // 表格单元格样式
const tableCellStyle = ({ row, columnIndex }) => { const tableCellStyle = ({ row, columnIndex }) => {
const baseStyle = { const baseStyle = {
border: "1px solid #ebeef5" border: "1px solid #ebeef5",
}; };
if (row.isTotal) { if (row.isTotal) {
...@@ -1633,7 +1731,7 @@ const tableHeaderCellStyle = () => ({ ...@@ -1633,7 +1731,7 @@ const tableHeaderCellStyle = () => ({
background: "#f0f2f5", background: "#f0f2f5",
color: "#333", color: "#333",
fontWeight: "600", fontWeight: "600",
textAlign: "center" textAlign: "center",
}); });
// 行类名(实现斑马纹效果) // 行类名(实现斑马纹效果)
...@@ -1657,6 +1755,24 @@ onMounted(() => { ...@@ -1657,6 +1755,24 @@ onMounted(() => {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
// 布局容器
.project-tab-content-wrapper {
display: flex;
gap: 20px;
position: relative;
}
.tab-content {
flex: 1;
min-width: 0; // 防止flex子项溢出
}
.navigation-wrapper {
flex-shrink: 0;
width: auto;
padding-left: 10px;
}
.tzxx { .tzxx {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
......
...@@ -276,7 +276,7 @@ watch( ...@@ -276,7 +276,7 @@ watch(
const newData = handleTableData(newVal); const newData = handleTableData(newVal);
tableData.value = newData; // 只更新内部数据,不emit tableData.value = newData; // 只更新内部数据,不emit
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
); );
// 监听时间列表变化,更新内部数据后统一emit【保留所有字段emit】 // 监听时间列表变化,更新内部数据后统一emit【保留所有字段emit】
...@@ -288,7 +288,7 @@ watch( ...@@ -288,7 +288,7 @@ watch(
tableData.value = newData; tableData.value = newData;
emitDataChange(newData); emitDataChange(newData);
}, },
{ deep: true, immediate: true } { deep: true, immediate: true },
); );
// 工具方法:数据变化校验+深拷贝emit【完整保留所有字段emit给父组件】 // 工具方法:数据变化校验+深拷贝emit【完整保留所有字段emit给父组件】
...@@ -419,7 +419,4 @@ onMounted(() => { ...@@ -419,7 +419,4 @@ onMounted(() => {
display: flex; display: flex;
} }
// 右侧表格容器加最小宽度,避免挤压 // 右侧表格容器加最小宽度,避免挤压
.right-table {
min-width: calc(160px * 12); // 按12列计算最小宽度
}
</style> </style>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment