diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index a6d40355b..035c40d92 100755
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -229,6 +229,7 @@ organizations=组织
images = 云脑镜像
search=搜索
code=代码
+data_analysis=数字看板
repo_no_results=未找到匹配的项目。
dataset_no_results = 未找到匹配的数据集。
user_no_results=未找到匹配的用户。
@@ -2162,19 +2163,6 @@ repos.stars=点赞数
repos.forks=派生数
repos.issues=任务数
repos.size=大小
-repos.id=ID
-repos.projectName=项目名称
-repos.isPrivate=私有
-repos.openi=OpenI指数
-repos.visit=浏览量
-repos.download=代码下载量
-repos.pr=PR数
-repos.commit=Commit数
-repos.closedIssues=已解决任务数
-repos.contributor=贡献者数
-repos.yes=是
-repos.no=否
-
datasets.dataset_manage_panel=数据集管理
datasets.owner=所有者
diff --git a/public/img/name.png b/public/img/name.png
new file mode 100755
index 000000000..11a5c8751
Binary files /dev/null and b/public/img/name.png differ
diff --git a/public/img/overview.png b/public/img/overview.png
new file mode 100755
index 000000000..b704ead84
Binary files /dev/null and b/public/img/overview.png differ
diff --git a/public/img/pro.png b/public/img/pro.png
new file mode 100755
index 000000000..7d44e180e
Binary files /dev/null and b/public/img/pro.png differ
diff --git a/public/img/pro.svg b/public/img/pro.svg
new file mode 100755
index 000000000..c7bcfff8b
--- /dev/null
+++ b/public/img/pro.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/routers/home.go b/routers/home.go
index dfdb030e4..d2c93c771 100755
--- a/routers/home.go
+++ b/routers/home.go
@@ -33,8 +33,9 @@ const (
// tplExploreOrganizations explore organizations page template
tplExploreOrganizations base.TplName = "explore/organizations"
// tplExploreCode explore code page template
- tplExploreCode base.TplName = "explore/code"
- tplExploreImages base.TplName = "explore/images"
+ tplExploreCode base.TplName = "explore/code"
+ tplExploreImages base.TplName = "explore/images"
+ tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis"
)
// Home render home page
@@ -501,6 +502,10 @@ func ExploreImages(ctx *context.Context) {
ctx.HTML(200, tplExploreImages)
}
+func ExploreDataAnalysis(ctx *context.Context) {
+ ctx.HTML(200, tplExploreExploreDataAnalysis)
+}
+
// NotFound render 404 page
func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index 383a308b6..a3de68373 100755
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -325,6 +325,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/organizations", routers.ExploreOrganizations)
m.Get("/code", routers.ExploreCode)
m.Get("/images", routers.ExploreImages)
+ m.Get("/data_analysis", routers.ExploreDataAnalysis)
}, ignSignIn)
m.Combo("/install", routers.InstallInit).Get(routers.Install).
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl
index 57df408c9..67d8fc1b7 100755
--- a/templates/base/head_navbar.tmpl
+++ b/templates/base/head_navbar.tmpl
@@ -37,6 +37,7 @@
{{.i18n.Tr "explore.users"}}
{{.i18n.Tr "explore.organizations"}}
{{.i18n.Tr "explore.images"}}
+ {{.i18n.Tr "explore.data_analysis"}}
{{else if .IsLandingPageHome}}
diff --git a/templates/base/head_navbar_home.tmpl b/templates/base/head_navbar_home.tmpl
index e6729da62..e334efba8 100644
--- a/templates/base/head_navbar_home.tmpl
+++ b/templates/base/head_navbar_home.tmpl
@@ -29,6 +29,7 @@
{{.i18n.Tr "explore.users"}}
{{.i18n.Tr "explore.organizations"}}
{{.i18n.Tr "explore.images"}}
+ {{.i18n.Tr "explore.data_analysis"}}
{{else if .IsLandingPageHome}}
diff --git a/templates/explore/data_analysis.tmpl b/templates/explore/data_analysis.tmpl
new file mode 100755
index 000000000..8277dc902
--- /dev/null
+++ b/templates/explore/data_analysis.tmpl
@@ -0,0 +1,14 @@
+{{template "base/head" .}}
+
+
+
+
+
+{{template "base/footer" .}}
+
+
diff --git a/web_src/js/components/DataAnalysis.vue b/web_src/js/components/DataAnalysis.vue
new file mode 100755
index 000000000..8c632bccb
--- /dev/null
+++ b/web_src/js/components/DataAnalysis.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+ 概览
+
+ 概览.......
+ 概览.......
+ 概览.......
概览.......
+ 概览.......
+
+
+
+
+
+
+ 项目分析
+
+
+
+
+
+
+ 用户分析
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web_src/js/components/ProAnalysis.vue b/web_src/js/components/ProAnalysis.vue
new file mode 100755
index 000000000..7df0854e5
--- /dev/null
+++ b/web_src/js/components/ProAnalysis.vue
@@ -0,0 +1,923 @@
+
+
+
+
+ 项目分析 数据更新时间:{{lastUpdatedTime}}/{{recordBeginTime}}
+
+
+
+
+
统计周期:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{scope.row.name}}
+
+
+
+
+ {{scope.row.isPrivate|changeType}}
+
+
+
+
+ {{scope.row.openi | rounding}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ OpenI / {{this.pro_name}} 数据更新时间:{{tableDataIDTotal.lastUpdatedTime}}/{{tableDataIDTotal.recordBeginTime}}
+
+
+ 项目描述:{{tableDataIDTotal.description | discriptionFun}}
+
+
+
+
+ 项目创建时间
+ {{tableDataIDTotal.creatTime}}
+
+
+ OPenI指数
+ {{tableDataIDTotal.openi | rounding}}
+
+
+ 评论数
+ {{tableDataIDTotal.comment}}
+
+
+ 浏览数
+ {{tableDataIDTotal.view}}
+
+
+ 代码下载量
+ {{tableDataIDTotal.download}}
+
+
+ 任务完成比例
+ {{tableDataIDTotal.issueClosedRatio}}
+
+
+
+
+
+
+
+
OpenI指数:{{tableDataIDTotal.openi | rounding}}
+
+
+
+
+
+
+
+
+
+
贡献者TOP10
+
+
+
+
+
+
+ {{scope.row.mode | showMode}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
统计周期:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web_src/js/components/UserAnalysis.vue b/web_src/js/components/UserAnalysis.vue
new file mode 100755
index 000000000..547a077f4
--- /dev/null
+++ b/web_src/js/components/UserAnalysis.vue
@@ -0,0 +1,415 @@
+
+
+
+ 用户分析 数据更新时间:{{lastUpdatedTime}}/{{recordBeginTime}}
+
+
+
统计周期:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 下载报告
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{scope.row.RegistDate | transformTimestamp}}
+
+
+
+
+ {{scope.row.CountDate | transformTimestamp}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web_src/js/excel/Blob.js b/web_src/js/excel/Blob.js
new file mode 100755
index 000000000..c43291d34
--- /dev/null
+++ b/web_src/js/excel/Blob.js
@@ -0,0 +1,179 @@
+/* eslint-disable */
+/* Blob.js
+ * A Blob implementation.
+ * 2014-05-27
+ *
+ * By Eli Grey, http://eligrey.com
+ * By Devin Samarin, https://github.com/eboyjr
+ * License: X11/MIT
+ * See LICENSE.md
+ */
+
+/*global self, unescape */
+/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
+ plusplus: true */
+
+/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
+
+(function (view) {
+ "use strict";
+
+ view.URL = view.URL || view.webkitURL;
+
+ if (view.Blob && view.URL) {
+ try {
+ new Blob;
+ return;
+ } catch (e) {}
+ }
+
+ // Internally we use a BlobBuilder implementation to base Blob off of
+ // in order to support older browsers that only have BlobBuilder
+ var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
+ var
+ get_class = function(object) {
+ return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
+ }
+ , FakeBlobBuilder = function BlobBuilder() {
+ this.data = [];
+ }
+ , FakeBlob = function Blob(data, type, encoding) {
+ this.data = data;
+ this.size = data.length;
+ this.type = type;
+ this.encoding = encoding;
+ }
+ , FBB_proto = FakeBlobBuilder.prototype
+ , FB_proto = FakeBlob.prototype
+ , FileReaderSync = view.FileReaderSync
+ , FileException = function(type) {
+ this.code = this[this.name = type];
+ }
+ , file_ex_codes = (
+ "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
+ + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
+ ).split(" ")
+ , file_ex_code = file_ex_codes.length
+ , real_URL = view.URL || view.webkitURL || view
+ , real_create_object_URL = real_URL.createObjectURL
+ , real_revoke_object_URL = real_URL.revokeObjectURL
+ , URL = real_URL
+ , btoa = view.btoa
+ , atob = view.atob
+
+ , ArrayBuffer = view.ArrayBuffer
+ , Uint8Array = view.Uint8Array
+ ;
+ FakeBlob.fake = FB_proto.fake = true;
+ while (file_ex_code--) {
+ FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
+ }
+ if (!real_URL.createObjectURL) {
+ URL = view.URL = {};
+ }
+ URL.createObjectURL = function(blob) {
+ var
+ type = blob.type
+ , data_URI_header
+ ;
+ if (type === null) {
+ type = "application/octet-stream";
+ }
+ if (blob instanceof FakeBlob) {
+ data_URI_header = "data:" + type;
+ if (blob.encoding === "base64") {
+ return data_URI_header + ";base64," + blob.data;
+ } else if (blob.encoding === "URI") {
+ return data_URI_header + "," + decodeURIComponent(blob.data);
+ } if (btoa) {
+ return data_URI_header + ";base64," + btoa(blob.data);
+ } else {
+ return data_URI_header + "," + encodeURIComponent(blob.data);
+ }
+ } else if (real_create_object_URL) {
+ return real_create_object_URL.call(real_URL, blob);
+ }
+ };
+ URL.revokeObjectURL = function(object_URL) {
+ if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
+ real_revoke_object_URL.call(real_URL, object_URL);
+ }
+ };
+ FBB_proto.append = function(data/*, endings*/) {
+ var bb = this.data;
+ // decode data to a binary string
+ if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
+ var
+ str = ""
+ , buf = new Uint8Array(data)
+ , i = 0
+ , buf_len = buf.length
+ ;
+ for (; i < buf_len; i++) {
+ str += String.fromCharCode(buf[i]);
+ }
+ bb.push(str);
+ } else if (get_class(data) === "Blob" || get_class(data) === "File") {
+ if (FileReaderSync) {
+ var fr = new FileReaderSync;
+ bb.push(fr.readAsBinaryString(data));
+ } else {
+ // async FileReader won't work as BlobBuilder is sync
+ throw new FileException("NOT_READABLE_ERR");
+ }
+ } else if (data instanceof FakeBlob) {
+ if (data.encoding === "base64" && atob) {
+ bb.push(atob(data.data));
+ } else if (data.encoding === "URI") {
+ bb.push(decodeURIComponent(data.data));
+ } else if (data.encoding === "raw") {
+ bb.push(data.data);
+ }
+ } else {
+ if (typeof data !== "string") {
+ data += ""; // convert unsupported types to strings
+ }
+ // decode UTF-16 to binary string
+ bb.push(unescape(encodeURIComponent(data)));
+ }
+ };
+ FBB_proto.getBlob = function(type) {
+ if (!arguments.length) {
+ type = null;
+ }
+ return new FakeBlob(this.data.join(""), type, "raw");
+ };
+ FBB_proto.toString = function() {
+ return "[object BlobBuilder]";
+ };
+ FB_proto.slice = function(start, end, type) {
+ var args = arguments.length;
+ if (args < 3) {
+ type = null;
+ }
+ return new FakeBlob(
+ this.data.slice(start, args > 1 ? end : this.data.length)
+ , type
+ , this.encoding
+ );
+ };
+ FB_proto.toString = function() {
+ return "[object Blob]";
+ };
+ FB_proto.close = function() {
+ this.size = this.data.length = 0;
+ };
+ return FakeBlobBuilder;
+ }(view));
+
+ view.Blob = function Blob(blobParts, options) {
+ var type = options ? (options.type || "") : "";
+ var builder = new BlobBuilder();
+ if (blobParts) {
+ for (var i = 0, len = blobParts.length; i < len; i++) {
+ builder.append(blobParts[i]);
+ }
+ }
+ return builder.getBlob(type);
+ };
+}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
\ No newline at end of file
diff --git a/web_src/js/excel/Export2Excel.js b/web_src/js/excel/Export2Excel.js
new file mode 100755
index 000000000..3c6d022af
--- /dev/null
+++ b/web_src/js/excel/Export2Excel.js
@@ -0,0 +1,141 @@
+/* eslint-disable */
+require('script-loader!file-saver');
+require('./Blob');
+require('script-loader!xlsx/dist/xlsx.core.min');
+function generateArray(table) {
+ var out = [];
+ var rows = table.querySelectorAll('tr');
+ var ranges = [];
+ for (var R = 0; R < rows.length; ++R) {
+ var outRow = [];
+ var row = rows[R];
+ var columns = row.querySelectorAll('td');
+ for (var C = 0; C < columns.length; ++C) {
+ var cell = columns[C];
+ var colspan = cell.getAttribute('colspan');
+ var rowspan = cell.getAttribute('rowspan');
+ var cellValue = cell.innerText;
+ if (cellValue !== "" && cellValue == +cellValue) cellValue = +cellValue;
+
+ //Skip ranges
+ ranges.forEach(function (range) {
+ if (R >= range.s.r && R <= range.e.r && outRow.length >= range.s.c && outRow.length <= range.e.c) {
+ for (var i = 0; i <= range.e.c - range.s.c; ++i) outRow.push(null);
+ }
+ });
+
+ //Handle Row Span
+ if (rowspan || colspan) {
+ rowspan = rowspan || 1;
+ colspan = colspan || 1;
+ ranges.push({s: {r: R, c: outRow.length}, e: {r: R + rowspan - 1, c: outRow.length + colspan - 1}});
+ }
+ ;
+
+ //Handle Value
+ outRow.push(cellValue !== "" ? cellValue : null);
+
+ //Handle Colspan
+ if (colspan) for (var k = 0; k < colspan - 1; ++k) outRow.push(null);
+ }
+ out.push(outRow);
+ }
+ return [out, ranges];
+};
+
+function datenum(v, date1904) {
+ if (date1904) v += 1462;
+ var epoch = Date.parse(v);
+ return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000);
+}
+
+function sheet_from_array_of_arrays(data, opts) {
+ var ws = {};
+ var range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}};
+ for (var R = 0; R != data.length; ++R) {
+ for (var C = 0; C != data[R].length; ++C) {
+ if (range.s.r > R) range.s.r = R;
+ if (range.s.c > C) range.s.c = C;
+ if (range.e.r < R) range.e.r = R;
+ if (range.e.c < C) range.e.c = C;
+ var cell = {v: data[R][C]};
+ if (cell.v == null) continue;
+ var cell_ref = XLSX.utils.encode_cell({c: C, r: R});
+
+ if (typeof cell.v === 'number') cell.t = 'n';
+ else if (typeof cell.v === 'boolean') cell.t = 'b';
+ else if (cell.v instanceof Date) {
+ cell.t = 'n';
+ cell.z = XLSX.SSF._table[14];
+ cell.v = datenum(cell.v);
+ }
+ else cell.t = 's';
+
+ ws[cell_ref] = cell;
+ }
+ }
+ if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range);
+ return ws;
+}
+
+function Workbook() {
+ if (!(this instanceof Workbook)) return new Workbook();
+ this.SheetNames = [];
+ this.Sheets = {};
+}
+
+function s2ab(s) {
+ var buf = new ArrayBuffer(s.length);
+ var view = new Uint8Array(buf);
+ for (var i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
+ return buf;
+}
+
+export function export_table_to_excel(id) {
+ var theTable = document.getElementById(id);
+ console.log('a')
+ var oo = generateArray(theTable);
+ var ranges = oo[1];
+
+ /* original data */
+ var data = oo[0];
+ var ws_name = "SheetJS";
+ console.log(data);
+
+ var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
+
+ /* add ranges to worksheet */
+ // ws['!cols'] = ['apple', 'banan'];
+ ws['!merges'] = ranges;
+
+ /* add worksheet to workbook */
+ wb.SheetNames.push(ws_name);
+ wb.Sheets[ws_name] = ws;
+
+ var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
+
+ saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), "test.xlsx")
+}
+
+function formatJson(jsonData) {
+ console.log(jsonData)
+}
+export function export_json_to_excel(th, jsonData, defaultTitle) {
+
+ /* original data */
+
+ var data = jsonData;
+ data.unshift(th);
+ var ws_name = "SheetJS";
+
+ var wb = new Workbook(), ws = sheet_from_array_of_arrays(data);
+
+
+ /* add worksheet to workbook */
+ wb.SheetNames.push(ws_name);
+ wb.Sheets[ws_name] = ws;
+
+ var wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: false, type: 'binary'});
+ var title = defaultTitle || '列表'
+ saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), title + ".xlsx")
+}
\ No newline at end of file
diff --git a/web_src/js/excel/util.js b/web_src/js/excel/util.js
new file mode 100755
index 000000000..b600e5e37
--- /dev/null
+++ b/web_src/js/excel/util.js
@@ -0,0 +1,17 @@
+export function export2Excel(columns,list,filename){
+ require.ensure([], () => {
+ const { export_json_to_excel } = require('./Export2Excel');
+ let tHeader = []
+ let filterVal = []
+ console.log(columns)
+ if(!columns){
+ return;
+ }
+ columns.forEach(item =>{
+ tHeader.push(item.title)
+ filterVal.push(item.key)
+ })
+ const data = list.map(v => filterVal.map(j => v[j]))
+ export_json_to_excel(tHeader, data, filename);
+ })
+}
\ No newline at end of file
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 560683b7e..a6d07d0bc 100755
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -13,7 +13,7 @@ import qs from 'qs';
import 'jquery.are-you-sure';
import './vendor/semanticdropdown.js';
import {svg} from './utils.js';
-
+import echarts from 'echarts'
import initContextPopups from './features/contextpopup.js';
import initGitGraph from './features/gitgraph.js';
import initClipboard from './features/clipboard.js';
@@ -35,8 +35,9 @@ import {createCodeEditor} from './features/codeeditor.js';
import MinioUploader from './components/MinioUploader.vue';
import ObsUploader from './components/ObsUploader.vue';
import EditAboutInfo from './components/EditAboutInfo.vue';
-import Images from './components/Images.vue'
-import EditTopics from './components/EditTopics.vue'
+import Images from './components/Images.vue';
+import EditTopics from './components/EditTopics.vue';
+import DataAnalysis from './components/DataAnalysis.vue'
import Contributors from './components/Contributors.vue'
Vue.use(ElementUI);
@@ -44,6 +45,10 @@ Vue.prototype.$axios = axios;
Vue.prototype.qs = qs;
const {AppSubUrl, StaticUrlPrefix, csrf} = window.config;
+Object.defineProperty(Vue.prototype, '$echarts', {
+ value: echarts
+})
+
function htmlEncode(text) {
return jQuery('')
.text(text)
@@ -2972,6 +2977,7 @@ $(document).ready(async () => {
initVueEditTopic();
initVueContributors();
initVueImages();
+ initVueDataAnalysis();
initTeamSettings();
initCtrlEnterSubmit();
initNavbarContentToggle();
@@ -3713,7 +3719,20 @@ function initVueImages() {
render: h => h(Images)
});
}
+function initVueDataAnalysis() {
+ const el = document.getElementById('data_analysis');
+ console.log("el",el)
+
+ if (!el) {
+ return;
+ }
+ new Vue({
+ el: '#data_analysis',
+
+ render: h => h(DataAnalysis)
+ });
+}
// 新增
function initObsUploader() {
const el = document.getElementById('obsUploader');