1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/shengjian-tech-opennft-front

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Это зеркальный репозиторий, синхронизируется ежедневно с исходного репозитория.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Проект начинается

Проект

На основе 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
        });
    }
    ...
}

Контейнер страницы PageContent

Система предоставляет компонент страницы с такими характеристиками:

  • добавлены стили margin и padding;
  • добавлен нижний колонтитул;
  • поддерживается загрузка страницы;
  • автоматически определяется, есть ли нижний инструмент FixBottom, и для него освобождается место.

По умолчанию значение свойства footer — true.

<PageContent footer={false}>...</PageContent>

Загрузка страницы может быть показана двумя способами.

  1. Способ model:

    this.props.action.page.showLoading();
    this.props.action.page.hideLoading();
  2. Способ 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>
    );
});

Все параметры модального компонента описаны ниже:

  • Если параметр является строкой, он используется как заголовок модального окна.
  • Если это функция, возвращаемое значение функции используется в качестве параметра модального режима.
  • Если это объект, он служит конфигурацией модального режима, см. antd Modal.
  • Параметр options.fullScreen по умолчанию равен false. Он определяет, будет ли модальное окно отображаться в полноэкранном режиме.

Компонент ModalContent

Содержимое всплывающего окна оборачивается компонентом 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. Поддерживаются два способа написания:

  1. Константный способ:
    export const PAGE_ROUTE = "/path";
  2. Использование декоратора config для страниц (рекомендуется):
    @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-запросы

AJAX-запросы в системе основаны на axios. На основе restful-соглашения предоставляются пять методов:

  • get — получение данных с сервера, параметры добавляются к URL и отправляются на сервер в виде строки запроса.
  • post — добавление данных, параметры отправляются на сервер в теле запроса.
  • put — изменение данных, параметры отправляются на сервер в теле запроса.
  • del — удаление данных, параметры добавляются к URL и отправляются на сервер в виде строки запроса.
  • patch — частичное изменение данных, параметры отправляются на сервер в теле запроса. Перевод текста на русский язык:
})
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.

Конфигурация options

Параметр Описание
Конфигурация axios Может принимать параметры axios
successTip Расширенный параметр, сообщение об успешном выполнении
errorTip Расширенный параметр, сообщение о неудачном выполнении
noEmpty Расширенный параметр, отфильтровывает пустые значения '', null, undefined и не передаёт их на сервер
originResponse Расширенный параметр, в .then можно получить полный response, а не только response.data

Примечание: глобальные параметры по умолчанию можно настроить в src/commons/ajax.js, по умолчанию baseURL='/api', timeout=1000 * 60.

Отображение результатов запроса

  1. Система автоматически отображает сообщения об ошибках ajax, разработчики могут настроить это через src/commons/handle-error.js;
  2. Сообщения об успешных запросах по умолчанию не отображаются, если они необходимы, можно настроить параметр successTip или обработать их в .then();
  3. Настройка сообщений об успешных запросах выполняется в src/commons/handle-success.js.

Пример:

this.props.ajax.del("/user/1", null, {
    successTip: "Удаление успешно!",
    errorTip: "Ошибка при удалении!",
    noEmpty: true,
});

Обработка loading

Система расширяет promise и предоставляет метод finally для выполнения операций независимо от успеха или неудачи запроса. Обычно используется для закрытия loading.

Пример:

this.setState({loading: true});
this.props.ajax
    .get('/url')
    .then(...)
    .finally(() => this.setState({loading: false}));

Mock-данные

Для параллельной разработки фронтенда и бэкенда, чтобы ускорить разработку бэкенда без ожидания интерфейса, система предоставляет функцию mock. Она основана на mockjs.

Написание mock-данных

В каталоге /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-запросы:

  • URL начинается с /mock/;
  • /src/mock/url-config.js настроенные запросы.
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 для реализации наложения стилей.

Написание темы

  • Используйте переменные, связанные с темой, в файлах less;
  • Напишите /src/theme.less, используя modifyVars less-loader для переопределения переменных less.

Примечание: в настоящее время каждый раз, когда вы изменяете theme.less, вам нужно перезапустить yarn start, чтобы изменения вступили в силу.

Ссылка

Навигационная компоновка

Для удовлетворения различных потребностей системы предоставляются четыре типа навигационной компоновки:

  • Меню заголовка;
  • Боковое меню;
  • Комбинация меню заголовка и бокового меню;
  • Вкладки.

Способ изменения

  • Пользователи могут выбрать макет через страницу настроек (если она у вас есть);
  • Разработчики могут указать способ макета через файл src/models/index.js.

Не требуется навигация

Некоторые страницы могут не требовать отображения навигации. Можно настроить следующим образом:

  • Настройка компонента высокого уровня на странице;
  • Добавление параметра noFrame=true в URL браузера.

Вкладки

На страницах заголовков есть следующие функции:

  1. После открытия новой вкладки после текущей вкладки;
  2. Запись и восстановление положения прокрутки;
  3. Сохранение состояния страницы (необходимо включить Keep Page Alive);
  4. Правое нажатие на вкладку;
  5. API операций с вкладками;
  6. Перетаскивание порядка вкладок;
  7. Попытка открыть родительскую страницу, если вторичная страница закрыта.

API операций с вкладками

В системе 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');
    }
    ...
}

Примечание:

  • Табы основаны на URL-адресах страниц. Каждый раз, когда используется this.props.history.push('/some/path'), выбирается или открывается новая вкладка (/path и /path?name=Tom соответствуют двум вкладкам).
  • Для страниц без меню необходимо отдельно установить заголовок, иначе вкладка не будет иметь заголовка.

Модели (redux) инкапсуляция

Инкапсулированный на основе redux, без изменения исходного кода redux, может использоваться в сочетании с другими решениями сообщества redux.

Примечание: обычно нет необходимости использовать redux~

Модели используются для управления данными и решают следующие проблемы:

  1. Пространство имён (предотвращение конфликтов имён данных и методов): данные и методы принадлежат конкретной модели, такой как: state.userCenter.xxx, this.props.action.userCenter.xxx();
  2. Как удобно получить данные: соединение с компонентом; @connect(state => ({name: state.user.name}));
  3. Как легко изменить данные: методы в this.props.action;
  4. Постоянное хранение клиентских данных (сохранение в LocalStorage): конфигурация syncStorage;
  5. Асинхронная обработка данных: асинхронная инкапсуляция на основе обещаний;
  6. Обработка ошибок запроса: автоматическая подсказка об ошибке через конфигурацию errorTip;
  7. Автоматическая подсказка об успешном выполнении запроса: через конфигурацию successTip;
  8. Упрощённое написание: типы действий редукторов могут быть записаны в одном файле, что снижает вероятность конфликтов и облегчает совместную работу нескольких человек. См. models/page.js для примера написания;
  9. Бизнес-код можно централизовать: бизнес-код может быть написан непосредственно в каталоге models или в конкретном бизнес-каталоге в модульном стиле.

src/models

Все модели определяются непосредственно в моделях или папках страниц:

Правила именования модулей моделей:

/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;

Компоненты и redux соединяются

Примечание: в тексте запроса присутствуют фрагменты кода на языке программирования 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-файлах

Без особых требований обычно не используется в обычных js-файлах.

import { action, store } from "src/models";

// Получение данных
const state = store.getState();

// Изменение данных
action.side.hide();

Упрощённое написание

Объединение action и reducer для упрощения написания;

Обратите внимание:

  • Все методы reducer, независимо от того, какой метод записи используется, могут напрямую возвращать новые данные, не беспокоясь о слиянии с исходными данными (…state), внутренняя реализация выполняет слияние;
  • В одном model, все атрибуты, кроме initialState syncStorage actions reducers и других ключевых слов, рассматриваются как объединённые методы action и reducer.

Одна функция

Одна функция может использоваться как метод action и как reduce.

  • Данные, передаваемые при вызове метода action, не будут обрабатываться и будут переданы непосредственно в reducer.
  • Можно получить только первый переданный параметр, и если необходимо передать несколько параметров, их необходимо передать в виде объекта. Если параметры не требуются, но необходимо использовать state, также необходимо определить фиктивный параметр.
  • Второй параметр функции — это state, а третий параметр — action. Эти два параметра можно опустить (обычно они опускаются).
  • Возвращаемое значение функции представляет собой объект или undefined, который объединяется с исходным state и становится новым state хранилища.
// 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 и reducer

Если у 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.

Пример конфигурации nginx

Здесь представлен только справочный файл, который можно настроить в соответствии с потребностями проекта.

Один домен соответствует одному проекту

Структура каталогов

.
├── /usr/local/nginx/html
│   ├── static
│   ├── index.html
│   └── favicon.ico

Конфигурация nginx

# Адрес службы бэкэнда
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

Структура каталога статических файлов nginx

.
├── /home/ubuntu/synext-admin
│   ├── build   // каталог статических файлов основного проекта
│   │   ├── static
│   │   ├── index.html
│   │   └── favicon.ico
│   ├── project1   // подкаталог статических файлов проекта с именем, соответствующим конфигурации location /project1 location ~ ^/project1/static/.*
│   │   ├── static
│   │   ├── index.html
│   │   └── favicon.ico

Конфигурация nginx

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 — не отображается при печати.

ESLint

Если проект не является корневым каталогом git, при фиксации будет выдана ошибка Not a git repository.

Чтобы исправить это, измените package.json и lint-staged следующим образом:

"lint-staged": {
    "gitDir": "../",
    "linters": {
        "**/*.{js,jsx}": "lint-staged:js",
        "**/*.less": "stylelint --syntax less"
    }
},

Webpack

Использование alias {'@': '/path/to/src', src:'/path/to/src'}

  • config/webpack.config.js;
  • упрощает написание путей, не нужно заботиться о структуре относительных путей;
  • копирование и вставка в другие файлы без необходимости изменения пути.

Операторы условного присваивания

const name = res?.data?.user?.name || "匿名";

Формы

  1. FormElement: типы включают:
'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 )

Вы можете оставить комментарий после Вход в систему

Введение

**Открытая платформа для торговли NFT.** Расширить Свернуть
JavaScript и 4 других языков
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://gitlife.ru/oschina-mirror/shengjian-tech-opennft-front.git
git@gitlife.ru:oschina-mirror/shengjian-tech-opennft-front.git
oschina-mirror
shengjian-tech-opennft-front
shengjian-tech-opennft-front
main