uniapp+uView固定列树形结构表格渲染

<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])

})

  • 11
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值