From 0740f644a4a6b58a04efc6bd842dbe68c9fcd7df Mon Sep 17 00:00:00 2001 From: zuiidea Date: Sat, 17 Sep 2016 14:45:49 +0800 Subject: [PATCH] some clean --- package.json | 1 + src/components/App/index.js | 112 ------ src/components/App/index.less | 154 -------- src/components/Breadcrumb/index.js | 2 +- src/components/DBTable/InnerForm.js | 449 ---------------------- src/components/DBTable/InnerPagination.js | 28 -- src/components/DBTable/InnerTable.js | 425 -------------------- src/components/DBTable/index.js | 276 ------------- src/components/DBTable/index.less | 32 -- src/components/Login/index.js | 80 ---- src/components/Login/index.less | 164 -------- src/components/Logo/index.js | 23 -- src/components/Logo/index.less | 6 - src/components/Sidebar/index.js | 4 +- src/components/Welcome/index.js | 19 - src/components/Welcome/index.less | 3 - src/config.js | 29 +- src/index.html | 2 +- src/index.js | 38 +- src/menu.js | 202 +++++----- src/schema/test.config.js | 8 - src/schema/test.dataSchema.js | 48 --- src/schema/test.querySchema.js | 101 ----- src/schema/testSms.config.js | 4 - src/schema/testSms.dataSchema.js | 34 -- src/schema/testSms.querySchema.js | 38 -- src/utils/Logger.js | 187 +++------ src/utils/ajax.js | 227 ----------- src/utils/lib.js | 19 +- src/view/data/list.jsx | 29 +- 30 files changed, 194 insertions(+), 2550 deletions(-) delete mode 100644 src/components/App/index.js delete mode 100644 src/components/App/index.less delete mode 100644 src/components/DBTable/InnerForm.js delete mode 100644 src/components/DBTable/InnerPagination.js delete mode 100644 src/components/DBTable/InnerTable.js delete mode 100644 src/components/DBTable/index.js delete mode 100644 src/components/DBTable/index.less delete mode 100644 src/components/Login/index.js delete mode 100644 src/components/Login/index.less delete mode 100644 src/components/Logo/index.js delete mode 100644 src/components/Logo/index.less delete mode 100644 src/components/Welcome/index.js delete mode 100644 src/components/Welcome/index.less delete mode 100644 src/schema/test.config.js delete mode 100644 src/schema/test.dataSchema.js delete mode 100644 src/schema/test.querySchema.js delete mode 100644 src/schema/testSms.config.js delete mode 100644 src/schema/testSms.dataSchema.js delete mode 100644 src/schema/testSms.querySchema.js delete mode 100644 src/utils/ajax.js diff --git a/package.json b/package.json index 0edc5e0..70e0cc6 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "react-dom": "15.0.2", "react-hot-loader": "1.3.0", "react-router": "2.0.1", + "reqwest": "^2.0.5", "strip-loader": "0.1.2", "style-loader": "0.13.1", "stylelint": "6.6.0", diff --git a/src/components/App/index.js b/src/components/App/index.js deleted file mode 100644 index edf889c..0000000 --- a/src/components/App/index.js +++ /dev/null @@ -1,112 +0,0 @@ -import React from 'react'; -import {Spin, message} from 'antd'; -import Header from '../Header'; -import Footer from '../Footer'; -import Sidebar from '../Sidebar'; -import Login from '../Login'; -import Breadcrumb from '../Breadcrumb'; -import './index.less'; -import globalConfig from 'config.js'; -import ajax from '../../utils/ajax'; -import Logger from '../../utils/Logger'; - -const logger = Logger.getLogger('App'); - -/** - * App组件 - * 定义整个页面的布局 - */ -class App extends React.Component { - - // 全局的状态 - state = { - loading: true, // 是否在loading状态 - login: false, // 是否登录 - userName: '未登录', // 登录后的用户名 - }; - - componentWillMount() { - logger.debug('mount and add styles'); - // 为了兼容登录组件 - document.body.style.background = 'white'; // FIXME: gross hack - } - - componentDidMount() { - if (!this.state.login) { - const hide = message.loading('正在获取用户信息...', 0); - - // 先去服务端验证下, 说不定已经登录了 - ajax.get(`${globalConfig.getAPIPath()}${globalConfig.login.getCurrentUser}`).end((err, res) => { - - // 注意这里, debug模式下每次刷新都必须重新登录 - if (ajax.isSuccess(res) && !globalConfig.debug) { - hide(); - this.loginSuccess(res.body.data); - } else { - // 如果服务端说没有登录, 就要跳转到sso或者登录界面 - if (globalConfig.isSSO()) { - // debug模式不支持调试单点登录 - // 因为没有单点登录的地址啊...跳不回来 - hide(); - logger.debug('not login, redirect to SSO login page'); - const redirect = encodeURIComponent(window.location.href); - window.location.href = `${globalConfig.login.sso}${redirect}`; - } else { - hide(); - message.error('获取用户信息失败, 请重新登录'); - logger.debug('not login, redirect to Login component'); - this.setState({loading: false, login: false}); - } - } - }); - } - } - - // 登录成功后的回调 - loginSuccess = (name, showMsg) => { - logger.debug('callback logingSuccess, name = %s', name); - if (showMsg) - message.success('登录成功'); - document.body.style.background = 'white'; // FIXME: gross hack - this.setState({loading: false, login: true, userName: name}); - } - - render() { - // 显示一个加载中 - if (this.state.loading) { - return
; - } - - // 跳转到登录界面 - if (!this.state.login) { - return ; - } - - return ( -
- {/*整个页面被一个ant-layout-aside的div包围, 分为sidebar/header/footer/content等几部分*/} - - -
- - -
- - - {/*TODO: 这里要组件化*/} -
-
- {this.state.loading ? '' : this.props.children} -
-
- -
- - -
-
- ); - } -} - -export default App; diff --git a/src/components/App/index.less b/src/components/App/index.less deleted file mode 100644 index 44aff70..0000000 --- a/src/components/App/index.less +++ /dev/null @@ -1,154 +0,0 @@ -/*定义整体layout的样式, 直接从antd官网copy的, 稍微修改了一些*/ -/*我本来想把样式拆到各个模块去, 但是太麻烦了...*/ - -.ant-layout-aside { - position: relative; - height: 100%; -} - -.ant-layout-aside .ant-layout-logo { - width: 200px; - height: 32px; - background: #333; - border-radius: 6px; - margin: 16px 8px 16px 12px; - transition: all 0.3s ease; -} - -.ant-layout-aside-collapse .ant-layout-logo { - width: 32px; - margin: 16px; - transition: all 0.3s ease; -} - -.ant-layout-aside .ant-layout-sider { - width: 224px; - background: #404040; - position: absolute; - overflow: auto; - padding-bottom: 24px; - height: 100%; - transition: all 0.3s ease; -} - -.ant-layout-aside-collapse .ant-layout-sider { - width: 64px; - transition: all 0.3s ease; -} - -.ant-layout-aside .ant-layout-sider > .ant-menu { - margin-bottom: 20px; -} - -.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item { - margin: 16px 0; -} - -.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item .nav-text { - vertical-align: baseline; - display: inline-block; -} - -.ant-layout-aside .ant-layout-sider > .ant-menu > .ant-menu-item > .anticon { - transition: font-size .3s; -} - -.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item { - transition: all 0s ease; -} - -.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item > .anticon { - font-size: 16px; - display: inline-block; -} - -.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item .nav-text { - display: none; -} - -.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item:hover { - background: #2db7f5; - color: #fff; - transition: all 0s ease; -} - -.ant-layout-aside-collapse .ant-layout-sider > .ant-menu > .ant-menu-item:hover .nav-text { - display: inline-block; - vertical-align: top; - background: #2db7f5; - color: #fff; - padding-right: 16px; - border-radius: 0 5px 5px 0; -} - -/* 实际使用中需要改成 position: fixed */ -.ant-layout-aside .ant-aside-action { - height: 42px; - width: 224px; - position: absolute; - bottom: 0; - background: #656565; - color: #fff; - text-align: center; - line-height: 42px; - cursor: pointer; - transition: all 0.3s ease; -} - -.ant-layout-aside-collapse .ant-aside-action { - width: 64px; - transition: all 0.3s ease; -} - -.ant-layout-aside .ant-layout-header { - background: #fff; - height: 48px; - border-bottom: 1px solid #e9e9e9; -} - -.ant-layout-aside .ant-layout-breadcrumb { - margin: 7px 0 -17px 24px; -} - -.ant-layout-aside .ant-layout-main { - margin-left: 224px; - transition: all 0.3s ease; - overflow: auto; - height: 100%; -} - -.ant-layout-aside-collapse .ant-layout-main { - margin-left: 64px; - transition: all 0.3s ease; -} - -.ant-layout-aside .ant-layout-container { - margin: 24px 16px; -} - -.ant-layout-aside .ant-layout-content { - background: #fff; - padding: 24px; -} - -.ant-layout-aside .ant-layout-footer { - height: 64px; - line-height: 64px; - text-align: center; - font-size: 12px; - color: #999; - background: #fff; - border-top: 1px solid #e9e9e9; - width: 100%; -} - -// 为了在屏幕中间显示一个loading -.center-div { - position: absolute; /*绝对定位*/ - top: 50%; /* 距顶部50%*/ - left: 50%; /* 距左边50%*/ - height: 2px; - margin-top: -1px; /*margin-top为height一半的负值*/ - width: 2px; - margin-left: -1px; /*margin-left为width一半的负值*/ -} diff --git a/src/components/Breadcrumb/index.js b/src/components/Breadcrumb/index.js index 297cee5..0f4432d 100644 --- a/src/components/Breadcrumb/index.js +++ b/src/components/Breadcrumb/index.js @@ -1,7 +1,7 @@ import React from 'react'; import {Breadcrumb, Icon} from 'antd'; import items from 'menu.js'; -import Logger from '../../utils/Logger'; +import Logger from '../../utils/logger'; const Item = Breadcrumb.Item; const logger = Logger.getLogger('Breadcrumb'); diff --git a/src/components/DBTable/InnerForm.js b/src/components/DBTable/InnerForm.js deleted file mode 100644 index 3d9cf4d..0000000 --- a/src/components/DBTable/InnerForm.js +++ /dev/null @@ -1,449 +0,0 @@ -import React from 'react'; -import { - Form, - Input, - Row, - Col, - Button, - DatePicker, - Select, - Icon, - Radio, - InputNumber, - Checkbox, - message, - Upload, - notification -} from 'antd'; -import globalConfig from 'config.js'; -import Logger from '../../utils/Logger'; - -const FormItem = Form.Item; -const RadioGroup = Radio.Group; -const CheckboxGroup = Checkbox.Group; -const Option = Select.Option; - -const logger = Logger.getLogger('InnerForm'); - -/** - * 内部表单组件 - */ -class InnerForm extends React.Component { - - // 本来想在初始化时解析schema, 结果发现不行 - // 解析schema的过程还是改到render里了 - - /** - * 辅助函数, 将一个input元素包装下 - * - * @param formItem - * @param field - * @returns {XML} - */ - colWrapper = (formItem, field) => { - //const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; - return ( - - - {formItem} - - - ); - } - - /** - * 将schema中的一列转换为下拉框 - * - * @param field - */ - transformSelect = (field) => { - // TODO: 这里是否要做schema校验 - const options = []; - const {getFieldProps} = this.props.form; - - logger.debug('transform field %o to Select component', field); - field.options.forEach((option) => { - options.push(); - }); - - return this.colWrapper(( - - ), field); - } - - /** - * 将schema中的一列转换为单选框 - * - * @param field - */ - transformRadio = (field) => { - const options = []; - const {getFieldProps} = this.props.form; - - logger.debug('transform field %o to Radio component', field); - field.options.forEach((option) => { - options.push({option.value}); - }); - - return this.colWrapper(( - - {options} - - ), field); - } - - /** - * 将schema中的一列转换为checkbox - * - * @param field - */ - transformCheckbox = (field) => { - const options = []; - const {getFieldProps} = this.props.form; - - logger.debug('transform field %o to Checkbox component', field); - field.options.forEach((option) => { - options.push({label: option.value, value: option.key}); - }); - - return this.colWrapper(( - - ), field); - } - - /** - * 转换为下拉多选框 - * - * @param field - * @returns {XML} - */ - transformMultiSelect = (field) => { - const options = []; - const {getFieldProps} = this.props.form; - - logger.debug('transform field %o to MultipleSelect component', field); - field.options.forEach((option) => { - options.push(); - }); - - return this.colWrapper(( - - ), field); - } - - /** - * 将schema中的一列转换为普通输入框 - * - * @param field - * @returns {XML} - */ - transformNormal = (field) => { - const {getFieldProps} = this.props.form; - switch (field.dataType) { - case 'int': - logger.debug('transform field %o to integer input component', field); - return this.colWrapper(( - - ), field); - case 'float': - logger.debug('transform field %o to float input component', field); - return this.colWrapper(( - - ), field); - case 'datetime': - logger.debug('transform field %o to datetime input component', field); - return this.colWrapper(( - - ), field); - default: // 默认就是普通的输入框 - logger.debug('transform field %o to varchar input component', field); - return this.colWrapper(( - - ), field); - } - } - - /** - * 也是个辅助函数, 由于是范围查询, 输入的formItem是两个, 一个用于begin一个用于end - * - * @param beginFormItem - * @param endFormItem - * @param field - * @returns {XML} - */ - betweenColWrapper = (beginFormItem, endFormItem, field) => { - // 布局真是个麻烦事 - // col内部又用了一个row做布局 - // const {getFieldProps} = this.props.form; - return ( - - - - - {beginFormItem} - - - - - {endFormItem} - - - - - ); - } - - /** - * between类型比较特殊, 普通元素每个宽度是8, int和float的between元素宽度也是8, 但datetime类型的between元素宽度是16 - * 否则排版出来不能对齐, 太丑了, 逼死强迫症 - * 而且普通的transform函数返回是一个object, 而这个函数返回是一个array, 就是因为datetime的between要占用两列 - * - * @param field - */ - transformBetween = (field) => { - const cols = []; - let beginFormItem; - let endFormItem; - const {getFieldProps} = this.props.form; - - switch (field.dataType) { - case 'int': - logger.debug('transform field %o to integer BETWEEN component', field); - beginFormItem = (); - endFormItem = (); - cols.push(this.betweenColWrapper(beginFormItem, endFormItem, field)); - break; - case 'float': - logger.debug('transform field %o to float BETWEEN component', field); - beginFormItem = (); - endFormItem = (); - cols.push(this.betweenColWrapper(beginFormItem, endFormItem, field)); - break; - // datetime类型的between要占用两个Col - // 不写辅助函数了, 直接这里写jsx吧... - case 'datetime': - logger.debug('transform field %o to datetime BETWEEN component', field); - cols.push( - - - - - - ); - cols.push( - - - - ); - break; - default: - // 理论上来说不会出现这种情况 - logger.error('unknown dataType: %s', field.dataType); - } - return cols; - } - - /** - * 表单的查询条件不能直接传给后端, 要处理一下 - * - * @param oldObj - * @returns {{}} - */ - filterQueryObj(oldObj) { - // 将提交的值中undefined的去掉 - const newObj = {}; - for (const key in oldObj) { - if (oldObj[key]) { - // 对于js的日期类型, 要转换成字符串再传给后端 - if (oldObj[key] instanceof Date) { - newObj[key] = oldObj[key].format('yyyy-MM-dd HH:mm:ss'); - } else { - newObj[key] = oldObj[key]; - } - } - } - logger.debug('old queryObj: %o, new queryObj %o', oldObj, newObj); - return newObj; - } - - /** - * 处理表单提交 - * - * @param e - */ - handleSubmit = (e) => { - e.preventDefault(); - - const oldObj = this.props.form.getFieldsValue(); - const newObj = this.filterQueryObj(oldObj); - - // 还是要交给上层组件处理, 因为要触发table组件的状态变化... - this.props.parentHandleSubmit(newObj); - } - - handleReset = (e) => { - e.preventDefault(); - this.props.form.resetFields(); - } - - /** - * 处理数据导入 - */ - handleImport = (info) => { - logger.debug('upload status: %s', info.file.status); - // 正在导入时显示一个提示信息 - if (info.file.status === 'uploading') { - if (!this.hideLoading) { - let hide = message.loading('正在导入...'); - this.hideLoading = hide; - } - } - // 导入完成, 无论成功或失败, 必须给出提示, 并且要用户手动关闭 - else if (info.file.status === 'error') { - this.hideLoading(); - this.hideLoading = undefined; - notification.error({ - message: '导入失败', - description: '文件上传失败, 请联系管理员', - duration: 0, - }); - } - // done的情况下还要判断返回值 - else if (info.file.status === 'done') { - this.hideLoading(); - this.hideLoading = undefined; - logger.debug('upload result %o', info.file.response); - if (!info.file.response.success) { - notification.error({ - message: '导入失败', - description: `请联系管理员, 错误信息: ${info.file.response.message}`, - duration: 0, - }); - } else { - notification.success({ - message: '导入成功', - description: info.file.response.data, - duration: 0, - }); - } - } - } - - /** - * 处理数据导出 - * 本质上也是提交表单, 跟handleSubmit有点类似 - * 但不用再提交给上层组件处理了, 因为不需要改变表格组件的状态 - */ - handleExport = (e) => { - e.preventDefault(); - - const oldObj = this.props.form.getFieldsValue(); - const newObj = this.filterQueryObj(oldObj); - - // 导出前必须选定了一些查询条件, 不允许导出全表 - // 防止误操作 - if (Object.keys(newObj).length === 0) { - message.warning('导出时查询条件不能为空', 4.5); - return; - } - - // ajax是不能处理下载请求的, 必须交给浏览器自己去处理 - // 坏处是我就不知道用户的下载是否成功了 - const url = `${globalConfig.getAPIPath()}/${this.props.tableName}/export`; - window.open(`${url}?q=${encodeURIComponent(JSON.stringify(newObj))}`); // 注意url编码 - } - - render() { - const rows = []; - let cols = []; - - // 参见antd的布局, 每行被分为24个格子 - // 普通的字段每个占用8格, between类型的字段每个占用16格 - let spaceLeft = 24; - this.props.schema.forEach((field)=> { - // 当前列需要占用几个格子? 普通的都是8, 只有datetime between是16 - let spaceNeed = 8; - if (field.showType === 'between' && field.dataType === 'datetime') { - spaceNeed = 16; - } - - // 如果当前行空间不足, 就换行 - if (spaceLeft < spaceNeed) { - rows.push({cols}); - cols = []; // 不知array有没有clear之类的方法 - spaceLeft = 24; // 剩余空间重置 - } - - // 开始push各种FormItem - switch (field.showType) { - case 'select': - cols.push(this.transformSelect(field)); - break; - case 'radio': - cols.push(this.transformRadio(field)); - break; - case 'checkbox': - cols.push(this.transformCheckbox(field)); - break; - case 'multiSelect': - cols.push(this.transformMultiSelect(field)); - break; - case 'between': - for (const col of this.transformBetween(field)) { // between类型比较特殊, 返回的是一个数组 - cols.push(col) - } - break; - default: - cols.push(this.transformNormal(field)); - } - - spaceLeft -= spaceNeed; - }); - - // 别忘了最后一行 - if (cols.length > 0) { - rows.push({cols}); - } - - // 上传相关配置 - const uploadProps = { - name: 'file', - action: `${globalConfig.getAPIPath()}/${this.props.tableName}/import`, - showUploadList: false, - onChange: this.handleImport, - }; - - // 表单的前面是一堆输入框, 最后一行是按钮 - return ( -
- {rows} - - - - - {this.props.tableConfig.showExport ? - : ''} - {this.props.tableConfig.showImport ? - : ''} - - -
- ); - } - -} - -InnerForm = Form.create()(InnerForm); // antd中的表单组件还要这么包装一层 - -export default InnerForm; diff --git a/src/components/DBTable/InnerPagination.js b/src/components/DBTable/InnerPagination.js deleted file mode 100644 index da016c4..0000000 --- a/src/components/DBTable/InnerPagination.js +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import {Pagination, Select} from 'antd'; - -/** - * 内部分页器组件 - */ -class InnerPagination extends React.Component { - - render() { - // 有些状态要传到父组件中去处理 - return ( -
- `每页${this.props.pageSize}条, 共 ${total} 条`} - pageSize={this.props.pageSize} defaultCurrent={1} - current={this.props.currentPage} - onChange={this.props.parentHandlePageChange} - /> -
- ); - } - -} - -export default InnerPagination; diff --git a/src/components/DBTable/InnerTable.js b/src/components/DBTable/InnerTable.js deleted file mode 100644 index ee88c02..0000000 --- a/src/components/DBTable/InnerTable.js +++ /dev/null @@ -1,425 +0,0 @@ -import React from 'react'; -import { - Form, - Input, - Button, - DatePicker, - Select, - Table, - Icon, - Radio, - InputNumber, - Checkbox, - Modal, - message, - notification, - Affix -} from 'antd'; -import globalConfig from 'config.js'; -import Logger from '../../utils/Logger'; -import ajax from '../../utils/ajax'; - -const FormItem = Form.Item; -const ButtonGroup = Button.Group; - -const logger = Logger.getLogger('InnerTable'); - -/** - * 内部表格组件 - */ -class InnerTable extends React.Component { - - // 很多时候都要在antd的组件上再包一层 - state = { - modalVisible: false, // modal是否可见 - modalTitle: '新增', // modal标题 - modalInsert: true, // 当前modal是用来insert还是update - - selectedRowKeys: [], // 当前有哪些行被选中, 这里只保存key - selectedRows: [], // 当前有哪些行被选中, 保存完整数据 - }; - - /** - * InnerTable组件的重render有两种可能: - * 1. 上层组件调用的render方法, 这个时候会触发componentWillReceiveProps方法 - * 2. 自身状态变化引起的重新render - * 注意区分 - * - * 对于第一种情况, 要将组件的状态还原到初始状态 - * - * @param nextProps - */ - componentWillReceiveProps = (nextProps) => { - logger.debug('receive new props and try to render, nextProps=%o', nextProps); - // 其实传入的props和当前的props可能是一样的, 这个方法不会判断修改才触发 - // 要自己判断props是否有变化 - - // 蛋疼的是, 要区分是上层组件引起的这个方法调用还是InnerTable组件本身引起的 - // 本来自身的变化是不应该触发的, 但不知antd的form组件做了什么, 每次给表单设置值的时候就会触发, 我讨厌黑盒... - // 利用了一个特性: 如果是上层触发的, tableLoading必定是true - if (nextProps.tableLoading === true) { - // 所有状态都要手动还原到初始值 - // 这个方法里setState不会触发render - this.setState({ - modalVisible: false, - modalTitle: '新增', - modalInsert: true, - - selectedRowKeys: [], - selectedRows: [], - }); - } - } - - /** - * 点击新增按钮, 弹出一个内嵌表单的modal - * - * @param e - */ - onClickInsert = (e) => { - e.preventDefault(); - this.props.form.resetFields(); - this.setState({modalVisible: true, modalTitle: '新增', modalInsert: true}); - } - - /** - * 点击更新按钮, 弹出一个内嵌表单的modal - * 注意区分单条更新和批量更新 - * - * @param e - */ - onClickUpdate = (e) => { - e.preventDefault(); - this.props.form.resetFields(); - const multiSelected = this.state.selectedRowKeys.length > 1; // 是否选择了多项 - // 如果只选择了一项, 就把原来的值填到表单里 - // 否则就只把要更新的主键填到表单里 - if (!multiSelected) { - logger.debug('update single record, and fill original values'); - this.props.form.setFieldsValue(this.state.selectedRows.pop()); // FIXME: 理论上来说不应该这样去修改state中的值 - } else { - const tmpObj = {}; - tmpObj[this.primaryKey] = this.state.selectedRowKeys.join(', '); - logger.debug('update multiple records, keys = %s', tmpObj[this.primaryKey]); - this.props.form.setFieldsValue(tmpObj); - } - - if (multiSelected) { - this.setState({modalVisible: true, modalTitle: '批量更新', modalInsert: false}); - } else { - this.setState({modalVisible: true, modalTitle: '更新', modalInsert: false}); - } - } - - /** - * 点击删除按钮, 弹出一个确认对话框 - * 注意区分单条删除和批量删除 - * - * @param e - */ - onClickDelete = (e) => { - e.preventDefault(); - Modal.confirm({ - title: this.state.selectedRowKeys.length > 1 ? '确认批量删除' : '确认删除', - content: `当前被选中的行: ${this.state.selectedRowKeys.join(', ')}`, - // 这里注意要用箭头函数, 否则this不生效 - onOk: () => { - this.handleDelete(); - }, - }); - } - - /** - * 隐藏modal - */ - hideModal = () => { - this.setState({modalVisible: false}); - } - - /** - * 点击modal中确认按钮的回调 - */ - handleModalOk = () => { - // 将表单中的undefined去掉 - // 表单传过来的主键是逗号分隔的字符串, 这里转换成数组 - const newObj = {}; - const primaryKeyArray = []; - const oldObj = this.props.form.getFieldsValue(); - for (const key in oldObj) { - if (!oldObj[key]) - continue; - - if (key === this.primaryKey && typeof oldObj[key] === 'string') { - for (const str of oldObj[key].split(', ')) { - // 按schema中的约定, 主键只能是int/varchar - if (this.primaryKeyType === 'int') { - primaryKeyArray.push(parseInt(str)); - } else { - primaryKeyArray.push(str); - } - } - } else { - newObj[key] = oldObj[key]; - } - } - if (this.primaryKey && primaryKeyArray.length > 0) { - newObj[this.primaryKey] = primaryKeyArray; - } - this.hideModal(); - logger.debug('click modal OK and the form obj = %o', newObj); - - if (this.state.modalInsert) { - this.handleInsert(newObj); - } else { - this.handleUpdate(newObj); - } - } - - - /*下面开始才是真正的数据库操作*/ - - - /** - * 真正去处理新增数据 - */ - handleInsert = (obj) => { - const url = `${globalConfig.getAPIPath()}/${this.props.tableName}/insert`; - const hide = message.loading('正在新增...', 0); - logger.debug('handleInsert: url = %s, obj = %o', url, obj); - - ajax.post(url).send(obj).end((err, res) => { - hide(); - logger.debug('handleInsert: return error = %o, res = %o', err, res); - // err就是一个字符串 - // res是一个Response对象, 其中的body字段才是服务端真正返回的数据 - if (err || !res.body.success) { - notification.error({ - message: '新增失败', - description: err ? '请求insert接口出错, 请联系管理员' : `请联系管理员, 错误信息: ${res.body.message}`, - duration: 0, - }); - } else { - notification.success({ - message: '新增成功', - description: this.primaryKey ? `新增数据行 主键=${res.body.data[this.primaryKey]}` : '', - duration: 0, - }); - } - }); - } - - /** - * 真正去更新数据 - */ - handleUpdate = (obj) => { - const keys = obj[this.primaryKey]; - const url = `${globalConfig.getAPIPath()}/${this.props.tableName}/update?keys=${keys instanceof Array ? keys.join(',') : keys}`; - const hide = message.loading('正在更新...', 0); - obj[this.primaryKey] = undefined; - - logger.debug('handleUpdate: url = %s, obj = %o', url, obj); - - ajax.post(url).send(obj).end((err, res) => { - hide(); - logger.debug('handleUpdate: return error = %o, res = %o', err, res); - - if (err || !res.body.success) { - notification.error({ - message: '更新失败', - description: err ? '请求update接口出错, 请联系管理员' : `请联系管理员, 错误信息: ${res.body.message}`, - duration: 0, - }); - } else { - notification.success({ - message: '更新成功', - description: `更新${res.body.data}条数据`, - duration: 0, - }); - // 更新数据后, 注意刷新下整个页面 - this.props.refresh(); - } - }); - } - - /** - * 真正去删除数据 - */ - handleDelete = () => { - const url = `${globalConfig.getAPIPath()}/${this.props.tableName}/delete?keys=${this.state.selectedRowKeys.join(',')}`; - const hide = message.loading('正在删除...', 0); - logger.debug('handleDelete: url = %s', url); - - ajax.post(url).end((err, res) => { - hide(); - logger.debug('handleDelete: return error = %o, res = %o', err, res); - if (err || !res.body.success) { - notification.error({ - message: '删除失败', - description: err ? '请求delete接口出错, 请联系管理员' : `请联系管理员, 错误信息: ${res.body.message}`, - duration: 0, - }); - } else { - notification.success({ - message: '删除成功', - description: `删除${res.body.data}条数据`, - duration: 0, - }); - // 更新数据后, 注意刷新下整个页面 - this.props.refresh(); - } - }); - } - - /** - * 辅助函数 - * - * @param formItem - * @param field - * @returns {XML} - */ - colWrapper = (formItem, field) => { - //const {getFieldProps, getFieldError, isFieldValidating} = this.props.form; - return ( - - {formItem} - - ); - } - - /** - * 将schema中的一个字段转换为表单的一项 - * - * @param field - */ - transFormField = (field) => { - const {getFieldProps} = this.props.form; - - // 对于主键, 直接返回一个不可编辑的textarea - if (this.primaryKey === field.key) { - logger.debug('key %o is primary, transform to text area', field); - return this.colWrapper(( - - ), field); - } - - switch (field.dataType) { - case 'int': - logger.debug('transform field %o to integer input', field); - return this.colWrapper(( - - ), field); - case 'float': - logger.debug('transform field %o to float input', field); - return this.colWrapper(( - - ), field); - case 'datetime': - logger.debug('transform field %o to datetime input', field); - return this.colWrapper(( - - ), field); - default: // 默认就是普通的输入框 - logger.debug('transform field %o to varchar input', field); - return this.colWrapper(( - - ), field); - } - } - - /** - * 处理表格的选择事件 - * - * @param selectedRowKeys - * @param selectedRows - */ - handleSelectChange = (selectedRowKeys, selectedRows) => { - this.setState({selectedRowKeys, selectedRows}); - } - - render() { - // 解析schema - const newCols = []; - this.props.schema.forEach((field) => { - const col = {}; - col.key = field.key; - col.dataIndex = field.key; - col.title = field.title; - if (field.render) { - col.render = field.render; - } - newCols.push(col); - // 当前列是否是主键? - if (field.primary) { - this.primaryKey = field.key; - this.primaryKeyType = field.dataType; - } - }); - - // 对数据也要处理一下 - // 每行数据都必须有个key属性, 如果指定了主键, 就以主键为key - // 否则直接用个自增数字做key - const newData = []; - let i = 0; - this.props.data.forEach((obj) => { - const newObj = Object.assign({}, obj); - if (this.primaryKey) { - newObj.key = obj[this.primaryKey]; - } else { - newObj.key = i; - i++; - } - newData.push(newObj); - }); - - // 生成表单项 - const formItems = []; - this.props.schema.forEach((field) => { - formItems.push(this.transFormField(field)); - }); - - const rowSelection = { - selectedRowKeys: this.state.selectedRowKeys, - onChange: this.handleSelectChange, - }; - - const hasSelected = this.state.selectedRowKeys.length > 0; // 是否选择 - const multiSelected = this.state.selectedRowKeys.length > 1; // 是否选择了多项 - - return ( -
-
- document.getElementById('main-content-div')}> - - - {/* 注意这里, 如果schema中没有定义主键, 不允许update或delete */} - - - - - -
- {formItems} -
-
-
- - - - ); - } - -} - -InnerTable = Form.create()(InnerTable); - -export default InnerTable; diff --git a/src/components/DBTable/index.js b/src/components/DBTable/index.js deleted file mode 100644 index de77996..0000000 --- a/src/components/DBTable/index.js +++ /dev/null @@ -1,276 +0,0 @@ -import React from 'react'; -import {message, notification} from 'antd'; -import Error from '../Error'; -import InnerForm from './InnerForm.js'; -import InnerTable from './InnerTable.js'; -import InnerPagination from './InnerPagination.js'; -import './index.less'; -import globalConfig from 'config.js'; -import ajax from '../../utils/ajax'; -import Logger from '../../utils/Logger'; - -const logger = Logger.getLogger('DBTable'); - -/** - * 操作数据库中的一张表的组件, 又可以分为3个组件: 表单+表格+分页器 - */ -class DBTable extends React.Component { - - // 代替componentWillMount - constructor(props) { - super(props); - // 组件初始化时尝试获取schema - this.tryFetchSchema(this.props); - } - - // 父组件要保存子组件的状态...非常蛋疼... - // 破坏了子组件的"封闭"原则 - - // 注意: 向父组件传状态, 通过回调函数的形式 - // 从父组件接收状态, 通过props的形式 - - state = { - // 表单组件的状态 - queryObj: {}, // 表单中的查询条件 - - // 表格组件的状态 - data: [], // 表格中显示的数据 - tableLoading: false, // 表格是否是loading状态 - - // 分页器的状态 - currentPage: 1, // 当前第几页 - pageSize: 50, // pageSize暂时不可修改, 固定50 - total: 0, // 总共有多少条数据 - }; - - /** - * 尝试获取某个表的querySchema和dataSchema - * 无论是从远端获取还是从本地配置读取, 这个方法必须是同步的 - * - * @param dbName - * @param tableName - */ - tryFetchSchema(props) { - const routes = props.routes; - // 这个tableName是路由表配置中传过来的 - // 可以用这个方法向组件传值 - const tableName = routes.pop().tableName; - if (tableName) { - logger.info('init component DBTable with tableName = %s', tableName); - } else { - logger.error('can not find tableName, check your router config'); - this.inited = false; // 是否成功获取schema - this.errorMsg = '找不到表名, 请检查路由配置'; // 如果没能成功获取schema, 错误信息是什么? - return; - } - - // 其实很多这种this.xxx变量也可以做成状态, 看情况了 - // 关键是这些变量变化时, 是否要触发重新render? - - this.tableName = tableName; - - // 尝试加载querySchema - try { - this.querySchema = require(`../../schema/${tableName}.querySchema.js`); - } catch (e) { - logger.error('load query schema error: %o', e); - this.inited = false; - this.errorMsg = `加载${tableName}表的querySchema出错, 请检查配置`; - return; - } - - // 尝试加载dataSchema - try { - this.dataSchema = require(`../../schema/${tableName}.dataSchema.js`); - } catch (e) { - logger.error('load data schema error: %o', e); - this.inited = false; - this.errorMsg = `加载${tableName}表的dataSchema出错, 请检查配置`; - return; - } - - // 尝试加载个性化配置, 加载失败也没影响 - try { - this.tableConfig = require(`../../schema/${tableName}.config.js`); - } catch (e) { - logger.warn('can not find config for table %s, use default instead', tableName); - - // TODO: 默认配置写在这里 - this.tableConfig = { - showExport: true, // 显示导出按钮, 默认true - showImport: true, // 显示导入按钮, 默认true - } - } - - this.inited = true; - } - - /** - * 刚进入页面时触发一次查询 - */ - componentDidMount() { - this.refresh(); - } - - // 在react router中切换时, 组件不会重新mount, 只有props会变化 - componentWillReceiveProps = (nextProps) => { - logger.debug('receive new props and try to render, nextProps = %o', nextProps); - this.tryFetchSchema(nextProps); // 根据新的props重新获取配置 - // 所有状态都要手动还原到初始值 - // this.setState({ - // queryObj: {}, - // data: [], - // tableLoading: false, - // currentPage: 1, - // pageSize: 50, - // total: 0, - // }); - - // 这种写法和setState有何区别? - // 似乎setState不是"立即"发生的, 会导致下面refresh时有bug - this.state.queryObj = {}; - this.state.data = []; - this.state.tableLoading = false; - this.state.currentPage = 1; - this.state.pageSize = 50; - this.state.total = 0; - - // 切换时也要重新查询一次数据 - this.refresh(); - } - - /** - * 切换分页时触发查询 - * - * @param page - */ - handlePageChange = (page) => { - logger.debug('handlePageChange, page = %d', page); - this.setState({tableLoading: true}); - this.select(this.state.queryObj, page, this.state.pageSize).then((result) => { - //message.success('查询成功'); - this.setState({ - currentPage: page, - data: result.data, - total: result.total, - tableLoading: false, - }); - }, this.handleError); - } - - /** - * 点击提交按钮时触发查询 - * - * @param queryObj - */ - handleFormSubmit = (queryObj) => { - logger.debug('handleFormSubmit, queryObj = %o', queryObj); - this.setState({tableLoading: true}); - // 这时查询条件已经变了, 要从第一页开始查 - this.select(queryObj, 1, this.state.pageSize).then((result) => { - //message.success('查询成功'); - this.setState({ - currentPage: 1, - data: result.data, - total: result.total, - tableLoading: false, - queryObj: queryObj, - }); - }, this.handleError); - } - - /** - * 统一处理ajax失败时的回调 - * - * @param errorMsg - */ - handleError = (errorMsg) => { - // 对于错误信息, 要很明显的提示用户, 这个通知框要用户手动关闭 - notification.error({ - message: '出错啦!', - description: `请联系管理员, 错误信息: ${errorMsg}`, - duration: 0, - }); - this.setState({tableLoading: false}); - }; - - /** - * 按当前的查询条件重新查询一次 - */ - refresh = () => { - this.setState({tableLoading: true}); - this.select(this.state.queryObj, this.state.currentPage, this.state.pageSize).then((result) => { - //message.success('查询成功'); - this.setState({ - data: result.data, - total: result.total, - tableLoading: false, - }); - }, this.handleError); - } - - /** - * 向服务端发送select请求, 会返回一个promise对象 - * - * @param queryObj 包含了form中所有的查询条件, 再加上page和pageSize, 后端就能拼成完整的sql - * @param page - * @param pageSize - * @returns {Promise} - */ - select(queryObj, page, pageSize) { - const hide = message.loading('正在查询...', 0); - // superagent请求会直接返回一个promise对象, 但不能直接用, 还是要包装一层 - const tmpObj = Object.assign({}, queryObj); // 创建一个新的临时对象, 其实直接修改queryObj也可以 - tmpObj.page = page; - tmpObj.pageSize = pageSize; - const url = `${globalConfig.getAPIPath()}/${this.tableName}/select`; // 拼接要请求的url地址 - logger.debug('select: url = %s, queryObj = %o', url, tmpObj); - - // 只是为了试下ES6的Promise特性...其实直接用superagent的API也可以 - const promise = new Promise((resolve, reject) => { - ajax.post(url).send(tmpObj).end((err, res) => { - hide(); - // err就是一个字符串 - // res是一个Response对象, 其中的body字段才是服务端真正返回的数据 - if (err || !res.body.success) { - reject(err ? '请求select接口出错, 请联系管理员' : res.body.message); - } else { - resolve(res.body); - } - }); - }); - - return promise; - } - - render() { - // 如果没能成功加载schema, 显示错误信息 - if (!this.inited) { - return ( - - ); - } - - // 之前是直接{...this.state}, 感觉会影响效率 - // 父组件传进去的方法名都是parentHandleXXX - - // InnerForm和InnerPagination都是无状态的 - // 但InnerTable还是要维护自己的一些状态 - - return ( -
- - - -
- ); - } - -} - -export default DBTable; diff --git a/src/components/DBTable/index.less b/src/components/DBTable/index.less deleted file mode 100644 index dd923ea..0000000 --- a/src/components/DBTable/index.less +++ /dev/null @@ -1,32 +0,0 @@ -.ant-advanced-search-form { - padding: 16px 8px; - background: #f8f8f8; - border: 1px solid #d9d9d9; - border-radius: 6px; -} - -/* 由于输入标签长度不确定,所以需要微调使之看上去居中 */ -.ant-advanced-search-form > .ant-row { - position: relative; - left: -6px; -} - -.ant-advanced-search-form .ant-btn + .ant-btn { - margin-left: 8px; -} - -/* 我讨厌写css... */ -.db-pagination { - margin-top: 8px; - margin-bottom: 8px; - float: left; -} - -.db-table-button { - margin-top: 8px; - margin-bottom: 8px; -} - -.ant-upload { - margin-left: 8px; -} diff --git a/src/components/Login/index.js b/src/components/Login/index.js deleted file mode 100644 index 3c5cc35..0000000 --- a/src/components/Login/index.js +++ /dev/null @@ -1,80 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import globalConfig from 'config'; -import ajax from '../../utils/ajax'; -import Logger from '../../utils/Logger'; -import {message} from 'antd'; -import './index.less'; - -const logger = Logger.getLogger('Login'); - -/** - * 定义Login组件 - */ -class Login extends React.Component { - - // 这个login组件是直接从网上找的: https://colorlib.com/wp/html5-and-css3-login-forms/ - // 不是react风格... - // TODO: 以后要把这个login组件改掉 - - handleSubmit = (e) => { - e.preventDefault(); // 这个很重要, 防止跳转 - const hide = message.loading('正在验证...', 0); - - const username = ReactDOM.findDOMNode(this.refs.user).value; - const password = ReactDOM.findDOMNode(this.refs.pass).value; - logger.debug('username = %s, password = %s', username, password); - - const button = ReactDOM.findDOMNode(this.refs.button); - button.setAttribute('disabled', 'true'); // 暂时屏蔽提交按钮, 防止用户重复点击 - - // 服务端验证 - ajax.post(`${globalConfig.getAPIPath()}${globalConfig.login.validate}`).type('form').send({ - username, - password, - }).end((err, res) => { - hide(); - logger.debug('login validate return: error %o result %o', err, res); - - if (ajax.isSuccess(res)) { - // 如果登录成功, 调用回调函数, 把状态往上层组件传 - if (this.props.loginSuccess) - this.props.loginSuccess(res.body.data, true); - else - message.info(`登录成功, 用户名: ${res.body.data}`); - } else { - message.error(`登录失败: ${res.body.message}, 请联系管理员`); - button.removeAttribute('disabled'); // 登录失败的话, 重新让按钮可用, 让用户重新登录 - } - }); - } - - componentWillMount() { - logger.debug('mount and add styles'); - document.body.style.background = null; - } - - componentWillUnmount() { - logger.debug('unmount and clear styles'); - // 组件unmount时设置下样式, 不然其他组件的显示会有问题 - // TODO: 不知道有没有更好的办法 - document.body.style.background = 'white'; - } - - render() { - return ( -
-
-

{globalConfig.name}

-
- - - - -
-
- ); - } -} - -export default Login; diff --git a/src/components/Login/index.less b/src/components/Login/index.less deleted file mode 100644 index 519fa04..0000000 --- a/src/components/Login/index.less +++ /dev/null @@ -1,164 +0,0 @@ -// 都是直接抄来的样式 -// 对我来说就像天书一样好嘛... -// 这玩意真的是人工手写出来的??? - -.btn { - display: inline-block; - *display: inline; - *zoom: 1; - padding: 4px 10px 4px; - margin-bottom: 0; - font-size: 13px; - line-height: 18px; - color: #333333; - text-align: center; - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - vertical-align: middle; - background-color: #f5f5f5; - background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); - background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); - background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); - background-image: linear-gradient(top, #ffffff, #e6e6e6); - background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); - border-color: #e6e6e6 #e6e6e6 #e6e6e6; - border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); - border: 1px solid #e6e6e6; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); - cursor: pointer; - *margin-left: .3em; -} - -.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { - background-color: #e6e6e6; -} - -.btn-large { - padding: 9px 14px; - font-size: 15px; - line-height: normal; - -webkit-border-radius: 5px; - -moz-border-radius: 5px; - border-radius: 5px; -} - -.btn:hover { - color: #333333; - text-decoration: none; - background-color: #e6e6e6; - background-position: 0 -15px; - -webkit-transition: background-position 0.1s linear; - -moz-transition: background-position 0.1s linear; - -ms-transition: background-position 0.1s linear; - -o-transition: background-position 0.1s linear; - transition: background-position 0.1s linear; -} - -.btn-primary, .btn-primary:hover { - text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); - color: #ffffff; -} - -.btn-primary.active { - color: rgba(255, 255, 255, 0.75); -} - -.btn-primary { - background-color: #4a77d4; - background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); - background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); - background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); - background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); - background-image: linear-gradient(top, #6eb6de, #4a77d4); - background-repeat: repeat-x; - filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0); - border: 1px solid #3762bc; - text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); -} - -.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { - filter: none; - background-color: #4a77d4; -} - -.btn-block { - width: 100%; - display: block; -} - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - -o-box-sizing: border-box; - box-sizing: border-box; -} - -html { - width: 100%; - height: 100%; - overflow: hidden; -} - -body { - width: 100%; - height: 100%; - font-family: 'Open Sans', sans-serif; - background: #092756; - background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104, 128, 138, .4) 10%, rgba(138, 114, 76, 0) 40%), -moz-linear-gradient(top, rgba(57, 173, 219, .25) 0%, rgba(42, 60, 87, .4) 100%), -moz-linear-gradient(-45deg, #670d10 0%, #092756 100%); - background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104, 128, 138, .4) 10%, rgba(138, 114, 76, 0) 40%), -webkit-linear-gradient(top, rgba(57, 173, 219, .25) 0%, rgba(42, 60, 87, .4) 100%), -webkit-linear-gradient(-45deg, #670d10 0%, #092756 100%); - background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104, 128, 138, .4) 10%, rgba(138, 114, 76, 0) 40%), -o-linear-gradient(top, rgba(57, 173, 219, .25) 0%, rgba(42, 60, 87, .4) 100%), -o-linear-gradient(-45deg, #670d10 0%, #092756 100%); - background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104, 128, 138, .4) 10%, rgba(138, 114, 76, 0) 40%), -ms-linear-gradient(top, rgba(57, 173, 219, .25) 0%, rgba(42, 60, 87, .4) 100%), -ms-linear-gradient(-45deg, #670d10 0%, #092756 100%); - background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104, 128, 138, .4) 10%, rgba(138, 114, 76, 0) 40%), linear-gradient(to bottom, rgba(57, 173, 219, .25) 0%, rgba(42, 60, 87, .4) 100%), linear-gradient(135deg, #670d10 0%, #092756 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#3E1D6D', endColorstr='#092756', GradientType=1); -} - -.login { - position: absolute; - top: 50%; - left: 50%; - margin: -150px 0 0 -150px; - width: 300px; - height: 300px; -} - -.login h1 { - color: #fff; - text-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - letter-spacing: 1px; - text-align: center; - padding-bottom: 15px; -} - -.login-input { - width: 100%; - margin-bottom: 10px; - background: rgba(0, 0, 0, 0.3); - border: none; - outline: none; - padding: 10px; - font-size: 13px; - color: #fff; - text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); - border: 1px solid rgba(0, 0, 0, 0.3); - border-radius: 4px; - box-shadow: inset 0 -5px 45px rgba(100, 100, 100, 0.2), 0 1px 1px rgba(255, 255, 255, 0.2); - -webkit-transition: box-shadow .5s ease; - -moz-transition: box-shadow .5s ease; - -o-transition: box-shadow .5s ease; - -ms-transition: box-shadow .5s ease; - transition: box-shadow .5s ease; -} - -.login-input:focus { - box-shadow: inset 0 -5px 45px rgba(100, 100, 100, 0.4), 0 1px 1px rgba(255, 255, 255, 0.2); -} diff --git a/src/components/Logo/index.js b/src/components/Logo/index.js deleted file mode 100644 index 0b98cf7..0000000 --- a/src/components/Logo/index.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import globalConfig from 'config.js'; -import './index.less'; - -/** - * 定义Logo组件 - */ -class Logo extends React.Component { - - render() { - // TODO: 这个logo的样式还是要优化下 - return ( -
-
- {globalConfig.name} -
-
- ); - } - -} - -export default Logo; diff --git a/src/components/Logo/index.less b/src/components/Logo/index.less deleted file mode 100644 index bedb839..0000000 --- a/src/components/Logo/index.less +++ /dev/null @@ -1,6 +0,0 @@ -.logo-text { - text-align: center; - font-family: "Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,SimSun,sans-serif"; - font-size: 22px; - color: #2db7f5; -} diff --git a/src/components/Sidebar/index.js b/src/components/Sidebar/index.js index 732775f..0012afa 100644 --- a/src/components/Sidebar/index.js +++ b/src/components/Sidebar/index.js @@ -1,8 +1,7 @@ import React from 'react' import { Link } from 'react-router' import { Menu, Icon } from 'antd' -import Logo from '../Logo' -import Logger from '../../utils/Logger' +import Logger from '../../utils/logger' import items from 'menu.js' const SubMenu = Menu.SubMenu @@ -47,7 +46,6 @@ const Sider = React.createClass({ }, transFormMenuItem(obj, paths) { const parentPath = paths.join('/') - logger.debug('transform %o to path %s', obj, parentPath) return ( {obj.child ? obj.name : {obj.name} } diff --git a/src/components/Welcome/index.js b/src/components/Welcome/index.js deleted file mode 100644 index 441df94..0000000 --- a/src/components/Welcome/index.js +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import './index.less'; - -/** - * 展示欢迎界面 - */ -class Welcome extends React.Component { - - render() { - return ( -
- Hello,React -
- ); - } - -} - -export default Welcome; diff --git a/src/components/Welcome/index.less b/src/components/Welcome/index.less deleted file mode 100644 index 998c32f..0000000 --- a/src/components/Welcome/index.less +++ /dev/null @@ -1,3 +0,0 @@ -.welcome { - color: red; -} diff --git a/src/config.js b/src/config.js index 2258764..b38f513 100644 --- a/src/config.js +++ b/src/config.js @@ -1,31 +1,23 @@ -/** - * 定义整个项目的全局配置 - */ - -// 约定优于配置 -// 我可以提供尽量多的配置, 但尽量不要太个性化, 接口的路径/名称/格式之类的 -// 遵循统一的规范, 好维护, 交给其他人也比较简单 - module.exports = { name: 'Ant Design 管理后台', footer: 'Ant Design Admin 版权所有 © 2016 由zuiidea支持', logoImg:'https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg', logoText:'Ant Design', - debug: true, // 是否开启debug模式, 不会请求后端接口, 使用mock的数据 - logLevel: 'debug', // 日志级别, 目前支持debug/info/warn/error 4种级别 + debug: true, + logLevel: 'debug', needLogin:false, api: { - host: 'http://localhost', // 调用ajax接口的地址, 默认值空, 如果是跨域的, 服务端要支持CORS - path: '/api', // ajax请求的路径 - timeout: 15000, // 请求的超时时间, 单位毫秒 + host: 'http://localhost', + path: '/api', + timeout: 15000, }, - login: { // 登录相关配置 - getCurrentUser: '/getCurrentUser', // 后端必须要提供接口校验当前用户的身份, 如果拿不到用户信息, 才会尝试登录 - sso: '', // 是否使用单点登录? 是的话我会把地址encode后加到后面, 然后跳转, 如果这个是空字符串, 说明不使用单点登录 - validate: '/login', // 校验用户信息, 表单的submit地址. 如果登录成功, 必须返回用户名 - logout: '/logout', // 退出的url, 用户点击退出时, 浏览器会直接跳转到这个链接 + login: { + getCurrentUser: '/getCurrentUser', + sso: '', + validate: '/login', + logout: '/logout', }, /** @@ -106,5 +98,4 @@ module.exports = { this.tmpApiPath = tmpApiPath; return tmpApiPath; }, - }; diff --git a/src/index.html b/src/index.html index 7bcdea0..e857d8f 100644 --- a/src/index.html +++ b/src/index.html @@ -8,7 +8,7 @@ -
+
diff --git a/src/index.js b/src/index.js index e11d3eb..2e10ce5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,56 +1,24 @@ -/** - * 程序的入口, 类似java中的main - */ - import React from 'react' import ReactDOM from 'react-dom' import {Router, Route, IndexRoute, hashHistory} from 'react-router' -import './utils/index.js' // 引入各种prototype辅助方法 +import './utils/index.js' -// 开始引入各种自定义的组件 import App from './view/index' import SignIn from './view/user/signIn' import List from './view/data/list' -import Welcome from './components/Welcome' import Error from './components/Error' import Hello from './components/Hello' -import DBTable from './components/DBTable' const routes = ( - + - - - - - - - - - - - - - - - - - - - - - - - - - ) -ReactDOM.render(routes, document.getElementById('root')) +ReactDOM.render(routes, document.getElementById('view')) diff --git a/src/menu.js b/src/menu.js index 1985d35..b9d3937 100644 --- a/src/menu.js +++ b/src/menu.js @@ -1,143 +1,113 @@ -/** - * 定义sidebar中的菜单项 - * - * 一些约定: - * 1.菜单最多3层; - * 2.只有顶层的菜单可以带图标; - * 3.只有"叶子"节点才能跳转; - * 4.所有的key都不能重复; - */ - module.exports = [ { key: 'index', - name: '菜单一', + name: '数据相关', icon: 'user', child: [ { key: 'list', name: '数据列表', }, - { - key: 'option1', - name: '模拟CRUD', - }, - { - key: 'option2', - name: '短信表管理', - }, - { - key: 'option3', - name: '选项3', - }, ], }, { - key: 'daohang', - name: '菜单二', + key: 'navigation', + name: '测试导航', icon: 'setting', child: [ { - key: '555', - name: '选项5', + key: 'navigation1', + name: '二级导航1', }, { - key: 'sanji', // 最多只能到三级导航 - name: '三级导航', + key: 'navigation2', + name: '二级导航2', child: [ { - key: '666', - name: '选项6', + key: 'navigation21', + name: '三级导航1', }, { - key: '777', - name: '选项7', - }, - { - key: '888', - name: '选项8', - }, - { - key: '999', - name: '选项9', - }, - ], - }, - ], - }, - { - key: 'test', - name: '菜单三', - icon: 'laptop', - child: [ - { - key: 'aaa', - name: '选项a', - }, - { - key: 'bbb', - name: '选项b', - }, - { - key: 'ccc', - name: '选项c', - }, - { - key: 'sanjiaaa', // 最多只能到三级导航 - name: '三级导航aaa', - child: [ - { - key: '666aa', - name: '选项6', - }, - ], - }, - { - key: 'sanjibbb', // 最多只能到三级导航 - name: '三级导航bbb', - child: [ - { - key: '666bb', - name: '选项6', - }, - ], - }, - ], - }, - { - key: 'menu4', - name: '菜单四', - icon: 'notification', - child: [ - { - key: 'menu4', - name: '菜单四一', - }, - ], - }, - { - key: 'menu5', - name: '菜单五', - icon: 'folder', - child: [ - { - key: 'menu51', - name: '菜单五一', - }, - { - key: 'menu52', - name: '菜单五二', - }, - { - key: 'menu53', - name: '菜单五三', - child: [ - { - key: 'menu531', - name: '菜单五三一', + key: 'navigation22', + name: '三级导航2', }, ], }, ], }, + // { + // key: 'test', + // name: '菜单三', + // icon: 'laptop', + // child: [ + // { + // key: 'aaa', + // name: '选项a', + // }, + // { + // key: 'bbb', + // name: '选项b', + // }, + // { + // key: 'ccc', + // name: '选项c', + // }, + // { + // key: 'sanjiaaa', // 最多只能到三级导航 + // name: '三级导航aaa', + // child: [ + // { + // key: '666aa', + // name: '选项6', + // }, + // ], + // }, + // { + // key: 'sanjibbb', // 最多只能到三级导航 + // name: '三级导航bbb', + // child: [ + // { + // key: '666bb', + // name: '选项6', + // }, + // ], + // }, + // ], + // }, + // { + // key: 'menu4', + // name: '菜单四', + // icon: 'notification', + // child: [ + // { + // key: 'menu4', + // name: '菜单四一', + // }, + // ], + // }, + // { + // key: 'menu5', + // name: '菜单五', + // icon: 'folder', + // child: [ + // { + // key: 'menu51', + // name: '菜单五一', + // }, + // { + // key: 'menu52', + // name: '菜单五二', + // }, + // { + // key: 'menu53', + // name: '菜单五三', + // child: [ + // { + // key: 'menu531', + // name: '菜单五三一', + // }, + // ], + // }, + // ], + // }, ]; diff --git a/src/schema/test.config.js b/src/schema/test.config.js deleted file mode 100644 index 9604054..0000000 --- a/src/schema/test.config.js +++ /dev/null @@ -1,8 +0,0 @@ -// 定义某个表渲染时的配置 -// 如果不需要个性化, 可以不要这个文件, 所有配置项都会使用默认值 -// 注意! config/dataSchema/querySchema文件的命名应该遵循CamelCase原则, 不要包含下划线/中划线 - -module.exports = { - showExport: false, // 显示导出按钮, 默认true - showImport: false, // 显示导入按钮, 默认true -} diff --git a/src/schema/test.dataSchema.js b/src/schema/test.dataSchema.js deleted file mode 100644 index ede4395..0000000 --- a/src/schema/test.dataSchema.js +++ /dev/null @@ -1,48 +0,0 @@ -// 定义某个表的dataSchema, 结构跟querySchema很相似, 见下面的例子 -// 注意: 所有的key不能重复 - -module.exports = [ - { - key: 'id', // 传递给后端的key - title: 'ID', // 前端显示的名字 - dataType: 'int', // 数据类型, 目前可用的: int/float/varchar/datetime - - // 这一列是否是主键? - // 如果不指定主键, 不能update/delete, 但可以insert - // 如果指定了主键, insert/update时不能填写主键的值; - // 如果是批量update/delete, 会把主键的一个数组传给后端 - // 只有int/varchar可以作为主键 - primary: true, - - // showType目前还不支持, 其实dataSchema和querySchema一样, 都是有showType属性的 - // 以后有空加上 - // showType: checkbox/radio - - // 扩展接口, 决定了这一列渲染成什么样子 - render: (text, record) => text, - }, - - { - key: 'name', - title: '用户名', - dataType: 'varchar', - }, - { - key: 'score', - title: '分数', - dataType: 'int', - }, - { - key: 'gpa', - title: 'GPA', - dataType: 'float', - }, - { - key: 'birthday', - title: '生日', - // 对于日期类型要注意下, 在js端日期被表示为yyyy-MM-dd HH:mm:ss的字符串, 在java端日期被表示为java.util.Date对象 - // fastjson反序列化时可以自动识别 - // 序列化倒是不用特别配置, 看自己需求, fastjson会序列化为一个字符串, 前端原样展示 - dataType: 'datetime', - }, -]; diff --git a/src/schema/test.querySchema.js b/src/schema/test.querySchema.js deleted file mode 100644 index ec7d72e..0000000 --- a/src/schema/test.querySchema.js +++ /dev/null @@ -1,101 +0,0 @@ -// 定义某个表的querySchema -// schema的结构和含义参考下面的例子 -// 注意: 所有的key不能重复 - -module.exports = [ - { - key: 'name', // 传递给后端的字段名 - title: '用户名', // 前端显示的名称 - placeholder: '请输入用户名', // 提示语, 可选 - - // 数据类型, 前端会根据数据类型展示不同的输入框 - // 目前可用的dataType: int/float/varchar/datetime - dataType: 'varchar', - - // 显示类型, 一些可枚举的字段, 比如type, 可以被显示为单选框或下拉框 - // 默认显示类型是normal, 就是一个普通的输入框, 这时可以省略showType字段 - // 目前可用的showType: normal/select/radio/between - // select和radio只能用于int和varchar - // between只能用于int/float/datetime, 会显示2个输入框, 用于范围查询 - showType: 'normal', - - // 有一点要注意, 就算showType是normal, 也不意味是等值查询, 只是说传递给后台的是单独的一个值 - // 至于后台用这个值做等值/大于/小于/like, 前端不关心 - }, - - { - key: 'age', - title: '年龄', - placeholder: '请输入年龄', - dataType: 'int', - }, - { - key: 'weight', - title: '体重(kg)', - dataType: 'float', // 小数会统一保留2位 - }, - { - key: 'type', - title: '类型', - dataType: 'int', - showType: 'select', // 下拉框选择, dataType可以是int或varchar, 但必须和options中的key类型一致 - options: [{key: 1, value: '类型1'}, {key: 2, value: '类型2'}], - }, - { - key: 'userType', - title: '用户类型', - dataType: 'varchar', // 这个数据类型要和options中key的类型一致 - showType: 'radio', // 单选框, 和下拉框schema是一样的, 只是显示时有差别 - options: [{key: 'typeA', value: '类型A'}, {key: 'typeB', value: '类型B'}], - }, - { - key: 'score', - title: '分数', - dataType: 'int', - showType: 'between', // 整数范围查询, 对于范围查询, 会自动生成xxBegin/xxEnd两个key传递给后端 - }, - { - key: 'gpa', - title: 'GPA', - dataType: 'float', - showType: 'between', // 小数也可以范围查询, 固定两位小数 - placeholderBegin: '哈哈', // 对于范围查询, 可以定义placeholderBegin和placeholderBegin, 用于两个框的提示语 - placeholderEnd: '切克闹', // 如果不定义, 对于int/float的范围查询, 提示语是"最小值"/"最大值", 对于日期的范围查询, 提示语是"开始日期"/"结束日期" - }, - { - key: 'height', - title: '身高(cm)', - dataType: 'float', // 小数等值查询 - }, - { - key: 'duoxuan1', - title: '多选1', - dataType: 'int', - showType: 'checkbox', // checkbox - options: [{key: 1, value: '类型1'}, {key: 2, value: '类型2'}], - }, - { - key: 'duoxuan2', - title: '多选2', - dataType: 'varchar', - showType: 'multiSelect', // 另一种多选 - options: [{key: 'type1', value: '类型1'}, {key: 'type2', value: '类型2'}], - }, - { - key: 'primarySchool', - title: '入学日期', - dataType: 'datetime', // 日期范围查询, 日期范围查询占用的显示空间会很大, 注意排版 - showType: 'between', - }, - { - key: 'birthday', - title: '出生日期', - dataType: 'datetime', - showType: 'between', - }, - { - key: 'xxbirthday', - title: 'XX日期', - dataType: 'datetime', // 日期等值查询 - }, -]; diff --git a/src/schema/testSms.config.js b/src/schema/testSms.config.js deleted file mode 100644 index 3da4109..0000000 --- a/src/schema/testSms.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - showExport: false, // 显示导出按钮, 默认true - showImport: false, // 显示导入按钮, 默认true -} diff --git a/src/schema/testSms.dataSchema.js b/src/schema/testSms.dataSchema.js deleted file mode 100644 index e1ddd7b..0000000 --- a/src/schema/testSms.dataSchema.js +++ /dev/null @@ -1,34 +0,0 @@ -module.exports = [ - { - key: 'id', - title: 'ID', - dataType: 'int', - primary: true, - }, - { - key: 'content', - title: '内容', - dataType: 'varchar', - }, - { - key: 'phoneModel', - title: '手机型号', - dataType: 'varchar', - }, - { - key: 'experience', - title: '使用经验', - dataType: 'varchar', - }, - { - key: 'frequency', - title: '使用频率', - dataType: 'varchar', - }, - { - key: 'isNative', - title: '是否母语', - dataType: 'varchar', - placeholder: '字符串yes/no', - }, -]; diff --git a/src/schema/testSms.querySchema.js b/src/schema/testSms.querySchema.js deleted file mode 100644 index 7b1ae0a..0000000 --- a/src/schema/testSms.querySchema.js +++ /dev/null @@ -1,38 +0,0 @@ -// 定义某个表的querySchema -// schema的结构和含义参考下面的例子 -// 注意: 所有的key不能重复 - -module.exports = [ - { - key: 'id', // 传递给后端的字段名 - title: 'ID', // 前端显示的名称 - dataType: 'int', - }, - { - key: 'content', - title: '内容', - dataType: 'varchar', - }, - { - key: 'phoneModel', - title: '手机型号', - dataType: 'varchar', - }, - { - key: 'experience', - title: '使用经验', - dataType: 'varchar', - }, - { - key: 'frequency', - title: '使用频率', - dataType: 'varchar', - }, - { - key: 'isNative', - title: '是否母语', - dataType: 'varchar', - showType: 'radio', - options: [{key: 'yes', value: '是'}, {key: 'no', value: '否'}], - }, -]; diff --git a/src/utils/Logger.js b/src/utils/Logger.js index ce26a49..87d180c 100644 --- a/src/utils/Logger.js +++ b/src/utils/Logger.js @@ -1,168 +1,107 @@ -import globalConfig from '../config'; +import config from '../config' -/** - * 日志工具类
- * - *

- * 工程变大之后, 日志就很重要了, 总不能一直console.log. - * 本来想看看有没有现成的工具, 找到一个log4js的库, 但只能用在node环境下, 浏览器环境下似乎没什么好用的. - * 作为一个用惯了slf4j的人, 干脆自己写个吧, 练练手. - * 目前有以下功能: - *

    - *
  • 支持常用的日志级别: debug/info/warn/error, 说实话其他的级别极少用到
  • - *
  • 支持变量替换: 类似slf4j中的logger.info("a={}",a)这种, 其实console本身已经支持的
  • - *
  • 根据日志级别设置样式: debug是黑色, info是默认, warn是黄色, error是红色, 看起来清晰很多
  • - *
  • 定义每个logger的名字: 也算是常规功能了吧
  • - *
- * 我是尽量按着slf4j的习惯来设计的, 目前还比较简单. - * 不支持pattern/appender之类的, 但对于二手前端来说, 也算够用了. - *

- */ class Logger { - // 定义一些预设的日志级别 - // 目前只有4种级别 + static LOG_LEVEL_DEBUG = 1 + static LOG_LEVEL_INFO = 2 + static LOG_LEVEL_WARN = 3 + static LOG_LEVEL_ERROR = 4 - static LOG_LEVEL_DEBUG = 1; - static LOG_LEVEL_INFO = 2; - static LOG_LEVEL_WARN = 3; - static LOG_LEVEL_ERROR = 4; + static loggerMap = new Map() - /*暂存所有logger*/ - static loggerMap = new Map(); + static defaultLogger = new Logger() - /*默认的logger*/ - static defaultLogger = new Logger(); - - /** - * 获取一个Logger实例 - * - * @param name - * @returns {*} - */ static getLogger(name) { if (name && name !== '') { - // 缓存 if (Logger.loggerMap.has(name)) { - return Logger.loggerMap.get(name); + return Logger.loggerMap.get(name) } - - const logger = new Logger(name); - Logger.loggerMap.set(name, logger); - return logger; + const logger = new Logger(name) + Logger.loggerMap.set(name, logger) + return logger } else { - return Logger.defaultLogger; + return Logger.defaultLogger } } constructor(name) { - this.name = name; // logger的名字 - const configLogLevel = globalConfig.logLevel; + this.name = name + const configLogLevel = config.logLevel if (configLogLevel === 'debug') { - this.logLevel = Logger.LOG_LEVEL_DEBUG; + this.logLevel = Logger.LOG_LEVEL_DEBUG } else if (configLogLevel === 'info') { - this.logLevel = Logger.LOG_LEVEL_INFO; + this.logLevel = Logger.LOG_LEVEL_INFO } else if (configLogLevel === 'warn') { - this.logLevel = Logger.LOG_LEVEL_WARN; + this.logLevel = Logger.LOG_LEVEL_WARN } else if (configLogLevel === 'error') { - this.logLevel = Logger.LOG_LEVEL_ERROR; + this.logLevel = Logger.LOG_LEVEL_ERROR } else { - // 默认都是info级别 - this.error('unsupported logLevel: %s, use INFO instead', configLogLevel); - this.logLevel = Logger.LOG_LEVEL_INFO; + this.error('unsupported logLevel: %s, use INFO instead', configLogLevel) + this.logLevel = Logger.LOG_LEVEL_INFO } } - /** - * 设置日志级别, 只有4种级别可选 - * - * @param newLogLevel 1~4之间的一个数字 - */ setLogLevel(newLogLevel) { if (isNaN(newLogLevel)) { - this.error('setLogLevel error, not a number: %s', newLogLevel); + this.error('setLogLevel error, not a number: %s', newLogLevel) } - if (newLogLevel < 1 || newLogLevel > 4) { - this.error('setLogLevel error, input = %s, must between 1 and 4', newLogLevel); + this.error('setLogLevel error, input = %s, must between 1 and 4', newLogLevel) } - - this.logLevel = newLogLevel; + this.logLevel = newLogLevel } - /** - * 打印info日志 - * - * @param pattern 日志格式, 支持%d/%s等占位符 - * @param args 可变参数, 用于替换pattern中的占位符 - */ info(pattern, ...args) { - // 先判断日志级别 - if (this.logLevel > Logger.LOG_LEVEL_INFO) - return; - - if (this.name) - args.unshift(`${this.name}: ${pattern}`); - else - args.unshift(pattern); - console.log.apply(console, args); + if (this.logLevel > Logger.LOG_LEVEL_INFO) { + return + } + if (this.name) { + args.unshift(`${this.name}: ${pattern}`) + } else { + args.unshift(pattern) + console.log.apply(console, args) + } } - /** - * 打印error日志 - * - * @param pattern - * @param args - */ error(pattern, ...args) { - if (this.logLevel > Logger.LOG_LEVEL_ERROR) - return; - - args.unshift('background: red; color: #bada55;'); - if (this.name) - args.unshift(`%c${this.name}: ${pattern}`); - else - args.unshift(`%c${pattern}`); - console.error.apply(console, args); + if (this.logLevel > Logger.LOG_LEVEL_ERROR) { + return + } + args.unshift('background: red color: #bada55') + if (this.name) { + args.unshift(`%c${this.name}: ${pattern}`) + } else { + args.unshift(`%c${pattern}`) + console.error.apply(console, args) + } } - /** - * 打印debug日志 - * - * @param pattern - * @param args - */ debug(pattern, ...args) { - if (this.logLevel > Logger.LOG_LEVEL_DEBUG) - return; - - args.unshift('background: black; color: #bada55;'); - if (this.name) - args.unshift(`%c${this.name}: ${pattern}`); - else - args.unshift(`%c${pattern}`); - console.debug.apply(console, args); - + if (this.logLevel > Logger.LOG_LEVEL_DEBUG) { + return + } + args.unshift('background: black color: #bada55') + if (this.name) { + args.unshift(`%c${this.name}: ${pattern}`) + } else { + args.unshift(`%c${pattern}`) + console.debug.apply(console, args) + } } - /** - * 打印warn日志 - * - * @param pattern - * @param args - */ warn(pattern, ...args) { - if (this.logLevel > Logger.LOG_LEVEL_WARN) - return; - - args.unshift('background: yellow; color: black;'); - if (this.name) - args.unshift(`%c${this.name}: ${pattern}`); - else - args.unshift(`%c${pattern}`); - console.warn.apply(console, args); + if (this.logLevel > Logger.LOG_LEVEL_WARN) { + return + } + args.unshift('background: yellow color: black') + if (this.name) { + args.unshift(`%c${this.name}: ${pattern}`) + } else { + args.unshift(`%c${pattern}`) + console.warn.apply(console, args) + } } } -export default Logger; +export default Logger diff --git a/src/utils/ajax.js b/src/utils/ajax.js deleted file mode 100644 index 6113452..0000000 --- a/src/utils/ajax.js +++ /dev/null @@ -1,227 +0,0 @@ -/** - * 对superagent api的一个mock, 用于debug - */ - -import Logger from './Logger'; -import superagent from 'superagent'; -import globalConfig from '../config'; - -// superagent的文档: http://visionmedia.github.io/superagent/#error-handling -// 常用的api形式: 只要mock这两种就可以了 -// ajax.get(url).end(func(err,res)) -// ajax.post(url).send(obj).end(func(err,res)) - -const logger = Logger.getLogger('mockAjax'); - -let currentUrl; // 一个临时变量, 当前正在请求的url -const error = {}; // 理论上服务端不会返回error, 都是HTTP200 -const result = { // 暂存mock的ajax返回, 总共有5个字段 - success: true, - code: 0, - message: 'just a mock ;) ', - total: 10000, - data: {}, -}; - -const successResult = (data) => { - result.success = true; - result.data = data; - result.code = 0; - result.message = ''; - result.total = 10000; -}; - -const errorResult = (code, message) => { - result.success = false; - result.data = {}; - result.code = code; - result.message = message; - result.total = 0; -}; - -// 从url中取出表名 -const getTableNameFromUrl = (url) => { - // url的格式是apiPath/{tableName}/[insert|update|delete] - const apiPath = globalConfig.getAPIPath(); - let begin = apiPath.length + 1; - while (url.charAt(begin) === '/') { - begin++; - } - let end = begin; - while (url.charAt(end) !== '/') { - end++; - } - - const tableName = url.substring(begin, end); - logger.debug('getTableNameFromUrl: begin=%d, end=%d, tableName=%s', begin, end, tableName); - - return tableName; -}; - -// 根据某个表的dataSchema创造mock数据 -const mockResult = (tableName, queryObj) => { - logger.debug('begin to mock data for table %s', tableName); - - // 尝试加载schema文件 - let schema; - try { - schema = require(`../schema/${tableName}.dataSchema.js`); - } catch (e) { - logger.error('can not find dataSchema file for table %s', tableName); - logger.error('%o', e); // 这个方式有点不爽...但是如果不设置pattern, 就不能用样式了, 纠结... - // 设置返回结果为失败 - errorResult(100, `can not find dataSchema file for table ${tableName}`); - - return; - } - - // 一般来说, 传入的查询条件都是肯定会有page/pageSize的, 以防万一 - if (!queryObj.page) { - queryObj.page = 1; - } - if (!queryObj.pageSize) { - queryObj.pageSize = 50; - } - - const tmp = []; - for (let i = 0; i < queryObj.pageSize; i++) { - const record = {}; - // 为了让mock的数据有些区别, 把page算进去 - schema.forEach((column)=> { - if (column.dataType === 'int') { - record[column.key] = 10000 * queryObj.page + i; - } else if (column.dataType === 'float') { - record[column.key] = 1.0 * queryObj.page + i * 0.01; - } else if (column.dataType === 'varchar') { - record[column.key] = `page=${queryObj.page} num=${i}`; - } else if (column.dataType === 'datetime') { - record[column.key] = new Date().plusDays(i).format('yyyy-MM-dd HH:mm:ss'); - } else { - logger.error('unsupported dataType %s', column.dataType); - } - }); - tmp.push(record); - } - - successResult(tmp); -}; - -// 开始mock supergent的各个方法 - -const end = (func) => { - // 模拟一个延时 - // 总是感觉setTimeout有点low啊, 不知道有没有更好的办法 - setTimeout(func, 1500, null, {body: result}); // 延时执行func函数, 参数跟在最后 -}; - -const get = (url) => { - logger.debug('get url %s', url); - currentUrl = url; - - // TODO: 要根据url返回不同的数据, 这里设置各种get的url - if (url === `${globalConfig.getAPIPath()}${globalConfig.login.getCurrentUser}`) - successResult('guest'); // debug模式下都是guest用户 - else - errorResult(200, `unsupported url ${url}`); - - return {end}; -}; - -const type = (str) => { - logger.debug('set type = %s', str); - return {send}; -}; - -const send = (obj) => { - logger.debug('send obj: %o', obj); - - // TODO: 要根据url返回不同的数据, 这里设置各种post的url - if (currentUrl === `${globalConfig.getAPIPath()}${globalConfig.login.validate}`) { - // 验证登录的用户名和密码 - if (obj.username === 'guest' && obj.password === 'guest') { - successResult('guest'); - } else { - errorResult(300, 'error username or password'); - } - } else { - // 如果不是登录请求, 目前只能是CRUD请求, 当然以后可能会扩展 - const tableName = getTableNameFromUrl(currentUrl); - if (tableName) { - // 要根据增删改查的情况分别模拟 - if (currentUrl.indexOf('select') > 0) { - mockResult(tableName, obj); - } else if (currentUrl.indexOf('insert') > 0) { - mockResult(tableName, obj); - const tmp = result.data[0]; - successResult(tmp); - } else if (currentUrl.indexOf('update') > 0) { - const tmp = currentUrl.indexOf('keys='); - successResult(currentUrl.substring(tmp).split(',').length); - } else { - errorResult(400, `unsupported url ${currentUrl}`); - } - } else { - errorResult(400, `unsupported url ${currentUrl}`); - } - } - - return {end}; -}; - -const post = (url) => { - logger.debug('post url %s', url); - currentUrl = url; - - // TODO: 要根据url返回不同的数据, 这里设置各种post的url - if (currentUrl.indexOf('delete') > 0) { - const tmp = currentUrl.indexOf('keys='); - successResult(currentUrl.substring(tmp).split(',').length); - } - - return {send, type, end}; -}; - -// 一个辅助的方法, superagent判断返回是否成功太麻烦了啊...要判断3个条件 -const isSuccess = (res) => { - if (res && res.body && res.body.success) { - return true; - } else { - return false; - } -}; - -// mock的版本只提供简单的API, 以后可能要扩展 -const mockAjax = {get, post, isSuccess}; - -// 对superagent的API简单包装下 -const realAjax = { - get: function (url) { - return this.innerConfig(superagent.get(url)); - }, - - post: function (url) { - return this.innerConfig(superagent.post(url)); - }, - - // 一些全局的配置 - innerConfig: function (req) { - let tmp = req; - // 是否是跨域请求 - if (globalConfig.isCrossDomain()) { - tmp = tmp.withCredentials(); - } - // 设置全局的超时时间 - if (globalConfig.api.timeout && !isNaN(globalConfig.api.timeout)) { - tmp = tmp.timeout(globalConfig.api.timeout); - } - return tmp; - }, - - isSuccess, -}; - -if (globalConfig.debug === true) { - module.exports = mockAjax; -} else { - module.exports = realAjax; -} diff --git a/src/utils/lib.js b/src/utils/lib.js index 1909d2c..d4a357d 100644 --- a/src/utils/lib.js +++ b/src/utils/lib.js @@ -1,7 +1,18 @@ import config from 'config' -import ajax from './ajax' -import Logger from './Logger' -import 'whatwg-fetch' +import Logger from './logger' +import reqwest from 'reqwest' import './lib.less' -module.exports = {ajax,config,Logger} +function ajax(data){ + if(config.debug){ + console.log(data); + }else { + reqwest(data) + } +} + +module.exports = { + config, + ajax, + Logger +} diff --git a/src/view/data/list.jsx b/src/view/data/list.jsx index 2dab110..89ab7f7 100644 --- a/src/view/data/list.jsx +++ b/src/view/data/list.jsx @@ -18,6 +18,7 @@ let List = React.createClass({ showTotal: total => `共 ${total} 条` }, data:{}, + params:{}, loading: false, }; }, @@ -27,23 +28,19 @@ let List = React.createClass({ fetch() { const _this = this this.setState({loading: true}) - - fetch('https://randomuser.me/api/') - .then(function(response) { - console.log(response); + ajax({ + url: 'https://randomuser.me/api/', + data: { + ..._this.state.params + }, + success: function (result) { + console.log(result); + const pager = _this.state.pager + const params = _this.state.params + pager.total = result.total + _this.setState({loading: false, data: result.talentList, pager, params}) + } }) - // Ajax({ - // url: contextPath + '/talent/index', - // data: { - // ..._this.state.params - // }, - // success: function (result) { - // const pager = _this.state.pager - // const params = _this.state.params - // pager.total = result.total - // _this.setState({loading: false, data: result.talentList, pager, params}) - // } - // }) }, render() { return (