├── static ├── .gitkeep └── css │ ├── railscasts.css │ └── application.css ├── .eslintignore ├── src ├── store │ ├── actions.js │ ├── mutation-types.js │ ├── index.js │ └── modules │ │ ├── current-project.js │ │ ├── alert.js │ │ ├── current-platform.js │ │ └── current-user.js ├── assets │ └── logo.png ├── components │ ├── global │ │ ├── LoadingOverlay.vue │ │ ├── UserPanel.vue │ │ ├── ContentFooter.vue │ │ ├── ContentHeader.vue │ │ ├── Alert.vue │ │ ├── SideBarItem.vue │ │ ├── SideBar.vue │ │ ├── TableBox.vue │ │ ├── NavBar.vue │ │ ├── ConfirmModal.vue │ │ └── Pagination.vue │ ├── admin │ │ ├── SideBar.vue │ │ └── TemplateForm.vue │ ├── projects │ │ ├── ServiceField.vue │ │ ├── SideBar.vue │ │ ├── Dependency.vue │ │ ├── Form.vue │ │ ├── Info.vue │ │ └── EnvForm.vue │ └── dashboard │ │ ├── Aggregate.vue │ │ └── WeeklyChart.vue ├── views │ ├── jobs │ │ └── Index.vue │ ├── projects │ │ ├── Home.vue │ │ ├── environments │ │ │ ├── configs │ │ │ │ ├── services │ │ │ │ │ ├── Edit.vue │ │ │ │ │ └── List.vue │ │ │ │ ├── Index.vue │ │ │ │ ├── Fastlane.vue │ │ │ │ └── GitClone.vue │ │ │ ├── New.vue │ │ │ ├── Clone.vue │ │ │ ├── List.vue │ │ │ └── Edit.vue │ │ ├── New.vue │ │ ├── Layout.vue │ │ ├── Edit.vue │ │ ├── List.vue │ │ └── builds │ │ │ ├── Info.vue │ │ │ ├── Detail.vue │ │ │ └── List.vue │ ├── dashboard │ │ └── List.vue │ ├── Layout.vue │ ├── admin │ │ ├── Layout.vue │ │ └── fastlane_templates │ │ │ ├── New.vue │ │ │ ├── Edit.vue │ │ │ └── List.vue │ ├── members │ │ └── List.vue │ ├── activity │ │ └── List.vue │ └── account │ │ └── Login.vue ├── router │ ├── index.js │ └── routes.js ├── constants │ ├── enum.js │ └── api.js ├── utils │ ├── storage.js │ └── networking.js ├── main.js └── App.vue ├── babel.config.js ├── .gitignore ├── .editorconfig ├── .postcssrc.js ├── index.html ├── mock ├── data │ ├── services.json │ ├── users.json │ ├── environments.json │ ├── configs.json │ ├── dependencies.json │ ├── builds.json │ └── projects.json └── server.js ├── .eslintrc.js ├── vue.config.js ├── package.json └── README.md /static/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /src/store/actions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thierryxing/Falcon/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .idea/* 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in projects.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Falcon 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/components/global/LoadingOverlay.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /src/views/jobs/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /src/views/projects/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | -------------------------------------------------------------------------------- /src/views/dashboard/List.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | -------------------------------------------------------------------------------- /src/views/projects/environments/configs/services/Edit.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/store/mutation-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | export const CREATE_USER = 'CREATE_USER' 5 | export const FETCH_USER = 'FETCH_USER' 6 | export const DELETE_USER = 'DELETE_USER' 7 | 8 | export const CREATE_ALERT = 'CREATE_ALERT' 9 | export const DELETE_ALERT = 'DELETE_ALERT' 10 | 11 | export const SET_PROJECT = 'SET_PROJECT' 12 | 13 | export const SET_PLATFORM = 'SET_PLATFORM' 14 | export const FETCH_PLATFORM = 'FETCH_PLATFORM' 15 | -------------------------------------------------------------------------------- /mock/data/services.json: -------------------------------------------------------------------------------- 1 | { 2 | "services": { 3 | "total": 10, 4 | "list": [ 5 | { 6 | "id": "fir", 7 | "name": "Fir", 8 | "desc": "Fir - 服务移动开发者,让开发测试更高效", 9 | "updated_at": "30 Mar 16:27", 10 | "active": true, 11 | "projectId": 4 12 | }, 13 | { 14 | "id": "bugly", 15 | "name": "Bugly", 16 | "desc": "Bugly内测分发平台", 17 | "updated_at": "30 Mar 16:27", 18 | "active": false, 19 | "projectId": 4 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import store from '@/store' 4 | import routes from './routes.js' 5 | 6 | Vue.use(Router) 7 | 8 | const router = new Router({ 9 | routes 10 | }) 11 | 12 | router.beforeEach((to, from, next) => { 13 | // if user not exist, then go to login 14 | if (to.name !== 'login') { 15 | let user = store.getters.currentUser 16 | if (user === null || user.id === null) { 17 | router.replace({name: 'login'}) 18 | } 19 | } 20 | next() 21 | }) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | import Vue from 'vue' 5 | import Vuex from 'vuex' 6 | import * as actions from './actions' 7 | import currentUser from './modules/current-user' 8 | import alert from './modules/alert' 9 | import currentProject from './modules/current-project' 10 | import currentPlatform from './modules/current-platform' 11 | 12 | Vue.use(Vuex) 13 | 14 | const debug = process.env.NODE_ENV !== 'production' 15 | 16 | export default new Vuex.Store({ 17 | actions, 18 | modules: { 19 | currentUser, 20 | alert, 21 | currentProject, 22 | currentPlatform 23 | }, 24 | strict: debug 25 | }) 26 | -------------------------------------------------------------------------------- /src/constants/enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/6/7. 3 | */ 4 | export default { 5 | 6 | ProjectType: { 7 | App: 'App', 8 | Lib: 'Lib' 9 | }, 10 | 11 | Platforms: { 12 | iOS: 'ios', 13 | Android: 'android' 14 | }, 15 | 16 | Templates: { 17 | Prod: 0, 18 | Test: 1, 19 | Beta: 2, 20 | Lib: 3 21 | }, 22 | 23 | BuildStatus: { 24 | Failed: 'failed', 25 | Success: 'success', 26 | Processing: 'processing', 27 | Canceled: 'canceled' 28 | }, 29 | 30 | CloneStatus: { 31 | Prepare: 'prepare', 32 | Processing: 'processing', 33 | Success: 'success', 34 | Failed: 'failed' 35 | } 36 | 37 | } 38 | 39 | -------------------------------------------------------------------------------- /src/store/modules/current-project.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | import * as types from '../mutation-types' 5 | import storage from '@/utils/storage' 6 | 7 | // initial state 8 | const state = { 9 | project: { 10 | guardian: {} 11 | } 12 | } 13 | 14 | // getters 15 | const getters = { 16 | currentProject: state => state.project 17 | } 18 | 19 | // actions 20 | const actions = { 21 | setProject ({commit}, project) { 22 | commit(types.SET_PROJECT, project) 23 | } 24 | } 25 | 26 | // mutations 27 | const mutations = { 28 | [types.SET_PROJECT] (state, project) { 29 | state.project = project 30 | } 31 | } 32 | 33 | export default { 34 | state, 35 | getters, 36 | actions, 37 | mutations 38 | } 39 | -------------------------------------------------------------------------------- /src/components/admin/SideBar.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | -------------------------------------------------------------------------------- /src/components/global/UserPanel.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 31 | -------------------------------------------------------------------------------- /src/store/modules/alert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | import * as types from '../mutation-types' 5 | import storage from '@/utils/storage' 6 | 7 | // initial state 8 | const state = { 9 | alert: null 10 | } 11 | 12 | // getters 13 | const getters = { 14 | currentAlert: state => state.alert 15 | } 16 | 17 | // actions 18 | const actions = { 19 | createAlert ({commit}, alert) { 20 | commit(types.CREATE_ALERT, alert) 21 | }, 22 | 23 | deleteAlert ({commit}) { 24 | commit(types.DELETE_ALERT) 25 | } 26 | } 27 | 28 | // mutations 29 | const mutations = { 30 | [types.CREATE_ALERT] (state, alert) { 31 | state.alert = alert 32 | }, 33 | 34 | [types.DELETE_ALERT] (state) { 35 | state.alert = null 36 | } 37 | } 38 | 39 | export default { 40 | state, 41 | getters, 42 | actions, 43 | mutations 44 | } 45 | -------------------------------------------------------------------------------- /src/views/Layout.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/views/admin/Layout.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | const prefix = '__falcon__' 2 | 3 | export default { 4 | /** 5 | * Get object from localStorage 6 | * 7 | * @param {string} key 8 | * @return {any} 9 | */ 10 | get (key) { 11 | let ret = window.localStorage.getItem(prefix + key) 12 | if (ret) { 13 | return JSON.parse(ret) 14 | } 15 | return null 16 | }, 17 | 18 | /** 19 | * Set object for localStorage 20 | * 21 | * @param {string} key 22 | * @param {any} value 23 | * @return {boolean} 24 | */ 25 | set (key, value) { 26 | window.localStorage.setItem(prefix + key, JSON.stringify(value)) 27 | return true 28 | }, 29 | 30 | /** 31 | * Remove object from localStorage 32 | * 33 | * @param {string} key 34 | * @return {boolean} 35 | */ 36 | remove (key) { 37 | window.localStorage.removeItem(prefix + key) 38 | return true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /static/css/railscasts.css: -------------------------------------------------------------------------------- 1 | .hljs{display:block;overflow-x:auto;padding:0.5em;background:#232323;color:#e6e1dc}.hljs-comment,.hljs-quote{color:#bc9458;font-style:italic}.hljs-keyword,.hljs-selector-tag{color:#c26230}.hljs-string,.hljs-number,.hljs-regexp,.hljs-variable,.hljs-template-variable{color:#a5c261}.hljs-subst{color:#519f50}.hljs-tag,.hljs-name{color:#e8bf6a}.hljs-type{color:#da4939}.hljs-symbol,.hljs-bullet,.hljs-built_in,.hljs-builtin-name,.hljs-attr,.hljs-link{color:#6d9cbe}.hljs-params{color:#d0d0ff}.hljs-attribute{color:#cda869}.hljs-meta{color:#9b859d}.hljs-title,.hljs-section{color:#ffc66d}.hljs-addition{background-color:#144212;color:#e6e1dc;display:inline-block;width:100%}.hljs-deletion{background-color:#600;color:#e6e1dc;display:inline-block;width:100%}.hljs-selector-class{color:#9b703f}.hljs-selector-id{color:#8b98ab}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}.hljs-link{text-decoration:underline} 2 | -------------------------------------------------------------------------------- /mock/data/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": { 3 | "total": 100, 4 | "list": [ 5 | { 6 | "id": 1, 7 | "name": "邢天宇", 8 | "username": "xingtianyu", 9 | "avatar": "https://adminlte.io/themes/AdminLTE/dist/img/user2-160x160.jpg", 10 | "created_at": "2017-01-09", 11 | "guard_projects": "GMediator_GMDoctor, GMDoctor" 12 | }, 13 | { 14 | "id": 2, 15 | "name": "乔金柱", 16 | "username": "qiaojinzhu", 17 | "avatar": "https://adminlte.io/themes/AdminLTE/dist/img/user2-160x160.jpg", 18 | "created_at": "2017-01-09", 19 | "guard_projects": "GMediator_GMDoctor, GMDoctor" 20 | } 21 | ] 22 | }, 23 | "login": { 24 | "id": 33, 25 | "name": "邢天宇", 26 | "avatar_url": "https://adminlte.io/themes/AdminLTE/dist/img/user2-160x160.jpg", 27 | "created_at": "17 Aug 16:22", 28 | "platform": "ios" 29 | }, 30 | "logout": {} 31 | } 32 | -------------------------------------------------------------------------------- /src/components/global/ContentFooter.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | -------------------------------------------------------------------------------- /src/store/modules/current-platform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | import * as types from '../mutation-types' 5 | import storage from '@/utils/storage' 6 | 7 | // initial state 8 | const state = { 9 | platform: {} 10 | } 11 | 12 | // getters 13 | const getters = { 14 | currentPlatform: state => state.platform 15 | } 16 | 17 | // actions 18 | const actions = { 19 | setPlatform ({commit}, platform) { 20 | commit(types.SET_PLATFORM, platform) 21 | }, 22 | 23 | fetchPlatform ({commit}) { 24 | commit(types.FETCH_PLATFORM) 25 | } 26 | } 27 | 28 | // mutations 29 | const mutations = { 30 | [types.SET_PLATFORM] (state, platform) { 31 | state.platform = platform 32 | storage.set('current_platform', platform) 33 | }, 34 | 35 | [types.FETCH_PLATFORM] (state) { 36 | state.platform = storage.get('current_platform') 37 | } 38 | } 39 | 40 | export default { 41 | state, 42 | getters, 43 | actions, 44 | mutations 45 | } 46 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | module.exports = { 3 | root: true, 4 | 'extends': [ 5 | 'airbnb-base', 6 | 'plugin:vue/essential', 7 | 'eslint:recommended' 8 | ], 9 | rules: { 10 | 'import/extensions': ['off', 'always', { 11 | 'vue': 'never' 12 | }], 13 | 'import/no-unresolved': ['off', 'always', { 14 | 'vue': 'never' 15 | }], 16 | // allow paren-less arrow functions 17 | 'arrow-parens': 0, 18 | // allow async-await 19 | 'generator-star-spacing': 0, 20 | // allow debugger during development 21 | 'no-unused-vars': 0, 22 | 'no-trailing-spaces': 0, 23 | 'indent': 0, 24 | 'semi': 0, 25 | 'object-property-newline': 0, 26 | 'space-before-function-paren': 0, 27 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 28 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 29 | }, 30 | parserOptions: { 31 | parser: 'babel-eslint' 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from '@/App' 5 | import router from '@/router' 6 | import store from '@/store' 7 | import VueResource from 'vue-resource' 8 | import VueHighlightJS from 'vue-highlightjs' 9 | 10 | import 'bootstrap/dist/css/bootstrap.css' 11 | import 'bootstrap/dist/js/bootstrap.min' 12 | import 'admin-lte/dist/css/AdminLTE.min.css' 13 | import 'admin-lte/dist/css/skins/skin-blue.min.css' 14 | import 'admin-lte/dist/js/adminlte.min' 15 | import 'font-awesome/css/font-awesome.min.css' 16 | import '../static/css/application.css' 17 | import '../static/css/railscasts.css' 18 | 19 | Vue.config.productionTip = false 20 | Vue.use(VueResource) 21 | Vue.use(VueHighlightJS) 22 | 23 | // Get User from local storage 24 | store.dispatch('fetchUser') 25 | 26 | /* eslint-disable no-new */ 27 | new Vue({ 28 | el: '#app', 29 | router, 30 | store, 31 | template: '', 32 | components: {App} 33 | }) 34 | 35 | -------------------------------------------------------------------------------- /src/store/modules/current-user.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/5/16. 3 | */ 4 | import * as types from '../mutation-types' 5 | import storage from '@/utils/storage' 6 | 7 | // initial state 8 | const state = { 9 | user: {} 10 | } 11 | 12 | // getters 13 | const getters = { 14 | currentUser: state => state.user 15 | } 16 | 17 | // actions 18 | const actions = { 19 | createUser ({commit}, user) { 20 | commit(types.CREATE_USER, user) 21 | }, 22 | 23 | fetchUser ({commit}) { 24 | commit(types.FETCH_USER) 25 | }, 26 | 27 | deleteUser ({commit}) { 28 | commit(types.DELETE_USER) 29 | } 30 | } 31 | 32 | // mutations 33 | const mutations = { 34 | [types.CREATE_USER] (state, user) { 35 | state.user = user 36 | storage.set('current_user', user) 37 | }, 38 | 39 | [types.FETCH_USER] (state) { 40 | state.user = storage.get('current_user') 41 | }, 42 | 43 | [types.DELETE_USER] (state) { 44 | storage.remove('current_user') 45 | state.user = {} 46 | } 47 | } 48 | 49 | export default { 50 | state, 51 | getters, 52 | actions, 53 | mutations 54 | } 55 | -------------------------------------------------------------------------------- /src/components/projects/ServiceField.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 104 | -------------------------------------------------------------------------------- /src/views/projects/environments/Edit.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 108 | 109 | -------------------------------------------------------------------------------- /src/views/projects/environments/configs/services/List.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 102 | -------------------------------------------------------------------------------- /src/components/global/NavBar.vue: -------------------------------------------------------------------------------- 1 | 2 | 58 | 59 | 101 | -------------------------------------------------------------------------------- /src/components/global/ConfirmModal.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 107 | 108 | 138 | -------------------------------------------------------------------------------- /static/css/application.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 3 | } 4 | 5 | img.avatar, div.avatar { 6 | border-radius: 50%; 7 | width: 35px; 8 | height: 35px; 9 | line-height: 35px; 10 | } 11 | 12 | div.avatar { 13 | background-color: #FFEBEE; 14 | color: #555; 15 | font-size: 16px; 16 | margin-right: 10px; 17 | text-align: center; 18 | vertical-align: top; 19 | } 20 | 21 | /* console output format*/ 22 | pre, textarea.code { 23 | clear: left; 24 | min-height: 42px; 25 | padding: 15px; 26 | color: #f1f1f1; 27 | font-family: Monaco, monospace; 28 | font-size: 12px; 29 | line-height: 20px; 30 | white-space: pre-wrap; 31 | word-wrap: break-word; 32 | background-color: #222; 33 | counter-reset: line-numbering; 34 | margin-top: 0 35 | } 36 | 37 | pre, code, textarea.code { 38 | font-family: Monaco, Helvetica, Arial, sans-serif !important; 39 | } 40 | 41 | textarea.code { 42 | line-height: 1.8em 43 | } 44 | 45 | pre code p { 46 | margin: 0; 47 | } 48 | 49 | textarea.code { 50 | color: #edede3; 51 | padding: 10px; 52 | } 53 | 54 | pre.bash .green { 55 | color: #42c88a; 56 | } 57 | 58 | pre.bash .magenta { 59 | color: #65d8ee; 60 | } 61 | 62 | pre.bash .m35 { 63 | color: #42c88a; 64 | } 65 | 66 | pre.bash .yellow { 67 | color: #d6cc75; 68 | } 69 | 70 | pre.bash .cyan { 71 | color: #ab7fd1; 72 | } 73 | 74 | pre.bash .red { 75 | color: #ed5c5c; 76 | } 77 | 78 | pre.bash .bold { 79 | color: #fff; 80 | font-weight: 900; 81 | } 82 | 83 | pre.bash .underline { 84 | text-decoration: underline; 85 | } 86 | 87 | /* will pagination */ 88 | .digg_pagination { 89 | background: white; 90 | cursor: default; 91 | border-radius: 4px; 92 | /* self-clearing method: */ 93 | } 94 | 95 | .digg_pagination a, .digg_pagination span, .digg_pagination em { 96 | text-decoration: none; 97 | line-height: 1.42857143; 98 | padding: 6px 12px; 99 | background: #fafafa; 100 | color: #666; 101 | border: 1px solid #ddd; 102 | margin-right: 1px; 103 | } 104 | 105 | .digg_pagination .disabled { 106 | color: #777; 107 | cursor: not-allowed; 108 | background-color: #fff; 109 | border: 1px solid #ddd; 110 | } 111 | 112 | .digg_pagination .current { 113 | font-style: normal; 114 | font-weight: bold; 115 | background: #337ab7; 116 | color: white; 117 | border: 1px solid #337ab7; 118 | } 119 | 120 | .digg_pagination a, .digg_pagination span, .digg_pagination em { 121 | text-decoration: none; 122 | line-height: 1.42857143; 123 | padding: 6px 12px; 124 | background: #fafafa; 125 | color: #666; 126 | border: 1px solid #ddd 127 | } 128 | 129 | .digg_pagination a:hover, .digg_pagination a:focus { 130 | color: #000033; 131 | border-color: #000033; 132 | } 133 | 134 | .digg_pagination .page_info { 135 | background: #2e6ab1; 136 | color: white; 137 | padding: 0.4em 0.6em; 138 | width: 22em; 139 | margin-bottom: 0.3em; 140 | text-align: center; 141 | } 142 | 143 | .digg_pagination .page_info b { 144 | color: #000033; 145 | background: #6aa6ed; 146 | padding: 0.1em 0.25em; 147 | } 148 | 149 | .digg_pagination:after { 150 | content: "."; 151 | display: block; 152 | height: 0; 153 | clear: both; 154 | visibility: hidden; 155 | } 156 | 157 | * html .digg_pagination { 158 | height: 1%; 159 | } 160 | 161 | *:first-child + html .digg_pagination { 162 | overflow: hidden; 163 | } 164 | 165 | /* rewrite form style*/ 166 | .form-group label.has-warning { 167 | color: #f39c12; 168 | } 169 | 170 | .fa-success:before { 171 | content: "\f00c"; 172 | } 173 | 174 | .fa-error:before { 175 | content: "\f05e"; 176 | } 177 | -------------------------------------------------------------------------------- /src/views/admin/fastlane_templates/List.vue: -------------------------------------------------------------------------------- 1 | 63 | 64 | 114 | -------------------------------------------------------------------------------- /src/views/projects/environments/configs/Fastlane.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 113 | 114 | 123 | -------------------------------------------------------------------------------- /mock/data/dependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "total": 100, 4 | "list": [ 5 | { 6 | "id": 8821, 7 | "name": "GMBase", 8 | "current_version": "0.1.1", 9 | "latest_version": "", 10 | "need_update": false, 11 | "projectId": 4 12 | }, 13 | { 14 | "id": 8822, 15 | "name": "GMBaseSwift", 16 | "current_version": "3.0.1", 17 | "latest_version": "", 18 | "need_update": false, 19 | "projectId": 4 20 | }, 21 | { 22 | "id": 8823, 23 | "name": "GMCache", 24 | "current_version": "0.1.1", 25 | "latest_version": "0.1.0", 26 | "need_update": false, 27 | "projectId": 4 28 | }, 29 | { 30 | "id": 8824, 31 | "name": "GMFoundation", 32 | "current_version": "0.0.5", 33 | "latest_version": "", 34 | "need_update": false, 35 | "projectId": 4 36 | }, 37 | { 38 | "id": 8825, 39 | "name": "GMHud", 40 | "current_version": "0.1.2", 41 | "latest_version": "", 42 | "need_update": false, 43 | "projectId": 4 44 | }, 45 | { 46 | "id": 8826, 47 | "name": "GMKit", 48 | "current_version": "0.7.24", 49 | "latest_version": "", 50 | "need_update": false, 51 | "projectId": 4 52 | }, 53 | { 54 | "id": 8827, 55 | "name": "GMKit/Category", 56 | "current_version": "0.7.24", 57 | "latest_version": "", 58 | "need_update": false, 59 | "projectId": 4 60 | }, 61 | { 62 | "id": 8828, 63 | "name": "GMKit/FDFullscreenPopGesture", 64 | "current_version": "0.7.24", 65 | "latest_version": "", 66 | "need_update": false, 67 | "projectId": 4 68 | }, 69 | { 70 | "id": 8829, 71 | "name": "GMKit/Kit", 72 | "current_version": "0.7.24", 73 | "latest_version": "", 74 | "need_update": false, 75 | "projectId": 4 76 | }, 77 | { 78 | "id": 8830, 79 | "name": "GMKit/Protocol", 80 | "current_version": "0.7.24", 81 | "latest_version": "", 82 | "need_update": false, 83 | "projectId": 4 84 | }, 85 | { 86 | "id": 8831, 87 | "name": "GMNetService", 88 | "current_version": "0.1.7", 89 | "latest_version": "", 90 | "need_update": false, 91 | "projectId": 4 92 | }, 93 | { 94 | "id": 8832, 95 | "name": "GMNetworking", 96 | "current_version": "3.0.3", 97 | "latest_version": "0.2.2", 98 | "need_update": false, 99 | "projectId": 4 100 | }, 101 | { 102 | "id": 8833, 103 | "name": "GMOCConstant", 104 | "current_version": "0.0.3", 105 | "latest_version": "", 106 | "need_update": false, 107 | "projectId": 4 108 | }, 109 | { 110 | "id": 8834, 111 | "name": "GMPhobos", 112 | "current_version": "0.3.7", 113 | "latest_version": "0.2.10", 114 | "need_update": false, 115 | "projectId": 4 116 | }, 117 | { 118 | "id": 8835, 119 | "name": "GMRefresh", 120 | "current_version": "0.1.5", 121 | "latest_version": "", 122 | "need_update": false, 123 | "projectId": 4 124 | }, 125 | { 126 | "id": 8836, 127 | "name": "GMSwiftConstant", 128 | "current_version": "0.3.0", 129 | "latest_version": "", 130 | "need_update": false, 131 | "projectId": 4 132 | }, 133 | { 134 | "id": 8837, 135 | "name": "GMUtil", 136 | "current_version": "0.3.0", 137 | "latest_version": "", 138 | "need_update": false, 139 | "projectId": 4 140 | }, 141 | { 142 | "id": 8810, 143 | "name": "AFNetworking", 144 | "current_version": "3.1.0", 145 | "latest_version": "", 146 | "need_update": false, 147 | "projectId": 4 148 | } 149 | ] 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/views/projects/builds/Detail.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 123 | 124 | 138 | -------------------------------------------------------------------------------- /src/utils/networking.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/4/14. 3 | */ 4 | import axios from 'axios' 5 | 6 | export default { 7 | 8 | httpMethod: { 9 | GET: 'get', 10 | POST: 'post', 11 | PUT: 'put', 12 | DELETE: 'delete' 13 | }, 14 | 15 | instance: axios.create({ 16 | baseURL: '/api/v1/', 17 | timeout: 10000 18 | }), 19 | 20 | /** 21 | * 22 | * @param url 23 | * @param pathParams 24 | * @param options 25 | * @returns {*|Promise} 26 | */ 27 | doGet(url, pathParams = null, options = null) { 28 | return this.doRequest(url, this.httpMethod.GET, pathParams, null, options) 29 | }, 30 | 31 | /** 32 | * Http Post 33 | * @param url 34 | * @param pathParams 35 | * @param body 36 | * @param options 37 | * @returns {*|Promise} 38 | */ 39 | doPost(url, pathParams = null, body = null, options = null) { 40 | return this.doRequest(url, this.httpMethod.POST, pathParams, body, options) 41 | }, 42 | 43 | /** 44 | * Http Put 45 | * @param url 46 | * @param pathParams 47 | * @param body 48 | * @param options 49 | * @returns {*|Promise} 50 | */ 51 | doPut(url, pathParams = null, body = null, options = null) { 52 | return this.doRequest(url, this.httpMethod.PUT, pathParams, body, options) 53 | }, 54 | 55 | /** 56 | * Http Delete 57 | * @param url 58 | * @param pathParams 59 | * @param options 60 | * @returns {*|Promise} 61 | */ 62 | doDelete(url, pathParams = null, options = null) { 63 | return this.doRequest(url, this.httpMethod.DELETE, pathParams, null, options) 64 | }, 65 | 66 | /** 67 | * Http Request 68 | * @param url 69 | * @param method 70 | * @param pathParams 71 | * @param body 72 | * @param options 73 | * @returns {Promise} 74 | */ 75 | doRequest(url, method, pathParams, body, options) { 76 | let wrapURL = this._wrapUrl(url, pathParams) 77 | let request = null 78 | switch (method) { 79 | case this.httpMethod.GET: { 80 | if (options !== null) { 81 | request = this.instance.get(wrapURL, {params: options}) 82 | } else { 83 | request = this.instance.get(wrapURL) 84 | } 85 | break 86 | } 87 | case this.httpMethod.POST: { 88 | request = this.instance.post(wrapURL, body, options) 89 | break 90 | } 91 | case this.httpMethod.PUT: { 92 | request = this.instance.put(wrapURL, body, options) 93 | break 94 | } 95 | case this.httpMethod.DELETE: { 96 | request = this.instance.delete(wrapURL, options) 97 | break 98 | } 99 | } 100 | return this._requestPromise(request) 101 | }, 102 | 103 | /** 104 | * Request Promise 105 | * @param request {Promise} Axios Http Promise 106 | * @returns {Promise} 107 | * @private 108 | */ 109 | _requestPromise(request) { 110 | console.log('_requestPromise') 111 | return new Promise(function (resolve, reject) { 112 | request.then(response => { 113 | if (response.data.status === 0) { 114 | resolve(response.data) 115 | } else { 116 | reject(response.data.message) 117 | } 118 | }, error => { 119 | reject(error) 120 | }) 121 | }) 122 | }, 123 | 124 | /** 125 | * 从URL匹配出参数pathParams 126 | * @param {string} url 127 | * @param {object} params 128 | * @returns {string} 129 | * @private 130 | */ 131 | _wrapUrl(url, params) { 132 | if (params !== null) { 133 | let matches = this._getMatches(url) 134 | for (let match of matches) { 135 | let value = params[match.replace(':', '')] 136 | if (value !== null) { 137 | url = url.replace(match, value) 138 | } 139 | } 140 | } 141 | return url 142 | }, 143 | 144 | /** 145 | * 正则Group匹配 146 | * @param {string} string 147 | * @returns {Array} 148 | * @private 149 | */ 150 | _getMatches(string) { 151 | let matches = [] 152 | let regex = /(:[a-z_A-Z]+)/ 153 | let match = regex.exec(string) 154 | while (match !== null) { 155 | matches.push(match[1]) 156 | string = string.replace(match[1], '') 157 | match = regex.exec(string) 158 | } 159 | return matches 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /src/router/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Thierry on 2017/6/14. 3 | */ 4 | import Layout from '@/views/Layout' 5 | import Dashboard from '@/views/dashboard/List' 6 | import Jobs from '@/views/jobs/Index' 7 | import Activity from '@/views/activity/List' 8 | import Members from '@/views/members/List' 9 | import Projects from '@/views/projects/List' 10 | 11 | import ProjectLayout from '@/views/projects/Layout' 12 | import ProjectNew from '@/views/projects/New' 13 | import ProjectEdit from '@/views/projects/Edit' 14 | import ProjectHome from '@/views/projects/Home' 15 | 16 | import Builds from '@/views/projects/builds/List' 17 | import BuildInfo from '@/views/projects/builds/Info' 18 | 19 | import Environments from '@/views/projects/environments/List' 20 | import EnvironmentCLone from '@/views/projects/environments/Clone' 21 | import EnvironmentNew from '@/views/projects/environments/New' 22 | import EnvironmentEdit from '@/views/projects/environments/Edit' 23 | import Configs from '@/views/projects/environments/configs/Index' 24 | import ConfigGitClone from '@/views/projects/environments/configs/GitClone' 25 | import ConfigFastlane from '@/views/projects/environments/configs/Fastlane' 26 | import ServiceList from '@/views/projects/environments/configs/services/List' 27 | import ServiceEdit from '@/views/projects/environments/configs/services/Edit' 28 | 29 | import BuildDetail from '@/views/projects/builds/Detail' 30 | 31 | import Login from '@/views/account/Login' 32 | 33 | import AdminLayout from '@/views/admin/Layout' 34 | import AdminFastlaneTemplates from '@/views/admin/fastlane_templates/List' 35 | import AdminFastlaneTemplatesNew from '@/views/admin/fastlane_templates/New' 36 | import AdminFastlaneTemplatesEdit from '@/views/admin/fastlane_templates/Edit' 37 | 38 | const loginRoutes = { 39 | path: '/login', 40 | name: 'login', 41 | component: Login 42 | } 43 | 44 | const dashboardRoutes = { 45 | path: '/', name: 'root', component: Layout, redirect: '/dashboard', 46 | children: [ 47 | {path: 'dashboard', name: 'dashboard', component: Dashboard, meta: {contentHeader: {title: 'Dashboard', subTitle: 'Control panel'}}}, 48 | {path: 'jobs', name: 'jobs', component: Jobs, meta: {contentHeader: {title: 'Background Jobs', subTitle: 'Sidekiq'}}}, 49 | {path: 'activity', name: 'activity', component: Activity, meta: {contentHeader: {title: 'Activity', subTitle: 'Executing jobs'}}}, 50 | {path: 'members', name: 'members', component: Members, meta: {contentHeader: {title: 'Member', subTitle: 'list'}}}, 51 | {path: 'project_list', name: 'project_list', component: Projects, meta: {contentHeader: {title: 'Project', subTitle: 'list'}}}, 52 | {path: 'project_new', name: 'project_new', component: ProjectNew, meta: {contentHeader: {title: 'Project', subTitle: 'new'}}} 53 | ] 54 | } 55 | 56 | const adminRoutes = { 57 | path: '/admin', name: 'admin', component: AdminLayout, redirect: '/admin/fastlane_templates', 58 | children: [ 59 | {path: 'fastlane_templates', name: 'fastlane_templates', component: AdminFastlaneTemplates, meta: {contentHeader: {title: 'Admin', subTitle: 'Fastlane Templates'}}}, 60 | {path: 'fastlane_templates/new', name: 'fastlane_templates_new', component: AdminFastlaneTemplatesNew, meta: {contentHeader: {title: 'Admin', subTitle: 'Fastlane Templates'}}}, 61 | {path: 'fastlane_templates/:id/edit', name: 'fastlane_templates_edit', component: AdminFastlaneTemplatesEdit, meta: {contentHeader: {title: 'Admin', subTitle: 'Fastlane Templates'}}} 62 | ] 63 | } 64 | 65 | const projectRoutes = { 66 | path: '/projects/:project_id', component: ProjectLayout, 67 | children: [ 68 | {name: 'home', path: 'home', component: ProjectHome}, 69 | {name: 'setting', path: 'setting', component: ProjectEdit}, 70 | {name: 'environments', path: 'environments', component: Environments}, 71 | {name: 'builds', path: 'builds', component: Builds}, 72 | {name: 'build_detail', path: 'builds/:build_id', component: BuildDetail}, 73 | {name: 'build_info', path: 'environments/:env_id/build_info', component: BuildInfo}, 74 | {name: 'environments_new', path: 'environments/new', component: EnvironmentNew}, 75 | {name: 'environments_edit', path: 'environments/:env_id/edit', component: EnvironmentEdit}, 76 | {name: 'environments_clone', path: 'environments/:env_id/clone', component: EnvironmentCLone}, 77 | { 78 | name: 'environments_config', 79 | path: 'environments/:env_id/config', 80 | component: Configs, 81 | children: [ 82 | {name: 'git_clone', path: 'git_clone', component: ConfigGitClone}, 83 | {name: 'fastlane', path: 'fastlane', component: ConfigFastlane}, 84 | {name: 'services', path: 'services', component: ServiceList}, 85 | {name: 'services_edit', path: 'services/:service_id/edit', component: ServiceEdit} 86 | ] 87 | } 88 | ] 89 | } 90 | 91 | export default [ 92 | loginRoutes, 93 | dashboardRoutes, 94 | projectRoutes, 95 | adminRoutes 96 | ] 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Falcon 2 | > 基于Vue2和AdminLTE的移动客户端自动化平台(前端) 3 | 4 | ## 关于Falcon 5 | 从16年初开始到现在,我们的前端团队将 Vue 作为主框架已经有一年多的时间了,而作为前端工程师出身的我,知识体系还停留在远古的 Prototype 和 jQuery 上,说起来也是一件十分惭愧的事情。 6 | 7 | 于是我下定决心花一段时间由浅入深地学习一下这个目前非常流行的框架,同时恶补一下ES6的知识。 8 | 9 | 本人一直赞同并践行一个观点: 10 | 11 | > 学习一项新技术的最佳途径就是动手写一个完整的项目。 12 | 13 | 因为在这个项目中你将遇到几乎所有的问题,那么解决这些问题的过程就是你不断提高,融会贯通的过程。 14 | 15 | 学习 Vue 也不例外,恰好最近正在计划为内部的自动化平台 Jaguar 提供 iOS 和 Android 的版本,以方便大家在移动端进行使用,那么用 Native 去写显然有些浪费资源,而做一个Hybrid的跨平台App应该是最经济的方式了,所以最终的计划是将 Jaguar 进行前后端分离,然后在前端使用Vue框架,并使其同时能够支持浏览器,iOS 和 Android 平台。 16 | 17 | 为了和 Jaguar(美洲豹)相对应,最终决定给项目取名叫 Falcon,即:猎鹰,和 Jaguar 一样都是速度飞快的动物。 18 | 19 | 经过一段时间的规划和整理,可以预计出最终的版本大约包含40个左右的页面,30 个左右的组件,这么算来也可以称之为一个不大不小的项目了。 20 | 21 | 最终的界面大概如下: 22 | ![Dashboard.png](http://upload-images.jianshu.io/upload_images/1639341-feeaad585cad9bba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 23 | 24 | ![Project.png](http://upload-images.jianshu.io/upload_images/1639341-00dfe3a7d6767857.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 25 | 26 | [Vue实战](http://www.jianshu.com/nb/13169691)将事无巨细的记录我在完成整个项目过程中对Vue的从浅到深理解,遇到的点点滴滴问题,希望可以帮助正在入门或打算尝试Vue框架的同学们。 27 | 28 | ## 如何运行 29 | 由于后端 Jaguar 服务配置起来比较繁琐,为了简化大家的使用,我已经将部分 API 放置在了 Easy-Mock 服务上(不断更新中),运行方法如下: 30 | 31 | ### 配置 proxy 32 | 首先保证,vue.config.js 中 proxy 的内容如下(注释部分): 33 | 34 | ``` 35 | proxy: { 36 | '/api/v1': { 37 | changeOrigin: true, 38 | secure: false, 39 | target: 'https://www.easy-mock.com/mock/5b18ccb77dddfa3903693dba' 40 | }, 41 | '/sidekiq': { 42 | changeOrigin: true, 43 | secure: false, 44 | target: 'http://localhost:3000' 45 | } 46 | } 47 | ``` 48 | 49 | ### 运行项目: 50 | 51 | * 安装npm依赖的库 52 | 53 | ``` 54 | npm install 55 | ``` 56 | 57 | * 运行服务 58 | 59 | ``` 60 | npm run serve 61 | ``` 62 | 63 | * 在浏览器中访问:[http://localhost:8080/#/](http://localhost:8080/#/) 64 | 65 | ### 登录 66 | 67 | 登录页面的用户名(邮箱)和密码可以随意填写 68 | 69 | ## 项目结构 70 | 71 | ``` 72 | . 73 | ├── App.vue 74 | ├── assets 75 | │   └── logo.png 76 | ├── components 77 | │   ├── global 78 | │   │   ├── Alert.vue 79 | │   │   ├── ConfirmModal.vue 80 | │   │   ├── ContentFooter.vue 81 | │   │   ├── ContentHeader.vue 82 | │   │   ├── LoadingOverlay.vue 83 | │   │   ├── NavBar.vue 84 | │   │   ├── Pagination.vue 85 | │   │   ├── SideBar.vue 86 | │   │   ├── SideBarItem.vue 87 | │   │   ├── TableBox.vue 88 | │   │   └── UserPanel.vue 89 | │   └── projects 90 | │   ├── Dependency.vue 91 | │   ├── EnvForm.vue 92 | │   ├── Form.vue 93 | │   ├── Info.vue 94 | │   └── SideBar.vue 95 | ├── constants 96 | │   ├── api.js 97 | │   └── enum.js 98 | ├── main.js 99 | ├── router 100 | │   ├── index.js 101 | │   └── routes.js 102 | ├── store 103 | │   ├── actions.js 104 | │   ├── index.js 105 | │   ├── modules 106 | │   │   ├── alert.js 107 | │   │   ├── current-platform.js 108 | │   │   ├── current-project.js 109 | │   │   └── current-user.js 110 | │   └── mutation-types.js 111 | ├── utils 112 | │   ├── networking.js 113 | │   └── storage.js 114 | └── views 115 | ├── Dashboard.vue 116 | ├── Layout.vue 117 | ├── account 118 | │   └── Login.vue 119 | ├── guardian 120 | │   └── List.vue 121 | ├── job 122 | │   └── Index.vue 123 | └── projects 124 | ├── Edit.vue 125 | ├── Home.vue 126 | ├── Layout.vue 127 | ├── New.vue 128 | ├── builds 129 | │   ├── CheckDependency.vue 130 | │   ├── Detail.vue 131 | │   ├── List.vue 132 | │   ├── PreBuildBeta.vue 133 | │   └── PreBuildLib.vue 134 | ├── environments 135 | │   ├── Clone.vue 136 | │   ├── List.vue 137 | │   ├── New.vue 138 | │   └── configs 139 | │   ├── Fastlane.vue 140 | │   ├── GitClone.vue 141 | │   ├── Info.vue 142 | │   └── List.vue 143 | └── services 144 | ├── Edit.vue 145 | └── List.vue 146 | 147 | 18 directories, 55 files 148 | ``` 149 | 150 | ## 功能 151 | 目前项目还在不断的开发中,已经包含的功能如下: 152 | 153 | - [x] 登录 154 | - [x] 登出 155 | - [x] 项目列表展示 156 | - [x] 新增项目 157 | - [x] 项目详情 158 | - [x] 项目依赖第三方库列表 159 | - [x] 项目下新增构建环境 160 | - [x] 配置环境 161 | - [x] 项目构建列表 162 | - [x] 项目构建前配置 163 | - [x] 第三方服务(Bugly,Fir)列表 164 | - [x] 项目设置 165 | - [x] 用户列表 166 | - ... 167 | 168 | ## 链接 169 | 170 | ### 文章 171 | 172 | * [Vue实战(一)Falcon计划](http://www.jianshu.com/p/52eac947cd96) 173 | * [Vue实战(二)框架和环境](http://www.jianshu.com/p/a6e37818fd9a) 174 | * [Vue实战(三)Mock服务JSON Server](http://www.jianshu.com/p/7094c477207d) 175 | * [Vue实战(四)组件和路由](http://www.jianshu.com/p/153f847fa2aa) 176 | * [Vue实战(五)网络层拦截器与全局异常信息展示](http://www.jianshu.com/p/0f3f27f33a6f) 177 | 178 | ### 三方工具 179 | 180 | * [AdminLTE](https://github.com/almasaeed2010/AdminLTE) 181 | * [JSON Server](https://github.com/typicode/json-server) 182 | * [Vue Highlightjs](https://github.com/metachris/vue-highlightjs) 183 | 184 | ## License 185 | MIT licensed. 186 | -------------------------------------------------------------------------------- /src/views/projects/environments/configs/GitClone.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 171 | -------------------------------------------------------------------------------- /src/views/projects/builds/List.vue: -------------------------------------------------------------------------------- 1 | 85 | 86 | 164 | -------------------------------------------------------------------------------- /mock/data/builds.json: -------------------------------------------------------------------------------- 1 | { 2 | "builds": { 3 | "total": 100, 4 | "list": [ 5 | { 6 | "id": 2418, 7 | "status": "success", 8 | "version": "7.2.0", 9 | "number": null, 10 | "updated_at": "22 May 06:44", 11 | "release_notes": [], 12 | "operator": { 13 | "id": 51, 14 | "name": "张伟男" 15 | }, 16 | "environment": { 17 | "id": 1, 18 | "name": "Prod" 19 | } 20 | }, 21 | { 22 | "id": 2411, 23 | "status": "success", 24 | "version": "7.2.0", 25 | "number": null, 26 | "updated_at": "22 May 06:44", 27 | "release_notes": [], 28 | "operator": { 29 | "id": 36, 30 | "name": "汪洋" 31 | }, 32 | "environment": { 33 | "id": 1, 34 | "name": "Prod" 35 | } 36 | }, 37 | { 38 | "id": 2410, 39 | "status": "success", 40 | "version": "7.2.0", 41 | "number": null, 42 | "updated_at": "22 May 06:44", 43 | "release_notes": [], 44 | "operator": { 45 | "id": 36, 46 | "name": "汪洋" 47 | }, 48 | "environment": { 49 | "id": 1, 50 | "name": "Prod" 51 | } 52 | }, 53 | { 54 | "id": 2406, 55 | "status": "success", 56 | "version": "7.2.0", 57 | "number": null, 58 | "updated_at": "22 May 06:44", 59 | "release_notes": [], 60 | "operator": { 61 | "id": 36, 62 | "name": "汪洋" 63 | }, 64 | "environment": { 65 | "id": 1, 66 | "name": "Prod" 67 | } 68 | }, 69 | { 70 | "id": 2402, 71 | "status": "success", 72 | "version": "7.1.5", 73 | "number": null, 74 | "updated_at": "22 May 06:44", 75 | "release_notes": [], 76 | "operator": { 77 | "id": 36, 78 | "name": "汪洋" 79 | }, 80 | "environment": { 81 | "id": 1, 82 | "name": "Prod" 83 | } 84 | }, 85 | { 86 | "id": 2401, 87 | "status": "success", 88 | "version": "7.1.5", 89 | "number": null, 90 | "updated_at": "22 May 06:44", 91 | "release_notes": [], 92 | "operator": { 93 | "id": 36, 94 | "name": "汪洋" 95 | }, 96 | "environment": { 97 | "id": 1, 98 | "name": "Prod" 99 | } 100 | }, 101 | { 102 | "id": 2399, 103 | "status": "failed", 104 | "version": "7.1.5", 105 | "number": null, 106 | "updated_at": "22 May 06:44", 107 | "release_notes": [], 108 | "operator": { 109 | "id": 36, 110 | "name": "汪洋" 111 | }, 112 | "environment": { 113 | "id": 1, 114 | "name": "Prod" 115 | } 116 | }, 117 | { 118 | "id": 2398, 119 | "status": "success", 120 | "version": "7.1.5", 121 | "number": null, 122 | "updated_at": "22 May 06:44", 123 | "release_notes": [], 124 | "operator": { 125 | "id": 36, 126 | "name": "汪洋" 127 | }, 128 | "environment": { 129 | "id": 1, 130 | "name": "Prod" 131 | } 132 | }, 133 | { 134 | "id": 2397, 135 | "status": "success", 136 | "version": "7.1.5", 137 | "number": null, 138 | "updated_at": "22 May 06:44", 139 | "release_notes": [], 140 | "operator": { 141 | "id": 36, 142 | "name": "汪洋" 143 | }, 144 | "environment": { 145 | "id": 1, 146 | "name": "Prod" 147 | } 148 | }, 149 | { 150 | "id": 2393, 151 | "status": "success", 152 | "version": "7.1.5", 153 | "number": "201705161539", 154 | "updated_at": "22 May 06:44", 155 | "release_notes": [], 156 | "operator": { 157 | "id": 36, 158 | "name": "汪洋" 159 | }, 160 | "environment": { 161 | "id": 2, 162 | "name": "Test" 163 | } 164 | }, 165 | { 166 | "id": 2383, 167 | "status": "success", 168 | "version": "7.1.5", 169 | "number": "201705151818", 170 | "updated_at": "22 May 06:44", 171 | "release_notes": [], 172 | "operator": { 173 | "id": 36, 174 | "name": "汪洋" 175 | }, 176 | "environment": { 177 | "id": 2, 178 | "name": "Test" 179 | } 180 | }, 181 | { 182 | "id": 2380, 183 | "status": "success", 184 | "version": "7.1.5", 185 | "number": "201705151537", 186 | "updated_at": "22 May 06:44", 187 | "release_notes": [], 188 | "operator": { 189 | "id": 34, 190 | "name": "翟国钧" 191 | }, 192 | "environment": { 193 | "id": 2, 194 | "name": "Test" 195 | } 196 | }, 197 | { 198 | "id": 2378, 199 | "status": "success", 200 | "version": "7.1.5", 201 | "number": "201705151425", 202 | "updated_at": "22 May 06:44", 203 | "release_notes": [], 204 | "operator": { 205 | "id": 34, 206 | "name": "翟国钧" 207 | }, 208 | "environment": { 209 | "id": 2, 210 | "name": "Test" 211 | } 212 | }, 213 | { 214 | "id": 2375, 215 | "status": "success", 216 | "version": "7.1.5", 217 | "number": "201705151112", 218 | "updated_at": "22 May 06:44", 219 | "release_notes": [], 220 | "operator": { 221 | "id": 34, 222 | "name": "翟国钧" 223 | }, 224 | "environment": { 225 | "id": 2, 226 | "name": "Test" 227 | } 228 | }, 229 | { 230 | "id": 2370, 231 | "status": "success", 232 | "version": "7.1.5", 233 | "number": null, 234 | "updated_at": "22 May 06:44", 235 | "release_notes": [], 236 | "operator": { 237 | "id": 37, 238 | "name": "李聪" 239 | }, 240 | "environment": { 241 | "id": 1, 242 | "name": "Prod" 243 | } 244 | } 245 | ] 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /mock/data/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "total": 100, 4 | "list": [ 5 | { 6 | "id": 4, 7 | "title": "Gengmei", 8 | "desc": "更美APP 用户版", 9 | "identifier": "com.wanmeizhensuo.ZhengXing", 10 | "type": "app", 11 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 12 | "version": "7.0.0", 13 | "platform": "ios", 14 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/gengmeiios.git", 15 | "updated_at": "30 Mar 16:29", 16 | "guardian": { 17 | "id": 34, 18 | "name": "翟国钧" 19 | } 20 | }, 21 | { 22 | "id": 78, 23 | "title": "GMDoctor", 24 | "desc": "更美医生新版", 25 | "identifier": "com.wanmeizhensuo.Doctor", 26 | "type": "app", 27 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 28 | "version": "2.9.5", 29 | "platform": "ios", 30 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMDoctor.git", 31 | "updated_at": "05 Jun 04:09", 32 | "guardian": { 33 | "id": 42, 34 | "name": "胡乐平" 35 | } 36 | }, 37 | { 38 | "id": 5, 39 | "title": "GMPhobos", 40 | "desc": "iOS埋点统计SDK", 41 | "identifier": "", 42 | "type": "lib", 43 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 44 | "version": "0.2.10", 45 | "platform": "ios", 46 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMPhobos.git", 47 | "updated_at": "22 Aug 17:47", 48 | "guardian": { 49 | "id": 34, 50 | "name": "翟国钧" 51 | } 52 | }, 53 | { 54 | "id": 8, 55 | "title": "GMKit", 56 | "desc": "更美UI基础控件库", 57 | "identifier": "", 58 | "type": "lib", 59 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 60 | "version": "", 61 | "platform": "ios", 62 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMKit.git", 63 | "updated_at": "17 Aug 18:36", 64 | "guardian": { 65 | "id": 36, 66 | "name": "汪洋" 67 | } 68 | }, 69 | { 70 | "id": 10, 71 | "title": "GMNetService", 72 | "desc": "更美网络库 OC版", 73 | "identifier": "", 74 | "type": "lib", 75 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 76 | "version": "", 77 | "platform": "ios", 78 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMNetService.git", 79 | "updated_at": "22 Aug 17:47", 80 | "guardian": { 81 | "id": 37, 82 | "name": "李聪" 83 | } 84 | }, 85 | { 86 | "id": 13, 87 | "title": "GMRefresh", 88 | "desc": "基于MJRefresh做的下拉刷新,自动加载更多", 89 | "identifier": "", 90 | "type": "lib", 91 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 92 | "version": "", 93 | "platform": "ios", 94 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMRefresh.git", 95 | "updated_at": "17 Aug 18:36", 96 | "guardian": { 97 | "id": 36, 98 | "name": "汪洋" 99 | } 100 | }, 101 | { 102 | "id": 18, 103 | "title": "GMUtil", 104 | "desc": "常用的工具库", 105 | "identifier": "", 106 | "type": "lib", 107 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 108 | "version": "", 109 | "platform": "ios", 110 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMUtil.git", 111 | "updated_at": "17 Aug 18:36", 112 | "guardian": { 113 | "id": 36, 114 | "name": "汪洋" 115 | } 116 | }, 117 | { 118 | "id": 22, 119 | "title": "GMediator", 120 | "desc": "iOS Business Module Communication Middleware", 121 | "identifier": "", 122 | "type": "lib", 123 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 124 | "version": "", 125 | "platform": "ios", 126 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMediator.git", 127 | "updated_at": "22 Aug 17:47", 128 | "guardian": { 129 | "id": 36, 130 | "name": "汪洋" 131 | } 132 | }, 133 | { 134 | "id": 23, 135 | "title": "GMBase", 136 | "desc": "各种基类库", 137 | "identifier": "", 138 | "type": "lib", 139 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 140 | "version": "", 141 | "platform": "ios", 142 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMBase.git", 143 | "updated_at": "17 Aug 18:37", 144 | "guardian": { 145 | "id": 36, 146 | "name": "汪洋" 147 | } 148 | }, 149 | { 150 | "id": 24, 151 | "title": "GMConversation", 152 | "desc": "会话、私信、客服业务组件", 153 | "identifier": "", 154 | "type": "lib", 155 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 156 | "version": "0.1.6", 157 | "platform": "ios", 158 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMConversation.git", 159 | "updated_at": "17 Aug 18:12", 160 | "guardian": { 161 | "id": 34, 162 | "name": "翟国钧" 163 | } 164 | }, 165 | { 166 | "id": 31, 167 | "title": "GMCache", 168 | "desc": "缓存库 OC版", 169 | "identifier": "", 170 | "type": "Lib", 171 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 172 | "version": "0.1.0", 173 | "platform": "ios", 174 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMCache.git", 175 | "updated_at": "17 Aug 18:32", 176 | "guardian": { 177 | "id": 37, 178 | "name": "李聪" 179 | } 180 | }, 181 | { 182 | "id": 48, 183 | "title": "GMSwiftConstant", 184 | "desc": "单独对swift的常量进行管理", 185 | "identifier": "", 186 | "type": "Lib", 187 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 188 | "version": "", 189 | "platform": "ios", 190 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMSwiftConstant.git", 191 | "updated_at": "17 Aug 18:32", 192 | "guardian": { 193 | "id": 37, 194 | "name": "李聪" 195 | } 196 | }, 197 | { 198 | "id": 50, 199 | "title": "GMLibrary", 200 | "desc": "基础组件第一版(已废弃,专门用于测试)", 201 | "identifier": "", 202 | "type": "Lib", 203 | "icon": "http://www.gmei.com/static/images/download/gm-logo-5aeee8294d.png", 204 | "version": "0.4.4", 205 | "platform": "ios", 206 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMLibrary.git", 207 | "updated_at": "22 Aug 17:48", 208 | "guardian": { 209 | "id": 33, 210 | "name": "邢天宇" 211 | } 212 | }, 213 | { 214 | "id": 54, 215 | "title": "GMediator_Conversation", 216 | "desc": "私信业务组件TargetAction接口", 217 | "identifier": "", 218 | "type": "Lib", 219 | "icon": null, 220 | "version": "", 221 | "platform": "ios", 222 | "git_repo_url": "git@git.gengmei.cc:gengmeiios/GMediator_Conversation.git", 223 | "updated_at": "22 Aug 17:48", 224 | "guardian": { 225 | "id": 34, 226 | "name": "翟国钧" 227 | } 228 | } 229 | ] 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/components/global/Pagination.vue: -------------------------------------------------------------------------------- 1 | 98 | 99 | 292 | 293 | 311 | --------------------------------------------------------------------------------