基于React16.x、Ant Design4.x,react-admin
├── config // 项目构建配置
├── public // 不参与构建的静态文件
├── scripts // 构建脚本
├── src
│ ├── assets // 全项目通用图片文件等
│ ├── commons // 全项目通用js,业务相关
│ ├── components // 全项目通用组件,业务相关
│ ├── config // 项目构建补充配置
│ ├── layouts // 页面框架布局组件+
│ ├── mock // 模拟数据
│ ├── models // 模块封装,基于redux,提供各组件共享数据、共享逻辑
│ ├── pages // 主项目页面组件
│ ├── ├── components // 主项目页面公共组件
│ ├── ├── ├── Header // 主项目页面公共组件
│ ├── ├── ├── ├── index.jsx // 主项目页面公共组件入口
│ ├── ├── ├── ├── style.less // 主项目页面公共组件样式
│ ├── ├── style.less // 主项目页面样式文件
│ ├── ├── index.jsx // 主项目页面入口文件
│ ├── ├── Project // 子项目顶级目录
│ ├── ├── ├── demo // 子项目一目录
│ ├── ├── ├── ├── assets // 子项目一公共图片文件
│ ├── ├── ├── ├── components // 子项目一公共组件库
│ ├── ├── ├── ├── pages // 子项目一页面目录
│ ├── ├── ├── ├── ├── bussinessadmin // 子项目一页面模块
│ ├── ├── ├── ├── ├── ├── components // 子项目一页面模块公共组件,components里面目录结构同上
│ ├── ├── ├── ├── ├── ├── index.jsx // 子项目一页面模块入口
│ ├── ├── ├── ├── ├── ├── style.less // 子项目一页面模块样式
│ ├── ├── ├── ├── style // 子项目一样式目录
│ ├── ├── ├── ├── index.jsx // 子项目一入口文件
│ ├── router // 路由
│ ├── ant.less // 主体配置
│ ├── App.js // 根组件
│ ├── index.css // 全局样式 慎用
│ ├── index.dark.css // 全局样式 慎用
│ ├── index.js // 项目入口
│ ├── menus.js // 菜单配置
│ ├── setupProxy.js // 后端联调代理配置
│ └── theme.less // 主题变量
├── package.json
├── README.md
└── yarn.lock
$ yarn
$ yarn start
#指定端口
$ PORT=8080 yarn start
# HTTPS方式启动
$ HTTPS=true yarn start
$ yarn build
// 构建输入到指定目录
$ BUILD_PATH=../dist yarn build
// 开发启动
$ src/commons/PRE_ROUTER.js更改前缀 yarn start
// 开发访问
'http://localhost:XXXX/前缀/'
//生产环境 同上
$ src/commons/PRE_ROUTER.js更改前缀 yarn start
// 访问
'http://xxx.com/前缀'
// 代理
$ '生产环境代理前缀请根据实际项目情况进行配置'
//路由
$ '根据目前路由router下新建路由文件,并在主路由文件引入,单独打包目录下项目直接注释引入即可(没有的路由不会进行打包),登录可直接覆盖,前提为已修改前缀等信息'
//在/src/menus.js文件中配置菜单数据,前端硬编码或异步加载菜单数据。
// 菜单支持头部、左侧、头部+左侧三种布局方式,默认左侧菜单。如需放开设置,请到'src/layouts/index.jsx'放开注释
//菜单字段说明。
字段 必须 说明
key 是 //需要唯一
parentKey 否 //用于关联父级
path 是 //菜单对应的路由地址
text 是 //菜单标题
icon 否 //菜单图标配置
url 否 //菜单对应会打开url对应的iframe页面,如果配置了url,path将无效
target 否 //配合url使用,菜单将为a标签 <a href={url} target={target}>{text}</a>
order 否 //菜单排序,数值越大越靠前显示
type 否 //如果菜单数据中携带功能权限配置,type==='1' 为菜单,type==='2'为功能
code 否 //功能码,如果是type==='2',会用到此字段
//配置组件
import React, {Component} from 'react';
import config from 'src/commons/config-hoc';
@config({
title: '页面title',
ajax: true,
...
})
export default class SomePage extend Component {
componentDidMount() {
this.props.ajax
.get(...)
.then(...)
}
...
}
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
noFrame | boolean | false | 标记当前页面为不需要导航框架的页面,比如登录页,通过脚本抓取实现 |
noAuth | boolean | false | 标记当前页面为不需要登录即可访问的页面,通过脚本抓取实现 |
keepAlive | boolean | - | 标记当前页面内容在页面切换之后是否保持 |
title | boolean 或 string 或 ReactNode 或 object 或 function(props) | true | true:当前页面显示通过菜单结构自动生成的title;false:当前页面不显示title;string:自定义title;object:{text,icon} text为显示的名称,icon为图标;function(props): 返回值作为title |
breadcrumbs | boolean 或 array 或 function(props) | true | true:当前页面显示通过菜单结构自动生成的面包屑;false:当前页面不显示面包屑;object:[{icon, text, ...}];function(props): 返回值作为面包屑 |
appendBreadcrumbs | array 或 function(props) | [] | 在当前面包屑基础上添加;function(props): 返回值作为新添加的面包屑 |
pageHead | boolean | - | 页面头部是否显示 |
side | boolean | - | 页面左侧是否显示 |
sideCollapsed | boolean | - | 左侧是否收起 |
ajax | boolean | true | 是否添加ajax高阶组件,内部可以通过this.props.ajax使用ajax API,组件卸载时,会自动打断未完成的请求 |
router | boolean | false | 是否添加withRouter装饰器,组件内部可以使用this.props.history等API |
query | boolean | false | 是否添加地址查询字符串转换高阶组件,内部可以通过this.props.query访问查询字符串 |
connect | boolean 或 function(state) | false | 是否与redux进行连接,true:只注入了this.props.action相关方法;false:不与redux进行连接;(state) => ({title: state.page.title}):将函数返回的数据注入this.props |
event | boolean | false | 是否添加event高阶组件,可以使用this.props.addEventListener添加dom事件,并在组件卸载时会自动清理;通过this.props.removeEventListener移出dom事件 |
pubSub | boolean | false | 是否添加发布订阅高阶组件,可以使用this.props.subscribe(topic, (msg, data) => {...})订阅事件,并在组件卸载时,会自动取消订阅; 通过this.props.publish(topic, data)发布事件 |
modal | string 或 object | false | 当前组件是否是modal。string: 弹框标题;object:弹框配置 |
注:
noFrame
、noAuth
、keepAlive
只有配置了path
才有效!title
、breadcrumbs
、appendBreadcrumbs
、pageHead
、side
、sideCollapsed
最好在路由对应的页面组件中使用//页面保持
//页面渲染一次之后会保持状态,再次跳转到此页面不会重新创建或重新渲染
开启方式:
1. /src/models/system.js initState.keepAlive 属性修改默认值
2. config装饰器 keepAlive属性
config
装饰器为组件注入了两个事件 onComponentWillShow
、onComponentWillHide
,如果页面使用了 Keep Alive功能,切换显示/隐藏时会触发
@config({
...
})
export default class SomePage extends React.Component {
constructor(...props) {
super(...props);
this.props.onComponentWillShow(() => {
// do some thing
});
this.props.onComponentWillHide(() => {
// do some thing
});
}
...
}
系统提供了页面的跟节点PageContent,有如下特性:
是否显示footer,默认true
<PageContent footer={false}>
...
</PageContent>
显示loading,有两种方式。
this.props.action.page.showLoading();
this.props.action.page.hideLoading();
const {loading} = this.state;
<PageContent loading={loading}>
...
</PageContent>
添加、修改等场景,往往会用到弹框,antd Modal组件使用不当会产生脏数据问题(两次弹框渲染数据互相干扰)
系统提供了基于modal封装的高阶组件,每次弹框关闭,都会销毁弹框内容,避免互相干扰
modal高阶组件集成到了config中,也可以单独引用:import { ModalContent } from 'src/commons/ra-lib';
import React from 'react';
import config from 'src/commons/config-hoc';
import { ModalContent } from 'src/commons/ra-lib';
export default config({
modal: {
title: '弹框标题',
},
})(props => {
const {onOk, onCancel} = props;
return (
<ModalContent
onOk={onOk}
onCancel={onCancel}
>
弹框内容
</ModalContent>
);
});
modal所有参数说明如下:
弹框内容通过 ModalContent包裹,具体参数如下:
参数 | 类型 | 默认值 | 说明 |
---|---|---|---|
surplusSpace | boolean | false | 是否使用屏幕垂直方向剩余空间 |
otherHeight | number | - | 除了主体内容之外的其他高度,用于计算主体高度; |
loading | boolean | false | 加载中 |
loadingTip | - | - | 加载提示文案 |
footer | - | - | 底部 |
okText | string | - | 确定按钮文案 |
onOk | function | - | 确定按钮事件 |
cancelText | string | - | 取消按钮文案 |
onCancel | function | - | 取消按钮事件 |
resetText | string | - | 重置按钮文案 |
onReset | function | - | 重置按钮事件 |
style | object | - | 最外层容器样式 |
bodyStyle | object | - | 内容容器样式 |
系统路由使用 react-router,通过route-loader 将路由内容填充到/src/pages/page-routes.js文件,支持两种写法:
export const PAGE_ROUTE = '/path';
@config({
path: '/path',
})
export default class SomePage extends React.Component {
...
}
二级页面如果要保持父级菜单的选中状态,以父级path开始并以/_/
作为分隔符即可:parent/path/_/child/path
// parent page
@config({
path: '/parent/path'
})
export default class Parent extends React.Component {
...
}
// child page
@config({
path: '/parent/path/_/child/path'
})
export default class Parent extends React.Component {
...
}
系统的ajax请求基于axios封装。 基于restful规范,提供了5个方法:
//第一种 config装饰器ajax属性(推荐)
import React, {Component} from 'react';
import config from 'src/commons/config-hoc';
@config({
ajax: true,
...
})
export default class SomePage extend Component {
componentDidMount() {
this.props.ajax
.get(...)
.then(...)
}
...
}
//第二种 ajax装饰器
import React, {Component} from 'react';
import {ajaxHoc} from 'src/commpons/ajax';
@ajaxHoc()
export default class SomePage extend Component {
componentDidMount() {
this.props.ajax
.get(...)
.then(...)
}
...
}
//第三种 直接引入ajax对象
import React, {Component} from 'react';
import {sxAjax} from 'src/commpons/ajax';
export default class SomePage extend Component {
componentDidMount() {
sxAjax.post(...).then(...);
// 组件卸载或者其他什么情况,需要打算ajax请求,可以用如下方式
const ajax = sxAjax.get(...);
ajax.then(...).finally(...);
ajax.cancel();
}
...
}
注:config、ajaxHoc方式做了封装,页面被卸载之后会自动打断未完成的请求
所有的ajax方法参数统一,都能够接受三个参数:
参数 | 说明 |
---|---|
url | 请求地址 |
params | 请求传递给后端的参数 |
options | 请求配置,即axios的配置。 |
参数 | 说明 |
---|---|
axios配置 | 可以接受axios参数 |
successTip | 扩展的参数,成功提示 |
errorTip | 扩展的参数,失败提示 |
noEmpty | 扩展的参数,过滤掉 ''、null、undefined的参数,不提交给后端 |
originResponse | 扩展参数,.then中可以拿到完整的response,而不只是response.data |
注:全局默认参数可以在src/commons/ajax.js中进行配置,默认baseURL='/api'、timeout=1000 * 60。
this.props.ajax.del('/user/1', null, {successTip: '删除成功!', errorTip: '删除失败!', noEmpty: true});
系统扩展了promise,提供了finally方法,用于无论成功还是失败,都要进行的处理。一般用于关闭loading
this.setState({loading: true});
this.props.ajax
.get('/url')
.then(...)
.finally(() => this.setState({loading: false}));
前后端并行开发,为了方便后端快速开发,不需要等待后端接口,系统提供了mock功能。基于mockjs
在/src/mock目录下进行mock数据编写,比如:
import {getUsersByPageSize} from './mockdata/user';
export default {
'post /mock/login': (config) => {
const {
userName,
password,
} = JSON.parse(config.data);
return new Promise((resolve, reject) => {
if (userName !== 'test' || password !== '111') {
setTimeout(() => {
reject({
code: 1001,
message: '用户名或密码错误',
});
}, 1000);
} else {
setTimeout(() => {
resolve([200, {
id: '1234567890abcde',
name: 'MOCK 用户',
loginName: 'MOCK 登录名',
}]);
}, 1000);
}
});
},
'post /mock/logout': {},
'get /mock/user-center': (config) => {
const {
pageSize,
pageNum,
} = config.params;
return new Promise((resolve) => {
setTimeout(() => {
resolve([200, {
pageNum,
pageSize,
total: 888,
list: getUsersByPageSize(pageSize),
}]);
}, 1000);
});
},
'get re:/mock/user-center/.+': {id: 1, name: '熊大', age: 22, job: '前端'},
'post /mock/user-center': true,
'put /mock/user-center': true,
'delete re:/mock/user-center/.+': 'id',
}
为了方便mock接口编写,系统提供了简化脚本(/src/mock/simplify.js),上面的例子就是简化写法
对象的key由 method url delay,各部分组成,以空格隔开
字段 | 说明 |
---|---|
method | 请求方法 get post等 |
url | 请求的url |
delay | 模拟延迟,毫秒 默认1000 |
系统封装的ajax可以通过以下两种方式,自动区分是mock数据,还是真实后端数据,无需其他配置
mock请求:
this.props.ajax.get('/mock/users').then(...);
如果后端真实接口准备好之后,去掉url中的/mock即可
注:mock功能只有开发模式下开启了,生产模式不会开启mock功能,如果其他环境要开启mock 使用MOCK=true参数,比如 MOCK=true yarn build
系统使用less进行样式的编写。 为了避免多人合作样式冲突,系统对src下的less文件启用了Css Module,css文件没有使用Css Module。
style.less
.root{
width: 100%;
height: 100%;
}
Some.jsx
import '/path/to/style.less';
export default class Some extends React.Component {
render() {
return (
<div styleName="root"></div>
);
}
}
注:基础组件不使用Css Module,不利于样式覆盖;
使用less,通过样式覆盖来实现。
/src/theme.less
通过less-loader的modifyVars
覆盖less中的变量;注:目前每次修改了theme.less 需要重新yarn start 才能生效
为了满足不同系统的需求,提供了四种导航布局:
src/models/index.js
指定布局方式;有些页面可能不需要显示导航,可以通过如下任意一种方式进行设置:
@config({
noFrame: true,
})
/path/to?noFrame=true
页面头部标签,有如下特性:
Keep Page Alive
);system model(redux)中提供了如下操作tab页的方法:
API | 说明 |
---|---|
setCurrentTabTitle(title) | 设置当前激活的 tab 标题 title: stirng 或 {text, icon} |
refreshTab(targetPath) | 刷新targetPath指定的tab页内容(重新渲染) |
refreshAllTab() | 刷新所有tab页内容(重新渲染) |
closeCurrentTab() | 关闭当前tab页 |
closeTab(targetPath) | 关闭targetPath对应的tab页 |
closeOtherTabs(targetPath) | 关闭除了targetPath对应的tab页之外的所有tab页 |
closeAllTabs() | 关闭所有tab页,系统将跳转首页 |
closeLeftTabs(targetPath) | 关闭targetPath对应的tab页左侧所有tab页 |
closeRightTabs(targetPath) | 关闭targetPath对应的tab页右侧所有的tab页 |
使用方式:
import config from 'src/commons/config-hoc';
@config({
connect: true,
})
export default class SomeComponent extends React.Component {
componentDidMount() {
this.props.action.system.closeTab('/some/path');
}
...
}
注:
this.props.history.push('/some/path')
,就会选中或者新打开一个tab页(/path
与 /path?name=Tom
属于不同url地址,会对应两个tab页);基于redux进行封装,不改变redux源码,可以结合使用redux社区中其他解决方案。
注:一般情况下,用不到redux~
models/page.js
中的写法;所有的model直接在models或pages下定义:
model模块名规则:
/path/to/models/user-center.js --> userCenter;
/path/to/models/user.js --> user;
/path/to/pages/users/model.js --> users;
/path/to/pages/users/job.model.js --> job;
/path/to/pages/users/user-center.model.js --> userCenter;
/path/to/pages/users/user.center.model.js --> userCenter;
提供了多种种方式,装饰器方式、函数调用、hooks、js文件直接使用;
推荐使用装饰器方式
import {connect} from 'path/to/models';
@connect(state => {
return {
...
}
})
class Demo extends Component{
...
}
import {connectComponent} from 'path/to/models';
class Demo extends Component {
...
}
function mapStateToProps(state) {
return {
...
};
}
export default connectComponent({LayoutComponent: Demo, mapStateToProps});
import {useSelector} from 'react-redux';
import {useAction} from 'src/models';
export default () => {
const action = useAction();
const show = useSelector(state => state.side.show);
console.log(show);
useEffect(() =>{
action.side.hide()
}, []);
return <div/>
}
对 useSelector 的说明:
useSelector(select) 默认对 select 函数的返回值进行引用比较 ===,并且仅在返回值改变时触发重渲染。
即:如果select函数返回一个临时对象,会多次re-render
最好不要这样使用:
const someData = useSelector(state => {
// 每次都返回一个新对象,导致re-render
return {name: state.name, age: state.age};
})
最好多次调用useSelector,单独返回数据,或者返回非引用类型数据
const name = useSelector(state => state.firstName + state.lastName);
const age = useSelector(state => state.age);
没有特殊需求,一般不会在普通js文件中使用
import {action, store} from 'src/models';
// 获取数据
const state = store.getState();
// 修改数据
action.side.hide();
action reducer 二合一,省去了actionType,简化写法;
注意:
一个函数,即可作为action方法,也作为reduce使用
// page.model.js
export default {
initialState: {
title: void 0,
name: void 0,
user: {},
toggle: true,
},
setTitle: title => ({title}),
setName: (name, state, action) => {
const {name: prevName} = state;
if(name !== prevName) return {name: 'Different Name'};
},
setUser: ({name, age} = {}) => ({user: {name, age}}),
setToggle: (arg, state) => ({toggle: !state.toggle}),
}
// 使用
this.props.action.page.setTitle('my title');
通过配置的方式,可以让redux中的数据自动与localStorage同步
export default {
initialState: {
title: '',
show: true,
user: {},
users: [],
job: {},
total: 0,
loading: false,
...
},
// initialState会全部同步到localStorage中
// syncStorage: true,
// 配置部分存数据储到localStorage中
syncStorage: {
titel: true,
user: { // 支持对象指定字段,任意层次
name: true,
address: {
city: true,
},
},
job: true,
users: [{name: true, age: true}], // 支持数组
},
}
如果action有额外的数据处理,并且一个action 只对应一个reducer,这种写法不需要指定actionType,可以有效简化代码;
export default {
initialState: {
title: '',
...
},
arDemo: {
// 如果是函数返回值将作为action.payload 传递给reducer,如果非函数,直接将payload的值,作为action.payload;
payload(options) {...},
// 如果是函数返回值将作为action.meta 传递给reducer,如果非函数,直接将meta的值,作为action.meta;
meta(options) {...},
reducer(state, action) {
returtn {...newState}; // 可以直接返回要修改的数据,内部封装会与原state合并`{...state, ...newState}`;
},
},
};
export default {
initialState: {
title: '',
...
},
fetchUser: {
// 异步action payload 返回promise
payload: ({params, options}) => axios.get('/mock/users', params, options),
// 异步action 默认使用通用异步meta配置 commonAsyncMeta,对successTip errorTip onResolve onReject onComplete 进行了合理的默认值处理,需要action以对象形式传参调用
// meta: commonAsyncMeta,
// meta: {
// successTip: '查询成功!欧耶~',
// errorTip: '自定义errorTip!马丹~',
// },
// meta: () => {
// return {...};
// },
// 基于promise 异步reducer写法;
reducer: {
pending: (state, action) => ({loading: true}),
resolve(state, {payload = {}}) {
const {total = 0, list = []} = payload;
return {
users: list,
total,
}
},
complete: (state, action) => ({loading: false}),
}
},
};
调用方式:
this.props.action.user
.fetchUser({
params,
options,
successTip,
errorTip,
onResolve,
onReject,
onComplete
});
参数约定为一个对象,各个属性说明如下:
参数 | 说明 |
---|---|
params | 请求参数 |
options | 请求配置 |
successTip | 成功提示信息 |
errorTip | 错误提示信息 |
onResolve | 成功回调 |
onReject | 失败回调 |
onComplete | 完成回调,无论成功、失败都会调用 |
支持这种比较传统的写法,一般也不会太用到
import {createAction} from 'redux-actions';
export const types = {
GET_MENU_STATUS: 'MENU:GET_MENU_STATUS', // 防止各个模块冲突,最好模块名开头
};
export default {
initialState: {
title: '',
...
},
// 单独action定义,需要使用actionType与reducer进行关联
actions: {
getMenuStatus: createAction(types.GET_MENU_STATUS),
},
// 单独reducer定义,使用了actionType,不仅可以处理当前model中的action
// 也可以处理其他任意action(只要actionType能对应)
reducers: {
[types.GET_MENU_STATUS](state) {
...
return {
...
};
}
},
}
系统菜单、具体功能点都可以进行权限控制。
菜单由后端提供(一般系统都是后端提供),后台通过登录用户返回用户的菜单权限;页面只显示获取到的菜单;
系统提供了一个基础的菜单、权限管理页面,需要后端配合存储数据。
可以通过src/components/permission
组件对功能的权限进行控制
import React, {Component} from 'react';
import Permission from 'src/components/permission';
export default class SomePage extends Component {
render() {
return (
<div>
<Permission code="USER_ADD">
<Button>添加用户</Button>
</Permission>
</div>
);
}
}
注:权限的code前端使用时会硬编码,注意语义化、唯一性。
一般系统都会提供角色管理功能,系统中提供了一个基础的角色管理功能,稍作修改即可使用。
开发时,要与后端进行接口对接,可以通过代理与后端进行连接,开发代理配置在src/setupProxy.js
中编写
const proxy = require('http-proxy-middleware');
const prefix = process.env.AJAX_PREFIX || '/api';
module.exports = function (app) {
app.use(proxy(prefix,
{
target: 'http://localhost:3000/',
pathRewrite: {
['^' + prefix]: '', // 如果后端接口无前缀,可以通过这种方式去掉
},
changeOrigin: true,
secure: false, // 是否验证证书
ws: true, // 启用websocket
},
));
};
注:更多代理配置请参考http-proxy-middleware
前端默认ajax前缀 /api 可以通过 AJAX_PREFIX 参数进行修改。
这里只是参考文件,根据自己的项目需求自行配置
.
├── /usr/local/nginx/html
│ ├── static
│ ├── index.html
│ └── favicon.ico
# 后端服务地址
upstream api_service {
server xxx.xxx.xxx.xxx:xxxx;
keepalive 2000;
}
server {
listen 80;
server_name www.xxxx.com xxxx.com; # 域名地址
root /usr/local/nginx/html; # 前端静态文件目录
location / {
index index.html;
try_files $uri $uri/ /index.html; #react-router 防止页面刷新出现404
}
# 静态文件缓存,启用Cache-Control: max-age、Expires
location ~ ^/static/(css|js|media)/ {
expires 10y;
access_log off;
add_header Cache-Control "public";
}
# 代理ajax请求 前端ajax请求以 /api 开头
location ^~/api {
rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀
proxy_pass http://api_service/;
proxy_set_header Host $http_host;
proxy_set_header Connection close;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Server $host;
}
}
多个项目挂载到同一个域名下,可以通过子目录方式区分
比如,如下地址各对应一个项目
前端项目构建时,添加BASE_NAME PUBLIC_URL参数
BASE_NAME=/project1 PUBLIC_URL=/project1 yarn build
.
├── /home/ubuntu/front-springrain
│ ├── build // 主项目 静态文件目录
│ │ ├── static
│ │ ├── index.html
│ │ └── favicon.ico
│ ├── project1 // 子项目静态目录 名称与 location /project1 location ~ ^/project1/static/.* 配置对应
│ │ ├── static
│ │ ├── index.html
│ │ └── favicon.ico
upstream api_service {
server xxx.xxx.xxx.xxx:xxxx;
keepalive 2000;
}
upstream api_service_project1 {
server xxx.xxx.xxx.xxx:xxxx;
keepalive 2000;
}
server {
listen 80;
server_name www.xxxx.com xxxx.com; # 域名地址
# Allow file uploads
client_max_body_size 100M;
# 主项目配置,访问地址 http://www.xxxx.com
location / {
root /home/ubuntu/front-springrain/build;
index index.html;
try_files $uri $uri/ /index.html;
}
# 静态文件缓存,启用Cache-Control: max-age、Expires
location ~ ^/static/.* {
root /home/ubuntu/front-springrain/build;
expires 20y;
access_log off;
add_header Cache-Control "public";
}
# 代理ajax请求 前端ajax请求以/api开头
location ^~/api {
rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀
proxy_pass http://api_service/;
proxy_set_header Host $http_host;
proxy_set_header Connection close;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Server $host;
}
# 子项目配置 访问地址 http://www.xxxx.com/project1
location /project1 {
root /home/ubuntu/front-springrain;
index index.html;
try_files $uri $uri/ /project1/index.html;
}
# 静态文件缓存,启用Cache-Control: max-age、Expires
location ~ ^/project1/static/.* {
root /home/ubuntu/front-springrain;
expires 10y;
access_log off;
add_header Cache-Control "public";
}
# 代理ajax请求 前端ajax请求以 /project1_api 开头
location ^~/project1_api {
rewrite ^/api/(.*)$ /$1 break; # 如果后端接口不是统一以api开头,去掉api前缀
proxy_pass http://api_service_project1/;
proxy_set_header Host $http_host;
proxy_set_header Connection close;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Server $host;
}
}
通过给元素添加相应的class,控制打印内容:
.just-print
只在打印时显示.no-print
在打印时不显示如果前端项目,不是git根目录,在提交的时候,会报错 Not a git repository
修改package.json,lint-staged 如下即可
"lint-staged": {
"gitDir": "../",
"linters": {
"**/*.{js,jsx}": "lint-staged:js",
"**/*.less": "stylelint --syntax less"
}
},
const name = res?.data?.user?.name || '匿名';
'input', 'hidden', 'number', 'textarea', 'password', 'mobile', 'email', 'select', 'select-tree', 'checkbox', 'checkbox-group', 'radio', 'radio-button', 'radio-group', 'switch', 'date', 'time', 'date-time', 'date-range', 'cascader', 'transfer', 'icon-picker'
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。