";
+ $filterHtml['default'] .= html::select('operator', $this->config->bi->conditionList, $operator, "class='form-control picker-select' onchange='changeCondition(this,this.value)'");
+ $filterHtml['default'] .= html::input('value', $value, "class='form-control $hidden' onchange='changeConditionValue(this,this.value)'");
+ $filterHtml['default'] .= "
";
+ }
+
+ /* type html */
+ $filterHtml['type'] = html::select('type', $this->lang->chart->fieldTypeList, $type, "class='form-control picker-select' onchange='changeType(this, this.value)'");
+
+ /* filter item html */
+ $filterHtml['item'] = '';
+ if($type == 'input') $filterHtml['item'] .= html::input('default', $default, "class='form-control'");
+ if($type == 'date' or $type == 'datetime')
+ {
+ if(empty($default)) $default = array('begin' => '', 'end' => '');
+ $class = $type == 'date' ? 'form-date' : 'form-datetime';
+ $filterHtml['item'] .= '
';
+ }
+ if($type == 'select') $filterHtml['item'] .= html::select('default', array('' => '') + $options, $default, "class='form-control picker-select' multiple");
+ if($type == 'condition')
+ {
+ $operator = isset($filter['operator']) ? $filter['operator'] : '';
+ $value = isset($filter['value']) ? $filter['value'] : '';
+ $hidden = strpos($operator, 'NULL') !== false ? 'hidden' : '';
+ $filterHtml['item'] = "
";
+ $filterHtml['item'] .= html::select('operator', $this->config->bi->conditionList, $operator, "class='form-control picker-select' onchange='changeCondition(this,this.value, true)'");
+ $filterHtml['item'] .= html::input('value', $value, "class='form-control $hidden'");
+ $filterHtml['item'] .= "
";
+ }
+
+ $htmls[] = $filterHtml;
+ }
+
+ echo json_encode($htmls);
+ }
+
+ /**
+ * Ajax get chart.
+ *
+ * @access public
+ * @return void
+ */
+ public function ajaxGetChart()
+ {
+ $post = fixer::input('post')->skipSpecial('settings,filters,sql,langs')->get();
+
+ $chartID = $post->id;
+ $settings = current($post->settings);
+ $type = $settings['type'];
+ $filters = isset($_POST['searchFilters']) ? $_POST['searchFilters'] : (isset($_POST['filters']) ? $post->filters : array());
+
+ $chart = $this->chart->getByID($chartID);
+
+ $filterFormat = array();
+ foreach($filters as $filter)
+ {
+ $field = $filter['field'];
+ if(!isset($filter['default'])) continue;
+
+ $default = $filter['default'];
+ switch($filter['type'])
+ {
+ case 'select':
+ if(empty($default)) break;
+ $default = array_filter($default, function($val){return !empty($val);});
+ $value = "('" . implode("', '", $default) . "')";
+ $filterFormat[$field] = array('operator' => 'IN', 'value' => $value);
+ break;
+ case 'input':
+ $filterFormat[$field] = array('operator' => 'like', 'value' => "'%$default%'");
+ break;
+ case 'date':
+ case 'datetime':
+ $begin = $default['begin'];
+ $end = $default['end'];
+
+ if(empty($begin) or empty($end)) break;
+
+ $value = "'$begin' and '$end'";
+ $filterFormat[$field] = array('operator' => 'BETWEEN', 'value' => $value);
+ break;
+ case 'condition':
+ $operator = $filter['operator'];
+ $value = $filter['value'];
+
+ if(in_array($operator, array('IN', 'NOT IN')))
+ {
+ $valueArr = explode(',', $value);
+ foreach($valueArr as $key => $val) $valueArr[$key] = '"' . $val . '"';
+ $value = '(' . implode(',', $valueArr) . ')';
+ }
+ elseif(in_array($operator, array('IS NOT NULL', 'IS NULL')))
+ {
+ $value = '';
+ }
+ $filterFormat[$field] = array('operator' => $operator, 'value' => $value);
+ break;
+ }
+ }
+
+ $sql = str_replace(';', '', "$post->sql");
+ $fields = $post->fieldSettings;
+ $langs = isset($post->langs) ? $post->langs : array();
+
+ switch($type)
+ {
+ case 'line':
+ $data = $this->chart->genLineChart($fields, $settings, $sql, $filterFormat, $langs);
+ break;
+ case 'cluBarX':
+ $data = $this->chart->genCluBar($fields, $settings, $sql, $filterFormat, '', $langs);
+ break;
+ case 'cluBarY':
+ $data = $this->chart->genCluBar($fields, $settings, $sql, $filterFormat, '', $langs);
+ break;
+ case 'pie':
+ $data = $this->chart->genPie($fields, $settings, $sql, $filterFormat);
+ break;
+ case 'radar':
+ $data = $this->chart->genRadar($fields, $settings, $sql, $filterFormat, $langs);
+ break;
+ case 'stackedBar':
+ $data = $this->chart->genCluBar($fields, $settings, $sql, $filterFormat, 'total', $langs);
+ break;
+ case 'stackedBarY':
+ $data = $this->chart->genCluBar($fields, $settings, $sql, $filterFormat, 'total', $langs);
+ break;
+ }
+
+ echo json_encode($data);
+ }
+}
diff --git a/module/chart/css/common.css b/module/chart/css/common.css
new file mode 100644
index 0000000000..daca1be13f
--- /dev/null
+++ b/module/chart/css/common.css
@@ -0,0 +1,7 @@
+.table-panel {overflow-x: auto;}
+.datatable {width: auto;}
+.datatable th.header {line-height: 32px !important;}
+.header-config {float: right; display: none;}
+.header:hover .header-config {display: inline-block;}
+.label-outline.label-info {border: unset; line-height: 14px;}
+.border-bottom {border-bottom: 1px solid #f4f5f7;}
diff --git a/module/chart/css/preview.css b/module/chart/css/preview.css
new file mode 100644
index 0000000000..e865798174
--- /dev/null
+++ b/module/chart/css/preview.css
@@ -0,0 +1,51 @@
+.m-chart-preview {color: rgb(49, 60, 82); background-color: rgb(244, 245, 247);}
+.main-col .btn-toolbar {margin-bottom: 10px;}
+.main-position {position: relative;}
+.child-position {position: absolute; right: 0;}
+.parent-position{padding-right: 180px;}
+
+#chartGroups li {overflow: hidden; white-space: nowrap;}
+#chartGroups .checkbox-primary {margin-left: 8px; display: inline-block;}
+#chartGroups .checkbox-primary + a {display: inline-block;}
+#chartGroups .checkbox-primary > input[type=checkbox][name^=chart] + label {padding-left: 0px;}
+
+.chart-item + .chart-item {margin-top: 10px;}
+.chartbox {
+ height: 400px;
+ overflow: overlay;
+ border: 1px solid #ddd;
+}
+.ant-table-tbody>tr>td, .ant-table-thead>tr>th {
+ padding: 8px!important;
+}
+.table-title {
+ top: 10px;
+ right: auto;
+ bottom: auto;
+ left: 10px;
+ width: auto;
+ height: auto;
+ margin: 0px 0px 10px 10px;
+ font-size: 18px;
+ font-weight: bold;
+ text-align: left;
+}
+
+#subNavbar .nav li.active a {display: none;}
+
+#mainContent .cell {padding: 11px;}
+#mainContent .panel {border: 0;}
+#mainContent .chart-item.main-row x-vue-echarts {max-height: 460px;}
+#mainContent .chart-item.main-row .ant-table-wrapper {max-height: 398px; overflow-y: overlay !important;border: 1px solid #e8e8e8; border-right: 0;}
+#mainContent .chart-item.main-row .ant-table-bordered .ant-table-body>table {border: 0;}
+#mainContent .chart-item.main-row .ant-table-bordered .ant-table-body>table tr:last-child td {border-bottom: 0;}
+
+.echart-content {width: 100%; min-height: 500px;}
+
+.filterBox{display: flex; margin-bottom: 20px;}
+.filter-items{display: flex; flex: 1; flex-wrap: wrap;}
+.filter-item{padding: 0 16px 5px 0;}
+.filter-item-grow{flex-grow: 1;}
+.queryBtn{display: flex; align-items: center; padding-bottom: 5px;}
+
+.filterBox .picker-selections{width: 128px; height: 32px; overflow: hidden; text-overflow: clip; white-space: nowrap;}
diff --git a/module/chart/js/common.js b/module/chart/js/common.js
new file mode 100644
index 0000000000..9e727b3bcb
--- /dev/null
+++ b/module/chart/js/common.js
@@ -0,0 +1,56 @@
+/**
+ * Ajax get chart data.
+ *
+ * @access public
+ * @return bool
+ */
+function ajaxGetChart(check = true, chart = DataStorage.chart, echart = window.echart)
+{
+ var chartParams = JSON.parse(JSON.stringify(chart));
+ if(typeof DataStorage != 'undefined') chartParams.fieldSettings = JSON.parse(JSON.stringify(DataStorage.fieldSettings));
+ if(typeof DataStorage != 'undefined') chartParams.langs = JSON.parse(JSON.stringify(DataStorage.langs));
+
+ /* Redraw echart. */
+ /* 拿数据并重绘图表。*/
+ $.post(createLink('chart', 'ajaxGetChart'), chartParams, function(resp)
+ {
+ var data = JSON.parse(resp);
+ if(echart)
+ {
+ echart.clear();
+ echart.setOption(data, true);
+ $('.btn-export').removeClass('hidden');
+ }
+ });
+}
+
+/**
+ * Init picker.
+ *
+ * @access public
+ * @return void
+ */
+function initPicker($row, pickerName = 'picker-select')
+{
+ $row.find('.' + pickerName).picker({'maxDropHeight': pickerHeight});
+ $row.find("." + pickerName).each(function(index)
+ {
+ if($(this).hasClass('required')) $(this).siblings("div .picker").addClass('required');
+ });
+}
+
+/**
+ * Init datepicker.
+ *
+ * @param object $obj
+ * @param function callback
+ * @access public
+ * @return void
+ */
+function initDatepicker($obj, callback)
+{
+ $obj.find('.form-date').datepicker();
+ $obj.find('.form-datetime').datetimepicker();
+
+ if(typeof callback == 'function') callback($obj);
+}
diff --git a/module/chart/js/preview.js b/module/chart/js/preview.js
new file mode 100644
index 0000000000..44eb412c0e
--- /dev/null
+++ b/module/chart/js/preview.js
@@ -0,0 +1,215 @@
+$(function()
+{
+ initCheckBox();
+ initQueryBtn();
+ $('#exportchart').modalTrigger();
+
+ chartMap = new Map();
+ if(charts.length > 0)
+ {
+ charts.forEach(function(chart)
+ {
+ var echartDom = $('#chartDraw' + chart.currentGroup + chart.id).get(0);
+ var echart = echarts.init(echartDom);
+ ajaxGetChart(false, chart, echart);
+ renderFilters(chart);
+
+ chartMap.set(chart.currentGroup + chart.id, chart);
+ });
+ }
+
+ calcPreviewGrowFilter();
+ $('body').resize(() => {calcPreviewGrowFilter(true);});
+
+ $('[data-toggle="tooltip"]').tooltip();
+})
+
+/**
+ * Init check box.
+ *
+ * @access public
+ * @return void
+ */
+function initCheckBox()
+{
+ /* Use parent checked prop set all child. */
+ $('input[data-type="parent"]').click(function()
+ {
+ $(this).parent().siblings('ul').find('input[data-type="child"]').prop('checked', $(this).prop('checked'));
+ });
+
+ $('input[data-type="child"]').click(function()
+ {
+ /* If child unchecked after click, set parent unchecked. */
+ if(!$(this).prop('checked'))
+ {
+ $(this).parentsUntil('#chartGroups', 'li').find('div').children('input[data-type="parent"]').prop('checked', false);
+ }
+ /* If child checked after click, if all child checked, set parent checked. */
+ else
+ {
+ if($(this).closest('ul').find('input[data-type="child"]').length >= 1)
+ {
+ var siblings_checked = $(this).closest('ul').find('input[data-type="child"]:checked').length;
+ var siblings_total = $(this).closest('ul').find('input[data-type="child"]').length;
+ if(siblings_checked == siblings_total)
+ {
+ $(this).parentsUntil('#chartGroups', 'li').find('div').children('input[data-type="parent"]').prop('checked', true);
+ }
+ }
+ }
+ });
+
+ /* Use click to trigger event. */
+ charts.forEach(function(item)
+ {
+ $("#chart_" + item.currentGroup + '_' + item.id).click();
+ });
+}
+
+/**
+ * Init query button.
+ *
+ * @access public
+ * @return void
+ */
+function initQueryBtn()
+{
+ $('.btn-query').click(function()
+ {
+ var chartID = $(this).attr('data-chart');
+ var chartInfo = chartMap.get(chartID);
+ chartInfo.searchFilters = JSON.parse(JSON.stringify(chartInfo.filters));
+
+ // Foreach filters and set current value in searchFilters. */
+ $('#filterItems' + chartID + ' .filter-items').children().each(function(index)
+ {
+ var $child = $(this);
+ var filter = chartInfo.searchFilters[index];
+ var type = filter.type;
+ var value = null;
+
+ if(type == 'input' || type == 'select')
+ {
+ var value = $child.find('#default').val();
+ chartInfo.searchFilters[index].default = value;
+ }
+ else if(type == 'date' || type == 'datetime')
+ {
+ var begin = $child.find('input').first().val();
+ var end = $child.find('input').last().val();
+
+ var value = {"begin":begin, "end":end};
+ chartInfo.searchFilters[index].default = value;
+ }
+ else if(type == 'condition')
+ {
+ var operator = $child.find('#operator').data('zui.picker').getValue();
+ var value = $child.find('#value').val();
+
+ chartInfo.searchFilters[index].operator = operator;
+ chartInfo.searchFilters[index].value = value;
+ }
+ });
+
+ //* Reload echart. */
+ var echartDom = $('#chartDraw' + chartID).get(0);
+ var echartInfo = echarts.init(echartDom);
+ ajaxGetChart(false, chartInfo, echartInfo);
+ });
+}
+
+function calcPreviewGrowFilter(resize = false)
+{
+ if(charts.length <= 0) return;
+ charts.forEach(function(chart)
+ {
+ /* When body resize, resize echarts. */
+ if(resize)
+ {
+ var echartDom = $('#chartDraw' + chart.currentGroup + chart.id).get(0);
+ var echart = echarts.init(echartDom);
+ echart.resize();
+ }
+
+ var $filterItems = $('#filterItems' + chart.currentGroup + chart.id + ' .filter-items');
+ /* When refreshing this page, ZenTao load this page twice, when the first load isn't complete, jump back to index and load the second time,
+ the first load can't calc filter width, can't get the element using jQuery at first load. */
+ var hasInit = true;
+ chart.filters.forEach(function(filter, index)
+ {
+ var nowItem = '.filter-item-' + index;
+ var $titleSpan = $filterItems.find(nowItem).find('.input-group-addon').first();
+ if(!$titleSpan.length) hasInit = false;
+ });
+ if(!hasInit) return;
+
+ var domWidth = $filterItems[0].getBoundingClientRect().width;
+ var nowWidth = domWidth;
+ var nowCount = 0;
+ var canGrowTotal = 0;
+ chart.filters.forEach(function(filter, index)
+ {
+ var nowItem = '.filter-item-' + index;
+ var leftPadding = parseInt($filterItems.find(nowItem).css('padding-left'));
+ var rightPadding = parseInt($filterItems.find(nowItem).css('padding-right'));
+ var spanWidth = $filterItems.find(nowItem).find('.input-group-addon').first()[0].getBoundingClientRect().width;
+ var filterWidth = ((filter.type == 'input' || filter.type == 'select') ? WIDTH_INPUT : WIDTH_DATE) + (spanWidth + leftPadding + rightPadding);
+
+ /* Clear the flex-basis and set flex-basic again. */
+ $filterItems.find(nowItem).css('flex-basis', '');
+ $filterItems.find(nowItem).css('flex-basis', filterWidth);
+ if(nowWidth - filterWidth >= 0)
+ {
+ nowWidth -= filterWidth;
+ nowCount ++;
+ }
+ else
+ {
+ canGrowTotal += nowCount;
+ nowWidth = domWidth - filterWidth;
+ nowCount = 1;
+ }
+ });
+
+ /* Clear all filter filter-item-grow and add class to support grow element. */
+ $filterItems.children().removeClass('filter-item-grow');
+ chart.filters.forEach(function(filter, index)
+ {
+ var nowItem = '.filter-item-' + index;
+ if(canGrowTotal >= index + 1) $filterItems.find(nowItem).addClass('filter-item-grow');
+ });
+ });
+}
+
+function renderFilters(chart)
+{
+ if(chart.filters.length == 0) return;
+
+ var fieldNames = {};
+ Object.keys(chart.fieldSettings).forEach(function(key){fieldNames[key] = chart.fieldSettings[key].name;});
+ $.post(createLink('chart', 'ajaxGetFilterForm', 'chartID=' + chart.id), {fieldList: fieldNames, fieldSettings: chart.fieldSettings, filters: chart.filters, langs: chart.langs}, function(resp)
+ {
+ resp = JSON.parse(resp);
+ chart.filters.forEach(function(filter, index)
+ {
+ var $filterItems = $('#filterItems' + chart.currentGroup + chart.id + ' .filter-items');
+ $filterItems.append(renderFilterItem(filter, resp, index));
+ });
+ });
+}
+
+function renderFilterItem(filter, resp, index, step)
+{
+ var tpl = $('#filterItemTpl').html();
+ var data =
+ {
+ index: index,
+ name: filter.name,
+ search: resp[index].item
+ };
+ var html = $($.zui.formatString(tpl, data))
+ initPicker(html);
+ initDatepicker(html);
+ return html;
+}
diff --git a/module/chart/lang/de.php b/module/chart/lang/de.php
new file mode 100644
index 0000000000..ef7fc798b9
--- /dev/null
+++ b/module/chart/lang/de.php
@@ -0,0 +1,33 @@
+chart->preview = 'View Chart';
+$lang->chart->query = 'Query';
+$lang->chart->toDesign = 'To Design';
+
+$lang->chart->group = 'Group';
+$lang->chart->field = 'Field';
+$lang->chart->agg = 'Aggregate';
+$lang->chart->chooseField = 'Choose field';
+$lang->chart->aggType = 'Aggregate type';
+$lang->chart->other = 'Other';
+$lang->chart->unlimited = 'Unlimited';
+$lang->chart->colon = 'To';
+
+$lang->chart->fieldTypeList = array();
+$lang->chart->fieldTypeList['input'] = 'Text';
+$lang->chart->fieldTypeList['date'] = 'Date';
+$lang->chart->fieldTypeList['datetime'] = 'Time';
+$lang->chart->fieldTypeList['select'] = 'Dropdown';
+//$lang->chart->fieldTypeList['condition'] = 'Condition';
+
+$lang->chart->groupWeek = 'Week %s,%s';
+
+$lang->chart->noChart = 'No Chart';
+$lang->chart->noChartSelected = 'Please select one chart.';
+
+$lang->chart->aggList = array();
+$lang->chart->aggList['count'] = 'Count';
+$lang->chart->aggList['distinct'] = 'Count after distinct';
+$lang->chart->aggList['avg'] = 'AVG';
+$lang->chart->aggList['sum'] = 'SUM';
+$lang->chart->aggList['max'] = 'MAX';
+$lang->chart->aggList['min'] = 'MIN';
diff --git a/module/chart/lang/en.php b/module/chart/lang/en.php
new file mode 100644
index 0000000000..ef7fc798b9
--- /dev/null
+++ b/module/chart/lang/en.php
@@ -0,0 +1,33 @@
+chart->preview = 'View Chart';
+$lang->chart->query = 'Query';
+$lang->chart->toDesign = 'To Design';
+
+$lang->chart->group = 'Group';
+$lang->chart->field = 'Field';
+$lang->chart->agg = 'Aggregate';
+$lang->chart->chooseField = 'Choose field';
+$lang->chart->aggType = 'Aggregate type';
+$lang->chart->other = 'Other';
+$lang->chart->unlimited = 'Unlimited';
+$lang->chart->colon = 'To';
+
+$lang->chart->fieldTypeList = array();
+$lang->chart->fieldTypeList['input'] = 'Text';
+$lang->chart->fieldTypeList['date'] = 'Date';
+$lang->chart->fieldTypeList['datetime'] = 'Time';
+$lang->chart->fieldTypeList['select'] = 'Dropdown';
+//$lang->chart->fieldTypeList['condition'] = 'Condition';
+
+$lang->chart->groupWeek = 'Week %s,%s';
+
+$lang->chart->noChart = 'No Chart';
+$lang->chart->noChartSelected = 'Please select one chart.';
+
+$lang->chart->aggList = array();
+$lang->chart->aggList['count'] = 'Count';
+$lang->chart->aggList['distinct'] = 'Count after distinct';
+$lang->chart->aggList['avg'] = 'AVG';
+$lang->chart->aggList['sum'] = 'SUM';
+$lang->chart->aggList['max'] = 'MAX';
+$lang->chart->aggList['min'] = 'MIN';
diff --git a/module/chart/lang/fr.php b/module/chart/lang/fr.php
new file mode 100644
index 0000000000..ef7fc798b9
--- /dev/null
+++ b/module/chart/lang/fr.php
@@ -0,0 +1,33 @@
+chart->preview = 'View Chart';
+$lang->chart->query = 'Query';
+$lang->chart->toDesign = 'To Design';
+
+$lang->chart->group = 'Group';
+$lang->chart->field = 'Field';
+$lang->chart->agg = 'Aggregate';
+$lang->chart->chooseField = 'Choose field';
+$lang->chart->aggType = 'Aggregate type';
+$lang->chart->other = 'Other';
+$lang->chart->unlimited = 'Unlimited';
+$lang->chart->colon = 'To';
+
+$lang->chart->fieldTypeList = array();
+$lang->chart->fieldTypeList['input'] = 'Text';
+$lang->chart->fieldTypeList['date'] = 'Date';
+$lang->chart->fieldTypeList['datetime'] = 'Time';
+$lang->chart->fieldTypeList['select'] = 'Dropdown';
+//$lang->chart->fieldTypeList['condition'] = 'Condition';
+
+$lang->chart->groupWeek = 'Week %s,%s';
+
+$lang->chart->noChart = 'No Chart';
+$lang->chart->noChartSelected = 'Please select one chart.';
+
+$lang->chart->aggList = array();
+$lang->chart->aggList['count'] = 'Count';
+$lang->chart->aggList['distinct'] = 'Count after distinct';
+$lang->chart->aggList['avg'] = 'AVG';
+$lang->chart->aggList['sum'] = 'SUM';
+$lang->chart->aggList['max'] = 'MAX';
+$lang->chart->aggList['min'] = 'MIN';
diff --git a/module/chart/lang/zh-cn.php b/module/chart/lang/zh-cn.php
new file mode 100644
index 0000000000..f3cdbe9097
--- /dev/null
+++ b/module/chart/lang/zh-cn.php
@@ -0,0 +1,33 @@
+chart->preview = '查看图表';
+$lang->chart->query = '查询';
+$lang->chart->toDesign = '进入设计';
+
+$lang->chart->group = '所属分组';
+$lang->chart->field = '关联字段';
+$lang->chart->agg = '汇总';
+$lang->chart->chooseField = '选择字段';
+$lang->chart->aggType = '统计方式';
+$lang->chart->other = '其他';
+$lang->chart->unlimited = '不限';
+$lang->chart->colon = '至';
+
+$lang->chart->fieldTypeList = array();
+$lang->chart->fieldTypeList['input'] = '文本框';
+$lang->chart->fieldTypeList['date'] = '日期';
+$lang->chart->fieldTypeList['datetime'] = '时间';
+$lang->chart->fieldTypeList['select'] = '下拉菜单';
+//$lang->chart->fieldTypeList['condition'] = '条件筛选';
+
+$lang->chart->groupWeek = '%s年第%s周';
+
+$lang->chart->noChart = '暂时没有图表';
+$lang->chart->noChartSelected = '请选择至少一个图表。';
+
+$lang->chart->aggList = array();
+$lang->chart->aggList['count'] = '计数';
+$lang->chart->aggList['distinct'] = '去重后计数';
+$lang->chart->aggList['avg'] = '平均值';
+$lang->chart->aggList['sum'] = '求和';
+$lang->chart->aggList['max'] = '最大值';
+$lang->chart->aggList['min'] = '最小值';
diff --git a/module/chart/model.php b/module/chart/model.php
new file mode 100644
index 0000000000..4be9cfcc82
--- /dev/null
+++ b/module/chart/model.php
@@ -0,0 +1,627 @@
+
+ * @package chart
+ * @version $Id: model.php 5086 2013-07-10 02:25:22Z wyd621@gmail.com $
+ * @link http://www.zentao.net
+ */
+class chartModel extends model
+{
+ /**
+ * Get first chart group id.
+ *
+ * @param int $dimensionID
+ * @access public
+ * @return int
+ */
+ public function getFirstChartGroupID($dimensionID)
+ {
+ return $this->dao->select('id')->from(TABLE_MODULE)
+ ->where('deleted')->eq('0')
+ ->andWhere('type')->eq('chart')
+ ->andWhere('root')->eq($dimensionID)
+ ->andWhere('grade')->eq(1)
+ ->orderBy('`order`')
+ ->limit(1)
+ ->fetch('id');
+ }
+
+ /**
+ * Get chart.
+ *
+ * @param int $chartID
+ * @access public
+ * @return object
+ */
+ public function getByID($chartID)
+ {
+ $chart = $this->dao->select('*')->from(TABLE_CHART)->where('id')->eq($chartID)->fetch();
+ if(!$chart) return false;
+
+ if(!empty($chart->fields) and $chart->fields != 'null')
+ {
+ $chart->fieldSettings = json_decode($chart->fields);
+ $chart->fields = array();
+
+ foreach($chart->fieldSettings as $field => $settings) $chart->fields[] = $field;
+ }
+ else
+ {
+ $chart->fieldSettings = array();
+ }
+
+ if($chart->sql == null) $chart->sql = '';
+ if(!empty($chart->filters)) $chart->filters = json_decode($chart->filters, true);
+
+ return $this->processChart($chart);
+ }
+
+ /**
+ * Process sql and correct type.
+ *
+ * @param object $chart
+ * @access public
+ * @return object
+ */
+ public function processChart($chart)
+ {
+ if(!empty($chart->sql)) $chart->sql = trim(str_replace(';', '', $chart->sql));
+ if(!empty($chart->settings)) $chart->settings = json_decode($chart->settings, true);
+
+ if(!empty($chart->settings) and is_array($chart->settings))
+ {
+ $firstSetting = current($chart->settings);
+ if(isset($firstSetting['type'])) $chart->type = $firstSetting['type'];
+ }
+
+ return $chart;
+ }
+
+ /**
+ * Get tree of charts and groups.
+ *
+ * @param int $groupID
+ * @param string $orderBy
+ * @access public
+ * @return array
+ */
+ public function getPreviewCharts($groupID, $orderBy = 'id_desc')
+ {
+ if(!$groupID) return array('', array());
+
+ $currentGroup = $this->loadModel('tree')->getByID($groupID);
+ if(empty($currentGroup) || $currentGroup->grade != 1) return array('', array());
+
+ $groups = $this->dao->select('id, grade, name')->from(TABLE_MODULE)->where('deleted')->eq('0')->andWhere('path')->like("{$currentGroup->path}%")->orderBy('`order`')->fetchAll();
+ if(!$groups) return array('', array());
+
+ $chartGroups = array();
+ foreach($groups as $group)
+ {
+ $chartGroups[$group->id] = $this->dao->select('*')->from(TABLE_CHART)
+ ->where('deleted')->eq(0)
+ ->andWhere("FIND_IN_SET($group->id, `group`)")
+ ->andWhere('stage')->ne('draft')
+ ->orderBy($orderBy)
+ ->fetchAll();
+ }
+
+ if(!$chartGroups) return array('', array());
+
+ $readyCharts = array();
+ foreach($chartGroups as $groupID => $chartGroup)
+ {
+ $charts = array();
+ foreach($chartGroup as $chart)
+ {
+ if($chart->stage != 'published') continue;
+ $charts[] = $chart;
+ }
+
+ if(!empty($charts)) $readyCharts[$groupID] = $charts;
+ }
+
+ $index = 1;
+ $chartTree = '';
+ $firstChart = null;
+ foreach($groups as $group)
+ {
+ if(!isset($readyCharts[$group->id])) continue;
+
+ if($group->grade == 2)
+ {
+ $class = $index == 1 ? 'open' : 'closed';
+ $chartTree .= "
';
+
+ $index++;
+ }
+
+ if($chartTree) $chartTree = "
';
+
+ $charts = array();
+ if($firstChart)
+ {
+ if(!empty($firstChart->settings)) $firstChart->settings = json_decode($firstChart->settings, true);
+ if(!empty($firstChart->filters)) $firstChart->filters = json_decode($firstChart->filters, true);
+ if(!empty($firstChart->fields) and $firstChart->fields != 'null')
+ {
+ $firstChart->fieldSettings = json_decode($firstChart->fields);
+ $firstChart->fields = array_keys(json_decode($firstChart->fields, true));
+ }
+
+ $charts[] = $firstChart;
+ }
+
+ return array($chartTree, $charts);
+ }
+
+ /**
+ * Gen radar.
+ *
+ * @param int $fields
+ * @param int $settings
+ * @param int $defaultSql
+ * @param int $filters
+ * @param array $langs
+ * @access public
+ * @return void
+ */
+ public function genRadar($fields, $settings, $defaultSql, $filters, $langs = array())
+ {
+ list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
+
+ $yDatas = array();
+ $max = 0;
+ foreach($yStats as $yStat)
+ {
+ $data = array();
+ foreach($xLabels as $xLabel)
+ {
+ $yStatXLabel = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
+ $data[] = $yStatXLabel;
+ }
+
+ if(max($yStat) > $max) $max = max($yStat);
+ $yDatas[] = $data;
+ }
+
+ $series = array();
+ $legend = new stdclass();
+ $series['type'] = 'radar';
+
+ $clientLang = $this->app->getClientLang();
+ foreach($yDatas as $index => $yData)
+ {
+ $fieldName = $fields[$metrics[$index]]['name'];
+
+ if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field']))
+ {
+ $relatedObject = $fields[$metrics[$index]]['object'];
+ $relatedField = $fields[$metrics[$index]]['field'];
+
+ $this->app->loadLang($relatedObject);
+ $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName;
+ }
+
+ if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang];
+
+ $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
+ $series['data'][] = array('name' => $seriesName, 'value' => $yData);
+ }
+
+ $indicator = array();
+ $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
+ foreach($xLabels as $xLabel)
+ {
+ $labelName = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
+ $indicator[] = array('name' => $labelName, 'max' => $max);
+ }
+
+ $options = array('series' => $series, 'legend' => $legend, 'radar' => array('indicator' => $indicator), 'tooltip' => array('trigger' => 'item'));
+ return $options;
+ }
+
+ /**
+ * Gen pie.
+ *
+ * @param string $schema
+ * @param object $settings
+ * @param string $sql
+ * @param array $filters
+ * @access public
+ * @return array
+ */
+ public function genPie($fields, $settings, $sql, $filters)
+ {
+ $group = isset($settings['group'][0]['field']) ? $settings['group'][0]['field'] : '';
+ $date = isset($settings['group'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['group'][0]['group']) : '';
+ $metric = isset($settings['metric'][0]['field']) ? $settings['metric'][0]['field'] : '';
+ $agg = isset($settings['metric'][0]['valOrAgg']) ? $settings['metric'][0]['valOrAgg'] : '';
+
+ $defaultSql = str_replace(';', '', $sql);
+ $groupSql = $groupBySql = "tt.`$group`";
+ if(!empty($date))
+ {
+ $groupSql = $date == 'MONTH' ? "YEAR(tt.`$group`) as ttyear, $date(tt.`$group`) as ttgroup" : "$date(tt.`$group`) as $group";
+ $groupBySql = $date == 'MONTH' ? "YEAR(tt.`$group`), $date(tt.`$group`)" : "$date(tt.`$group`)";
+ }
+
+ if($agg == 'distinct')
+ {
+ $aggSQL = "count($agg tt.`$metric`) as `$metric`";
+ }
+ else
+ {
+ $aggSQL = "$agg(tt.`$metric`) as `$metric`";
+ }
+
+ $sql = "select $groupSql,$aggSQL from ($defaultSql) tt";
+ if(!empty($filters))
+ {
+ $wheres = array();
+ foreach($filters as $field => $filter)
+ {
+ $wheres[] = "$field {$filter['operator']} {$filter['value']}";
+ }
+
+ $whereStr = implode(' and ', $wheres);
+ $sql .= " where $whereStr";
+ }
+ $sql .= " group by $groupBySql";
+ $rows = $this->dao->query($sql)->fetchAll();
+ $stat = $this->processRows($rows, $date, $group, $metric);
+
+ $maxCount = 50;
+ if(empty($date)) arsort($stat);
+
+ $copyStat = $stat;
+ $other = array_sum(array_splice($copyStat, $maxCount));
+ $stat[$this->lang->chart->other] = $other;
+ if(empty($date)) arsort($stat);
+
+ $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
+
+ $data = array();
+ foreach($stat as $name => $value)
+ {
+ if($value == 0) continue;
+
+ $value = round($value, 2);
+ $labelName = isset($optionList[$name]) ? $optionList[$name] : $name;
+
+ $data[] = array('name' => $labelName, 'value' => $value);
+ }
+
+ $label = array('show' => true, 'position' => 'outside', 'formatter' => '{b} {d}%');
+ $legend = new stdclass();
+ $legend->type = 'scroll';
+ $legend->orient = 'vertical';
+ $legend->right = 0;
+
+ $series[] = array('data' => $data, 'type' => 'pie', 'label' => $label);
+ $options = array('series' => $series, 'legend' => $legend, 'tooltip' => array('trigger' => 'item', 'formatter' => "{b}
{c} ({d}%)"));
+ return $options;
+ }
+
+ /**
+ * Process rows.
+ *
+ * @param array $rows
+ * @param string $date
+ * @param string $group
+ * @param string $metric
+ * @access public
+ * @return array
+ */
+ public function processRows($rows, $date, $group, $metric)
+ {
+ $stat = array();
+ foreach($rows as $row)
+ {
+ if(!empty($date) and $date == 'MONTH')
+ {
+ $stat[sprintf("%04d", $row->ttyear) . '-' . sprintf("%02d", $row->ttgroup)] = $row->$metric;
+ }
+ elseif(!empty($date) and $date == 'YEARWEEK')
+ {
+ $yearweek = sprintf("%06d", $row->$group);
+ $year = substr($yearweek, 0, strlen($yearweek) - 2);
+ $week = substr($yearweek, -2);
+
+ $weekIndex = in_array($this->app->getClientLang(), array('zh-cn', 'zh-tw')) ? sprintf($this->lang->chart->groupWeek, $year, $week) : sprintf($this->lang->chart->groupWeek, $week, $year);
+ $stat[$weekIndex] = $row->$metric;
+ }
+ elseif(!empty($date) and $date == 'YEAR')
+ {
+ $stat[sprintf("%04d", $row->$group)] = $row->$metric;
+ }
+ else
+ {
+ $stat[$row->$group] = $row->$metric;
+ }
+ }
+
+ return $stat;
+ }
+
+ /**
+ * Gen line.
+ *
+ * @param string $schema
+ * @param object $settings
+ * @param string $sql
+ * @param array $filters
+ * @param array $langs
+ * @access public
+ * @return array
+ */
+ public function genLineChart($fields, $settings, $defaultSql, $filters, $langs = array())
+ {
+ list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
+
+ $fieldType = $fields[$settings['xaxis'][0]['field']]['type'];
+ if($fieldType == 'date') sort($xLabels);
+
+ $yDatas = array();
+ foreach($xLabels as $xLabel)
+ {
+ foreach($yStats as $index => $yStat)
+ {
+ if(!isset($yDatas[$index])) $yDatas[$index] = array();
+ $yDatas[$index][] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
+ }
+ }
+
+ $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
+ foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
+
+ $xaxis = array('type' => 'category', 'data' => $xLabels);
+ $yaxis = array('type' => 'value');
+ $legend = new stdclass();
+ $series = array();
+ $clientLang = $this->app->getClientLang();
+ foreach($yDatas as $index => $yData)
+ {
+ $fieldName = $fields[$metrics[$index]]['name'];
+
+ if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field']))
+ {
+ $relatedObject = $fields[$metrics[$index]]['object'];
+ $relatedField = $fields[$metrics[$index]]['field'];
+
+ $this->app->loadLang($relatedObject);
+ $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName;
+ }
+
+ if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang];
+
+ $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
+ $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'line');
+ }
+
+ $options = array('series' => $series, 'legend' => $legend, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis'));
+ return $options;
+ }
+
+ /**
+ * Gen cluBar.
+ *
+ * @param int $fields
+ * @param int $settings
+ * @param int $defaultSql
+ * @param int $filters
+ * @param int $stack
+ * @param array $langs
+ * @access public
+ * @return void
+ */
+ public function genCluBar($fields, $settings, $defaultSql, $filters, $stack = '', $langs = array())
+ {
+ list($group, $metrics, $aggs, $xLabels, $yStats) = $this->getMultiData($settings, $defaultSql, $filters);
+
+ $yDatas = array();
+ foreach($yStats as $yStat)
+ {
+ $data = array();
+ foreach($xLabels as $xLabel)
+ {
+ $data[] = isset($yStat[$xLabel]) ? $yStat[$xLabel] : 0;
+ }
+ $yDatas[] = $data;
+ }
+
+ $optionList = $this->getSysOptions($fields[$group]['type'], $fields[$group]['object'], $fields[$group]['field']);
+ foreach($xLabels as $index => $xLabel) $xLabels[$index] = isset($optionList[$xLabel]) ? $optionList[$xLabel] : $xLabel;
+
+ $xaxis = array('type' => 'category', 'data' => $xLabels, 'axisLabel' => array('interval' => 0));
+ $yaxis = array('type' => 'value');
+ $legend = new stdclass();
+
+ /* Cluster bar X graphs and cluster bar Y graphs are really just x and y axes switched, so cluster bar Y $xaixs and $yaxis are swapped so that the method can be reused. */
+ /* 簇状柱形图和簇状条形图其实只是x轴和y轴换了换,所以交换一下簇状条形图 xAxis和yAxis即可,这样方法就可以复用了。*/
+ if(in_array($settings['type'], array('cluBarY', 'stackedBarY'))) list($xaxis, $yaxis) = array($yaxis, $xaxis);
+
+ $series = array();
+ $clientLang = $this->app->getClientLang();
+ foreach($yDatas as $index => $yData)
+ {
+ $fieldName = $fields[$metrics[$index]]['name'];
+
+ if(!empty($fields[$metrics[$index]]['object']) and !empty($fields[$metrics[$index]]['field']))
+ {
+ $relatedObject = $fields[$metrics[$index]]['object'];
+ $relatedField = $fields[$metrics[$index]]['field'];
+
+ $this->app->loadLang($relatedObject);
+ $fieldName = isset($this->lang->$relatedObject->$relatedField) ? $this->lang->$relatedObject->$relatedField : $fieldName;
+ }
+
+ if(isset($langs[$metrics[$index]]) and !empty($langs[$metrics[$index]][$clientLang])) $fieldName = $langs[$metrics[$index]][$clientLang];
+
+ $seriesName = $fieldName . '(' . $this->lang->chart->aggList[$aggs[$index]] . ')';
+ $series[] = array('name' => $seriesName, 'data' => $yData, 'type' => 'bar', 'stack' => $stack);
+ }
+
+ $options = array('series' => $series, 'legend' => $legend, 'xAxis' => $xaxis, 'yAxis' => $yaxis, 'tooltip' => array('trigger' => 'axis'));
+ return $options;
+ }
+
+ /**
+ * Get multi data.
+ *
+ * @param int $settings
+ * @param int $defaultSql
+ * @param int $filters
+ * @access public
+ * @return void
+ */
+ public function getMultiData($settings, $defaultSql, $filters, $sort = false)
+ {
+ $group = isset($settings['xaxis'][0]['field']) ? $settings['xaxis'][0]['field'] : '';
+ $date = isset($settings['xaxis'][0]['group']) ? zget($this->config->chart->dateConvert, $settings['xaxis'][0]['group']) : '';
+ $metrics = array();
+ $aggs = array();
+ foreach($settings['yaxis'] as $yaxis)
+ {
+ $metrics[] = $yaxis['field'];
+ $aggs[] = $yaxis['valOrAgg'];
+ }
+ $yCount = count($metrics);
+
+ $xLabels = array();
+ $yStats = array();
+
+ for($i = 0; $i < $yCount; $i ++)
+ {
+ $metric = $metrics[$i];
+ $agg = $aggs[$i];
+
+ $groupSql = $groupBySql = "tt.`$group`";
+ if(!empty($date))
+ {
+ $groupSql = $date == 'MONTH' ? "YEAR(tt.`$group`) as ttyear, $date(tt.`$group`) as ttgroup" : "$date(tt.`$group`) as $group";
+ $groupBySql = $date == 'MONTH' ? "YEAR(tt.`$group`), $date(tt.`$group`)" : "$date(tt.`$group`)";
+ }
+
+ if($agg == 'distinct')
+ {
+ $aggSQL = "count($agg tt.`$metric`) as `$metric`";
+ }
+ else
+ {
+ $aggSQL = "$agg(tt.`$metric`) as `$metric`";
+ }
+
+ $sql = "select $groupSql,$aggSQL from ($defaultSql) tt";
+ if(!empty($filters))
+ {
+ $wheres = array();
+ foreach($filters as $field => $filter)
+ {
+ $wheres[] = "$field {$filter['operator']} {$filter['value']}";
+ }
+
+ $whereStr = implode(' and ', $wheres);
+ $sql .= " where $whereStr";
+ }
+ $sql .= " group by $groupBySql";
+ $rows = $this->dao->query($sql)->fetchAll();
+ $stat = $this->processRows($rows, $date, $group, $metric);
+
+ $maxCount = 50;
+ if($sort) arsort($stat);
+ $yStats[] = $stat;
+
+ $xLabels = array_merge($xLabels, array_keys($stat));
+ $xLabels = array_unique($xLabels);
+ }
+
+ return array($group, $metrics, $aggs, $xLabels, $yStats);
+ }
+
+ /**
+ * Adjust the action is clickable.
+ *
+ * @param object $chart
+ * @param string $action
+ * @static
+ * @access public
+ * @return bool
+ */
+ public static function isClickable($chart, $action)
+ {
+ return true;
+ }
+
+ /**
+ * Get sys options.
+ *
+ * @param string $type
+ * @access public
+ * @return array
+ */
+ public function getSysOptions($type, $object = '', $field = '')
+ {
+ $options = array('' => '');
+ switch($type)
+ {
+ case 'user':
+ $options = $this->loadModel('user')->getPairs();
+ break;
+ case 'product':
+ $options = $this->loadModel('product')->getPairs();
+ break;
+ case 'project':
+ $options = $this->loadModel('project')->getPairsByProgram();
+ break;
+ case 'execution':
+ $options = $this->loadModel('execution')->getPairs();
+ break;
+ case 'dept':
+ $options = $this->loadModel('dept')->getOptionMenu(0);
+ break;
+ case 'option':
+ if($field)
+ {
+ $path = $this->app->getModuleRoot() . 'dataview' . DS . 'table' . DS . "$object.php";
+ include $path;
+
+ $options = $schema->fields[$field]['options'];
+ }
+ break;
+ case 'object':
+ if($field)
+ {
+ $table = zget($this->config->objectTables, $object);
+ $options = $this->dao->select("id, {$field}")->from($table)->fetchPairs();
+ }
+ break;
+ /*case 'string':
+ if($field)
+ {
+ $table = zget($this->config->objectTables, $object);
+ $options = $this->dao->select("{$field}, {$field}")->from($table)->fetchPairs();
+ }
+ break;
+ */
+ }
+
+ return $options;
+ }
+}
+
diff --git a/module/chart/view/preview.html.php b/module/chart/view/preview.html.php
new file mode 100644
index 0000000000..130ca95f40
--- /dev/null
+++ b/module/chart/view/preview.html.php
@@ -0,0 +1,113 @@
+
+ * @package chart
+ * @version $Id$
+ * @link http://www.zentao.net
+ */
+?>
+getModuleRoot() . 'common/view/header.html.php';?>
+
+
+id) ? $group->id : 0);?>
+
+chart->noChartSelected);?>
+chart);?>
+chart->widthInput);?>
+chart->widthDate);?>
+bi->pickerHeight);?>
+
+
+
+ $groupName)
+ {
+ if(!$groupID) continue;
+ $activeClass = $groupID == $group->id ? 'btn-active-text' : '';
+ echo html::a(inlink('preview', "dimensionID={$dimensionID}&groupID={$groupID}"), $groupName, '', "class='btn btn-link {$activeClass}' id='{$groupID}Tab'");
+ }
+ ?>
+
+ config->edition == 'biz' or $this->config->edition == 'max'):?>
+
+ " . $lang->export, '', "class='btn btn-link btn-export' id='exportchart'");?>
+ chart->toDesign, '', "class='btn btn-primary '");?>
+
+
+
+
+
+
+
+
+
name) ? $group->name : '';?>
+
+
+
+
+
+ chart->noChart;?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ chart->noChart;?>
+
+
+
+
+
+
+
+
+
+
+
+ filters)):?>
+
chart->query, "data-chart={$chart->currentGroup}{$chart->id}", 'btn btn-primary btn-query');?>
+
+
+
+
+
+
+
+
+
+
+getModuleRoot() . 'common/view/footer.html.php';?>
+
+
+
+
+ {name}
+ {search}
+
+
+
diff --git a/module/common/lang/common.php b/module/common/lang/common.php
index b132596b80..d5f2ff99ce 100644
--- a/module/common/lang/common.php
+++ b/module/common/lang/common.php
@@ -44,6 +44,7 @@ $lang->user = new stdclass();
$lang->screen = new stdclass();
$lang->report = new stdclass();
$lang->pivot = new stdclass();
+$lang->chart = new stdclass();
$lang->repo = new stdclass();
$lang->jenkins = new stdclass();
$lang->gitlab = new stdclass();
diff --git a/module/common/lang/de.php b/module/common/lang/de.php
index e2dc554e56..aa3f5692b2 100644
--- a/module/common/lang/de.php
+++ b/module/common/lang/de.php
@@ -177,6 +177,7 @@ $lang->repo->codeRepo = 'Code Repo';
$lang->screen->common = 'Screen';
$lang->report->common = 'BI';
$lang->pivot->common = 'Pivot Table';
+$lang->chart->common = 'Chart';
$lang->system->common = 'System';
$lang->admin->common = 'Admin';
$lang->story->common = 'Story';
diff --git a/module/common/lang/en.php b/module/common/lang/en.php
index f8c4c2d0dc..290dc884d7 100644
--- a/module/common/lang/en.php
+++ b/module/common/lang/en.php
@@ -177,6 +177,7 @@ $lang->repo->codeRepo = 'Code Repo';
$lang->screen->common = 'Screen';
$lang->report->common = 'BI';
$lang->pivot->common = 'Pivot Table';
+$lang->chart->common = 'Chart';
$lang->system->common = 'System';
$lang->admin->common = 'Admin';
$lang->story->common = 'Story';
diff --git a/module/common/lang/fr.php b/module/common/lang/fr.php
index 8f94086c24..b03088ded3 100644
--- a/module/common/lang/fr.php
+++ b/module/common/lang/fr.php
@@ -177,6 +177,7 @@ $lang->repo->codeRepo = 'Code Repo';
$lang->screen->common = 'Screen';
$lang->report->common = 'BI';
$lang->pivot->common = 'Pivot Table';
+$lang->chart->common = 'Chart';
$lang->system->common = 'System';
$lang->admin->common = 'Admin';
$lang->story->common = 'Story';
diff --git a/module/common/lang/menu.php b/module/common/lang/menu.php
index 87598c28d5..0b0061d814 100644
--- a/module/common/lang/menu.php
+++ b/module/common/lang/menu.php
@@ -519,10 +519,12 @@ $lang->doc->menuOrder[30] = 'custom';
$lang->report->menu = new stdclass();
$lang->report->menu->screen = array('link' => "{$lang->screen->common}|screen|browse");
$lang->report->menu->pivot = array('link' => "{$lang->pivot->common}|pivot|preview");
+$lang->report->menu->chart = array('link' => "{$lang->chart->common}|chart|preview");
/* Report menu order. */
$lang->report->menuOrder[5] = 'screen';
$lang->report->menuOrder[10] = 'pivot';
+$lang->report->menuOrder[15] = 'chart';
/* Company menu.*/
$lang->company->menu = new stdclass();
@@ -611,6 +613,7 @@ $lang->navGroup->api = 'doc';
$lang->navGroup->screen = 'report';
$lang->navGroup->pivot = 'report';
+$lang->navGroup->chart = 'report';
$lang->navGroup->qa = 'qa';
$lang->navGroup->bug = 'qa';
diff --git a/module/common/lang/zh-cn.php b/module/common/lang/zh-cn.php
index 2cfdd47270..233c891556 100644
--- a/module/common/lang/zh-cn.php
+++ b/module/common/lang/zh-cn.php
@@ -177,6 +177,7 @@ $lang->repo->codeRepo = '代码库';
$lang->screen->common = '大屏';
$lang->report->common = 'BI';
$lang->pivot->common = '透视表';
+$lang->chart->common = '图表';
$lang->system->common = '组织';
$lang->admin->common = '后台';
$lang->story->common = $lang->SRCommon;
diff --git a/module/dataview/lang/de.php b/module/dataview/lang/de.php
new file mode 100644
index 0000000000..e5dce9a094
--- /dev/null
+++ b/module/dataview/lang/de.php
@@ -0,0 +1,3 @@
+dataview->id = 'ID';
+$lang->dataview->name = 'Name';
diff --git a/module/dataview/lang/en.php b/module/dataview/lang/en.php
new file mode 100644
index 0000000000..e5dce9a094
--- /dev/null
+++ b/module/dataview/lang/en.php
@@ -0,0 +1,3 @@
+dataview->id = 'ID';
+$lang->dataview->name = 'Name';
diff --git a/module/dataview/lang/fr.php b/module/dataview/lang/fr.php
new file mode 100644
index 0000000000..e5dce9a094
--- /dev/null
+++ b/module/dataview/lang/fr.php
@@ -0,0 +1,3 @@
+dataview->id = 'ID';
+$lang->dataview->name = 'Name';
diff --git a/module/dataview/lang/zh-cn.php b/module/dataview/lang/zh-cn.php
new file mode 100644
index 0000000000..f4e3a0a39f
--- /dev/null
+++ b/module/dataview/lang/zh-cn.php
@@ -0,0 +1,3 @@
+dataview->id = '编号';
+$lang->dataview->name = '名称';
diff --git a/module/dataview/table/bug.php b/module/dataview/table/bug.php
new file mode 100644
index 0000000000..53cc4f8d55
--- /dev/null
+++ b/module/dataview/table/bug.php
@@ -0,0 +1,63 @@
+app->loadLang('bug');
+$this->app->loadLang('product');
+$this->app->loadLang('story');
+$this->app->loadLang('project');
+$this->app->loadLang('tree');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'bug';
+
+$schema->tables = array();
+$schema->tables['bug'] = 'zt_bug';
+$schema->tables['product'] = 'zt_product';
+$schema->tables['story'] = 'zt_story';
+$schema->tables['productline'] = 'zt_module';
+$schema->tables['program'] = 'zt_project';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['bugmodule'] = 'zt_module';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = bug.product';
+$schema->joins['story'] = 'story.id = bug.story';
+$schema->joins['productline'] = 'productline.id = product.line';
+$schema->joins['program'] = 'program.id = product.program';
+$schema->joins['project'] = 'project.id = bug.project';
+$schema->joins['bugmodule'] = 'bugmodule.id = bug.module';
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->bug->id);
+$schema->fields['title'] = array('type' => 'string', 'name' => $this->lang->bug->title);
+$schema->fields['steps'] = array('type' => 'text', 'name' => $this->lang->bug->steps);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->bug->status, 'options' => $this->lang->bug->statusList);
+$schema->fields['confirmed'] = array('type' => 'option', 'name' => $this->lang->bug->confirmed, 'options' => $this->lang->bug->confirmedList);
+$schema->fields['severity'] = array('type' => 'option', 'name' => $this->lang->bug->severity, 'options' => $this->lang->bug->severityList);
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->bug->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->bug->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['bugmodule'] = array('type' => 'object', 'name' => $this->lang->bug->module, 'object' => 'bugmodule', 'show' => 'bugmodule.name');
+$schema->fields['story'] = array('type' => 'object', 'name' => $this->lang->story->common, 'object' => 'story', 'show' => 'story.title');
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->bug->pri, 'options' => $this->lang->bug->priList);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->bug->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->bug->openedDate);
+$schema->fields['resolvedBy'] = array('type' => 'user', 'name' => $this->lang->bug->resolvedBy);
+$schema->fields['resolution'] = array('type' => 'option', 'name' => $this->lang->bug->resolution, 'options' => $this->lang->bug->resolutionList);
+$schema->fields['resolvedDate'] = array('type' => 'date', 'name' => $this->lang->bug->resolvedDate);
+
+$schema->objects = array();
+
+$schema->objects['story'] = array();
+$schema->objects['story']['id'] = array('type' => 'number', 'name' => $this->lang->story->id);
+$schema->objects['story']['title'] = array('type' => 'string', 'name' => $this->lang->story->common);
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->common);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->common);
+
+$schema->objects['bugmodule'] = array();
+$schema->objects['bugmodule']['id'] = array('type' => 'number', 'name' => $this->lang->bug->id);
+$schema->objects['bugmodule']['name'] = array('type' => 'string', 'name' => $this->lang->tree->module);
diff --git a/module/dataview/table/bugbuild.php b/module/dataview/table/bugbuild.php
new file mode 100644
index 0000000000..84d8f87008
--- /dev/null
+++ b/module/dataview/table/bugbuild.php
@@ -0,0 +1,92 @@
+app->loadLang('bug');
+$this->app->loadLang('product');
+$this->app->loadLang('testtask');
+$this->app->loadLang('build');
+$this->app->loadLang('execution');
+$this->app->loadLang('project');
+$this->app->loadLang('tree');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'bug';
+
+$schema->tables = array();
+$schema->tables['bug'] = 'zt_bug';
+$schema->tables['product'] = 'zt_product';
+$schema->tables['testtask'] = 'zt_testtask';
+$schema->tables['testcase'] = 'zt_case';
+$schema->tables['build'] = 'zt_build';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['module'] = 'zt_module';
+$schema->tables['casemodule'] = 'zt_module';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = bug.product';
+$schema->joins['testtask'] = 'testtask.id = bug.testtask';
+$schema->joins['build'] = 'build.id = testtask.build';
+$schema->joins['execution'] = 'execution.id = build.execution';
+$schema->joins['project'] = 'project.id = build.project';
+$schema->joins['module'] = 'module.id = bug.module';
+$schema->joins['testcase'] = 'testcase.id = bug.case';
+$schema->joins['casemodule'] = 'casemodule.id = testcase.module';
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->bug->id);
+$schema->fields['title'] = array('type' => 'string', 'name' => $this->lang->bug->title);
+$schema->fields['steps'] = array('type' => 'text', 'name' => $this->lang->bug->steps);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->bug->status, 'options' => $this->lang->bug->statusList);
+$schema->fields['confirmed'] = array('type' => 'option', 'name' => $this->lang->bug->confirmed, 'options' => $this->lang->bug->confirmedList);
+$schema->fields['severity'] = array('type' => 'option', 'name' => $this->lang->bug->severity, 'options' => $this->lang->bug->severityList);
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->bug->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->bug->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['build'] = array('type' => 'object', 'name' => $this->lang->build->common, 'object' => 'build', 'show' => 'build.name');
+$schema->fields['module'] = array('type' => 'object', 'name' => $this->lang->bug->module, 'object' => 'module', 'show' => 'module.name');
+$schema->fields['testtask'] = array('type' => 'object', 'name' => $this->lang->testtask->common, 'object' => 'testtask', 'show' => 'testtask.name');
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->bug->pri, 'options' => $this->lang->bug->priList);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->bug->openedBy);
+$schema->fields['openedDate'] = array('type' => 'datetime', 'name' => $this->lang->bug->openedDate);
+$schema->fields['resolvedBy'] = array('type' => 'user', 'name' => $this->lang->bug->resolvedBy);
+$schema->fields['resolution'] = array('type' => 'option', 'name' => $this->lang->bug->resolution, 'options' => $this->lang->bug->resolutionList);
+$schema->fields['resolvedDate'] = array('type' => 'datetime', 'name' => $this->lang->bug->resolvedDate);
+$schema->fields['casemodule'] = array('type' => 'object', 'name' => $this->lang->tree->module, 'object' => 'casemodule', 'show' => 'casemodule.name');
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->common);
+
+$schema->objects['testtask'] = array();
+$schema->objects['testtask']['id'] = array('type' => 'number', 'name' => $this->lang->testtask->id);
+$schema->objects['testtask']['name'] = array('type' => 'string', 'name' => $this->lang->testtask->common);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->build->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->common);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->common);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->common);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->build->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->common);
+
+$schema->objects['module'] = array();
+$schema->objects['module']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['module']['name'] = array('type' => 'string', 'name' => $this->lang->tree->module);
+
+$schema->objects['testcase'] = array();
+$schema->objects['testcase']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['testcase']['title'] = array('type' => 'string', 'name' => $this->lang->testcase->common);
+
+$schema->objects['casemodule'] = array();
+$schema->objects['casemodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['casemodule']['name'] = array('type' => 'string', 'name' => $this->lang->dataview->name);
diff --git a/module/dataview/table/build.php b/module/dataview/table/build.php
new file mode 100644
index 0000000000..82de562429
--- /dev/null
+++ b/module/dataview/table/build.php
@@ -0,0 +1,45 @@
+app->loadLang('product');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+$this->app->loadLang('build');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'build';
+
+$schema->tables = array();
+$schema->tables['product'] = 'zt_product';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['build'] = 'zt_build';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = build.product';
+$schema->joins['project'] = 'project.id = build.project';
+$schema->joins['execution'] = 'execution.id = build.execution';
+
+$schema->fields = array();
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->build->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->build->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['execution'] = array('type' => 'object', 'name' => $this->lang->build->execution, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->build->name);
+$schema->fields['builder'] = array('type' => 'user', 'name' => $this->lang->build->builder);
+$schema->fields['stories'] = array('type' => 'string', 'name' => $this->lang->build->stories);
+$schema->fields['bugs'] = array('type' => 'string', 'name' => $this->lang->build->bugs);
+$schema->fields['date'] = array('type' => 'date', 'name' => $this->lang->build->date);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->build->desc);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
diff --git a/module/dataview/table/casemodule.php b/module/dataview/table/casemodule.php
new file mode 100644
index 0000000000..71c4d5f7bb
--- /dev/null
+++ b/module/dataview/table/casemodule.php
@@ -0,0 +1,18 @@
+app->loadLang('tree');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'casemodule';
+
+$schema->tables = array();
+$schema->tables['casemodule'] = 'zt_module';
+
+$schema->joins = array();
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->tree->module);
+
+$schema->objects = array();
diff --git a/module/dataview/table/casestep.php b/module/dataview/table/casestep.php
new file mode 100644
index 0000000000..009c3d7447
--- /dev/null
+++ b/module/dataview/table/casestep.php
@@ -0,0 +1,30 @@
+app->loadLang('testcase');
+
+$casestepLang = new stdclass();
+$casestepLang->type = '步骤类型';
+$casestepLang->typeList['step'] = '步骤';
+$casestepLang->typeList['group'] = '分组';
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'casestep';
+
+$schema->tables = array();
+$schema->tables['casestep'] = 'zt_casestep';
+$schema->tables['testcase'] = 'zt_case';
+
+$schema->joins = array();
+$schema->joins['testcase'] = 'testcase.id = casestep.`case`';
+
+$schema->fields = array();
+$schema->fields['case'] = array('type' => 'object', 'name' => $this->lang->testcase->common, 'object' => 'testcase', 'show' => 'testcase.title');
+$schema->fields['type'] = array('type' => 'option', 'name' => $casestepLang->type, 'options' => $casestepLang->typeList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->testcase->desc);
+$schema->fields['expect'] = array('type' => 'string', 'name' => $this->lang->testcase->expect);
+$schema->fields['version'] = array('type' => 'number', 'name' => $this->lang->testcase->version);
+
+$schema->objects = array();
+$schema->objects['testcase'] = array();
+$schema->objects['testcase']['id'] = array('type' => 'number', 'name' => $this->lang->testcase->id);
+$schema->objects['testcase']['title'] = array('type' => 'string', 'name' => $this->lang->testcase->title);
diff --git a/module/dataview/table/execution.php b/module/dataview/table/execution.php
new file mode 100644
index 0000000000..6868b5cf16
--- /dev/null
+++ b/module/dataview/table/execution.php
@@ -0,0 +1,37 @@
+app->loadLang('execution');
+$this->app->loadLang('project');
+$this->app->loadLang('product');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'execution';
+
+$schema->tables = array();
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+
+$schema->joins = array();
+$schema->joins['project'] = 'execution.project = project.id';
+
+$schema->fields = array();
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->project->name, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
+$schema->fields['code'] = array('type' => 'string', 'name' => $this->lang->execution->code);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->execution->type, 'options' => $this->lang->execution->typeList);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->execution->status, 'options' => $this->lang->execution->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->execution->desc);
+$schema->fields['begin'] = array('type' => 'date', 'name' => $this->lang->execution->begin);
+$schema->fields['end'] = array('type' => 'date', 'name' => $this->lang->execution->end);
+$schema->fields['PO'] = array('type' => 'user', 'name' => $this->lang->product->PO);
+$schema->fields['PM'] = array('type' => 'user', 'name' => $this->lang->execution->PM);
+$schema->fields['QD'] = array('type' => 'user', 'name' => $this->lang->execution->QD);
+$schema->fields['RD'] = array('type' => 'user', 'name' => $this->lang->execution->RD);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->execution->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->execution->openedDate);
+
+$schema->objects = array();
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
diff --git a/module/dataview/table/product.php b/module/dataview/table/product.php
new file mode 100644
index 0000000000..13689982db
--- /dev/null
+++ b/module/dataview/table/product.php
@@ -0,0 +1,41 @@
+app->loadLang('product');
+$this->app->loadLang('program');
+$this->app->loadLang('tree');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'product';
+
+$schema->tables = array();
+$schema->tables['product'] = 'zt_product';
+$schema->tables['program'] = 'zt_project';
+$schema->tables['line'] = 'zt_module';
+
+$schema->joins = array();
+$schema->joins['program'] = 'product.program = program.id';
+$schema->joins['line'] = 'product.line = line.id';
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->fields['program'] = array('type' => 'object', 'name' => $this->lang->product->program, 'object' => 'program', 'show' => 'program.name');
+$schema->fields['line'] = array('type' => 'object', 'name' => $this->lang->product->line, 'object' => 'line', 'show' => 'line.name');
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
+$schema->fields['code'] = array('type' => 'string', 'name' => $this->lang->product->code);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->product->type, 'options' => $this->lang->product->typeList);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->product->status, 'options' => $this->lang->product->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->product->desc);
+$schema->fields['PO'] = array('type' => 'user', 'name' => $this->lang->product->PO);
+$schema->fields['QD'] = array('type' => 'user', 'name' => $this->lang->product->QD);
+$schema->fields['RD'] = array('type' => 'user', 'name' => $this->lang->product->RD);
+$schema->fields['createdBy'] = array('type' => 'user', 'name' => $this->lang->product->createdBy);
+$schema->fields['createdDate'] = array('type' => 'date', 'name' => $this->lang->product->createdDate);
+
+$schema->objects = array();
+
+$schema->objects['program'] = array();
+$schema->objects['program']['id'] = array('type' => 'number', 'name' => $this->lang->program->id);
+$schema->objects['program']['name'] = array('type' => 'string', 'name' => $this->lang->program->name);
+
+$schema->objects['line'] = array();
+$schema->objects['line']['name'] = array('type' => 'string', 'name' => $this->lang->product->line);
diff --git a/module/dataview/table/productplan.php b/module/dataview/table/productplan.php
new file mode 100644
index 0000000000..4130e5c2fc
--- /dev/null
+++ b/module/dataview/table/productplan.php
@@ -0,0 +1,28 @@
+app->loadLang('productplan');
+$this->app->loadLang('product');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'productplan';
+
+$schema->tables = array();
+$schema->tables['productplan'] = 'zt_productplan';
+$schema->tables['product'] = 'zt_product';
+
+$schema->joins = array();
+$schema->joins['product'] = 'productplan.product = product.id';
+
+$schema->fields = array();
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->productplan->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['title'] = array('type' => 'string', 'name' => $this->lang->productplan->title);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->productplan->status, 'options' => $this->lang->productplan->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->productplan->desc);
+$schema->fields['begin'] = array('type' => 'date', 'name' => $this->lang->productplan->begin);
+$schema->fields['end'] = array('type' => 'date', 'name' => $this->lang->productplan->end);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
diff --git a/module/dataview/table/project.php b/module/dataview/table/project.php
new file mode 100644
index 0000000000..2f2def927f
--- /dev/null
+++ b/module/dataview/table/project.php
@@ -0,0 +1,30 @@
+app->loadLang('project');
+$this->app->loadLang('product');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'project';
+
+$schema->tables = array();
+$schema->tables['project'] = 'zt_project';
+
+$schema->joins = array();
+
+$schema->fields = array();
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+$schema->fields['code'] = array('type' => 'string', 'name' => $this->lang->project->code);
+$schema->fields['model'] = array('type' => 'option', 'name' => $this->lang->project->model, 'options' => $this->lang->project->modelList);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->project->type, 'options' => $this->lang->project->typeList);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->project->status, 'options' => $this->lang->project->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->project->desc);
+$schema->fields['begin'] = array('type' => 'date', 'name' => $this->lang->project->begin);
+$schema->fields['end'] = array('type' => 'date', 'name' => $this->lang->project->end);
+$schema->fields['PO'] = array('type' => 'user', 'name' => $this->lang->product->PO);
+$schema->fields['PM'] = array('type' => 'user', 'name' => $this->lang->project->common . $this->lang->project->PM);
+$schema->fields['QD'] = array('type' => 'user', 'name' => $this->lang->project->QD);
+$schema->fields['RD'] = array('type' => 'user', 'name' => $this->lang->project->RD);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->project->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->project->openedDate);
+
+$schema->objects = array();
diff --git a/module/dataview/table/release.php b/module/dataview/table/release.php
new file mode 100644
index 0000000000..a0c74459d5
--- /dev/null
+++ b/module/dataview/table/release.php
@@ -0,0 +1,46 @@
+app->loadLang('release');
+$this->app->loadLang('product');
+$this->app->loadLang('project');
+$this->app->loadLang('build');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'release';
+
+$schema->tables = array();
+$schema->tables['release'] = 'zt_release';
+$schema->tables['product'] = 'zt_product';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['build'] = 'zt_build';
+
+$schema->joins = array();
+$schema->joins['product'] = 'release.product = product.id';
+$schema->joins['project'] = 'release.project = project.id';
+$schema->joins['build'] = 'release.build = build.id';
+
+$schema->fields = array();
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->release->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->release->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['build'] = array('type' => 'object', 'name' => $this->lang->release->build, 'object' => 'build', 'show' => 'build.name');
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->release->name);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->release->status, 'options' => $this->lang->release->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->release->desc);
+$schema->fields['date'] = array('type' => 'date', 'name' => $this->lang->release->date);
+$schema->fields['stories'] = array('type' => 'string', 'name' => $this->lang->release->stories);
+$schema->fields['bugs'] = array('type' => 'string', 'name' => $this->lang->release->bugs);
+$schema->fields['leftBugs'] = array('type' => 'string', 'name' => $this->lang->release->leftBugs);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->build->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->common);
diff --git a/module/dataview/table/story.php b/module/dataview/table/story.php
new file mode 100644
index 0000000000..022c4225b6
--- /dev/null
+++ b/module/dataview/table/story.php
@@ -0,0 +1,49 @@
+app->loadLang('product');
+$this->app->loadLang('productplan');
+$this->app->loadLang('story');
+$this->app->loadLang('project');
+$this->app->loadLang('tree');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'story';
+
+$schema->tables = array();
+$schema->tables['story'] = 'zt_story';
+$schema->tables['product'] = 'zt_product';
+$schema->tables['productline'] = 'zt_module';
+$schema->tables['productplan'] = 'zt_productplan';
+$schema->tables['program'] = 'zt_project';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['storymodule'] = 'zt_module';
+$schema->tables['storyspec'] = 'zt_storyspec';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = story.product';
+$schema->joins['storymodule'] = 'storymodule.id = story.module';
+$schema->joins['storyspec'] = 'storyspec.story = story.id';
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->story->id);
+$schema->fields['title'] = array('type' => 'string', 'name' => $this->lang->story->title);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->story->status, 'options' => $this->lang->story->statusList);
+$schema->fields['stage'] = array('type' => 'option', 'name' => $this->lang->story->stage, 'options' => $this->lang->story->stageList);
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->story->pri, 'options' => $this->lang->story->priList);
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->story->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['storymodule'] = array('type' => 'object', 'name' => $this->lang->story->module, 'object' => 'storymodule', 'show' => 'storymodule.name');
+$schema->fields['closedDate'] = array('type' => 'date', 'name' => $this->lang->story->closedDate);
+$schema->fields['closedReason'] = array('type' => 'option', 'name' => $this->lang->story->closedReason, 'options' => $this->lang->story->reasonList);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->story->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->story->openedDate);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->common);
+
+$schema->objects['storymodule'] = array();
+$schema->objects['storymodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['storymodule']['name'] = array('type' => 'string', 'name' => $this->lang->tree->name);
diff --git a/module/dataview/table/task.php b/module/dataview/table/task.php
new file mode 100644
index 0000000000..d5d1a562d1
--- /dev/null
+++ b/module/dataview/table/task.php
@@ -0,0 +1,63 @@
+app->loadLang('task');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+$this->app->loadLang('story');
+$this->app->loadLang('tree');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'task';
+
+$schema->tables = array();
+$schema->tables['task'] = 'zt_task';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['story'] = 'zt_story';
+$schema->tables['taskmodule'] = 'zt_module';
+
+$schema->joins = array();
+$schema->joins['execution'] = 'task.execution = execution.id';
+$schema->joins['project'] = 'task.project = project.id';
+$schema->joins['story'] = 'task.story = story.id';
+$schema->joins['taskmodule'] = 'task.module = taskmodule.id';
+
+$schema->fields = array();
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->task->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['execution'] = array('type' => 'object', 'name' => $this->lang->task->execution, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['story'] = array('type' => 'object', 'name' => $this->lang->task->story, 'object' => 'story', 'show' => 'story.title');
+$schema->fields['taskmodule'] = array('type' => 'object', 'name' => $this->lang->task->module, 'object' => 'taskmodule', 'show' => 'taskmodule.name');
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->task->name);
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->task->pri, 'options' => $this->lang->task->priList);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->task->type, 'options' => $this->lang->task->typeList);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->task->status, 'options' => $this->lang->task->statusList);
+$schema->fields['desc'] = array('type' => 'string', 'name' => $this->lang->task->desc);
+$schema->fields['estimate'] = array('type' => 'string', 'name' => $this->lang->task->estimate);
+$schema->fields['consumed'] = array('type' => 'string', 'name' => $this->lang->task->consumed);
+$schema->fields['left'] = array('type' => 'string', 'name' => $this->lang->task->left);
+$schema->fields['estStarted'] = array('type' => 'date', 'name' => $this->lang->task->estStarted);
+$schema->fields['deadline'] = array('type' => 'date', 'name' => $this->lang->task->deadline);
+$schema->fields['assignedTo'] = array('type' => 'user', 'name' => $this->lang->task->assignedTo);
+$schema->fields['finishedBy'] = array('type' => 'user', 'name' => $this->lang->task->finishedBy);
+$schema->fields['closedBy'] = array('type' => 'user', 'name' => $this->lang->task->closedBy);
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->task->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->task->openedDate);
+
+$schema->objects = array();
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
+
+$schema->objects['story'] = array();
+$schema->objects['story']['id'] = array('type' => 'number', 'name' => $this->lang->story->id);
+$schema->objects['story']['title'] = array('type' => 'string', 'name' => $this->lang->story->title);
+
+$schema->objects['taskmodule'] = array();
+$schema->objects['taskmodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['taskmodule']['name'] = array('type' => 'string', 'name' => $this->lang->tree->name);
diff --git a/module/dataview/table/team.php b/module/dataview/table/team.php
new file mode 100644
index 0000000000..ef28e65a4e
--- /dev/null
+++ b/module/dataview/table/team.php
@@ -0,0 +1,36 @@
+app->loadLang('task');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+
+$teamLang = new stdclass();
+$teamLang->type = '对象类型';
+$teamLang->typeList['task'] = '任务';
+$teamLang->typeList['execution'] = '执行';
+$teamLang->typeList['project'] = '项目';
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'team';
+
+$schema->tables = array();
+$schema->tables['team'] = 'zt_team';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['task'] = 'zt_task';
+
+$schema->joins = array();
+$schema->joins['execution'] = 'team.root = execution.id';
+$schema->joins['project'] = 'team.root = project.id';
+$schema->joins['task'] = 'team.root = task.id';
+
+$schema->fields = array();
+$schema->fields['root'] = array('type' => 'object', 'name' => $this->lang->execution->common, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['account'] = array('type' => 'user', 'name' => $this->lang->team->account);
+$schema->fields['type'] = array('type' => 'option', 'name' => $teamLang->type, 'options' => $teamLang->typeList);
+
+$schema->objects = array();
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
diff --git a/module/dataview/table/testcase.php b/module/dataview/table/testcase.php
new file mode 100644
index 0000000000..53d99060df
--- /dev/null
+++ b/module/dataview/table/testcase.php
@@ -0,0 +1,52 @@
+app->loadLang('product');
+$this->app->loadLang('testcase');
+$this->app->loadLang('testtask');
+$this->app->loadLang('tree');
+$this->app->loadLang('story');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'testcase';
+
+$schema->tables = array();
+$schema->tables['testcase'] = 'zt_case';
+$schema->tables['product'] = 'zt_product';
+$schema->tables['casemodule'] = 'zt_module';
+$schema->tables['story'] = 'zt_story';
+$schema->tables['casestep'] = 'zt_casestep';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = testcase.product';
+$schema->joins['casemodule'] = 'casemodule.id = testcase.module';
+$schema->joins['story'] = 'story.id = testcase.story';
+$schema->joins['casestep'] = 'casestep.case = testcase.id';
+
+$schema->fields = array();
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->testcase->id);
+$schema->fields['title'] = array('type' => 'string', 'name' => $this->lang->testcase->title);
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->testcase->pri, 'options' => $this->lang->testcase->priList);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->testcase->type, 'options' => $this->lang->testcase->typeList);
+$schema->fields['stage'] = array('type' => 'option', 'name' => $this->lang->testcase->stage, 'options' => $this->lang->testcase->stageList);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->testcase->status, 'options' => $this->lang->testcase->statusList);
+$schema->fields['version'] = array('type' => 'number', 'name' => $this->lang->testcase->version);
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->testcase->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['story'] = array('type' => 'object', 'name' => $this->lang->testcase->story, 'object' => 'story', 'show' => 'story.title');
+$schema->fields['casemodule'] = array('type' => 'object', 'name' => $this->lang->testcase->module, 'object' => 'casemodule', 'show' => 'casemodule.name');
+$schema->fields['openedBy'] = array('type' => 'user', 'name' => $this->lang->testcase->openedBy);
+$schema->fields['openedDate'] = array('type' => 'date', 'name' => $this->lang->testcase->openedDate);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->common);
+
+$schema->objects['casemodule'] = array();
+$schema->objects['casemodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['casemodule']['name'] = array('type' => 'string', 'name' => $this->lang->tree->name);
+
+$schema->objects['story'] = array();
+$schema->objects['story']['id'] = array('type' => 'number', 'name' => $this->lang->story->id);
+$schema->objects['story']['title'] = array('type' => 'string', 'name' => $this->lang->story->title);
diff --git a/module/dataview/table/testresult.php b/module/dataview/table/testresult.php
new file mode 100644
index 0000000000..6b951aef68
--- /dev/null
+++ b/module/dataview/table/testresult.php
@@ -0,0 +1,84 @@
+app->loadLang('testcase');
+$this->app->loadLang('testtask');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+$this->app->loadLang('tree');
+$this->app->loadLang('build');
+$this->app->loadLang('caselib');
+$this->app->loadLang('product');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'testresult';
+
+$schema->tables = array();
+$schema->tables['testcase'] = 'zt_case';
+$schema->tables['testrun'] = 'zt_testrun';
+$schema->tables['testresult'] = 'zt_testresult';
+$schema->tables['testtask'] = 'zt_testtask';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['casemodule'] = 'zt_module';
+$schema->tables['build'] = 'zt_build';
+$schema->tables['caselib'] = 'zt_testsuite';
+$schema->tables['product'] = 'zt_product';
+
+$schema->joins = array();
+$schema->joins['testcase'] = 'testcase.id = testresult.case';
+$schema->joins['testrun'] = 'testrun.id = testresult.run';
+$schema->joins['testtask'] = 'testrun.task = testtask.id';
+$schema->joins['project'] = 'project.id = testtask.project';
+$schema->joins['execution'] = 'execution.id = testtask.execution';
+$schema->joins['casemodule'] = 'casemodule.id = testcase.module';
+$schema->joins['build'] = 'build.id = testtask.build';
+$schema->joins['caselib'] = 'caselib.id = testcase.lib';
+$schema->joins['product'] = 'product.id = testcase.product';
+
+$schema->fields = array();
+$schema->fields['caseResult'] = array('type' => 'option', 'name' => $this->lang->testtask->caseResult, 'options' => $this->lang->testcase->resultList);
+$schema->fields['stepResults'] = array('type' => 'json', 'name' => $this->lang->testtask->stepResults);
+$schema->fields['lastRunner'] = array('type' => 'user', 'name' => $this->lang->testtask->lastRunner);
+$schema->fields['date'] = array('type' => 'date', 'name' => $this->lang->testtask->date);
+$schema->fields['testcase'] = array('type' => 'object', 'name' => $this->lang->testcase->common, 'object' => 'testcase', 'show' => 'testcase.title');
+$schema->fields['testtask'] = array('type' => 'object', 'name' => $this->lang->testtask->common, 'object' => 'testtask', 'show' => 'testtask.name');
+$schema->fields['execution'] = array('type' => 'object', 'name' => $this->lang->execution->common, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->project->common, 'object' => 'project', 'show' => 'execution.name');
+$schema->fields['casemodule'] = array('type' => 'object', 'name' => $this->lang->tree->common, 'object' => 'casemodule', 'show' => 'casemodule.name');
+$schema->fields['build'] = array('type' => 'object', 'name' => $this->lang->build->common, 'object' => 'build', 'show' => 'build.name');
+$schema->fields['caselib'] = array('type' => 'object', 'name' => $this->lang->caselib->common, 'object' => 'caselib', 'show' => 'caselib.name');
+
+$schema->objects = array();
+
+$schema->objects['testcase'] = array();
+$schema->objects['testcase']['id'] = array('type' => 'number', 'name' => $this->lang->testcase->id);
+$schema->objects['testcase']['title'] = array('type' => 'string', 'name' => $this->lang->testcase->title);
+
+$schema->objects['testtask'] = array();
+$schema->objects['testtask']['id'] = array('type' => 'number', 'name' => $this->lang->testtask->id);
+$schema->objects['testtask']['name'] = array('type' => 'string', 'name' => $this->lang->testtask->name);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->name);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
+
+$schema->objects['casemodule'] = array();
+$schema->objects['casemodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['casemodule']['name'] = array('type' => 'string', 'name' => $this->lang->dataview->name);
+
+$schema->objects['caselib'] = array();
+$schema->objects['caselib']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['caselib']['name'] = array('type' => 'string', 'name' => $this->lang->caselib->name);
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
diff --git a/module/dataview/table/testrun.php b/module/dataview/table/testrun.php
new file mode 100644
index 0000000000..a5a5e191d2
--- /dev/null
+++ b/module/dataview/table/testrun.php
@@ -0,0 +1,78 @@
+app->loadLang('testcase');
+$this->app->loadLang('testtask');
+$this->app->loadLang('tree');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+$this->app->loadLang('build');
+$this->app->loadLang('product');
+$this->app->loadLang('dataview');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'testrun';
+
+$schema->tables = array();
+$schema->tables['testrun'] = 'zt_testrun';
+$schema->tables['testcase'] = 'zt_case';
+$schema->tables['testtask'] = 'zt_testtask';
+$schema->tables['casemodule'] = 'zt_module';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['build'] = 'zt_build';
+$schema->tables['product'] = 'zt_product';
+
+$schema->joins = array();
+$schema->joins['testcase'] = 'testcase.id = testrun.case';
+$schema->joins['product'] = 'product.id = testcase.product';
+$schema->joins['testtask'] = 'testtask.id = testrun.task';
+$schema->joins['casemodule'] = 'casemodule.id = testcase.module';
+$schema->joins['project'] = 'project.id = testtask.project';
+$schema->joins['execution'] = 'execution.id = testtask.execution';
+$schema->joins['build'] = 'build.id = testtask.build';
+
+$schema->fields = array();
+$schema->fields['testtask'] = array('type' => 'object', 'name' => $this->lang->testtask->common, 'object' => 'testtask', 'show' => 'testtask.name');
+$schema->fields['testcase'] = array('type' => 'object', 'name' => $this->lang->testcase->common, 'object' => 'testcase', 'show' => 'testcase.title');
+$schema->fields['assignedTo'] = array('type' => 'user', 'name' => $this->lang->testcase->assignedTo);
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->project->common, 'object' => 'project', 'show' => 'execution.name');
+$schema->fields['build'] = array('type' => 'object', 'name' => $this->lang->build->common, 'object' => 'build', 'show' => 'build.name');
+$schema->fields['execution'] = array('type' => 'object', 'name' => $this->lang->execution->common, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['casemodule'] = array('type' => 'object', 'name' => $this->lang->tree->common, 'object' => 'casemodule', 'show' => 'casemodule.name');
+$schema->fields['lastRunner'] = array('type' => 'user', 'name' => $this->lang->testtask->lastRunner);
+$schema->fields['lastRunDate'] = array('type' => 'user', 'name' => $this->lang->testtask->lastRunDate);
+$schema->fields['lastRunResult'] = array('type' => 'option', 'name' => $this->lang->testtask->lastRunResult, 'options' => $this->lang->testcase->resultList);
+
+$schema->objects = array();
+
+$schema->objects['testcase'] = array();
+$schema->objects['testcase']['id'] = array('type' => 'number', 'name' => $this->lang->testcase->id);
+$schema->objects['testcase']['title'] = array('type' => 'string', 'name' => $this->lang->testcase->title);
+
+$schema->objects['testtask'] = array();
+$schema->objects['testtask']['id'] = array('type' => 'number', 'name' => $this->lang->testtask->id);
+$schema->objects['testtask']['name'] = array('type' => 'string', 'name' => $this->lang->testtask->name);
+
+$schema->objects['testcase'] = array();
+$schema->objects['testcase']['id'] = array('type' => 'number', 'name' => $this->lang->testcase->id);
+$schema->objects['testcase']['title'] = array('type' => 'string', 'name' => $this->lang->testcase->title);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->name);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
+
+$schema->objects['casemodule'] = array();
+$schema->objects['casemodule']['id'] = array('type' => 'number', 'name' => $this->lang->dataview->id);
+$schema->objects['casemodule']['name'] = array('type' => 'string', 'name' => $this->lang->dataview->name);
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
diff --git a/module/dataview/table/testtask.php b/module/dataview/table/testtask.php
new file mode 100644
index 0000000000..04194e6432
--- /dev/null
+++ b/module/dataview/table/testtask.php
@@ -0,0 +1,56 @@
+app->loadLang('product');
+$this->app->loadLang('project');
+$this->app->loadLang('execution');
+$this->app->loadLang('build');
+$this->app->loadLang('testtask');
+
+$schema = new stdclass();
+
+$schema->primaryTable = 'testtask';
+
+$schema->tables = array();
+$schema->tables['product'] = 'zt_product';
+$schema->tables['project'] = 'zt_project';
+$schema->tables['execution'] = 'zt_project';
+$schema->tables['build'] = 'zt_build';
+$schema->tables['testtask'] = 'zt_testtask';
+
+$schema->joins = array();
+$schema->joins['product'] = 'product.id = testtask.product';
+$schema->joins['project'] = 'project.id = testtask.project';
+$schema->joins['execution'] = 'execution.id = testtask.execution';
+$schema->joins['build'] = 'build.id = testtask.build';
+
+$schema->fields = array();
+$schema->fields['product'] = array('type' => 'object', 'name' => $this->lang->testtask->product, 'object' => 'product', 'show' => 'product.name');
+$schema->fields['project'] = array('type' => 'object', 'name' => $this->lang->testtask->project, 'object' => 'project', 'show' => 'project.name');
+$schema->fields['execution'] = array('type' => 'object', 'name' => $this->lang->testtask->execution, 'object' => 'execution', 'show' => 'execution.name');
+$schema->fields['build'] = array('type' => 'object', 'name' => $this->lang->testtask->build, 'object' => 'build', 'show' => 'build.name');
+
+$schema->fields['id'] = array('type' => 'number', 'name' => $this->lang->testtask->id);
+$schema->fields['name'] = array('type' => 'string', 'name' => $this->lang->testtask->name);
+$schema->fields['type'] = array('type' => 'option', 'name' => $this->lang->testtask->type, 'options' => $this->lang->testtask->typeList);
+$schema->fields['owner'] = array('type' => 'user', 'name' => $this->lang->testtask->owner);
+$schema->fields['pri'] = array('type' => 'option', 'name' => $this->lang->testtask->pri, 'options' => $this->lang->testtask->priList);
+$schema->fields['begin'] = array('type' => 'date', 'name' => $this->lang->testtask->begin);
+$schema->fields['end'] = array('type' => 'date', 'name' => $this->lang->testtask->end);
+$schema->fields['status'] = array('type' => 'option', 'name' => $this->lang->testtask->status, 'options' => $this->lang->testtask->statusList);
+
+$schema->objects = array();
+
+$schema->objects['product'] = array();
+$schema->objects['product']['id'] = array('type' => 'number', 'name' => $this->lang->product->id);
+$schema->objects['product']['name'] = array('type' => 'string', 'name' => $this->lang->product->name);
+
+$schema->objects['project'] = array();
+$schema->objects['project']['id'] = array('type' => 'number', 'name' => $this->lang->project->id);
+$schema->objects['project']['name'] = array('type' => 'string', 'name' => $this->lang->project->name);
+
+$schema->objects['execution'] = array();
+$schema->objects['execution']['id'] = array('type' => 'number', 'name' => $this->lang->execution->id);
+$schema->objects['execution']['name'] = array('type' => 'string', 'name' => $this->lang->execution->name);
+
+$schema->objects['build'] = array();
+$schema->objects['build']['id'] = array('type' => 'number', 'name' => $this->lang->build->id);
+$schema->objects['build']['name'] = array('type' => 'string', 'name' => $this->lang->build->name);
diff --git a/module/doc/control.php b/module/doc/control.php
index 603d4fee04..19e7ce81fb 100755
--- a/module/doc/control.php
+++ b/module/doc/control.php
@@ -106,7 +106,7 @@ class doc extends control
$this->view->param = $param;
$this->view->libID = $libID;
$this->view->lib = $this->doc->getLibById($libID);
- $this->view->libTree = $this->doc->getLibTree($libID, $libs, 'mine', 0);
+ $this->view->libTree = $this->doc->getLibTree($type != 'mine' ? 0 : $libID, $libs, 'mine', 0);
$this->view->pager = $pager;
$this->view->type = $type;
$this->view->objectID = 0;
@@ -163,10 +163,10 @@ class doc extends control
}
}
- if($type == 'execution') $objects = $this->execution->getPairs(0, 'all', 'multiple,leaf,noprefix');
if($type == 'execution')
{
+ $objects = $this->execution->getPairs(0, 'sprint,stage', 'multiple,leaf,noprefix');
$execution = $this->execution->getByID($objectID);
if($execution->type == 'stage') $this->lang->doc->execution = str_replace($this->lang->executionCommon, $this->lang->project->stage, $this->lang->doc->execution);
}
@@ -730,28 +730,21 @@ class doc extends control
*/
public function collect($objectID, $objectType)
{
+ $table = '';
if($objectType == 'doc') $table = TABLE_DOC;
- if($objectType == 'doclib') $table = TABLE_DOCLIB;
- if($objectType == 'module') $table = TABLE_MODULE;
- $collectors = $this->dao->select('collector')->from($table)->where('id')->eq($objectID)->fetch('collector');
+ if(empty($table)) return;
- $hasCollect = strpos($collectors, ",{$this->app->user->account},") !== false;
- if($hasCollect)
+ $action = $this->doc->getActionByObject($objectID, 'collect');
+ if($action)
{
- $collectors = str_replace(",{$this->app->user->account},", ',', $collectors);
+ $this->doc->deleteAction($action->id);
}
else
{
- $collectors = explode(',', $collectors);
- $collectors[] = $this->app->user->account;
- $collectors = implode(',', $collectors);
+ $this->doc->createAction($objectID, 'collect');
}
- $collectors = trim($collectors, ',') ? ',' . trim($collectors, ',') . ',' : '';
-
- $this->dao->update($table)->set('collector')->eq($collectors)->where('id')->eq($objectID)->exec();
-
- return $this->send(array('status' => $hasCollect ? 'no' : 'yes'));
+ return $this->send(array('status' => $action ? 'no' : 'yes'));
}
/**
@@ -810,6 +803,8 @@ class doc extends control
*/
public function ajaxGetModules($objectType, $objectID, $docType = 'doc')
{
+ if(empty($objectID)) return print(html::select('module', array(), '', "class='form-control'"));
+
if($docType == 'doc')
{
$unclosed = strpos($this->config->doc->custom->showLibs, 'unclosed') !== false ? 'unclosedProject' : '';
@@ -1131,6 +1126,7 @@ class doc extends control
$doc = $this->doc->getById($docID, $version, true);
if(!$doc) return print(js::error($this->lang->notFound));
+ $this->doc->createAction($docID, 'view');
if($doc->keywords)
{
$doc->keywords = str_replace(",", ',', $doc->keywords);
@@ -1303,7 +1299,7 @@ class doc extends control
$this->view->isFirstLoad = $isFirstLoad;
$this->view->param = $queryID;
$this->view->users = $this->user->getPairs('noletter');
- $this->view->libTree = $this->doc->getLibTree($libID, $libs, $type, $moduleID, $objectID, $browseType);
+ $this->view->libTree = $this->doc->getLibTree($libID, $libs, $type, $moduleID, $objectID, $browseType, $param);
$this->view->libID = $libID;
$this->view->moduleID = $moduleID;
$this->view->objectDropdown = $objectDropdown;
@@ -1312,6 +1308,7 @@ class doc extends control
$this->view->pager = $pager;
$this->view->objectID = $objectID;
$this->view->orderBy = $orderBy;
+ $this->view->release = $browseType == 'byrelease' ? $param : 0;
$this->view->canExport = $libType == 'api' ? common::hasPriv('api', 'export') : common::hasPriv('doc', $type . '2export');
$this->view->exportMethod = $libType == 'api' ? 'export' : $type . '2export';
$this->view->apiLibID = key($apiLibs);
diff --git a/module/doc/css/common.css b/module/doc/css/common.css
index 8f07a8858b..c8f86deaed 100644
--- a/module/doc/css/common.css
+++ b/module/doc/css/common.css
@@ -12,7 +12,6 @@
.col-spliter:hover:after,
.row-spliting .col-spliter:before,
.row-spliting .col-spliter:after {opacity: 1;}
-
@media (max-width: 720px)
{
.split-row > .side-col,
diff --git a/module/doc/css/myspace.css b/module/doc/css/myspace.css
index 80cea2d24b..6b3cebabe7 100644
--- a/module/doc/css/myspace.css
+++ b/module/doc/css/myspace.css
@@ -1 +1,14 @@
-#mainContent .c-id > a {color: #0c60e1;}
+.flex {display: flex;}
+.flex-none {flex: none;}
+.flex-auto {flex: auto;}
+.flex-full > .panel {box-shadow: none;}
+.float-r {float: right;}
+.h-full {height: 100%;}
+.w-full {width: 100%;}
+.overflow-x-auto {overflow-x: auto;}
+.overflow-auto {overflow: auto;}
+.overflow-visible {overflow: visible;}
+.overflow-hidden {overflow: hidden;}
+#mainContent > .panel {margin-bottom: 0; height: calc(100vh - 125px); overflow-y: auto;}
+#bysearchTab::before {display: none;}
+#queryBox #groupAndOr {min-width: 60px;}
diff --git a/module/doc/css/selectlibtype.css b/module/doc/css/selectlibtype.css
index cbffff4aec..61649d1aea 100644
--- a/module/doc/css/selectlibtype.css
+++ b/module/doc/css/selectlibtype.css
@@ -1 +1,3 @@
#lib_chosen.chosen-container .chosen-results {max-height: 100px;}
+.radio-inline+.radio-inline { margin-top: 0; margin-left: 0px;}
+.radio-inline {margin-right: 10px;}
diff --git a/module/doc/css/tablecontents.css b/module/doc/css/tablecontents.css
index ed62b56ae8..d062e89317 100644
--- a/module/doc/css/tablecontents.css
+++ b/module/doc/css/tablecontents.css
@@ -9,14 +9,15 @@
.overflow-auto {overflow: auto;}
.overflow-visible {overflow: visible;}
.overflow-hidden {overflow: hidden;}
-#main {padding-bottom: 0;}
.img-lib {width: 14px; height: 14px; margin-bottom: 2px; flex: 0 0 14px;}
+#main {padding-bottom: 0;}
#mainContent {padding-top: 0; padding-bottom: 0;}
#mainContent #createDropdown {display: inline-block;}
#mainContent #createDropdown ul {position: absolute;}
#pageNav .dropdown-menu {max-height: inherit;}
#mainContent > .panel {margin-bottom: 0; height: calc(100vh - 125px); overflow-y: auto;}
#mainContent > .main-col {padding-left: 0;}
+#swapper {margin-right: 15px;}
.no-content {width: 100px; height: 100px; margin: 0 auto;}
.notice {text-align: center; padding-left: 15px; padding-top: 20px;}
diff --git a/module/doc/css/view.css b/module/doc/css/view.css
index 510a575687..84e78686ce 100644
--- a/module/doc/css/view.css
+++ b/module/doc/css/view.css
@@ -1,3 +1,4 @@
+.h-full-adjust {overflow-y: auto; height: calc(100vh - 100px);}
.main-col .block-files .panel-heading {padding-right: 20px;}
.main-col .block-files .panel-heading .panel-title {height: 35px; line-height: 30px;}
.main-col .doc-title {display: flex; font-size: 16px; margin-bottom: 15px;}
@@ -31,7 +32,7 @@
.outline-content a {color: #838A9D;}
.outline-content li.text-ellipsis.active>a {font-weight: 700; color: #0c64eb;}
#outline li.has-list.open:before {content: unset;}
-#fileTree {margin-bottom: 40px; max-height: calc(100vh - 180px); overflow: auto;}
+#fileTree {margin-bottom: 40px; max-height: calc(100vh - 160px); overflow: auto;}
.title {font-size: 20px !important;}
.article-content.comment {width: 100% !important;}
@@ -47,5 +48,10 @@
.overflow-auto {overflow: auto;}
.overflow-visible {overflow: visible;}
.overflow-hidden {overflow: hidden;}
-#mainContent > .panel {margin-bottom: 0; height: calc(100vh - 125px); overflow-y: auto;}
+#mainContent > .panel {margin-bottom: 0; overflow-y: auto;}
.detail-title .actions .ajaxCollect > img {width: 20px;}
+.actions > .text {color: #8c8c8c; font-size: 14px;}
+.flex-content {display: flex; width: 100%; height: 100%;}
+.flex-content > #content {flex: auto; overflow-y: auto;}
+.flex-content > #history {flex: 0 0 302px;;}
+.panel {margin-bottom: 0;}
diff --git a/module/doc/js/common.js b/module/doc/js/common.js
index 6ccf395459..5b6fb6e1ed 100644
--- a/module/doc/js/common.js
+++ b/module/doc/js/common.js
@@ -10,6 +10,7 @@ function loadObjectModules(objectType, objectID, docType)
{
if(typeof docType == 'undefined') docType = 'doc';
var link = createLink('doc', 'ajaxGetModules', 'objectType=' + objectType + '&objectID=' + objectID + '&type=' + docType);
+ if(objectType == 'execution' && objectID == 0) var link = createLink('doc', 'ajaxGetModules', 'objectType=project&objectID=' + $('#project').val() + '&type=' + docType);
$('#moduleBox').load(link, function(){$('#moduleBox').find('select').picker(); $('#moduleLabel').remove();});
}
@@ -342,25 +343,35 @@ function locateNewLib(type, objectID, libID)
*/
function setSavePath()
{
+ var getSubPath = function($obj)
+ {
+ var $td = $obj.parent();
+ var usePicker = $td.find('.picker').length == 1;
+ var subPath = $obj.find('option:checked').text();
+ if(usePicker) subPath = $td.find('.picker .picker-selection-text').text();
+ return subPath;
+ }
+
savePath = defaultSave;
if($('#modalBasicInfo #product').length == 1)
{
- savePath += $('#modalBasicInfo #product option:checked').text() + '/';
+ savePath += getSubPath($('#modalBasicInfo #product')) + '/';
}
else if($('#modalBasicInfo #project').length == 1 && $('#modalBasicInfo #execution').length == 0)
{
- savePath += $('#modalBasicInfo #project option:checked').text() + '/';
+ savePath += getSubPath($('#modalBasicInfo #project')) + '/';
}
else if($('#modalBasicInfo #project').length == 1 && $('#modalBasicInfo #execution').length == 1)
{
- if($('#modalBasicInfo #execution').val() == '') savePath += $('#modalBasicInfo #project option:checked').text() + '/';
- if($('#modalBasicInfo #execution').val() != '') savePath += $('#modalBasicInfo #execution option:checked').text() + '/';
+ var executionID = $('#modalBasicInfo #execution').val();
+ if(executionID == '0' || executionID == '') savePath += getSubPath($('#modalBasicInfo #project')) + '/';
+ if(executionID != '0' && executionID != '') savePath += getSubPath($('#modalBasicInfo #execution')) + '/';
}
else if($('#modalBasicInfo #execution').length == 1)
{
- savePath += $('#modalBasicInfo #execution option:checked').text() + '/';
+ savePath += getSubPath($('#modalBasicInfo #execution')) + '/';
}
- savePath += $('#modalBasicInfo #module option:checked').text();
+ savePath += getSubPath($('#modalBasicInfo #module'));
$('#savePath').html(savePath).attr('title', savePath);
}
diff --git a/module/doc/js/view.js b/module/doc/js/view.js
index 7f8d8f5f36..79f29ef07b 100644
--- a/module/doc/js/view.js
+++ b/module/doc/js/view.js
@@ -188,7 +188,7 @@ $(function()
simplemde.togglePreview();
}
});
- });
+ })
$('#sidebar .icon.icon-angle-right').click(function()
{
@@ -199,4 +199,20 @@ $(function()
})
$('.outline .outline-toggle i.icon-angle-right').trigger("click");
+ $('#hisTrigger').on('click', function()
+ {
+ var $history = $('#history');
+ var $icon = $(this);
+ if($history.hasClass('hidden'))
+ {
+ $history.removeClass('hidden');
+ $icon.addClass('text-primary');
+ }
+ else
+ {
+ $history.addClass('hidden');
+ $icon.removeClass('text-primary');
+ }
+ })
+ $('#history').find('.btn.pull-right').removeClass('pull-right');
})
diff --git a/module/doc/lang/de.php b/module/doc/lang/de.php
index 64f72e1c3d..f5ea435d7d 100644
--- a/module/doc/lang/de.php
+++ b/module/doc/lang/de.php
@@ -70,7 +70,7 @@ $lang->doc->project = $lang->projectCommon;
$lang->doc->execution = $lang->execution->common;
$lang->doc->lib = 'Bibliothek';
$lang->doc->module = 'Modul';
-$lang->doc->libAndModule = 'Bibliothek & Modul';
+$lang->doc->libAndModule = 'Bibliothek&Modul';
$lang->doc->object = 'Object';
$lang->doc->title = 'Titel';
$lang->doc->digest = 'Zusammenfassung';
@@ -121,6 +121,7 @@ $lang->doc->nextStep = 'Next';
$lang->doc->closed = 'Closed';
$lang->doc->saveDraft = 'Save Draft';
$lang->doc->defaultSave = 'Default Save: ';
+$lang->doc->position = 'Position';
$lang->doc->moduleDoc = 'Nach Modulen';
$lang->doc->searchDoc = 'Suche';
@@ -137,6 +138,7 @@ $lang->doc->myDoc = 'Meine Dokumente';
$lang->doc->myView = 'Recently Viewed';
$lang->doc->myCollection = 'Meine Favoriten';
$lang->doc->myCreation = 'Created By';
+$lang->doc->myLib = 'My Library';
$lang->doc->tableContents = 'Catalog';
$lang->doc->addCatalog = 'Add Catalog';
$lang->doc->editCatalog = 'Edit Catalog';
diff --git a/module/doc/lang/en.php b/module/doc/lang/en.php
index b71dbc2647..568b6e12be 100644
--- a/module/doc/lang/en.php
+++ b/module/doc/lang/en.php
@@ -70,7 +70,7 @@ $lang->doc->project = $lang->projectCommon;
$lang->doc->execution = $lang->execution->common;
$lang->doc->lib = 'Library';
$lang->doc->module = 'Catalog';
-$lang->doc->libAndModule = 'Library & Catalog';
+$lang->doc->libAndModule = 'Library&Catalog';
$lang->doc->object = 'Object';
$lang->doc->title = 'Name';
$lang->doc->digest = 'Summary';
@@ -121,6 +121,7 @@ $lang->doc->nextStep = 'Next';
$lang->doc->closed = 'Closed';
$lang->doc->saveDraft = 'Save Draft';
$lang->doc->defaultSave = 'Default Save: ';
+$lang->doc->position = 'Position';
$lang->doc->moduleDoc = 'By Module';
$lang->doc->searchDoc = 'Search';
@@ -137,6 +138,7 @@ $lang->doc->myDoc = 'My Documents';
$lang->doc->myView = 'Recently Viewed';
$lang->doc->myCollection = 'My Favorites';
$lang->doc->myCreation = 'Created By';
+$lang->doc->myLib = 'My Library';
$lang->doc->tableContents = 'Directory';
$lang->doc->addCatalog = 'Add Catalog';
$lang->doc->editCatalog = 'Edit Catalog';
diff --git a/module/doc/lang/fr.php b/module/doc/lang/fr.php
index 41026a545f..aacd0e804a 100644
--- a/module/doc/lang/fr.php
+++ b/module/doc/lang/fr.php
@@ -70,7 +70,7 @@ $lang->doc->project = $lang->projectCommon;
$lang->doc->execution = $lang->execution->common;
$lang->doc->lib = 'Bibliothèque';
$lang->doc->module = 'Catégorie';
-$lang->doc->libAndModule = 'Library & Catalog';
+$lang->doc->libAndModule = 'Library&Catalog';
$lang->doc->object = 'Object';
$lang->doc->title = 'Nom';
$lang->doc->digest = 'Résumé';
@@ -121,6 +121,7 @@ $lang->doc->nextStep = 'Next';
$lang->doc->closed = 'Closed';
$lang->doc->saveDraft = 'Save Draft';
$lang->doc->defaultSave = 'Default Save: ';
+$lang->doc->position = 'Position';
$lang->doc->moduleDoc = 'Par Module';
$lang->doc->searchDoc = 'Rechercher';
@@ -137,6 +138,7 @@ $lang->doc->myDoc = 'Mes Documents';
$lang->doc->myView = 'Recently Viewed';
$lang->doc->myCollection = 'Mes Favoris';
$lang->doc->myCreation = 'Created By';
+$lang->doc->myLib = 'My Library';
$lang->doc->tableContents = 'Catalog';
$lang->doc->addCatalog = 'Add Catalog';
$lang->doc->editCatalog = 'Edit Catalog';
diff --git a/module/doc/lang/zh-cn.php b/module/doc/lang/zh-cn.php
index 4632983eef..cbc116a00c 100644
--- a/module/doc/lang/zh-cn.php
+++ b/module/doc/lang/zh-cn.php
@@ -121,6 +121,7 @@ $lang->doc->nextStep = '下一步';
$lang->doc->closed = '已关闭';
$lang->doc->saveDraft = '存为草稿';
$lang->doc->defaultSave = '默认存入:';
+$lang->doc->position = '所在位置';
$lang->doc->moduleDoc = '按模块浏览';
$lang->doc->searchDoc = '搜索';
@@ -137,6 +138,7 @@ $lang->doc->myDoc = '我的文档';
$lang->doc->myView = '最近浏览';
$lang->doc->myCollection = '我的收藏';
$lang->doc->myCreation = '我创建的';
+$lang->doc->myLib = '我的个人库';
$lang->doc->tableContents = '目录';
$lang->doc->addCatalog = '添加目录';
$lang->doc->editCatalog = '编辑目录';
diff --git a/module/doc/model.php b/module/doc/model.php
index a414b800a1..c9b32a2e23 100644
--- a/module/doc/model.php
+++ b/module/doc/model.php
@@ -467,11 +467,12 @@ class docModel extends model
}
elseif($browseType == "collectedbyme")
{
- $docs = $this->dao->select('*')->from(TABLE_DOC)
- ->where('deleted')->eq(0)
- ->andWhere('lib')->in($allLibs)
- ->beginIF($this->config->doc->notArticleType)->andWhere('type')->notIN($this->config->doc->notArticleType)->fi()
- ->andWhere('collector')->like("%,{$this->app->user->account},%")
+ $docs = $this->dao->select('t1.*')->from(TABLE_DOC)->alias('t1')
+ ->leftJoin(TABLE_DOCACTION)->alias('t2')->on("t1.id=t2.doc && t2.action='collect'")
+ ->where('t1.deleted')->eq(0)
+ ->andWhere('t1.lib')->in($allLibs)
+ ->beginIF($this->config->doc->notArticleType)->andWhere('t1.type')->notIN($this->config->doc->notArticleType)->fi()
+ ->andWhere('t2.actor')->eq($this->app->user->account)
->orderBy($sort)
->page($pager)
->fetchAll('id');
@@ -484,9 +485,14 @@ class docModel extends model
$docContents = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->in(array_keys($docs))->orderBy('version,doc')->fetchAll('doc');
+ $modules = array();
$objects = array('product' => 'products', 'execution' => 'executions', 'project' => 'projects');
+ $this->loadModel('tree');
foreach($docs as $index => $doc)
{
+ if(!isset($modules[$doc->lib])) $modules[$doc->lib] = $this->tree->getOptionMenu($doc->lib, 'doc', 0, 0, 'nodeleted', 'all', ' > ');
+ $doc->moduleName = zget($modules[$doc->lib], $doc->module);
+ $doc->moduleName = ltrim($doc->moduleName, '/');
foreach($objects as $type => $object)
{
if(!empty($doc->{$type}))
@@ -530,6 +536,7 @@ class docModel extends model
}
}
+ $docs = $this->processCollector($docs);
return $docs;
}
@@ -621,7 +628,7 @@ class docModel extends model
public function getDocs($libID, $module, $browseType, $orderBy, $pager = null)
{
$docIdList = $this->getPrivDocs($libID, $module, 'children');
- return $this->dao->select('*')->from(TABLE_DOC)
+ $docs = $this->dao->select('*')->from(TABLE_DOC)
->where('deleted')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('id')->in($docIdList)
@@ -630,6 +637,8 @@ class docModel extends model
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
+ $docs = $this->processCollector($docs);
+ return $docs;
}
/**
@@ -684,6 +693,10 @@ class docModel extends model
if(strpos($this->server->http_referer, $loginLink) !== false) return print(js::locate(inlink('index')));
return print(js::locate('back'));
}
+
+ $docs = $this->processCollector(array($doc->id => $doc));
+ $doc = $docs[$doc->id];
+
$version = $version ? $version : $doc->version;
$docContent = $this->dao->select('*')->from(TABLE_DOCCONTENT)->where('doc')->eq($doc->id)->andWhere('version')->eq($version)->fetch();
@@ -811,7 +824,8 @@ class docModel extends model
if(empty($docContent->content)) return dao::$errors['content'] = sprintf($this->lang->error->notempty, $this->lang->doc->content);
}
- $doc->draft = $docContent->content;
+ $doc->draft = $docContent->content;
+ $doc->vision = $this->config->vision;
$this->dao->insert(TABLE_DOC)->data($doc, 'content')->autoCheck()
->batchCheck($requiredFields, 'notempty')
->exec();
@@ -1011,6 +1025,11 @@ class docModel extends model
{
$this->config->doc->search['params']['execution']['values'] = array('' => '') + $this->loadModel('execution')->getPairs($this->session->project, 'sprint,stage', 'multiple,leaf,noprefix') + array('all' => $this->lang->doc->allExecutions);
}
+ elseif($type == 'mine')
+ {
+ unset($this->config->doc->search['fields']['addedBy']);
+ unset($this->config->doc->search['fields']['editedBy']);
+ }
else
{
unset($this->config->doc->search['fields']['execution']);
@@ -1587,7 +1606,7 @@ class docModel extends model
->leftjoin(TABLE_DOCLIB)->alias('t2')->on('t2.project=t1.id')
->where("CONCAT(',', t2.users, ',')")->like("%,{$this->app->user->account},%")
->andWhere('t1.vision')->eq($this->config->vision)
- ->andWhere('t1.model')->ne('kanban')
+ ->beginIF($this->config->vision == 'rnd')->andWhere('model')->ne('kanban')->fi()
->andWhere('t1.deleted')->eq(0)
->beginIF(!$this->app->user->admin)->andWhere('t1.id')->in($this->app->user->view->projects)->fi()
->fetchPairs();
@@ -1597,7 +1616,7 @@ class docModel extends model
->where('type')->eq('project')
->andWhere('vision')->eq($this->config->vision)
->andWhere('deleted')->eq(0)
- ->andWhere('model')->ne('kanban')
+ ->beginIF($this->config->vision == 'rnd')->andWhere('model')->ne('kanban')->fi()
->beginIF(!$this->app->user->admin)->andWhere('id')->in($this->app->user->view->projects)->fi()
->orderBy('order_asc')
->fetchAll('id');
@@ -2032,11 +2051,16 @@ class docModel extends model
$lately = date('Y-m-d', strtotime('-3 day'));
$statisticInfo = $this->dao->select("count(id) as totalDocs, count(IF(editedDate like '{$today}%', 1, null)) as todayEditedDocs,
count(IF(editedDate > '{$lately}', 1, null)) as lastEditedDocs, count(IF(addedDate > '{$lately}', 1, null)) as lastAddedDocs,
- count(IF(collector like '%,{$this->app->user->account},%', 1, null)) as myCollection, count(IF(addedBy = '{$this->app->user->account}', 1, null)) as myDocs")->from(TABLE_DOC)
+ count(IF(addedBy = '{$this->app->user->account}', 1, null)) as myDocs")->from(TABLE_DOC)
->where('deleted')->eq(0)
->andWhere('vision')->eq($this->config->vision)
->andWhere('id')->in($docIdList)
->fetch();
+ $statisticInfo->myCollection = $this->dao->select('count(*) as count')->from(TABLE_DOCACTION)
+ ->where('doc')->in($docIdList)
+ ->andWhere('action')->eq('collect')
+ ->andWhere('actor')->eq($this->app->user->account)
+ ->fetch('count');
$statisticInfo->pastEditedDocs = $statisticInfo->totalDocs - $statisticInfo->todayEditedDocs;
$statisticInfo->lastEditedProgress = $statisticInfo->totalDocs ? round($statisticInfo->lastEditedDocs / $statisticInfo->totalDocs, 2) * 100 : 0;
@@ -2316,7 +2340,6 @@ class docModel extends model
}
}
-
if(isset($moduleDocs[0]))
{
if(!isset($treeMenu[0])) $treeMenu[0] = '';
@@ -2742,16 +2765,19 @@ class docModel extends model
if(strpos($query, "`lib` = 'all'") !== false) $query = str_replace("`lib` = 'all'", '1', $query);
$docIdList = $this->getPrivDocs(array_keys($libs));
- return $this->dao->select('*')->from(TABLE_DOC)
+ $docs = $this->dao->select('*')->from(TABLE_DOC)
->where('deleted')->eq(0)
->andWhere($query)
->andWhere('lib')->in(array_keys($libs))
->andWhere('vision')->eq($this->config->vision)
->beginIF($this->config->doc->notArticleType)->andWhere('type')->notIN($this->config->doc->notArticleType)->fi()
->beginIF(!empty($docIdList))->andWhere('id')->in($docIdList)->fi()
+ ->andWhere("(status = 'normal' or (status = 'draft' and addedBy='{$this->app->user->account}'))")
->orderBy($orderBy)
->page($pager)
->fetchAll('id');
+ $docs = $this->processCollector($docs);
+ return $docs;
}
/**
@@ -2787,9 +2813,9 @@ class docModel extends model
* @access public
* @return array
*/
- public function getModuleTree($rootID, $moduleID = 0, $type = 'doc', $parent = 0, &$modules = array())
+ public function getModuleTree($rootID, $moduleID = 0, $type = 'doc', $parent = 0, $modules = array())
{
- if(empty($modules))
+ if(is_array($modules) and empty($modules))
{
$modules = $this->dao->select('*')->from(TABLE_MODULE)
->where('root')->eq((int)$rootID)
@@ -2843,10 +2869,11 @@ class docModel extends model
* @param int $objectID
* @param int $executionID
* @param string $browseType bySearch
+ * @param int $param
* @access public
* @return array
*/
- public function getLibTree($libID, $libs, $type, $moduleID, $objectID = 0, $browseType = '')
+ public function getLibTree($libID, $libs, $type, $moduleID, $objectID = 0, $browseType = '', $param = 0)
{
if($type == 'project')
{
@@ -2857,16 +2884,23 @@ class docModel extends model
$executionLibs[$lib->execution][$lib->id] = $lib;
}
- $executionPairs = $this->dao->select('id,name')->from(TABLE_EXECUTION)
- ->where('id')->in(array_keys($executionLibs))
- ->fetchPairs();
+ $executionPairs = $this->dao->select('id,name')->from(TABLE_EXECUTION)->where('id')->in(array_keys($executionLibs))->fetchPairs();
}
+ $release = null;
+ if($browseType == 'byrelease' and $param) $release = $this->loadModel('api')->getRelease(0, 'byId', $param);
+
$libTree = array($type => array());
$apiLibs = array();
$apiLibIDList = array();
foreach($libs as $lib)
{
+ $releaseModule = array();
+ if($release and $release->lib == $lib->id)
+ {
+ foreach($release->snap['modules'] as $module) $releaseModule[$module['id']] = (object)$module;
+ }
+
$item = new stdclass();
$item->id = $lib->id;
$item->type = $lib->type == 'api' ? 'api' : 'lib';
@@ -2874,7 +2908,7 @@ class docModel extends model
$item->objectType = $type;
$item->objectID = $objectID;
$item->active = $lib->id == $libID && $browseType != 'bySearch' ? 1 : 0;
- $item->children = $this->getModuleTree($lib->id, $moduleID, $lib->type == 'api' ? 'api' : 'doc');
+ $item->children = $this->getModuleTree($lib->id, $moduleID, $lib->type == 'api' ? 'api' : 'doc', 0, $releaseModule);
$showDoc = $this->loadModel('setting')->getItem('owner=' . $this->app->user->account . '&module=doc&key=showDoc');
$showDoc = $showDoc === '0' ? 0 : 1;
if($this->app->rawMethod == 'view' and $lib->type != 'api' and $showDoc)
@@ -2975,6 +3009,47 @@ class docModel extends model
}
if($type != 'project') $libTree = array_values($libTree[$type]);
+ if($type == 'mine')
+ {
+ $myLib = new stdclass();
+ $myLib->id = 0;
+ $myLib->name = $this->lang->doc->myLib;
+ $myLib->type = 'min';
+ $myLib->objectType = 'doc';
+ $myLib->objectID = 0;
+ $myLib->active = 0;
+ $myLib->hasAction = false;
+ $myLib->children = $libTree;
+
+ $myView = new stdclass();
+ $myView->id = 0;
+ $myView->name = $this->lang->doc->myView;
+ $myView->type = 'view';
+ $myView->objectType = 'doc';
+ $myView->objectID = 0;
+ $myView->hasAction = false;
+ $myView->active = zget($this->app->rawParams, 'type', '') == 'view' ? 1 : 0;
+
+ $myCollection = new stdclass();
+ $myCollection->id = 0;
+ $myCollection->name = $this->lang->doc->myCollection;
+ $myCollection->type = 'collect';
+ $myCollection->objectType = 'doc';
+ $myCollection->objectID = 0;
+ $myCollection->hasAction = false;
+ $myCollection->active = zget($this->app->rawParams, 'type', '') == 'collect' ? 1 : 0;
+
+ $myCreation = new stdclass();
+ $myCreation->id = 0;
+ $myCreation->name = $this->lang->doc->myCreation;
+ $myCreation->type = 'createdBy';
+ $myCreation->objectType = 'doc';
+ $myCreation->objectID = 0;
+ $myCreation->hasAction = false;
+ $myCreation->active = zget($this->app->rawParams, 'type', '') == 'createdBy' ? 1 : 0;
+
+ $libTree = array($myLib, $myView, $myCollection, $myCreation);
+ }
return $libTree;
}
@@ -3072,4 +3147,92 @@ class docModel extends model
if(count($sameNames) > 0 and $libType == 'project') dao::$errors['name'] = $this->lang->doclib->apiNameUnique[$libType] . sprintf($this->lang->error->unique, $this->lang->doclib->name, $lib->name);
if(count($sameNames) > 0 and $libType == 'nolink') dao::$errors['name'] = $this->lang->doclib->apiNameUnique[$libType] . sprintf($this->lang->error->unique, $this->lang->doclib->name, $lib->name);
}
+
+ /**
+ * Create action.
+ *
+ * @param int $docID
+ * @param string $action view|collect
+ * @param string $account
+ * @access public
+ * @return int|bool
+ */
+ public function createAction($docID, $action, $account = '')
+ {
+ $docID = (int)$docID;
+ if(empty($docID)) return false;
+
+ if(empty($account))$account = $this->app->user->account;
+ if($action == 'collect') $this->dao->delete()->from(TABLE_DOCACTION)->where('doc')->eq($docID)->andWhere('action')->eq('collect')->andWhere('actor')->eq($account)->exec();
+
+ $data = new stdclass();
+ $data->doc = $docID;
+ $data->action = $action;
+ $data->actor = $account;
+ $data->date = helper::now();
+ $this->dao->insert(TABLE_DOCACTION)->data($data)->autoCheck()->exec();
+ if(!dao::isError())
+ {
+ $actionID = $this->dao->lastInsertID();
+ if($action == 'view') $this->dao->update(TABLE_DOC)->set('views = views + 1')->where('id')->eq($docID)->exec();
+ if($action == 'collect')
+ {
+ $collectCount = $this->dao->select('count(*) as count')->from(TABLE_DOCACTION)->where('doc')->eq($docID)->andWhere('action')->eq('collect')->fetch('count');
+ $this->dao->update(TABLE_DOC)->set('collector')->eq($collectCount)->where('id')->eq($docID)->exec();
+ }
+
+ return $actionID;
+ }
+ }
+
+ /**
+ * Get action by object.
+ *
+ * @param int $docID
+ * @param string $action view|collect
+ * @param string $account
+ * @access public
+ * @return object
+ */
+ public function getActionByObject($docID, $action, $account = '')
+ {
+ if(empty($account)) $account = $this->app->user->account;
+ return $this->dao->select('*')->from(TABLE_DOCACTION)->where('doc')->eq((int)$docID)->andWhere('action')->eq($action)->andWhere('actor')->eq($account)->fetch();
+ }
+
+ /**
+ * Delete action.
+ *
+ * @param int $actionID
+ * @access public
+ * @return void
+ */
+ public function deleteAction($actionID)
+ {
+ $action = $this->dao->select('*')->from(TABLE_DOCACTION)->where('id')->eq($actionID)->fetch();
+ $this->dao->delete()->from(TABLE_DOCACTION)->where('id')->eq($actionID)->exec();
+ if($action->action == 'collect')
+ {
+ $collectCount = $this->dao->select('count(*) as count')->from(TABLE_DOCACTION)->where('doc')->eq($action->doc)->andWhere('action')->eq('collect')->fetch('count');
+ $this->dao->update(TABLE_DOC)->set('collector')->eq($collectCount)->where('id')->eq($action->doc)->exec();
+ }
+ }
+
+ /**
+ * Process collector to account.
+ *
+ * @param array $docs
+ * @access public
+ * @return array
+ */
+ public function processCollector($docs)
+ {
+ $actionGroup = $this->dao->select('*')->from(TABLE_DOCACTION)->where('doc')->in(array_keys($docs))->andWhere('action')->eq('collect')->fetchGroup('doc', 'actor');
+ foreach($docs as $docID => $doc)
+ {
+ $doc->collector = '';
+ if(isset($actionGroup[$docID])) $doc->collector = ',' . implode(',', array_keys($actionGroup[$docID])) . ',';
+ }
+ return $docs;
+ }
}
diff --git a/module/doc/view/content.html.php b/module/doc/view/content.html.php
index de873d0a55..d438656fb2 100644
--- a/module/doc/view/content.html.php
+++ b/module/doc/view/content.html.php
@@ -1,7 +1,7 @@
doc->confirmDelete);?>
-
-
+
+
+
doc->diff?>
', '', "title='{$lang->fullscreen}' class='btn btn-link fullscreen-btn'");
+ echo html::a("javascript:fullScreen()", '
', '', "title='{$lang->fullscreen}' class='btn btn-link fullscreen-btn'");
if(common::hasPriv('doc', 'edit'))
{
$iframe = '';
@@ -41,12 +42,7 @@
$iframe = 'iframe';
$onlybody = 'true';
}
- echo html::a(inlink('edit', "docID=$doc->id&comment=false&objectType=$objectType&objectID=$object->id&libID=$libID", '', $onlybody), '
', '', "title='{$lang->doc->edit}' class='btn btn-link $iframe' data-app='{$this->app->tab}'");
- }
- if(common::hasPriv('doc', 'delete'))
- {
- $deleteURL = $this->createLink('doc', 'delete', "docID=$doc->id&confirm=yes&from=lib");
- echo html::a("javascript:ajaxDeleteDoc(\"$deleteURL\", \"docList\", confirmDelete)", '
', '', "title='{$lang->doc->delete}' class='btn btn-link'");
+ echo html::a(inlink('edit', "docID=$doc->id&comment=false&objectType=$objectType&objectID=$object->id&libID=$libID", '', $onlybody), '
', '', "title='{$lang->doc->edit}' class='btn btn-link $iframe' data-app='{$this->app->tab}'");
}
?>
@@ -54,6 +50,14 @@
id&objectType=doc");?>" title="doc->collect;?>" class='ajaxCollect btn btn-link'>
+ createLink('doc', 'delete', "docID=$doc->id&confirm=yes&from=lib");
+ echo html::a("javascript:ajaxDeleteDoc(\"$deleteURL\", \"docList\", confirmDelete)", '
', '', "title='{$lang->doc->delete}' class='btn btn-link'");
+ }?>
+
history?>>
+
config->edition == 'max' and $this->app->tab == 'project'):?>
import}'>";
- echo html::a('javascript:;', "
", '', "data-toggle='dropdown' class='btn btn-link'");
+ echo html::a('javascript:;', "
", '', "data-toggle='dropdown' class='btn btn-link'");
echo "
-
+
createLink('action', 'comment', "objectType=doc&objectID=$doc->id");
@@ -166,62 +170,6 @@
-
config->edition == 'max'):?>
diff --git a/module/doc/view/createtexttype.html.php b/module/doc/view/createtexttype.html.php
index 29fef3b581..079f223b97 100644
--- a/module/doc/view/createtexttype.html.php
+++ b/module/doc/view/createtexttype.html.php
@@ -68,7 +68,7 @@
| doc->project;?> |
- project : $objectID, "class='form-control chosen' onchange=loadExecutions(this.value)");?> |
+ project : $objectID, "class='form-control picker-select' onchange=loadExecutions(this.value)");?> |
app->tab == 'doc'):?>
doc->execution?> |
doc->placeholder->execution}' onchange='loadObjectModules(\"execution\", this.value)'")?> |
@@ -77,17 +77,17 @@
| doc->execution;?> |
- |
+ |
| doc->product;?> |
- |
+ |
| doc->libAndModule?> |
- |
+ |
| doc->keywords;?> |
diff --git a/module/doc/view/doclist.html.php b/module/doc/view/doclist.html.php
index 05c88973bb..62719df5f6 100644
--- a/module/doc/view/doclist.html.php
+++ b/module/doc/view/doclist.html.php
@@ -16,6 +16,7 @@ body {margin-bottom: 25px;}
#docListForm th.c-user {width: 80px;}
#docListForm th.c-actions {width: 84px; padding-left: 15px;}
#docListForm .checkbox-primary {line-height: 16px;}
+#docListForm table .checkbox-primary {top: -2px;}
#docListForm .checkbox-primary > label {height: 16px; line-height: 16px; padding-left: 16px;}
#docListForm .checkbox-primary > label:before {left: -1px; font-size: 10px;}
#docListForm .checkbox-primary > label:after {width: 12px; height: 12px;}
diff --git a/module/doc/view/edittexttype.html.php b/module/doc/view/edittexttype.html.php
index fe019995e1..272ce6ca7a 100644
--- a/module/doc/view/edittexttype.html.php
+++ b/module/doc/view/edittexttype.html.php
@@ -70,12 +70,12 @@
| doc->project;?> |
- |
+ |
| doc->libAndModule?> |
- lib . '_' . $doc->module, "class='form-control chosen'");?> |
+ lib . '_' . $doc->module, "class='form-control picker-select'");?> |
| doc->keywords;?> |
diff --git a/module/doc/view/lefttree.html.php b/module/doc/view/lefttree.html.php
index 5b101f17f4..25199c0572 100644
--- a/module/doc/view/lefttree.html.php
+++ b/module/doc/view/lefttree.html.php
@@ -23,13 +23,13 @@
.input-tree {width: 120px;}
.tree-icon {position: absolute; right: 0;}
.tree li.has-input {overflow: hidden;}
+.tree li.has-input > input {margin-left: 15px;}
.img-lib {flex: 0 0 14px; height: 14px; margin-right: 5px;}
.tree-icon {position: absolute; right: 0;}
.tree li > a {max-width: 100%; padding: 2px;}
.file-tree a.show-icon > div,
.file-tree a.hover > div {padding-right: 15px;}
-.tree li.has-input {overflow: hidden;}
-.tree-text {overflow: hidden;}
+.tree-text {overflow: hidden; min-width: 50px;}
i.btn-info, i.btn-info:hover {border: none; background: #fff; box-shadow: unset;}
.tree-version-trigger {padding: 0 10px; width: 54px; border-radius: 5px; background: #F9F9F9; display: flex; align-items: center;}
.tree-version-trigger > .text {overflow: hidden; flex: 0 0 30px;}
@@ -39,7 +39,7 @@ i.btn-info, i.btn-info:hover {border: none; background: #fff; box-shadow: unset;
/* css for sidebar */
.sidebar-toggle {flex: 0 0 16px;}
.sidebar-toggle > .icon {width: 12px; height: 30px; margin-top: -10px; line-height: 30px; color: #fff; text-align: center; background: #7dcdfe; border-radius: 6px; cursor: pointer; padding-left: 0; padding-top: 0;}
-.bottom-btn-tree {position: fixed; bottom: 66px; left: 48px;}
+.bottom-btn-tree {position: absolute; bottom: 15px; left: 25px;}
.spliter {flex: 0 0 12px;}
.spliter-btn {height: 28px; width: 10px; background: #fff; position: absolute; top: 30%; left: -1px; border: 1px solid #D9D9D9; border-radius: 2px;}
@@ -92,39 +92,54 @@ js::set('canViewFiles', common::hasPriv('doc', 'showfiles'));
-
-
-
-
-
-
-
-
-rawModule, 'editLib') || common::hasPriv($app->rawModule, 'deleteLib'));?>
+
';
$item += '';
if(item.versions) versionsData[item.id] = item.versions;
@@ -268,8 +288,8 @@ $(function()
if(isFirstLoad) ele.data('zui.tree').collapse();
- var $leaf = ele.find('li.active');
- if($leaf.length) $leaf[$leaf.length - 1].scrollIntoView(false);
+ var $leaf = ele.find('li.active > a');
+ if($leaf.length && $('#fileTree').height() >= $('#sideBar').height()) $('#sideBar')[0].scrollTop = $($leaf[$leaf.length - 1]).offset().top;
ele.on('click', '.icon-drop', function(e)
{
@@ -329,8 +349,9 @@ $(function()
if($(this).data('type') == 'annex') return;
if(!$(this).data('action')) return;
- var libClass = '.libDorpdown';
- if(!$(this).hasClass('lib')) libClass = '.moduleDorpdown';
+ var moduleType = $(this).data('type') == 'api' ? 'api' : 'doc';
+ var libClass = '.' + moduleType + 'LibDorpdown';
+ if(!$(this).hasClass('lib')) libClass = '.' + moduleType + 'ModuleDorpdown';
$(this).find('.icon').removeClass('hidden');
$(this).addClass('show-icon');
@@ -360,7 +381,7 @@ $(function()
libID = $(this).closest('.lib').data('id');
}
- return lcatePage(libID, moduleID, $(this).data('type'));
+ return locatePage(libID, moduleID, $(this).data('type'));
}).on('click', '.tree-version-trigger', function(e)
{
$('.dropdown-in-tree').remove();
@@ -408,8 +429,10 @@ $(function()
* @access public
* @return void
*/
- function lcatePage(libID, moduleID, type)
+ function locatePage(libID, moduleID, type)
{
+ if(!libID) libID = 0;
+ if(!moduleID) moduleID = 0;
linkParams = linkParams.replace('%s', 'libID=' + libID + '&moduleID=' + moduleID);
var methodName = '';
if(config.currentModule == 'api')
@@ -422,10 +445,13 @@ $(function()
methodName = 'showFiles';
linkParams = 'type=' + objectType + '&objectID=' + objectID;
}
- else if(objectType == 'mine')
+ else if(objectType == 'mine' || objectType == 'view' || objectType == 'collect' || objectType == 'createdBy')
{
+ var mySpaceType = 'mine';
+ if(type == 'view' || type == 'collect' || type == 'createdBy') mySpaceType = type;
+
methodName = 'mySpace';
- linkParams = 'type=mine&libID=' + libID;
+ linkParams = 'type='+ mySpaceType + '&libID=' + libID + '&moduleID=' + moduleID;
}
else if(['text', 'word', 'ppt', 'excel'].indexOf(type) !== -1)
{
@@ -509,7 +535,7 @@ $(function()
else
{
var $input = $('[data-id=ulTreeModal]').html();
- var $rootDom = $('[data-id=' + item.libid + ']a');
+ var $rootDom = $('[data-id=' + item.libid + ']a.lib');
var $li = $rootDom.parent();
moduleData.isUpdate = true;
$rootDom.after($input);
@@ -548,9 +574,16 @@ $(function()
}
}).on('click', '#versionSwitcher a', function()
{
- var $item = $(this);
- linkParams = linkParams.replace('%s', 'libID=' + $item.closest('ul').data('libId') + '&moduleID=0&apiID=0&version=0&release=' + $item.data('id'));
- location.href = createLink(config.currentModule, 'index', linkParams);
+ var libID = $(this).closest('#versionSwitcher').data('lib');
+ var moduleID = $(this).data('id');
+ var params = 'libID=' + libID + '&moduleID=0&apiID=0&version=0&release=' + moduleID;
+ var methodName = config.currentMethod;
+ if(config.currentModule == 'doc')
+ {
+ params = linkParams.replace('%s', 'libID=' + libID + '&moduleID=' + moduleID).replace('browseType=&', 'browseType=byrelease&').replace('param=0', 'param=' + moduleID);
+ methodName = objectType + 'Space';
+ }
+ location.href = createLink(config.currentModule, methodName, params);
}).on('blur', '.file-tree input.input-tree', function()
{
var $input = $(this);
@@ -583,7 +616,7 @@ $(function()
}
var module = result.module;
- return lcatePage(module.root, module.id, 'doc');
+ return locatePage(module.root, module.id, 'doc');
});
}).on('keydown', '.file-tree input.input-tree', function(e)
{
diff --git a/module/doc/view/mydoclist.html.php b/module/doc/view/mydoclist.html.php
index b877953a20..5e2b984ee2 100644
--- a/module/doc/view/mydoclist.html.php
+++ b/module/doc/view/mydoclist.html.php
@@ -15,10 +15,10 @@ body {margin-bottom: 25px;}
#docListForm th.c-id {width: 72px;}
#docListForm th.c-user {width: 80px;}
#docListForm th.c-actions {width: 84px; padding-left: 15px;}
-#docListForm .checkbox-primary {line-height: 16px;}
-#docListForm .checkbox-primary > label {height: 16px; line-height: 16px; padding-left: 16px;}
-#docListForm .checkbox-primary > label:before {left: -1px; font-size: 10px;}
-#docListForm .checkbox-primary > label:after {width: 12px; height: 12px;}
+#docListForm .table .c-name > .doc-title {display: inline-block; max-width: 90%; overflow: hidden; background: transparent; padding-right:0px;}
+#docListForm .table .c-name > .draft {background-color:rgba(129, 102, 238, 0.12); color:#8166EE;}
+#docListForm .table .c-name > .ajaxCollect {float: right; position: relative; right: 10px; top: 0px;}
+#docListForm table.table > thead > tr {height: 32px;}