Проект начинается
На основе React 16.x, Ant Design 4.x и react-admin.
├── config // проектная конфигурация сборки
├── public // статические файлы, не участвующие в сборке
├── scripts // скрипты сборки
└── src
├── assets // общие изображения для всего проекта
├── commons // общие js для всего проекта, связанные с бизнесом
├── components // общие компоненты для всего проекта, связанные с бизнесом
├── config // дополнительная конфигурация сборки проекта
├── layouts // компоненты структуры страницы +
├── mock // данные моделирования
├── models // модули упаковки, основанные на redux, предоставляют общие данные и логику компонентов
├── pages // каталог основных страниц проекта
│ ├── Project // каталог верхнего уровня подпроекта
│ │ ├── nft // NFT — каталог версии ПК
│ │ │ ├── assets // общедоступные файлы изображений NFT — версия ПК
│ │ │ ├── components // общедоступная библиотека компонентов NFT — версия ПК
│ │ │ └── pages // каталог страниц NFT — версия ПК
│ │ └── mobile // каталог мобильных проектов
│ │ ├── nft-mobile // NFT — каталог версий WeChat
│ │ │ ├── assets // общедоступные файлы изображений NFT — версия WeChat
│ │ │ ├── components // общедоступная библиотека компонентов NFT — версия WeChat
│ │ │ └── pages // каталог страниц NFT — версия WeChat
├── 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
После установки зависимостей файл setupProxy.js в каталоге src будет использоваться как файл локального прокси проекта. Пожалуйста, своевременно измените адрес интерфейса, который вам нужно проксировать!
!!! Версия WeChat может работать только в WeChat, и вы должны ввести свой собственный appid в router/AuthRoute.jsx. В настоящее время он пуст.
Это два проекта, которые опираются на одну и ту же базовую структуру, разделяют PC и WeChat терминалы и могут переключаться между ними, изменяя маршруты в router/app.router.js и заменяя AuthRoute (соответственно AuthRoute.jsx для версии WeChat и AuthRoutePC.jsx для PC версии) в src/commons/PRE_ROUTER.js. Также необходимо изменить конфигурацию в package.json, чтобы обновить домашнюю страницу до соответствующего названия проекта.
Конфигурация в двух наборах PRE_ROUTER.js выглядит следующим образом:
Версия WeChat: Маршрут префикс ---- /front_nft_mobile Адрес запроса префикс ---- /api/nft Страница входа ---- /nft_mobile_home
PC версия: Маршрут префикс ---- /front_nft_pc Адрес запроса префикс ---- /api/nft Страница входа ---- /nft_home
$ yarn start
# Укажите порт
$ PORT=8080 yarn start
# Запуск HTTPS
$ HTTPS=true yarn start
$ yarn build
# Сборка в указанный каталог
$ BUILD_PATH=../dist yarn build
// Разработка и запуск
$ BASE_NAME=/synext-admin yarn start
// Доступ к разработке
'http://localhost:XXXX/synext-admin/'
// Производственная среда такая же, как и выше
$ BASE_NAME=/synext-admin yarn start
// доступ
'http://xxx.com/synext-admin'
// Файл /src/menus.js используется для настройки данных меню, а меню загружается спереди или асинхронно.
// Меню поддерживает три макета: заголовок, левая сторона и заголовок + левая сторона. По умолчанию используется левое меню. Если вы хотите настроить его, перейдите в 'src/layouts/index.jsx' и снимите комментарий.
// Описание полей меню.
Поле Обязательно Описание
key Да // Должен быть уникальным
parentKey Нет // Используется для связывания родительского уровня
path Да // Маршрут, соответствующий меню
text Да // Заголовок меню
icon Нет // Настройка значка меню
url Нет // Меню соответствует открытому URL-адресу iframe страницы, если настроено url, путь недействителен
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: 'Заголовок страницы',
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: заголовок страницы формируется автоматически через меню; false: заголовок не отображается; string: пользовательский заголовок; object: {text, icon}, где text — отображаемое название, а icon — иконка; function(props): возвращаемое значение используется как заголовок. |
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 для работы с 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 | Позволяет использовать компонент высокого уровня для добавления событий DOM. При удалении компонента события автоматически удаляются. Можно использовать this.props.addEventListener для добавления событий и this.props.removeEventListener для их удаления. |
pubSub | boolean | false | Позволяет использовать компонент высокого уровня для реализации модели публикации-подписки. Можно использовать this.props.subscribe(topic, (msg, data) => {...}) для подписки на события и this.props.publish(topic, data) для их публикации. |
modal | string или object | false | Указывает, является ли текущий компонент модальным. string: заголовок диалогового окна; object: конфигурация диалогового окна. |
Декоратор 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
});
}
...
}
Система предоставляет компонент страницы с такими характеристиками:
По умолчанию значение свойства footer
— true.
<PageContent footer={false}>...</PageContent>
Загрузка страницы может быть показана двумя способами.
Способ model:
this.props.action.page.showLoading();
this.props.action.page.hideLoading();
Способ props:
const { loading } = this.state;
<PageContent loading={loading}>...</PageContent>;
Всплывающие окна часто используются для добавления, изменения и других операций. Если использовать компонент antd 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>
);
});
Все параметры модального компонента описаны ниже:
options.fullScreen
по умолчанию равен false. Он определяет, будет ли модальное окно отображаться в полноэкранном режиме.Содержимое всплывающего окна оборачивается компонентом 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 {
...
}
Если вторичная страница должна сохранять состояние выбора родительской страницы, путь должен начинаться с пути родительской страницы и разделяться символом _/_
: parent/path/_/child/path
.
// Родительская страница
@config({
path: '/parent/path'
})
export default class Parent extends React.Component {
...
}
// Дочерняя страница
@config({
path: '/parent/path/_/child/path'
})
export default class Child extends React.Component {
...
}
AJAX-запросы в системе основаны на axios. На основе restful-соглашения предоставляются пять методов:
})
export default class SomePage extends Component {
componentDidMount() {
this.props.ajax
.get(...)
.then(...)
}
...
}
// Второй вид ajax-декоратора
import React, {Component} from 'react';
import {ajaxHoc} from 'src/commpons/ajax';
@ajaxHoc()
export default class SomePage extends Component {
componentDidMount() {
this.props.ajax
.get(...)
.then(...)
}
...
}
// Третий вид — прямое введение ajax-объекта
import React, {Component} from 'react';
import {sxAjax} from 'src/commpons/ajax';
export default class SomePage extends 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), и приведённый выше пример написан с использованием упрощённого синтаксиса.
Ключ объекта соответствует методу и URL. delay, различные части составляют, разделенные пробелами
Поле | Описание |
---|---|
method | запросы get post и т. д. |
url | запрашиваемый URL |
delay | имитация задержки, миллисекунды, по умолчанию 1000 |
Система инкапсулирует ajax, который можно автоматически различать между mock данными и реальными данными бэкэнда без дополнительной настройки:
mock-запросы:
this.props.ajax.get('/mock/users').then(...);
Если готов реальный интерфейс бэкэнда, удалите /mock из URL.
Примечание: функция mock доступна только в режиме разработки. В производственном режиме функция mock не будет включена. Если вы хотите включить функцию mock в других средах, используйте параметр MOCK=true, например MOCK=true yarn build
.
Система использует less для написания стилей. Чтобы избежать конфликтов при совместной работе над стилями, система активировала Css Module для файлов less в папке src. Файлы 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
, используя modifyVars
less-loader для переопределения переменных less.Примечание: в настоящее время каждый раз, когда вы изменяете theme.less, вам нужно перезапустить yarn start, чтобы изменения вступили в силу.
Для удовлетворения различных потребностей системы предоставляются четыре типа навигационной компоновки:
src/models/index.js
.Некоторые страницы могут не требовать отображения навигации. Можно настроить следующим образом:
На страницах заголовков есть следующие функции:
В системе model (redux) предоставляются следующие методы работы с вкладками:
API | Описание |
---|---|
setCurrentTabTitle(title) | Установить текущий заголовок активной вкладки title: stirng или {text, icon} |
refreshTab(targetPath) | Обновить содержимое целевой вкладки targetPath (повторно отобразить) |
refreshAllTab() | Обновите содержимое всех вкладок (повторное отображение) |
closeCurrentTab() | Закройте текущую вкладку |
closeTab(targetPath) | Закрыть вкладку targetPath |
closeOtherTabs(targetPath) | Закройте все вкладки, кроме targetPath |
closeAllTabs() | Закройте все вкладки и перейдите на главную страницу |
closeLeftTabs(targetPath) | Закройте все левые вкладки от targetPath |
closeRightTabs(targetPath) | Закройте все правые вкладки от targetPath |
Использование:
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')
, выбирается или открывается новая вкладка (/path
и /path?name=Tom
соответствуют двум вкладкам).Инкапсулированный на основе redux, без изменения исходного кода redux, может использоваться в сочетании с другими решениями сообщества redux.
Примечание: обычно нет необходимости использовать redux~
models/page.js
для примера написания;Все модели определяются непосредственно в моделях или папках страниц:
Правила именования модулей моделей:
/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;
Примечание: в тексте запроса присутствуют фрагменты кода на языке программирования JavaScript, а также ссылки на внешние ресурсы. Эти фрагменты и ссылки не были переведены. Предоставили различные способы: декоратор, функция вызова, хуки, использование в 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 возвращает временный объект, будет происходить многократное повторное отображение.
Лучше не использовать таким образом:
const someData = useSelector(state => {
// Каждый раз возвращается новый объект, что приводит к повторному отображению
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 для упрощения написания;
Обратите внимание:
Одна функция может использоваться как метод 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: {
// Если возвращаемое значение является функцией, оно будет передано в reducer как payload action. Если это не функция, значение payload будет напрямую передано как payload action;
payload(options) {...},
// Если возвращаемое значение является функцией, оно будет передано в reducer как meta action. Если это не функция, мета-значение будет напрямую передано как meta action;
meta(options) {...},
reducer(state, action) {
returtn {...newState}; // Можно напрямую вернуть данные для модификации, внутренняя реализация объединит их с исходными данными `{...state, ...newState}`;
},
},
};
export default {
initialState: {
title: '',
...
},
fetchUser: {
// Асинхронный payload действия возвращает обещание
payload: ({params, options}) => axios.get('/mock/users', params, options),
// По умолчанию асинхронные действия используют общую асинхронную конфигурацию meta commonAsyncMeta, которая разумно обрабатывает значения successTip errorTip onResolve onReject onComplete, и действие должно вызываться в форме объекта для передачи параметров
// 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: '',
...
},
// Отдельное определение действия, требующее использования actionType и reducer для связи
actions: {
getMenuStatus: createAction(types.GET_MENU_STATUS),
},
// Отдельный редуктор, использующий actionType, может обрабатывать не только текущее состояние модели, но и другие произвольные действия (при условии, что actionType может соответствовать)
reducers: {
[types.GET_MENU_STATUS](state) {
...
return {
...
};
}
},
}
# Контроль доступа
Системные меню и конкретные функции могут быть ограничены.
## Меню контроля доступа
Меню предоставляется бэкэндом, и бэкэнд возвращает меню пользователя после входа в систему; страница отображает только полученное меню.
Система предоставляет базовую страницу меню и управления правами доступа, которая требует поддержки со стороны бэкэнда для хранения данных.
## Контроль доступа к функциям
Компонент «src/components/permission» можно использовать для управления доступом к функциям.
```js
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>
);
}
}
Примечание: код разрешения будет жёстко закодирован при использовании на переднем конце, обратите внимание на семантику и уникальность.
Большинство систем предоставляют функцию управления ролями, система предоставляет базовую функцию управления ролями, которую можно легко модифицировать.
При разработке необходимо подключиться к бэкэнду через прокси, настройка разработки прокси находится в файле «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, запросы переднего конца начинаются с /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/synext-admin
│ ├── 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/synext-admin/build;
index index.html;
try_files $uri $uri/ /index.html;
}
# настройка кэширования статических файлов, включение Cache-Control: max-age, Expires
location ~ ^/static/.* {
root /home/ubuntu/synext-admin/build;
expires 20y;
access_log off;
add_header Cache-Control "public";
}
# проксирование запросов ajax, запросы фронтенда начинаются с /api
location ^~/api {
rewrite ^/api/(.*)$ /$1 break; # если интерфейс не использует унифицированный префикс 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/synext-admin;
index index.html;
try_files $uri $uri/ /project1/index.html;
}
# настройка кэширования статических файлов, включение Cache-Control: max-age, Expires
location ~ ^/project1/static/.* {
root /home/ubuntu/synext-admin;
expires 10y;
access_log off;
add_header Cache-Control "public";
}
# проксирование запросов ajax, запросы фронтенда начинаются с /project1_api
location ^~/project1_api {
rewrite ^/api/(.*)$ /$1 break; # если интерфейс не использует унифицированный префикс 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;
}
}
Элементы могут быть добавлены с соответствующими классами для управления содержимым при печати:
.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'
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Комментарии ( 0 )