<template>
<view class="page">
<!-- 顶部标签栏 -->
<u-sticky bgColor="#fff">
<u-tabs
:list="list"
@click="handleTabClick"
lineColor="#3849B4"
lineWidth="80rpx"
lineHeight="7rpx"
:activeStyle="{
color: '#3849B4',
fontWeight: 'bold',
fontSize: '28rpx',
}"
:inactiveStyle="{
color: '#999999',
fontSize: '28rpx',
}"
itemStyle="margin-bottom:5rpx;"
class="uTabs"
></u-tabs>
</u-sticky>
<!-- 无数据图片 -->
<view class="noDataBox" v-if="showNoDataImg">
<view class="noDataImg"></view>
<view class="noDataText"> 近5年暂无数据~ </view>
</view>
<!-- 定期报告 -->
<scroll-view
:scroll-x="true"
:scroll-y="true"
class="contentBox"
:style="contentStyle"
v-if="!showNoDataImg"
>
<!-- 顶部 -->
<view class="topHeader">
<!-- 顶部左侧 -->
<view class="top-fixed-column"></view>
<view class="top-fixed-header">
<!-- 顶部右侧时间列表 -->
<view
class="item w-268"
v-for="(item, index) in nodeLabelArr"
:key="index"
>
<view class="itemText">
{{ item }}
</view>
</view>
</view>
</view>
<view class="content-container" :style="containerStyle">
<!-- 右侧数据区域 -->
<view class="right-content">
<TableRowItem
v-for="item in liabilitiesColumn"
:key="item.id"
:item="item"
:table-data="reportList"
/>
</view>
</view>
</scroll-view>
<!-- 报表类型 -->
<view class="bottomBox">
<view class="reportTypeList">
<view
class="reportTypeBtn"
v-for="(item1, index1) in reportTypeList"
:key="index1"
:class="reportActive == index1 ? 'reportActive' : ''"
@click="reportTypeClick(item1, index1)"
>
{{ item1.label }}
</view>
</view>
</view>
</view>
</template>
<script>
import {
queryStatementNav,
queryReportNode,
queryReportList,
} from "@/api/financial.js"; // 后端接口
import { deepClone } from "@/common/common.js";
import TableRowItem from "./components/TableRowItem.vue";
export default {
components: {
TableRowItem,
},
data() {
this.leftTreeData = {}; // 缓存报表节点数
return {
navTitle: "", // 标题
list: [], // tab栏列表
activeTab: 6, // 点击tab栏的id
companyCode: "", // 公司代码
reportTypeList: [
// 报表类型
{
label: "全部",
value: "4",
},
{
label: "年报",
value: "3",
},
{
label: "三季报",
value: "2",
},
{
label: "中报",
value: "1",
},
{
label: "一季报",
value: "0",
},
],
reportTypeArr: [], // 过滤后的报表类型
reportActive: 0, // 选中报表下标
nodeTypeLabelArr: [], // 定期报告
nodeLabelArr: [], //过滤后的定期报告列表
contentStyle: {
// 内容样式
height: "",
},
liabilitiesColumn: [], // 节点数据
reportList: [], // 报表数据
nodeType: ["3", "2", "1", "0"], // 报告期
rowLength: 0,
fixedWidth: 160,
showNoDataImg: false,
containerStyle: {}, // 内容样式
};
},
onLoad(option) {
this.navTitle = option.title; // 页面标题
const arr = this.navTitle.split(/(|)/g);
this.companyCode = arr[1];
},
mounted() {
this.$nextTick(() => {
this.contentStyle.height = `calc(100% - 84px)`;
});
this.reportTypeArr = this.reportTypeList;
this.getNavList();
},
methods: {
nativeBack() {
uni.navigateBack({
delta: 1, //返回层数,2则上上页
});
},
// 查询导航栏列表
async getNavList() {
let params = {
id: localStorage.getItem("pageId"),
};
let res = await queryStatementNav(params);
if (res.code == 0) {
this.list = res.data[0].children;
this.getFistNode();
this.getReportData();
} else {
uni.$u.toast(res.msg);
}
},
// 获取左侧节点数据
async getFistNode() {
//显示加载框
uni.showLoading({
title: "加载中",
mask: true,
});
let params = {
nodeId: this.activeTab,
};
// 报表第一列指标树缓存
if (this.leftTreeData[this.activeTab]) {
this.liabilitiesColumn = deepClone(this.leftTreeData[this.activeTab]);
return;
}
let res = await queryReportNode(params);
res = res.data || [];
uni.hideLoading();
res.forEach((item) => {
item.level = 1;
if (item.children && item.children.length) {
item.children.forEach((item1) => {
item1.level = 2;
if (item1.children && item1.children.length) {
item1.children.forEach((item2) => {
item2.level = 3;
if (item2.children && item2.children.length) {
item2.children.forEach((item3) => {
item3.level = 4;
});
}
});
}
});
}
});
this.liabilitiesColumn = res;
if (res.length) {
this.leftTreeData[this.activeTab] = deepClone(res);
}
},
// 获取财务报表数据
async getReportData() {
//显示加载框
uni.showLoading({
title: "加载中",
mask: true,
});
let data = {
params: this.handleReportParams().params,
code: this.companyCode,
// code: '000065',
mainNodeId: this.activeTab,
};
const res = await queryReportList(data);
if (res.code == 0) {
if (res.data) {
uni.hideLoading();
// this.reportList = Array.from(res.data)
const { data } = res;
let newArr = [];
let newNodeArr = [];
data.forEach((item, index) => {
if (JSON.stringify(item) != "{}") {
newArr.push(item[index]);
newNodeArr.push(this.nodeTypeLabelArr[index]);
}
});
this.reportList = newArr;
this.nodeLabelArr = newNodeArr;
if (this.nodeLabelArr.length == 0) {
this.showNoDataImg = true;
} else {
this.showNoDataImg = false;
}
} else {
uni.hideLoading();
this.showNoDataImg = true;
}
}
},
// 搜索参数转化
handleReportParams() {
let date = new Date();
let year = date.getFullYear();
const startYear = year - 5;
const endYear = year;
const params = [];
const nodeTypeArr = this.reportTypeArr
.slice(1)
.sort(this.compare("value"));
const { handleSort } = this;
const reportPeriodType = localStorage.getItem("pageId");
this.nodeLabelArr = [];
this.nodeTypeLabelArr = [];
// 报表数据列id(后端给予已有接口返回数据为对象[key:columnId]:value形式,前端必须保存生成的参数顺序保证渲染匹配)
let columnIndex = 0;
for (let i = +endYear, l = +startYear; i >= l; i--) {
for (let j = nodeTypeArr.length - 1, k = 0; j >= k; j--) {
const { label: reportNodeAlias, value: reportPeriodOption } =
nodeTypeArr[j];
const columnId = `${columnIndex++}`;
params.push({
columnId,
reportPeriodType,
reportPeriodOption,
date: i,
reportType: "10",
reportTypeOption: "A",
});
this.nodeTypeLabelArr.push(`${i}年${reportNodeAlias}`);
}
}
return {
params,
columnIndex, // 记录列的长度
};
},
compare(prop) {
return function (obj1, obj2) {
var val1 = obj1[prop];
var val2 = obj2[prop];
if (val1 < val2) {
return -1;
} else if (val1 > val2) {
return 1;
} else {
return 0;
}
};
},
// 报表类型切换
reportTypeClick(item1, index1) {
this.reportActive = index1;
this.reportTypeArr = [];
this.reportList = [];
if (this.reportActive == 0) {
this.reportTypeArr = this.reportTypeList;
} else {
this.reportTypeArr = [
{
label: "全部",
value: "4",
},
];
let obj = {
label: item1.label,
value: item1.value,
};
this.reportTypeArr.push(obj);
}
this.getReportData();
},
// 导航栏切换
handleTabClick(item) {
//显示加载框
uni.showLoading({
title: "加载中",
mask: true,
});
this.reportTypeArr = [];
this.activeTab = item.id;
this.reportActive = 0;
this.reportTypeArr = this.reportTypeList;
this.getFistNode();
this.getReportData();
},
},
};
</script>
<style lang="scss" scoped>
uni-page-body,
uni-page-refresh {
height: 100%;
overflow: hidden;
}
.page {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
position: relative;
flex-direction: column;
.uTabs {
border-bottom: 1rpx solid #dcdee3;
display: flex;
height: 80rpx;
line-height: 80rpx;
}
.contentBox {
position: relative;
display: flex;
flex-direction: column;
margin-bottom: 100rpx;
font-size: 26rpx;
width: auto;
.topHeader {
height: 100rpx;
flex-shrink: 0;
width: auto;
display: flex;
position: absolute;
.top-fixed-column {
width: 320rpx;
position: sticky;
left: 0;
z-index: 1;
height: 100rpx;
background-color: #fff;
}
.top-fixed-header {
background-color: #fff;
height: 100rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 30rpx 0 0;
}
}
.content-container {
padding: 0 30rpx 0 0;
width: auto;
height: calc(100% - 100rpx);
flex: 1;
display: flex;
position: absolute;
top: 100rpx;
}
}
.bottomBox {
width: 100%;
height: 100rpx;
position: fixed;
bottom: 0;
background: #ffffff;
box-shadow: 0rpx -2rpx 16rpx 0rpx rgba(55, 59, 74, 0.1);
.reportTypeList {
width: 96%;
margin: 17rpx 2% 17rpx;
height: 66rpx;
border: 1rpx solid #dcdee3;
.reportTypeBtn {
height: 100%;
width: 20%;
font-size: 28rpx;
font-weight: 500;
color: #999999;
display: inline-block;
line-height: 66rpx;
text-align: center;
}
.reportActive {
background: #3849b4;
color: #ffffff;
}
}
}
}
.itemText {
height: 100rpx;
line-height: 100rpx;
}
.row {
display: flex;
height: 100rpx;
box-sizing: border-box;
}
.label {
background-color: #ffffff;
}
.c-333 {
color: #333333;
}
.w-268 {
width: 200rpx;
}
.item {
color: #666666;
font-size: 26rpx;
text-align: right;
&.c-333 {
color: #333333;
}
&.right {
text-align: right;
}
}
.noDataBox {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
.noDataImg {
width: 320rpx;
height: 268rpx;
background-image: url("@/static/images/noData.png");
background-size: 100% 100%;
}
.noDataText {
color: #666666;
font-size: 26rpx;
margin-top: 53rpx;
text-align: center;
}
}
.sticky {
position: sticky;
z-index: 1;
}
</style>
TableRowItem子组件代码:
<template>
<view>
<view
:key="item.id"
class="row"
:class="item.level===1?'menu-style':''"
>
<view
:class="['label','item']"
v-if="item">
<view :style="item.level==1 ? {paddingLeft:'12rpx',backgroundColor: '#F5F5F5'} : (item.level==2 ? {paddingLeft:'40rpx'} : (item.level==3 ? {paddingLeft:'68rpx'} : {paddingLeft:'96rpx'}))">
{{ item.name }}
</view>
</view>
<view
v-for="(propItem,index) in tableData"
:key="index"
class="w-268 item right"
>
<view v-if="propItem&&propItem[item.field] == null">
{{'--'}}
</view>
<view v-else>
{{ propItem&&propItem[item.field] | bigNumberTransform }}
</view>
</view>
</view>
<template v-if="item.children&&item.children.length">
<TableRowItem
v-for="subItem in item.children"
:key="subItem.id"
:item="subItem"
:table-data="tableData"
/>
</template>
</view>
</template>
<script>
export default {
name: 'TableRowItem',
props: {
item: {
type: Object,
required: true
},
tableData: {
type: Array,
default: () => []
},
},
data() {
return {
paddingLeft: 0
}
},
mounted() {
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.row {
display: flex;
height: 100rpx;
box-sizing: border-box;
position: sticky;
left: 0;
align-items: center;
}
.label {
background-color: #ffffff;
position: sticky;
left: 0;
z-index: 1;
width: 320rpx !important;
}
.c-333 {
color: #333333;
}
.w-268 {
width: 200rpx;
}
.item {
flex-shrink: 0;
color: #666666;
font-size: 26rpx;
width: 200rpx;
&.c-333 {
color: #333333;
}
&.right {
text-align: right;
}
}
.menu-style {
background-color: #F5F5F5;
border-bottom: 2rpx solid #fff;
font-size: 28rpx;
}
</style>
common.js文件方法:
/**
*
* @param {Any} 需判断是否引用类型数据(简化版,目前数组/对象/map)
* @returns Boolean
*/
function isQuoteType (source) {
let flag = Object.prototype.toString.call(source).toLowerCase()
flag = (/\[object (\w+)\]/.exec(flag))[1]
const isQuote = ['object', 'array', 'map'].includes(flag)
return {
isQuote,
flag
}
}
/**
*
* @param {Any} source 需深度克隆的数据(简化版,目前递归数组/对象/map)
* @returns
*/
export function deepClone (source) {
const { isQuote, flag } = isQuoteType(source)
if (isQuote) {
const isMap = flag.includes('map')
const isObj = flag.includes('object')
const target = isMap ? new Map() : isObj ? {} : []
if (isObj) {
Object.keys(source).forEach((key) => {
if (isQuoteType(source[key])) {
target[key] = deepClone(source[key])
} else {
target[key] = source[key]
}
})
} else {
source.forEach((item, key) => {
if (isQuoteType(item)) {
if (isMap) {
target.set(key, deepClone(item))
} else {
target[key] = deepClone(item)
}
} else {
target[key] = item
}
})
}
return target
} else {
return source
}
}
过滤器管道符filter.js文件方法:
/**
* 大数字转换,将大额数字转换为万、千万、亿等
* @param value 数字值
*/
export function bigNumberTransform(value_) {
let value
if (value_ == null || value_ == '') {
value = ''
} else {
value = Math.abs(value_)
}
const newValue = ['', '', '']
let fr = 1000
let num = 3
let text1 = ''
let fm = 1
while (value / fr >= 1) {
fr *= 10
num += 1
}
if (num <= 4) { // 千
newValue[0] = value
// newValue[0] = parseInt(value / 1000) + ''
// newValue[1] = '千'
} else if (num <= 8) { // 万
text1 = parseInt(num - 4) / 3 > 1 ? '千万' : '万'
// tslint:disable-next-line:no-shadowed-variable
fm = text1 === '万' ? 10000 : 10000000
if (value % fm === 0) {
newValue[0] = parseInt(value / fm) + ''
} else {
newValue[0] = parseFloat(value / fm).toFixed(2) + ''
}
newValue[1] = text1
} else if (num <= 16) { // 亿
text1 = (num - 8) / 3 > 1 ? '千亿' : '亿'
text1 = (num - 8) / 4 > 1 ? '万亿' : text1
text1 = (num - 8) / 7 > 1 ? '千万亿' : text1
// tslint:disable-next-line:no-shadowed-variable
fm = 1
if (text1 === '亿') {
fm = 100000000
} else if (text1 === '千亿') {
fm = 100000000000
} else if (text1 === '万亿') {
fm = 1000000000000
} else if (text1 === '千万亿') {
fm = 1000000000000000
}
if (value % fm === 0) {
newValue[0] = parseInt(value / fm) + ''
} else {
newValue[0] = parseFloat(value / fm).toFixed(2) + ''
}
newValue[1] = text1
}
if (value < 1000) {
newValue[0] = value + ''
newValue[1] = ''
}
let text = newValue.join('')
if (value_ < 0) {
text = "-" + text
}
return text
}
export default {
bigNumberTransform
}
使用过滤器方法还需要在main.js文件中引入:
import filters from '@/common/filters.js'
使用:
Object.keys(filters).forEach((key) => {
Vue.filter(key, filters[key])
})