diff --git a/backend/entity/doc.go b/backend/entity/doc.go
new file mode 100644
index 00000000..b356d38a
--- /dev/null
+++ b/backend/entity/doc.go
@@ -0,0 +1,8 @@
+package entity
+
+type DocItem struct {
+ Title string `json:"title"`
+ Url string `json:"url"`
+ Path string `json:"path"`
+ Children []DocItem `json:"children"`
+}
diff --git a/backend/main.go b/backend/main.go
index a35291ca..ea9c30df 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -135,6 +135,8 @@ func main() {
// release版本
anonymousGroup.GET("/version", routes.GetVersion) // 获取发布的版本
anonymousGroup.GET("/releases/latest", routes.GetLatestRelease) // 获取最近发布的版本
+ // 文档
+ anonymousGroup.GET("/docs", routes.GetDocs) // 获取文档数据
}
authGroup := app.Group("/", middlewares.AuthorizationMiddleware())
{
@@ -259,7 +261,6 @@ func main() {
authGroup.GET("/git/branches", routes.GetGitBranches) // 获取 Git 分支
authGroup.GET("/git/public-key", routes.GetGitSshPublicKey) // 获取 SSH 公钥
}
-
}
// 路由ping
diff --git a/backend/routes/doc.go b/backend/routes/doc.go
new file mode 100644
index 00000000..6426cdcc
--- /dev/null
+++ b/backend/routes/doc.go
@@ -0,0 +1,25 @@
+package routes
+
+import (
+ "crawlab/services"
+ "github.com/apex/log"
+ "github.com/gin-gonic/gin"
+ "net/http"
+ "runtime/debug"
+)
+
+func GetDocs(c *gin.Context) {
+ type ResData struct {
+ String string `json:"string"`
+ }
+ data, err := services.GetDocs()
+ if err != nil {
+ log.Errorf(err.Error())
+ debug.PrintStack()
+ }
+ c.JSON(http.StatusOK, Response{
+ Status: "ok",
+ Message: "success",
+ Data: ResData{String:data},
+ })
+}
diff --git a/backend/services/doc.go b/backend/services/doc.go
new file mode 100644
index 00000000..572e5cb4
--- /dev/null
+++ b/backend/services/doc.go
@@ -0,0 +1,27 @@
+package services
+
+import (
+ "github.com/apex/log"
+ "github.com/imroc/req"
+ "runtime/debug"
+)
+
+func GetDocs() (data string, err error) {
+ // 获取远端数据
+ res, err := req.Get("https://docs.crawlab.cn/search_plus_index.json")
+ if err != nil {
+ log.Errorf(err.Error())
+ debug.PrintStack()
+ return data, err
+ }
+
+ // 反序列化
+ data, err = res.ToString()
+ if err != nil {
+ log.Errorf(err.Error())
+ debug.PrintStack()
+ return data, err
+ }
+
+ return data, nil
+}
diff --git a/frontend/.env.development b/frontend/.env.development
index ba21c0c6..275af85c 100644
--- a/frontend/.env.development
+++ b/frontend/.env.development
@@ -1,3 +1,4 @@
NODE_ENV='development'
VUE_APP_BASE_URL=http://localhost:8000
-VUE_APP_CRAWLAB_BASE_URL=https://api.crawlab.cn
\ No newline at end of file
+VUE_APP_CRAWLAB_BASE_URL=https://api.crawlab.cn
+VUE_APP_DOC_URL=http://docs.crawlab.cn
diff --git a/frontend/.env.production b/frontend/.env.production
index d01edd25..7cca0821 100644
--- a/frontend/.env.production
+++ b/frontend/.env.production
@@ -1,3 +1,4 @@
NODE_ENV='production'
VUE_APP_BASE_URL=/api
VUE_APP_CRAWLAB_BASE_URL=https://api.crawlab.cn
+VUE_APP_DOC_URL=http://docs.crawlab.cn
diff --git a/frontend/.env.test b/frontend/.env.test
index e9aeafc9..f29b1cd3 100644
--- a/frontend/.env.test
+++ b/frontend/.env.test
@@ -1,3 +1,4 @@
NODE_ENV='test'
VUE_APP_BASE_URL='http://localhost:8000'
VUE_APP_CRAWLAB_BASE_URL=https://api.crawlab.cn
+VUE_APP_DOC_URL=http://docs.crawlab.cn
diff --git a/frontend/src/components/Documentation/Documentation.vue b/frontend/src/components/Documentation/Documentation.vue
new file mode 100644
index 00000000..3c03b267
--- /dev/null
+++ b/frontend/src/components/Documentation/Documentation.vue
@@ -0,0 +1,84 @@
+
+
+
+
+ {{node.label}}
+
+
+
+
+ {{node.label}}
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/i18n/zh.js b/frontend/src/i18n/zh.js
index 10211c3d..f02ca169 100644
--- a/frontend/src/i18n/zh.js
+++ b/frontend/src/i18n/zh.js
@@ -450,6 +450,10 @@ export default {
'General': '通用',
'Enable Tutorial': '启用教程',
+ // 全局
+ 'Related Documentation': '相关文档',
+ 'Click to view related Documentation': '点击查看相关文档',
+
// 其他
tagsView: {
closeOthers: '关闭其他',
diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js
index 4fcb86db..9afa6d40 100644
--- a/frontend/src/store/index.js
+++ b/frontend/src/store/index.js
@@ -17,6 +17,7 @@ import setting from './modules/setting'
import version from './modules/version'
import tour from './modules/tour'
import project from './modules/project'
+import doc from './modules/doc'
import getters from './getters'
Vue.use(Vuex)
@@ -39,6 +40,7 @@ const store = new Vuex.Store({
version,
tour,
project,
+ doc,
// 统计
stats
},
diff --git a/frontend/src/store/modules/doc.js b/frontend/src/store/modules/doc.js
new file mode 100644
index 00000000..7d2e9e98
--- /dev/null
+++ b/frontend/src/store/modules/doc.js
@@ -0,0 +1,61 @@
+import request from '../../api/request'
+
+const state = {
+ docData: []
+}
+
+const getters = {}
+
+const mutations = {
+ SET_DOC_DATA (state, value) {
+ state.docData = value
+ }
+}
+
+const actions = {
+ async getDocData ({ commit }) {
+ const res = await request.get('/docs')
+
+ const data = JSON.parse(res.data.data.string)
+
+ // init cache
+ const cache = {}
+
+ // iterate paths
+ for (let path in data) {
+ if (data.hasOwnProperty(path)) {
+ const d = data[path]
+ if (path.match(/\/$/)) {
+ cache[path] = d
+ cache[path].children = []
+ } else if (path.match(/\.html$/)) {
+ const parentPath = path.split('/')[0] + '/'
+ cache[parentPath].children.push(d)
+ }
+ }
+ }
+
+ commit('SET_DOC_DATA', Object.values(cache).map(d => {
+ d.level = 1
+ d.label = d.title
+ d.url = process.env.VUE_APP_DOC_URL + '/' + d.url
+ if (d.children) {
+ d.children = d.children.map(c => {
+ c.level = 2
+ c.label = c.title
+ c.url = process.env.VUE_APP_DOC_URL + '/' + c.url
+ return c
+ })
+ }
+ return d
+ }))
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ getters,
+ mutations,
+ actions
+}
diff --git a/frontend/src/utils/doc.js b/frontend/src/utils/doc.js
new file mode 100644
index 00000000..ae41c282
--- /dev/null
+++ b/frontend/src/utils/doc.js
@@ -0,0 +1,20 @@
+export default {
+ docs: [
+ {
+ path: '/projects',
+ pattern: '^Project'
+ },
+ {
+ path: '/spiders',
+ pattern: '^Spider'
+ },
+ {
+ path: '/tasks',
+ pattern: '^Task'
+ },
+ {
+ path: '/schedules',
+ pattern: '^Schedule'
+ }
+ ]
+}
diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js
index c371f672..765d0e93 100644
--- a/frontend/src/utils/index.js
+++ b/frontend/src/utils/index.js
@@ -3,11 +3,13 @@ import encrypt from './encrypt'
import tour from './tour'
import log from './log'
import scrapy from './scrapy'
+import doc from './doc'
export default {
stats,
encrypt,
tour,
log,
- scrapy
+ scrapy,
+ doc
}
diff --git a/frontend/src/views/layout/Layout.vue b/frontend/src/views/layout/Layout.vue
index 119c7337..b7aaa004 100644
--- a/frontend/src/views/layout/Layout.vue
+++ b/frontend/src/views/layout/Layout.vue
@@ -1,12 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -18,16 +42,23 @@ import {
TagsView
} from './components'
import ResizeMixin from './mixin/ResizeHandler'
+import Documentation from '../../components/Documentation/Documentation'
export default {
name: 'Layout',
components: {
+ Documentation,
Navbar,
Sidebar,
TagsView,
AppMain
},
mixins: [ResizeMixin],
+ data () {
+ return {
+ isShowDocumentation: false
+ }
+ },
computed: {
sidebar () {
return this.$store.state.app.sidebar
@@ -47,7 +78,18 @@ export default {
methods: {
handleClickOutside () {
this.$store.dispatch('CloseSideBar', { withoutAnimation: false })
+ },
+ onClickDocumentation () {
+ this.isShowDocumentation = true
+
+ this.$st.sendEv('全局', '点击页面文档')
+ },
+ onCloseDocumentation () {
+ this.isShowDocumentation = false
}
+ },
+ async created () {
+ await this.$store.dispatch('doc/getDocData')
}
}
@@ -77,4 +119,45 @@ export default {
position: absolute;
z-index: 999;
}
+
+ .documentation {
+ z-index: 9999;
+ position: fixed;
+ right: 25px;
+ bottom: 20px;
+ font-size: 24px;
+ cursor: pointer;
+ color: #909399;
+ }
+
+
+