明树Git Lab
Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
J
jt_front
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
jt_front
Commits
24964840
Commit
24964840
authored
Apr 02, 2026
by
zhanghan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
增加电梯导航
parent
2ce8e606
Pipeline
#109163
passed with stage
in 21 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
711 additions
and
16 deletions
+711
-16
README.md
src/components/CollapseNavigation/README.md
+172
-0
index.vue
src/components/CollapseNavigation/index.vue
+293
-0
collapseNav.js
src/directives/collapseNav.js
+115
-0
index.js
src/directives/index.js
+2
-0
annualAdd.vue
src/views/everydayPage/annualAdd.vue
+127
-11
annualPlan.vue
src/views/everydayPage/annualPlan.vue
+2
-5
No files found.
src/components/CollapseNavigation/README.md
0 → 100644
View file @
24964840
# 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`
src/components/CollapseNavigation/index.vue
0 → 100644
View file @
24964840
<
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
>
src/directives/collapseNav.js
0 → 100644
View file @
24964840
/**
* 折叠面板导航指令
* 用法: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);
*/
src/directives/index.js
View file @
24964840
import
delayRender
from
"./delayRender"
;
import
collapseNav
from
"./collapseNav"
;
export
default
{
"delay-render"
:
delayRender
,
// 指令名与指令实现映射
"collapse-nav"
:
collapseNav
,
// 折叠面板导航指令
};
src/views/everydayPage/annualAdd.vue
View file @
24964840
...
...
@@ -3,12 +3,16 @@
<div
class=
"add-project-content"
v-loading=
"loading"
>
<routerBack
/>
<div
class=
"tabs-content"
>
<div
class=
"project-tab-content"
>
<div
class=
"project-tab-content
-wrapper
"
>
<div
class=
"tab-content"
>
<el-form
label-width=
"150"
:model=
"formData"
:disabled=
"isPreview"
>
<el-collapse
v-model=
"activeCollapse"
>
<!-- 项目基本信息:字段完全对齐数据库 -->
<el-collapse-item
title=
"项目基本信息"
name=
"项目基本信息"
>
<el-collapse-item
title=
"项目基本信息"
name=
"项目基本信息"
id=
"collapse-项目基本信息"
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"12"
>
<el-form-item
label=
"项目信息"
required
>
...
...
@@ -150,6 +154,7 @@
<el-collapse-item
title=
"国资委中央企业投资信息"
name=
"国资委中央企业投资信息"
id=
"collapse-国资委中央企业投资信息"
>
<div
class=
"tzxx"
>
<!-- 战略类A:按数据库字段补充所有表单项 -->
...
...
@@ -489,7 +494,11 @@
</div>
</el-collapse-item>
<!-- 最终分类情况:补充长文本输入框 -->
<el-collapse-item
title=
"最终分类情况"
name=
"最终分类情况"
>
<el-collapse-item
title=
"最终分类情况"
name=
"最终分类情况"
id=
"collapse-最终分类情况"
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"24"
>
<el-form-item
label=
"最终分类情况说明"
>
...
...
@@ -508,6 +517,7 @@
<el-collapse-item
title=
"可研/决策信息(单位:万元)"
name=
"可研/决策信息(单位:万元)"
id=
"collapse-可研/决策信息(单位:万元)"
>
<el-table
:data=
"financialIndicators"
...
...
@@ -572,7 +582,11 @@
</el-collapse-item>
<!-- 年度投资计划:基础信息 -->
<el-collapse-item
title=
"年度投资计划"
name=
"年度投资计划"
>
<el-collapse-item
title=
"年度投资计划"
name=
"年度投资计划"
id=
"collapse-年度投资计划"
>
<el-row
:gutter=
"20"
>
<!-- 基础短字段:span12分栏 -->
<el-col
:span=
"8"
>
...
...
@@ -773,6 +787,7 @@
<el-collapse-item
title=
"项目年度计划表格(单位:万元)"
name=
"项目年度计划表格(单位:万元)"
id=
"collapse-项目年度计划表格(单位:万元)"
>
<div
class=
"annualPlans"
>
<annualPlan
...
...
@@ -788,6 +803,7 @@
<el-collapse-item
title=
"项目年度计划(资金支付口径)"
name=
"项目年度计划(资金支付口径)"
id=
"collapse-项目年度计划(资金支付口径)"
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"6"
>
...
...
@@ -911,6 +927,7 @@
<el-collapse-item
title=
"2026年参股单位出资情况修正(单位:万元)"
name=
"2026年参股单位出资情况修正(单位:万元)"
id=
"collapse-2026年参股单位出资情况修正(单位:万元)"
>
<el-row
:gutter=
"20"
>
<el-col
:span=
"24"
>
...
...
@@ -932,6 +949,15 @@
</el-collapse>
</el-form>
</div>
<div
class=
"navigation-wrapper"
>
<CollapseNavigation
:nav-items=
"navigationItems"
:active-item=
"activeCollapse"
title=
"目录导航"
width=
"auto"
@
nav-click=
"handleNavClick"
/>
</div>
</div>
</div>
...
...
@@ -955,6 +981,7 @@ import { ElMessage } from "element-plus";
import
annualPlan
from
"./annualPlan.vue"
;
import
DynamicTable
from
"@/components/FormDynamicTable/index.vue"
;
import
routerBack
from
"@/components/common/routerBack.vue"
;
import
CollapseNavigation
from
"@/components/CollapseNavigation/index.vue"
;
// ========== 年度计划子组件专属时间列表【核心:适配任意年份,父子唯一统一】 ==========
const
annualDynamicTimeList
=
ref
([]);
...
...
@@ -1025,6 +1052,77 @@ const router = useRouter();
const
route
=
useRoute
();
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
([
"项目基本信息"
,
...
...
@@ -1372,7 +1470,7 @@ const initAnnualPlanTable = () => {
total
:
0
,
...
annualDynamicTimeList
.
value
.
reduce
(
(
obj
,
time
)
=>
({
...
obj
,
[
time
]:
0
}),
{}
{}
,
),
}));
formData
.
xmndjh
=
tableData
;
...
...
@@ -1393,7 +1491,7 @@ const updateAllFinancialTotalRow = () => {
dynamicTimeList
.
value
.
forEach
((
time
)
=>
(
totalRow
[
time
]
=
0
));
totalRow
.
parentCode
.
forEach
((
code
)
=>
{
const
childRow
=
financialIndicators
.
value
.
find
(
(
item
)
=>
item
.
serialNumber
===
code
(
item
)
=>
item
.
serialNumber
===
code
,
);
if
(
childRow
)
{
totalRow
.
total
+=
Number
(
childRow
.
total
)
||
0
;
...
...
@@ -1409,7 +1507,7 @@ const updateAllFinancialTotalRow = () => {
const
initFinancialRowTotal
=
()
=>
{
// 仅计算可研表格的非合计行
financialIndicators
.
value
.
forEach
(
(
row
)
=>
!
row
.
isTotal
&&
updateFinancialRowTotal
(
row
)
(
row
)
=>
!
row
.
isTotal
&&
updateFinancialRowTotal
(
row
)
,
);
// 仅更新可研表格的合计行
updateAllFinancialTotalRow
();
...
...
@@ -1462,7 +1560,7 @@ const fillFinancialTable = (backfillData) => {
const
backfillRow
=
backfillData
.
find
(
(
item
)
=>
item
.
serialNumber
===
frontRow
.
serialNumber
&&
item
.
indicatorName
===
frontRow
.
indicatorName
item
.
indicatorName
===
frontRow
.
indicatorName
,
);
if
(
!
backfillRow
)
return
;
frontRow
.
total
=
Number
(
backfillRow
.
total
)
||
0
;
...
...
@@ -1544,7 +1642,7 @@ const getJsqtzjcDetail = () => {
// 2. 可研表格:初始化时间+回填(与年度计划无关)
if
(
data
.
kyjcxx
&&
Array
.
isArray
(
data
.
kyjcxx
)
&&
data
.
kyjcxx
.
length
>
0
)
{
dynamicTimeList
.
value
=
Object
.
keys
(
data
.
kyjcxx
[
0
]).
filter
((
key
)
=>
/^
\d{4}(
-
\d{2})?
$/
.
test
(
key
)
/^
\d{4}(
-
\d{2})?
$/
.
test
(
key
)
,
);
}
else
{
generateDynamicTime
();
...
...
@@ -1615,7 +1713,7 @@ const backClick = () => router.back(-1);
// 表格单元格样式
const
tableCellStyle
=
({
row
,
columnIndex
})
=>
{
const
baseStyle
=
{
border
:
"1px solid #ebeef5"
border
:
"1px solid #ebeef5"
,
};
if
(
row
.
isTotal
)
{
...
...
@@ -1633,7 +1731,7 @@ const tableHeaderCellStyle = () => ({
background
:
"#f0f2f5"
,
color
:
"#333"
,
fontWeight
:
"600"
,
textAlign
:
"center"
textAlign
:
"center"
,
});
// 行类名(实现斑马纹效果)
...
...
@@ -1657,6 +1755,24 @@ onMounted(() => {
</
script
>
<
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 {
display: flex;
flex-wrap: wrap;
...
...
src/views/everydayPage/annualPlan.vue
View file @
24964840
...
...
@@ -276,7 +276,7 @@ watch(
const
newData
=
handleTableData
(
newVal
);
tableData
.
value
=
newData
;
// 只更新内部数据,不emit
},
{
deep
:
true
,
immediate
:
true
}
{
deep
:
true
,
immediate
:
true
}
,
);
// 监听时间列表变化,更新内部数据后统一emit【保留所有字段emit】
...
...
@@ -288,7 +288,7 @@ watch(
tableData
.
value
=
newData
;
emitDataChange
(
newData
);
},
{
deep
:
true
,
immediate
:
true
}
{
deep
:
true
,
immediate
:
true
}
,
);
// 工具方法:数据变化校验+深拷贝emit【完整保留所有字段emit给父组件】
...
...
@@ -419,7 +419,4 @@ onMounted(() => {
display
:
flex
;
}
// 右侧表格容器加最小宽度,避免挤压
.right-table
{
min-width
:
calc
(
160px
*
12
);
// 按12列计算最小宽度
}
</
style
>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment