前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JqGridView 1.0.0.0发布

JqGridView 1.0.0.0发布

作者头像
雪雁-心莱科技
发布2018-12-27 10:33:50
6500
发布2018-12-27 10:33:50
举报
文章被收录于专栏:magicodesmagicodes

前几个月,客户要求显示列表做到列锁定+表头锁定+列组合,但从Extjs到Jquery EasyUi,从Jquery Grid到Telerik等等组件,发现无一符合条件,要么只能用列锁定,要么只能用列组合,当两者结合就不行了。于是只好开始自己琢磨了,然后就有了jqGridView。

设计思路

在开始之前,总得理下思路。我CSS不行,JS一般,但是我有思路,先看看下面两个图:

image
image

z

image
image

从上图中可以看出,毫无疑问的,我们需要将一个列表切成4块——锁定列表头、锁定列数据行、非锁定列表头、非锁定列数据行。如图:

image
image

其中,锁定列表头、锁定列数据列、非锁定列表头均无滚动条,滚动条全在非锁定列数据列,但拖动右侧滚动条,需要联动锁定列数据行,但拖动底部滚动条,需联动非锁定列表头。

在我认为,磨刀不误砍柴工。好的想法、好的设计才能更大可能性的走向成功。

接下来,是细节的实现了:

选择什么编程语言呢?好像还没写过Jquery插件,那么就用这个来练练手吧。我对开发新东西或者实现自己的想法或者有兴趣却不熟悉的编码特别来劲。

选择什么方式呢?开始,毫无意外的想到使用Table来组合,于是坑次坑次的开始了。当编码完成后,发现一个棘手的问题——模块之间无法对齐。即使设置了每个单元格宽度以及表格宽度也不行,请了美工辅助也不行,于是放弃了。有意向的朋友可以试试。table不行,那就试试Div吧,人总不能在一条路上走死吧。于是又坑次坑次的开始了,终于修改N次后完成了。

样例

首先举几个例子来说明如何使用:

简单单行表头

代码语言:javascript
复制
    <script type="text/javascript">
        $(function() {
            $.jqGridView('<%=gvData.ClientID %>', {
                lockColumns: 3,
                isRemoveGridView: true,
                rowStyle: 'gv-tr-rowStyle',
                alternatingRowStyle: 'gv-tr-alternatingRowStyle',
                hoverRowStyle: 'gv-tr-hoverRowStyle'
            });
        });
    </script>

image
image

合并列

代码语言:javascript
复制
    <script type="text/javascript">
        $(function () {
            $.jqGridView('<%=gvData.ClientID %>', {
                lockColumns: 3,
                isRemoveGridView: true,
                rowStyle: 'gv-tr-rowStyle',
                alternatingRowStyle: 'gv-tr-alternatingRowStyle',
                hoverRowStyle: 'gv-tr-hoverRowStyle',
                leftGroupCols: '<tr><th rowspan="2" colspan="3">单位</th></tr>',
                rightGroupCols: '<tr><th scope="col" colspan="3" style="text-align:center">待定</th><th scope="col" colspan="3" style="text-align:center">1级</th><th scope="col" colspan="3" style="text-align:center">2级</th><th scope="col" colspan="3" style="text-align:center">3级</th><th scope="col" colspan="3" style="text-align:center">4级</th><th scope="col" colspan="3" style="text-align:center">5级</th><th scope="col" colspan="3" style="text-align:center">6级</th><th scope="col" colspan="3" style="text-align:center">7级</th><th scope="col" colspan="3" style="text-align:center">8级</th><th scope="col" colspan="3" style="text-align:center">9级</th><th scope="col" colspan="3" style="text-align:center">10级</th><th scope="col" colspan="3" style="text-align:center">11级</th><th scope="col" colspan="3" style="text-align:center">12级</th><th scope="col" colspan="3" style="text-align:center">13级</th><th scope="col" colspan="3" style="text-align:center">14级</th><th scope="col" colspan="3" style="text-align:center">15级</th><th scope="col" colspan="2" style="text-align:center">16级</th></tr>'
            });
        });
    </script>
image
image

行点击事件

代码语言:javascript
复制
    <script type="text/javascript">
        $(function () {
            $.jqGridView('<%=gvData.ClientID %>', {
                lockColumns: 3,
                isRemoveGridView: true,
                rowStyle: 'gv-tr-rowStyle',
                alternatingRowStyle: 'gv-tr-alternatingRowStyle',
                hoverRowStyle: 'gv-tr-hoverRowStyle',
                leftGroupCols: '<tr><th rowspan="2" colspan="3">单位</th></tr>',
                rightGroupCols: '<tr><th scope="col" colspan="3" style="text-align:center">待定</th><th scope="col" colspan="3" style="text-align:center">1级</th><th scope="col" colspan="3" style="text-align:center">2级</th><th scope="col" colspan="3" style="text-align:center">3级</th><th scope="col" colspan="3" style="text-align:center">4级</th><th scope="col" colspan="3" style="text-align:center">5级</th><th scope="col" colspan="3" style="text-align:center">6级</th><th scope="col" colspan="3" style="text-align:center">7级</th><th scope="col" colspan="3" style="text-align:center">8级</th><th scope="col" colspan="3" style="text-align:center">9级</th><th scope="col" colspan="3" style="text-align:center">10级</th><th scope="col" colspan="3" style="text-align:center">11级</th><th scope="col" colspan="3" style="text-align:center">12级</th><th scope="col" colspan="3" style="text-align:center">13级</th><th scope="col" colspan="3" style="text-align:center">14级</th><th scope="col" colspan="3" style="text-align:center">15级</th><th scope="col" colspan="2" style="text-align:center">16级</th></tr>',
                rowClick: function (e) {
                    var tds = e.data.tds;
                    var rowIndex = e.data.rIndex;
                    var isLeft = e.data.isLeft;
                    alert("列1行" + rowIndex + "的值为:" + tds.eq(0).text() + ",您点击的是" + (isLeft ? "锁定列" : "非锁定列"));
                }
            });
        });
    </script>

image
image
image
image

样式

既然决定使用div,那么样式少不了。我倾向于外观方面的控制全放在样式里面,于是定义了以下样式:

代码语言:javascript
复制
jqGridView样式/*jqGridView Style */
.gv-dataContent
{
	width: 750px;
	height: 320px;
	margin: 5px auto;
	text-align:left;
}

.gv-dataContent th, tr
{
	white-space: nowrap;
}
.gv-dataContent td, th
{
	padding: 0 0 0 0;
	margin: 0 0 0 0;
	white-space: nowrap;
}
.gv-header-left
{
	width: auto;
	overflow: hidden;
	float: left;
	border-left: 1px solid #539ebb;
	border-collapse: collapse;
}
.gv-header-right
{
	overflow: hidden;
	border-collapse: collapse;
}
.gv-data-left
{
	width: auto;
	overflow: hidden;
	float: left;
	border-left: 1px solid #539ebb;
	border-bottom: 1px solid #539ebb;
	border-collapse: collapse;
}
.gv-data-right
{
	overflow: scroll;
	border-collapse: collapse;
}
.gv-div-table
{
	border-collapse: collapse;
	width: auto;
}
.gv-div-tr
{
	clear: both;
	height: 30px;
	overflow: hidden;
}
.gv-div-th, .gv-div-td
{
	height: 30px;
	float: left;
	border: 1px solid white;
	margin: 0px 0 0 -1px;
	text-align: center;
	vertical-align: middle;
	line-height: 30px;
	border-collapse: collapse;
	float: left;
	color: #3274a4;
}
.gv-div-th
{
	background-color: #86A9D2;
	font-weight: bold;
	border: 1px solid white;
}
.gv-tr-rowStyle
{
	background-color: #8ecbe3;
}
.gv-tr-alternatingRowStyle
{
	background-color: #d3ebf4;
}
.gv-tr-hoverRowStyle
{
	background-color: #6786d6;
	cursor:pointer;
}
/*GridView 样式*/
.HeaderStyle
{
	background-color:#86A9D2;
	font-weight:bold;
	height:26px;
	white-space: nowrap;
	text-align:center;
	border: 1px solid white;
	color: #3274a4;
}
.RowStyle
{
	background-color: #8ecbe3;
	height:26px;
	text-align:center;
	white-space: nowrap;
	border: 1px solid white;
	color: #3274a4;
}
.AlternatingRowStyle
{
	background-color: #d3ebf4;
	height:26px;
	white-space: nowrap;
	border: 1px solid white;
	color: #3274a4;
	text-align: center;
}
.datacontent
{	
	font-size:12px;
	font-family:微软雅黑;
	width:740px;
    border:1px solid #7C9BBF; 
    margin:0 auto;
	clear: both; 
	text-align: center; 	
	*text-align: center; 
	margin-left: 5px;
	padding:5px;
	width:760px;
	overflow:auto;
      
}

从样式中可以看出大体的规则,也是仿照table元素来的。table、tr、th、td等等。其中有几个重要样式要注意了:

  1. .gv-dataContent    整个jqGridView的整体样式
  2. .gv-header-left      左侧表头区域
  3. .gv-header-right    右侧表头区域
  4. .gv-data-left    左侧数据区域
  5. .gv-data-right  右侧数据区域
  6. .gv-div-table   表示一个表格,每个区域均存在
  7. .gv-div-tr    表示行样式
  8. .gv-div-th   表示表头单元格样式
  9. .gv-div-td   表示单元格样式
  10. .gv-tr-rowStyle   表示奇数行样式
  11. .gv-tr-alternatingRowStyle   表示偶数行样式
  12. .gv-tr-hoverRowStyle  表示悬浮行样式

编码

先贴上代码:

1: /** 2: * 本插件用于实现GridView的列锁定和表头锁定,以及表头组合 3: * @example $.jqGridView('<%=gvData.ClientID %>', { lockColumns: 3 }); 4: * @param String gridViewClientID GridView的客户端ID 5: * @option Number lockColumns 锁定的列数。如果包含合并表头,请与合并表头的列数一致。 6: * @option String leftGroupCols 左侧合并列的HTML,不设置则默认为单行表头。 7: * @option String rightGroupCols 右侧合并列的HTML,不设置则默认为单行表头。 8: * @option String removeLeftHeaderStrBySplit 根据分隔符移除左侧头部字符。 9: * @option String removeRightHeaderStrBySplit 根据分隔符移除右侧头部字符。 10: * @option String rowStyle 偶数行样式。 11: * @option String alternatingRowStyle 奇数行样式。 12: * @option String hoverRowStyle 悬浮行样式。 13: * @option Bool isRemoveEmptyAndZeroCols 是否移除空列或者0列。 14: * @option Bool isHideGridView 是否在处理后隐藏GridView。 15: * @option Bool isRemoveGridView 是否在处理后移除GridView。 16: * @option String emptyMessage 没有数据时显示的内容,默认为“没有数据。”。 17: * @option event rowClick 行单击事件。 18: * @author 雪雁 19: * @email codelove1314@foxmail.com 20: * @webSite http://www.cnblogs.com/codelove/ 21: */ 22: jQuery.jqGridView = function (gridViewClientID, options) { 23: if (gridViewClientID !== undefined && options !== undefined) { 24: function formatHeaderHtml(html) { 25: return html.replace(/\<tr/g, '<div class="gv-div-tr" ').replace(/<\/tr>/g, '</div>') 26: .replace(/\<th/g, '<div class="gv-div-th" ').replace(/<\/th>/g, '</div>') 27: .replace(/\<td/g, '<div class="gv-div-th" ').replace(/<\/td>/g, '</div>'); 28: } 29: //锁定的列数 30: var lockColumns = options.lockColumns === undefined ? 1 : options.lockColumns; 31: //左侧组合列HTML 32: var leftGroupCols = $(options.leftGroupCols === undefined ? '' : formatHeaderHtml(options.leftGroupCols)); 33: //右侧组合列HTML 34: var rightGroupCols = $(options.rightGroupCols === undefined ? '' : formatHeaderHtml(options.rightGroupCols)); 35: //根据分隔符移除左侧头部字符 36: var removeLeftHeaderStrBySplit = options.removeLeftHeaderStrBySplit === undefined ? '' : options.removeLeftHeaderStrBySplit; 37: var removeRightHeaderStrBySplit = options.removeRightHeaderStrBySplit === undefined ? '' : options.removeRightHeaderStrBySplit; 38: //偶数行样式 39: var rowStyle = options.rowStyle === undefined ? '' : options.rowStyle; 40: //奇数行样式 41: var alternatingRowStyle = options.alternatingRowStyle === undefined ? '' : options.alternatingRowStyle; 42: //鼠标悬浮行样式 43: var hoverRowStyle = options.hoverRowStyle === undefined ? '' : options.hoverRowStyle; 44: var isSafari = $.browser.safari; 45: //数据空显示内容 46: var emptyMessage = options.emptyMessage === undefined ? '没有数据。' : options.emptyMessage; 47: var gvData = $('#' + gridViewClientID); 48: if (!gvData || gvData.length == 0) { 49: console.error("GridView不存在,请检查!!!", gridViewClientID, options); 50: return; 51: } 52: //是否移除空列或者0列 53: if (options.isRemoveEmptyAndZeroCols !== undefined && options.isRemoveEmptyAndZeroCols) { 54: var arr_remove = new Array(gvData.find('tr:eq(0) th').length); 55: var rowsCount = gvData.find('tr:gt(0)').each(function (rIndex) { 56: var tr = $(this); 57: tr.find('td').each(function (cIndex) { 58: if (arr_remove[cIndex] === undefined || arr_remove[cIndex] == null) 59: arr_remove[cIndex] = 0; 60: var val = $(this).text().replace(/(^\s*)|(\s*$)/g, ""); 61: if (val == '' || val == 0) { 62: arr_remove[cIndex]++; 63: } 64: }); 65: }).length; 66: gvData.find('tr').each(function (rIndex) { 67: var tr = $(this); 68: tr.find('td,th').each(function (cIndex) { 69: if (arr_remove[cIndex] == rowsCount) 70: $(this).remove(); 71: }); 72: }); 73: } 74: var leftCols = lockColumns - 1; 75: var rightCols = lockColumns; 76: var isRemoveGridView = options.isRemoveGridView === undefined ? true : options.isRemoveGridView; 77: //所有列宽 78: var colsLengsArr = new Array(); 79: var colsCount = gvData.find('tr:eq(0) th').each(function (i) { 80: colsLengsArr[i] = ($(this).outerWidth() + 1); 81: }).length; 82: if (lockColumns >= colsCount) lockColumns = colsCount; 83: //左侧table宽度 84: var leftTableWidth = 1; 85: //右侧table宽度 86: var rightTableWidth = 1; 87: for (var i = 0; i < lockColumns; i++) { 88: leftTableWidth += (colsLengsArr[i] + 1); 89: if (isSafari) leftTableWidth += 0.3; 90: } 91: for (var i = lockColumns; i < colsLengsArr.length; i++) { 92: rightTableWidth += (colsLengsArr[i] + 1); 93: if (isSafari) rightTableWidth += 0.3; 94: } 95:  96: gvData.parent().prepend('<div class="gv-dataContent"></div>'); 97: var gv_dataContent = $('.gv-dataContent'); 98: if (gvData.find('tr').length <= 1) { 99: gv_dataContent.prepend('<div class="gv-empty">' + emptyMessage + '</div>'); 100: return; 101: } 102: //右侧区域宽度 103: var rightAreaWidth = gv_dataContent.width() - (leftTableWidth + 25); 104: //数据区域高度 105: var dataAreaHeight = gv_dataContent.height(); 106:  107: gv_dataContent.prepend('<div class="gv-header-left"></div><div class="gv-header-right"></div><div class="gv-data-left"></div><div class="gv-data-right"></div>'); 108: var gv_header_left = gv_dataContent.find('div.gv-header-left'); 109: var gv_header_right = gv_dataContent.find('div.gv-header-right'); 110: var gv_data_left = gv_dataContent.find('div.gv-data-left'); 111: var gv_data_right = gv_dataContent.find('div.gv-data-right'); 112: if (lockColumns == colsCount) { 113: gv_header_right.hide(); gv_data_right.hide(); 114: } else { 115: if (rightAreaWidth > 0) { 116: gv_header_right.width(rightAreaWidth); 117: gv_data_right.width(rightAreaWidth + 18); 118: } 119: } 120: var gvData_header_left = gvData.clone(); 121: gvData_header_left.find('tr:gt(0)').remove(); 122:  123: var gvData_header_right = gvData_header_left.clone(); 124: gv_header_right.find('tr th').remove(); 125:  126: gv_data_right.find('tr:eq(0)').prepend(gvData_header_left.find('th:gt(' + lockColumns + ')').clone()); 127: gvData_header_right.find('th:lt(' + rightCols + ')').remove(); 128: gvData_header_left.find('th:gt(' + leftCols + ')').remove(); 129: var colIndex = 0; 130:  131: function setThs(jqTr, jqHeader, isLeft) { 132: // var trHtml = '<div class="gv-div-table" style="width:' + (isLeft ? leftTableWidth : rightTableWidth) + 'px;"><div class="gv-div-tr">'; 133: var trHtml = '<div class="gv-div-table" style="width:' + (isLeft ? 'auto' : (rightTableWidth + 'px;')) + '"><div class="gv-div-tr">'; 134: jqTr.find('th').each(function (j) { 135: trHtml += '<div class="gv-div-th" style="width:' + colsLengsArr[colIndex] + 'px;">'; 136: if (removeLeftHeaderStrBySplit != '') { 137: var splitStrs = $(this).text().split(removeLeftHeaderStrBySplit); 138: trHtml += splitStrs.length > 1 ? splitStrs[1] : splitStrs[0]; 139: } else if (removeRightHeaderStrBySplit != '') { 140: var splitStrs = $(this).text().split(removeRightHeaderStrBySplit); 141: trHtml += splitStrs[0]; 142: } 143: else 144: trHtml += $(this).html(); 145: trHtml += '</div>'; 146: colIndex++; 147: }); 148: trHtml += '</div></div>'; 149: jqHeader.prepend(trHtml); 150: } 151:  152: //设置左侧头部HTML 153: setThs(gvData_header_left, gv_header_left, true); 154: //设置右侧头部HTML 155: setThs(gvData_header_right, gv_header_right, false); 156: // var gvData_Data_left = $('<div class="gv-div-table" style="width:' + leftTableWidth + 'px;"></div>'); 157: var gvData_Data_left = $('<div class="gv-div-table" style="width:auto;"></div>'); 158: var gvData_Data_right = $('<div class="gv-div-table" style="width:' + rightTableWidth + 'px;"></div>'); 159: gvData.find("tr:gt(0)").each(function (i) { 160: var tr = $(this); 161: var trLeft = tr.clone(); 162: var trRight = tr.clone(); 163: trLeft.find('td:gt(' + leftCols + ')').remove(); 164: trRight.find('td:lt(' + rightCols + ')').remove(); 165: colIndex = 0; 166: function setTrTds(tr_left, gvData_Data_left, tr_right, gvData_Data_right, trInfo) { 167: var trLeftHtml = '<div class="gv-div-tr'; 168: if (rowStyle != '' && i % 2 == 0) 169: trLeftHtml += ' ' + rowStyle; 170: else if (alternatingRowStyle != '' && i % 2 == 1) 171: trLeftHtml += ' ' + alternatingRowStyle; 172: trLeftHtml += '">'; 173: var trRightHtml = trLeftHtml; 174: tr_left.find('td').each(function (j) { 175: trLeftHtml += '<div class="gv-div-td" style="width:' + colsLengsArr[colIndex] + 'px;">' + $(this).html() + '</div>'; 176: colIndex++; 177: }); 178: tr_right.find('td').each(function (j) { 179: trRightHtml += '<div class="gv-div-td" style="width:' + colsLengsArr[colIndex] + 'px;">' + $(this).html() + '</div>'; 180: colIndex++; 181: }); 182: trLeftHtml += '</div>'; trRightHtml += '</div>'; 183: var jqLeftTrHrml = $(trLeftHtml); var jqRightTrHrml = $(trRightHtml); 184: if (options.rowClick !== undefined) { 185: jqLeftTrHrml.bind('click', { tds: trInfo.find('td'), rIndex: i, isLeft: true }, options.rowClick); 186: jqRightTrHrml.bind('click', { tds: trInfo.find('td'), rIndex: i, isLeft: false }, options.rowClick); 187: } 188: if (hoverRowStyle != '') { 189: jqLeftTrHrml.hover(function () { jqLeftTrHrml.addClass(hoverRowStyle); jqRightTrHrml.addClass(hoverRowStyle); }, function () { jqLeftTrHrml.removeClass(hoverRowStyle); jqRightTrHrml.removeClass(hoverRowStyle); }); 190: jqRightTrHrml.hover(function () { jqLeftTrHrml.addClass(hoverRowStyle); jqRightTrHrml.addClass(hoverRowStyle); }, function () { jqLeftTrHrml.removeClass(hoverRowStyle); jqRightTrHrml.removeClass(hoverRowStyle); }); 191: } 192: gvData_Data_left.append(jqLeftTrHrml); 193: gvData_Data_right.append(jqRightTrHrml); 194: } 195: setTrTds(trLeft, gvData_Data_left, trRight, gvData_Data_right, tr); 196: }); 197: gv_data_left.prepend(gvData_Data_left); 198: gv_data_right.prepend(gvData_Data_right); 199: if (options.isHideGridView !== undefined && options.isHideGridView) 200: gvData.hide(); 201: if (isRemoveGridView) 202: gvData.remove(); 203: if (leftGroupCols != '' && rightGroupCols != '') { 204: dataAreaHeight -= 62; 205: colIndex = 0; 206: function calcGroupCol(groupCols) { 207: var groupThs = groupCols.find('.gv-div-th'); 208: groupThs.each(function (i) { 209: var col_width = 0; 210: if ($(this).attr('colspan') !== undefined) { 211: var colSpan = parseInt($(this).attr('colspan')); 212: for (var i = 0; i < colSpan; i++) { 213: col_width += colsLengsArr[colIndex]; 214: colIndex++; 215: } 216: col_width += (colSpan - 1); 217: } 218: else if ($(this).attr('rowspan') !== undefined) { 219: var rowspan = parseInt($(this).attr('rowspan')); 220: col_height = rowspan * 30 + (rowspan - 1); 221: $(this).height(col_height).css('border-bottom-style', 'none'); 222: col_width = colsLengsArr[colIndex]; 223: if (colIndex <= leftCols) 224: gv_header_left.find('.gv-div-th').eq(colIndex).html('').css('border-top-style', 'none'); 225: else if (colIndex >= rightCols) 226: gv_header_right.find('.gv-div-th').eq(colIndex - lockColumns).html('').css('border-top-style', 'none'); 227: colIndex++; 228: } 229: else { 230: col_width = colsLengsArr[colIndex]; 231: colIndex++; 232: } 233: $(this).width(col_width); 234: }); 235: } 236: calcGroupCol(leftGroupCols); 237: calcGroupCol(rightGroupCols); 238:  239: gv_header_left.find('.gv-div-table').prepend(leftGroupCols); 240: gv_header_right.find('.gv-div-table').prepend(rightGroupCols); 241: } 242: else 243: dataAreaHeight -= 31; 244: if (dataAreaHeight > 0) { 245: gv_data_left.height(dataAreaHeight - 18); 246: gv_data_right.height(dataAreaHeight); 247: } 248: //设置滚动事件 249: $('.gv-data-right').scroll(function () { 250: $('.gv-data-left').scrollTop($(this).scrollTop()); 251: $('.gv-header-right').scrollLeft($(this).scrollLeft()); 252: }); 253: } 254: };

最后

虽然实现了上面的一些功能,但还是有些缺陷让人遗憾:

  1. 本人美工不行,样式上有些瑕疵。
  2. 测试过IE7、IE8、IE9与Chrome浏览器,目前是正常的,但Chrome浏览器可能有不兼容的情况出现。这点需要有时间慢慢解决。
  3. 不能支持超过2行以上的组合表头,不支持行合并。因为整个jqGridView均使用div组合,所以在行合并上是软肋,不知道各位朋友有什么好的方案没。

如果您对jqGridView感兴趣,或者有好的解决方案,或者对源码做了一些修正,请通知我。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2012-07-29 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 设计思路
  • 样例
    • 简单单行表头
      • 合并列
        • 行点击事件
        • 样式
        • 编码
        • 最后
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档