From 2850799963a5bff04aeeb9eb73dc4eb91a0dc165 Mon Sep 17 00:00:00 2001 From: king <18310653075@163.com> Date: 星期日, 18 七月 2021 00:54:37 +0800 Subject: [PATCH] 2021-07-18 --- src/views/interface/header/index.scss | 63 + src/views/interface/header/index.jsx | 45 + src/views/design/header/index.scss | 7 src/views/appmanage/index.scss | 19 src/views/design/header/index.jsx | 7 src/views/interface/workspace/request/index.jsx | 300 +++++++ src/views/appmanage/scriptform/index.scss | 12 src/views/interface/workspace/request/index.scss | 141 +++ src/views/interface/api/index.js | 175 ++++ src/views/interface/history/index.scss | 113 ++ src/router/index.js | 18 src/views/interface/index.scss | 6 src/views/appmanage/scriptform/index.jsx | 241 ++++++ src/views/interface/workspace/editTable/index.jsx | 255 ++++++ src/views/interface/workspace/editTable/index.scss | 85 ++ src/views/interface/workspace/index.scss | 52 + src/views/interface/workspace/index.jsx | 115 +++ src/views/appmanage/index.jsx | 332 +++++++- src/views/interface/index.jsx | 36 src/views/interface/history/index.jsx | 246 ++++++ 20 files changed, 2,212 insertions(+), 56 deletions(-) diff --git a/src/router/index.js b/src/router/index.js index 8f61de0..907d84e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -18,6 +18,7 @@ const MenuDesign = asyncLoadComponent(() => import('@/views/menudesign')) const BillPrint = asyncLoadComponent(() => import('@/views/billprint')) const PrintT = asyncLoadComponent(() => import('@/views/printTemplate')) +const Interface = asyncLoadComponent(() => import('@/views/interface')) const routers = [ {path: '/login', name: 'login', component: Login, auth: false}, @@ -31,7 +32,8 @@ {path: '/mobdesign/:param', name: 'mobdesign', component: MobDesign, auth: true}, {path: '/menudesign/:param', name: 'menudesign', component: MenuDesign, auth: true}, {path: '/billprint/:param', name: 'billprint', component: BillPrint, auth: true}, - {path: '/paramsmain/:param', name: 'pmain', component: Main, auth: true} + {path: '/paramsmain/:param', name: 'pmain', component: Main, auth: true}, + {path: '/interface', name: 'interface', component: Interface, auth: true} ] export default class RouteConfig extends Component { @@ -105,15 +107,11 @@ return ( <HashRouter> <Switch> - { - routers.map((item, index) => { - return ( - <Route key={index} path={item.path} name={item.name} exact render={ props => { - return this.controlRoute(item, props) - }}/> - ) - }) - } + {routers.map((item, index) => { + return ( + <Route key={index} path={item.path} name={item.name} exact render={ props => this.controlRoute(item, props)}/> + ) + })} <Redirect exact from="/" to="login"/> <Route component= {NotFound}/> </Switch> diff --git a/src/views/appmanage/index.jsx b/src/views/appmanage/index.jsx index aef6542..463e01d 100644 --- a/src/views/appmanage/index.jsx +++ b/src/views/appmanage/index.jsx @@ -19,6 +19,7 @@ const Header = asyncComponent(() => import('@/mob/header')) const MutilForm = asyncComponent(() => import('./mutilform')) const TransForm = asyncComponent(() => import('./transform')) +const ScriptForm = asyncComponent(() => import('./scriptform')) const SubMutilForm = asyncComponent(() => import('./submutilform')) let base_url = '' @@ -81,9 +82,10 @@ title: '鎿嶄綔', key: 'action', align: 'center', + width: '230px', render: (text, record) => ( - <div> - <Button type="link" onClick={() => this.setState({ selectTran: record, transVisible: 'edit' })} style={{color: '#8E44AD'}}>淇敼</Button> + <div onClick={() => this.forbid = true}> + <Button type="link" onClick={() => this.setState({ editTran: record, transVisible: 'edit' })} style={{color: '#8E44AD'}}>淇敼</Button> <Button type="link" onClick={() => this.deleteTran(record)} style={{color: '#ff4d4f'}}>鍒犻櫎</Button> <Button type="link" onClick={() => this.enableTran(record)} style={{color: '#26C281'}}>鍚敤</Button> </div> @@ -94,9 +96,23 @@ translist: [], tranSearchKey: '', selectTran: null, + editTran: null, transIndex: 1, - transTotal: 0 + transTotal: 0, + scriptcolumns: [ + { title: '鍏抽敭瀛�', dataIndex: 'KeyWords', key: 'KeyWords', align: 'left' }, + { title: '鎻忚堪', dataIndex: 'Remark', key: 'Remark', align: 'left' }, + { title: '绫诲瀷', dataIndex: 'TypeName', key: 'TypeName', align: 'left' }, + { title: '鎺掑簭', dataIndex: 'Sort', key: 'Sort', align: 'left' }, + ], + scriptVisible: false, + scriptlist: [], + scriptSearchKey: '', + scriptIndex: 1, + scriptTotal: 0, } + + forbid = false UNSAFE_componentWillMount() { document.body.className = '' @@ -138,7 +154,10 @@ if (result.status) { this.setState({ loading: false, - translist: result.data + translist: result.data, + selectTran: null, + scriptlist: [], + transTotal: result.total }) } else { this.setState({ @@ -153,6 +172,72 @@ }) } + getScriptList = () => { + const { scriptSearchKey, scriptIndex, selectTran } = this.state + + if (!selectTran || !selectTran.ID) { + notification.warning({ + top: 92, + message: '缂哄皯浼犺緭鍙稩D!', + duration: 3 + }) + return + } + + let param = { + func: 's_get_sVersionDetail', + dataM: 'Y', + PageSize: 10, + PageIndex: scriptIndex, + OrderCol: 'Sort desc', + BID: selectTran.ID, + } + + if (scriptSearchKey) { + param.TypeName = scriptSearchKey + param.KeyWords = scriptSearchKey + param.Remark = scriptSearchKey + } + + this.setState({ + loading: true + }) + + Api.getCloudConfig(param).then(result => { + if (result.status) { + this.setState({ + loading: false, + scriptlist: result.data, + scriptTotal: result.total, + selectScriptKeys: [] + }) + } else { + this.setState({ + loading: false + }) + notification.warning({ + top: 92, + message: result.message, + duration: 5 + }) + } + }) + } + + scriptSearch = (value) => { + this.setState({scriptSearchKey: value, scriptIndex: 1}, () => { + this.getScriptList() + }) + } + + changeScriptTable = (pagination) => { + this.setState({ + scriptIndex: pagination.current + }, () => { + this.getScriptList() + }) + } + changeTable = (pagination) => { this.setState({ transIndex: pagination.current @@ -162,13 +247,13 @@ } tranSearch = (value) => { - this.setState({tranSearchKey: value}, () => { + this.setState({tranSearchKey: value, transIndex: 1}, () => { this.getTransList() }) } submitTrans = () => { - const { transVisible, selectTran } = this.state + const { transVisible, editTran } = this.state this.transRef.handleConfirm().then(res => { this.setState({ @@ -184,7 +269,7 @@ } else { param.func = 's_sVersion_upt' param.ProgramName = res.ProgramName - param.ID = selectTran.ID + param.ID = editTran.ID } Api.getCloudConfig(param).then(result => { @@ -217,6 +302,49 @@ }) } + submitScript = () => { + // const { selectTran } = this.state + + this.scriptRef.handleConfirm().then(res => { + this.setState({ + confirmloading: true + }) + // let param = {} + // s_sVersionDetail_Add + // s_sVersionDetail_CloudAdd + + // Api.getCloudConfig(param).then(result => { + // if (result.status) { + // notification.success({ + // top: 92, + // message: '鎿嶄綔鎴愬姛锛�', + // duration: 3 + // }) + // this.setState({ + // scriptIndex: 1, + // confirmloading: false, + // scriptVisible: false + // }, () => { + // this.getScriptList() + // }) + // } else { + // this.setState({ + // confirmloading: false + // }) + // notification.warning({ + // top: 92, + // message: result.message, + // duration: 5 + // }) + // } + // }, () => { + // this.setState({ + // confirmloading: false + // }) + // }) + }) + } + deleteTran = (record) => { const _this = this @@ -235,10 +363,6 @@ top: 92, message: '鎿嶄綔鎴愬姛锛�', duration: 3 - }) - - _this.setState({ - selectTran: null, }) _this.getTransList() } else { @@ -276,10 +400,6 @@ top: 92, message: '鎿嶄綔鎴愬姛锛�', duration: 3 - }) - - _this.setState({ - selectTran: record, }) _this.getTransList() } else { @@ -322,6 +442,10 @@ return item }) + + if (!selectApp && applist[0]) { + selectApp = applist[0] + } this.setState({ loading: false, @@ -495,11 +619,113 @@ this.setState({ selectApp }) } - onTransChange = selectedRowKeys => { - const { translist } = this.state - let selectTran = translist.filter(item => item.ID === selectedRowKeys[0])[0] + onScriptChange = selectedRowKeys => { + this.setState({ selectScriptKeys: selectedRowKeys }) + } - this.setState({ selectTran }) + onScriptSelect = (record) => { + const { selectScriptKeys } = this.state + + if (selectScriptKeys.includes(record.ID)) { + this.setState({ selectScriptKeys: selectScriptKeys.filter(key => key !== record.ID) }) + } else { + this.setState({ selectScriptKeys: [...selectScriptKeys, record.ID]}) + } + } + + deleteScripts = () => { + const { selectScriptKeys, selectTran } = this.state + + if (selectScriptKeys.length === 0) { + notification.warning({ + top: 92, + message: '璇烽�夋嫨瑕佸垹闄ょ殑鑴氭湰锛�', + duration: 3 + }) + return + } + + let params = selectScriptKeys.map(key => { + return { + func: 's_sVersionDetail_del', + BID: selectTran.ID, + ID: key + } + }) + + const _this = this + + confirm({ + content: '纭畾瑕佹墽琛屽悧锛�', + onOk() { + return new Promise(resolve => { + let deffers = params.map((param, i) => { + return new Promise(resolve => { + setTimeout(() => { + Api.getCloudConfig(param).then(res => { + resolve(res) + }, () => { + resolve({status: false, message: '鍒犻櫎澶辫触锛�'}) + }) + }, i * 150) + }) + }) + Promise.all(deffers).then(result => { + let errorMsg = '' + result.forEach(res => { + if (!res.status) { + errorMsg = res.message + } + }) + if (errorMsg) { + notification.warning({ + top: 92, + message: errorMsg, + duration: 3 + }) + } else { + notification.success({ + top: 92, + message: '鎵ц鎴愬姛銆�', + duration: 3 + }) + _this.setState({ + scriptIndex: 1 + }, () => { + _this.getScriptList() + }) + } + resolve() + }) + }) + }, + onCancel() {} + }) + } + + onTransChange = selectedRowKeys => { + const { translist, selectTran } = this.state + let _selectTran = translist.filter(item => item.ID === selectedRowKeys[0])[0] + + this.setState({ selectTran: _selectTran }) + + if (!selectTran || selectTran.ID !== _selectTran.ID) { + this.setState({ scriptIndex: 1 }, () => { + this.getScriptList() + }) + } + } + + onTransSelect = (record) => { + const { selectTran } = this.state + + this.setState({ selectTran: record }) + + if (!selectTran || selectTran.ID !== record.ID) { + this.setState({ scriptIndex: 1 }, () => { + this.getScriptList() + }) + } } /** @@ -661,7 +887,7 @@ } render () { - const { loading, visible, subVisible, columns, transcolumns, applist, translist, transVisible, selectApp, selectTran, selectSubApp } = this.state + const { loading, visible, subVisible, columns, transcolumns, applist, translist, transVisible, selectApp, selectTran, selectSubApp, scriptVisible, scriptlist, scriptcolumns, selectScriptKeys } = this.state return ( <div className="mk-app-manage"> @@ -669,8 +895,7 @@ <Header view="manage" /> {loading ? <div className="loading-mask"> - <div className="ant-spin-blur"></div> - <Spin /> + <Spin size="large" /> </div> : null } <div className="view-wrap"> @@ -700,32 +925,41 @@ pagination={{ current: this.state.transIndex, pageSize: 10, - total: this.state.transTotal || 0 + total: this.state.transTotal || 0, + showTotal: (total, range) => `${range[0]}-${range[1]} 鍏� ${total} 鏉 }} rowSelection={{ type: 'radio', selectedRowKeys: selectTran ? [selectTran.ID] : [], onChange: this.onTransChange }} - onRow={(record) => ({ onClick: () => this.setState({ selectTran: record })})} + onRow={(record) => ({ onClick: () => { + if (this.forbid) { + this.forbid = false + return + } + this.onTransSelect(record) + }})} onChange={this.changeTable} /> </div> - {/* <div className={'script-table' + (this.state.transTotal <= 10 ? ' no-footer' : '')}> + {selectTran ? <div className="script-table"> <div className="app-action"> - <Button className="mk-green" onClick={() => this.setState({ scriptVisible: 'plus' })}>娣诲姞鑴氭湰</Button> - <Search placeholder="缁煎悎鎼滅储" onSearch={value => this.scriptSearch(value)} enterButton /> + <Button className="mk-green" onClick={() => this.setState({ scriptVisible: true })}>娣诲姞鑴氭湰</Button> + <Button className="mk-danger" onClick={this.deleteScripts} style={{marginLeft: '15px'}}>鍒犻櫎</Button> + <Search placeholder="缁煎悎鎼滅储" defaultValue={this.state.scriptSearchKey} onSearch={value => this.scriptSearch(value)} enterButton /> </div> <Table rowKey="ID" - columns={transcolumns} - dataSource={translist} + columns={scriptcolumns} + dataSource={scriptlist} pagination={{ - current: this.state.transIndex, + current: this.state.scriptIndex, pageSize: 10, - total: this.state.transTotal || 0 + total: this.state.scriptTotal || 0, + showTotal: (total, range) => `${range[0]}-${range[1]} 鍏� ${total} 鏉 }} - rowSelection={{ type: 'radio', selectedRowKeys: selectTran ? [selectTran.ID] : [], onChange: this.onTransChange }} - onRow={(record) => ({ onClick: () => this.setState({ selectTran: record })})} - onChange={this.changeTable} + rowSelection={{ type: 'checkbox', selectedRowKeys: selectScriptKeys, onChange: this.onScriptChange }} + onRow={(record) => ({ onClick: () => this.onScriptSelect(record)})} + onChange={this.changeScriptTable} /> - </div> */} + </div> : null} </div> <div className="right-view"> {selectApp ? <div className="app-title">{selectApp.remark}</div> : null} @@ -813,8 +1047,8 @@ title={'缂栬緫搴旂敤'} width={'600px'} maskClosable={false} - visible={!!visible} - onCancel={() => this.setState({visible: false})} + visible={visible !== false} + onCancel={() => this.setState({visible: false, confirmloading: false})} confirmLoading={this.state.confirmloading} onOk={this.submitCard} cancelText="鍙栨秷" @@ -827,22 +1061,36 @@ title={transVisible === 'plus' ? '娣诲姞浼犺緭鍙�' : '缂栬緫浼犺緭鍙�'} width={'600px'} maskClosable={false} - visible={!!transVisible} - onCancel={() => this.setState({transVisible: false})} + visible={transVisible !== false} + onCancel={() => this.setState({transVisible: false, confirmloading: false})} confirmLoading={this.state.confirmloading} onOk={this.submitTrans} cancelText="鍙栨秷" okText="纭畾" destroyOnClose > - <TransForm type={transVisible} card={transVisible === 'edit' ? selectTran : ''} wrappedComponentRef={(inst) => this.transRef = inst} inputSubmit={this.submitTrans} /> + <TransForm type={transVisible} card={transVisible === 'edit' ? this.state.editTran : ''} wrappedComponentRef={(inst) => this.transRef = inst} inputSubmit={this.submitTrans} /> + </Modal> + <Modal + title={'娣诲姞鑴氭湰'} + width={750} + maskClosable={false} + visible={scriptVisible} + onCancel={() => this.setState({scriptVisible: false, confirmloading: false})} + confirmLoading={this.state.confirmloading} + onOk={this.submitScript} + cancelText="鍙栨秷" + okText="纭畾" + destroyOnClose + > + <ScriptForm applist={applist} wrappedComponentRef={(inst) => this.scriptRef = inst} inputSubmit={this.submitScript} /> </Modal> <Modal title={'缂栬緫瀛愬簲鐢�'} width={'850px'} maskClosable={false} - visible={!!subVisible} - onCancel={() => this.setState({subVisible: false})} + visible={subVisible !== false} + onCancel={() => this.setState({subVisible: false, confirmloading: false})} confirmLoading={this.state.confirmloading} onOk={this.submitSubCard} cancelText="鍙栨秷" diff --git a/src/views/appmanage/index.scss b/src/views/appmanage/index.scss index 3d5b6a2..168ecbf 100644 --- a/src/views/appmanage/index.scss +++ b/src/views/appmanage/index.scss @@ -3,6 +3,21 @@ min-height: 100vh; padding: 70px 30px; + .loading-mask { + position: fixed; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + z-index: 2; + background: rgba(255, 255, 255, 0.35); + + .ant-spin { + position: absolute; + left: 50%; + top: 50%; + } + } .mob-header-container { padding-right: 0px; z-index: 10; @@ -17,8 +32,8 @@ flex: 1; width: 60%; padding-right: 5px; - .trans-table { - margin-top: 30px; + .trans-table, .script-table { + margin-top: 40px; .ant-input-search { width: 250px; float: right; diff --git a/src/views/appmanage/scriptform/index.jsx b/src/views/appmanage/scriptform/index.jsx new file mode 100644 index 0000000..0e0d257 --- /dev/null +++ b/src/views/appmanage/scriptform/index.jsx @@ -0,0 +1,241 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import { Form, Row, Col, Input, Select, notification } from 'antd' +import moment from 'moment' + +import Api from '@/api' +import Utils from '@/utils/utils.js' +import './index.scss' + +const { TextArea } = Input + +class ScriptForm extends Component { + static propTpyes = { + applist: PropTypes.array, + inputSubmit: PropTypes.func + } + + state = { + type: 'subapp', + sublist: [], + views: [], + appId: '', + subAppId: '' + } + + viewList = {} + + UNSAFE_componentWillMount() { + const { applist } = this.props + + let sublist = [] + let appId = '' + let subAppId = '' + if (applist[0]) { + sublist = applist[0].sublist || [] + appId = applist[0].ID + ',' + applist[0].kei_no + + if (sublist[0]) { + subAppId = sublist[0].ID + ',' + sublist[0].lang + ',' + sublist[0].typename + } + } + + this.viewList = {} + + this.setState({sublist, appId, subAppId}) + } + + /** + * @description 鑾峰彇琛ㄥ崟鍊� + */ + handleConfirm = () => { + return new Promise(resolve => { + this.props.form.validateFieldsAndScroll((err, values) => { + if (!err) { + resolve(values) + } + }) + }) + } + + changeType = (val) => { + this.setState({type: val}, () => { + this.getViews() + }) + } + + changeApp = (val) => { + const { applist } = this.props + + let app = applist.filter(item => `${item.ID},${item.kei_no}` === val)[0] + + let appId = '' + let subAppId = '' + let sublist = [] + if (app) { + sublist = app.sublist || [] + appId = app.ID + ',' + app.kei_no + + if (sublist[0]) { + subAppId = sublist[0].ID + ',' + sublist[0].lang + ',' + sublist[0].typename + } + } + + this.setState({sublist, subAppId, appId}, () => { + this.getViews() + }) + this.props.form.setFieldsValue({subAppId}) + } + + changeSubApp = (val) => { + this.setState({subAppId: val}, () => { + this.getViews() + }) + } + + getViews = () => { + const { type, appId, subAppId } = this.state + + if (type !== 'view' || !appId || !subAppId) return + + this.setState({views: []}) + this.props.form.setFieldsValue({viewId: ''}) + + let kei = appId.split(',')[1] + let m = subAppId.split(',') + + let _param = { + func: 's_get_app_menus', + TypeCharOne: kei, + typename: m[2], + lang: m[1], + LText: `select '${window.GLOB.appkey}'`, + timestamp: moment().format('YYYY-MM-DD HH:mm:ss') + } + + _param.secretkey = Utils.encrypt(_param.LText, _param.timestamp) + + Api.getCloudConfig(_param).then(res => { + if (!res.status) { + notification.warning({ + top: 92, + message: res.message, + duration: 5 + }) + return + } + + this.setState({views: res.menus}) + }) + } + + /** + * @description 鍥炶溅鎻愪氦 + */ + handleSubmit = (e) => { + e.preventDefault() + this.props.inputSubmit() + } + + render() { + const { applist } = this.props + const { getFieldDecorator } = this.props.form + const { sublist, appId, subAppId, type, views } = this.state + const formItemLayout = { + labelCol: { + xs: { span: 24 }, + sm: { span: 8 } + }, + wrapperCol: { + xs: { span: 24 }, + sm: { span: 16 } + } + } + return ( + <Form {...formItemLayout} className="app-script-form"> + <Row gutter={24}> + <Col span={12}> + <Form.Item label="绫诲瀷"> + {getFieldDecorator('VType', { + initialValue: 'subapp', + rules: [{ + required: true, + message: '璇烽�夋嫨绫诲瀷!' + }] + })( + <Select onChange={this.changeType}> + <Select.Option value="subapp">瀛愬簲鐢�</Select.Option> + <Select.Option value="view">椤甸潰</Select.Option> + </Select> + )} + </Form.Item> + </Col> + <Col span={12}> + <Form.Item label="搴旂敤"> + {getFieldDecorator('appId', { + initialValue: appId, + rules: [{ + required: true, + message: '璇烽�夋嫨搴旂敤!' + }] + })( + <Select onChange={this.changeApp}> + {applist.map(item => { + return <Select.Option key={item.ID} value={item.ID + ',' + item.kei_no}>{item.remark}</Select.Option> + })} + </Select> + )} + </Form.Item> + </Col> + <Col span={12}> + <Form.Item label="瀛愬簲鐢�"> + {getFieldDecorator('subAppId', { + initialValue: subAppId, + rules: [{ + required: true, + message: '璇烽�夋嫨瀛愬簲鐢�!' + }] + })( + <Select onChange={this.changeSubApp}> + {sublist.map(item => { + return <Select.Option key={item.ID} value={item.ID + ',' + item.lang + ',' + item.typename}>{`${item.typename}锛�${item.lang !== 'zh-CN' ? '鑻辨枃' : '涓枃'}锛�${item.title || ''}`}</Select.Option> + })} + </Select> + )} + </Form.Item> + </Col> + {type === 'view' ? <Col span={12}> + <Form.Item label="椤甸潰"> + {getFieldDecorator('viewId', { + initialValue: '', + rules: [{ + required: true, + message: '璇烽�夋嫨椤甸潰!' + }] + })( + <Select> + {views.map(item => { + return <Select.Option key={item.MenuID} value={item.MenuID}>{item.MenuName}</Select.Option> + })} + </Select> + )} + </Form.Item> + </Col> : null} + <Col span={24} className="remark"> + <Form.Item label="璇存槑"> + {getFieldDecorator('Remark', { + initialValue: '', + rules: [{ + max: 512, + message: '璇存槑涓嶅彲瓒呰繃512涓瓧绗�!' + }] + })(<TextArea autoSize={{ minRows: 2, maxRows: 6 }} />)} + </Form.Item> + </Col> + </Row> + </Form> + ) + } +} + +export default Form.create()(ScriptForm) \ No newline at end of file diff --git a/src/views/appmanage/scriptform/index.scss b/src/views/appmanage/scriptform/index.scss new file mode 100644 index 0000000..74e8f0b --- /dev/null +++ b/src/views/appmanage/scriptform/index.scss @@ -0,0 +1,12 @@ +.app-script-form { + padding: 0px 24px 20px; + + .remark { + .ant-form-item-label { + width: 16%; + } + .ant-form-item-control-wrapper { + width: 84%; + } + } +} \ No newline at end of file diff --git a/src/views/design/header/index.jsx b/src/views/design/header/index.jsx index 5b8c73d..5b14744 100644 --- a/src/views/design/header/index.jsx +++ b/src/views/design/header/index.jsx @@ -2,7 +2,7 @@ import { withRouter } from 'react-router-dom' import {connect} from 'react-redux' import { is, fromJS } from 'immutable' -import { Dropdown, Menu, Icon, Modal, Form, notification, Switch, Button } from 'antd' +import { Dropdown, Menu, Icon, Modal, notification, Switch, Button } from 'antd' import asyncComponent from '@/utils/asyncComponent' import { @@ -327,6 +327,9 @@ {!editLevel && options.sysType === 'local' && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ? <span onClick={() => {window.open('#/appmanage')}} className="mobile" type="edit"> 搴旂敤绠$悊 <Icon type="arrow-right" /></span> : null } + {!editLevel && options.sysType === 'local' && this.props.memberLevel >= 20 ? + <span onClick={() => {window.open('#/interface')}} className="interface" type="edit"> 鎺ュ彛璋冭瘯 <Icon type="arrow-right" /></span> : null + } {/* window.btoa(window.encodeURIComponent(JSON.stringify({ MenuType: 'home', MenuId: 'home_page_id', MenuName: '棣栭〉' }))) */} {!editLevel && window.GLOB.systemType !== 'production' && this.props.memberLevel >= 20 ? <span className="home-edit" onClick={() => {window.open('#/menudesign/JTdCJTIyTWVudVR5cGUlMjIlM0ElMjJob21lJTIyJTJDJTIyTWVudUlkJTIyJTNBJTIyaG9tZV9wYWdlX2lkJTIyJTJDJTIyTWVudU5hbWUlMjIlM0ElMjIlRTklQTYlOTYlRTklQTElQjUlMjIlN0Q=')}}> @@ -379,4 +382,4 @@ } } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Form.create()(Header))) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)) \ No newline at end of file diff --git a/src/views/design/header/index.scss b/src/views/design/header/index.scss index 705e4d5..4f796e4 100644 --- a/src/views/design/header/index.scss +++ b/src/views/design/header/index.scss @@ -130,6 +130,13 @@ color: #1890ff; cursor: pointer; } + .interface { + position: absolute; + top: 170px; + right: 50px; + color: #1890ff; + cursor: pointer; + } .home-edit { position: absolute; top: 100px; diff --git a/src/views/interface/api/index.js b/src/views/interface/api/index.js new file mode 100644 index 0000000..59f4941 --- /dev/null +++ b/src/views/interface/api/index.js @@ -0,0 +1,175 @@ +import axios from 'axios' +import md5 from 'md5' +import jsSHA from 'jssha' +import { notification } from 'antd' + +window.GLOB.WebSql = null + +if (window.openDatabase) { + let service = window.GLOB.service ? '-' + window.GLOB.service.replace('/', '') : '' + try { + window.GLOB.WebSql = openDatabase(`mkdb${service}`, '1', 'mk-pc-database', 50 * 1024 * 1024) + window.GLOB.WebSql.transaction(tx => { + tx.executeSql('CREATE TABLE IF NOT EXISTS INTERFACES (uuid varchar(50), createDate varchar(50), method varchar(50), interface text, params text, headers text, active varchar(50), raw text, formData text, CDefine1 varchar(50), CDefine2 varchar(50), CDefine3 varchar(50), CDefine4 varchar(50), CDefine5 text)', [], () => { + + }, () => { + // eslint-disable-next-line + throw 'CREATE TABLE ERROR' + }) + }) + } catch (e) { + console.warn('WebSql 鍒濆鍖栧け璐ワ紒') + window.GLOB.WebSql = null + } +} + +axios.defaults.crossDomain = true +axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8' +axios.defaults.withCredentials = false + +axios.interceptors.request.use((config) => { + return config +}, (error) => { + return Promise.reject(error) +}) + +axios.interceptors.response.use((response) => { + return Promise.resolve(response) +}, (error) => { + return Promise.reject(error) +}) + +class Api { + /** + * @description 鐧诲綍绯荤粺, 鑾峰彇鐢ㄦ埛淇℃伅 + */ + dologon (url, method, header, n) { + let config = { + url, + method + } + + if (header) { + config.headers = header + } + + if (n) { + let _param = JSON.parse(n) + let param = {} + + Object.keys(_param).forEach(key => { + param[key.toLowerCase()] = _param[key] + }) + + if (param.type && param.username && param.password && param.timestamp) { + if (param.type === 'S') { + let shaObj = new jsSHA('SHA-1', 'TEXT') + shaObj.update(param.password) + param.password = shaObj.getHash('HEX').toUpperCase() + param.password = md5(param.username + param.password + param.timestamp) + } else if (/^mk_/ig.test(param.type)) { + let shaObj = new jsSHA('SHA-1', 'TEXT') + shaObj.update(param.password) + param.password = shaObj.getHash('HEX').toUpperCase() + param.password = md5(param.privatekey + param.username + param.password + param.timestamp) + + delete param.privatekey + } + } + + config.data = JSON.stringify(param) + } + + return axios(config) + } + + /** + * @description 閫氱敤璇锋眰 + */ + normalRequest (url, method, header, n) { + let config = { + url, + method + } + + if (header) { + config.headers = header + } + if (n) { + config.data = n + } + + return axios(config) + } + + writeInWebSql (data) { + if (!window.GLOB.WebSql) { + notification.warning({ top: 92, message: 'WebSql寮�鍚け璐ワ紒', duration: 5 }) + return + } + return new Promise((resolve, reject) => { + window.GLOB.WebSql.transaction(tx => { + tx.executeSql(`INSERT INTO INTERFACES (uuid, createDate, method, interface, params, headers, active, raw, formData) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, data, (tx, results) => { + resolve(results) + }, () => { + resolve() + }) + }) + }) + } + + getInterfaces () { + if (!window.GLOB.WebSql) { + notification.warning({ top: 92, message: 'WebSql寮�鍚け璐ワ紒', duration: 5 }) + return + } + return new Promise((resolve, reject) => { + window.GLOB.WebSql.transaction(tx => { + tx.executeSql(`SELECT * FROM INTERFACES`, [], (tx, results) => { + // let paramItem = results.rows[0] + resolve(results) + }, () => { + window.GLOB.WebSql = null + reject() + }) + }) + }) + } + + delInterface (uuid) { + if (!window.GLOB.WebSql) { + notification.warning({ top: 92, message: 'WebSql寮�鍚け璐ワ紒', duration: 5 }) + return + } + return new Promise((resolve, reject) => { + window.GLOB.WebSql.transaction(tx => { + tx.executeSql(`DELETE FROM INTERFACES where uuid = '${uuid}'`, [], (tx, results) => { + resolve(results) + }, () => { + resolve() + }) + }) + }) + } + + /** + * @description 娓呯┖鎺ュ彛璋冪敤璁板綍 + */ + clearInterfaces () { + if (!window.GLOB.WebSql) { + notification.warning({ top: 92, message: 'WebSql寮�鍚け璐ワ紒', duration: 5 }) + return + } + return new Promise((resolve, reject) => { + window.GLOB.WebSql.transaction(tx => { + tx.executeSql(`DELETE FROM INTERFACES`, [], (tx, results) => { + resolve(results) + }, () => { + resolve() + }) + }) + }) + } +} + +export default new Api() \ No newline at end of file diff --git a/src/views/interface/header/index.jsx b/src/views/interface/header/index.jsx new file mode 100644 index 0000000..03804df --- /dev/null +++ b/src/views/interface/header/index.jsx @@ -0,0 +1,45 @@ +import React, {Component} from 'react' +import { withRouter } from 'react-router-dom' + +import Utils from '@/utils/utils.js' +import options from '@/store/options.js' +import avatar from '@/assets/img/avatar.jpg' +import MainLogo from '@/assets/img/main-logo.png' +import './index.scss' + +class Header extends Component { + state = { + userName: sessionStorage.getItem('CloudUserName'), + avatar: Utils.getrealurl(sessionStorage.getItem('CloudAvatar')), + } + + UNSAFE_componentWillMount() { + if (options.sysType !== 'local' || !sessionStorage.getItem('LoginUID')) { + sessionStorage.clear() + this.props.history.replace('/login') + } + } + + close = () => { + window.close() + } + + render () { + + return ( + <header className="interface-header-container"> + <div className="header-logo"><img src={MainLogo} alt=""/></div> + <div className="title">鎺ュ彛璋冭瘯</div> + <div className="header-setting"> + <span className="close" onClick={this.close}>鍏抽棴</span> + <img src={this.state.avatar || avatar} alt=""/> + <span> + <span className="username">{this.state.userName}</span> + </span> + </div> + </header> + ) + } +} + +export default withRouter(Header) \ No newline at end of file diff --git a/src/views/interface/header/index.scss b/src/views/interface/header/index.scss new file mode 100644 index 0000000..36f8765 --- /dev/null +++ b/src/views/interface/header/index.scss @@ -0,0 +1,63 @@ +.interface-header-container { + position: fixed; + z-index: 20; + left: 0; + top: 0; + font-weight: bold!important; + width: 100%; + height: 48px; + background: #000; + + .header-logo { + float: left; + width: 180px; + line-height: 48px; + text-align: center; + padding-left: 5px; + box-sizing: border-box; + opacity: 1; + img { + max-width: 100%; + max-height: 40px; + } + } + + .title { + position: absolute; + top: 10px; + left: 50%; + transform: translateX(-50%); + color: #ffffff; + font-size: 18px; + } + + .header-setting { + float: right; + line-height: 48px; + margin-right: 10px; + .close { + margin-right: 20px; + cursor: pointer; + padding: 10px; + } + img { + width: 29px; + height: 29px; + border-radius: 30px; + margin-right: 7px; + } + span { + color: #ffffff; + font-size: 0.95rem; + .username { + display: inline-block; + height: 30px; + max-width: 95px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + } + } +} + diff --git a/src/views/interface/history/index.jsx b/src/views/interface/history/index.jsx new file mode 100644 index 0000000..15700b5 --- /dev/null +++ b/src/views/interface/history/index.jsx @@ -0,0 +1,246 @@ +import React, {Component} from 'react' +import { fromJS } from 'immutable' +import { Input, Modal, Empty, Icon } from 'antd' +import moment from 'moment' + +import Api from '@/views/interface/api' +import Utils from '@/utils/utils.js' +import MKEmitter from '@/utils/events.js' +import './index.scss' + +const { Search } = Input +const { confirm } = Modal + +class History extends Component { + state = { + list: [], + historys: [], + searchKey: '' + } + + componentDidMount() { + MKEmitter.addListener('insertInterface', this.insertInterface) + setTimeout(() => { + Api.getInterfaces().then(res => { + if (!res || !res.rows) return + + let rows = [...res.rows] + rows.sort((a,b) => { + return a.createDate < b.createDate ? 1 : -1 + }) + + let list = [] + let item = null + + rows.forEach(m => { + let date = m.createDate.substring(0, 10) + + if (m.params) { + try { + m.params = JSON.parse(m.params) + } catch { + m.params = [] + } + } else { + m.params = [] + } + + if (m.headers) { + try { + m.headers = JSON.parse(m.headers) + } catch { + m.headers = [] + } + } else { + m.headers = [] + } + + if (m.formData) { + try { + m.formData = JSON.parse(m.formData) + } catch { + m.formData = [] + } + } else { + m.formData = [] + } + + if (item && item.date !== date) { + list.push(item) + item = null + } + + if (!item) { + item = {date, sublist: []} + item.sublist.push(m) + } else if (item && item.date === date) { + item.sublist.push(m) + } + }) + + if (item) { + list.push(item) + } + + this.setState({list, historys: fromJS(list).toJS()}) + }) + }, 200) + } + + /** + * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊 + */ + componentWillUnmount () { + this.setState = () => { + return + } + MKEmitter.removeListener('insertInterface', this.insertInterface) + } + + clear = () => { + const _this = this + confirm({ + content: 'Are you sure you want to clear all your history requests?', + onOk() { + Api.clearInterfaces().then(res => { + if (res && res.rows.length === 0) { + _this.setState({list: [], historys: []}) + Modal.success({ + title: '娓呴櫎鎴愬姛銆�' + }) + } else { + Modal.error({ + title: '娓呴櫎澶辫触锛佽鍒锋柊閲嶈瘯銆�' + }) + } + }) + }, + onCancel() {} + }) + } + + delete = (m) => { + const { searchKey } = this.state + Api.delInterface(m.uuid).then(res => { + if (res) { + let list = this.state.list.filter(item => { + item.sublist = item.sublist.filter(cell => cell.uuid !== m.uuid) + + return item.sublist.length > 0 + }) + + let historys = fromJS(list).toJS() + if (searchKey) { + historys = historys.filter(item => { + item.sublist = item.sublist.filter(cell => cell.interface.indexOf(searchKey) > -1) + + return item.sublist.length > 0 + }) + } + + this.setState({list, historys}) + } else { + Modal.error({ + title: '鍒犻櫎澶辫触锛佽鍒锋柊閲嶈瘯銆�' + }) + } + }) + } + + insertInterface = (item) => { + item.uuid = Utils.getuuid() + item.createDate = moment().format('YYYY-MM-DD HH:mm:ss') + + Api.writeInWebSql([item.uuid, item.createDate, item.method, item.interface, JSON.stringify(item.params), JSON.stringify(item.headers), item.active, item.raw, JSON.stringify(item.formData)]).then(res => { + if (res) { + let list = fromJS(this.state.list).toJS() + + if (list[0]) { + if (list[0].date === item.createDate.substring(0, 10)) { + list[0].sublist.unshift(item) + } else { + list.unshift({ + date: item.createDate.substring(0, 10), + sublist: [item] + }) + } + } else { + list.push({ + date: item.createDate.substring(0, 10), + sublist: [item] + }) + } + + let historys = fromJS(list).toJS() + if (this.state.searchKey) { + historys = historys.filter(item => { + item.sublist = item.sublist.filter(cell => cell.interface.indexOf(this.state.searchKey) > -1) + + return item.sublist.length > 0 + }) + } + + this.setState({ list, historys }) + } else { + Modal.error({ + title: '娣诲姞澶辫触锛佽鍒锋柊閲嶈瘯銆�' + }) + } + }) + } + + use = (m) => { + MKEmitter.emit('useInterface', fromJS(m).toJS()) + } + + changeSearch = (value) => { + const { list } = this.state + + let historys = fromJS(list).toJS() + if (value) { + historys = historys.filter(item => { + item.sublist = item.sublist.filter(cell => cell.interface.indexOf(value) > -1) + + return item.sublist.length > 0 + }) + } + + this.setState({searchKey: value, historys}) + } + + render () { + const { historys } = this.state + + return ( + <aside className="interface-side-menu"> + <Search placeholder="Filter" onSearch={value => this.changeSearch(value)}/> + <div className="title"> + History + <span onClick={this.clear}>Clear all</span> + </div> + <div className="list-view"> + {historys.length === 0 ? + <Empty /> : + historys.map((item, index) => ( + <div className="list-line" key={index}> + <div className="line-title">{item.date}</div> + {item.sublist.map(m => ( + <div className="line-item" key={m.uuid}> + <div className="method">POST</div> + <div className="inter">{m.interface}</div> + <div className="action"> + <Icon type="delete" onClick={() => this.delete(m)} /> + <Icon type="right" onClick={() => this.use(m)} /> + </div> + </div> + ))} + </div> + )) + } + </div> + </aside> + ) + } +} + +export default History \ No newline at end of file diff --git a/src/views/interface/history/index.scss b/src/views/interface/history/index.scss new file mode 100644 index 0000000..012eebb --- /dev/null +++ b/src/views/interface/history/index.scss @@ -0,0 +1,113 @@ +.interface-side-menu { + display: inline-block; + width: 300px; + border-right: 2px solid #e8e8e8; + height: 100%; + + .ant-input-search { + margin: 10px 20px; + width: calc(100% - 40px); + .ant-input { + border-radius: 20px; + } + } + .title { + padding-left: 20px; + font-size: 16px; + border-bottom: 1px solid #e8e8e8; + padding-bottom: 3px; + span { + font-size: 14px; + float: right; + color: #fa541c; + margin-right: 10px; + cursor: pointer; + } + } + .list-view { + .ant-empty { + margin-top: 50px; + } + .list-line { + border-bottom: 1px solid #e8e8e8; + .line-title { + padding: 5px 15px; + color: rgba(0, 0, 0, 0.85); + } + .line-item { + position: relative; + display: flex; + padding-left: 25px; + margin-bottom: 8px; + transition: all 0.3s; + .method { + width: 42px; + color: #faad14; + font-size: 12px; + font-weight: 600; + line-height: 40px; + } + .inter { + width: 225px; + height: 42px; + word-break: break-all; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + } + .action { + width: 70px; + position: absolute; + line-height: 40px; + right: 0px; + height: 42px; + background: #ffffff; + padding-left: 15px; + opacity: 0; + transition: all 0.3s; + i { + cursor: pointer; + } + i:first-child { + margin-right: 12px; + } + .anticon-delete { + color: #fa541c; + } + .anticon-right { + color: #1890ff; + } + } + } + .line-item:hover { + background: #e9e9e9; + .action { + opacity: 1; + background: #e9e9e9; + } + } + } + } + + .list-view { + height: calc(100vh - 128px); + overflow-x: hidden; + overflow-y: auto; + } + .list-view::-webkit-scrollbar { + width: 5px; + } + .list-view::-webkit-scrollbar-thumb { + border-radius: 5px; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.09); + background: rgba(0, 0, 0, 0.09); + } + .list-view::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05); + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.07); + background: rgba(0, 0, 0, 0); + } +} \ No newline at end of file diff --git a/src/views/interface/index.jsx b/src/views/interface/index.jsx new file mode 100644 index 0000000..eaaa452 --- /dev/null +++ b/src/views/interface/index.jsx @@ -0,0 +1,36 @@ +import React, {Component} from 'react' +import { ConfigProvider } from 'antd' +import enUS from 'antd/es/locale/en_US' +import zhCN from 'antd/es/locale/zh_CN' + +// import Api from '@/views/interface/api' +// import asyncComponent from '@/utils/asyncComponent' +// import options from '@/store/options.js' +import Header from './header' +import History from './history' +import WorkSpace from './workspace' + +import './index.scss' + +const _locale = sessionStorage.getItem('lang') !== 'en-US' ? zhCN : enUS + +class Interface extends Component { + componentDidMount() { + + // + } + + render () { + return ( + <div className="interface-view"> + <ConfigProvider locale={_locale}> + <Header key="header"/> + <History key="history"/> + <WorkSpace key="workspace"/> + </ConfigProvider> + </div> + ) + } +} + +export default Interface \ No newline at end of file diff --git a/src/views/interface/index.scss b/src/views/interface/index.scss new file mode 100644 index 0000000..6c53933 --- /dev/null +++ b/src/views/interface/index.scss @@ -0,0 +1,6 @@ +.interface-view { + padding-top: 48px; + width: 100vw; + height: 100vh; + overflow: hidden; +} \ No newline at end of file diff --git a/src/views/interface/workspace/editTable/index.jsx b/src/views/interface/workspace/editTable/index.jsx new file mode 100644 index 0000000..d4001e3 --- /dev/null +++ b/src/views/interface/workspace/editTable/index.jsx @@ -0,0 +1,255 @@ +import React, {Component} from 'react' +import PropTypes from 'prop-types' +import { Table, Input, Form, Icon } from 'antd' +import Utils from '@/utils/utils.js' +import './index.scss' + +const EditableContext = React.createContext() + +const EditableRow = ({ form, index, ...props }) => ( + <EditableContext.Provider value={form}> + <tr {...props} /> + </EditableContext.Provider> +) + +const EditableFormRow = Form.create()(EditableRow) + +class EditableCell extends Component { + state = { + editing: false + } + + toggleEdit = () => { + const { dataIndex } = this.props + + if (!dataIndex) return + + const editing = !this.state.editing + this.setState({ editing }, () => { + if (editing && this.input && this.input.select) { + this.input.select() + } else if (editing && this.input && this.input.focus) { + this.input.focus() + } + }) + } + + save = e => { + const { record, handleSave } = this.props + this.form.validateFields((error, values) => { + handleSave({ ...record, ...values }) + if (error && error[e.currentTarget.id]) { + return + } + this.toggleEdit() + }) + } + + renderCell = form => { + this.form = form + const { children, dataIndex, record } = this.props + const { editing } = this.state + + return editing ? ( + <Form.Item style={{ margin: 0 }}> + {form.getFieldDecorator(dataIndex, { + initialValue: record[dataIndex] + })(<Input ref={node => (this.input = node)} autoComplete="off" onPressEnter={this.save} onBlur={this.save} />)} + </Form.Item> + ) : ( + <div + className="editable-cell-value-wrap" + onClick={this.toggleEdit} + > + {children} + </div> + ) + } + + render() { + const { dataIndex, title, record, index, handleSave, children, ...restProps } = this.props + return ( + <td {...restProps}> + <EditableContext.Consumer style={{padding: 0}}>{this.renderCell}</EditableContext.Consumer> + </td> + ) + } +} + +class EditTable extends Component { + static propTpyes = { + data: PropTypes.array, + onChange: PropTypes.func + } + + state = { + dataSource: [], + selectedRowKeys: [], + count: 0, + columns: [{ + dataIndex: 'key', + title: 'KEY', + width: '33%' + }, { + dataIndex: 'value', + title: 'VALUE', + width: '33%' + }, { + dataIndex: 'description', + title: 'DESCRIPTION', + className: 'no-border', + width: '33%' + }, { + dataIndex: '', + title: '', + width: '20px', + render: (text, record) => { + return (<Icon type="close" onClick={() => this.delete(record)}/>) + } + }] + } + + UNSAFE_componentWillMount () { + const { data } = this.props + + let _data = data || [] + let selectedRowKeys = [] + if (_data && _data.length > 0) { + _data.forEach(item => { + if (item.selected) { + selectedRowKeys.push(item.uuid) + } + }) + } else { + _data = [{ + uuid: Utils.getuuid(), + key: '', + value: '', + description: '', + selected: true + }] + selectedRowKeys.push(_data[0].uuid) + this.props.onChange(_data) + } + + this.setState({ + dataSource: _data, + selectedRowKeys + }) + } + + handleDelete = key => { + const { dataSource } = this.state + let _data = dataSource.filter(item => item.key !== key) + + this.setState({ dataSource: _data }, () => { + this.props.onChange(_data) + }) + } + + delete = (item) => { + const { dataSource, selectedRowKeys } = this.state + + let _data = dataSource.filter(cell => cell.uuid !== item.uuid) + let _keys = selectedRowKeys.filter(key => key !== item.uuid) + + if (_data.length === 0) { + _data = [{ + uuid: Utils.getuuid(), + key: '', + value: '', + description: '', + selected: true + }] + _keys = [_data[0].uuid] + this.props.onChange(_data) + } + + this.setState({ + dataSource: _data, + selectedRowKeys: _keys + }, () => { + this.props.onChange(_data) + }) + } + + handleAdd = (e) => { + e.stopPropagation() + const { dataSource, selectedRowKeys } = this.state + const item = { + uuid: Utils.getuuid(), + key: '', + value: '', + description: '', + selected: true + } + + let _data = [...dataSource, item] + + this.setState({ + dataSource: _data, + selectedRowKeys: [...selectedRowKeys, item.uuid] + }, () => { + this.props.onChange(_data) + }) + } + + handleSave = row => { + const newData = this.state.dataSource.map(item => { + if (row.uuid === item.uuid) return row + return item + }) + + this.setState({ dataSource: newData }, () => { + this.props.onChange(newData) + }) + } + + onChange = selectedRowKeys => { + const newData = this.state.dataSource.map(item => { + item.selected = selectedRowKeys.includes(item.uuid) + return item + }) + + this.setState({ dataSource: newData, selectedRowKeys }, () => { + this.props.onChange(newData) + }) + } + + render() { + const { dataSource, selectedRowKeys } = this.state + const components = { + body: { + row: EditableFormRow, + cell: EditableCell + } + } + const columns = this.state.columns.map(col => { + return { + ...col, + onCell: record => ({ + record, + dataIndex: col.dataIndex, + title: col.title, + handleSave: this.handleSave, + }) + } + }) + return ( + <div className="params-edit-table"> + <Icon className="add-row" type="plus" onClick={this.handleAdd} /> + <Table + rowKey="uuid" + components={components} + bordered + dataSource={dataSource} + columns={columns} + rowSelection={{ type: 'checkbox', selectedRowKeys, onChange: this.onChange }} + pagination={false} + /> + </div> + ) + } +} + +export default EditTable \ No newline at end of file diff --git a/src/views/interface/workspace/editTable/index.scss b/src/views/interface/workspace/editTable/index.scss new file mode 100644 index 0000000..10cd1a4 --- /dev/null +++ b/src/views/interface/workspace/editTable/index.scss @@ -0,0 +1,85 @@ +.params-edit-table { + .add-row { + position: absolute; + z-index: 1; + right: 10px; + top: 0px; + padding: 5px; + font-size: 22px; + color: #26C281; + } + .ant-table table { + border-radius: 0; + } + .no-border { + border-right: 0!important; + } + .ant-table-thead > tr { + > th { + padding: 10px 10px; + } + .ant-table-selection-column { + .ant-table-header-column { + display: none; + } + } + } + .ant-table-tbody { + > tr { + > td { + background: #ffffff!important; + padding: 2px 10px; + .ant-input { + height: 28px; + } + .ant-form-item { + height: 30px; + line-height: 30px; + .ant-form-item-control { + line-height: 30px; + } + } + .anticon-close { + opacity: 0; + } + } + } + > tr:hover { + > td { + background: #ffffff!important; + .anticon-close { + opacity: 1; + } + } + } + } + .editable-cell-value-wrap { + cursor: pointer; + height: 36px; + width: 300px; + display: table-cell; + vertical-align: middle; + word-wrap: break-word; + word-break: break-word; + .ant-input { + height: 30px; + padding: 0 11px; + } + } + .ant-form-item-control-wrapper { + width: 100%; + } + .ant-table-placeholder { + padding: 5px 16px; + .ant-empty-normal { + margin: 0; + } + } + .operation-btn { + margin-right: 10px; + cursor: pointer; + } + .ant-form-explain { + font-size: 12px; + } +} diff --git a/src/views/interface/workspace/index.jsx b/src/views/interface/workspace/index.jsx new file mode 100644 index 0000000..2f60d6b --- /dev/null +++ b/src/views/interface/workspace/index.jsx @@ -0,0 +1,115 @@ +import React, {Component} from 'react' +import { Icon, Tabs } from 'antd' + +import Utils from '@/utils/utils.js' +import MKEmitter from '@/utils/events.js' +import Request from './request' +import './index.scss' +import { fromJS } from 'immutable' + + +class WorkSpace extends Component { + state = { + tabviews: [{ + uuid: Utils.getuuid(), + createDate: '', + method: 'POST', + interface: '', + params: [], + headers: [], + active: 'raw', + raw: '', + formData: [] + }] + } + + componentDidMount() { + MKEmitter.addListener('useInterface', this.useInterface) + } + + /** + * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊 + */ + componentWillUnmount () { + this.setState = () => { + return + } + MKEmitter.removeListener('useInterface', this.useInterface) + } + + useInterface = (item) => { + item.uuid = Utils.getuuid() + this.setState({tabviews: [...this.state.tabviews, item]}) + } + + handleTabview = (view) => { + let tabviews = fromJS(this.state.tabviews).toJS() + tabviews = tabviews.filter(item => item.uuid !== view.uuid) + + this.setState({tabviews}, () => { + if (tabviews.length > 0) return + + setTimeout(() => { + this.setState({tabviews: [{ + uuid: Utils.getuuid(), + createDate: '', + method: 'POST', + interface: '', + params: [], + headers: [], + active: 'raw', + raw: '', + formData: [] + }]}) + }, 300) + }) + } + + handleAdd = () => { + this.setState({tabviews: [...this.state.tabviews, { + uuid: Utils.getuuid(), + createDate: '', + method: 'POST', + interface: '', + params: [], + headers: [], + active: 'raw', + raw: '', + formData: [] + }]}) + } + + render () { + const { tabviews } = this.state + + return ( + <div className="workspace-wrap"> + <Icon className="add-view" type="plus" onClick={this.handleAdd} /> + <Tabs type="card"> + {tabviews.map(view => { + return ( + <Tabs.TabPane + tab={ + <span className="control"> + <span className="method"> + {view.method || 'POST'} + </span> + <span className="interface"> + {view.interface || 'Untitled Request'} + </span> + <Icon type="close" onClick={() => this.handleTabview(view)}/> + </span> + } + key={view.uuid} + > + <Request config={view} /> + </Tabs.TabPane> + ) + })} + </Tabs> + </div> + ) + } +} + +export default WorkSpace \ No newline at end of file diff --git a/src/views/interface/workspace/index.scss b/src/views/interface/workspace/index.scss new file mode 100644 index 0000000..7078d44 --- /dev/null +++ b/src/views/interface/workspace/index.scss @@ -0,0 +1,52 @@ +.workspace-wrap { + display: inline-block; + width: calc(100vw - 300px); + height: 100%; + vertical-align: top; + padding-top: 10px; + position: relative; + .add-view { + position: absolute; + right: 25px; + top: 12px; + z-index: 2; + padding: 5px; + cursor: pointer; + font-size: 22px; + color: #26C281; + } + + .ant-tabs.ant-tabs-card .ant-tabs-card-bar { + >.ant-tabs-nav-container { + margin-right: 100px; + } + padding-left: 5px; + .ant-tabs-tab { + margin-right: 7px; + padding: 0 10px; + .method { + color: #faad14; + font-size: 12px; + font-weight: 600; + margin-right: 5px; + vertical-align: top; + } + .interface { + display: inline-block; + color: rgba(0, 0, 0, 0.65); + width: 200px; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + vertical-align: top; + } + .anticon-close { + color:rgba(0, 0, 0, 0.65); + margin: 0; + } + } + .ant-tabs-tab.ant-tabs-tab-active { + border-top-color: #fa541c; + } + } +} \ No newline at end of file diff --git a/src/views/interface/workspace/request/index.jsx b/src/views/interface/workspace/request/index.jsx new file mode 100644 index 0000000..9e74b51 --- /dev/null +++ b/src/views/interface/workspace/request/index.jsx @@ -0,0 +1,300 @@ +import React, {Component} from 'react' +import { fromJS } from 'immutable' +import md5 from 'md5' +import { Input, Select, Button, Tabs, Radio, Modal } from 'antd' + +import Api from '@/views/interface/api' +import Utils from '@/utils/utils.js' +import EditTable from '../editTable' +import CodeMirror from '@/templates/zshare/codemirror' +import MKEmitter from '@/utils/events.js' +import './index.scss' + +const InputGroup = Input.Group +const { Option } = Select + +class Request extends Component { + state = { + active: 'raw', + body: null, + response: null, + status: '' + } + + UNSAFE_componentWillMount() { + const { config } = this.props + + this.setState({active: config.active, config: fromJS(config).toJS()}) + } + + componentDidMount() { + + } + + /** + * @description 缁勪欢閿�姣侊紝娓呴櫎state鏇存柊 + */ + componentWillUnmount () { + this.setState = () => { + return + } + } + + onChange = (e) => { + const { config } = this.state + + this.setState({active: e.target.value, config: {...config, active: e.target.value}}) + } + + rawChange = (val) => { + const { config } = this.state + + this.setState({config: {...config, raw: val}}) + } + + changeFormData = (vals) => { + const { config } = this.state + + this.setState({config: {...config, formData: vals}}) + } + + changeHeader = (vals) => { + const { config } = this.state + + this.setState({config: {...config, headers: vals}}) + } + + changeParams = (vals) => { + const { config } = this.state + + this.setState({config: {...config, params: vals}}) + } + + changeMethod = (val) => { + const { config } = this.state + + this.setState({config: {...config, method: val}}) + } + + changeInter = (e) => { + const { config } = this.state + + this.setState({config: {...config, interface: e.target.value}}) + } + + send = () => { + const { config } = this.state + + let raws = null + if (!config.interface) { + Modal.error({ + title: '璇峰~鍏ユ帴鍙e湴鍧�锛�' + }) + return + } else if (config.active === 'raw') { + if (config.raw) { + try { + raws = JSON.parse(config.raw) + } catch { + Modal.error({ + title: '鍙傛暟鏍煎紡閿欒锛屽繀椤讳负JSON鏍煎紡锛�' + }) + return + } + } + } + + let url = config.interface + + let m = [] + config.params.forEach(item => { + if (!item.selected || !item.key) return + m.push(`${item.key}=${item.value}`) + }) + + m = m.join('&') + + if (m) { + if (/\?/ig.test(url)) { + url = url + '&' + m + } else { + url = url + '?' + m + } + } + + let header = null + config.headers.forEach(item => { + if (!item.selected || !item.key) return + + header = header || {} + + header[item.key] = item.value + }) + + let n = null + if (config.active === 'raw' && raws) { + n = JSON.stringify(raws) + } else if (config.active === 'formData') { + config.formData.forEach(item => { + if (!item.selected || !item.key) return + + n = n || {} + + n[item.key] = item.value + }) + n = JSON.stringify(n) + } + + if (/logon/ig.test(url)) { + Api.dologon(url, config.method, header, n).then(res => { + this.handleResponse(res) + }, (err) => { + this.handleResponse(err) + }) + } else if (/dostars/ig.test(url)) { + if (n) { + n = JSON.parse(n) + n = this.encryptParam(n) + n = JSON.stringify(n) + } + Api.normalRequest(url, config.method, header, n).then(res => { + this.handleResponse(res) + }, (err) => { + this.handleResponse(err) + }) + } else { + Api.normalRequest(url, config.method, header, n).then(res => { + this.handleResponse(res) + }, (err) => { + this.handleResponse(err) + }) + } + } + + handleResponse = (res) => { + let body = null + + if (res && res.data) { + try { + body = JSON.stringify(res.data, null, 2) + + body = body.replace(/\n/ig, '<br/>') + body = body.replace(/\s/ig, ' ') + } catch { + body = null + } + } + + let status = '' + if (res.status) { + status = res.status + res.statusText + } else if (res.name === 'Error') { + status = res.message + body = `璇锋眰寮傚父${status === 'Network Error' ? '锛屽彲鑳藉師鍥狅細1銆佺綉缁滃紓甯革紱2銆佹帴鍙h法鍩熴��' : '锛�'}` + } + + this.setState({response: res, body, status}) + } + + encryptParam (param) { + param.nonc = Utils.getuuid() + + let keys = Object.keys(param).sort() + let values = '' + keys.forEach(key => { + if (key.toLowerCase() === 'rduri' || key.toLowerCase() === 't') return + if (param[key] === undefined) { + delete param[key] + } else if (typeof(param[key]) === 'object') { + values += key + JSON.stringify(param[key]) + } else { + values += key + param[key] + } + }) + param.sign = md5(values) + param.t = new Date().getTime() + + return param + } + + save = () => { + const { config } = this.state + + if (!config.interface) { + Modal.error({ + title: '璇峰~鍏ユ帴鍙e湴鍧�锛�' + }) + return + } + + MKEmitter.emit('insertInterface', config) + } + + render () { + const { active, config, response, body, status } = this.state + + let hasParam = config.params.filter(item => item.selected && item.key).length > 0 + let hasHeader = config.headers.filter(item => item.selected && item.key).length + let hasBody = false + + if (active === 'raw' && config.raw) { + hasBody = true + } else if (active === 'formData' && config.formData.filter(item => item.selected && item.key).length > 0) { + hasBody = true + } + + return ( + <div className="request-wrap"> + <div className="request-interface"> + <InputGroup compact> + <Select defaultValue={config.method} onChange={this.changeMethod}> + <Option value="POST">POST</Option> + <Option value="GET">GET</Option> + </Select> + <Input placeholder="Enter request URL" defaultValue={config.interface} onChange={this.changeInter}/> + </InputGroup> + <Button type="primary" onClick={this.send}>Send</Button> + <Button onClick={this.save}>Save</Button> + </div> + <Tabs animated={false}> + <Tabs.TabPane forceRender={true} tab={<span className={hasParam ? 'active' : ''}>Params</span>} key="Params"> + <EditTable data={config.params} onChange={this.changeParams}/> + </Tabs.TabPane> + <Tabs.TabPane forceRender={true} tab={<span>Headers{hasHeader ? <span className="number">{`(${hasHeader})`}</span> : ''}</span>} key="Headers"> + <EditTable data={config.headers} onChange={this.changeHeader}/> + </Tabs.TabPane> + <Tabs.TabPane forceRender={true} tab={<span className={hasBody ? 'active' : ''}>Body</span>} key="Body"> + <div className="body-class"> + <Radio.Group onChange={this.onChange} value={active}> + <Radio value={'none'}>none</Radio> + <Radio value={'formData'}>formData</Radio> + <Radio value={'raw'}>raw</Radio> + </Radio.Group> + </div> + <div className={'body-content ' + (active === 'none' ? 'show' : '')}> + <div className="no-body">This request does not have a body</div> + </div> + <div className={'body-content ' + (active === 'formData' ? 'show' : '')}> + <EditTable data={config.formData} onChange={this.changeFormData}/> + </div> + <div className={'body-content ' + (active === 'raw' ? 'show' : '')}> + <CodeMirror value={config.raw} mode="text/javascript" onChange={this.rawChange} /> + </div> + </Tabs.TabPane> + </Tabs> + <div className="response"> + {response ? <div className="header"> + {body ? 'Body' : 'Response'} + {status ? <span className="status">Status: <span className={status === '200OK' ? 'green' : ''}>{status}</span></span> : null} + </div> : <div className="header"> + Response + <span className="empty">Hit the Send button to get a response.</span> + </div>} + <div style={{paddingLeft: '3px', paddingTop: '3px'}} dangerouslySetInnerHTML={{__html: body}}></div> + </div> + </div> + ) + } +} + +export default Request \ No newline at end of file diff --git a/src/views/interface/workspace/request/index.scss b/src/views/interface/workspace/request/index.scss new file mode 100644 index 0000000..bd7c551 --- /dev/null +++ b/src/views/interface/workspace/request/index.scss @@ -0,0 +1,141 @@ +.request-wrap { + position: relative; + padding: 0px 10px; + height: calc(100vh - 115px); + overflow-x: hidden; + overflow-y: auto; + + .request-interface { + background: #ffffff; + margin-bottom: 10px; + .ant-select-selection { + height: 40px; + width: 90px; + } + .ant-select-selection__rendered { + line-height: 40px; + } + .ant-input { + height: 40px; + width: calc(100% - 100px); + } + .ant-input-group { + display: inline-block; + width: calc(100% - 210px); + vertical-align: top; + } + .ant-btn { + height: 40px; + width: 90px; + } + .ant-btn-primary { + margin-right: 10px; + } + } + .ant-tabs.ant-tabs-line { + .ant-tabs-top-bar { + margin-bottom: 0px; + border: 0; + .ant-tabs-nav { + .ant-tabs-tab { + color: rgba(0, 0, 0, 0.65); + padding-bottom: 5px; + span { + position: relative; + } + .number { + color: #26C281; + margin-left: 3px; + font-size: 13px; + } + span.active::after { + content: ' '; + display: block; + position: absolute; + right: -12px; + top: 2px; + width: 8px; + height: 8px; + border-radius: 8px; + background-color: #26C281; + } + } + .ant-tabs-tab.ant-tabs-tab-active { + color: rgba(0, 0, 0, 0.85); + } + } + .ant-tabs-ink-bar { + background-color: #fa541c; + } + } + } + .body-class { + height: 40px; + line-height: 40px; + padding-left: 15px; + border-top: 1px solid #e8e8e8; + } + .body-content { + display: none; + .no-body { + color: rgba(0, 0, 0, 0.45); + border-top: 1px solid #e8e8e8; + text-align: center; + height: 40px; + line-height: 40px; + } + } + .body-content.show { + display: block; + } + .anticon-fullscreen { + display: none; + } + .code-mirror-wrap .code-mirror-area { + border-radius: 0; + } + .response { + min-height: 400px; + border-left: 1px solid #e8e8e8; + border-right: 1px solid #e8e8e8; + .header { + position: relative; + height: 40px; + background-color: #fafafa; + line-height: 40px; + padding-left: 10px; + border-bottom: 1px solid #e8e8e8; + .empty { + position: absolute; + top: 150px; + left: 50%; + transform: translateX(-50%); + font-size: 20px; + color:rgba(0, 0, 0, 0.45) + } + .status { + font-size: 13px; + float: right; + margin-right: 40px; + span.green { + color: #26C281; + } + } + } + } +} + +.request-wrap::-webkit-scrollbar { + width: 5px; +} +.request-wrap::-webkit-scrollbar-thumb { + border-radius: 5px; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.09); + background: rgba(0, 0, 0, 0.09); +} +.request-wrap::-webkit-scrollbar-track { + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05); + border-radius: 3px; + border: 1px solid rgba(0, 0, 0, 0.07); + background: rgba(0, 0, 0, 0); +} \ No newline at end of file -- Gitblit v1.8.0