概述
本 Demo 展示了 SpreadJS 的分组列(Outline Column)功能,实现了一个项目工作分解结构(WBS)的树形数据展示。Demo 演示了如何通过 outlineColumn API 配置树形结构,以及如何动态调整分组列的各种选项,包括显示指示器、复选框、自定义图标、设置最大级别等。
实现思路
加载预设的项目任务数据,通过 setDataSource 方法绑定数据源
根据数据中的 level 字段,为每个单元格设置文本缩进(textIndent),建立数据的层级关系
调用 outlineColumn.options() 方法配置分组列,指定列索引、显示选项和图标
调用 outlineColumn.refresh() 刷新分组列显示
绑定界面事件处理器,允许用户动态修改分组列的各项配置
代码解析
配置分组列选项
这段代码初始化了分组列的核心配置。columnIndex 指定第 0 列作为分组列,showImage 和 showCheckBox 分别控制是否显示层级图标和复选框。images 数组为不同层级设置图标(每个元素对应一个级别),expandIndicator 和 collapseIndicator 定义展开和折叠状态的指示器图片。maxLevel 设置最大层级为 2,超过此层级的数据将不再细分。
设置文本缩进建立层级关系
这段代码通过遍历数据源,根据每行的 level 字段设置文本缩进。textIndent(level) 方法的参数值越大,缩进越深,从而在视觉上体现层级关系。同时根据层级设置不同的背景色,提升可读性。
动态修改分组列配置
这段代码展示了如何动态修改分组列配置。每次修改选项后,都需要调用 outlineColumn.refresh() 方法来刷新显示。Demo 中提供了多个复选框和按钮,允许用户实时调整指示器、复选框、图标的显示状态,以及上传自定义图标。
运行效果
表格左侧显示项目任务的树形结构,不同层级有不同的缩进和背景色
点击分组标记可以展开或折叠该分组下的子项
点击复选框可以选中或取消选中任务,选中状态会联动影响其所有子任务
通过右侧面板可以动态控制分组列的显示选项:显示/隐藏指示器、复选框、图像
可以上传自定义图片来替换不同层级的图标和展开/折叠指示器
可以修改最大级别参数来控制树形结构的深度
API 参考
outlineColumn.options()
获取或设置大纲列的选项。
outlineColumnOptions.columnIndex:指定作为分组列的列索引
outlineColumnOptions.showIndicator:是否显示展开/折叠指示器
outlineColumnOptions.showCheckBox:是否显示复选框
outlineColumnOptions.showImage:是否显示层级图标
outlineColumnOptions.images:字符串数组,为不同层级指定图标(URL 或 Base64)
outlineColumnOptions.expandIndicator:展开指示器图片(URL 或 Base64)
outlineColumnOptions.collapseIndicator:折叠指示器图片(URL 或 Base64)
outlineColumnOptions.maxLevel:最大层级数,默认为 10
outlineColumn.refresh()
刷新分组列的显示。修改分组列配置后需要调用此方法使更改生效。
window.onload = function() {
var spread = new GC.Spread.Sheets.Workbook(_getElementById('ss'), { sheetCount: 2 });
initSpread(spread);
};
function initSpread(spread) {
var sheet = spread.getActiveSheet();
sheet.suspendPaint();
loadData(sheet);
initOutlineColumn(sheet);
setOutlineColumnOptions(sheet);
sheet.getRange(0,3,25,3).hAlign(GC.Spread.Sheets.HorizontalAlign.center);
sheet.resumePaint();
}
function loadData(sheet) {
var sd = data;
sheet.setDataSource(sd);
sheet.setColumnCount(6);
sheet.setColumnWidth(0, 150);
sheet.setColumnWidth(1, 350);
sheet.setColumnWidth(2, 150);
sheet.setColumnWidth(3, 150);
sheet.setColumnWidth(4, 150);
sheet.setColumnWidth(5, 150);
for (var r = 0; r < sd.length; r++) {
var level = sd[r].level;
if(level==0)
{
sheet.getRange(r,0,1,7).backColor("#CCCCCC");
}
else if(level==1)
{
sheet.getRange(r,0,1,7).backColor("#EEEEEE");
}
sheet.getCell(r, 0).textIndent(level);
}
}
const defaultImages = ['$DEMOROOT$/spread/source/images/task-1.png', '$DEMOROOT$/spread/source/images/task-2.png', '$DEMOROOT$/spread/source/images/task-3.png'];
const defaultExpandIndicator = '$DEMOROOT$/spread/source/images/increaseIndicator.png';
const defaultCollapseIndicator = '$DEMOROOT$/spread/source/images/decreaseIndicator.png';
function initOutlineColumn(sheet) {
sheet.outlineColumn.options({
columnIndex: 0,
showImage: true,
showCheckBox: true,
images: defaultImages,
expandIndicator: defaultExpandIndicator,
collapseIndicator: defaultCollapseIndicator,
maxLevel: 2
});
sheet.showRowOutline(false);
sheet.outlineColumn.refresh();
}
function setOutlineColumnOptions(sheet) {
var picture1Url = defaultImages[0];
var picture2Url = defaultImages[1];
var picture3Url = defaultImages[2];
var indicator1Url = defaultExpandIndicator;
var indicator2Url = defaultCollapseIndicator;
var options = sheet.outlineColumn.options();
_getElementById('maxLevel').value = options.maxLevel;
_getElementById('showIndicator').addEventListener('change', function() {
sheet.outlineColumn.options().showIndicator = _getElementById('showIndicator').checked;
sheet.outlineColumn.refresh();
});
_getElementById('showCheckBox').addEventListener('change', function() {
sheet.outlineColumn.options().showCheckBox = _getElementById('showCheckBox').checked;
sheet.outlineColumn.refresh();
});
_getElementById('showImage').addEventListener('change', function() {
sheet.outlineColumn.options().showImage = _getElementById('showImage').checked;
sheet.outlineColumn.refresh();
});
_getElementById('customIndicator').addEventListener('change', function() {
if (!_getElementById('customIndicator').checked) {
_getElementById('setIndicators').setAttribute('disabled', true);
_getElementById('indicator1').setAttribute('disabled', true);
_getElementById('indicator2').setAttribute('disabled', true);
sheet.outlineColumn.options().expandIndicator = null;
sheet.outlineColumn.options().collapseIndicator = null;
sheet.outlineColumn.refresh();
} else {
_getElementById('setIndicators').removeAttribute('disabled');
_getElementById('indicator1').removeAttribute('disabled');
_getElementById('indicator2').removeAttribute('disabled');
_getElementById('setIndicators').addEventListener('click', _handel());
}
});
_getElementById('setIndicators').addEventListener('click', _handel);
function _handel(){
sheet.outlineColumn.options().expandIndicator =indicator1Url;
sheet.outlineColumn.options().collapseIndicator = indicator2Url;
sheet.outlineColumn.refresh();
}
_getElementById('setMaxLevel').addEventListener('click', function() {
sheet.outlineColumn.options().maxLevel = parseInt(_getElementById('maxLevel').value);
sheet.outlineColumn.refresh();
});
_getElementById('setImages').addEventListener('click', function() {
sheet.outlineColumn.options().images = [
picture1Url,
picture2Url,
picture3Url
];
sheet.outlineColumn.refresh();
});
_getElementById('image1').addEventListener('change', function() {
var file = this.files[0];
if (!/image\/\w+/.test(file.type)) {
alert('The file must be an image!');
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
picture1Url = this.result;
};
});
_getElementById('image2').addEventListener('change', function() {
var file = this.files[0];
if (!/image\/\w+/.test(file.type)) {
alert('The file must be an image!');
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
picture2Url = this.result;
};
});
_getElementById('image3').addEventListener('change', function() {
var file = this.files[0];
if (!/image\/\w+/.test(file.type)) {
alert('The file must be an image!');
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
picture3Url = this.result;
};
});
_getElementById('indicator1').addEventListener('change', function() {
var file = this.files[0];
if (!/image\/\w+/.test(file.type)) {
alert('The file must be an image!');
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
indicator1Url = this.result;
};
});
_getElementById('indicator2').addEventListener('change', function() {
var file = this.files[0];
if (!/image\/\w+/.test(file.type)) {
alert('The file must be an image!');
return false;
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(e) {
indicator2Url = this.result;
};
});
}
function _getElementById(id) {
return document.getElementById(id);
}
<!doctype html>
<html style="height:100%;font-size:14px;">
<head>
<meta name="spreadjs culture" content="zh-cn" />
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" type="text/css"
href="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/styles/gc.spread.sheets.excel2013white.css">
<script src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets/dist/gc.spread.sheets.all.min.js"
type="text/javascript"></script>
<script
src="$DEMOROOT$/zh/purejs/node_modules/@grapecity-software/spread-sheets-resources-zh/dist/gc.spread.sheets.resources.zh.min.js"
type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/js/license.js" type="text/javascript"></script>
<script src="$DEMOROOT$/spread/source/data/outlineColumn-wbs.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="styles.css">
</head>
<body>
<div class="sample-tutorial">
<div id="ss" class="sample-spreadsheets"></div>
<div class="options-container">
<div class="option-row">
<label>大纲列选项:</label>
</div>
<hr>
<div class="option-row">
<div class="option-row">
<input type="checkbox" id="showIndicator" checked="checked" />
<label for="showIndicator">显示指示器</label>
</div>
<div class="option-row">
<input type="checkbox" id="showCheckBox" checked="checked" />
<label for="showCheckBox">显示复选框</label>
</div>
<div class="option-row">
<input type="checkbox" id="showImage" checked="checked" />
<label for="showImage">显示图像</label>
</div>
<div class="option-row">
<input type="checkbox" id="customIndicator" checked="checked" />
<label for="customIndicator" style="width: auto">允许自定义指示器</label>
</div>
<div class="option-row">
<label for="maxLevel" class="rightAlignLabel">最大级别:</label>
<input type="text" id="maxLevel" style="width: 90px;" />
</div>
<div class="option-row" style="padding-top: 6px">
<input type="button" id="setMaxLevel" value="设置" />
</div>
</div>
<div class="option-row">
<label>自定义图像设置:</label>
</div>
<hr>
<div class="option-row">
<label for="image1" class="rightAlignLabel"> 1 级图像: </label>
<input type="file" name="img1" id="image1" />
</div>
<div class="option-row">
<label for="image2" class="rightAlignLabel"> 2 级图像: </label>
<input type="file" name="img2" id="image2" />
</div>
<div class="option-row">
<label for="image3" class="rightAlignLabel"> 3 级图像: </label>
<input type="file" name="img3" id="image3" />
</div>
<div class="option-row" style="padding-top: 6px">
<input type="button" id="setImages" value="设置" />
</div>
<div class="option-row">
<label>自定义指示器图像设置:</label>
</div>
<hr>
<div class="option-row">
<label for="indicator1" class="rightAlignLabel">展开图像:</label>
<input type="file" name="in1" id="indicator1" />
</div>
<div class="option-row">
<label for="indicator2" class="rightAlignLabel">折叠图像:</label>
<input type="file" name="in2" id="indicator2" />
</div>
<div class="option-row" style="padding-top: 6px">
<input type="button" id="setIndicators" value="设置" />
</div>
</div>
</div>
</body>
</html>
.colorLabel {
background-color: #F4F8EB;
}
.rightAlignLabel {
width: 120px;
text-align: right;
margin-right: 8px;
}
input[type="text"] {
width: 190px;
}
.sample-tutorial {
position: relative;
height: 100%;
overflow: hidden;
}
.sample-spreadsheets {
width: calc(100% - 280px);
height: 100%;
overflow: hidden;
float: left;
}
.options-container {
float: right;
width: 280px;
padding: 12px;
height: 100%;
box-sizing: border-box;
background: #fbfbfb;
overflow: auto;
}
.option-row {
font-size: 14px;
padding: 5px;
margin-top: 10px;
}
label {
margin-bottom: 6px;
}
input {
padding: 4px 6px;
}
input[type=button] {
margin-top: 6px;
width: 190px;
}
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}