uniapp + uView使用AntV F6 + table表格插件使用
作者:互联网
AntV官网下载F6文件到项目中
<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="width: 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" :sortWays="handleSortByColumn(contentsData)"
@click="oneRowClick(contentsData)" 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 { // 引入vuex中存储的iOS刘海屏高度
mapState
} from 'vuex';
export default {
data() {
return {
navTitle: '', // 标题
option: { // 导图数据
id: "canvasId",
data: '',
},
treeDom: { // 导图宽高
width: 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' // 引入F6文件
import TreeGraph from '@/static/common/js/F6/extends/graph/treeGraph.js' // 引入F6文件
import { // 接口方法
queryFirstnodeList,
fetchGraphChildren,
queryCompanyTable
} from '@/api/company.js'
import { // js文件中公共方法引入
computeWidthHeight,
addCollapseNode,
handleChildrenData,
getArcPosition
} from './graphConfig';
import { // 自己写的获取session方法
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: {
width: 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,
width: 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: {
width: 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,
width: 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,
width: 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,
width: 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) {
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
this.showLinkTable = true
res.data.titles.forEach(item => {
this.handleSortByColumn({
data: res.data.resultList,
order: 'asc',
property: item.field
})
let obj = {
label: item.fieldTitle,
key: item.field,
width: (40 + item.fieldTitle.length * 16) + 'px',
sort: true
}
// obj.label == ''
this.columnsData.push(obj)
})
//隐藏加载框
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}`
})
},
// 排序要求''排在最底下
handleSortByColumn({
data,
order,
property
}) {
let dataList = data
if (order) {
// 是否是升序
const isAsc = order === 'asc'
let effectiveArr = data.filter((item) => item[property] !== '')
const invalidArr = data.filter((item) => item[property] === '')
effectiveArr = effectiveArr.sort((a, b) => {
if (a[property] > b[property]) {
return isAsc ? 1 : -1
} else {
return isAsc ? -1 : 1
}
})
dataList = effectiveArr.concat(invalidArr)
this.contentsData = dataList
}
},
// 关闭表格弹窗
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 {
width: 100%;
background-color: #ffffff;
.tableHeader {
width: 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;
width: 80%;
height: 100rpx;
line-height: 100rpx;
white-space: nowrap;
}
.tableIcon {
width: 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 {
width: 100%;
height: 800rpx;
margin: 0rpx 0rpx 30rpx;
.time {
font-size: 22rpx;
height: 68rpx;
color: #999999;
margin-left: 30rpx;
line-height: 68rpx;
}
.lyyTable {
width: 100%;
height: calc(100% - 68rpx) !important;
overflow: hidden;
font-size: 24rpx;
}
}
}
.explainPopup {
width: 100%;
background-color: #fff;
.explainHeader {
width: 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;
width: 80%;
height: 100rpx;
line-height: 100rpx;
white-space: nowrap;
}
.explainIcon {
width: 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, width: 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',width: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-width: 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',width: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-width: 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',width: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))
switch (value.sortWay) {
case 'none':
that.sortContents = contents
this.renderContents()
break
case 'asc': //正序
that.sortContents = that.sortContents.sort(function(a, b) {
//需要排序的列为数字时直接计算
if (!isNaN(Number(a[that.lastSortItem])) && !isNaN(Number(b[that
.lastSortItem]))) {
if (a[that.lastSortItem] == '' || b[that.lastSortItem] == ''){
return b[that.lastSortItem] - a[that.lastSortItem]
}else {
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')
}
})
break
case 'desc': //倒序
that.sortContents = that.sortContents.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 b[that.lastSortItem].charCodeAt() - a[that.lastSortItem].charCodeAt()
return b[that.lastSortItem].localeCompare(a[that.lastSortItem], 'zh-cn')
}
})
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 {
width: 100vw;
height: 100%;
border-bottom: 1px solid #f0f0f0;
}
.uni-table-scroll {
overflow: unset !important;
border-top: none;
}
.no_data {
position: fixed;
width: 750upx;
background-color: #FFF;
height: 50px;
text-align: center;
line-height: 50px;
}
#lyy-thead {
/*min-width: 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{
width: 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; }标签:uniapp,插件,const,item,uView,content,width,var,return 来源: https://www.cnblogs.com/xiaofang234/p/16408159.html