Commit e613d128 by 杨周龙

基础配置

1 parent 95426d82
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
serviceWorker.js
setupTests.js
tag.js
config-overrides.js
reportWebVitals.js
{
"env": {
"node": true,
"mocha": true,
"jest": true,
"es6": true,
"browser": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": [
"react",
"jsx-a11y",
"react-hooks"
],
"settings": {
"react": {
"version": "detect"
}
},
"globals": {
"JSX": true,
"React": true,
"NodeJS": true,
"Promise": true
},
"rules": {
"no-console": [1, { "allow": ["error"] }],
"consistent-return": 2,
"curly": [2, "multi-or-nest"],
"dot-location": 0,
"eqeqeq": 2,
"no-alert": 2,
"no-eq-null": 2,
"no-lone-blocks": 2,
"no-return-await": 2,
"no-unused-expressions": 2,
"no-label-var": 1,
"array-bracket-spacing": 2,
"brace-style": 0,
"comma-spacing": 1,
"consistent-this": 1,
"eol-last": 0,
"multiline-ternary": [1, "always-multiline"],
"new-cap": [2, { "capIsNew": false }],
"no-trailing-spaces": 0,
"semi": ["error", "never"],
"space-before-blocks": 2,
"space-in-parens": 2,
"spaced-comment": 2,
"switch-colon-spacing": ["error", { "after": true, "before": false }],
"arrow-spacing": 2,
"quotes": [0, "single"],
"key-spacing": 2,
"comma-dangle": ["error", "never"],
"react-hooks/exhaustive-deps": 0,
"no-empty-function": 1,
"react-native/no-inline-styles": 0,
"react/forbid-prop-types": 0,
"react/prop-types": 0,
"react/display-name": 0,
"prefer-promise-reject-errors": 0,
"react/no-array-index-key": 2,
"react/no-unused-state": 2,
"react/jsx-indent-props": 1,
"react/jsx-no-comment-textnodes": 1,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-target-blank": [1, { "enforceDynamicLinks": "always" }],
"react/jsx-no-undef": 2,
"react/jsx-props-no-multi-spaces": 1,
"react/jsx-tag-spacing": 1,
"react/jsx-uses-vars": 2,
"react/jsx-wrap-multilines": 2,
"react-hooks/rules-of-hooks": 2
}
}
{
"git": {
"changelog": "npm run lint"
},
"hooks": {
"before:git:release": ["npm run lint", "npm run tag"]
}
}
......@@ -2,7 +2,7 @@ const { override, addWebpackAlias } = require('customize-cra')
const path = require('path')
module.exports = override(
//配置路径别名
// 配置路径别名
addWebpackAlias({
'@': path.resolve(__dirname, './src'),
'_c': path.resolve(__dirname, './src/components')
......
import React from 'react';
import React from 'react'
import RouterView from './router'
import { Provider } from 'react-redux'
import { ConfigProvider } from 'antd'
import zhCN from 'antd/lib/locale/zh_CN'
import moment from 'moment'
import 'moment/locale/zh-cn'
import store from './store'
moment.locale('zh')
function App() {
return (
<Provider store={store}>
<ConfigProvider locale={zhCN}>
<RouterView></RouterView>
</ConfigProvider>
</Provider>
)
}
export default App;
export default App
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
import React from 'react'
import { render, screen } from '@testing-library/react'
import App from './App'
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
test('renders APP', () => {
render(<App />)
const linkElement = screen.getByText(/启动应用中/ig)
expect(linkElement).toBeInTheDocument()
})
import http from '@/libs/fetch'
import { APP } from '@/config'
export const login = (data) => {
export const getbaseConfig = () => {
return http.request({
url: '/user/sysLogin/login',
method: 'post',
data
url: APP.GET_PALTFORM_CONFIG_URL + 'api/platform/getConfig',
method: 'get'
})
}
export const sendCode = (data) => {
export const getAppSite = appCode => {
return http.request({
url: '/user/sysLogin/' + data.type + '/captcha?phone=' + data.phone,
data
url: APP.GET_PALTFORM_CONFIG_URL + 'api/app/version/' + appCode,
method: 'get'
})
}
......@@ -3,6 +3,13 @@ import http from '@/libs/fetch'
export const getMenu = () => {
return http.request({
url: '/user/sysLogin/getMenu',
params: { applyPlatform: 0, applyCarrier: 0, productId: 100005 }
params: { applyPlatform: 0, applyCarrier: 0, productId: 100000 }
})
}
export const deleteSessionid = () => {
return http.request({
url: '/user/sysLogin/deleteSessionid',
method: 'delete'
})
}
/**
*name: 面包屑导航组件
*creator: zyj
*create-date: 20200723
*modifier: zyj
*modify-date: 20200724
* */
import view from './view'
import { connect } from 'react-redux'
import './index.scss'
const mapState = state => ({
openRoute: state.open,
userRoute: state.route.route
})
export default connect(mapState)(view)
.breadcrumb-box {
margin-bottom: 12px;
padding: 7px 14px;
height: 34px;
background-color: #fff;
.ant-breadcrumb > span:last-child {
color: #1892FF;
}
}
\ No newline at end of file
import React, { useEffect, useState } from 'react'
import { Breadcrumb } from 'antd'
import basisRoute from '../../router/modules/basisRoute'
function getRouteChain(routes = [], activeName) {
let result = []
routes.forEach(e => {
if (e.url === activeName) {
result.push({ name: e.name, url: e.url })
return
}
if (e.childMenus && e.childMenus.length > 0) {
const childRoutersult = getRouteChain(e.childMenus, activeName)
if (childRoutersult.length > 0) {
result.push({ name: e.name, url: e.url })
result = result.concat(childRoutersult)
}
}
})
return result
}
const getMenuName = (menu, route) => {
const data = { url: '/', name: '首页', disable: true }
menu.forEach(menus => {
if (menus.childMenus) {
menus.childMenus.forEach(e => {
if (e.url === route.path || (route.parentName && route.parentName.includes(menus.url)))
data.name = menus.name
data.url = menus.url
})
}
})
return data
}
function Breadcrumbs({ openRoute, userRoute, match }) {
const [routes, setRoutes] = useState([])
useEffect(() => {
let route = getRouteChain(userRoute, match.path)
if (route.length === 0) route = getRouteChain(basisRoute, match.path)
if (route.length === 0) {
const isActiveRoute = openRoute.find(e => e.url === match.path) || { name: match.path, url: match.path, parentName: [] }
route = getRouteChain(userRoute, isActiveRoute.parentName ? isActiveRoute.parentName[0] : '')
route.push(isActiveRoute)
}
const menuName = getMenuName(userRoute, route[0])
route.unshift(menuName)
setRoutes(route)
}, [openRoute, userRoute, match])
return (
<div className="breadcrumb-box">
<Breadcrumb separator=">" >
{routes.map(e => (e.disable ? <Breadcrumb.Item key={e.name}>{e.name}</Breadcrumb.Item> : <Breadcrumb.Item href={e.url} key={e.name}>{e.name}</Breadcrumb.Item>))}
</Breadcrumb>
</div>
)
}
export default Breadcrumbs
......@@ -3,9 +3,68 @@
background: #1890FF;
height: 60px;
}
.site-layout-header{
display: flex;
justify-content: center;
align-items: center;
position: relative;
&::before{
content: "";
width: 1px;
height: 36px;
position: absolute;
top: 50%;
right: 0px;
transform: translate(0%, -50%);
background-color: #fff;
}
}
.icon{
width: 18px;
height: 18px;
margin-right: 10px;
}
}
\ No newline at end of file
.logo-box {
margin: 0 auto;
width: 1200px;
display: flex;
align-items: center;
color: #fff;
}
.platform {
font-size: 16px;
line-height: 28px;
color: #fff;
}
.site-layout-tab-header{
display: flex;
align-items: center;
justify-content: space-between;
padding-left: 16px;
padding-right: 16px;
}
.user-menu-box{
display: flex;
align-items: center;
cursor: pointer;
.user-title{
color: #fff;
font-size: 18px;
font-weight: 500;
padding-left: 4px;
}
}
.content{
margin: 11px;
flex: 1;
height: calc(100vh - 82px);
overflow: auto;
padding-bottom: 30px;
}
.meun-box{
height: calc(100vh - 82px);
overflow: hidden;
}
}
\ No newline at end of file
import React, { useState, useEffect } from 'react'
import { NavLink, useLocation } from 'react-router-dom'
import { Layout, Menu } from 'antd'
import React, { useState, useEffect, useRef } from 'react'
import { NavLink, useLocation, useHistory } from 'react-router-dom'
import { Layout, Menu, Avatar, Dropdown, Popover } from 'antd'
import { getAllRouter } from '@/libs/utilts'
import { APP } from '../../config'
import { UserOutlined, ExportOutlined, RetweetOutlined, BankFilled, RightOutlined } from '@ant-design/icons'
import { deleteSessionid } from '@/api/user'
import BetterScroll from 'better-scroll'
import version from '../../version'
const { Header, Content, Sider } = Layout
const { SubMenu } = Menu
const theme = "light"
let scroll = null
const Item = ({url, children, ...props}) => {
return (
<Menu.Item {...props} >
<NavLink to={{ pathname: url }}>{children}</NavLink>
</Menu.Item>
)
}
const Page = ({ children, userRoute = [] }) => {
const [collapsed, setCollapsed] = useState(false)
const Page = ({ children, userRoute = [], user, SYSTEM_CLEAR_LOGIN, baseConfig, business, company }) => {
const [index, setIdex] = useState([])
const [openKeys, setOpenKeys] = useState([])
const [serviceList, setServiceList] = useState([])
const location = useLocation()
const onCollapse = () => setCollapsed(!collapsed)
const history = useHistory()
const menu = useRef(null)
useEffect(() => {
const allRouter = getAllRouter(userRoute)
const route = allRouter.find(e => e.url === location.pathname)
if (route)
if (route) {
setIdex([route.url])
else
setIdex(['/'])
const open = userRoute.find(e => e.childMenus.find(c => c.url === route.url))
setOpenKeys([open.name])
}
}, [location, userRoute])
useEffect(() => {
if (baseConfig.other) setServiceList(baseConfig.other.serviceList.split(',').map(item => item.split('|')))
}, [baseConfig.other])
useEffect(() => {
if (scroll === null)
scroll = new BetterScroll(menu.current, { mouseWheel: true })
}, [])
const loginOut = () => {
deleteSessionid()
SYSTEM_CLEAR_LOGIN()
}
const downMenu = (
<Menu>
<Menu.Item >
<span style={{ fontWeight: '700', paddingRight: 50 }}>{user.phone}</span>
</Menu.Item>
<Menu.Divider />
<Menu.Item onClick={() => history.push('/setters')} >
<span style={{ fontWeight: '700', paddingRight: 50 }}>个人设置</span>
</Menu.Item>
<Menu.Divider />
{serviceList.map(e => {
return (
<Menu.Item key={e[0]} icon={<RetweetOutlined />}>
<a href={e[1]} target="_blank" rel="noopener noreferrer" >{e[0]}</a>
</Menu.Item>
)
})}
<Menu.Item icon={<ExportOutlined />} onClick={loginOut}>退出登录</Menu.Item>
</Menu>
)
return (
<Layout style={{ minHeight: '100vh' }} className="site-layout" >
<Sider theme={theme} collapsible collapsed={collapsed} onCollapse={onCollapse}>
<Header className="site-layout-background" style={{ padding: 0 }} />
<Menu theme={theme} mode="inline" selectedKeys={index} >
<Sider theme={theme} >
<Header className="site-layout-background site-layout-header" style={{ padding: 0 }} >
<Popover trigger="click" content={version} placement="right">
<img src={baseConfig.logo} width="102" height="36" alt="信巴迪logo" />
</Popover>
</Header>
<div className="meun-box" ref={menu}>
<div >
<Menu theme={theme} mode="inline" selectedKeys={index} openKeys={openKeys} onOpenChange={e => setOpenKeys(e)} >
{userRoute.map(e => {
return (
<SubMenu key={e.name} icon={<img className="icon" alt="图标" src={APP.STATIS_URL + e.icon} />} title={e.name}>
{e.childMenus.map(child => <Item key={child.url} url={child.url} >{child.name}</Item>)}
{e.childMenus.map(child => (
<Menu.Item key={child.url} url={child.url} >
<NavLink to={{ pathname: child.url }}>{child.name}</NavLink>
</Menu.Item>)
)}
</SubMenu>
)
})}
</Menu>
</div>
</div>
</Sider>
<Layout>
<Header className="site-layout-background" style={{ padding: 0 }} />
<Content style={{ margin: '16px', display: 'flex' }}>
<div style={{ flex: 1, backgroundColor: '#fff' }}>
<Header className="site-layout-background site-layout-tab-header">
<div className="platform">
<BankFilled style={{ fontSize: 25 }} />
<span > {company.fullName}</span>
<RightOutlined />
<span> {business.mchName}</span>
</div>
<Dropdown overlay={downMenu} >
<div className="user-menu-box">
{/* <img alt="消息" style={{ marginRight: 5 }} src={require('../../assets/images/message.png')} /> */}
<Avatar icon={<UserOutlined />} size={28}></Avatar>
<span className="user-title">{user.name}</span>
</div>
</Dropdown>
</Header>
<Content>
<div className="content" >
<div>
{children}
</div>
</div>
</Content>
</Layout>
</Layout>
......
import React, { useState } from 'react'
import { Spin } from 'antd'
import baseConnect from '../../store/modules/baseConfig/connect'
import './index.scss'
const Component = ({ url, baseConfig }) => {
const [loading, setLoading] = useState(true)
return (
<div className="iframe-box">
{loading && <div className="iframe-loading"><Spin /></div>}
<iframe onLoad={() => setLoading(false)} title={url} src={baseConfig.other?.host + url + '#commo'} className="iframe" />
</div>
)
}
export default baseConnect(Component)
.iframe{
width: 100%;
height: 100%;
border: 0px;
padding: 0px;
margin: 0px;
}
.iframe-box{
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: calc(100vh - 100px);
overflow: auto;
}
.iframe-loading{
position: fixed;
top: 50%;
left: 50%;
}
\ No newline at end of file
const getProcessValue = (dev, ceshi, prod) => {
return process.env.NODE_ENV === 'development' ? dev : (process.ceshi ? ceshi : prod)
}
const USER_CONFIG = {
EbankTips: '因工行只支持360、IE、Safari浏览器,请将以下链接复制至360、IE、Safari浏览器打开进行支付'
const getProcessValue = (dev, ceshi, test, uat, prod, dev_prod) => {
if (process.env.NODE_ENV === "development") return test
if (process.env.REACT_APP_API === "development") return ceshi
if (process.env.REACT_APP_API === "test") return test
if (process.env.REACT_APP_API === "uat") return uat
if (process.env.REACT_APP_API === "dev_prod") return dev_prod
return prod
}
const APP = {
/** @description token存储的名称 */
tokenName: 'sessionid',
tokenName: "sessionid",
/** @description token在存储的时长,默认无操作后的30分钟。0 表示关闭浏览器即删除。单位 秒 */
tokenExpires: 0,
/** @description token使用的存储类型,可选值: coookie, session, 默认值是session*/
tokenType: 'cookie',
tokenType: "cookie",
/** @description 是否只限制此子域名可以访问此token,注意这会影响到主域名和其他子域名进行token共享默认false*/
tokenDomain: process.env.NODE_ENV === 'development' ? 'localhost' :'.b2bwings.com',
/**@description 页面加载出现loading条,默认值 true*/
tokenDomain:
process.env.NODE_ENV === "development" ? "localhost" : ".b2bwings.com",
/** @description 页面加载出现loading条,默认值 true*/
loading: true,
/**@description 初始的HTTP请求的全局header头*/
/** @description 初始的HTTP请求的全局header头*/
header: { },
/**@description API基础请求地址*/
baseUrl: {
dev: 'http://gateway-dev.b2bwings.com/',
pro: 'https://gateway.b2bwings.com/'
},
/**@description 应用的登陆API的PATH*/
loginPath: '/user/sysLogin/login',
/**@description 应用的退出API的PATH*/
logoutPath: '/v1/vendors/logout',
/**@description 401页面地址,用于提醒用户登陆状态失效*/
error404: '/404',
/**@description 菜单缓存本地的名称*/
MENU_NAEM: 'SYS_MENU',
/**@description 资源组缓存本地的名称*/
CODES_NAME: 'SYS_CODES',
/**@description 路由历史记录缓存本地的名称*/
HISTORY_NAME: 'SYS_HISTORY',
/**@description 静态资源目录,必须。用于菜单图标显示*/
STATIS_URL: 'https://b2bwings-system-image.oss-cn-shenzhen.aliyuncs.com/',
/**@description 信让的跳转URL列表 */
domainWhiteList: [
'/.*',
'http://localhost',
'http://xbd-dev.b2bwings.com',
'https://xbd-dev.b2bwings.com',
'http://xbd.b2bwings.com/',
'https://xbd.b2bwings.com/',
'https://ufa.b2bwings.com/',
'http://ufa-dev.b2bwings.com/',
'https://bwfs.oss-cn-shenzhen.aliyuncs.com',
'https://supplier-dev.b2bwings.com',
'http://supplier-dev.b2bwings.com',
'https://supplier.b2bwings.com',
'http://supplier.b2bwings.com',
'https://seller-dev.b2bwings.com',
'http://seller-dev.b2bwings.com',
'https://seller.b2bwings.com',
'http://seller.b2bwings.com',
'https://mall-dev.b2bwings.com',
'http://mall-dev.b2bwings.com',
'https://mall.b2bwings.com',
'http://mall.b2bwings.com'
],
/** 独立服务平台,主题颜色不同 */
singeService: [
{
name: '供应商工作台',
host: getProcessValue(['localhost:8087'],
['supplier-dev.b2bwings.com'],
['supplier.b2bwings.com']
),
productId: 100007,
less: 'mall'
},
{
name: '卖家工作台',
host: getProcessValue(['localhost:8085'],
['seller-dev.b2bwings.com'],
['seller.b2bwings.com']
/** @description API基础请求地址*/
baseUrl: getProcessValue(
"https://gateway-dev.b2bwings.com/",
"https://gateway-dev.b2bwings.com/",
"https://gateway-test.b2bwings.com/",
"https://gateway-uat.b2bwings.com/",
"https://gateway.b2bwings.com/",
"https://gateway-prod.b2bwings.com/"
),
productId: 100005,
less: 'mall'
},
{
name: '商城',
host: getProcessValue(['localhost:8086'],
['mall-dev.b2bwings.com'],
['mall.b2bwings.com']
),
productId: 100006,
less: 'mall'
}
],
host: getProcessValue('localhost:8088', 'xbd-dev.b2bwings.com', 'xbd.b2bwings.com'),
// 财务业务类型
FINANCIAL_BUSINESS_TYPE: (val) => {
if(val===null||val===''||val === undefined) return ''
let result = {
T100:'平台信息服务',
T201:'物流运输服务',
T202:'预付运费充值',
T300:'充值',
T301:'结算',
T302:'转账',
T303:'提现',
T304:'提现-退款',
T400:'支付手续费',
T401:'手续费-退款',
T402:'支付-退款',
T500:'渠道营销服务',
T600:'在途转可用',
T700:'退款手续费',
T800:'资金分账',
T900:'保证金缴纳',
T901:'保证金退款',
T902:'保证金抵扣',
}
return result['T'+val]?result['T'+val]:null
},
// 财务收支类型
FINANCIAL_RECEIVE_AND_PAY_TYPE: (val) => {
if(val===null||val===''||val === undefined) return ''
let types = {
t100:'收入',
t101:'支出',
t102:'预收',
t103:'预付',
t104:'应收',
t105:'应付',
t106:'核销',
t107:'在途转可用',
t108:'退款收入',
t109:'退款支出'
}
return types['t'+val]?types['t'+val]:''
},
/**@description 其他菜单的域名*/
othreDomain: getProcessValue('http://seller-dev.b2bwings.com', 'http://seller-dev.b2bwings.com', 'https://seller.b2bwings.com'),
/** @description 应用的登陆API的PATH*/
loginPath: "/user/sysLogin/login",
/** @description 应用的退出API的PATH*/
logoutPath: "/v1/vendors/logout",
/** @description 401页面地址,用于提醒用户登陆状态失效*/
error404: "/404",
/** @description 菜单缓存本地的名称*/
MENU_NAEM: "SYS_MENU",
/** @description 资源组缓存本地的名称*/
CODES_NAME: "SYS_CODES",
/** @description 路由历史记录缓存本地的名称*/
HISTORY_NAME: "SYS_HISTORY",
/** @description 静态资源目录,必须。用于菜单图标显示*/
STATIS_URL: "https://b2bwings-system-image.oss-cn-shenzhen.aliyuncs.com/",
// 注册链接
registerLink: 'http://xbd-dev.b2bwings.com/register?callbackUrl=',
registerLink: `/register`,
// 找回密码链接
findPasswordLink: 'http://xbd-dev.b2bwings.com/findPassword?callbackUrl='
findPasswordLink: '/findPassword?callbackUrl=',
/** @description 图片静态资源地址*/
IMG_URL: "https://images.b2bwings.com/",
/** @description 物流平台-图片静态资源地址*/
WULIU_IMG_URL: "https://files.b2bwings.com/",
/** @public 获取配置数据API */
GET_PALTFORM_CONFIG_URL: getProcessValue(
"https://app-admin-dev.b2bwings.com/",
"https://app-admin-dev.b2bwings.com/",
"https://app-admin-dev.b2bwings.com/",
"https://app-admin-dev.b2bwings.com/",
"https://app-admin.b2bwings.com/",
"https://app-admin-dev.b2bwings.com/"
)
}
export default { USER_CONFIG, APP }
export { USER_CONFIG, APP }
export {
APP
}
......@@ -5,11 +5,24 @@ body {
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #434343;
font-size: 13px;
min-width: 1280px;
overflow: auto;
}
html,body,ul,li,ol{
margin: 0;
padding: 0;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.loading-page{
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
\ No newline at end of file
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import 'antd/dist/antd.css';
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
import reportWebVitals from './reportWebVitals'
import 'antd/dist/antd.css'
import { init } from 'loading-bar'
import 'loading-bar/src/index.css'
import store from './store'
import { install } from 'system-collect'
import http from './libs/fetch'
import { APP } from './config/index'
import { setAPP } from 'xbd-cookie'
setAPP(APP)
init()
store.dispatch.baseConfig.init()
.then(res => {
if(!res) {
console.error('平台配置参数未获取', res)
return
}
http.setHeader('operationChannel', res.operationChannel)
install(
process.env.REACT_APP_API === 'production' ? 'production' : 'development',
'browser',
res.platformName,
res.app_code
)
})
if (process.env.NODE_ENV !== 'development') document.domain = 'b2bwings.com'
ReactDOM.render(
<App />,
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
)
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
process.env.NODE_ENV === 'development' ? serviceWorker.unregister() : serviceWorker.register()
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
import HttpRequest from '@/libs/http-request'
import { APP } from '@/config'
const baseUrl = process.env.NODE_ENV === 'development' || process.ceshi ? APP.baseUrl.dev : APP.baseUrl.pro
const http = new HttpRequest(baseUrl)
const http = new HttpRequest(APP.baseUrl)
export default http
\ No newline at end of file
import axios, { CancelToken } from 'axios'
import axios from 'axios'
import { APP } from '@/config'
import store from '@/store'
import { Modal } from 'antd'
import { downFile } from './utilts'
class HttpRequest {
constructor (baseUrl, loginUrl = APP.loginPath) {
constructor(baseUrl, loginUrl = APP.loginPath) {
this.baseUrl = baseUrl
this.loginUrl = loginUrl
this.header = { ...APP.header }
this.instance = null
this.init()
}
/**初始化 */
/** 初始化 */
init() {
let instance = axios.create({ baseURL: this.baseUrl, headers: this.header })
this.interceptors(instance)
this.instance = instance
}
/**绑定请求拦截器 */
interceptors (instance) {
/** 绑定请求拦截器 */
interceptors(instance) {
// 请求
instance.interceptors.request.use(config => {
// config.headers['Authorization'] = getToken()
return config
}, error => {
if (process.env.NODE_ENV === 'development') console.error(error)
......@@ -31,24 +31,19 @@ class HttpRequest {
// 响应
instance.interceptors.response.use(res => {
if (res.headers['content-type'] && res.headers['content-type'].indexOf('download') !== -1) return res
if (res.headers['content-type'] && res.headers['content-type'].indexOf('excel') !== -1) return res
if (res.data.type === 'multipart/form-data' && res.data.size) return res
if (res.headers['content-type'] && res.headers['content-disposition']) return res
return this.handleRespone(res)
}, error => {
if (process.env.NODE_ENV === 'development') console.error(error)
return Promise.reject(error.response ?
`发生错误!状态码:${error.response.status}${error.response.message||error.response.data.message}`
`发生错误!状态码:${error.response.status}${error.response.message || error.response.data.message}`
: error.message + "")
})
}
/**请求 */
async request (options) {
/** 请求 */
async request(options) {
return this.instance(options)
}
......@@ -59,7 +54,7 @@ class HttpRequest {
* @param {function} errorCallback
*/
sourceRequest(options, successCallback, errorCallback) {
const source = CancelToken.source()
const source = axios.CancelToken.source()
options = Object.assign(options, { cancelToken: source.token })
this.instance(options)
.then(successCallback)
......@@ -90,30 +85,32 @@ class HttpRequest {
*/
handleRespone(res) {
if (res.headers['content-type'] && res.headers['content-type'].indexOf('download') !== -1 && !res?.data?.code) return downFile(res)
if (res.headers['content-type'] && res.headers['content-type'].indexOf('excel') !== -1 && !res?.data?.code) return downFile(res)
if (res.data.type === 'multipart/form-data' && res.data.size) return res
if (res.headers['content-type'] && res.headers['content-disposition']) return res
if (!res.data) return res
const { code, message, data, list } = res.data
if(typeof code === 'undefined') return data
if (typeof code === 'undefined') return data
if(typeof list !== 'undefined' && typeof data === 'undefined') return list
if (typeof list !== 'undefined' && typeof data === 'undefined') return list
switch(code) {
switch (code) {
case 0:
return data ? data : { data, list }
case "0":
return data ? data : { data, list }
case 200:
return Array.isArray(data)?data:(typeof data === 'object' ? {message, code, ...data} : {data, message})
// case 400001:
// return Array.isArray(data)?data:(typeof data === 'object' ? {message, code, ...data} : {data, message})
return Array.isArray(data) ? data : (typeof data === 'object' ? { message, code, ...data } : { data, message })
case 401:
if (process.env.NODE_ENV === 'development') console.error(`发生错误,错误代码: ${code};详情:${message}`)
store.dispatch.user.SYSTEM_OUT()
Modal.error('未登录或登录状已失效,请重新登陆')
return Promise.reject('')
// store.commit('SYSTEM_CLEAR') // 清除登陆状态
break
return Promise.reject('未登录或登录状已失效,请重新登陆')
case 403:
if (process.env.NODE_ENV === 'development') console.error(`发生错误,错误代码: ${code};详情:${message}`, data)
return Promise.reject(`发生错误!错误代码: ${code},无权限使用此功能`, data)
......
import moment from 'moment'
/** 将全部二级路由转为一级路由 */
export const getAllRouter = route => {
const childRoute = []
if (!route) return childRoute
route.map(e => childRoute.push(...e.childMenus))
return childRoute
}
export const downFile = res => {
let fileName = res.headers['content-disposition'].replace(/.*?filename=/ig, '')
fileName = decodeURIComponent(fileName)
const ready = new FileReader()
ready.onload = () => {
let a = document.createElement('a')
a.href = ready.result
a.download = fileName
a.click()
}
ready.readAsDataURL(res.data)
}
/**
* 日期范围组件限定可选日期
* @param {*} current
*/
export const disabledDate = (current) => {
return current > moment().endOf('day')
}
// 防止丢失精度 除法
export function numDiv(arg1, arg2 = 100) {
if (!arg1) return 0
var r1 = (typeof arg1 === "number" ? arg1.toString() : arg1),
r2 = (typeof arg2 === "number" ? arg2.toString() : arg2),
m,
resultVal, d = arguments[2]
m = (r2.split(".")[1] ? r2.split(".")[1].length : 0) - (r1.split(".")[1] ? r1.split(".")[1].length : 0)
resultVal = Number(r1.replace(".", "")) / Number(r2.replace(".", "")) * Math.pow(10, m)
return typeof d !== "number" ? Number(resultVal) : Number(resultVal.toFixed(parseInt(d)))
}
/**
* @function: 数字使用千分位符显示
* @param: money 数字或字符串类型, 传入分
* @returns 返回元并使用分位符显示字符串
* */
export function formatMoney(number) {
if (!number) return 0
let money = numDiv(number)
let result = null
let num = Number(money).toFixed(2)
result = Number(num).toLocaleString('en-US')
if (result.indexOf('.') === -1)
return result + '.00'
else
return result.split('.')[1].length >= 2 ? result : result + '0'
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
import React from 'react'
import BreadCrumbs from '../components/BreadCrumbs'
import { show, hide } from 'loading-bar'
// 路由守卫
export const Guards = (Page) => {
export const Guards = (Page, isBread = false) => {
return class extends React.Component {
/** 进入页面 */
......@@ -15,7 +16,12 @@ export const Guards = (Page) => {
}
render() {
return <Page {...this.props} />
return (
<>
{ isBread ? <BreadCrumbs {...this.props} /> : null}
<Page {...this.props} />
</>
)
}
}
}
export default [ ]
\ No newline at end of file
export default []
import loadable from '@loadable/component'
const exact = true
export default [
{
url: '/login',
exact,
component: loadable(() => import('@/view/login'))
}
]
import React, { useState, useEffect } from 'react'
import { Switch, Route, useHistory } from 'react-router-dom'
import { Switch, Route, useHistory, Redirect } from 'react-router-dom'
import { Guards } from './guards'
import { APP } from '@/config'
import connect from '../store/modules/route/connect'
import { getAllRouter } from '../libs/utilts'
import { getToken } from 'xbd-cookie'
import Layout from '@/components/Layout'
import { show, hide } from 'loading-bar'
import http from '@/libs/fetch'
import { getMenu } from '@/api/user'
import { Modal } from 'antd'
import { Modal, Spin } from 'antd'
import Ifram from '../components/iframe'
import error401 from "@/view/error/401"
import error404 from "@/view/error/404"
import fullScreenRoute from './modules/fullScreenRoute'
/** 获取全部路由的路径 */
const getAllRouterName = route => route.map(e => e.url)
/** 路由视图 */
const RouterView = ({ basisRoute = [], serviceRoute = [], userRoute = [], userSatatus, SYSTEM_LOGIN }) => {
const [openRoute, setOpenRoute] = useState([])
const RouterView = ({
basisRoute = [],
serviceRoute = [],
userRoute = [],
userSatatus,
SYSTEM_LOGIN,
openRoute = [],
SYSTEM_SAVE_OPEN_ROUTE,
baseConfig
}) => {
const history = useHistory()
const [loading, setLoading] = useState(false)
const [loading, setLoading] = useState(true)
useEffect(() => {
// 二级路由转一级路由
const allRoute = getAllRouter(userRoute)
const allName = getAllRouterName(serviceRoute)
const newRoutes = allRoute.map(e => {
const index = serviceRoute.findIndex(service => service.url === e.url)
const newE = { ...e }
const index = serviceRoute.findIndex(service => e.url === service.url || e.url === service.alias)
if (index !== -1)
newE.component = serviceRoute[index].component
else {
const indexOfName = allName.findIndex(url => url === e.url)
if (indexOfName !== -1)
newE.component = serviceRoute[index].component
return { ...e, ...serviceRoute[index], component: serviceRoute[index].component }
else
newE.component = () => {
window.location.href = APP.othreDomain + newE.url
return (<>{newE.url}</>)
}
}
return { ...e, ...serviceRoute[index], component: () => (<Ifram url={e.url} />) }
return newE
})
setOpenRoute(newRoutes)
}, [userRoute, serviceRoute])
const parentRoute = serviceRoute.map(e => {
const index = newRoutes.findIndex(service => e?.parentName?.includes(service.url))
if (index !== -1)
return e
else
return null
}).filter(e => e)
SYSTEM_SAVE_OPEN_ROUTE([...newRoutes, ...parentRoute])
}, [userRoute, serviceRoute, SYSTEM_SAVE_OPEN_ROUTE])
// 校验权限
useEffect(() => {
const token = getToken()
const isService = serviceRoute.find(e => e.url === history.location.pathname)
const isFullScreenRoute = fullScreenRoute.find(e => e.url === history.location.pathname)
setLoading(true)
if (baseConfig.operationChannel === undefined) return
if (userSatatus)
return
setLoading(false)
else if (token) {
show()
http.setHeader('sessionid', token)
http.setHeader('Sessionid', token)
getMenu()
.then(res => {
hide()
SYSTEM_LOGIN(res)
if (!isService) history.replace('/')
setLoading(false)
if (isFullScreenRoute) history.replace(res.menu.length ? res.menu[0].childMenus[0].url : '/')
})
.catch(err => {
hide()
setLoading(false)
Modal.error({ title: '错误', content: err, onOk: () => history.replace('/login') })
})
}
else if (isService)
else if (!isFullScreenRoute) {
setLoading(false)
history.replace('/login')
} else
setLoading(false)
}, [serviceRoute, openRoute, baseConfig, userSatatus, SYSTEM_LOGIN, history])
}, [serviceRoute])
if (userSatatus)
if (loading && !userSatatus && openRoute.length === 0) {
return (
<div className="loading-page">
<Spin tip="启动应用中..." size="large" />
</div>
)
}
else if (userSatatus) {
return (
<Layout>
<Switch>
{basisRoute.map(e => <Route path={e.url} exact={e.exact} component={Guards(e.component)} key={e.url} />)}
{openRoute.map(e => <Route path={e.url} exact={e.exact} component={Guards(e.component)} key={e.url} />)}
{basisRoute.map(e => e && <Route path={e.url} exact={e.exact} component={Guards(e.component)} key={e.url} />)}
{openRoute.map(e => e && <Route path={e.url} exact={e.exact} component={Guards(e.component)} key={e.url} />)}
<Route component={Guards(error401)} />
</Switch>
</Layout>
)
else
}
else if (!userSatatus) {
return (
<Switch>
{fullScreenRoute.map(e => <Route path={e.url} exact={e.exact} component={Guards(e.component)} key={e.url} />)}
<Route component={Guards(error404)} />
<Redirect path="/" to="/login" />
</Switch>
)
}
else return null
}
export default connect(RouterView)
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}
......@@ -2,4 +2,4 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';
import '@testing-library/jest-dom';
......@@ -2,11 +2,15 @@ import { init } from '@rematch/core'
import { createConetxt } from './context'
import route from './modules/route/index'
import user from './modules/user/index'
import open from './modules/open/index'
import baseConfig from './modules/baseConfig/index'
const store = init({
models: {
route,
user
user,
open,
baseConfig
}
})
createConetxt(store)
......
import { connect } from 'react-redux'
const mapState = state => ({
baseConfig: state.baseConfig
})
export default connect(mapState)
import { getbaseConfig } from '@/api/config'
import { Modal } from 'antd'
export default {
state: {},
reducers: {
setState(_, data) {
return data || {}
}
},
effects: {
init() {
return getbaseConfig()
.then(res => {
window.document.title = res.platformName
this.setState(res)
return res
})
.catch(() => {
Modal.error('加载平台信息失败,请刷新重试')
})
}
}
}
\ No newline at end of file
export default {
state: [],
reducers: {
change(_, route) {
return route
}
}
}
\ No newline at end of file
import { connect } from 'react-redux'
const mapState = state => ({
userRoute: state.route.route,
userSatatus: state.user.userSatatus
userSatatus: state.user.userSatatus,
user: state.user.user,
productIds: state.user.productIds,
openRoute: state.open,
baseConfig: state.baseConfig,
business: state.user.business,
company: state.user.company
})
const mapDispatch = dispatch => ({
add: route => dispatch.route.add(route),
change: route => dispatch.route.change(route),
SYSTEM_LOGIN: res =>{
const { codes, loginUserVo } = res
dispatch.user.SYSTEM_LOGIN({ codes, ...loginUserVo })
SYSTEM_LOGIN: res => {
const { codes, loginUserVo, productIds } = res
dispatch.user.SYSTEM_LOGIN({ codes, ...loginUserVo, productIds })
dispatch.route.change(res.menu)
},
SYSTEM_CLEAR_LOGIN: () => {
dispatch.user.SYSTEM_OUT()
dispatch.route.change([])
},
SYSTEM_SAVE_OPEN_ROUTE: route => {
dispatch.open.change(route)
}
})
......
import { connect } from 'react-redux'
const mapState = state => ({
user: state.user.user,
platformAccount: state.user.platformAccount,
company: state.user.company,
business: state.user.business
})
export default connect(mapState)
\ No newline at end of file
import { clearToken } from 'xbd-cookie'
export default {
state: {
......@@ -6,17 +7,30 @@ export default {
business: {},
company: {},
platformAccount: {},
user: {}
user: {},
productIds: []
},
reducers: {
change(state, data) {
return { ...state, user: { ...state.user, ...data } }
},
// 登录成功
SYSTEM_LOGIN(_, res) {
const { business, company, platformAccount, user, codes } = res
return {business, company, platformAccount, user, codes, userSatatus: true}
const { business, company, platformAccount, user, codes, productIds } = res
return { business, company, platformAccount, user, codes, userSatatus: true, productIds }
},
// 退出登录
SYSTEM_OUT() {
clearToken()
return {
userSatatus: false,
codes: [],
business: {},
company: {},
platformAccount: {},
user: {},
productIds: []
}
}
}
}
const version = 'V12-7 10.57.41'
export default version
import React from 'react'
/**
*name: 401页
*path: /401
*creator: zyj
*create-date: 20200722
*modifier:
*modify-date:
* */
import React, { useState, useEffect } from "react"
import { Button, Statistic } from 'antd'
import './index.scss'
import connect from '@/store/modules/route/connect'
const Page = ({ history }) => {
const { Countdown } = Statistic
const Page401 = ({ history, userRoute = [], SYSTEM_CLEAR_LOGIN }) => {
const [time, setTime] = useState(0)
useEffect(() => {
setTime(Date.now() + 6000)
}, [])
const onFinish = () => {
if (userRoute.length)
history.replace(userRoute[0].childMenus[0].url)
else {
SYSTEM_CLEAR_LOGIN()
history.replace('/login')
}
}
return (
<div>
<h1 onClick={() => history.push('/')} >401,暂无权限。点击回到首页</h1>
<div className="error-box">
<section className="error-img" style={{ backgroundImage: `url(${require('./images/401.png')})` }}></section>
<div style={{ marginBottom: 68 }}>
您没有访问该页面的权限,<Countdown value={time} format="s" onFinish={onFinish} /> 秒后自动返回{userRoute.length ? '首页' : '登录页'}
</div>
{
userRoute.length ?
<Button type="primary" onClick={onFinish}>立即返回首页</Button> :
<Button type="primary" onClick={onFinish}>立即返回登录页</Button>
}
</div>
)
}
export default Page
\ No newline at end of file
export default connect(Page401)
import React from 'react'
/**
*name: 404页
*path: /404
*creator: zyj
*create-date: 20200722
*modifier:
*modify-date:
* */
import React, { useState, useEffect } from "react"
import { Button, Statistic } from 'antd'
import './index.scss'
import connect from '@/store/modules/route/connect'
const Page = ({ history }) => {
const { Countdown } = Statistic
const Page404 = ({ history, userRoute = [] }) => {
const [time, setTime] = useState(0)
useEffect(() => {
setTime(Date.now() + 6000)
}, [])
const onFinish = () => {
if (userRoute.length)
history.replace(userRoute[0].childMenus[0].url)
else
history.replace('/')
}
return (
<div>
<h1 onClick={() => history.push('/login')} >404错误,点击回到首页</h1>
<div className="error-box">
<header className="login-header">
<section className='logo-box'>
<img src={require('../login/images/xbd_logo.png')} width="102" height="36" alt="信巴迪logo" />
<span className="platform-name">卖家工作台</span>
</section>
</header>
<section className="error-img" style={{ backgroundImage: `url(${require('./images/401.png')})` }}></section>
<div style={{ marginBottom: 68 }}>
你想要访问的页面不存在,<Countdown value={time} onFinish={onFinish} /> 秒后自动返回首页
</div>
<Button type="primary" onClick={onFinish}>立即返回首页</Button>
</div>
)
}
export default Page
\ No newline at end of file
export default connect(Page404)
.error-box {
padding-bottom: 30px;
text-align: center;
background-color: #fff;
&::before{
content: '';
display: table;
}
.login-box {
min-height: 100vh;;
background-color: #EBEBEB;
}
.login-header {
display: flex;
align-items: center;
height: 60px;
background-color: #1890FF;
.logo-box {
margin: 0 auto;
width: 1200px;
display: flex;
align-items: center;
color: #fff;
}
.platform-name {
margin-left: 16px;
padding-left: 12px;
border-left: 1px solid #fff;
font-size: 20px;
line-height: 28px;
}
}
.ant-statistic {
display: inline-block;
vertical-align: bottom;
.ant-statistic-content {
font-size: 16px;
}
}
.error-img {
margin: 39px auto 16px;
width: 714px;
height: 563px;
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
}
\ No newline at end of file
import View from './view'
import connect from '@/store/modules/route/connect'
export default connect(View)
\ No newline at end of file
.login-box {
min-height: 100vh;;
background-color: #EBEBEB;
}
.login-header {
padding-left: 18.75%;
display: flex;
align-items: center;
height: 60px;
background-color: #1890FF;
.logo-box {
display: flex;
align-items: center;
color: #fff;
}
.platform-name {
margin-left: 16px;
padding-left: 12px;
border-left: 1px solid #fff;
font-size: 20px;
line-height: 28px;
}
}
.content-wrap {
background-color: #F5F6F7;
}
.content {
margin: 0 auto;
padding: 99px 0;
display: flex;
align-items: center;
width: 1024px;
.login-bg {
margin-right: 52px;
width: 581px;
height: 504px;
background-image: url(./images/login-bg.png);
background-position: 0 center;
background-repeat: no-repeat;
background-size: contain;
}
.login-controller-box {
padding: 0 33px;
width: 390px;
height: 414px;
background: #ffffff;
border-radius: 8px;
box-shadow: 0px 4px 28px -18px
}
.tabs-box {
margin-bottom: 25px;
display: flex;
align-items: center;
font-size: 16px;
line-height: 24px;
border-bottom: 1px solid #D7D7D7;
}
.tabs-item {
position: relative;
padding: 14px 0;
flex: 1;
text-align: center;
color: #C0C5CC;
cursor: pointer;
transition: all .25s ease-in;
&::after {
content: '';
display: block;
position: absolute;
left: 50%;
bottom: -1px;
width: 2em;
height: 2px;
margin-left: -1em;
transform: scale(0);
transition: transform .25s ease-in;
}
&:hover{
font-size: 20px;
color: #1892FF;
}
}
.tab-active {
font-size: 20px;
color: #1892FF;
&::after {
transform: scale(1);;
background-color: #1892FF;
}
}
// 表单
.login-form {
.ant-input-affix-wrapper {
height: 48px;
}
.anticon {
font-size: 20px;
}
.ant-input {
font-size: 16px;
}
.btn-login-code {
margin-left: 10px;
height: 48px;
min-width: 120px;
}
.forget-password {
position: absolute;
right: 0;
top: -16px;
}
}
}
.footer-info-box {
}
\ No newline at end of file
import React, { useState, useEffect } from 'react'
import { Form, Input, Button, Checkbox, message } from 'antd'
import { UserOutlined, UnlockOutlined, MessageOutlined } from '@ant-design/icons'
import { APP } from '@/config'
import { sha256 } from 'js-sha256'
import { login, sendCode } from '@/api/login'
import { setToken } from 'xbd-cookie'
import './index.scss'
const PassWordLoginItem = ({ checkPhone }) => (
<>
<Form.Item
name="phone"
rules={[{ required: true, validator: checkPhone }]}
>
<Input
placeholder="请输入手机号码"
prefix={<UserOutlined />}
/>
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: '登录密码不能为空' }]}
>
<Input.Password
placeholder="请输入登录密码"
prefix={<UnlockOutlined />}
/>
</Form.Item>
<p style={{ position: 'relative', height: 22 }}>
<a href={APP.findPasswordLink + window.location.origin + '/login'} className="forget-password">忘记密码?</a>
</p>
</>
)
const CodeLoginItem = ({countDown, btnDisable, getCode, checkPhone, btnLoading }) => (
<>
<Form.Item
name="phone"
rules={[{ required: true, validator: checkPhone }]}
>
<Input
placeholder="请输入手机号码"
prefix={<UserOutlined/>}
/>
</Form.Item>
<Form.Item
name="code"
rules={[{ required: true, message: '验证码不能为空' }]}
>
<section style={{display: 'flex'}}>
<Input
placeholder="请输入验证码"
prefix={<MessageOutlined/>}
/>
<Button
loading={btnLoading && countDown <= 0}
disabled={btnDisable || countDown > 0}
onClick={getCode}
className="btn-login-code"
>
{countDown <= 0 ? '获取验证码' : (countDown + 's')}
</Button>
</section>
</Form.Item>
</>
)
function LoginPage({ history, SYSTEM_LOGIN }) {
const [tabKey, setTabKey] = useState(0)
// 获取验证码按钮 display
const [btnDisable, setBtnDisable] = useState(true)
// 获取验证码按钮 loading
const [btnLoading, setBtnLoading] = useState(false)
// 获取验证码按钮 倒计时
const [countDown, setCountDown] = useState(0)
// 登录按钮 loading
let [loginbtnLoading, setLoginbtnLoading] = useState(false)
const [form] = Form.useForm()
const onFinish = (data) => {
let { phone, password, code } = data
const body = {
code,
loginAccount: phone,
loginPassword: password ? sha256(password) : '',
applyPlatform: [0],
applyCarrier: [0],
operationChannel: 0
}
setLoginbtnLoading(true)
login(body)
.then((res) => {
loginbtnLoading && setLoginbtnLoading(false)
setToken(res.sessionid)
SYSTEM_LOGIN(res)
})
.catch(err => {
setLoginbtnLoading(false)
message.error(err)
})
}
// 切换登陆方式
const changeLoginTabs = (num) => {
setTabKey(num)
}
// 获取验证码返回结果处理函数
function handleGetCode() {
let count = 60
let timer = null
timer = setInterval(() => {
setCountDown(count)
count--;
if (count < 0) {
clearInterval(timer)
}
}, 1000)
}
// 获取验证码
const getCode = () => {
let phone = form.getFieldsValue().phone
setBtnLoading(true)
const body = {
type: 'LOGIN',
phone
}
sendCode(body).then(res => {
setBtnLoading(false)
message.success('发送成功');
handleGetCode()
}).catch(err => {
setBtnLoading(false)
message.error(err)
console.error(err)
})
}
// 校验手机号码
let checkPhone = (rule, value) => {
const regular = /^1[3456789]\d{9}$/
if (!regular.test(value)) {
setBtnDisable(true)
return Promise.reject("手机号码格式错误")
} else {
setBtnDisable(false)
return Promise.resolve();
}
}
return (
<div className="login-box">
<header className="login-header">
<section className='logo-box'>
<img src={require('./images/xbd_logo.png')} width="102" height="36" alt="信巴迪logo"/>
<span className="platform-name">卖家工作台</span>
</section>
</header>
<div className="content-wrap">
<div className="content">
<section className="login-bg"></section>
<section className="login-controller-box">
<section className="tabs-box">
<span className={`tabs-item ${tabKey===0&&'tab-active'}`} onClick={()=>{changeLoginTabs(0)}}>密码登录</span>
<span className={`tabs-item ${tabKey===1&&'tab-active'}`} onClick={()=>{changeLoginTabs(1)}}>验证码登录</span>
</section>
<Form
form={form}
onFinish={onFinish}
initialValues={{ remember: true }}
className="login-form"
>
{
tabKey===0?<PassWordLoginItem checkPhone={checkPhone} />:(
<CodeLoginItem
countDown={countDown}
btnDisable={btnDisable}
getCode={getCode}
checkPhone={checkPhone}
btnLoading={btnLoading}
/>
)
}
<Form.Item name="btnSubmit" style={{marginBottom:16}}>
<Button
type="primary"
style={{ width: '100%', height: 48, fontSize: 18 }}
htmlType="submit"
loading={loginbtnLoading}
>登录</Button>
</Form.Item>
<Form.Item
name="remember"
valuePropName="checked"
style={{marginBottom: 20}}
>
<Checkbox style={{ userSelect: 'none' }} noStyle>记住账号</Checkbox>
</Form.Item>
<p style={{ 'textAlign': 'center' }}>
还没有账号?
<a
href={APP.registerLink + window.location.origin + '/login'}
style={{
color: '#1892FF',
cursor: 'pointer',
userSelect: 'none'
}}
>
马上去注册
</a>
</p>
</Form>
</section>
</div>
</div>
<footer>
<section className="footer-info-box">
</section>
</footer>
</div>
)
}
export default LoginPage
\ No newline at end of file
const fs = require('fs')
const { join } = require('path')
const version = `V${new Date().getMonth() + 1}-${new Date().getDate()} ${new Date().getHours()}.${new Date().getMinutes()}.${new Date().getSeconds()}`
const content = `
const version = '${version}'
export default version
`
fs.writeFile(join(__dirname, './src/version.js'), content, { encoding: 'utf-8' }, () => {
console.log('创建版本成功,当前版本是' + version)
})
/* eslint-disable no-console */
console.log('\x1B[31m', 'eslint 不再输出报表txt文件')
console.log('\x1B[34m', '\n\n 如需获取报表文件请运行')
console.log('\x1B[32m', '\n npm run lint-rep')
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!