549 lines
20 KiB
Vue
549 lines
20 KiB
Vue
<template>
|
||
<div style="flex: 1">
|
||
<Container name="预算填报详情" icon="cockpitItemIcon" size="indicatorDetailsBg" topSize="indicatorDetailsTitleBg">
|
||
<div class="kpi-content" style="padding: 14px 14px; display: flex;flex-direction: column; width: 100%;">
|
||
<!-- 查询表单区域 -->
|
||
<div class="bottom"
|
||
style="display: flex;gap: 8px; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 64px;padding: 16px 16px;">
|
||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;margin-top: 10px;"></div>
|
||
<el-form :inline="true" :model="form" class="demo-form-inline">
|
||
<el-form-item label="所属层级">
|
||
<el-select v-model="form.levelId" placeholder="请选择">
|
||
<el-option v-for="item in levelLList" :key="item.id" :label="item.name" :value="item.id">
|
||
</el-option>
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<el-form-item :label="timeType === 'month' ? '填报月份' : '填报年份'">
|
||
<!-- 根据timeType切换日期选择器类型:月/年 -->
|
||
<el-date-picker v-model="form.date" :type="timeType" :placeholder="timeType === 'month' ? '选择月' : '选择年'"
|
||
@change="handleDateChange">
|
||
</el-date-picker>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button style="background-color: #0B58FF;" type="primary" @click="onSubmit">查询</el-button>
|
||
<el-button type="primary" plain size="medium" @click='importExcel'>导入</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</div>
|
||
|
||
<!-- 表格操作区域 + 表格区域 -->
|
||
<div class="bottom"
|
||
style="display: flex; width: 100%;margin-top: 8px;background-color: rgba(249, 252, 255, 1);height: 772px;padding: 16px 16px;flex-direction: column; gap: 8px;">
|
||
|
||
<!-- 只读模式:显示编辑按钮 -->
|
||
<div v-if="!isDetail" style="display: flex;gap: 8px;align-items: center;height: 32px;">
|
||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;"></div>
|
||
<div style="width: 58px;
|
||
height: 16px;
|
||
font-family: PingFangSC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 14px;
|
||
color: rgba(0,0,0,0.85);
|
||
line-height: 16px;
|
||
text-align: right;
|
||
font-style: normal;">指标详情</div>
|
||
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
||
@click="handleEdit">编辑</el-button>
|
||
</div>
|
||
|
||
<!-- 编辑模式:显示快捷操作按钮 -->
|
||
<div v-if="isDetail" style="display: flex;gap: 8px;align-items: center;height: 32px;">
|
||
<div style="width: 4px;height: 16px;background: #0B58FF;border-radius: 1px;"></div>
|
||
<div
|
||
style="width: 58px;height: 16px;font-family: PingFangSC, PingFang SC;font-weight: 400;font-size: 14px;color: rgba(0,0,0,0.85);line-height: 16px;text-align: right;font-style: normal;">
|
||
快捷操作</div>
|
||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||
@click="copyLastMonth">复制上月/年</el-button>
|
||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||
@click="allUp">全部上调5%</el-button>
|
||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||
@click="allDown">全部下调5%</el-button>
|
||
<el-button style="height: 32px;line-height: 10px;" type="primary" plain size="medium"
|
||
@click="handleClear">清空配置</el-button>
|
||
<el-button style="background-color: #0B58FF;height: 32px;line-height: 10px;" type="primary"
|
||
@click="handleSave">保存</el-button>
|
||
<el-button text style="height: 32px;line-height: 10px;" plain size="medium"
|
||
@click="handleCancel">取消</el-button>
|
||
</div>
|
||
|
||
<!-- 表格组件:添加key属性强制刷新,绑定所有必要属性 -->
|
||
<base-table style="height: 700px;" :maxHeight=" '700' " @emitFun="inputChange" class="right-aside"
|
||
:table-props="tableProps" :page="form.pageNo" :limit="form.pageSize" :table-data="tableData" ref="baseTable" id='calendarTable'
|
||
:key="tableKey"></base-table>
|
||
</div>
|
||
</div>
|
||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
|
||
<el-upload ref="upload" :limit="1" accept=".xlsx, .xls" action="#" :disabled="upload.isUploading"
|
||
:on-change="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag>
|
||
<i class="el-icon-upload"></i>
|
||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||
<div class="el-upload__tip text-center" slot="tip">
|
||
<span>仅允许导入xls、xlsx格式文件。</span>
|
||
</div>
|
||
<div class="el-upload__tip" slot="tip">
|
||
<el-radio-group v-model="upload.timeDim">
|
||
<el-radio :label="2">月预算</el-radio>
|
||
<el-radio :label="3">年预算</el-radio>
|
||
</el-radio-group>
|
||
</div>
|
||
</el-upload>
|
||
<div slot="footer" class="dialog-footer">
|
||
<el-button type="primary" @click="submitFileForm">确 定</el-button>
|
||
<el-button @click="upload.open = false">取 消</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</Container>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import Container from './container.vue'
|
||
import { getLevelStruc, getTargetMonthPage, updateTargetMonthData, getTargetYearPage, updateTargetYearData,copyLastMonthData, copyLastYearData} from '@/api/cockpit'
|
||
import inputArea from './inputArea.vue' // 导入输入组件
|
||
import { publicFormatter } from '@/utils/dict';
|
||
import {getAccessToken, getTenantId} from '@/utils/auth'
|
||
import axios from 'axios';
|
||
export default {
|
||
name: 'ProductionStatus',
|
||
components: {
|
||
Container,
|
||
inputArea // 注册输入组件
|
||
},
|
||
props: {
|
||
timeType: {
|
||
type: String,
|
||
default: 'month', // 默认月份维度
|
||
// validator: (val) => ['month', 'year'].includes(val) // 校验传入值只能是month/year
|
||
}
|
||
},
|
||
data() {
|
||
return {
|
||
form: {
|
||
levelId: undefined,
|
||
pageNo: 1,
|
||
pageSize: 100,
|
||
date: undefined, // 统一存储日期(月份/年份)
|
||
startTime: undefined, // 起始时间戳
|
||
endTime: undefined // 结束时间戳
|
||
},
|
||
upload: {
|
||
// 是否显示弹出层
|
||
open: false,
|
||
// 弹出层标题
|
||
title: "预算填报导入",
|
||
// 是否禁用上传
|
||
isUploading: false,
|
||
fileList:[],
|
||
currentFile:null,
|
||
timeDim: 2
|
||
},
|
||
getDataList: null, // 动态切换的查询接口
|
||
updateData: null, // 动态切换的更新接口
|
||
isDetail: false, // 编辑状态标识:false=只读,true=编辑
|
||
tableData: [], // 表格数据
|
||
levelLList: [], // 所属层级下拉数据
|
||
tableProps: [], // 表格列配置
|
||
tableKey:0,// 强制表格更新
|
||
allUpBtn: false, // 全部上调按钮状态
|
||
allDownBtn: false // 全部下调按钮状态
|
||
}
|
||
},
|
||
watch: {
|
||
// 监听timeType变化,动态切换接口并重新初始化
|
||
timeType: {
|
||
immediate: true, // 首次加载立即执行
|
||
handler(newVal) {
|
||
// 根据timeType切换接口
|
||
if (newVal === 'month') {
|
||
this.getDataList = getTargetMonthPage;
|
||
this.updateData = updateTargetMonthData;
|
||
} else if (newVal === 'year') {
|
||
this.getDataList = getTargetYearPage;
|
||
this.updateData = updateTargetYearData;
|
||
}
|
||
// 重新初始化日期和时间戳
|
||
this.initDefaultDate();
|
||
this.calculateTimeStamp();
|
||
// 重新初始化表格配置
|
||
this.initTableProps(this.isDetail);
|
||
// 重新请求数据
|
||
this.$nextTick(() => {
|
||
this.getData();
|
||
});
|
||
}
|
||
},
|
||
// 监听isDetail变化,确保配置同步(双重保障)
|
||
isDetail(newVal) {
|
||
this.initTableProps(newVal);
|
||
}
|
||
},
|
||
mounted() {
|
||
// 1. 初始化默认日期
|
||
this.initDefaultDate();
|
||
// 2. 计算对应时间戳
|
||
this.calculateTimeStamp();
|
||
// 3. 先初始化表格配置(优先于数据请求,避免表格空配置渲染)
|
||
this.initTableProps(this.isDetail);
|
||
// 4. 等待配置就绪后,再请求数据,避免异步冲突
|
||
this.$nextTick(() => {
|
||
this.getData();
|
||
});
|
||
},
|
||
methods: {
|
||
// 清空配置
|
||
handleClear() {
|
||
// 清空target
|
||
this.tableData.forEach(item => {
|
||
item.target = null;
|
||
})
|
||
this.tableKey++;
|
||
this.allDownBtn = false;
|
||
this.allUpBtn = false;
|
||
},
|
||
//复制上月/上年数据(调用接口不同)
|
||
copyLastMonth() {
|
||
if(this.timeType === 'month') {
|
||
this.$modal.confirm('是否确认复制上月数据?').then(() => {
|
||
this.copyLastMonthDataPage()
|
||
}).then(() => {
|
||
this.$modal.msgSuccess("复制成功");
|
||
}).catch(() => { });
|
||
}else{
|
||
this.$modal.confirm('是否确认复制上年数据?').then(() => {
|
||
this.copyLastYearDataPage()
|
||
}).then(() => {
|
||
this.$modal.msgSuccess("复制成功");
|
||
}).catch(() => { });
|
||
}
|
||
},
|
||
copyLastMonthDataPage() {
|
||
copyLastMonthData({
|
||
levelId: this.form.levelId,
|
||
startTime: this.form.startTime,
|
||
endTime: this.form.endTime,
|
||
pageSize: this.form.pageSize,
|
||
pageNo: this.form.pageNo
|
||
}).then((res) => {
|
||
this.tableData = res.data.map(item => {
|
||
// 新增unitLabel字段,存储匹配后的显示名称
|
||
return {
|
||
...item
|
||
};
|
||
});
|
||
this.tableKey++;
|
||
this.allDownBtn = false;
|
||
this.allUpBtn = false;
|
||
}).catch(err => {
|
||
console.error('获取表格数据失败:', err);
|
||
this.tableData = [];
|
||
});
|
||
},
|
||
copyLastYearDataPage() {
|
||
copyLastYearData({
|
||
levelId: this.form.levelId,
|
||
startTime: this.form.startTime,
|
||
endTime: this.form.endTime,
|
||
pageSize: this.form.pageSize,
|
||
pageNo: this.form.pageNo
|
||
}).then((res) => {
|
||
this.tableData = res.data.map(item => {
|
||
// 新增unitLabel字段,存储匹配后的显示名称
|
||
return {
|
||
...item
|
||
};
|
||
});
|
||
this.tableKey++;
|
||
this.allDownBtn = false;
|
||
this.allUpBtn = false;
|
||
}).catch(err => {
|
||
console.error('获取表格数据失败:', err);
|
||
this.tableData = [];
|
||
});
|
||
},
|
||
//全部上调5%
|
||
allUp() {
|
||
if(this.allUpBtn || this.allDownBtn) {
|
||
this.$modal.msgWarning("数据已调整,请先保存数据");
|
||
return
|
||
}
|
||
for(let i = 0; i < this.tableData.length; i++) {
|
||
this.tableData[i].target = (this.tableData[i].target * 1.05).toFixed(2)
|
||
}
|
||
// 强制表格组件刷新
|
||
this.tableKey++;
|
||
this.allUpBtn = true;
|
||
},
|
||
// 全部下调5%
|
||
allDown() {
|
||
if(this.allUpBtn || this.allDownBtn) {
|
||
this.$modal.msgWarning("数据已调整,请先保存数据");
|
||
return;
|
||
}
|
||
for(let i = 0; i < this.tableData.length; i++) {
|
||
this.tableData[i].target = (this.tableData[i].target * 0.95).toFixed(2)
|
||
}
|
||
// 强制表格组件刷新
|
||
this.tableKey++;
|
||
this.allDownBtn = true;
|
||
},
|
||
// 查询按钮
|
||
onSubmit() {
|
||
// 清空原有表格数据,重新请求
|
||
this.tableData = [];
|
||
this.$nextTick(() => {
|
||
this.getDataPage();
|
||
});
|
||
},
|
||
// 切换到编辑模式
|
||
handleEdit() {
|
||
this.isDetail = true;
|
||
// 先更新表格配置,再强制表格刷新
|
||
this.initTableProps(this.isDetail);
|
||
this.tableKey++;
|
||
},
|
||
// 保存数据(使用动态切换的updateData接口)
|
||
handleSave() {
|
||
this.$modal.confirm('是否确认保存数据?').then(() => {
|
||
return this.updateData(this.tableData)
|
||
}).then(() => {
|
||
this.isDetail = false;
|
||
// 重置表格配置
|
||
this.initTableProps(this.isDetail);
|
||
// 清空并重新请求数据,恢复原始状态
|
||
this.$nextTick(() => {
|
||
this.tableData = [];
|
||
this.getData();
|
||
});
|
||
this.$modal.msgSuccess("保存成功");
|
||
}).catch(() => { });
|
||
},
|
||
// 请求下拉数据和表格数据(使用动态切换的getDataList接口)
|
||
getData() {
|
||
if (!this.getDataList) {
|
||
console.warn('当前时间维度异常,无法获取数据');
|
||
return;
|
||
}
|
||
// 1. 请求所属层级下拉数据
|
||
getLevelStruc().then((res) => {
|
||
this.levelLList = res.data || [];
|
||
this.form.levelId = this.levelLList[0].id;
|
||
this.getDataPage()
|
||
}).catch(err => {
|
||
console.error('获取所属层级失败:', err);
|
||
this.levelLList = [];
|
||
});
|
||
this.$emit('updateLeft')
|
||
},
|
||
getDataPage() {
|
||
// 2. 请求表格分页数据(使用动态接口)
|
||
this.getDataList({
|
||
levelId: this.form.levelId,
|
||
startTime: this.form.startTime,
|
||
endTime: this.form.endTime,
|
||
pageSize: this.form.pageSize,
|
||
pageNo: this.form.pageNo
|
||
}).then((res) => {
|
||
console.log('表格数据:', res);
|
||
this.tableData = res.data.map(item => {
|
||
// 新增unitLabel字段,存储匹配后的显示名称
|
||
return {
|
||
...item
|
||
};
|
||
});
|
||
}).catch(err => {
|
||
console.error('获取表格数据失败:', err);
|
||
this.tableData = [];
|
||
});
|
||
},
|
||
// 表格单元格数据变更回调
|
||
inputChange(val) {
|
||
// 安全修改:判断索引是否存在,避免数组越界
|
||
if (this.tableData[val._pageIndex - 1]) {
|
||
this.tableData[val._pageIndex - 1][val.prop] = val[val.prop];
|
||
// 标记数据为已修改状态
|
||
this.tableData[val._pageIndex - 1].status = 1;
|
||
}
|
||
},
|
||
// 取消编辑,恢复只读模式
|
||
handleCancel() {
|
||
this.isDetail = false;
|
||
this.allUpBtn = false;
|
||
this.allDownBtn = false;
|
||
// 重置表格配置
|
||
this.initTableProps(this.isDetail);
|
||
// 清空并重新请求数据,恢复原始状态
|
||
this.$nextTick(() => {
|
||
this.tableData = [];
|
||
this.getData();
|
||
});
|
||
},
|
||
// 初始化表格列配置(核心:精准控制inputArea挂载)
|
||
initTableProps(isEdit) {
|
||
console.log('当前编辑状态:', isEdit, '当前时间维度:', this.timeType);
|
||
// 基础表格列配置(只读模式使用)
|
||
const baseTableProps = [
|
||
{ prop: 'type', label: '指标类型', align: 'center' },
|
||
{ prop: 'name', label: '指标名称', align: 'center' },
|
||
{ prop: 'unit', label: '单位', align: 'center', filter: publicFormatter('lb_dw') },
|
||
{ prop: 'target', label: '预估值', align: 'center' },
|
||
];
|
||
if (isEdit) {
|
||
// 编辑模式:仅给「预估值」列添加inputArea(精准挂载,避免无效配置)
|
||
this.tableProps = baseTableProps.map(item => {
|
||
if (item.prop === 'target') { // 只给需要编辑的列添加子组件
|
||
return {
|
||
...item,
|
||
subcomponent: inputArea // 挂载输入组件
|
||
};
|
||
}
|
||
return item; // 其他列保持原有配置
|
||
});
|
||
} else {
|
||
// 只读模式:深拷贝基础配置,避免引用污染
|
||
// this.tableProps = JSON.parse(JSON.stringify(baseTableProps));
|
||
this.tableProps = baseTableProps;
|
||
}
|
||
console.log('表格配置:', this.tableProps);
|
||
},
|
||
// 初始化默认日期(根据timeType)
|
||
initDefaultDate() {
|
||
const currentDate = new Date();
|
||
this.form.date = currentDate;
|
||
},
|
||
// 计算时间戳(根据timeType切换:月/年)
|
||
calculateTimeStamp(selectDate) {
|
||
let targetDate = selectDate || this.form.date;
|
||
if (!targetDate) {
|
||
targetDate = new Date();
|
||
} else {
|
||
targetDate = new Date(targetDate);
|
||
}
|
||
|
||
const year = targetDate.getFullYear();
|
||
let month = targetDate.getMonth(); // 月份0-11
|
||
|
||
if (this.timeType === 'month') {
|
||
// 月维度:当月1号 00:00:00 → 当月最后一天 23:59:59
|
||
const startDate = new Date(year, month, 1, 0, 0, 0);
|
||
this.form.startTime = startDate.getTime();
|
||
|
||
const lastDay = new Date(year, month + 1, 0).getDate();
|
||
const endDatePrecise = new Date(year, month, lastDay, 23, 59, 59);
|
||
this.form.endTime = endDatePrecise.getTime();
|
||
} else if (this.timeType === 'year') {
|
||
// 年维度:当年1月1号 00:00:00 → 当年12月31号 23:59:59
|
||
const startDate = new Date(year, 0, 1, 0, 0, 0);
|
||
this.form.startTime = startDate.getTime();
|
||
|
||
const endDatePrecise = new Date(year, 11, 31, 23, 59, 59);
|
||
this.form.endTime = endDatePrecise.getTime();
|
||
}
|
||
},
|
||
// 日期选择器变更事件(统一处理月/年变更)
|
||
handleDateChange(val) {
|
||
if (!val) {
|
||
// 清空选择时,重置时间戳
|
||
this.form.startTime = undefined;
|
||
this.form.endTime = undefined;
|
||
return;
|
||
}
|
||
// 计算选中日期对应的时间戳
|
||
this.calculateTimeStamp(val);
|
||
},
|
||
// 导入
|
||
importExcel() {
|
||
this.upload.open = true
|
||
},
|
||
// 文件上传中处理
|
||
handleFileUploadProgress(file, fileList) {
|
||
console.log('文件上传中:',file, fileList)
|
||
this.upload.isUploading = true;
|
||
this.upload.fileList = fileList;
|
||
this.upload.currentFile = file.raw;
|
||
},
|
||
handleFileSuccess() {},
|
||
importTemplate() {},
|
||
// 提交上传文件
|
||
async submitFileForm() {
|
||
try {
|
||
if (!this.upload.currentFile) {
|
||
return this.$message.error('请先选择要上传的文件!')
|
||
}
|
||
const formData = new FormData()
|
||
formData.append('file', this.upload.currentFile) // 文件字段
|
||
formData.append('timeDim', this.upload.timeDim) // 年月维度字段
|
||
formData.append('reportDate', this.form.endTime) // 时间维度字段
|
||
formData.append('levelId', this.form.levelId) // 层级维度字段
|
||
const response = await axios({
|
||
url: process.env.VUE_APP_BASE_API + '/admin-api/lb/index-target-month/import',
|
||
method: 'post',
|
||
data: formData,
|
||
headers: {
|
||
'Content-Type': 'multipart/form-data',
|
||
'Authorization': "Bearer " + getAccessToken(),
|
||
'tenant-id': getTenantId(),
|
||
},
|
||
timeout: 30000
|
||
})
|
||
// 4. 处理响应结果
|
||
if (response.data.code === 0) {
|
||
this.$message.success('文件上传成功!')
|
||
// 重置表单
|
||
this.upload.fileList = []
|
||
this.upload.timeDim = 2
|
||
this.upload.currentFile = null
|
||
this.upload.open = false
|
||
this.upload.isUploading = false
|
||
this.$refs.upload.clearFiles();
|
||
this.getData()
|
||
} else {
|
||
this.$message.error(`上传失败:${response.data.msg || '未知错误'}`)
|
||
}
|
||
} catch (error) {
|
||
// 5. 异常处理
|
||
console.error('文件上传出错:', error)
|
||
this.$message.error('上传失败!')
|
||
}
|
||
},
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang='scss' scoped>
|
||
// 月份列表容器样式(保留原有配置,若无使用可忽略)
|
||
.month-list {
|
||
// 内联样式已优化行间距,此处可留空或补充
|
||
}
|
||
|
||
// 基础月份样式(保留原有配置,若无使用可忽略)
|
||
.monthItem {
|
||
width: 164px;
|
||
height: 42px;
|
||
border-radius: 4px;
|
||
font-family: PingFangSC, PingFang SC;
|
||
font-weight: 400;
|
||
font-size: 20px;
|
||
color: rgba(0, 0, 0, 0.85);
|
||
line-height: 42px;
|
||
text-align: center;
|
||
font-style: normal;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
border: 2px solid transparent;
|
||
margin: 0;
|
||
}
|
||
|
||
.monthItem.has-data {
|
||
background-color: #D1E8FF;
|
||
}
|
||
|
||
.monthItem:not(.has-data) {
|
||
background-color: #EFF3F8;
|
||
}
|
||
|
||
.monthItem.active {
|
||
border: 2px solid #0B58FF !important;
|
||
}
|
||
</style>
|