明树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
0380bcfc
Commit
0380bcfc
authored
Feb 02, 2026
by
zhanghan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
年度计划k
parent
5bc37918
Pipeline
#106888
passed with stage
in 19 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
290 additions
and
166 deletions
+290
-166
routes.js
src/router/routes.js
+35
-0
investmentCecovery.vue
src/views/castbehind/investmentCecovery.vue
+26
-0
cost.vue
src/views/elseManage/cost.vue
+26
-0
link.vue
src/views/elseManage/link.vue
+26
-0
property.vue
src/views/elseManage/property.vue
+26
-0
annualAdd.vue
src/views/everydayPage/annualAdd.vue
+47
-58
annualPlan.vue
src/views/everydayPage/annualPlan.vue
+104
-108
No files found.
src/router/routes.js
View file @
0380bcfc
...
...
@@ -162,6 +162,13 @@ const routes = [
title
:
"运营期投资检查"
,
component
:
()
=>
import
(
"@/views/castbehind/runningPeriod.vue"
),
},
{
path
:
"/investmentCecovery"
,
name
:
"investmentCecovery"
,
title
:
"运营期投资回收"
,
component
:
()
=>
import
(
"@/views/castbehind/investmentCecovery.vue"
),
},
{
path
:
"/runningPeriodAdd"
,
name
:
"runningPeriodAdd"
,
...
...
@@ -298,6 +305,34 @@ const routes = [
// },
],
},
{
path
:
"/elseManage"
,
name
:
"elseManage"
,
title
:
"其他管理"
,
redirect
:
"/share"
,
children
:
[
{
path
:
"/cost"
,
name
:
"cost"
,
title
:
"成本管理"
,
component
:
()
=>
import
(
"@/views/elseManage/cost.vue"
),
},
{
path
:
"/property"
,
name
:
"property"
,
title
:
"成本管理"
,
component
:
()
=>
import
(
"@/views/elseManage/property.vue"
),
},
{
path
:
"/link"
,
name
:
"link"
,
title
:
"成本管理"
,
component
:
()
=>
import
(
"@/views/elseManage/link.vue"
),
},
],
},
{
path
:
"/systemManage"
,
name
:
"systemManage"
,
...
...
src/views/castbehind/investmentCecovery.vue
0 → 100644
View file @
0380bcfc
<
template
>
<div
class=
"building-container"
>
<img
src=
"@/assets/images/building.png"
alt=
""
/>
<div
class=
"title"
>
努力开发中……
</div>
</div>
</
template
>
<
script
setup
></
script
>
<
style
lang=
"less"
>
.building-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
max-width: 300px;
}
.title {
font-size: 18px;
color: #999;
}
}
</
style
>
src/views/elseManage/cost.vue
0 → 100644
View file @
0380bcfc
<
template
>
<div
class=
"building-container"
>
<img
src=
"@/assets/images/building.png"
alt=
""
/>
<div
class=
"title"
>
努力开发中……
</div>
</div>
</
template
>
<
script
setup
></
script
>
<
style
lang=
"less"
>
.building-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
max-width: 300px;
}
.title {
font-size: 18px;
color: #999;
}
}
</
style
>
src/views/elseManage/link.vue
0 → 100644
View file @
0380bcfc
<
template
>
<div
class=
"building-container"
>
<img
src=
"@/assets/images/building.png"
alt=
""
/>
<div
class=
"title"
>
努力开发中……
</div>
</div>
</
template
>
<
script
setup
></
script
>
<
style
lang=
"less"
>
.building-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
max-width: 300px;
}
.title {
font-size: 18px;
color: #999;
}
}
</
style
>
src/views/elseManage/property.vue
0 → 100644
View file @
0380bcfc
<
template
>
<div
class=
"building-container"
>
<img
src=
"@/assets/images/building.png"
alt=
""
/>
<div
class=
"title"
>
努力开发中……
</div>
</div>
</
template
>
<
script
setup
></
script
>
<
style
lang=
"less"
>
.building-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
img {
max-width: 300px;
}
.title {
font-size: 18px;
color: #999;
}
}
</
style
>
src/views/everydayPage/annualAdd.vue
View file @
0380bcfc
...
...
@@ -487,7 +487,7 @@
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"新建/续建"
>
<CommonSelector
v-model=
"formData.xjXj"
dictName=
"
sf
"
/>
<CommonSelector
v-model=
"formData.xjXj"
dictName=
"
xj
"
/>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
...
...
@@ -785,6 +785,7 @@ import { ElMessage } from "element-plus";
import
annualPlan
from
"./annualPlan.vue"
;
import
DynamicTable
from
"@/components/FormDynamicTable/index.vue"
;
// ========== 年度计划子组件专属时间列表【核心:父子唯一统一,不与可研混用】 ==========
const
annualDynamicTimeList
=
ref
([
"2025及以前"
,
"2026"
,
...
...
@@ -823,7 +824,6 @@ const transferColumns = ref([
placeholder
:
"请输入差异说明"
,
},
]);
const
cgdwczqkxz
=
ref
([]);
// ========== 路由/实例/全局方法 ==========
const
router
=
useRouter
();
...
...
@@ -849,12 +849,11 @@ const isPreview = ref(!!route.query.isPreview);
const
projectList
=
ref
([]);
const
rcCgqyglId
=
ref
(
route
.
query
.
id
||
""
);
const
timeHeaderType
=
ref
(
"year"
);
// year/ month 切换时间表头类型
const
dynamicTimeList
=
ref
([]);
//
动态时间列表(两个表格共用表头,无其他数据关联)
const
dynamicTimeList
=
ref
([]);
//
可研表格专属时间列表,与年度计划彻底隔离
// ========== 数据源完全隔离:可研表格 & 年度计划表格 各自独立 ==========
// 1. 可研/决策信息表格:专属数据源(独立)
const
financialIndicators
=
ref
([]);
// 可研表格渲染数据源
// 2. 项目年度计划表格:仅用子组件双向绑定的formData.xmndjh,移除冗余的annualPlanIndicators(彻底解耦)
// ========== 可研/决策信息表格:原始配置+专属初始化/方法(完全独立) ==========
const
financialOriginConfig
=
ref
([
...
...
@@ -1175,17 +1174,22 @@ const initFinancialTable = () => {
initFinancialRowTotal
();
};
// ========== 【专属方法】项目年度计划表格:初始化(独立,
子组件专属17行
) ==========
// ========== 【专属方法】项目年度计划表格:初始化(独立,
仅新增模式执行,不覆盖接口数据
) ==========
const
initAnnualPlanTable
=
()
=>
{
if
(
dynamicTimeList
.
value
.
length
===
0
)
return
;
// 生成17行带唯一id的独立数据(子组件专属,与可研表格无任何关联)
// 仅新增模式初始化,编辑模式由接口回填,不执行此方法
if
(
rcCgqyglId
.
value
)
return
;
if
(
annualDynamicTimeList
.
value
.
length
===
0
)
return
;
// 生成17行带唯一id的独立数据(保留基础id,子组件会保留所有字段)
const
tableData
=
Array
.
from
({
length
:
17
},
(
_
,
index
)
=>
({
id
:
index
+
1
,
// 子组件
row-key="id" 专属,唯一标识
id
:
index
+
1
,
// 子组件
唯一标识,必须保留
total
:
0
,
...
dynamicTimeList
.
value
.
reduce
((
obj
,
time
)
=>
({
...
obj
,
[
time
]:
0
}),
{}),
...
annualDynamicTimeList
.
value
.
reduce
(
(
obj
,
time
)
=>
({
...
obj
,
[
time
]:
0
}),
{},
),
}));
// 仅赋值给年度计划表格的绑定字段,与可研表格无任何引用
formData
.
xmndjh
=
tableData
;
console
.
log
(
"新增模式初始化xmndjh:"
,
formData
.
xmndjh
);
};
// ========== 【专属方法】可研/决策信息表格:单行合计计算(独立) ==========
...
...
@@ -1241,11 +1245,11 @@ const handleFinancialChange = (currentRow) => {
// ========== 【专属方法】项目年度计划表格:子组件回调(独立,仅影响自身) ==========
const
handleAnnualPlanTableChange
=
(
newData
)
=>
{
if
(
isPreview
.
value
)
return
;
// 仅接收子组件的年度计划数据,与可研表格无任何交互
formData
.
xmndjh
=
[...
newData
]
;
formData
.
xmndjh
=
newData
;
console
.
log
(
"子组件回调更新xmndjh:"
,
formData
.
xmndjh
)
;
};
// ==========
动态时间生成:仅生成一次,两个表格共用表头(无数据关联)
==========
// ==========
可研表格动态时间生成:仅可研用,与年度计划无关
==========
const
generateDynamicTime
=
()
=>
{
const
now
=
new
Date
();
const
currentYear
=
now
.
getFullYear
();
...
...
@@ -1261,9 +1265,8 @@ const generateDynamicTime = () => {
}
}
dynamicTimeList
.
value
=
timeList
;
//
两个表格分别独立初始化,无互相调用
//
仅初始化可研表格,年度计划表格单独初始化
initFinancialTable
();
initAnnualPlanTable
();
};
// ========== 回填逻辑:完全隔离,各自处理自身表格 ==========
...
...
@@ -1286,21 +1289,23 @@ const fillFinancialTable = (backfillData) => {
initFinancialRowTotal
();
};
// 2.
项目年度计划表格:专属回填(独立,按id匹配,精准无错位)
// 2.
年度计划表格:专属回填【核心:用子组件的annualDynamicTimeList,不与可研混用】
const
fillAnnualPlanTable
=
(
backfillData
)
=>
{
if
(
!
Array
.
isArray
(
backfillData
)
||
backfillData
.
length
===
0
)
return
;
// 先初始化年度计划表格的空数据
initAnnualPlanTable
();
// 按唯一id匹配回填,避免indicatorName重复导致的错位
console
.
log
(
"接口返回的xmndjh回填数据:"
,
backfillData
);
// 按唯一id匹配回填,保留所有原有字段,仅更新数字字段
formData
.
xmndjh
.
forEach
((
frontRow
)
=>
{
const
backfillRow
=
backfillData
.
find
((
item
)
=>
item
.
id
===
frontRow
.
id
);
if
(
!
backfillRow
)
return
;
// 仅赋值total,不碰其他字段
frontRow
.
total
=
Number
(
backfillRow
.
total
)
||
0
;
dynamicTimeList
.
value
.
forEach
((
time
)
=>
{
// 仅遍历子组件的年度时间列表赋值,其他字段完全保留
annualDynamicTimeList
.
value
.
forEach
((
time
)
=>
{
frontRow
[
time
]
=
backfillRow
[
time
]
!==
undefined
?
Number
(
backfillRow
[
time
])
||
0
:
0
;
});
});
console
.
log
(
"回填后的xmndjh(保留所有字段):"
,
formData
.
xmndjh
);
};
// ========== 业务方法:获取项目列表(通用) ==========
...
...
@@ -1322,8 +1327,6 @@ const changeProject = (val) => {
data
:
{
id
:
val
},
callback
:
(
data
)
=>
{
loading
.
value
=
false
;
console
.
log
(
data
,
"项目详情数据"
);
formData
.
projectName
=
data
.
projectName
||
""
;
formData
.
sbdw
=
data
.
sbdw
||
""
;
formData
.
xmgsmc
=
data
.
xmgsmc
||
""
;
...
...
@@ -1342,7 +1345,7 @@ const changeProject = (val) => {
if
(
selectItem
)
formData
.
projectName
=
selectItem
.
projectName
;
};
// ========== 获取详情(编辑/预览):
两个表格分别独立回填
==========
// ========== 获取详情(编辑/预览):
核心修复【不覆盖接口返回的xmndjh,不混用时间列表】
==========
const
getJsqtzjcDetail
=
()
=>
{
if
(
!
rcCgqyglId
.
value
)
return
;
loading
.
value
=
true
;
...
...
@@ -1352,17 +1355,11 @@ const getJsqtzjcDetail = () => {
callback
:
(
data
)
=>
{
loading
.
value
=
false
;
if
(
!
data
)
return
ElMessage
.
error
(
"未查询到数据"
);
console
.
log
(
data
,
"data"
);
Object
.
assign
(
formData
,
data
);
// 回填基础字段
if
(
data
.
xmndjh
&&
Array
.
isArray
(
data
.
xmndjh
)
&&
data
.
xmndjh
.
length
>
0
)
{
annualDynamicTimeList
.
value
=
Object
.
keys
(
data
.
xmndjh
[
0
]).
filter
(
(
key
)
=>
/^
\d{4}
.*/
.
test
(
key
),
);
}
console
.
log
(
"接口返回完整数据:"
,
data
);
// 步骤1:提取/生成时间列表(共用表头)
// 1. 回填基础字段,保留xmndjh的完整结构
Object
.
assign
(
formData
,
data
);
// 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
),
...
...
@@ -1370,16 +1367,14 @@ const getJsqtzjcDetail = () => {
}
else
{
generateDynamicTime
();
}
initFinancialTable
();
if
(
data
.
kyjcxx
)
fillFinancialTable
(
data
.
kyjcxx
);
//
步骤2:两个表格分别独立初始化
if
(
d
ynamicTimeList
.
value
.
length
>
0
)
{
initFinancialTable
();
initAnnualPlanTable
();
//
3. 年度计划表格:【核心】直接使用接口返回的xmndjh,不重新初始化/覆盖
if
(
d
ata
.
xmndjh
&&
Array
.
isArray
(
data
.
xmndjh
)
&&
data
.
xmndjh
.
length
>
0
)
{
formData
.
xmndjh
=
data
.
xmndjh
;
// 保留接口返回的所有字段
fillAnnualPlanTable
(
data
.
xmndjh
);
// 仅更新数字字段,不改变结构
}
// 步骤3:两个表格分别独立回填(完全隔离)
if
(
data
.
kyjcxx
)
fillFinancialTable
(
data
.
kyjcxx
);
if
(
data
.
xmndjh
)
fillAnnualPlanTable
(
data
.
xmndjh
);
},
error
:
()
=>
{
loading
.
value
=
false
;
...
...
@@ -1388,22 +1383,19 @@ const getJsqtzjcDetail = () => {
});
};
// ========== 保存表单:
两个表格的提交数据完全独立拆分
==========
// ========== 保存表单:
核心修复【移除xmndjh的字段过滤,传递完整数据】
==========
const
saveClick
=
()
=>
{
console
.
log
(
formData
.
xmndjh
,
"formData.xmndjh"
);
console
.
log
(
annualDynamicTimeList
,
"formData.xmndjh"
);
if
(
!
formData
.
projectId
)
return
ElMessage
.
warning
(
"请选择项目信息"
);
loading
.
value
=
true
;
const
url
=
rcCgqyglId
.
value
?
"/api/project/updateTzjh"
:
"/api/project/createTzjh"
;
// 组装提交数据:
两个表格的字段完全独立拆分,无任何混叠
// 组装提交数据:
【核心】xmndjh直接传递,不做任何过滤,保留所有字段
const
submitData
=
{
...
formData
,
projectId
:
String
(
formData
.
projectId
),
//
1. 可研/决策信息表格:专属提交数据(仅自身字段
)
//
可研表格:按需过滤(保留原有逻辑
)
kyjcxx
:
financialIndicators
.
value
.
map
((
row
)
=>
{
const
filterRow
=
{
serialNumber
:
row
.
serialNumber
,
...
...
@@ -1414,15 +1406,10 @@ const saveClick = () => {
dynamicTimeList
.
value
.
forEach
((
time
)
=>
(
filterRow
[
time
]
=
row
[
time
]));
return
filterRow
;
}),
// 2. 项目年度计划表格:专属提交数据(仅自身字段,子组件绑定的xmndjh)
xmndjh
:
formData
.
xmndjh
.
map
((
row
)
=>
{
const
filterRow
=
{
id
:
row
.
id
,
total
:
row
.
total
};
annualDynamicTimeList
.
value
.
forEach
(
(
time
)
=>
(
filterRow
[
time
]
=
row
[
time
]),
);
return
filterRow
;
}),
// 年度计划表格:【关键修复】直接传完整的xmndjh,不做map过滤!
xmndjh
:
formData
.
xmndjh
,
};
console
.
log
(
"提交的xmndjh(完整字段):"
,
submitData
.
xmndjh
);
proxy
.
$post
({
url
:
url
,
...
...
@@ -1444,14 +1431,16 @@ const backClick = () => router.back(-1);
const
tableCellStyle
=
({
row
})
=>
row
.
isTotal
?
{
background
:
"#f5f7fa"
,
fontWeight
:
"bold"
}
:
{};
// ========== 页面初始化:
两个表格分别独立处理
==========
// ========== 页面初始化:
新增/编辑分离,不覆盖接口数据
==========
onMounted
(()
=>
{
getProjectData
();
if
(
rcCgqyglId
.
value
)
{
// 编辑/预览:从接口获取完整数据,不执行本地初始化
setTimeout
(()
=>
getJsqtzjcDetail
(),
100
);
}
else
{
// 新增模式:仅生成一次时间,两个表格分别独立初始化
generateDynamicTime
();
// 新增模式:分别初始化两个表格,互不影响
generateDynamicTime
();
// 可研表格时间
initAnnualPlanTable
();
// 年度计划表格初始化
}
});
</
script
>
...
...
src/views/everydayPage/annualPlan.vue
View file @
0380bcfc
...
...
@@ -105,10 +105,10 @@
</table>
</div>
<!-- 右侧:Element 时间输入表格(绑定
父组件传递的数据源+动态时间
) -->
<!-- 右侧:Element 时间输入表格(绑定
**内部响应式数据**,不再直接绑props
) -->
<div
class=
"right-table"
>
<el-table
:data=
"
modelValue
"
:data=
"
tableData
"
style=
"width: 100%"
border
:cell-style=
"tableCellStyle"
...
...
@@ -116,7 +116,7 @@
:header-row-style="{ height: '48px' }"
>
<el-table-column
v-for=
"time in
d
ynamicTimeList"
v-for=
"time in
validD
ynamicTimeList"
:key=
"time"
:label=
"time"
width=
"160"
...
...
@@ -128,7 +128,7 @@
:min=
"0"
:precision=
"2"
controls-position=
"right"
@
change=
"handleChange(row)"
@
change=
"
() =>
handleChange(row)"
:disabled=
"isPreview"
style=
"width: 100%"
/>
...
...
@@ -140,165 +140,171 @@
</template>
<
script
setup
>
import
{
defineProps
,
computed
,
defineEmits
,
watch
,
onMounted
}
from
"vue"
;
// 修复1:删除错误的deepClone导入,Vue无此API
import
{
defineProps
,
computed
,
defineEmits
,
watch
,
onMounted
,
ref
}
from
"vue"
;
// 1. 定义props和emit(保持不变)
const
props
=
defineProps
({
modelValue
:
{
type
:
Array
,
default
:
()
=>
[]
},
dynamicTimeList
:
{
type
:
Array
,
default
:
()
=>
[]
},
isPreview
:
{
type
:
Boolean
,
default
:
false
},
});
// ✅ 新增1:处理时间列表 - 去重+非空,避免异常列,兼容所有自定义字符串字段
const
emit
=
defineEmits
([
"update:modelValue"
,
// v-model双向绑定
"handleAnnualPlanChange"
,
// 原有业务事件
]);
// 2. 核心:组件内部响应式数据,隔离props循环
const
tableData
=
ref
([]);
// 3. 处理时间列表 - 去重+非空+去除隐形空白字符(保持原有逻辑)
const
validDynamicTimeList
=
computed
(()
=>
{
return
[...
new
Set
(
props
.
dynamicTimeList
)].
filter
(
(
time
)
=>
!!
time
&&
typeof
time
===
"string"
,
);
return
[...
new
Set
(
props
.
dynamicTimeList
)]
.
map
((
time
)
=>
{
return
typeof
time
===
"string"
?
time
.
trim
()
:
""
;
})
.
filter
((
time
)
=>
!!
time
);
});
//
✅ 新增2:核心方法 - 初始化行的时间字段,无值则补0(解决2025及以前无默认值问题)
//
工具方法:初始化行的时间字段,无值则补0【仅赋值年份字段,不碰其他字段】
const
initRowTimeField
=
(
row
)
=>
{
if
(
!
row
)
return
;
if
(
!
row
||
typeof
row
!==
"object"
)
return
;
validDynamicTimeList
.
value
.
forEach
((
time
)
=>
{
// 字段不存在/非数字,自动补0,确保v-model绑定有效
if
(
row
[
time
]
===
undefined
||
isNaN
(
Number
(
row
[
time
])))
{
// 仅对年份字段做处理,其他字段完全不碰
if
(
row
[
time
]
===
undefined
||
row
[
time
]
===
null
||
isNaN
(
Number
(
row
[
time
]))
)
{
row
[
time
]
=
0
;
}
else
{
// 确保是数字类型,不修改原有值
row
[
time
]
=
Number
(
row
[
time
]);
}
});
};
//
✅ 新增3:核心方法 - 计算行合计(基于处理后的时间列表)
//
工具方法:计算行合计【仅读取年份字段,不碰其他字段】
const
calcRowTotal
=
(
row
)
=>
{
if
(
!
row
)
return
0
;
if
(
!
row
||
typeof
row
!==
"object"
)
return
0
;
return
validDynamicTimeList
.
value
.
reduce
((
sum
,
time
)
=>
{
return
sum
+
(
Number
(
row
[
time
])
||
0
);
},
0
);
};
// 监听数据源变化 - 优化:新增字段初始化+容错
// 工具方法:深拷贝+数据处理【强制保留所有原有字段,仅新增/更新total、处理年份】
const
handleTableData
=
(
sourceData
)
=>
{
if
(
!
Array
.
isArray
(
sourceData
)
||
sourceData
.
length
===
0
)
return
[];
// 深拷贝:保留原始数据的所有字段(id、名称等)
const
newData
=
JSON
.
parse
(
JSON
.
stringify
(
sourceData
));
newData
.
forEach
((
row
)
=>
{
initRowTimeField
(
row
);
// 仅处理年份字段
row
.
total
=
calcRowTotal
(
row
);
// 新增/更新total字段,不删除其他字段
});
return
newData
;
};
// 监听props.modelValue变化,仅同步到内部tableData,不emit【打印日志调试字段是否丢失】
watch
(
()
=>
props
.
modelValue
,
(
newVal
)
=>
{
if
(
newVal
.
length
===
0
)
return
;
const
newData
=
[...
newVal
];
// 深拷贝避免响应式引用问题
newData
.
forEach
((
row
)
=>
{
if
(
row
)
{
initRowTimeField
(
row
);
// ✅ 新增:初始化字段(补0)
row
.
total
=
calcRowTotal
(
row
);
// 改用统一的合计方法
}
});
emit
(
"update:modelValue"
,
newData
);
// 同步父组件
console
.
log
(
"父组件传递的原始modelValue:"
,
newVal
);
// 调试:看父组件传的是否有id/名称字段
if
(
newVal
.
length
>
0
)
{
console
.
log
(
"父组件传的第一行字段:"
,
Object
.
keys
(
newVal
[
0
]));
// 关键调试:看是否丢失字段
}
const
newData
=
handleTableData
(
newVal
);
tableData
.
value
=
newData
;
// 只更新内部数据,不emit
},
{
deep
:
true
,
immediate
:
true
},
);
//
✅ 新增4:监听时间列表变化 - 新增/修改2025及以前这类字段时,自动初始化所有行
//
监听时间列表变化,更新内部数据后统一emit【保留所有字段emit】
watch
(
()
=>
validDynamicTimeList
.
value
,
()
=>
{
if
(
props
.
modelValue
.
length
===
0
)
return
;
const
newData
=
[...
props
.
modelValue
];
newData
.
forEach
((
row
)
=>
{
initRowTimeField
(
row
);
row
.
total
=
calcRowTotal
(
row
);
});
emit
(
"update:modelValue"
,
newData
);
emit
(
"handleAnnualPlanChange"
,
newData
);
if
(
tableData
.
value
.
length
===
0
)
return
;
const
newData
=
handleTableData
(
tableData
.
value
);
tableData
.
value
=
newData
;
emitDataChange
(
newData
);
},
{
deep
:
true
,
immediate
:
true
},
);
// 获取对应行的合计值 - 优化:增加数字校验,避免报错
// 工具方法:数据变化校验+深拷贝emit【完整保留所有字段emit给父组件】
const
emitDataChange
=
(
newData
)
=>
{
if
(
props
.
isPreview
)
return
;
const
emitData
=
JSON
.
parse
(
JSON
.
stringify
(
newData
));
// 深拷贝保留所有字段
emit
(
"update:modelValue"
,
emitData
);
emit
(
"handleAnnualPlanChange"
,
emitData
);
console
.
log
(
"子组件emit的完整数据:"
,
emitData
);
// 调试:看emit的是否有所有字段
};
// 7. 获取对应行的合计值 - 从内部tableData取值
const
getRowTotal
=
(
index
)
=>
{
const
row
=
props
.
modelV
alue
[
index
-
1
];
const
row
=
tableData
.
v
alue
[
index
-
1
];
if
(
!
row
||
isNaN
(
Number
(
row
.
total
)))
return
"0.00"
;
return
Number
(
row
.
total
).
toFixed
(
2
);
};
onMounted
(()
=>
{
console
.
log
(
"兼容后的时间列表:"
,
validDynamicTimeList
.
value
);
});
// ✅ 修复5:handleChange - 输入后同步父组件,左侧合计实时刷新(核心!)
// 8. 输入变化处理 - 更新内部数据后emit【仅更新年份/total,保留其他字段】
const
handleChange
=
(
row
)
=>
{
if
(
props
.
isPreview
||
!
row
)
return
;
row
.
total
=
calcRowTotal
(
row
);
// 计算合计
const
newData
=
[...
props
.
modelValue
];
// 深拷贝
// 触发双向绑定+原有事件,保证兼容
emit
(
"update:modelValue"
,
newData
);
emit
(
"handleAnnualPlanChange"
,
newData
);
if
(
props
.
isPreview
||
!
row
||
typeof
row
!==
"object"
)
return
;
row
.
total
=
calcRowTotal
(
row
);
// 仅更新total
emitDataChange
(
tableData
.
value
);
// 完整emit所有字段
};
// 🔥 核心:触发双向绑定更新+原有事件兼容
const
emit
=
defineEmits
([
"update:modelValue"
,
// v-model必须的更新事件,同步数据给父组件
"handleAnnualPlanChange"
,
// 保留原有事件,兼容父组件逻辑
]);
// 监听数据源变化(父组件回填时触发),确保合计值正确
watch
(
()
=>
props
.
modelValue
,
(
newVal
)
=>
{
if
(
newVal
.
length
===
0
)
return
;
// 父组件回填数据后,重新计算所有行合计
newVal
.
forEach
((
row
)
=>
{
if
(
row
)
{
row
.
total
=
props
.
dynamicTimeList
.
reduce
((
sum
,
time
)
=>
{
return
sum
+
(
Number
(
row
[
time
])
||
0
);
},
0
);
}
});
// 同步更新父组件数据
emit
(
"update:modelValue"
,
newVal
);
},
{
deep
:
true
,
immediate
:
true
},
);
// 获取对应行的合计值(父组件数据源,保留2位小数)
// 合计行样式(和父组件表格样式统一)
// 合计行样式(保持不变)
const
tableCellStyle
=
({
row
})
=>
row
.
isTotal
?
{
background
:
"#f5f7fa"
,
fontWeight
:
"bold"
}
:
{};
row
?.
isTotal
?
{
background
:
"#f5f7fa"
,
fontWeight
:
"bold"
}
:
{};
// 调试日志(打印内部数据字段,确认是否保留)
onMounted
(()
=>
{
console
.
log
(
"处理后的有效时间字段:"
,
validDynamicTimeList
.
value
);
console
.
log
(
"子组件内部tableData:"
,
tableData
.
value
);
if
(
tableData
.
value
.
length
>
0
)
{
console
.
log
(
"子组件tableData第一行所有字段:"
,
Object
.
keys
(
tableData
.
value
[
0
]),
);
}
});
</
script
>
<
style
scoped
lang=
"scss"
>
//
核心:左右布局样式
//
所有样式保持不变
.annual-plan-wrap
{
display
:
flex
;
align-items
:
flex-start
;
gap
:
0
;
// 左右无缝衔接,视觉成一个整体
gap
:
0
;
width
:
100%
;
box-sizing
:
border-box
;
padding
:
0
;
margin
:
0
;
overflow-x
:
auto
;
// 整体横向滚动,避免左右分离滚动
overflow-x
:
auto
;
}
// 左侧原生表格容器:固定宽度不挤压
.left-table
{
flex-shrink
:
0
;
}
// 左侧原生表格样式(160px列宽 + 48px行高 + 无缝边框)
.investment-table
{
width
:
fit-content
;
border-collapse
:
collapse
;
border
:
1px
solid
#ebeef5
;
// 和父组件表格边框色统一
border-right
:
0
;
// 取消右侧边框,和右侧EL表格无缝衔接
border
:
1px
solid
#ebeef5
;
border-right
:
0
;
}
.investment-table
td
{
width
:
160px
;
// 和右侧EL表格列宽完全统一
height
:
49px
;
// 匹配原有行高,避免错位
border
:
1px
solid
#ebeef5
;
// 和父组件表格边框色统一
padding
:
0
!
important
;
// 取消内边距,文字居中更整齐
width
:
160px
;
height
:
49px
;
border
:
1px
solid
#ebeef5
;
padding
:
0
!
important
;
text-align
:
center
;
vertical-align
:
middle
;
word-break
:
break-all
;
box-sizing
:
border-box
;
}
// 左侧表格层级背景色(保留原有样式)
.first-col
{
font-weight
:
bold
;
background-color
:
#f5f7fa
;
// 和父组件合计行背景统一
background-color
:
#f5f7fa
;
}
.second-col
{
background-color
:
#f5f7fa
;
...
...
@@ -307,42 +313,32 @@ const tableCellStyle = ({ row }) =>
font-weight
:
500
;
background-color
:
#f5f7fa
;
}
// 右侧EL表格样式优化(和父组件完全统一:行高/边框/内边距)
:deep
(
.el-table
)
{
--el-table-border-color
:
#ebeef5
;
// 边框色和父组件表格一致
--el-table-row-height
:
48px
!
important
;
// 行高和左侧强制统一
border-left
:
0
;
// 取消左侧边框,和左侧无缝衔接
--el-table-border-color
:
#ebeef5
;
--el-table-row-height
:
48px
!
important
;
border-left
:
0
;
}
// 表头行高也强制48px,避免表头错位
:deep
(
.el-table__header
tr
),
:deep
(
.el-table__body
tr
)
{
height
:
48px
!
important
;
}
:deep
(
.el-table-cell
)
{
padding
:
0
!
important
;
// 内边距和左侧一致
padding
:
0
!
important
;
text-align
:
center
;
vertical-align
:
middle
;
box-sizing
:
border-box
;
}
// 输入框适配行高,居中显示
:deep
(
.el-input-number
)
{
width
:
100%
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
}
:deep
(
.el-input-number__input
)
{
text-align
:
right
;
padding-right
:
25px
;
width
:
90%
;
// 微调宽度,避免输入框溢出
width
:
90%
;
}
// 去除多余样式
.flex
{
display
:
flex
;
}
...
...
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