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 @@ + + + + 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; + } + + +