• uniapp中使用AntV F6 + table表格插件使用


    首先看页面效果:

    AntV官网下载F6文件到项目中与uViewUI插件

    <template>
    <view class="page">
    <!-- 导航栏 -->
    <b-nav-bar class="title">
    <template slot="left">
    <view @click="nativeBack" class="iconfont icon-zuofanhui nBack ml15"></view>
    </template>
    <view>{{navTitle}}</view>
    </b-nav-bar>
    <!-- 树状图 -->
    <view class="page over_hidden">
    <!-- #ifdef APP-PLUS || H5 -->
    <view :id="option.id" :option="option" :treeDom="treeDom" :change:treeDom="treeGraph.changeTreeGraphSize"
    :change:option="treeGraph.changeTreeGraphData"
    style=" 100%;height: 100%;display: flex;justify-content: center;align-items: center;"></view>
    <!-- #endif -->
    <!-- #ifndef APP-PLUS || H5 -->
    <view>非 APP、H5 环境不支持</view>
    <!-- #endif -->
    </view>
    <!-- 表格弹窗 -->
    <u-popup :show="showLinkTable" mode="bottom" :round="10" class="tablePopup">
    <view class="tableHeader">
    <scroll-view class="tableTitle" scroll-x="true">
    <view style="display: inline-block;">{{tableTitle}}</view>
    </scroll-view>
    <view class="tableIcon" @click="closePopup">
    <text class="iconfont icon-shanchu1"></text>
    </view>
    </view>
    <view class="tableContent">
    <view class="time">
    最新时间:{{newTime}}
    </view>
    <lyy-table :headerFixed="true" :columnFixed="1" :emptyString="''" :contents="contentsData" :headers="columnsData" class="lyyTable" @row-click="rowclick"></lyy-table>
    </view>
    </u-popup>
    <!-- 点击问号注释说明弹窗 -->
    <u-popup :show="showExplain" mode="bottom" :round="10" class="explainPopup">
    <view class="explainHeader">
    <scroll-view class="explainTitle" scroll-x="true">
    <view style="display: inline-block;">{{explainTitle}}</view>
    </scroll-view>
    <view class="explainIcon" @click="canclePopup">
    <text class="iconfont icon-shanchu1"></text>
    </view>
    </view>
    <view class="explainContent">
    {{nodeInterpret}}
    </view>
    </u-popup>
    </view>
    </template>

    <script>
    import {
    mapState
    } from 'vuex';
    export default {
    data() {
    return {
    navTitle: '', // 标题
    option: { // 导图数据
    id: "canvasId",
    data: '',
    },
    treeDom: {
    null,
    height: null
    }
    }
    },
    computed: {
    ...mapState(['stabarHeight']) //刘海屏高度存储在vuex里面
    },
    mounted() {
    uni
    .createSelectorQuery()
    .select("#canvasId")
    .boundingClientRect((data) => {
    this.treeDom = data;
    })
    .exec();
    },
    methods: {
    nativeBack() {
    uni.navigateTo({
    url: `/pages/look-company/index/index`
    })
    },
    }
    }
    </script>
    </script>

    <script module="treeGraph" lang="renderjs">
    import F6 from '@/static/common/js/F6/f6.min.js'
    import TreeGraph from '@/static/common/js/F6/extends/graph/treeGraph.js'
    import { // 接口文件
    queryFirstnodeList,
    fetchGraphChildren,
    queryCompanyTable
    } from '@/api/company.js'
    import { // 公共方法文件,在下方有提供
    computeWidthHeight,
    addCollapseNode,
    handleChildrenData,
    getArcPosition
    } from './graphConfig';
    import {
    getSession
    } from '@/util/storage';
    F6.registerGraph('TreeGraph', TreeGraph)
    // 折叠图标
    const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
    return [
    ['M', x - r, y],
    ['L', x + r, y],
    ]
    }

    // 展开图标
    const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
    return [
    ['M', x - r, y],
    ['L', x + r, y],
    ['M', x, y - r],
    ['L', x, y + r]
    ]
    }

    // 默认边和箭头样式
    const defaultEdgeStyle = {
    stroke: '#3849B4',
    endArrow: {
    path: 'M 0,0 L 5,4 L 5,-4 Z',
    lineWidth: 1,
    fill: '#3849B4',
    },
    }

    export default {
    data() {
    return {
    graph: null,
    treeData: {},
    graphReady: false,
    companyName: '', // 公司名称
    companyCode: '', // 公司代码
    topTreeData: [], // 二级节点
    showLinkTable: false, // 是否显示表格弹窗
    tableId: '', // 表格id
    tableName: '', // 表名
    contentsData: [], //表格数据
    columnsData: [], //表头数据
    tableTitle: '', // 表格标题
    newTime: '', //最新时间
    showExplain: false, // 是否显示点击问号注释说明弹窗
    explainTitle: '', // 注释说明弹窗标题
    nodeInterpret: '', // 注释说明弹窗内容
    }
    },
    created() {
    this.initData()
    },
    mounted() {
    this.init() // 初始化F6
    },
    onLoad(option) {
    this.navTitle = `${option.title}(${option.code})` // 页面标题
    this.companyName = option.title // 公司名
    this.companyCode = option.code // 公司代码
    },
    methods: {
    // 初始化一、二级节点
    async initData() {
    await this.requestTopTreeNode()
    this.setDefaultNode()
    },
    // 获取二级分类节点
    async requestTopTreeNode() {
    try {
    let res = await queryFirstnodeList({
    token: sessionStorage.getItem('token')
    })
    res = res.data || []
    res.forEach((item) => {
    item.level = 2
    item.hasChildren = true // 隐藏展示/折叠图表
    item.collapsed = true // 当前折叠状态
    item.isGetChildren = false // 是否已加载了children
    })
    this.topTreeData = res
    } catch (err) {
    err.msg && this.$alert(err.msg, '提示')
    }
    },
    // 设置初始树数据
    setDefaultNode(data) {
    if (!data) {
    const children = JSON.parse(JSON.stringify(this.topTreeData))
    data = {
    id: 'root',
    name: this.companyName,
    children,
    level: 1
    }
    this.treeData = data
    }
    if (this.graphReady) {
    this.showLinkTable = false
    this.graph.data(this.treeData)
    this.graph.render()
    this.graph.fitCenter()
    } else {
    const unwatch = this.$watch('graphReady', (val) => {
    if (val) {
    this.setDefaultNode(data)
    unwatch()
    }
    })
    }
    },
    // 初始化
    init() {
    this.treeDom.width = wx.getSystemInfoSync().screenWidth // 获取屏幕宽度
    let sysInfo = getSession('systemInfo')
    if (sysInfo.platform.indexOf('ios') > -1) {
    this.treeDom.height = wx.getSystemInfoSync().screenHeight - 44 - this.stabarHeight // 获取ios屏幕高度
    } else {
    this.treeDom.height = wx.getSystemInfoSync().screenHeight - 44 // 获取安卓屏幕高度
    }
    // 绘制节点
    F6.registerNode('icon-node', {
    draw(cfg, group) {
    const styles = this.getShapeStyle(cfg);
    const {
    labelCfg = {}
    } = cfg;
    // const w = styles.width;
    // const h = styles.height;
    let keyShape = {}
    // 根节点,高度固定,宽度根据公司名调整
    if (cfg.level === 1) {
    const w = 75
    const h = 30
    keyShape = group.addShape('rect', {
    attrs: {
    w,
    height: h,
    x: -w / 2,
    y: -h / 2,
    fill: '#3849B4',
    radius: 12,
    },
    })
    group.addShape('text', {
    attrs: {
    ...labelCfg.style,
    text: cfg.name,
    x: 0,
    y: 0,
    fontSize: 11,
    fill: '#ffffff',
    },
    })
    } else {
    // 不管二/三级几点是否有展开/折叠图标宽度都不计算上,在layout中会考虑
    let w = 75
    let h = 30
    let content = ''
    // 三级以上节点宽高动态计算
    if (cfg.level >= 3) {
    const res = computeWidthHeight({
    content: cfg.content
    })
    w = res.width - 30
    h = res.height
    content = res.content
    }
    cfg.hasChildren && addCollapseNode(group, w, cfg)
    // 配置了有展开/折叠图标则渲染
    // 二级分类节点,宽高固定
    if (cfg.level === 2) {
    keyShape = group.addShape('rect', {
    attrs: {
    fill: '#EEF2F8',
    stroke: '#3849B4',
    radius: 6,
    w,
    height: h,
    fontSize: 11,
    x: -w / 2,
    y: -h / 2
    },
    })
    group.addShape('text', {
    attrs: {
    ...labelCfg.style,
    text: cfg.name,
    x: 0,
    y: 0,
    fontSize: 11
    },
    name: 'collapse-icon'
    })
    } else {
    // 三级以上节点配置
    keyShape = group.addShape('rect', {
    attrs: {
    w,
    height: h,
    x: -w / 2,
    y: -h / 2,
    fill: '#E5E5E5',
    radius: 6,
    fontSize: 11
    },
    })
    // 上部关系节点名称
    group.addShape('rect', {
    attrs: {
    x: 1 - w / 2,
    y: 1 - h / 2,
    w - 2,
    height: 32,
    fill: '#EEF2F8',
    radius: [6, 6, 0, 0],
    fontSize: 11
    },
    })
    group.addShape('text', {
    attrs: {
    ...labelCfg.style,
    text: cfg.name,
    x: 1,
    y: 18 - h / 2,
    },
    name: 'collapse-icon'
    })
    // 判断是否有说明文字(即?)
    if (cfg.nodeInterpret) {
    group.addShape('text', {
    attrs: {
    ...labelCfg.style,
    x: w / 2 - 12,
    y: 19 - h / 2,
    fontFamily: 'iconfont', // 对应css里面的font-family: "iconfont";
    textAlign: 'right',
    fill: '#999999',
    text: '\ue715',
    cursor: 'pointer',
    lineHeight: 12,
    fontSize: 11
    },
    name: 'interpret-icon',
    })
    }
    // 下方实体节点内容
    group.addShape('rect', {
    attrs: {
    x: 1 - w / 2,
    y: 34 - h / 2,
    w - 2,
    height: h - 35,
    fill: '#ffffff',
    fontSize: 11,
    radius: [0, 0, 6, 6]
    },
    })
    // 可能有多行文字
    for (let i = 0, l = content.length; i < l; i++) {
    const attrs = {
    ...labelCfg.style,
    text: content[i],
    x: 0,
    y: 34 - h / 2 + 14 + 14 * i,
    textAlign: 'center',
    fill: '#999999',
    fontSize: 11,
    lineHeight: 14
    }
    // 如果只有一行文字,设置y居中
    if (l === 1) {
    attrs.y = 17
    }
    const config = {
    attrs
    }
    // 可点击节点样式
    if (cfg.showType === '3') {
    config.attrs.fill = '#3849B4'
    config.attrs.cursor = 'pointer'
    config.name = 'link-text'
    } else {
    config.name = 'collapse-icon'
    }
    group.addShape('text', config)
    }
    }
    }
    return keyShape;
    },
    update(cfg, item) {
    const group = item.getContainer();
    const icon = group.find((e) => e.get('name') === 'collapse-icon');
    // 如果不展示展开/折叠,调整样式造成视觉隐藏
    if (!cfg.hasChildren) {
    icon.attr('lineWidth', 0)
    icon.attr('cursor', 'auto')
    const iconBox = group.find((e) => e.get('name') === 'marker-box')
    iconBox.attr('width', 0)
    iconBox.attr('stroke', '#ffffff')
    } else {
    icon.attr('symbol', cfg.collapsed ? EXPAND_ICON : COLLAPSE_ICON);
    }
    }
    }, 'rect');
    // 绘制连线
    F6.registerEdge('flow-line', {
    draw(cfg, group) {
    const {
    startPoint,
    endPoint,
    targetNode,
    sourceNode,
    style: {
    stroke,
    endArrow
    }
    } = cfg;
    let path = null
    // 是否向左
    const isLeft = startPoint.x > endPoint.x
    // 因为设置的连接点为容器的中心
    const sourceNodeWidth = sourceNode._cfg.originStyle.width
    startPoint.x = startPoint.x + (isLeft ? -sourceNodeWidth / 2 : sourceNodeWidth / 2)
    const targetNodeWidth = targetNode._cfg.originStyle.width
    endPoint.x = endPoint.x + (isLeft ? targetNodeWidth / 2 : -targetNodeWidth / 2)
    startPoint.x = startPoint.x + (sourceNode._cfg.model.hasChildren ? (isLeft ? -16 : 16) : 0)
    // 如果两个节点中心点纵向距离小于10直接绘一条线
    if (Math.abs(startPoint.y - endPoint.y) < 8) {
    path = [
    ['M', startPoint.x, startPoint.y],
    ['L', startPoint.x + (isLeft ? -10 : 10), startPoint.y],
    ['L', startPoint.x + (isLeft ? -10 : 10), endPoint.y],
    ['L', endPoint.x, endPoint.y]
    ]
    } else {
    // 判断弧线部分向上/下
    const isTop = startPoint.y < endPoint.y
    // 计算弧线开始/结束坐标
    const arcStartX = startPoint.x + (isLeft ? -10 : 10)
    const arcStartY = endPoint.y + (isTop ? -4 : 4)
    const arcEndX = arcStartX + (isLeft ? -4 : 4)
    const arcEndY = endPoint.y
    // 取到弧线的控制点坐标(上下2个控制点)
    const arcControlList = getArcPosition(arcStartX, arcStartY, arcEndX, arcEndY, 16)
    const arcControlItem = isLeft ? (isTop ? arcControlList.bottom : arcControlList.top) :
    (isTop ? arcControlList.top : arcControlList.bottom)
    // 绘制路径
    path = [
    ['M', startPoint.x, startPoint.y],
    ['L', arcStartX, startPoint.y],
    ['L', arcStartX, arcStartY],
    ['Q', arcControlItem.x, arcControlItem.y, arcEndX, arcEndY],
    ['L', endPoint.x, arcEndY]
    ];
    }
    const shape = group.addShape('path', {
    attrs: {
    stroke,
    path,
    endArrow
    }
    });
    return shape;
    }
    });
    // 实例化F6
    this.graph = new F6.TreeGraph({
    container: this.option.id,
    this.treeDom.width,
    height: this.treeDom.height,
    animate: true,
    // fitView: true, // 画布自适应
    // fitCenter: true, //图将会被平移,图的中心将对齐到画布中心,但不缩放。优先级低于 fitView。
    minZoom: 0.2,
    maxZoom: 1.5,
    linkCenter: true, //指定边是否连入节点的中心
    modes: {
    default: ['drag-canvas', 'zoom-canvas'],
    },
    defaultNode: {
    type: 'icon-node',
    anchorPoints: [
    [0, 0.5],
    [1, 0.5],
    ],
    labelCfg: {
    style: {
    fill: '#3849B4',
    fontSize: 12,
    textAlign: 'center',
    textBaseline: 'middle',
    },
    },
    },
    defaultEdge: {
    type: 'flow-line',
    style: defaultEdgeStyle,
    },
    layout: {
    type: 'compactBox',
    direction: 'H',
    getId(d) {
    return d.id;
    },
    getHeight(d) {
    if (d.level >= 3) {
    const {
    height
    } = computeWidthHeight({
    content: d.content
    })
    return height
    } else {
    return 30
    }
    },
    getWidth(d) {
    const {
    level
    } = d
    if (level === 1) {
    return 75
    } else if (level === 2) {
    return 75
    } else {
    let {
    width
    } = computeWidthHeight({
    content: d.content
    })
    if (d.hasChildren) {
    width += 16
    }
    return width
    }
    },
    getVGap() {
    return 20;
    },
    getHGap(d) {
    if (d.x > 0) {
    return 60
    } else {
    return 25
    }
    }
    },
    })
    // 监听点击事件
    this.graph.on('node:tap', this.graphNodeClick);
    this.graphReady = true
    this.graph.data(this.treeData);
    this.graph.render();
    },
    // 元素点击事件
    async graphNodeClick(evt) {
    const {
    item,
    target
    } = evt;
    // const targetType = target.get('type');
    const name = target.get('name')
    const model = item.getModel()
    // 增加元素
    if (model.hasChildren && name === 'collapse-icon') {
    this.haneldCollapse(item)
    } else if (name === 'link-text') {
    this.handleLink(item)
    //显示加载框
    uni.showLoading({
    title: '加载中',
    mask: true
    });
    } else if (name === 'interpret-icon') {
    this.showExplain = true
    this.nodeInterpret = model.nodeInterpret
    this.explainTitle = model.name
    }
    },
    // 处理展开折叠
    async haneldCollapse(item) {
    const {
    graph
    } = this
    const model = item.getModel()
    let collapsed = model.collapsed
    let hasChildren = model.hasChildren
    if (model.collapsed && !model.isGetChildren) {
    const id = model.id.split('_')[0];
    try {
    const res = await fetchGraphChildren(id, this.companyCode);
    if (!res.data || res.data.length == 0) {
    model.hasChildren = false;
    } else {
    const children = handleChildrenData(res.data, model.level + 1);
    if (children.length) {
    model.children = children;
    } else {
    model.hasChildren = false
    }
    }
    } catch (err) {
    model.hasChildren = false
    }
    model.isGetChildren = true;
    }
    collapsed = !collapsed;
    model.collapsed = collapsed;
    graph.updateChild(model, model.id);
    const updateParams = {
    collapsed
    }
    if (hasChildren !== model.hasChildren) {
    updateParams.hasChildren = model.hasChildren
    }
    graph.updateItem(item, updateParams);
    },
    // 获取可点击实体表格信息
    async handleLink(item) {
    this.columnsData = []
    const model = item.getModel()
    this.tableId = model.id.split('_')[0];
    this.tableName = model.content.join('')
    let params = {
    id: this.tableId,
    name: this.tableName
    }
    const res = await queryCompanyTable(params)
    if (res.code == 0) {
    this.tableTitle = res.data.name
    this.newTime = res.data.latestTime
    this.contentsData = res.data.resultList
    res.data.titles.forEach(item => {
    let obj = {
    label: item.fieldTitle,
    key: item.field,
    (40 + item.fieldTitle.length * 16) + 'px',
    sort: true
    }
    // obj.label == ''
    this.columnsData.push(obj)
    })
    this.showLinkTable = true
    //隐藏加载框
    uni.hideLoading();
    } else {
    //隐藏加载框
    uni.hideLoading();
    uni.$u.toast(res.msg)
    }
    },
    rowclick(e) {
    uni.redirectTo({
    url: `/pages/look-company/companyDetail/companyDetail?title=${e.Stknme}&code=${e.Stkcd}`
    })
    },
    // 关闭表格弹窗
    closePopup() {
    this.showLinkTable = false
    },
    // 关闭点击问号注释弹窗
    canclePopup() {
    this.showExplain = false
    },
    // 清除画布
    handleClear() {
    this.graph && this.graph.destroy()
    this.graph = null
    this.graphReady = false
    },
    },
    beforeDestroy() {
    this.handleClear()
    },
    }
    </script>

    <style lang="scss" scoped>
    .page {
    background: #ffffff;
    overflow: hidden;

    .title {
    font-size: 32rpx;
    text-align: center;
    border-bottom: 1px solid #DCDEE3;

    .nBack {
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    }
    }

    .tablePopup {
    100%;
    background-color: #ffffff;

    .tableHeader {
    100%;
    height: 100rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid #DCDEE3;

    .tableTitle {
    font-size: 30rpx;
    font-family: PingFang SC;
    font-weight: bold;
    color: #333333;
    margin-left: 30rpx;
    80%;
    height: 100rpx;
    line-height: 100rpx;
    white-space: nowrap;
    }

    .tableIcon {
    44rpx;
    height: 44rpx;
    background-color: #F4F4F4;
    margin-right: 30rpx;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;

    .icon-shanchu1 {
    font-size: 32rpx;
    color: #C0C0C0;

    }
    }
    }

    .tableContent {
    100%;
    height: 800rpx;
    margin: 0rpx 0rpx 30rpx;

    .time {
    font-size: 22rpx;
    height: 68rpx;
    color: #999999;
    margin-left: 30rpx;
    line-height: 68rpx;
    }

    .lyyTable {
    100%;
    height: calc(100% - 68rpx) !important;
    overflow: hidden;
    font-size: 24rpx;
    }
    }
    }

    .explainPopup {
    100%;
    background-color: #fff;

    .explainHeader {
    100%;
    height: 100rpx;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid #DCDEE3;

    .explainTitle {
    font-size: 30rpx;
    font-family: PingFang SC;
    font-weight: bold;
    color: #333333;
    margin-left: 30rpx;
    80%;
    height: 100rpx;
    line-height: 100rpx;
    white-space: nowrap;
    }

    .explainIcon {
    44rpx;
    height: 44rpx;
    background-color: #F4F4F4;
    margin-right: 30rpx;
    border-radius: 50%;
    display: flex;
    justify-content: center;
    align-items: center;

    .icon-shanchu1 {
    font-size: 32rpx;
    color: #C0C0C0;

    }
    }
    }

    .explainContent {
    padding: 38rpx 57rpx 38rpx 30rpx;
    font-size: 24rpx;
    color: #666666;
    }
    }
    }

    /deep/ .uni-table-th {
    color: #3849B4;
    background-color: #F7F8FB !important;
    font-size: 24rpx;
    }

    /deep/ .uni-table-td {
    font-size: 24rpx;
    }
    </style>

    storage.js文件获取session方法:
     
    // sessionStorage获取
    export function getSession(key) {
      return getObjectValue(sessionStorage.getItem(getMixKey(key)))
    }
    graphConfig.js文件公共方法:
     
    /** 获取Path贝塞尔曲线控制点坐标
     * @param {Number} startX 弧线起始x坐标
     * @param {Number} startY 弧线起始Y坐标
     * @param {Number} endX 弧线结束X坐标
     * @param {Number} endY 弧线结束Y坐标
     * @param {Number} angle 弧线角度
     */
    export function getArcPosition (startX, startY, endX, endY, angle) {
      const PI = Math.PI;
      // 两点间的x轴夹角弧度
      const yOffset = endY - startY
      const xOffset = endX - startX
      let xAngle = Math.atan2(yOffset, xOffset);
      // 转为角度
      xAngle = 360 * xAngle / (2 * PI);
      // 两点间的长度
      const L = Math.sqrt(yOffset * yOffset + xOffset * xOffset);
      // 计算等腰三角形斜边长度
      const L2 = L / 2 / Math.cos(angle * 2 * PI / 360);
      // 求第一个顶点坐标,位于下边
      const top = {};
      // 求第二个顶点坐标,位于上边
      const bottom = {};
      top.x = startX + Math.round(L2 * Math.cos((xAngle + angle) * 2 * PI / 360));
      top.y = startY + Math.round(L2 * Math.sin((xAngle + angle) * 2 * PI / 360));
      bottom.x = startX + Math.round(L2 * Math.cos((xAngle - angle) * 2 * PI / 360));
      bottom.y = startY + Math.round(L2 * Math.sin((xAngle - angle) * 2 * PI / 360));
      return { top, bottom }
    }
    // 折叠图标
    const COLLAPSE_ICON = function COLLAPSE_ICON(x, y, r) {
    return [
    ['M', x - r, y],
    ['L', x + r, y],
    ]
    }
     
    // 展开图标
    const EXPAND_ICON = function EXPAND_ICON(x, y, r) {
    return [
    ['M', x - r, y],
    ['L', x + r, y],
    ['M', x, y - r],
    ['L', x, y + r]
    ]
    }
     
    // 添加展开/折叠节点(canvas)
    export function addCollapseNode (group, w, cfg) {
      const isLeft = cfg.x < 0
      const x = isLeft ? -w / 2 - 11 : w / 2 - 1
      const radius = isLeft ? [2, 0, 0, 2] : [0, 2, 2, 0]
      group.addShape('rect', {
        attrs: {
          x,
          y: -8,
          10,
          height: 18,
          stroke: '#E5E5E5',
          fill: '#EEF2F8',
          radius,
        },
        name: 'marker-box',
      });
      //+/-
      group.addShape('marker', {
        attrs: {
          x: x + (isLeft ? 6 : 6),
          y: 1,
          r: 3,
          stroke: '#A9ADB7',
          lineWidth: 1,
          cursor: 'pointer',
          symbol: cfg.collapsed ? EXPAND_ICON : COLLAPSE_ICON
        },
        name: 'collapse-icon',
      })
    }
     
    // 默认边和箭头样式
    export const defaultEdgeStyle = {
      stroke: '#3849B4',
      endArrow: {
        path: 'M 0,0 L 5,4 L 5,-4 Z',
        lineWidth: 1,
        fill: '#3849B4',
      },
    }
     
     
    /**
     * 计算节点宽度和高度
     * @param {Array} content 要展示的实体内容
     * @param {StrNumbering} vPadding 横向内部padding
     * @param {Number} hPadding 纵向内部padding
     * @param {Number} defaultWidth 默认宽度
     * @param {Number} maxWidth  最大宽度
     * @param {Number} defaultHeight  默认高度
     * @param {Number} maxHeight  最大高度
     * @param {Number} otherHeight  其他占用高度
     * @returns
     */
    export function computeWidthHeight ({ content, vPadding = 12, hPadding = 16, defaultWidth = 200, maxWidth = 240, defaultHeight = 50, maxHeight = 160, otherHeight = 34 }) {
      let rowNum = content.length
      let width = defaultWidth
      let defaultLength = Math.floor((width - 2 - vPadding) / 12)
      let res = []
      // 判断150宽度能否满足每一行数据要求
      const isDissatisfy = content.some((item) => computeStrOccupyLength(item) > defaultLength)
      // 如果不满足则切割字符串
      if (isDissatisfy) {
        width = maxWidth
        defaultLength = Math.floor((width - 2 - vPadding) / 12)
        for (let i = 0, l = content.length; i < l; i++) {
          let item = content[i]
          const matchItems = splitStr(item, defaultLength)
          rowNum += (matchItems.length - 1)
          res.push(...matchItems)
        }
      } else {
        res = content
      }
      let height = Math.max(defaultHeight, rowNum * 14 + hPadding)
      height = Math.min(height, maxHeight) + otherHeight
      return {
        width,
        height,
        content: res
      }
    }
     
    // 计算字符串占用长度
    function computeStrOccupyLength (str) {
      let num = 0
      str.split('').forEach((s) => {
        if (/[0123456789.?()-:]/.test(s)) {
          num += 0.5
        } else {
          num += 1
        }
      })
      return Math.ceil(num)
    }
     
    // 根据数量截取字符串
    function splitStr (str, maxNum) {
      let num = maxNum
      const res = []
      let tmp = []
      str.split('').forEach((s, i) => {
        tmp.push(s)
        if (/[0123456789.?()-:]/.test(s)) {
          num -= 0.5
        } else {
          num -= 1
        }
        if (num < 1 || i === str.length - 1) {
          res.push(tmp.join(''))
          tmp = []
          num = maxNum
        }
      })
      return res
    }
     
    // 处理添加子节点
    export function handleChildrenData (data, level) {
      const res = [];
      data.forEach((item) => {
        const { children, name, parentId, nodeInterpret } = item;
        children.forEach((subItem, index) => {
          let content = subItem.name;
          if (content) {
            const { showType, isChildren } = subItem;
            const id = `${subItem.id}_${index}`;
            content = content.split('\n').filter((item) => item);
            res.push({
              id,
              parentId,
              content,
              name,
              nodeInterpret,
              showType,
              level,
              collapsed: true,
              hasChildren: Boolean(isChildren),
            });
          }
        });
      });
      return res;
    }
     

    表格插件文件:

    去插件市场下载表格插件:

    我这里改了排序,数据为空默认展示,列点击事件源代码,直接复制就好:

    <template>
    <view :style="{height}" :prop="ready" :change:prop="tableRender.documentReady">
    <scroll-view scroll-x scroll-y class="container" @scrolltolower="scrolltolower">
    <uni-table stripe border :loading="loading" style="min-height: 100%;">
    <uni-tr id="lyy-thead" :class="headerFixed?'fixed-head':''" style="display: flex;">
    <uni-th v-for="(item,index) in headers" :key="index" :width="item.width" align="center" :style="{
    display:item.hidden?'none':'flex',item.width,justifyContent: 'center',
    left:columnFixed>0?fixedLeft(index):'unset',
    right:columnFixed<0?fixedRight(index):'unset',
    position:isFixed(index)?'sticky':'unset',
    borderLeft:columnFixed<0&&isFixed(index)?'1px solid #f0f0f0':'unset',
    zIndex:index<=columnFixed-1?999:99,
    backgroundColor:'inherit'
    }">
    <view @click="doSort(item)" style="display: flex;flex-direction: row;justify-content: center;">
    <text :style="{lineHeight:'20px'}">{{item.label}}</text>
    <view class="header-icon"
    style="line-height:6px;display:flex;flex-direction:column;margin-left:5px;justify-content: center;"
    v-if="item.sort">
    <text class="iconfont icon-arrow-up"
    :style="{color:lastSortItem===item.key&&sortWay=='asc'?'#3849B4':'#bcbcbc'}" />

    <text class="iconfont icon-arrow-down"
    :style="{color:lastSortItem===item.key&&sortWay=='desc'?'#3849B4':'#bcbcbc'}" />
    </view>

    </view>
    </uni-th>
    </uni-tr>
    <!--<uni-tr v-if="headerFixed" :style="{height: theadHeight}">
    </uni-tr>
    <!-- <uni-td class="no_data" align="center">暂无数据</uni-td> -->
    <view v-if="contents.length<1" class="no_data">暂无数据</view>
    <uni-tr v-else v-for="(content,sindex) in sortContents" :key="sindex"
    style="display:flex;table-layout: fixed;min- 100%;" @click.native="getRow(content)">
    <!-- @click.native="rowClick(content)">-->
    <uni-td v-for="(header,hindex) in headers" class="tableCell" :data-wrap="overflow" :key="hindex"
    align="center" :style="{display:header.hidden?'none':'flex',textAlign:'center',header.width,
    left:columnFixed>0?fixedLeft(hindex):'unset',
    right:columnFixed<0?fixedRight(hindex):'unset',
    justifyContent:'center',
    alignItems:'center',
    position:isFixed(hindex)?'sticky':'unset',
    borderLeft:columnFixed<0&&isFixed(hindex)?'1px solid #f0f0f0':'unset',
    zIndex:hindex<=columnFixed-1?9:'unset',
    backgroundColor:'inherit',
    overflow:'hidden'}">
    <template v-if="header.format!==undefined">
    <lyy-progress
    v-if="header.format.type==='progress'&&!isNaN(parseFloat(content[header.key]))"
    :percent="content[header.key].toFixed(2)" show-info round></lyy-progress>
    <view v-else-if="header.format.type==='html'" v-html="content[header.key]"></view>
    <text v-else>{{content[header.key]}}</text>
    </template>
    <text v-else>{{content[header.key]}}</text>
    </uni-td>
    </uni-tr>
    <uni-tr v-if="contents.length>0&&totalRow.length>0" style="min- 100%;display: flex;"
    @click.native="getRow(content)">
    <uni-td v-for="(header,index) in headers" :key="Math.random()" align="center" :style="{textAlign: 'center',display:header.hidden?'none':'table-cell',header.width,
    left:columnFixed>0?fixedLeft(index):'unset',
    right:columnFixed<0?fixedRight(index):'unset',
    position:isFixed(index)?'sticky':'unset',
    borderLeft:columnFixed<0&&isFixed(index)?'1px solid #f0f0f0':'unset',
    zIndex:index<=columnFixed-1?9:'unset',
    backgroundColor:'inherit'}">
    <text v-if="index==0">合计</text>
    <view v-else>
    <!--<progress v-if="typeof header.format!=='undefined'&& header.format.type==='progress'" :percent="renderTotalRow(header)" :show-info="true" stroke-width="10" :active="true"></progress>-->
    <lyy-progress
    v-if="typeof header.format!=='undefined'&& header.format.type==='progress'&&!isNaN(parseFloat(renderTotalRow(header)))"
    :percent="renderTotalRow(header)" :show-info="true" round></lyy-progress>
    <text v-else>{{ renderTotalRow(header)}}</text>
    </view>
    </uni-td>
    </uni-tr>
    </uni-table>
    <!-- <uni-load-more v-show="showLoadMore" :status="loadMore"></uni-load-more> -->
    </scroll-view>
    </view>
    </template>

    <script>
    import lyyProgress from './lyy-progress'
    /**
    * lyyTable ver1.3.8
    * @description lyyTable表格组件 ver1.3.8
    */
    export default {
    name: "lyyTable",
    components: {
    lyyProgress
    },
    data() {
    return {
    ready: 1,
    lastSortItem: '', //上一次排序列
    sortWay: 'none', //默认无排序
    sortIndex: 0,
    sortContents: [], //排序时的表格内容
    footContent: {},
    scrollHeight: '',
    theadHeight: ''
    }
    },
    props: {
    //表格高度 1.3.8
    // #ifdef H5
    height: {
    type: String,
    default: 'calc(100vh - 44px - env(safe-area-inset-top))'
    },
    // #endif
    // #ifndef H5
    height: {
    type: String,
    default: '100vh'
    },
    // #endif
    overflow: {
    type: String,
    default: 'nowrap',
    validator: val => ['wrap', 'nowrap'].indexOf(val) > -1
    },
    //显示加载
    loading: {
    type: Boolean,
    default: false
    },
    //上拉加载文字,参考uni-load-more
    loadMore: {
    type: String,
    default: 'more'
    },
    //是否显示上拉加载组件
    showLoadMore: {
    type: Boolean,
    default: false
    },
    //固定表头
    headerFixed: {
    type: Boolean,
    default: false
    },
    //固定首列 ver1.3.3弃用
    /*firstColumnFixed: {
    type: Boolean,
    default: false
    },*/
    //固定列数 ver1.3.3新增
    columnFixed: {
    type: Number,
    default: 0
    },
    //排序方式
    sortWays: {
    type: Array,
    default: () => ['none', 'asc', 'desc']
    },
    //数据为空时的占位符
    emptyString: {
    type: String,
    default: '-'
    },
    //表头
    headers: {
    type: Array,
    default: () => [],
    },
    //表格数据
    contents: {
    type: Array,
    default: () => []
    },
    //合计列
    totalRow: {
    type: Array,
    default: () => []
    }
    },
    mounted() {
    //uni.setStorageSync('contents',this.contents)
    this.sortContents = JSON.parse(JSON.stringify(this.contents))
    this.renderContents()
    this.createTotalRow()
    if (this.overflow == 'nowrap') {
    this.ready = this.headers.length * this.contents.length
    }
    //ver 1.2.0 修复 uni-table width 问题
    /*this.$nextTick(() => {
    console.log('abc',this.$refs)
    const query = uni.createSelectorQuery().in(this)
    query.select('.uni-table').boundingClientRect(dom=>{
    console.log(123456,dom)
    }).exec()
    this.$refs['uni-table'].removeAttribute('style')
    })*/
    //ver 1.2.0 新增 固定表头
    /*if (this.headerFixed) {
    /*var wHeight = document.body.clientHeight
    var tablePoseY = document.getElementById('lyy-tbody').getBoundingClientRect().y
    document.getElementById('lyy-tbody').style.height = wHeight - tablePoseY + 'px'
    const query = uni.createSelectorQuery().in(this)

    query.select('#lyy-thead').boundingClientRect(dom => {
    console.log(dom)
    this.theadHeight = dom.height + 'px'
    }).exec()
    }*/
    },
    watch: {
    contents: {
    //console.log(value)
    handler(value) {
    this.sortContents = JSON.parse(JSON.stringify(value))
    console.log('len--------', value.length)
    this.renderContents()
    this.createTotalRow()
    this.lastSortItem = ''
    this.sortWay = 'none'
    for (var header of this.headers) {
    this.renderTotalRow(header)
    }
    //this.$forceUpdate()
    this.$nextTick(function() {
    if (this.overflow == 'nowrap') {
    this.ready = this.headers.length * this.contents.length
    }
    })
    },
    deep: true

    },
    //监听排序变化
    sortChange(value) {
    var that = this
    var contents = JSON.parse(JSON.stringify(that.contents))

    // 修改排序规则:空值默认排在最后,其他非空值按升序降序排
    let effectiveArr = contents.filter((item) => item[that.lastSortItem] !== '')
    const invalidArr = contents.filter((item) => item[that.lastSortItem] === '')
    switch (value.sortWay) {
    case 'none':
    that.sortContents = contents
    this.renderContents()
    break
    case 'asc': //正序
    effectiveArr = effectiveArr.sort(function(a, b) {
    //需要排序的列为数字时直接计算
    if (!isNaN(Number(a[that.lastSortItem])) && !isNaN(Number(b[that
    .lastSortItem]))) {
    return a[that.lastSortItem] - b[that.lastSortItem]
    }
    //非数字转为ASCII排序(1.3.7弃用)
    //1.3.7更改排序方式,使中文排序更符合中国习惯
    else {
    //(1.3.7弃用) return a[that.lastSortItem].charCodeAt() - b[that.lastSortItem].charCodeAt()
    return a[that.lastSortItem].localeCompare(b[that.lastSortItem], 'zh-cn')
    }
    })
    that.sortContents = effectiveArr.concat(invalidArr)
    break
    case 'desc': //倒序
    effectiveArr = effectiveArr.sort(function(a, b) {
    //需要排序的列为数字时直接计算
    if (!isNaN(Number(a[that.lastSortItem])) && !isNaN(Number(b[that
    .lastSortItem]))) {
    return b[that.lastSortItem] - a[that.lastSortItem]
    }
    //非数字转为ASCII排序(1.3.7弃用)
    //1.3.7更改排序方式,使中文排序更符合中国习惯
    else {
    //(1.3.7弃用) return a[that.lastSortItem].charCodeAt() - b[that.lastSortItem].charCodeAt()
    return b[that.lastSortItem].localeCompare(a[that.lastSortItem], 'zh-cn')
    }
    })
    that.sortContents = effectiveArr.concat(invalidArr)
    break
    }
    that.$forceUpdate()
    }
    },
    computed: {
    //将排序方式、上次排序列作为一个整体进行监听,不然会出现切换排序列不排序的现象
    sortChange() {
    var {
    sortWay,
    lastSortItem
    } = this
    return {
    sortWay,
    lastSortItem
    }
    }
    },
    methods: {
    //点击排序表头时存储上次排序列名,并循环切换排序方式
    doSort(item) {
    if (item.sort) {
    if (this.lastSortItem !== item.key) {
    this.lastSortItem = item.key
    this.sortIndex = 0
    this.sortIndex++
    this.sortWay = this.sortWays[this.sortIndex]
    } else {
    if (this.sortIndex < 2) {
    this.sortIndex++
    this.sortWay = this.sortWays[this.sortIndex]
    } else {
    this.sortIndex = 0
    this.sortWay = this.sortWays[0]
    }
    }
    }
    },
    // 表格行点击事件
    getRow(data) {
    this.$emit("row-click", data)
    },
    //表格内容渲染
    renderContents() {
    const headers = this.headers
    //防止修改穿透
    var contents = JSON.parse(JSON.stringify(this.contents))
    //var contents=uni.getStorageSync('contents')
    let sortContents = JSON.parse(JSON.stringify(this.sortContents))
    var newArr = []
    var result = ''
    sortContents.forEach(function(content, index) {
    var item = content
    headers.forEach(function(header) {
    //字符类型格式化
    if (typeof header.format !== 'undefined' && (header.format.type === 'string' ||
    header.format.type === 'html')) {
    var template = header.format.template
    var keys = header.format.keys
    //console.log(typeof template)
    if (typeof template === 'function') {
    var arg = []
    keys.forEach((el, i) => {
    arg.push(contents[index][el])
    })
    result = template(arg)
    //console.log(result)
    } else {
    keys.forEach((el, i) => {
    var value = contents[index][el]
    var reg = new RegExp('\\{' + i + '}', 'g')
    template = template.replace(reg, value)
    })
    result = template
    }
    item[header.key] = result
    }
    //计算类型格式化
    else if (typeof header.format !== 'undefined' && (header.format.type ===
    'compute' || header.format.type === 'progress')) {
    //console.log(header.format.template)
    var temp = header.format.template
    var keys = header.format.keys
    //1.3.7 使计算列支持function
    if (typeof temp === 'function') {
    var arg = []
    keys.forEach((el, i) => {
    arg.push(contents[index][el])
    })
    item[header.key] = temp(arg)
    //console.log(result)
    } else {
    keys.forEach((el, i) => {
    var reg = new RegExp('\\{' + i + '}', 'g')
    temp = temp.replace(reg, contents[index][el])
    })
    //console.log(temp)
    item[header.key] = eval(temp)
    //this.sortContents[index][header.key]=result
    }
    }
    })
    newArr.push(item)
    })
    this.sortContents = newArr
    },
    createTotalRow() {
    if (this.totalRow.length > 0 && this.sortContents[0] !== undefined) {
    /*var obj = {...this.contents[0]}
    console.log(obj)
    for (var i in obj) {
    this.footContent[i] = obj[i]
    }*/
    this.footContent = JSON.parse(JSON.stringify(this.sortContents[0]))
    for (var i in this.footContent) {
    var result = 0
    if (this.sortContents.length > 0) {
    for (var j in this.sortContents) {
    result += parseFloat(this.sortContents[j][i]) || 0
    }
    }
    this.footContent[i] = result
    }
    }
    },
    //合计列渲染
    renderTotalRow(header) {
    var content = JSON.parse(JSON.stringify(this.footContent))
    var result = this.emptyString
    if (this.totalRow.indexOf(header.key) > -1) {
    if (typeof header.format !== 'undefined' && header.format.type === 'progress') {
    var temp = header.format.template
    var keys = header.format.keys
    for (var index in keys) {
    var reg = new RegExp('\\{' + index + '}', 'g')
    temp = temp.replace(reg, content[keys[index]])
    }
    result = eval(temp)
    result = isNaN(result) ? 0 : result.toFixed(2)
    } else {
    if (content[header.key] != null && !isNaN(content[header.key])) {
    result = content[header.key]
    }
    }
    }
    return result
    },
    //行点击事件
    rowClick(data) {
    this.$emit('rowClick', data)
    },
    //上拉加载事件
    scrolltolower(e) {
    if (e.detail.direction == 'bottom') {
    this.$emit('onPullup')
    }
    },
    //固定列left计算
    fixedLeft(index) {
    var headers = this.headers.filter(item => !item.hidden)
    var left = 'calc(1px'
    for (var i = 1; i < index + 1; i++) {
    left += ' + ' + headers[i - 1].width
    }
    left += ')'
    return left
    },
    //v1.3.5
    //固定列right计算
    fixedRight(index) {
    var headers = this.headers.filter(item => !item.hidden)
    var columnFixed = Math.abs(this.columnFixed)
    if (index >= headers.length + this.columnFixed) {
    var right = 'calc(1px'
    for (var i = index; i < headers.length - 1; i++) {
    if (index + 1 == headers.length) {
    break
    } else {
    right += ' + ' + headers[i + 1].width
    }
    }
    right += ')'
    return right
    } else {
    return 'unset'
    }
    },
    //v1.3.5
    isFixed(index) {
    if (this.columnFixed > 0) {
    return index <= this.columnFixed - 1
    } else if (this.columnFixed < 0) {
    var headers = this.headers.filter(item => !item.hidden)
    return index >= headers.length + this.columnFixed
    } else {
    return false
    }
    }
    }
    }
    </script>
    <script module="tableRender" lang="renderjs">
    function test(a, b) {
    alert(a, b)
    }
    export default {
    methods: {
    documentReady(newValue, oldValue, ownerInstance, instance) {
    //alert(`${newValue},${oldValue}`)
    console.log(newValue, oldValue)
    if (newValue > oldValue) {
    var cells = document.querySelectorAll('.tableCell')

    function changeWrap(e) {
    console.log(1)
    var wrap = e.currentTarget.dataset.wrap
    e.currentTarget.dataset.wrap = wrap == 'nowrap' ? 'unset' : 'nowrap'
    }

    function fun(e) {
    changeWrap(e)
    }
    // for (var i = oldValue - 1; i < cells.length; i++) {
    // cells[i].addEventListener('click', fun)
    // }
    }
    }
    }
    }
    </script>
    <style>
    /deep/.uni-table-loading {
    display: none !important;
    }
    </style>

    <style scoped>
    @import './css/iconfont.css';

    .container {
    100vw;
    height: 100%;
    border-bottom: 1px solid #f0f0f0;
    }

    .uni-table-scroll {
    overflow: unset !important;
    border-top: none;
    }

    .no_data {
    position: fixed;
    750upx;
    background-color: #FFF;
    height: 50px;
    text-align: center;
    line-height: 50px;
    }

    #lyy-thead {
    /*min- 750upx;*/
    display: table;
    background-color: #FFF;
    }

    [data-wrap='unset'] {
    white-space: unset !important;
    display: flex !important;
    }

    [data-wrap='nowrap'] {
    white-space: nowrap !important;
    }

    [data-wrap='nowrap'] uni-text {
    100%;
    text-overflow: ellipsis;
    overflow: hidden;
    }

    .fixed-head {
    position: sticky;
    top: 0;
    z-index: 99;
    border-top: 1px solid #f0f0f0;
    }

    /*修复滑动时偏移1px问题*/
    /*.table--border {
    border-top: none;
    border-left: none;
    }*/

    .uni-table-tr {
    background-color: #FFF;
    }
    </style>

    排序字体图标需要缩小,在css文件中iconfont.css文件里面改字体图标样式即可:
    .iconfont {
      font-family: "iconfont" !important;
      font-size: 12px !important;
      transform: scale(0.7); // 图标缩小,根据需求自己定义缩小值,因浏览器默认字体是12px,设置即可小于12px
      font-style: normal;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
     
  • 相关阅读:
    Mdate时间插件
    JS数组映射保存数据-场景
    基于微信的图片放大预览
    移动前端自适应布局适配解决方案
    JS数组映射详解
    回复与发表切换
    this应用详解-js原生
    淘宝虚拟产品自动发货软件
    搭建个人博客
    2019免杀大马
  • 原文地址:https://www.cnblogs.com/xiaofang234/p/16408159.html
Copyright © 2020-2023  润新知