├── src ├── index.css ├── images │ ├── icon.png │ ├── Android.png │ ├── iphone.png │ └── icon_project.png ├── css │ ├── AppLogo.css │ ├── ActionBar.css │ └── Login.css ├── App.test.js ├── utils │ ├── DateUtil.js │ ├── CryptionUtil.js │ ├── URL.js │ ├── NavRouter.js │ └── HttpUtil.js ├── pages │ ├── DataStatisticsPage.js │ ├── ErrorPage.js │ ├── AppLoginStatisticsPage.js │ ├── AppLoginTablePage.js │ ├── AppDownloadPage.js │ ├── AppPage.js │ ├── LoginPage.js │ ├── ProjectListPage.js │ └── ReleaseVersionPage.js ├── index.js ├── components │ ├── ActionBar.js │ ├── CustomToolTip.js │ ├── AddProjectForm.js │ ├── CreateProjectButton.js │ ├── TabContent.js │ ├── MenuNavigation.js │ └── EditProjectForm.js ├── App.js ├── logo.svg └── registerServiceWorker.js ├── public ├── favicon.ico ├── manifest.json └── index.html ├── screenshots ├── pic1.png ├── pic2.jpg ├── pic3.jpg ├── pic4.jpg ├── pic5.png └── pic6.jpg ├── .idea ├── misc.xml ├── modules.xml ├── app-manager-system.iml ├── inspectionProfiles │ └── Project_Default.xml └── workspace.xml ├── config-overrides.js ├── .gitignore ├── package.json ├── README.md └── LICENSE /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/src/images/icon.png -------------------------------------------------------------------------------- /screenshots/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic1.png -------------------------------------------------------------------------------- /screenshots/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic2.jpg -------------------------------------------------------------------------------- /screenshots/pic3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic3.jpg -------------------------------------------------------------------------------- /screenshots/pic4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic4.jpg -------------------------------------------------------------------------------- /screenshots/pic5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic5.png -------------------------------------------------------------------------------- /screenshots/pic6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/screenshots/pic6.jpg -------------------------------------------------------------------------------- /src/images/Android.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/src/images/Android.png -------------------------------------------------------------------------------- /src/images/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/src/images/iphone.png -------------------------------------------------------------------------------- /src/images/icon_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/panyz/AppManagerSystem_React/HEAD/src/images/icon_project.png -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /src/css/AppLogo.css: -------------------------------------------------------------------------------- 1 | .logo { 2 | height: 64px; 3 | position: relative; 4 | line-height: 64px; 5 | padding-left: 16px; 6 | -webkit-transition: all .3s; 7 | transition: all .3s; 8 | background: #002140; 9 | overflow: hidden; 10 | } 11 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by panyz on 2018/5/29. 3 | */ 4 | const {injectBabelPlugin} = require('react-app-rewired'); 5 | 6 | module.exports = function override(config, env) { 7 | config = injectBabelPlugin(['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], config); 8 | return config; 9 | }; -------------------------------------------------------------------------------- /src/utils/DateUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by panyz on 2018/7/2. 3 | */ 4 | function getMonthDate() { 5 | let date=new Date; 6 | let year=date.getFullYear(); 7 | let month=date.getMonth()+1; 8 | month =(month<10 ? "0"+month:month); 9 | return (year.toString()+"-"+month.toString()); 10 | } 11 | 12 | export const getCurrentYearMonth = () => getMonthDate(); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /.idea/app-manager-system.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/css/ActionBar.css: -------------------------------------------------------------------------------- 1 | .trigger { 2 | font-size: 18px; 3 | padding: 0 24px; 4 | cursor: pointer; 5 | transition: color .3s; 6 | line-height: 64px; 7 | } 8 | 9 | .trigger:hover { 10 | color: #1890ff; 11 | } 12 | 13 | .user{ 14 | float: right; 15 | margin-right: 10px; 16 | padding: 0 24px; 17 | } 18 | 19 | .user:hover{ 20 | background: #E6F7FF; 21 | } 22 | 23 | .logout,.userName { 24 | margin-left: 10px; 25 | } 26 | 27 | .avatar { 28 | color: #f56a00; 29 | background-color: #fde3cf; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /src/pages/DataStatisticsPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 数据统计界面 3 | * Created by panyz on 2018/6/29. 4 | */ 5 | import React, {Component} from 'react'; 6 | import AppDownloadPage from '../pages/AppDownloadPage'; 7 | import AppLoginStatisticsPage from '../pages/AppLoginStatisticsPage'; 8 | 9 | 10 | class DataStatisticsPage extends Component { 11 | render() { 12 | return ( 13 |
14 | 15 | 16 |
17 | ) 18 | } 19 | } 20 | 21 | export default DataStatisticsPage; -------------------------------------------------------------------------------- /src/pages/ErrorPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by panyz on 2018/6/11. 3 | */ 4 | import React, {Component} from 'react'; 5 | 6 | class ErrorPage extends Component { 7 | render() { 8 | return ( 9 |
10 | 12 |
404
13 |
抱歉,你访问的页面不存在
14 |
15 | ) 16 | } 17 | } 18 | 19 | export default ErrorPage; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app-manager-system", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "antd": "^3.5.4", 7 | "crypto-js": "^3.1.9-1", 8 | "es6-promise": "^4.2.4", 9 | "prop-types": "latest", 10 | "qrcode.react": "^0.8.0", 11 | "react": "^16.4.0", 12 | "react-dom": "^16.4.0", 13 | "react-redux": "^5.0.7", 14 | "react-router": "^4.2.0", 15 | "react-router-dom": "^4.2.2", 16 | "react-scripts": "1.1.4", 17 | "recharts": "^1.0.0-beta.10", 18 | "redux": "^4.0.0", 19 | "whatwg-fetch": "^2.0.4", 20 | "moment": "latest" 21 | }, 22 | "scripts": { 23 | "start": "react-app-rewired start", 24 | "build": "react-app-rewired build", 25 | "test": "react-app-rewired test --env=jsdom", 26 | "eject": "react-scripts eject" 27 | }, 28 | "devDependencies": { 29 | "babel-plugin-import": "^1.7.0", 30 | "react-app-rewired": "^1.5.2" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import Login from './pages/LoginPage'; 6 | import ErrorPage from './pages/ErrorPage'; 7 | 8 | import {BrowserRouter, Redirect, Route, Switch} from 'react-router-dom'; 9 | import registerServiceWorker from './registerServiceWorker'; 10 | 11 | //未登录访问权限控制 12 | const PrivateRoute = ({component: Component, ...rest}) => ( 13 | sessionStorage.getItem("isAuth") ? () : 16 | } 17 | /> 18 | ); 19 | 20 | //应用入口 21 | ReactDOM.render(( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ), document.getElementById('root')); 31 | registerServiceWorker(); 32 | 33 | -------------------------------------------------------------------------------- /src/utils/CryptionUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AES加密解密组件 3 | * Created by panyz on 2018/6/28. 4 | */ 5 | import CryptoJS from 'crypto-js'; 6 | 7 | let key = "panyz20180628Web";//AES密钥 8 | let iv = "panyz20180628Web";//AES向量 9 | 10 | const ADD = "/add/"; 11 | 12 | /** 13 | * AES加密 14 | * @return {string} 15 | */ 16 | export function Encrypt(data) { 17 | let encrypted = CryptoJS.AES.encrypt(data, CryptoJS.enc.Latin1.parse(key), { 18 | iv: CryptoJS.enc.Latin1.parse(iv), 19 | mode: CryptoJS.mode.CBC, 20 | padding: CryptoJS.pad.ZeroPadding 21 | }); 22 | let result = encrypted.toString(); 23 | result = result.replace("\r\n", ""); 24 | result = result.replace("+", ADD); 25 | return result; 26 | } 27 | 28 | /** 29 | * AES解密 30 | * @return {string} 31 | */ 32 | export function Decrypt(data) { 33 | let data2 = data.replace(ADD, "+"); 34 | let decrypted = CryptoJS.AES.decrypt(data2, CryptoJS.enc.Latin1.parse(key), { 35 | iv: CryptoJS.enc.Latin1.parse(iv), 36 | mode: CryptoJS.mode.CBC, 37 | padding: CryptoJS.pad.ZeroPadding 38 | }); 39 | return decrypted.toString(CryptoJS.enc.Utf8); 40 | } -------------------------------------------------------------------------------- /src/css/Login.css: -------------------------------------------------------------------------------- 1 | .login { 2 | background-size: 100%; 3 | background: #f0f2f5 url("https://gw.alipayobjects.com/zos/rmsportal/TVYTbAXWheQpRcWDaDMu.svg"); 4 | height: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | justify-content: center; 8 | align-items: center; 9 | } 10 | 11 | .header{ 12 | flex: 1; 13 | width: 368px; 14 | display: flex; 15 | justify-content: center; 16 | flex-direction: column; 17 | align-items: center; 18 | } 19 | 20 | .alert{ 21 | width: 70%; 22 | } 23 | 24 | .content { 25 | flex: 3; 26 | display: flex; 27 | flex-direction: column; 28 | justify-content: center; 29 | text-align: center; 30 | width: 368px; 31 | margin: 24px 0; 32 | background-color: white; 33 | padding: 24px; 34 | border-radius: 24px; 35 | } 36 | 37 | .footer{ 38 | flex:1; 39 | display: flex; 40 | flex-direction: column; 41 | justify-content: center; 42 | } 43 | 44 | .inputAccount,.inputPwd{ 45 | margin-top:12px; 46 | } 47 | 48 | .btn-login{ 49 | margin-top: 12px; 50 | width: 100%; 51 | } 52 | 53 | .rememberPwd{ 54 | float: left; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/components/ActionBar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 顶部操作栏 3 | * Created by panyz on 2018/5/29. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Icon, Avatar, Menu, Dropdown} from 'antd'; 7 | import {NavLink} from 'react-router-dom'; 8 | import '../css/ActionBar.css'; 9 | 10 | const menu = ( 11 | 12 | 13 | 14 | 15 | 退出登录 16 | 17 | 18 | 19 | ); 20 | 21 | class ActionBar extends Component { 22 | 23 | render() { 24 | return ( 25 |
26 |
27 | { 30 | this.props.onToggle() 31 | }} 32 | /> 33 | 34 |
35 | 36 | {sessionStorage.getItem("account")} 37 |
38 |
39 |
40 | 41 |
42 | ) 43 | } 44 | } 45 | 46 | export default ActionBar; 47 | -------------------------------------------------------------------------------- /src/utils/URL.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 网络请求URL 3 | * Created by panyz on 2018/6/8. 4 | */ 5 | const BASE_URL = "your server name and port"; 6 | 7 | export const LOGIN = BASE_URL + "userAction/login";//登录接口 8 | 9 | export const GET_ALL_PROJECT = BASE_URL + "projectList/getAllProject";//获取项目接口 10 | 11 | export const ADD_PROJECT = BASE_URL + "projectList/addProject";//添加项目 12 | 13 | export const UPDATE_PROJECT = BASE_URL + "projectList/updateProject";//编辑项目 14 | 15 | export const DELETE_PROJECT = BASE_URL + "projectList/deleteProject";//删除项目 16 | 17 | export const GET_APP_BY_SYSTEM = BASE_URL + "app/getAppList";//通过App系统获取App列表 18 | 19 | export const UPLOAD_INSTALL_PACKAGE = BASE_URL + "upload/uploadAppPackage";//上传安装包 20 | 21 | export const GET_PROJECT_INFO = BASE_URL + "app/getProjectInfo";//获取项目信息 22 | 23 | export const ADD_APP_VERSION = BASE_URL + "app/addApp";//版本发布 24 | 25 | export const GET_TEST_APP = BASE_URL + "app/getTestApp";//获取测试App 26 | 27 | export const ADD_TEST_APP = BASE_URL + "testApp/addTestApp";//测试App版本发布 28 | 29 | export const UPLOAD_ICON = BASE_URL + "upload/uploadIcon";//上传图标 30 | 31 | export const GET_DOWNLOAD_STATISTICS = BASE_URL + "appDownloadStatistics/getDownloadStatistics";//获取下载统计数据 32 | 33 | export const GET_LOGIN_STATISTICS_CHART = BASE_URL + "appLoginStatistics/getLoginStatisticsChart";//获取登录统计图表数据 34 | 35 | export const GET_LOGIN_STATISTICS_TABLE = BASE_URL + "appLoginStatistics/getLoginStatisticsTable";//获取登录统计报表数据 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/utils/NavRouter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义菜单导航路由 3 | * Created by panyz on 2018/6/12. 4 | */ 5 | import React, {Component} from 'react'; 6 | import ErrorPage from "../pages/ErrorPage"; 7 | import ProjectListPage from '../pages/ProjectListPage'; 8 | import AppPage from '../pages/AppPage'; 9 | import ReleaseVersionPage from '../pages/ReleaseVersionPage'; 10 | import DataStatisticsPage from '../pages/DataStatisticsPage'; 11 | 12 | class NavRouter extends Component { 13 | 14 | /*判断页面是否存在*/ 15 | _handleViewVisible = (page) => { 16 | return page === 'projectList' || page === 'android' || page === 'ios' 17 | || page === 'releaseVersion' || page === 'dataStatistics'; 18 | }; 19 | 20 | /*返回对应的页面*/ 21 | _handleView = (page) => { 22 | if (page === 'projectList') { 23 | return ();//项目管理 24 | } else if (page === 'android' || page === 'ios') { 25 | return ();//App管理 26 | } else if (page === 'releaseVersion') { 27 | return //版本发布 28 | } else if (page === 'dataStatistics') { 29 | return ;//数据统计 30 | } 31 | }; 32 | 33 | render() { 34 | let page = this.props.match.params.id; 35 | return ( 36 |
37 | {this._handleViewVisible(page) ? this._handleView(page) : ()} 38 |
39 | ); 40 | } 41 | } 42 | 43 | export default NavRouter; -------------------------------------------------------------------------------- /src/components/CustomToolTip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 自定义图表弹出框 3 | * Created by panyz on 2018/7/9. 4 | */ 5 | import React,{Component} from 'react'; 6 | import PropTypes from 'prop-types'; 7 | 8 | class CustomToolTip extends Component{ 9 | static propTypes = { 10 | type: PropTypes.string, 11 | payload: PropTypes.array, 12 | label: PropTypes.string, 13 | }; 14 | 15 | _checkInfo = (data) => { 16 | return !(data === null || data === undefined || data.length === 0); 17 | }; 18 | 19 | render() { 20 | const {active,data} = this.props; 21 | if (active && this._checkInfo(data)) { 22 | const {payload, label} = this.props; 23 | return ( 24 |
25 | {data !== [] ?( 26 |
27 |

{`${label}`}

28 |

{`${payload[0].name}` + ":" + `${payload[0].value}`}

29 |

{`${payload[1].name}` + ":" + `${payload[1].value}`}

30 |

{"总计" + ":" + `${payload[0].value + payload[1].value}`}

31 |
32 | ):(
33 | 34 |
) } 35 | 36 |
37 | ); 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | 44 | export default CustomToolTip; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | App管理平台 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/utils/HttpUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 网络请求组件 3 | * Created by panyz on 2018/6/7. 4 | */ 5 | import 'es6-promise'; 6 | import 'whatwg-fetch'; 7 | 8 | /** 9 | * 网络请求处理 10 | * @param url 11 | * @param method 12 | * @param body 13 | */ 14 | export default function httpRequest(url, method, body) { 15 | method = method.toUpperCase(); 16 | if (method === 'GET') { 17 | body = undefined; 18 | } 19 | 20 | return fetch(url, { 21 | method: method, 22 | headers: { 23 | 'Content-Type': 'application/x-www-form-urlencoded', 24 | 'Accept': 'application/json;charset=utf-8' 25 | }, 26 | body: body 27 | }) 28 | .then( 29 | (res) => { 30 | if (res.status >= 200 && res.status < 300) { 31 | return res; 32 | } else { 33 | return Promise.reject('请求失败'); 34 | } 35 | } 36 | ) 37 | }; 38 | 39 | /** 40 | * 请求参数处理 41 | * @return {string} 42 | */ 43 | function addParams(map) { 44 | let params = new StringBuffer(); 45 | if (map.size !== 0) { 46 | for (let [key, value] of map) { 47 | params.append(key).append('=').append(value).append("&"); 48 | } 49 | } 50 | return params.toString(); 51 | 52 | } 53 | 54 | function StringBuffer() { 55 | this.__strings__ = []; 56 | } 57 | 58 | StringBuffer.prototype.append = function (str) { 59 | this.__strings__.push(str); 60 | return this; 61 | }; 62 | 63 | StringBuffer.prototype.toString = function () { 64 | return this.__strings__.join(""); 65 | }; 66 | 67 | export const doGet = (url) => httpRequest(url, "GET"); 68 | export const doPost = (url, body) => httpRequest(url, "POST", body); 69 | export const requestParams = (map) => addParams(map); 70 | 71 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import MenuNavigation from './components/MenuNavigation'; 3 | import ActionBar from './components/ActionBar'; 4 | import NavRouter from "./utils/NavRouter"; 5 | import {Layout} from 'antd'; 6 | import {Route} from "react-router-dom"; 7 | 8 | const {Header, Content, Footer, Sider} = Layout; 9 | 10 | 11 | class App extends Component { 12 | 13 | state = { 14 | collapsed: false, 15 | }; 16 | 17 | toggle = () => { 18 | this.setState({ 19 | collapsed: !this.state.collapsed, 20 | }); 21 | }; 22 | 23 | render() { 24 | console.log(this.props); 25 | const {match} = this.props; 26 | return ( 27 |
28 | 29 | 34 | 35 | 36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | Copyright©2018 panyz 49 |
50 |
51 |
52 | 53 |
54 | ); 55 | } 56 | } 57 | 58 | export default App; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppManagerSystem_React(App管理平台系统) 2 | ## 简介 3 | > 基于create-react-app手脚架创建的项目,使用React.js进行开发的App管理平台系统Web前端工程。 4 | 5 | 做了2年的Android开发,第一次使用react去折腾一个前端项目,如有做得不好的地方,还望纠正。 6 | 7 | 为什么做一个App管理平台系统?因为每次发布App的时候都是用公司N年前开发的一个发布系统,界面难看且落后。所以抽时间想自己也搞一个出来,折腾新的技术也是挺有趣的。 8 | 9 | 为什么选择React?因为之前我用过纯React Native和干货集中营开放的API开发过一个App应用[GankAndPanyz](https://github.com/panyz/GankAndPanyz),所以还算是熟悉React的语法。 10 | 11 | 第一次跨岗去尝试前后端分离,嗯!服务端代码也是自己来搞(使用的是Spring+SpringMVC+MyBatis)和MySQL数据库,虽然对一些原理还不是很深入,但是能把系统做了出来,收获还是挺大的。从移动端->前端->后端->全栈工程师,挺进! 12 | 13 | [项目源码Github地址](https://github.com/panyz/AppManagerSystem_React) 14 | 15 | ## 功能 16 | 1. **登录功能** 17 | 2. **数据统计功能**:App登录统计、App下载统计 18 | 3. **项目管理功能**:项目的添加、编辑、删除、整合了Android、iOS下载二维码 19 | 4. **App管理功能(Android、iOS)**:根据场景App分为正式环境和测试环境进行展示每个App的本地链接下载和二维码下载 20 | 5. **版本发布功能**:根据对应的手机系统上传安装包和相应的信息进行版本发布 21 | 22 | ## 使用到的第三方框架 23 | - ant design of react:UI框架 24 | - React-Router:路由框架 25 | - fetch: 网络请求框架 26 | - moment:日期处理框架 27 | - rechart:图表框架 28 | - qrcode.react 二维码框架 29 | - crypto-js:加密解密框架 30 | 31 | ## 截图 32 | ![login](https://upload-images.jianshu.io/upload_images/2355123-de190f26d93b8217.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 33 | 34 | ![datastatistics1](https://upload-images.jianshu.io/upload_images/2355123-aff1a91cb3da2dfe.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 35 | 36 | ![datastatistics2](https://upload-images.jianshu.io/upload_images/2355123-a78ec17af295b6e6.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 37 | 38 | ![projectlist](https://upload-images.jianshu.io/upload_images/2355123-3f67216176c47a8c.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 39 | 40 | ![appmanager](https://upload-images.jianshu.io/upload_images/2355123-cf3ea9351db97b45.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 41 | 42 | ![version](https://upload-images.jianshu.io/upload_images/2355123-54d434aa04cc5cfe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) -------------------------------------------------------------------------------- /src/components/AddProjectForm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 添加项目组件 3 | * Created by panyz on 2018/6/12. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Modal, Form, Input} from 'antd'; 7 | 8 | const FormItem = Form.Item; 9 | 10 | class AddProjectForm extends Component { 11 | render() { 12 | const {visible, onCancel, onCreate, form} = this.props; 13 | const {getFieldDecorator} = form; 14 | return ( 15 | 22 | 23 |
24 | 25 | {getFieldDecorator('projectName', { 26 | rules: [{ 27 | required: true, 28 | message: '请输入项目名称' 29 | }] 30 | })( 31 | 32 | )} 33 | 34 | 35 | 36 | {getFieldDecorator('projectCode', { 37 | rules: [{ 38 | required: true, 39 | message: '请输入项目编码' 40 | }] 41 | })( 42 | 43 | )} 44 | 45 | 46 | 47 | {getFieldDecorator('projectDes', { 48 | rules: [{ 49 | required: true, 50 | message: '请输入项目描述' 51 | }] 52 | })( 53 | 54 | )} 55 | 56 |
57 |
58 | ); 59 | } 60 | } 61 | 62 | const createProjectForm = Form.create()(AddProjectForm); 63 | export default createProjectForm; -------------------------------------------------------------------------------- /src/pages/AppLoginStatisticsPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App登录统计 3 | * Created by panyz on 2018/7/2. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Tabs} from 'antd'; 7 | import TabContent from '../components/TabContent'; 8 | 9 | import {DatePicker, Icon} from 'antd'; 10 | import moment from 'moment'; 11 | import {getCurrentYearMonth} from '../utils/DateUtil'; 12 | import {GET_ALL_PROJECT} from '../utils/URL'; 13 | import {doPost} from '../utils/HttpUtil'; 14 | 15 | const {MonthPicker} = DatePicker; 16 | const TabPane = Tabs.TabPane; 17 | 18 | class AppLoginStatisticsPage extends Component { 19 | 20 | state = { 21 | data: [], 22 | month: moment(Date.now()).format("YYYY-MM"), 23 | }; 24 | 25 | componentDidMount() { 26 | doPost(GET_ALL_PROJECT, null) 27 | .then(res => res.json()) 28 | .then(json => { 29 | if (json.code === 1) { 30 | this.setState({ 31 | data: json.data, 32 | }); 33 | } 34 | }) 35 | .catch(err => console.log(err)); 36 | } 37 | 38 | /*动态处理选项卡*/ 39 | _handleTabPan = (data) => { 40 | return data.map( 41 | (item) => { 42 | return ( 43 | {( 44 | )} 45 | ) 46 | } 47 | ); 48 | }; 49 | 50 | /*日期选择处理*/ 51 | _onDateChange = (date, dateString) => { 52 | this.setState({month: dateString}); 53 | }; 54 | 55 | render() { 56 | return ( 57 |
58 |
59 | App登录统计量 61 | 65 |
66 | 67 | {this._handleTabPan(this.state.data)} 68 | 69 |
70 | ) 71 | } 72 | } 73 | 74 | export default AppLoginStatisticsPage; -------------------------------------------------------------------------------- /src/components/CreateProjectButton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目管理中添加项目组件 3 | * Created by panyz on 2018/6/12. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Button, message} from 'antd'; 7 | import AddProjectForm from "../components/AddProjectForm"; 8 | import {ADD_PROJECT} from '../utils/URL'; 9 | import {doPost, requestParams} from '../utils/HttpUtil'; 10 | 11 | class CreateProjectButton extends Component { 12 | 13 | state = { 14 | visible: false, 15 | }; 16 | 17 | /*弹出对话框*/ 18 | _showModal = () => { 19 | this.setState({ 20 | visible: true 21 | }) 22 | }; 23 | 24 | /*取消对话框*/ 25 | _handleCancel = () => { 26 | this.setState({ 27 | visible: false, 28 | }) 29 | }; 30 | 31 | /*添加项目处理*/ 32 | _handleCreate = () => { 33 | const form = this.formRef.props.form; 34 | form.validateFields((err, values) => { 35 | if (!err) { 36 | let params = new Map(); 37 | params.set("projectName", values.projectName); 38 | params.set("projectCode", values.projectCode); 39 | params.set("projectDes", values.projectDes); 40 | doPost(ADD_PROJECT, requestParams(params)) 41 | .then(res => res.json()) 42 | .then(json => { 43 | if (json.code === 1) { 44 | message.success(json.msg); 45 | this.props.onSuccess(); 46 | } else { 47 | message.error(json.msg) 48 | } 49 | form.resetFields(); 50 | this.setState({ 51 | visible: false, 52 | }); 53 | }) 54 | .catch(err => console.error(err)) 55 | } 56 | }); 57 | }; 58 | 59 | _saveFormRef = (formRef) => { 60 | this.formRef = formRef; 61 | }; 62 | 63 | render() { 64 | return ( 65 |
66 | 67 | 73 |
74 | ); 75 | } 76 | } 77 | 78 | export default CreateProjectButton; -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/TabContent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App登录统计具体内容 3 | * Created by panyz on 2018/7/2. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {LineChart, Line, XAxis, YAxis, Tooltip, CartesianGrid,ResponsiveContainer } from 'recharts'; 7 | import {GET_LOGIN_STATISTICS_CHART} from '../utils/URL'; 8 | import {doPost, requestParams} from '../utils/HttpUtil'; 9 | import CustomTooltip from '../components/CustomToolTip'; 10 | import {message, Spin} from 'antd'; 11 | import AppLoginTablePage from '../pages/AppLoginTablePage'; 12 | 13 | class TabContent extends Component { 14 | 15 | state = { 16 | data: [], 17 | date: this.props.date, 18 | loading: true 19 | }; 20 | 21 | componentWillReceiveProps(nextProps) { 22 | if (this.props.date !== nextProps.date) { 23 | this._loadData(this.props.appCode, nextProps.date); 24 | this.setState({date: nextProps.date}); 25 | } 26 | } 27 | 28 | componentDidMount() { 29 | this._loadData(this.props.appCode, this.props.date); 30 | } 31 | 32 | /*加载统计数据*/ 33 | _loadData = (appCode, date) => { 34 | this.setState({loading: true}); 35 | let params = new Map(); 36 | params.set("appCode", appCode); 37 | params.set("date", date); 38 | doPost(GET_LOGIN_STATISTICS_CHART, requestParams(params)) 39 | .then(res => res.json()) 40 | .then(json => { 41 | if (json.code === 1) { 42 | this.setState({ 43 | data: json.data, 44 | loading: false 45 | }) 46 | } else { 47 | this.setState({data: [], loading: false}); 48 | message.warn(json.msg); 49 | } 50 | }) 51 | .catch(err => console.error(err)); 52 | }; 53 | 54 | render() { 55 | return ( 56 | 57 | 58 | 60 | 61 | 62 | 63 | }/> 64 | 65 | 66 | 67 | 68 | 69 | 70 | ) 71 | } 72 | } 73 | 74 | export default TabContent; 75 | -------------------------------------------------------------------------------- /src/components/MenuNavigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 菜单导航栏 3 | * Created by panyz on 2018/5/29. 4 | */ 5 | import React, {Component} from 'react'; 6 | import '../css/AppLogo.css'; 7 | import {Menu, Icon} from 'antd'; 8 | import {NavLink} from 'react-router-dom'; 9 | 10 | 11 | const SubMenu = Menu.SubMenu; 12 | 13 | class MenuNavigation extends Component { 14 | 15 | state = { 16 | key:['1'], 17 | }; 18 | 19 | 20 | componentDidMount(){ 21 | let pathname = window.location.pathname; 22 | let key = pathname.split("/")[2]; 23 | this.setState({ 24 | key: [key], 25 | }); 26 | 27 | } 28 | 29 | _onKeySelected = (item) => { 30 | this.setState({ 31 | key: [item.key] 32 | }); 33 | }; 34 | 35 | 36 | render() { 37 | const urlPath = this.props.baseUrl; 38 | return ( 39 |
40 |
41 |

logoApp管理平台

42 |
43 | 45 | 46 | 47 | 48 | 49 | 数据统计 50 | 51 | 52 | 53 | 54 | 55 | 56 | 项目管理 57 | 58 | 59 | 60 | 版本管理}> 61 | 62 | 63 | Android 64 | 65 | 66 | 67 | 68 | iOS 69 | 70 | 71 | 72 | 73 | 版本发布 74 | 75 | 76 | 77 | 78 | 79 |
80 | 81 | ); 82 | } 83 | } 84 | 85 | export default MenuNavigation; -------------------------------------------------------------------------------- /src/pages/AppLoginTablePage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App登录统计报表 3 | * Created by panyz on 2018/7/2. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Table, message} from 'antd'; 7 | import {GET_LOGIN_STATISTICS_TABLE} from '../utils/URL'; 8 | import {doPost, requestParams} from '../utils/HttpUtil'; 9 | 10 | 11 | const columns = [{ 12 | key: 'loginAccount', 13 | title: '用户账号', 14 | dataIndex: 'loginAccount', 15 | width: 100, 16 | fixed: 'left' 17 | }, { 18 | title: '应用名称', 19 | dataIndex: 'appName', 20 | key: 'appName', 21 | }, { 22 | title: '应用版本', 23 | dataIndex: 'appVersion', 24 | key: 'appVersion', 25 | }, { 26 | title: '登录时间', 27 | dataIndex: 'loginDate', 28 | key: 'loginDate', 29 | }, { 30 | title: '手机系统', 31 | dataIndex: 'systemType', 32 | key: 'systemType', 33 | }, { 34 | title: '手机机型', 35 | dataIndex: 'phoneModal', 36 | key: 'phoneModal', 37 | }, { 38 | title: '手机imei', 39 | dataIndex: 'imei', 40 | key: 'imei', 41 | }, { 42 | title: '地理位置', 43 | dataIndex: 'address', 44 | key: 'address', 45 | }]; 46 | 47 | class AppLoginTablePage extends Component { 48 | 49 | state = { 50 | data: { 51 | data: [], 52 | pageSize: 15, 53 | totalCount: 0, 54 | }, 55 | currentPage: 1 56 | }; 57 | 58 | componentDidMount() { 59 | if (this.props.appCode !== '') { 60 | this._loadData(this.props.appCode, 1, this.props.date); 61 | } 62 | } 63 | 64 | componentWillReceiveProps(nextProps) { 65 | this._loadData(nextProps.appCode, 1, nextProps.date); 66 | } 67 | 68 | /*加载报表数据*/ 69 | _loadData = (appCode, currentPage, date) => { 70 | let params = new Map(); 71 | params.set("appCode", appCode); 72 | params.set("currentPage", currentPage); 73 | params.set("date", date); 74 | doPost(GET_LOGIN_STATISTICS_TABLE, requestParams(params)) 75 | .then(res => res.json()) 76 | .then(json => { 77 | if (json.code === 1) { 78 | this.setState({ 79 | data: { 80 | data: json.data.data, 81 | pageSize: json.data.pageSize, 82 | totalCount: json.data.totalCount 83 | }, 84 | currentPage: json.data.currentPage 85 | }) 86 | } else { 87 | this.setState({ 88 | data: { 89 | data: [] 90 | } 91 | }); 92 | message.warn(json.msg); 93 | } 94 | }) 95 | .catch(err => console.error(err)); 96 | }; 97 | 98 | render() { 99 | return ( 100 |
101 | { 111 | this._loadData(this.props.appCode, page, this.props.date); 112 | }, 113 | hideOnSinglePage: true, 114 | }} 115 | /> 116 | 117 | ) 118 | } 119 | } 120 | 121 | export default AppLoginTablePage; -------------------------------------------------------------------------------- /src/components/EditProjectForm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目编辑 3 | * Created by panyz on 2018/6/13. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Modal, Form, Input, Upload, Icon, message, Button} from 'antd'; 7 | import {UPLOAD_ICON} from '../utils/URL'; 8 | 9 | const FormItem = Form.Item; 10 | 11 | 12 | class EditProjectForm extends Component { 13 | 14 | render() { 15 | const {visible, onCancel, onCreate, form, item} = this.props; 16 | const {getFieldDecorator} = form; 17 | const props = { 18 | name: 'icon', 19 | data: { 20 | projectCode: item.projectCode, 21 | }, 22 | action: UPLOAD_ICON, 23 | headers: { 24 | 'Access-Control-Allow-Headers': "x-requested-with", 25 | authorization: 'authorization-text' 26 | }, 27 | onChange(info) { 28 | if (info.file.status !== 'uploading') { 29 | 30 | } 31 | if (info.file.status === 'done') { 32 | message.success(`${info.file.name} 上传成功.`); 33 | } else if (info.file.status === 'error') { 34 | message.error(`${info.file.name} 上传失败.`); 35 | } 36 | }, 37 | }; 38 | 39 | 40 | return ( 41 | 48 | 49 |
50 | 51 | {getFieldDecorator('projectName', { 52 | initialValue: item.projectName, 53 | rules: [{ 54 | required: true, 55 | message: '请输入项目名称' 56 | }] 57 | })( 58 | 59 | )} 60 | 61 | 62 | 63 | {getFieldDecorator('projectCode', { 64 | initialValue: item.projectCode, 65 | rules: [{ 66 | required: true, 67 | message: '请输入项目编码' 68 | }] 69 | })( 70 | 71 | )} 72 | 73 | 74 | 75 | {getFieldDecorator('projectDes', { 76 | initialValue: item.projectDes, 77 | rules: [{ 78 | required: true, 79 | message: '请输入项目描述' 80 | }] 81 | })( 82 | 83 | )} 84 | 85 | 86 | 87 | 88 | {getFieldDecorator('appIcon', { 89 | rules: [{ 90 | required: false, 91 | }] 92 | })( 93 |
94 | 95 | 98 | 99 | *图标的大小为48x48 100 |
101 | )} 102 |
103 | 104 | 105 |
106 | ); 107 | } 108 | } 109 | 110 | const editForm = Form.create()(EditProjectForm); 111 | export default editForm; -------------------------------------------------------------------------------- /src/pages/AppDownloadPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App下载统计 3 | * Created by panyz on 2018/6/29. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {BarChart, Bar, XAxis, YAxis, Tooltip, CartesianGrid,ResponsiveContainer} from 'recharts'; 7 | import {Icon, DatePicker, message, Spin} from 'antd'; 8 | import {GET_DOWNLOAD_STATISTICS} from '../utils/URL'; 9 | import {doPost, requestParams} from '../utils/HttpUtil'; 10 | import moment from 'moment'; 11 | import 'moment/locale/zh-cn'; 12 | import locale from 'antd/lib/date-picker/locale/zh_CN'; 13 | import CustomTooltip from '../components/CustomToolTip'; 14 | 15 | const {RangePicker} = DatePicker; 16 | const dateFormat = 'YYYY-MM-DD'; 17 | const currentDate = new Date(); 18 | const lastMonthDate = new Date(); 19 | lastMonthDate.setMonth(currentDate.getMonth() - 1); 20 | 21 | class AppDownloadPage extends Component { 22 | 23 | state = { 24 | data: [], 25 | loading: true 26 | }; 27 | 28 | componentDidMount() { 29 | let startDate = moment(lastMonthDate).format(dateFormat); 30 | let endDate = moment(currentDate).format(dateFormat); 31 | this._loadDownloadStatistics(startDate, endDate); 32 | } 33 | 34 | /*加载下载统计数据*/ 35 | _loadDownloadStatistics = (startDate, endDate) => { 36 | this.setState({loading: true}); 37 | let params = new Map(); 38 | params.set("startDate", startDate); 39 | params.set("endDate", endDate); 40 | doPost(GET_DOWNLOAD_STATISTICS, requestParams(params)) 41 | .then(res => res.json()) 42 | .then(json => { 43 | if (json.code === 1) { 44 | this.setState({ 45 | data: json.data, 46 | loading: false 47 | }); 48 | } else { 49 | this.setState({ 50 | data: [], 51 | loading: false 52 | }); 53 | message.warn(json.msg); 54 | } 55 | }) 56 | .catch(err => console.error(err)); 57 | }; 58 | 59 | /*选择日期的处理*/ 60 | _onDateChange = (date, dateString) => { 61 | this._loadDownloadStatistics(dateString[0], dateString[1]); 62 | }; 63 | 64 | 65 | render() { 66 | return ( 67 |
68 |
69 | App下载统计量 71 | 78 |
79 |
80 | 81 | 82 | 84 | 85 | 86 | 87 | }/> 88 | 89 | 90 | 91 | 92 | 93 | 94 |
95 | ) 96 | } 97 | } 98 | 99 | export default AppDownloadPage; -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/pages/AppPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * App(Android、iOS)管理页面 3 | * Created by panyz on 2018/6/14. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Card, Icon, message, List, Popover, Radio} from 'antd'; 7 | import {doPost, requestParams} from '../utils/HttpUtil'; 8 | import {GET_APP_BY_SYSTEM, GET_TEST_APP} from '../utils/URL'; 9 | import QRCode from 'qrcode.react'; 10 | 11 | const RadioGroup = Radio.Group; 12 | 13 | class AppPage extends Component { 14 | 15 | state = { 16 | dataLoading: true, 17 | data: [], 18 | modalVisible: false, 19 | item: {}, 20 | radioDefaultKey: "a" 21 | }; 22 | 23 | componentWillReceiveProps(nextProps) { 24 | if (nextProps.appSystem !== this.props.appSystem) { 25 | this.setState({radioDefaultKey: "a"}); 26 | this._loadAppList(nextProps.appSystem, GET_APP_BY_SYSTEM); 27 | } 28 | } 29 | 30 | componentDidMount() { 31 | this._loadAppList(this.props.appSystem, GET_APP_BY_SYSTEM); 32 | } 33 | 34 | /*加载App的数据*/ 35 | _loadAppList = (appSystem, url) => { 36 | let params = new Map(); 37 | params.set("appSystem", appSystem); 38 | doPost(url, requestParams(params)) 39 | .then(res => res.json()) 40 | .then(json => { 41 | if (json.code !== 1) { 42 | message.warn(json.msg); 43 | } 44 | this.setState({ 45 | dataLoading: false, 46 | data: json.data 47 | }) 48 | }) 49 | .catch(err => console.error(err)); 50 | }; 51 | 52 | /*对App的图标进行处理*/ 53 | _getImage = (icon, appSystem) => { 54 | if (icon === null || icon === undefined || icon === '') { 55 | if (appSystem === "android") { 56 | return require('../images/Android.png'); 57 | } else if (appSystem === "ios") { 58 | return require('../images/iphone.png') 59 | } 60 | } else { 61 | return icon; 62 | } 63 | }; 64 | 65 | /*对App的二维码进行处理*/ 66 | _handleQrCode = (item) => { 67 | return ( 68 | 75 | ); 76 | }; 77 | 78 | /*正式环境与测试环境之间的切换处理*/ 79 | _onRadioChange = (e) => { 80 | if ("a" === e.target.value) { 81 | this.setState({radioDefaultKey: "a"}); 82 | this._loadAppList(this.props.appSystem, GET_APP_BY_SYSTEM); 83 | } else if ("b" === e.target.value) { 84 | this.setState({radioDefaultKey: "b"}); 85 | this._loadAppList(this.props.appSystem, GET_TEST_APP); 86 | } 87 | }; 88 | 89 | 90 | render() { 91 | 92 | return ( 93 |
94 | 97 | 正式环境 98 | 测试环境 99 | 100 |
101 | ( 107 | 108 | 下载, 112 | 113 | 二维码 114 | ]} 115 | > 116 | } 119 | title={item.appName + " V" + item.versionName} 120 | description={item.versionDes} 121 | /> 122 | 123 | 124 | )} 125 | /> 126 | 127 |
128 |
129 | ) 130 | } 131 | } 132 | 133 | export default AppPage; 134 | -------------------------------------------------------------------------------- /src/pages/LoginPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 登录界面 3 | * Created by panyz on 2018/6/1. 4 | */ 5 | import React, {Component} from 'react'; 6 | import '../css/Login.css'; 7 | import {Input, Icon, Button, Form, Checkbox, Alert} from 'antd'; 8 | import {doPost, requestParams} from '../utils/HttpUtil'; 9 | import {LOGIN as url_login} from '../utils/URL'; 10 | import {Encrypt} from '../utils/CryptionUtil'; 11 | 12 | const FormItem = Form.Item; 13 | 14 | class Login extends Component { 15 | 16 | constructor(props) { 17 | super(props); 18 | this.state = { 19 | isRemember: localStorage.getItem("password") !== null, 20 | errorMessage: "", 21 | visible: false, 22 | loading: false 23 | } 24 | } 25 | 26 | /*是否记住密码*/ 27 | _rememberPwd = (e) => { 28 | this.setState({ 29 | isRemember: e.target.checked, 30 | }); 31 | }; 32 | 33 | /*登录操作*/ 34 | _hanldSubmit = (e) => { 35 | e.preventDefault(); 36 | this.props.form.validateFields((err, values) => { 37 | if (!err) { 38 | let params = new Map(); 39 | params.set("account", values.account); 40 | params.set("pwd", Encrypt(values.password)); 41 | doPost(url_login, requestParams(params)) 42 | .then( 43 | res => res.json() 44 | ) 45 | .then(json => { 46 | if (json.code !== 1) { 47 | this.setState({ 48 | visible: true, 49 | loading: false, 50 | errorMessage: json.msg 51 | }) 52 | } else { 53 | this._handleRememberPwd(values); 54 | sessionStorage.setItem("isAuth", true); 55 | sessionStorage.setItem("account", json.data.account); 56 | sessionStorage.setItem("userAuth", json.data.userAuth); 57 | this.props.history.push("/home/dataStatistics"); 58 | } 59 | console.log(json); 60 | }) 61 | .catch(err => console.warn(err)); 62 | } 63 | }) 64 | }; 65 | 66 | /*记住密码后进行存储*/ 67 | _handleRememberPwd = (values) => { 68 | if (this.state.isRemember) { 69 | localStorage.setItem("account", values.account); 70 | localStorage.setItem("password", values.password); 71 | } else { 72 | localStorage.removeItem("password"); 73 | } 74 | }; 75 | 76 | /*初始化登录信息*/ 77 | _initLoginInfo = (value) => { 78 | if (value === null || value === undefined) { 79 | return ""; 80 | } else { 81 | return value; 82 | } 83 | }; 84 | 85 | render() { 86 | const {getFieldDecorator} = this.props.form; 87 | return ( 88 |
89 | 90 |
91 | {this.state.visible ? ( 92 | this.setState({visible: false})}/> 99 | ) : null} 100 |
101 | 102 |
103 |

logoApp管理平台

104 |
105 | 106 | {getFieldDecorator("account", 107 | { 108 | initialValue: this._initLoginInfo(localStorage.getItem("account")), 109 | rules: [{ 110 | required: true, 111 | message: '请输入账号!' 112 | }] 113 | })( 114 | } 118 | /> 119 | )} 120 | 121 | 122 | 123 | {getFieldDecorator('password', { 124 | initialValue: this._initLoginInfo(localStorage.getItem("password")), 125 | rules: [{ 126 | required: true, 127 | message: '请输入密码' 128 | }] 129 | })( 130 | } 136 | /> 137 | )} 138 | 139 | 140 | {getFieldDecorator('remember', { 141 | valuePropName: 'checked', 142 | initialValue: this.state.isRemember 143 | })( 144 | 记住密码 145 | )} 146 | 153 | 154 | 155 |
156 | 157 |
158 | Copyright©2018 panyz 159 |
160 | 161 |
162 | ); 163 | } 164 | } 165 | 166 | const LoginForm = Form.create({})(Login); 167 | 168 | export default LoginForm; -------------------------------------------------------------------------------- /src/pages/ProjectListPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 项目管理界面 3 | * Created by panyz on 2018/6/12. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {List, Popconfirm, message, Popover, Icon} from 'antd'; 7 | import {doPost, requestParams} from '../utils/HttpUtil'; 8 | import {GET_ALL_PROJECT, DELETE_PROJECT, UPDATE_PROJECT} from '../utils/URL'; 9 | import EditProjectForm from "../components/EditProjectForm"; 10 | import CreateProjectButton from '../components/CreateProjectButton'; 11 | import QRCode from 'qrcode.react'; 12 | 13 | class ProjectListPage extends Component { 14 | 15 | state = { 16 | dataLoading: true, 17 | data: [], 18 | item: {}, 19 | visible: false, 20 | }; 21 | 22 | componentDidMount() { 23 | this._loadProjectListData(); 24 | } 25 | 26 | /*加载项目列表数据*/ 27 | _loadProjectListData = () => { 28 | doPost(GET_ALL_PROJECT, null) 29 | .then(res => res.json()) 30 | .then(json => { 31 | this.setState({ 32 | dataLoading: false, 33 | data: json.data, 34 | }) 35 | }) 36 | .catch(err => console.log(err)) 37 | }; 38 | 39 | /*编辑完成后的处理*/ 40 | _handleCancel = () => { 41 | this.formRef.props.form.resetFields(); 42 | this.setState({ 43 | visible: false, 44 | loading: false 45 | }) 46 | }; 47 | 48 | _saveFormRef = (formRef) => { 49 | this.formRef = formRef; 50 | }; 51 | 52 | /*编辑处理*/ 53 | _handleEdit = () => { 54 | this.setState({loading: true}); 55 | const form = this.formRef.props.form; 56 | form.validateFields((err, values) => { 57 | if (!err) { 58 | let params = new Map(); 59 | params.set("projectId", this.state.item.projectId); 60 | params.set("projectName", values.projectName); 61 | params.set("projectCode", values.projectCode); 62 | params.set("projectDes", values.projectDes); 63 | doPost(UPDATE_PROJECT, requestParams(params)) 64 | .then(res => res.json()) 65 | .then(json => { 66 | if (json.code === 1) { 67 | message.success(json.msg); 68 | this._loadProjectListData(); 69 | } else { 70 | message.error(json.msg); 71 | } 72 | form.resetFields(); 73 | this.setState({ 74 | visible: false, 75 | }) 76 | }) 77 | .catch(err => console.error(err)); 78 | } 79 | }); 80 | form.resetFields(); 81 | }; 82 | 83 | /*处理二维码*/ 84 | _handleQrCode = (url) => { 85 | if (url === null || url === undefined || url === '') { 86 | return "暂无App发布上线"; 87 | } else { 88 | return ( 89 | 96 | ); 97 | } 98 | }; 99 | 100 | /*处理图标*/ 101 | _hanldeIcon = (url) => { 102 | console.log(url); 103 | if (url === null || url === undefined || url === '') { 104 | return require('../images/icon_project.png'); 105 | } else { 106 | return url; 107 | } 108 | }; 109 | 110 | render() { 111 | const {dataLoading, data} = this.state; 112 | return ( 113 |
114 |
115 | 116 |
117 | ( 122 | 125 | 二维码 126 | ] : [ 127 | 128 | 二维码 129 | , 130 | { 131 | this.setState({ 132 | visible: true, 133 | item: item 134 | }) 135 | }}>编辑, 136 | { 138 | let params = new Map(); 139 | params.set("projectId", item.projectId); 140 | doPost(DELETE_PROJECT, requestParams(params)) 141 | .then(res => res.json()) 142 | .then(json => { 143 | if (json.code === 1) { 144 | message.success(json.msg); 145 | this._loadProjectListData(); 146 | } else { 147 | message.error(json.msg); 148 | } 149 | }) 150 | .catch(err => console.error(err)); 151 | }}> 152 | 删除 153 | ] 154 | }> 155 | } 157 | title={{item.projectName}} 158 | description={"项目编码: " + item.projectCode} 159 | /> 160 |
{item.projectDes}
161 |
162 | )} 163 | /> 164 | 171 |
172 | 173 | ); 174 | } 175 | } 176 | 177 | export default ProjectListPage; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/pages/ReleaseVersionPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 版本发布界面 3 | * Created by panyz on 2018/6/20. 4 | */ 5 | import React, {Component} from 'react'; 6 | import {Form, Input, Tooltip, Icon, Select, InputNumber, Button, Upload, message} from 'antd'; 7 | import * as URL from "../utils/URL"; 8 | import {doPost, requestParams} from '../utils/HttpUtil'; 9 | 10 | const FormItem = Form.Item; 11 | const Option = Select.Option; 12 | const {TextArea} = Input; 13 | 14 | const formItemLayout = { 15 | labelCol: { 16 | xs: {span: 24}, 17 | sm: {span: 8}, 18 | }, 19 | wrapperCol: { 20 | xs: {span: 24}, 21 | sm: {span: 16}, 22 | }, 23 | }; 24 | 25 | /*文件上传的一些属性*/ 26 | const props = { 27 | name: 'file', 28 | data: { 29 | type: '', 30 | projectCode: '', 31 | environment:'', 32 | }, 33 | action: URL.UPLOAD_INSTALL_PACKAGE, 34 | headers: { 35 | 'Access-Control-Allow-Headers': "x-requested-with", 36 | authorization: 'authorization-text' 37 | }, 38 | onChange(info) { 39 | if (info.file.status !== 'uploading') { 40 | console.log(info.file, info.fileList); 41 | } 42 | if (info.file.status === 'done') { 43 | message.success(`${info.file.name} 上传成功`); 44 | 45 | } else if (info.file.status === 'error') { 46 | message.error(`${info.file.name} 上传失败.`); 47 | } 48 | }, 49 | }; 50 | 51 | const projectMap = new Map(); 52 | 53 | 54 | class ReleaseVersionPage extends Component { 55 | 56 | state = { 57 | data: [], 58 | btnLoading: false 59 | }; 60 | 61 | componentDidMount() { 62 | doPost(URL.GET_PROJECT_INFO, null) 63 | .then(res => res.json()) 64 | .then(json => { 65 | if (json.code === 1) { 66 | json.data.map((item) => { 67 | projectMap.set(item.appCode, item.appName) 68 | }); 69 | this.setState({ 70 | data: json.data 71 | }); 72 | } 73 | 74 | }) 75 | .catch(err => console.error(err)); 76 | } 77 | 78 | /*版本发布的信息检查处理*/ 79 | checkInfo = (values) => { 80 | if (values.versionEnvironment === "请选择发布环境") { 81 | message.warn("请选择发布环境"); 82 | return false; 83 | } 84 | if (values.appSystem === "请选择系统") { 85 | message.warn("请选择系统"); 86 | return false; 87 | } 88 | if (values.appCode === "请选择项目") { 89 | message.warn("请选择项目"); 90 | return false; 91 | } 92 | if (values.updateType === "请选择更新类型") { 93 | message.warn("请选择更新类型"); 94 | return false; 95 | } 96 | return true; 97 | }; 98 | 99 | 100 | /*版本发布处理*/ 101 | handleSubmit = (e) => { 102 | e.preventDefault(); 103 | this.props.form.validateFields((err, values) => { 104 | console.log(values); 105 | 106 | if (this.checkInfo(values) && !err) { 107 | this.setState({btnLoading: true}); 108 | let params = new Map(); 109 | params.set("appSystem", values.appSystem); 110 | params.set("appCode", values.appCode); 111 | params.set("appName", projectMap.get(values.appCode)); 112 | params.set("versionCode", values.versionCode); 113 | params.set("versionName", values.versionName); 114 | params.set("versionDes", values.versionDes); 115 | params.set("versionDownload", values.versionDownload); 116 | params.set("updateType", values.updateType); 117 | params.set("iosPlist", values.iosPlist); 118 | let url = ""; 119 | if ("real" === values.versionEnvironment) { 120 | url = URL.ADD_APP_VERSION; 121 | } else if ("test" === values.versionEnvironment) { 122 | url = URL.ADD_TEST_APP; 123 | } 124 | doPost(url, requestParams(params)) 125 | .then(res => res.json()) 126 | .then(json => { 127 | if (json.code === 1) { 128 | this.setState({btnLoading: false}); 129 | message.success(json.msg); 130 | this.props.form.resetFields(); 131 | } else { 132 | message.error(json.msg); 133 | } 134 | 135 | }) 136 | } 137 | }); 138 | }; 139 | 140 | /*动态处理项目选项*/ 141 | _handleProjectOptions = (data) => { 142 | return ( 143 | data.map((item) => { 144 | return ( 145 | 146 | ) 147 | }) 148 | 149 | ); 150 | }; 151 | 152 | /*安装包上传前的信息检查处理*/ 153 | _uploadCheck = (system, appCode,environment) => { 154 | if (system !== '请选择系统'){ 155 | props.data.type = system; 156 | } 157 | if (appCode !== '请选择项目'){ 158 | props.data.projectCode = appCode; 159 | } 160 | if (environment !== '请选择环境'){ 161 | props.data.environment = environment; 162 | } 163 | }; 164 | 165 | 166 | render() { 167 | const {getFieldDecorator, getFieldValue} = this.props.form; 168 | return ( 169 |
180 | 181 |
182 | 183 | 184 | {getFieldDecorator('versionEnvironment', { 185 | initialValue: '请选择发布环境', 186 | rules: [{ 187 | required: true, 188 | }] 189 | })( 190 | 194 | )} 195 | 196 | 197 | 198 | {getFieldDecorator('appSystem', { 199 | initialValue: '请选择系统', 200 | rules: [{ 201 | required: true, 202 | }] 203 | })( 204 | 208 | )} 209 | 210 | 211 | 212 | {getFieldDecorator('appCode', { 213 | initialValue: '请选择项目', 214 | rules: [{ 215 | required: true 216 | }], 217 | })( 218 | 221 | )} 222 | 223 | 224 | 版本号  226 | 227 | 228 | 229 | 230 | )} {...formItemLayout}> 231 | {getFieldDecorator('versionCode', { 232 | rules: [{ 233 | required: true, message: '请输入版本号', 234 | }], 235 | })( 236 | 237 | )} 238 | 239 | 240 | 版本名称  242 | 243 | 244 | 245 | 246 | )}{...formItemLayout} 247 | > 248 | {getFieldDecorator('versionName', { 249 | rules: [{required: true, message: '请输入版本名称'}], 250 | })( 251 | 252 | )} 253 | 254 | 255 | 256 | {getFieldDecorator('versionDes', { 257 | rules: [{required: true, message: '请输入版本描述'}], 258 | })( 259 |