Commit ae661001 authored by 张仲鸣's avatar 张仲鸣

Merge branch 'master' of g.civnet.cn:test/maintenance

parents b78b8904 0096bd1f
......@@ -22,6 +22,7 @@ module.exports = {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
rules: {
......
......@@ -7,6 +7,8 @@ stats.json
# Cruft
.DS_Store
npm-debug.log
yarn.lock
package-lock.json
.idea
src/umi
src/.umi
......
......@@ -35,8 +35,6 @@
"update:deps": "yarn upgrade-interactive --latest",
"presetup": "npm i chalk shelljs",
"setup": "node ./internals/scripts/setup.js",
"clean": "shjs ./internals/scripts/clean.js",
"clean:all": "npm run analyze:clean && npm run test:clean && npm run build:clean",
"generate": "plop --plopfile internals/generators/index.js",
"lint": "npm run lint:js && npm run lint:css",
"lint:css": "stylelint src/**/*.less",
......@@ -46,6 +44,7 @@
"lint:staged": "lint-staged",
"pretest": "npm run test:clean && npm run lint",
"test:clean": "rimraf ./coverage",
"clean:node_modules": "rimraf ./node_modules",
"test": "cross-env NODE_ENV=test jest --coverage",
"test:watch": "cross-env NODE_ENV=test jest --watchAll",
"coveralls": "cat ./coverage/lcov.info | coveralls",
......@@ -83,11 +82,14 @@
},
"dependencies": {
"@ant-design/pro-card": "^1.4.6",
"@antv/data-set": "^0.11.7",
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@babel/polyfill": "7.4.3",
"@babel/preset-typescript": "^7.12.1",
"@babel/runtime": "^7.10.5",
"ace-builds": "^1.4.12",
"antd-img-crop": "^3.13.2",
"bizcharts": "^4.0.15",
"chalk": "2.4.2",
"compression": "1.7.4",
"connected-react-router": "6.4.0",
......@@ -106,6 +108,7 @@
"minimist": "1.2.0",
"prop-types": "15.7.2",
"react": "16.8.6",
"react-ace": "^9.2.0",
"react-dom": "16.8.6",
"react-helmet": "6.0.0-beta",
"react-intl": "2.8.0",
......
......@@ -3,7 +3,6 @@
require('./env');
const express = require('express');
const logger = require('./logger');
const argv = require('./argv');
const port = require('./port');
const setup = require('./middlewares/frontendMiddleware');
......
......@@ -2,8 +2,7 @@ const path = require('path');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const webpackHotMiddleware = require('webpack-hot-middleware');
const { createProxyMiddleware } = require('http-proxy-middleware');
const logger = require('../logger');
const { addProxyMiddleware } = require('../proxy');
function createWebpackMiddleware(compiler, publicPath) {
return webpackDevMiddleware(compiler, {
......@@ -14,116 +13,6 @@ function createWebpackMiddleware(compiler, publicPath) {
});
}
const setupProxyFeature = (app, webpackConfig) => {
console.log(webpackConfig);
if (!Array.isArray(webpackConfig.proxy)) {
if (Object.prototype.hasOwnProperty.call(webpackConfig.proxy, 'target')) {
webpackConfig.proxy = [webpackConfig.proxy];
} else {
webpackConfig.proxy = Object.keys(webpackConfig.proxy).map(context => {
let proxyOptions;
// For backwards compatibility reasons.
const correctedContext = context
.replace(/^\*$/, '**')
.replace(/\/\*$/, '');
if (typeof webpackConfig.proxy[context] === 'string') {
proxyOptions = {
context: correctedContext,
target: webpackConfig.proxy[context],
};
} else {
proxyOptions = Object.assign({}, webpackConfig.proxy[context]);
proxyOptions.context = correctedContext;
}
proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
return proxyOptions;
});
}
}
// eslint-disable-next-line consistent-return
const getProxyMiddleware = proxyConfig => {
const context = proxyConfig.context || proxyConfig.path;
// It is possible to use the `bypass` method without a `target`.
// However, the proxy middleware has no use in this case, and will fail to instantiate.
if (proxyConfig.target) {
return createProxyMiddleware(context, proxyConfig);
}
};
/**
* Assume a proxy configuration specified as:
* proxy: [
* {
* context: ...,
* ...options...
* },
* // or:
* function() {
* return {
* context: ...,
* ...options...
* };
* }
* ]
*/
webpackConfig.proxy.forEach(proxyConfigOrCallback => {
let proxyMiddleware;
let proxyConfig =
typeof proxyConfigOrCallback === 'function'
? proxyConfigOrCallback()
: proxyConfigOrCallback;
proxyMiddleware = getProxyMiddleware(proxyConfig);
if (proxyConfig.ws) {
this.websocketProxies.push(proxyMiddleware);
}
// eslint-disable-next-line consistent-return
const handle = (req, res, next) => {
if (typeof proxyConfigOrCallback === 'function') {
const newProxyConfig = proxyConfigOrCallback();
if (newProxyConfig !== proxyConfig) {
proxyConfig = newProxyConfig;
proxyMiddleware = getProxyMiddleware(proxyConfig);
}
}
// - Check if we have a bypass function defined
// - In case the bypass function is defined we'll retrieve the
// bypassUrl from it otherwise bypassUrl would be null
const isByPassFuncDefined = typeof proxyConfig.bypass === 'function';
const bypassUrl = isByPassFuncDefined
? proxyConfig.bypass(req, res, proxyConfig)
: null;
if (typeof bypassUrl === 'boolean') {
// skip the proxy
req.url = null;
next();
} else if (typeof bypassUrl === 'string') {
// byPass to that url
req.url = bypassUrl;
next();
} else if (proxyMiddleware) {
return proxyMiddleware(req, res, next);
} else {
next();
}
};
app.use(handle);
// Also forward error requests to the proxy so it can handle them.
app.use((error, req, res, next) => handle(req, res, next));
});
};
module.exports = function addDevMiddlewares(app, webpackConfig) {
const compiler = webpack(webpackConfig);
const middleware = createWebpackMiddleware(
......@@ -133,24 +22,10 @@ module.exports = function addDevMiddlewares(app, webpackConfig) {
app.use(middleware);
app.use(webpackHotMiddleware(compiler));
addProxyMiddleware(app);
// Since webpackDevMiddleware uses memory-fs internally to store build
// artifacts, we use it instead
const fs = middleware.fileSystem;
if (process.env.PROXY) {
const proxies = process.env.PROXY.split(';');
// 设置代理
setupProxyFeature(app, {
proxy: proxies.map(proxyStr => {
const mathes = proxyStr.match(/^\s*([/\w]+)\s*:\s*(.+)\s*$/);
return {
path: mathes[1],
target: mathes[2],
changeOrigin: true,
};
}),
});
}
app.get('*', (_req, res) => {
fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
......
const { createProxyMiddleware } = require('http-proxy-middleware');
const logger = require('./logger');
const setupProxyFeature = (app, webpackConfig) => {
if (!Array.isArray(webpackConfig.proxy)) {
if (Object.prototype.hasOwnProperty.call(webpackConfig.proxy, 'target')) {
webpackConfig.proxy = [webpackConfig.proxy];
} else {
webpackConfig.proxy = Object.keys(webpackConfig.proxy).map(context => {
let proxyOptions;
// For backwards compatibility reasons.
const correctedContext = context
.replace(/^\*$/, '**')
.replace(/\/\*$/, '');
if (typeof webpackConfig.proxy[context] === 'string') {
proxyOptions = {
context: correctedContext,
target: webpackConfig.proxy[context],
};
} else {
proxyOptions = Object.assign({}, webpackConfig.proxy[context]);
proxyOptions.context = correctedContext;
}
proxyOptions.logLevel = proxyOptions.logLevel || 'warn';
return proxyOptions;
});
}
}
// eslint-disable-next-line consistent-return
const getProxyMiddleware = proxyConfig => {
const context = proxyConfig.context || proxyConfig.path;
// It is possible to use the `bypass` method without a `target`.
// However, the proxy middleware has no use in this case, and will fail to instantiate.
if (proxyConfig.target) {
return createProxyMiddleware(context, proxyConfig);
}
};
/**
* Assume a proxy configuration specified as:
* proxy: [
* {
* context: ...,
* ...options...
* },
* // or:
* function() {
* return {
* context: ...,
* ...options...
* };
* }
* ]
*/
webpackConfig.proxy.forEach(proxyConfigOrCallback => {
let proxyMiddleware;
let proxyConfig =
typeof proxyConfigOrCallback === 'function'
? proxyConfigOrCallback()
: proxyConfigOrCallback;
proxyMiddleware = getProxyMiddleware(proxyConfig);
if (proxyConfig.ws) {
this.websocketProxies.push(proxyMiddleware);
}
// eslint-disable-next-line consistent-return
const handle = (req, res, next) => {
if (typeof proxyConfigOrCallback === 'function') {
const newProxyConfig = proxyConfigOrCallback();
if (newProxyConfig !== proxyConfig) {
proxyConfig = newProxyConfig;
proxyMiddleware = getProxyMiddleware(proxyConfig);
}
}
// - Check if we have a bypass function defined
// - In case the bypass function is defined we'll retrieve the
// bypassUrl from it otherwise bypassUrl would be null
const isByPassFuncDefined = typeof proxyConfig.bypass === 'function';
const bypassUrl = isByPassFuncDefined
? proxyConfig.bypass(req, res, proxyConfig)
: null;
if (typeof bypassUrl === 'boolean') {
// skip the proxy
req.url = null;
next();
} else if (typeof bypassUrl === 'string') {
// byPass to that url
req.url = bypassUrl;
next();
} else if (proxyMiddleware) {
return proxyMiddleware(req, res, next);
} else {
next();
}
};
app.use(handle);
// Also forward error requests to the proxy so it can handle them.
app.use((error, req, res, next) => handle(req, res, next));
});
};
const addProxyMiddleware = app => {
if (process.env.PROXY) {
const proxies = process.env.PROXY.split(';');
// 设置代理
setupProxyFeature(app, {
proxy: proxies.map(proxyStr => {
const mathes = proxyStr.match(/^\s*([/\w]+)\s*:\s*(.+)\s*$/);
return {
path: mathes[1],
target: mathes[2],
changeOrigin: true,
};
}),
});
}
};
module.exports = {
addProxyMiddleware,
};
......@@ -18,6 +18,7 @@ import configureStore from './configureStore';
import App from './containers/App';
import history from './utils/history';
import config from './routes/config';
import { PictureWallProvider } from '@/components/Upload/context';
const initialState = Immutable.Map();
const store = configureStore(initialState, history);
const MOUNT_NODE = document.getElementById('app');
......@@ -27,7 +28,9 @@ const render = () => {
<Provider store={store}>
<ConnectedRouter history={history}>
<ConfigProvider locale={zhCN}>
<App routesConfig={config} />
<PictureWallProvider>
<App routesConfig={config} />
</PictureWallProvider>
</ConfigProvider>
</ConnectedRouter>
</Provider>,
......
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1606814471866'); /* IE9 */
src: url('iconfont.ttf?t=1606814471866') format('truetype') /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.iconliulanqi:before {
content: "\e63b";
}
.iconliulanqi1:before {
content: "\e643";
}
.iconanzhuo1:before {
content: "\e63a";
}
.iconxiaochengxu:before {
content: "\e60e";
}
.iconcity:before {
content: "\e79c";
}
.iconanzhuo:before {
content: "\e687";
}
.iconCustomermanagement-fill:before {
content: "\e77c";
}
.iconaccount-fill:before {
content: "\e7aa";
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
import React, { useEffect } from 'react';
import { Form, Button, Select, Input, Radio } from 'antd';
import { Form, Button, Select, Input, Radio, Checkbox } from 'antd';
import Upload from '@/components/Upload';
import { useForm } from 'antd/lib/form/Form';
......@@ -21,9 +21,10 @@ export const renderButtonGroup = props => {
export const ITEM_TYPE = {
INPUT: 'INPUT',
SELECT: 'SELECT',
IMGSHOP: 'IMGSHOP',
SINGLE_RADIO: 'SINGLE_RADIO',
SELECT: 'SELECT', // 下拉选择
IMGSHOP: 'IMGSHOP', // 图片库
SINGLE_RADIO: 'SINGLE_RADIO', // 单选radio
CHECK_BOX: 'CHECK_BOX', // 多选框
};
const BaseFormItem = itemOption => {
......@@ -35,8 +36,8 @@ const BaseFormItem = itemOption => {
case ITEM_TYPE.SELECT:
return (
<Select {...rest}>
{options.map(o => (
<Select.Option {...o} />
{options.map((o, i) => (
<Select.Option key={`item_${i}`} {...o} />
))}
</Select>
);
......@@ -46,11 +47,19 @@ const BaseFormItem = itemOption => {
// eslint-disable-next-line no-case-declarations
return (
<Radio.Group {...rest}>
{options.map(o => (
<Radio {...o} />
{options.map((o, i) => (
<Radio key={`item_${i}`} {...o} />
))}
</Radio.Group>
);
case ITEM_TYPE.CHECK_BOX:
return (
<Checkbox.Group {...rest}>
{options.map((o, i) => (
<Checkbox key={`item_${i}`} {...o} />
))}
</Checkbox.Group>
);
default:
return null;
}
......
......@@ -44,7 +44,7 @@ const ListCard = props => {
useEffect(() => {
setValueList(checkList);
}, [dataList, loading]);
}, [dataList, loading, checkList]);
const updateValueList = (checkedKeys, childrenKeys, sourceItem) => {
// eslint-disable-next-line no-console
......@@ -79,7 +79,7 @@ const ListCard = props => {
<>
{dataList && dataList.length > 0 ? (
dataList
.filter(d => d.type === 'widgetGroup')
.filter(d => d.type === 'widgetGroup' || 'widget')
.map((item, index) => (
<ListCardItem
item={item}
......
......@@ -3,7 +3,8 @@
// display: flex;
width: 100%;
flex-wrap: wrap;
border: 1px solid #b5b8c8;
border: 1px solid #c2cdfd;
border-radius: 5px;
margin-top: 20px;
min-height: 50px;
padding: 0 10px 10px 20px;
......@@ -15,6 +16,9 @@
margin: -10px 0 0 0px;
line-height: 20px;
}
.topCheckbox>label :hover{
font-weight: 600;
}
.checkdiv {
display: flex;
flex-wrap: wrap;
......
......@@ -10,7 +10,7 @@ import connectHistoryHoc from '@/containers/connectHistoryHoc';
class AvatarDropdown extends React.Component {
/* eslint-disable no-unused-vars */
onMenuClick = event => {
const { logout, history } = this.props;
const { logout, history, loginName } = this.props;
switch (event.key) {
case 'logout':
setAuthority([]);
......@@ -24,9 +24,10 @@ class AvatarDropdown extends React.Component {
render() {
const {
loginName,
currentUser = {
avatar: logo,
name: '测试人员',
name: '',
},
} = this.props;
const menuHeaderDropdown = (
......@@ -55,7 +56,7 @@ class AvatarDropdown extends React.Component {
</Menu.Item>
</Menu>
);
return currentUser && currentUser.name ? (
return currentUser ? (
<HeaderDropdown overlay={menuHeaderDropdown}>
<span className={`${styles.action} ${styles.account}`}>
<Avatar
......@@ -64,7 +65,9 @@ class AvatarDropdown extends React.Component {
src={currentUser.avatar}
alt="avatar"
/>
<span className={`${styles.name} anticon`}>{currentUser.name}</span>
<span className={`${styles.name} anticon`}>
{currentUser.name || loginName || '管理员'}
</span>
</span>
</HeaderDropdown>
) : (
......
import { getImageBases } from '@/services/common/api';
import React, { useEffect, useState } from 'react';
const UploadContext = React.createContext();
const PictureWallProvider = props => {
const { children, ...rest } = props;
const [imgBed, setImgBed] = useState([]);
const update = () => {
getImageBases('').then(res => {
if (res.code === 0) {
setImgBed(res.data);
}
});
};
useEffect(() => {
update();
}, []);
return (
<UploadContext.Provider value={{ imgBed, update }}>
{children}
</UploadContext.Provider>
);
};
export { UploadContext as default, PictureWallProvider };
......@@ -8,6 +8,7 @@ import { uuid } from '@/utils/tools';
import { get, PUBLISH_SERVICE } from '@/services';
import styles from './index.less';
import { getImageBases } from '@/services/common/api';
import UploadContext from './context.js'
const { TabPane } = Tabs;
......@@ -17,6 +18,12 @@ const wallCateName: any = {
bg: '背景',
uploaded: '上传',
};
const tabNames:any = {
CityTemp: '用户上传',
icon: '图标',
androidMenu: '安卓图标',
menuNew: '应用图标'
}
function getBase64(file: File | Blob) {
return new Promise<string>((resolve, reject) => {
......@@ -36,7 +43,9 @@ interface PicturesWallType {
onChange?: (v: any) => void;
cropRate?: number | boolean;
isCrop?: boolean;
type?: 'CityTemp'|'icon'|'androidMenu'|'menuNew'
type?: 'CityTemp'|'icon'|'androidMenu'|'menuNew',
value?: string | string[],
uploadContext: any,
}
class PicturesWall extends React.Component<PicturesWallType> {
......@@ -45,10 +54,71 @@ class PicturesWall extends React.Component<PicturesWallType> {
previewImage: '',
wallModalVisible: false,
previewTitle: '',
imgBed: [],
imgBed: this.props.uploadContext?.imgBed || [],
curSelectedImg: '',
fileList: this.props.fileList || [],
};
prevProps:{},
fileList: this.props.value ? Array.isArray(this.props.value) ? this.props.value.map((v) => ({
url: v,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
})) as UploadFile<any>[]: [{
url: this.props.value,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
}] as UploadFile<any>[] : [],
};
static getDerivedStateFromProps = (props, state) => {
const {value, uploadContext = {}} = props;
const { imgBed, update } = uploadContext;
const fileList = state.fileList;
const shouldUpdate = value && fileList.every(f => Array.isArray(value) ? !value.some(v => f.url === v) : f.url !== value)
if(value !== state.prevProps.value && shouldUpdate){
return {
prevProps: props,
fileList: Array.isArray(value) ? value.map((v) => ({
url: v,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
})) as UploadFile<any>[]: [{
url: value,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
}] as UploadFile<any>[]
}
}
if(imgBed != state.prevProps.uploadContext?.imgBed){
return {
prevProps: props,
imgBed
}
}
return {
prevProps: props
};
}
update = () =>{
if(this.props.value){
this.setState({
fileList: Array.isArray(this.props.value) ? this.props.value.map((v) => ({
url: v,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
})) as UploadFile<any>[]: [{
url: this.props.value,
uid: uuid(8, 16),
name: '熊猫运维中台系统',
status: 'done',
}] as UploadFile<any>[]
})
}
}
handleCancel = () => this.setState({ previewVisible: false });
......@@ -99,16 +169,17 @@ class PicturesWall extends React.Component<PicturesWallType> {
};
handleChange = ({ file, fileList }: UploadChangeParam<UploadFile<any>>) => {
const { maxLen = 1 } = this.props;
const { maxLen = 1, uploadContext } = this.props;
this.setState({ fileList });
if (file.status === 'done') {
const files = fileList.map(item => {
const { status, data, name, thumbUrl } = item;
const { status, name, thumbUrl } = item;
const url = item.response && item.response.data || thumbUrl ;
return { uid: uuid(8, 16), name, status, url };
});
this.setState({fileList: files})
this.props.onChange && this.props.onChange( maxLen === 1 ? files[0].url : files.map(f => f.url));
uploadContext?.update && uploadContext.update()
}
};
......@@ -128,14 +199,14 @@ class PicturesWall extends React.Component<PicturesWallType> {
return isJpgOrPng && isLt2M;
};
componentDidMount() {
const { type = ''} = this.props;
getImageBases(type).then(res => {
if(res.code === 0){
this.setState({imgBed: res.data})
}
})
}
// componentDidMount() {
// const { type = ''} = this.props;
// getImageBases(type).then(res => {
// if(res.code === 0){
// this.setState({imgBed: res.data})
// }
// })
// }
getImageUrl(path){
if(path&&path.indexOf('http') === 0){
......@@ -144,6 +215,9 @@ class PicturesWall extends React.Component<PicturesWallType> {
if(path&&path.indexOf('data:') === 0) {
return path
}
if(path && path.indexOf('assets') === 0) {
return `${window.location.origin}/Web4/${path}`.replace(/\\/g, '/')
}
return `${window.location.origin}/${path}`.replace(/\\/g, '/')
}
......@@ -245,7 +319,7 @@ class PicturesWall extends React.Component<PicturesWallType> {
<Tabs defaultActiveKey={imgBed[0]?.moduleName} tabPosition="left" style={{ height: 520 }}>
{imgBed.map((item, i) => {
return (
<TabPane tab={item.moduleName} key={item.moduleName}>
<TabPane tab={tabNames[item.moduleName]} key={item.moduleName}>
<div className={styles.imgBox}>
{item.child?.map(m => m.fileUrls).flat(Infinity).map((item: string, i: number) => {
return (
......@@ -278,4 +352,9 @@ class PicturesWall extends React.Component<PicturesWallType> {
}
}
export default PicturesWall;
const PicturesWallWrapper = (props: any) => {
return <UploadContext.Consumer>
{(uploadContext) => <PicturesWall {...props} uploadContext={uploadContext}/>}
</UploadContext.Consumer>
}
export default PicturesWallWrapper;
......@@ -4,6 +4,7 @@ import {
LOAD_REPOS_SUCCESS,
SET_AUTHORITY,
SET_USER_MODE,
SET_LOGIN_NAME,
} from './constants';
export function loadRepos() {
......@@ -40,3 +41,10 @@ export function setUserMode(userMode) {
userMode,
};
}
export function setLoginName(loginName) {
return {
type: SET_LOGIN_NAME,
loginName,
};
}
......@@ -3,3 +3,4 @@ export const LOAD_REPOS_SUCCESS = 'App/LOAD_REPOS_SUCCESS';
export const LOAD_REPOS_ERROR = 'App/LOAD_REPOS_ERROR';
export const SET_AUTHORITY = 'App/SET_AUTHORITY';
export const SET_USER_MODE = 'App/SET_USER_MODE';
export const SET_LOGIN_NAME = 'App/SET_LOGIN_NAME';
......@@ -11,6 +11,7 @@ const mapState = store => {
global: storeObj.global,
auth: storeObj.global.auth,
userMode: storeObj.global.userMode,
loginName: storeObj.global.loginName,
};
};
const mapDispatch = dispatch => ({
......@@ -20,6 +21,7 @@ const mapDispatch = dispatch => ({
dispatch(actionCreators.setAuth([]));
},
setUserMode: userMode => dispatch(actionCreators.setUserMode(userMode)),
setLoginName: loginName => dispatch(actionCreators.setLoginName(loginName)),
});
const appConnector = connect(
......
......@@ -6,6 +6,7 @@ import {
LOAD_REPOS_SUCCESS,
SET_AUTHORITY,
SET_USER_MODE,
SET_LOGIN_NAME,
} from './constants';
export const initialState = fromJS({
......@@ -17,6 +18,7 @@ export const initialState = fromJS({
repositories: false,
},
userMode: '',
loginName: '',
});
/* eslint-disable default-case, no-param-reassign */
......@@ -52,6 +54,10 @@ const appReducer = (state = initialState, action) => {
return state.merge({
userMode: action.userMode,
});
case SET_LOGIN_NAME:
return state.merge({
loginName: action.loginName,
});
default:
return state;
}
......
......@@ -62,4 +62,9 @@ iframe {
}
iframe{
border: none !important;
}
\ No newline at end of file
}
:global{
.ant-tree-node-content-wrapper .anticon-folder, .ant-tree-icon__customize, .ant-tree-switcher-icon{
color: #1890ff !important;
}
}
......@@ -94,6 +94,24 @@ const CurrentSolution = () => {
</div>
</Spin>
</Card>
<div className={styles.textBox}>
<p>
<strong>友情提示:</strong>
</p>
<p>
熊猫智慧水务应用解决方案的管理,集成了数据库管理、用户权限管理、基础平台管理、应用中心管理、系统日志管理等核心模块;
</p>
<p>
主要为客户项目经理在实施应用部署的过程中提供可视化配置系统,一站式交付用户需求的熊猫智慧水务相关产品;数据库支持SQL
Server、Oracle、MongoDB、MySQL四大类常见数据库;
</p>
<p>
平台中心涵盖GIS平台、物联网平台、业务平台、消息平台等基础平台,2021年会推出数字孪生平台、数据治理平台、AI平台等进阶平台;应用中心主要把客户应用按端分离,分为PCWeb端、无线App端、微信小程序端,结合应用界面与菜单权限配置完成;
</p>
<p>
用户中心负责管理用户与平台的关系、用户与端的关系、用户与功能菜单的关系、用户与设备的关系。
</p>
</div>
</PageContainer>
);
};
......
......@@ -13,4 +13,13 @@
}
.tAlign{
text-align: center;
}
.textBox{
margin-left: 20px;
margin-top: 20px;
p{
margin: 5px;
color:gray;
font-size: 16px;
}
}
\ No newline at end of file
......@@ -501,7 +501,7 @@ const InitDataBase = props => {
))}
</Select>
</Form.Item>
<Form.Item>
<Form.Item wrapperCol={{ offset: 3 }}>
<div className={styles.tCenter}>
<Space size="large" className={styles.btnBox}>
<Space>
......
......@@ -13,5 +13,5 @@
}
.btnBox {
display: flex !important;
justify-content: space-around;
justify-content: space-between;
}
\ No newline at end of file
......@@ -268,7 +268,7 @@ const ManagementDataBase = () => {
</Spin>
</Card>
<Card className={styles.mgTop20}>
<div className={styles.tableTitle}>数据库升级记录</div>
<div className={styles.tableTitle}>数据库版本记录</div>
<Table
className={styles.mgTop20}
columns={logColumns}
......@@ -280,7 +280,7 @@ const ManagementDataBase = () => {
</Card>
<Card className={styles.mgTop20}>
<div className={styles.tableTitle}>
表字段手动修复
手动修复
(字段长度不统一,请手动修改差异,数据可能会截断,请谨慎操作)
</div>
<Table
......
......@@ -139,7 +139,7 @@ const MongDBTable = props => {
title: '类型',
dataIndex: 'type',
key: 'type',
width: 100,
// width: 200,
ellipsis: true,
},
{
......
import React, { useState, useEffect } from 'react';
import {
DatePicker,
Table,
Row,
Col,
Button,
notification,
message,
Spin,
} from 'antd';
import { SwapRightOutlined } from '@ant-design/icons';
import { Chart, Interval, Tooltip, Axis } from 'bizcharts';
import { DataSet } from '@antv/data-set';
import moment from 'moment';
import { post, PUBLISH_SERVICE } from '@/services/index';
import styles from './index.less';
const ServiceLog = () => {
const [loading, setLoading] = useState(false); // 源数据
const [data0, setData0] = useState([]); // 源数据
const [IPCount, setIPCount] = useState([]); // 接口调用次数,统计数据
const [startTime, setStartTime] = useState(moment().startOf('day')); // 默认值当天0点
const [endTime, setEndTime] = useState(
moment(new Date(), 'YYYY-MM-DD HH:mm:ss'), // 默认值当前时间
);
const columns = [
{
title: 'IP',
dataIndex: 'IP',
key: 'IP',
fixed: 'left',
},
{
title: '登录时间',
dataIndex: 'LoginTime',
key: 'LoginTime',
},
{
title: '登录名',
dataIndex: 'LoginName',
key: 'LoginName',
},
{
title: '用户名',
dataIndex: 'ShowName',
key: 'ShowName',
},
{
title: '系统类型',
dataIndex: 'SystemType',
key: 'SystemType',
},
{
title: '终端',
dataIndex: 'Terminal',
key: 'Terminal',
},
];
// 在起止时间任意一个变化后获取数据
useEffect(() => {
if (startTime && endTime) {
setLoading(true);
getData();
}
}, [startTime, endTime]);
const getData = () => {
post(`${PUBLISH_SERVICE}/LogCenter/GetLoginLog`, {
PageIndex: 0,
PageSize: 0,
DateFrom: startTime.format('YYYY-MM-DD HH:mm:ss'),
DateTo: endTime.format('YYYY-MM-DD HH:mm:ss'),
IP: '',
Module: '',
Description: '',
LoginName: '',
UserName: '',
})
.then(res => {
if (res.code === 0) {
setData0(res.data);
dataTransforrm(res.data);
console.log(res.data);
} else {
notification.error({
message: '数据获取失败',
description: res.message,
});
}
setLoading(false);
})
.catch(err => {
message.error(err);
setLoading(false);
});
};
const dataTransforrm = data => {
data.map((item, index) => {
item.key = index;
return item;
});
const dv1 = new DataSet.View().source(data);
dv1
.transform({
type: 'aggregate', // 别名summary
fields: ['IP'], // 统计字段集
operations: ['count'], // 统计操作集
as: ['计数'], // 存储字段集
groupBy: ['IP'], // 分组字段集
})
.transform({
type: 'sort-by',
fields: ['计数'], // 根据指定的字段集进行排序,与lodash的sortBy行为一致
order: 'DESC', // 默认为 ASC,DESC 则为逆序
});
console.log(dv1.rows);
setIPCount(dv1.rows.slice(0, 20));
};
// DatePicker改变点击确定时
const changeStartTime = time => {
setStartTime(time);
};
const changeEndTime = time => {
setEndTime(time);
};
// 近1/6/12/24小时
const setTime = time => {
setEndTime(moment(new Date(), 'YYYY-MM-DD HH:mm:ss'));
setStartTime(
moment(
new Date(new Date().getTime() - time * 60 * 60 * 1000),
'YYYY-MM-DD HH:mm:ss',
),
);
};
return (
<>
<div className={styles.serviceLog}>
<Row className={styles.head}>
<Col span={24}>
<span style={{ lineHeight: 2 }}>时间:</span>
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="起始时间"
value={startTime}
onChange={changeStartTime}
allowClear={false}
/>
<SwapRightOutlined style={{ lineHeight: 2 }} />
<DatePicker
showTime
format="YYYY-MM-DD HH:mm:ss"
placeholder="结束时间"
value={endTime}
onChange={changeEndTime}
style={{ marginRight: '10px' }}
allowClear={false}
/>
<Button onClick={() => setTime(1)}>1小时</Button>
<Button onClick={() => setTime(6)}>6小时</Button>
<Button onClick={() => setTime(12)}>12小时</Button>
<Button onClick={() => setTime(24)}>1</Button>
<Button onClick={() => setTime(24 * 7)}>1</Button>
</Col>
</Row>
<Spin spinning={loading} tip="loading">
<Row className={styles.chart}>
<Col span={12}>
<Chart
height={316}
width={400}
autoFit
data={IPCount}
interactions={['active-region']}
padding="auto"
>
<Axis
name="IP"
label="null"
title={{ offset: 20, position: 'end' }}
/>
<Axis name="计数" title />
<Interval position="IP*计数" />
<Tooltip shared />
</Chart>
</Col>
</Row>
<div className={styles.table}>
<Table
size="small"
bordered
columns={columns}
dataSource={data0}
scroll={{ x: 'max-content' }}
pagination={{
showTotal: (total, range) =>
`第${range[0]}-${range[1]} 条/共 ${total} 条`,
pageSizeOptions: [10, 20, 50, 100],
defaultPageSize: 10,
showQuickJumper: true,
showSizeChanger: true,
}}
/>
</div>
</Spin>
</div>
</>
);
};
export default ServiceLog;
.serviceLog{
.head{
padding: 10px;
background: white;
margin-bottom: 2px;
min-width: 1030px;
}
.chart{
padding: 16px;
background: white;
}
.table{
border-top: 1px solid #f0eded;
// overflow: auto;//不要这个,pagination否则无法固定底部
.ant-table-thead tr th{
font-weight: 600;
color:rgba(0,0,0,0.85);
}
.ant-table-content{
height:calc(100vh - 518px);
border-right: white;
overflow: auto !important;
}
.ant-pagination{
z-index: 999;
border-top: 1px solid #f0eded;
}
.ant-table-pagination{
padding-right: 12px;
background: white;
margin: 1px 0;
padding:8px;
padding-right: 20px;
}
}
}
\ No newline at end of file
This diff is collapsed.
.serviceLog{
.head{
padding: 10px;
background: white;
margin-bottom: 2px;
min-width: 1030px;
}
.chart{
padding: 10px;
background: white;
}
.table{
border-top: 1px solid #f0eded;
// overflow: auto;//不要这个,pagination否则无法固定底部
.ant-table-thead tr th{
font-weight: 600;
color:rgba(0,0,0,0.85);
}
.ant-table-content{
height:calc(100vh - 520px);
border-right: white;
overflow: auto !important;
}
.ant-pagination{
z-index: 999;
border-top: 1px solid #f0eded;
}
.ant-table-pagination{
padding-right: 12px;
background: white;
margin: 1px 0;
padding:8px;
padding-right: 20px;
}
}
}
\ No newline at end of file
This diff is collapsed.
.serviceLog{
.head{
padding: 10px;
background: white;
margin-bottom: 2px;
min-width: 1030px;
}
.chart{
padding: 10px;
background: white;
}
.table{
border-top: 1px solid #f0eded;
// overflow: auto;//不要这个,pagination否则无法固定底部
.ant-table-thead tr th{
font-weight: 600;
color:rgba(0,0,0,0.85);
}
.ant-table-content{
height:calc(100vh - 520px);
border-right: white;
overflow: auto !important;
}
.ant-pagination{
z-index: 999;
border-top: 1px solid #f0eded;
}
.ant-table-pagination{
padding-right: 12px;
background: white;
margin: 1px 0;
padding:8px;
padding-right: 20px;
}
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Select, Input, Button, Radio, notification, Spin } from 'antd';
import { editWebsite, getWebsite } from '@/services/mobileConfig/api';
import {
editWebsite,
getWebsite,
addWebsite,
} from '@/services/mobileConfig/api';
import PicturesWall from '@/components/Upload/index';
const { Item } = Form;
const { Option } = Select;
const SiteConfig = props => {
const { miniTitle, submitCallback } = props;
const { miniTitle, submitCallback, subType, addCallback } = props;
console.log(subType, 'ubType');
const [config, setConfig] = useState(''); // 网站配置信息
const [loginList, setLoginList] = useState([
{ text: '默认界面', value: 'default' },
......@@ -22,7 +27,7 @@ const SiteConfig = props => {
const layout = {
layout: 'horizontal',
labelCol: { span: 7 },
wrapperCol: { span: 6 },
wrapperCol: { span: 8 },
};
useEffect(() => {
console.log(miniTitle, 'miniTitle');
......@@ -37,8 +42,9 @@ const SiteConfig = props => {
})
.then(res => {
setLoading(false);
let obj = { ...form.getFieldsValue() };
let arr = Object.keys({ ...form.getFieldsValue() });
console.log(res.shortcutIcon);
let obj = {};
let arr = Object.keys(form.getFieldsValue());
arr.map(k => {
obj[k] = res[k];
});
......@@ -57,38 +63,69 @@ const SiteConfig = props => {
setLoading(true);
const obj = { ...form.getFieldsValue() };
let params = { ...obj, mode: 'single', client: 'miniapp' };
editWebsite(params, {
headers: {
'content-type': 'application/x-www-form-urlencggoded;charset=UTF-8',
},
})
.then(res => {
setLoading(false);
if (res.success) {
submitCallback();
notification.success({
message: '提示',
duration: 3,
description: '编辑成功',
});
} else {
notification.error({
message: '提示',
duration: 3,
description: res.message || '编辑失败',
});
}
console.log(res, 'res');
if (subType === 'add') {
addWebsite(params, {
headers: {
'content-type':
'application/x-www-form-urlencggoded;charset=UTF-8',
},
})
.catch(err => {
setLoading(false);
});
.then(res => {
setLoading(false);
if (res.success) {
addCallback(params.title);
notification.success({
message: '提示',
duration: 3,
description: '新增成功',
});
} else {
notification.error({
message: '提示',
duration: 10,
description: res.message || '新增失败',
});
}
console.log(res, 'res');
})
.catch(err => {
setLoading(false);
});
} else {
editWebsite(params, {
headers: {
'content-type':
'application/x-www-form-urlencggoded;charset=UTF-8',
},
})
.then(res => {
setLoading(false);
if (res.success) {
submitCallback();
notification.success({
message: '提示',
duration: 3,
description: '编辑成功',
});
} else {
notification.error({
message: '提示',
duration: 3,
description: res.message || '编辑失败',
});
}
console.log(res, 'res');
})
.catch(err => {
setLoading(false);
});
}
}
});
};
return (
<Spin spinning={loading} tip="loading...">
<div style={{ minHeight: 'calc(100vh - 172px)', marginTop: '20px' }}>
<div style={{ minHeight: 'calc(100vh - 192px)', marginTop: '20px' }}>
<Form form={form} {...layout}>
<Item
label="应用名称:"
......@@ -102,8 +139,7 @@ const SiteConfig = props => {
>
<Input placeholder="请输入应用名称" allowClear />
</Item>
<Item
{/* <Item
label="系统图标:"
name="shortcutIcon"
rules={[
......@@ -114,11 +150,20 @@ const SiteConfig = props => {
]}
>
<Input placeholder="请输入系统图标名称" allowClear />
</Item>
<Item label="系统图标预览:">
</Item> */}
<Item
label="系统图标:"
name="shortcutIcon"
rules={[
{
required: true,
message: '请选择系统图标',
},
]}
>
<PicturesWall />
</Item>
<Item
label="登陆页面:"
name="loginTemplate"
......@@ -176,7 +221,7 @@ const SiteConfig = props => {
))}
</Select>
</Item>
<Item label="开启云登陆:" name="cloudLogin">
<Item label="开启云登陆:" name="cloudLogin" initialValue={false}>
<Radio.Group onChange={radioChange}>
<Radio value></Radio>
<Radio value={false}></Radio>
......
import React, { useState, useEffect } from 'react';
import { Card, Tabs } from 'antd';
import React, { useState, useEffect, useMemo } from 'react';
import { Card, Tabs, Button, Popconfirm, notification, Spin } from 'antd';
import ProCard from '@ant-design/pro-card';
import PageContainer from '@/components/BasePageContainer';
import { miniAppSiteTree } from '@/services/mobileConfig/api';
import SevenParams from './menuconfig/SevenParams';
import VersionPublish from './menuconfig/VersionPublish';
import {
getMiniAppModuleTree,
deleteWebsite,
} from '@/services/mobileConfig/api';
import SiteConfig from './SiteConfig';
import MenuConfig from './menuconfig/MenuConfig';
const { TabPane } = Tabs;
const MobileConfigPage = props => {
const [activeKey, setActiveKey] = useState('0'); // tabs活动页
const [activeKey, setActiveKey] = useState('1'); // tabs活动页
const [miniTitle, setMiniTitle] = useState('');
const [flag, setFlag] = useState(1);
const [showConfig, setShowConfig] = useState(true);
const [loading, setLoading] = useState(false);
const [position, setPosition] = useState([]);
const [subType, setSubType] = useState('');
useEffect(() => {
console.log(33, '33');
miniAppSiteTree({
setLoading(true);
getMiniAppModuleTree({
userMode: 'admin',
select: '',
_version: 9999,
_dc: new Date().getTime(),
node: -2,
}).then(res => {
if (res) {
const title = res[0].children[0].text || '熊猫掌天下小程序';
setMiniTitle(title);
setLoading(false);
console.log(res, 'res');
if (res.code === 0) {
const { data } = res;
let obj =
data[0].children.length > 0 &&
data[0].children.find(item => item.id.includes('miniapp'));
console.log(obj, 'obj');
if (data[0].children.length > 0 && obj) {
const title = obj.text || '';
setShowConfig(true);
setMiniTitle(title);
setPosition(['right']);
} else {
setPosition(['left']);
setShowConfig(false);
}
} else {
setShowConfig(false);
}
console.log(res);
});
}, [flag]);
useEffect(() => {
// getMiniAppModuleTree({
// userMode: 'super',
// }).then(res => {
// console.log(res);
// });
}, []);
// 修改选中的tab
const handleChange = key => {
setActiveKey(key);
};
const submitCallback = () => {
setFlag(flag + 1);
setSubType('');
};
const addCallback = val => {
console.log(val);
setSubType('');
setMiniTitle(val);
setPosition(['right']);
};
// 删除小程序
const delMini = () => {
setLoading(true);
deleteWebsite({
client: 'miniapp',
_version: 9999,
_dc: Date.now(),
})
.then(res => {
setLoading(false);
if (res.success) {
setFlag(flag + 1);
notification.success({
message: '提示',
duration: 3,
description: '删除成功',
});
} else {
notification.success({
message: '提示',
duration: 3,
description: res.message || '删除失败',
});
}
})
.catch(() => {
setLoading(false);
});
};
const addMini = () => {
setShowConfig(true);
setSubType('add');
};
const options = {
left: (
<Button type="primary" onClick={addMini}>
新增小程序
</Button>
),
right: (
<Popconfirm
title="是否删除小程序网站,及相关联的角色和菜单"
okText="确认"
cancelText="取消"
placement="left"
onConfirm={delMini}
>
<Button type="primary" danger>
删除小程序
</Button>
</Popconfirm>
),
};
const slot = useMemo(() => {
if (position.length === 0) return null;
return position.reduce((acc, direction) => {
console.log(acc, direction);
return { ...acc, [direction]: options[direction] };
}, {});
}, [position]);
const tabArr = [
{
title: '网站配置',
key: '0',
component: (
<SiteConfig miniTitle={miniTitle} submitCallback={submitCallback} />
<SiteConfig
miniTitle={miniTitle}
submitCallback={submitCallback}
subType={subType}
addCallback={addCallback}
/>
),
},
{
......@@ -50,14 +153,38 @@ const MobileConfigPage = props => {
return (
<PageContainer>
<ProCard>
<Tabs activeKey={activeKey} type="card" onChange={handleChange}>
{tabArr?.length > 0 &&
tabArr.map(item => (
<TabPane tab={item.title} key={item.key}>
{activeKey === item.key && item.component}
<Spin tip="loading..." spinning={loading}>
<Tabs
activeKey={activeKey}
type="card"
onChange={handleChange}
tabBarExtraContent={slot}
>
{/* {showConfig &&
tabArr?.length > 0 &&
tabArr.map(item => (
<TabPane tab={item.title} key={item.key}>
{activeKey === item.key && item.component}
</TabPane>
))} */}
{showConfig && (
<TabPane tab={tabArr[0].title} key={tabArr[0].key}>
{activeKey === tabArr[0].key && tabArr[0].component}
</TabPane>
)}
{showConfig && subType !== 'add' && (
<TabPane tab={tabArr[1].title} key={tabArr[1].key}>
{activeKey === tabArr[1].key && tabArr[1].component}
</TabPane>
)}
{showConfig && (
<TabPane tab="其他配置" key="tab3">
<SevenParams />
<VersionPublish />
</TabPane>
))}
</Tabs>
)}
</Tabs>
</Spin>
</ProCard>
</PageContainer>
);
......
import React, { useState } from 'react';
import { Form, Input } from 'antd';
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Row, Col } from 'antd';
import styles from './addForm.less';
import PicturesWall from '@/components/Upload/index';
import CheckList from './checkBox';
import checkStyles from './checkBox';
const { Item } = Form;
const AddForm = props => {
const {} = props;
const {
submitCallback,
nodeType,
nodeObj,
addType,
submitLoading,
valueCallback,
addList,
} = props;
const [form] = Form.useForm();
const [otherForm] = Form.useForm();
console.log(nodeObj, 'nodeObj');
const layout = {
layout: '',
layout: 'horizontal',
labelCol: { span: 4, offset: 1 },
wrapperCol: { span: 16 },
};
const submit = () => {
if (addType === 1 || addType === 2) {
let obj = form.getFieldsValue();
submitCallback(obj, nodeObj);
}
if (addType === 3 || addType === 4) {
let obj = otherForm.getFieldsValue();
submitCallback(obj, nodeObj);
}
};
const finish = () => {
submit();
};
useEffect(() => {}, [nodeObj, addType]);
return (
<Form form={form} {...layout}>
<Item label="分组名称">
<Input />
</Item>
<Item label="分组别名">
<Input />
</Item>
<Item label="在线图标">
<Input />
</Item>
<Item label="离线图标">
<Input />
</Item>
<Item label="功能参数">
<Input />
</Item>
</Form>
<div>
{(addType === 1 || addType === 2) && (
<Form
form={form}
name="groupform"
{...layout}
className={styles.formStyle}
onFinish={finish}
>
<Item
label="分组名称"
name="menuName"
rules={[
{
required: true,
message: '请输入分组名称',
},
]}
>
<Input />
</Item>
<Item label="分组别名" name="shortName">
<Input />
</Item>
{addType === 1 && (
<Item
label="在线图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择在线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{addType === 1 && (
<Item
label="离线图标"
name="offlineImgUrl"
rules={[
{
required: true,
message: '请选择离线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{addType === 2 && (
<Item
label="分组图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择分组图标',
},
]}
>
<PicturesWall />
</Item>
)}
<Item label="功能参数" name="funParam">
<Input />
</Item>
<Item wrapperCol={{ offset: 5 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit" loading={submitLoading}>
提交
</Button>
</Item>
</Form>
)}
{(addType === 3 || addType === 4) && (
<Form
form={otherForm}
name="menuform"
{...layout}
onFinish={finish}
className={styles.formStyle}
>
<Item
label="菜单名称"
name="menuName"
rules={[
{
required: true,
message: '请输入菜单名称',
},
]}
>
<Input />
</Item>
<Item label="菜单别名" name="shortName">
<Input />
</Item>
{addType === 3 && (
<Item
label="在线图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择在线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{addType === 3 && (
<Item
label="离线图标"
name="offlineImgUrl"
rules={[
{
required: true,
message: '请选择离线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{addType === 4 && (
<Item
label="菜单图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择菜单图标',
},
]}
>
<PicturesWall />
</Item>
)}
<Item
label="功能路径"
name="pageUrl"
rules={[
{
required: true,
message: '请输入功能路径',
},
]}
>
<Input />
</Item>
<Item label="功能参数" name="funParam">
<Input />
</Item>
{/* <CheckList
nodeType={addType}
valueCallback={valueCallback}
addList={addList}
/> */}
<Item wrapperCol={{ offset: 5 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit" loading={submitLoading}>
提交
</Button>
</Item>
</Form>
)}
</div>
);
};
export default AddForm;
This diff is collapsed.
.contentContainer{
// min-height: calc(100vh - 48px);
overflow-x: auto;
display: flex;
.menuContainer{
min-width: 300px;
border:2px solid #eee;
min-height:calc(100vh - 172px);
overflow-y:auto;
.ant-tree-list{
padding: 10px;
height:calc(100vh - 330px);
.ant-tree-switcher{
line-height: 1;
color:#1890FF;
}
.ant-tree-iconEle{
line-height: 1.2;
color:#1890FF;
}
}
}
.editContainer{
margin-left: 12px;
flex: 1;
padding: 16px;
float: left;
min-width: 500px;
border:2px solid #eee;
.ant-table-pagination-right{
padding-right: 12px;
}
}
.previewContainer{
margin-left: 12px;
// flex: 1;
float: left;
min-width: 700px;
border:2px solid #eee;
.ant-table-pagination-right{
padding-right: 12px;
}
}
.ant-tree-treenode{
width: 100% !important;
.ant-tree-node-content-wrapper{
display: inline-block;
width: 100%;
}
.iconWraper1{
float: right;
span{
display: none;
}
}
}
.ant-tree-treenode:hover{
.iconWraper1>span{
margin-left: 12px;
font-size: 18px;
display: inline-block;
}
}
}
.redText{
color: red;
cursor: pointer;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import ProCard from '@ant-design/pro-card';
import AddForm from './AddForm';
import LeftPart from './LeftPart';
import MiniMenu from './miniMenu';
const MenuConfig = props => {
const [flag, setFlag] = useState(1);
return (
<div split="vertical">
<LeftPart />
{/* <ProCard><AddForm /></ProCard> */}
<MiniMenu />
</div>
);
};
......
This diff is collapsed.
import React, { useEffect, useState, useRef } from 'react';
import { Switch, Button, Message, Spin, Modal } from 'antd';
import BaseForm from '@/components/BaseForm/index';
import { getApkNameAndDate } from '@/services/appConfig/api';
import styles from './VersionPublish.less';
export default () => {
const items = [
{
label: '更新描述',
dataIndex: 'describe',
rules: [
{
required: true,
message: '请输入apk的更新描述',
},
],
formType: 'INPUT',
},
{
label: '强制更新',
dataIndex: 'forceUpdate',
initialValue: 1,
rules: [
{
required: true,
message: '请选择是否强制更新',
},
],
options: [
{
children: '是',
value: 1,
},
{
children: '否',
value: 0,
},
],
formType: 'SINGLE_RADIO',
},
{
label: 'apk上传',
dataIndex: 'file',
rules: [
{
required: true,
message: '请上传对应的apk',
},
],
formType: 'IMGSHOP',
},
];
const formEntity = useRef(null);
const [loading, setLoading] = useState(true); // 显示请求的loading
const [showModal, setShowModal] = useState(false); // 显示更新的modal
const [fileData, setFileData] = useState({}); // 获取到的包名和更新时间
const submitForm = () => {
console.log(formEntity);
const { validateFields } = formEntity.current;
validateFields()
.then(values => {
console.log(values);
})
.catch(res => {
const { errorFields, values } = res;
console.log(errorFields, values);
});
};
const handleGetForm = form => {
formEntity.current = form;
};
useEffect(() => {
getApkNameAndDate({})
.then(res => {
console.log(res);
const message = res.ResultMessage || '';
const [fileName, updateTime] = message.split(',');
setFileData({
fileName,
updateTime,
});
setLoading(false);
})
.catch(err => {
Message.error(err);
setLoading(false);
});
}, []);
return (
<div className={loading ? styles.loadingContainer : styles.VersionPublish}>
{loading ? (
<Spin />
) : (
<div className="base-info">
<div className={styles.row}>
<div className={styles.label}>当前包名:</div>
<div className="value">
{fileData.fileName || '未找到对应的包名'}
</div>
</div>
<div className={styles.row}>
<div className={styles.label}>更新时间:</div>
<div className="value">{fileData.updateTime}</div>
</div>
<div className={styles.row}>
<div className={styles.label}>下载地址:</div>
<div className="value">
<a href="https://example.com" target="_blank">
https://example.com
</a>
</div>
</div>
<div className={styles.row}>
<div className={styles.label}>更新描述:</div>
<div className="value">
更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:更新描述:
</div>
</div>
<div className={styles.row}>
<div className={styles.label}>强制更新:</div>
<div className="value">
<Switch checked disabled />
</div>
</div>
<Button type="primary" onClick={() => setShowModal(true)}>
更新
</Button>
</div>
)}
{showModal && (
<Modal
visible={showModal}
closable={false}
maskClosable
onCancel={() => setShowModal(false)}
onOk={submitForm}
>
<BaseForm items={items} getForm={handleGetForm} />
</Modal>
)}
</div>
);
};
.loadingContainer {
display: flex;
justify-content: center;
align-items: center;
padding: 0 15px;
min-height: 300px;
}
.VersionPublish {
display: flex;
padding: 0 15px;
min-height: 300px;
}
.row {
display: flex;
align-items: flex-start;
margin-bottom: 15px;
}
.label {
width: 100px;
flex-shrink: 0;
}
\ No newline at end of file
.formStyle{
margin-bottom: 40px;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Checkbox, Empty } from 'antd';
import styles from './checkBox.less';
const CheckList = props => {
const { info = {}, valueCallback = () => {}, nodeType = '', addList } = props;
const [list, setList] = useState([]);
const [addRoleList, setAddRoleList] = useState([]);
useEffect(() => {
if (info.pageUrl) {
let arr = [...info.relatedRoleList];
let arr2 = [];
arr.map(item => {
if (item.related) {
arr2.push(item.relatedRoleCode);
}
});
setList(arr);
}
}, [info]);
useEffect(() => {
if (addList && addList.length > 0) {
setAddRoleList([...addList]);
}
}, [addList]);
const handleSelect = (e, val) => {
list.forEach(item => {
if (item.relatedRoleCode === val) {
item.related = e.target.checked;
}
});
setList([...list]);
valueCallback(list);
};
const handleRoleSelect = (e, val) => {
addRoleList.forEach(item => {
if (item.roleCode === val) {
item.related = e.target.checked;
}
});
setAddRoleList([...addRoleList]);
valueCallback(addRoleList);
};
return (
<div>
{nodeType === 3 || nodeType === 4 ? (
<div className={styles.box}>
{/* <Checkbox>全选/反选</Checkbox> */}
{list &&
list.length > 0 &&
list.map(item => (
<div key={item.relatedRoleCode} className={styles.check}>
<Checkbox
checked={item.related}
onChange={e => {
handleSelect(e, item.relatedRoleCode);
}}
>
{item.relatedRoleName}
</Checkbox>
</div>
))}
</div>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
{/* {
<div className={styles.box}>
{addRoleList &&
addRoleList.length > 0 &&
addRoleList.map(item => (
<div key={item.roleCode} className={styles.check}>
<Checkbox
checked={item.related}
onChange={e => {
handleRoleSelect(e, item.roleCode);
}}
>
{item.roleName}
</Checkbox>
</div>
))}
</div>
} */}
</div>
);
};
export default CheckList;
.box{
display: flex;
padding: 10px;
margin-top: 10px;
flex-wrap: wrap;
// border: 1px solid gray;
}
.check{
flex-shrink: 0;
flex-grow: 0;
min-width: 150px;
padding-bottom: 10px;
}
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Form, Input, Button, Row, Col } from 'antd';
import styles from './addForm.less';
import PicturesWall from '@/components/Upload/index';
const { Item } = Form;
const EditForm = props => {
const { submitCallback, nodeType, info } = props;
const [form] = Form.useForm();
const [otherForm] = Form.useForm();
const layout = {
layout: 'horizontal',
labelCol: { span: 4, offset: 1 },
wrapperCol: { span: 16 },
};
// 回显表单
useEffect(() => {
form.resetFields();
otherForm.resetFields();
}, [info]);
useEffect(() => {
if (nodeType === 1 || nodeType === 2) {
let arr = Object.keys(form.getFieldsValue());
let obj = {};
arr.map(i => {
obj[i] = info[i];
});
form.setFieldsValue({ ...obj, shortName: info.menuShortName });
}
if (nodeType === 3 || nodeType === 4) {
let arr = Object.keys(otherForm.getFieldsValue());
let obj = {};
arr.map(i => {
obj[i] = info[i];
});
otherForm.setFieldsValue({ ...obj, shortName: info.menuShortName });
}
}, [info]);
const submit = () => {
if (nodeType === 1 || nodeType === 2) {
let obj = form.getFieldsValue();
console.log(obj, '1,2');
submitCallback(obj);
}
if (nodeType === 3 || nodeType === 4) {
let obj = otherForm.getFieldsValue();
console.log(obj, '3,4');
submitCallback(obj);
}
};
const onFinish = () => {
submit();
};
return (
<div style={{ marginTop: '10px' }}>
{(nodeType === 1 || nodeType === 2) && (
<Form
form={form}
name="editGroup"
{...layout}
className={styles.formStyle}
onFinish={onFinish}
>
<Item
label="分组名称"
name="menuName"
rules={[
{
required: true,
message: '请输入分组名称',
},
]}
>
<Input />
</Item>
<Item label="分组别名" name="shortName">
<Input />
</Item>
{nodeType === 1 && (
<Item
label="在线图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择在线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{nodeType === 1 && (
<Item
label="离线图标"
name="offlineImgUrl"
rules={[
{
required: true,
message: '请选择离线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{nodeType === 2 && (
<Item
label="分组图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择分组图标',
},
]}
>
<PicturesWall />
</Item>
)}
<Item label="功能参数" name="funParam">
<Input />
</Item>
<Item wrapperCol={{ offset: 5 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Item>
</Form>
)}
{(nodeType === 3 || nodeType === 4) && (
<Form
form={otherForm}
name="editMenu"
{...layout}
onFinish={onFinish}
className={styles.formStyle}
>
<Item
label="菜单名称"
name="menuName"
rules={[
{
required: true,
message: '请输入菜单名称',
},
]}
>
<Input />
</Item>
<Item label="菜单别名" name="shortName">
<Input />
</Item>
{nodeType === 3 && (
<Item
label="在线图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择在线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{nodeType === 3 && (
<Item
label="离线图标"
name="offlineImgUrl"
rules={[
{
required: true,
message: '请选择离线图标',
},
]}
>
<PicturesWall />
</Item>
)}
{nodeType === 4 && (
<Item
label="菜单图标"
name="imageUrl"
rules={[
{
required: true,
message: '请选择菜单图标',
},
]}
>
<PicturesWall />
</Item>
)}
<Item
label="功能路径"
name="pageUrl"
rules={[
{
required: true,
message: '请输入功能路径',
},
]}
>
<Input />
</Item>
<Item label="功能参数" name="funParam">
<Input />
</Item>
<Item wrapperCol={{ offset: 5 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit">
提交
</Button>
</Item>
</Form>
)}
</div>
);
};
export default EditForm;
This diff is collapsed.
.box{
min-height: calc( 100vh - 172px);
display: flex;
}
.left{
min-width: 300px;
width: 350px;
min-height: 100%;
border: 1px solid #eee;
padding: 10px;
.ant-tree-node-content-wrapper{
display: flex;
align-items: center;
.ant-tree-iconEle{
display: flex;
align-items: center;
}
.ant-tree-title{
width: 100%;
}
}
.leftTop{
display: flex;
align-items: center;
margin-bottom: 10px;
justify-content: space-between;
.tRight{
display: flex;
align-items: center;
.icon1{
font-size: 20px;
margin-right: 10px;
}
.icon2{
font-size: 18px;
margin-right: 5px;
}
.icon:hover{
cursor: pointer;
}
}
}
}
.middle{
min-width: 500px;
width: 500px;
min-height: 100%;
border: 1px solid #eee;
padding: 10px;
margin: 0 10px;
}
.title{
display: flex;
align-items: center;
width: 100%;
}
.tip{
display: none;
}
.title:hover{
.tip{
display: flex;
align-items: center;
justify-content: flex-end;
width: 100%;
}
}
.fsize{
font-size: 18px;
margin-left: 10px;
}
.rightBox{
min-width: 200px;
// width: 600px;
width: 100%;
min-height: 100%;
border: 1px solid #eee;
padding: 10px;
}
:global{
.ant-tree .ant-tree-node-content-wrapper .ant-tree-iconEle .anticon-folder{
}
}
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment