├── src
├── components
│ ├── Header.jsx
│ └── indexCard.jsx
├── assets
│ ├── yay.jpg
│ └── css
│ │ ├── login.less
│ │ └── home.less
├── pages
│ ├── docs.tsx
│ ├── about.jsx
│ ├── my.jsx
│ ├── index.tsx
│ ├── login.jsx
│ └── home.jsx
├── routes
│ └── index.js
├── layouts
│ ├── index.less
│ └── index.jsx
├── models
│ └── user.js
├── api
│ └── user.js
└── utils
│ └── request.js
├── typings.d.ts
├── .npmrc
├── tsconfig.json
├── README.md
├── .gitignore
├── .umirc.prod.js
├── .umirc.test.js
├── .umirc.uat.js
├── mock
└── user.ts
├── .umirc.js
└── package.json
/src/components/Header.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/indexCard.jsx:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | import 'umi/typings';
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://mirrors.huaweicloud.com/repository/npm/
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./src/.umi/tsconfig.json"
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/yay.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lakyia/react-h5-template/HEAD/src/assets/yay.jpg
--------------------------------------------------------------------------------
/src/assets/css/login.less:
--------------------------------------------------------------------------------
1 | .loginContainer {
2 | position: relative;
3 | background: #fff;
4 | height: 100vh;
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-h5-template
2 | ## 基于 react18 + umi4 + antd mobile 5 + less + dva + mock + viewport 适配方案 + axios 封装 + 多环境配置,构建手机端模板,react移动脚手架
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /.env.local
3 | /.umirc.local.ts
4 | /config/config.local.ts
5 | /src/.umi
6 | /src/.umi-production
7 | /src/.umi-test
8 | /dist
9 | .DS_Store
--------------------------------------------------------------------------------
/src/pages/docs.tsx:
--------------------------------------------------------------------------------
1 | const DocsPage = () => {
2 | return (
3 |
4 |
This is umi docs.
5 |
6 | );
7 | };
8 |
9 | export default DocsPage;
10 |
--------------------------------------------------------------------------------
/.umirc.prod.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "umi";
2 |
3 | export default defineConfig({
4 | define: {
5 | baseApi: "https://api.somedomain.com",
6 | "process.env": {
7 | NODE_ENV: "uat",
8 | UMI_ENV: "uat",
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/.umirc.test.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "umi";
2 |
3 | export default defineConfig({
4 | define: {
5 | baseApi: "https://apitest.somedomain.com",
6 | "process.env": {
7 | NODE_ENV: "uat",
8 | UMI_ENV: "uat",
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/.umirc.uat.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "umi";
2 |
3 | export default defineConfig({
4 | define: {
5 | baseApi: "https://apiuat.somedomain.com",
6 | "process.env": {
7 | NODE_ENV: "uat",
8 | UMI_ENV: "uat",
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/src/pages/about.jsx:
--------------------------------------------------------------------------------
1 | export default function AboutPage() {
2 | return (
3 |
4 |
This is About page
5 |
6 | To get started, edit pages/about.tsx and save to reload.
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/mock/user.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | "/phu/captcha/getCaptcha": {
3 | data: {
4 | captcha: "base64.......",
5 | captchaToken: "token....",
6 | },
7 | code: 200,
8 | msg: "成功",
9 | },
10 | // 返回值也可以是对象形式
11 | "/api/users/1": { id: 1, name: "foo" },
12 | };
13 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | export default {
2 | routes: [
3 | { path: "/login", component: "login" },
4 | {
5 | path: "/",
6 | component: "@/layouts/index",
7 | routes: [
8 | { path: "/home", component: "home" },
9 | { path: "/my", component: "my" },
10 | { path: "/about", component: "about" },
11 | ],
12 | },
13 | ],
14 | };
15 |
--------------------------------------------------------------------------------
/src/pages/my.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from "umi";
2 |
3 | function MyPage(props) {
4 | const { user, dispatch } = props;
5 | console.log(user);
6 | const val = user.user.test;
7 | return (
8 |
11 | );
12 | }
13 | const stateToProps = ({ user }) => {
14 | return {
15 | user,
16 | };
17 | };
18 | export default connect(stateToProps)(MyPage);
19 |
--------------------------------------------------------------------------------
/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import yayJpg from "../assets/yay.jpg";
2 |
3 | export default function HomePage() {
4 | return (
5 |
6 |
Yay! Welcome to umi!
7 |
8 |
9 |
10 |
11 | To get started, edit pages/index.tsx and save to reload.
12 |
13 |
去首页看看。
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/.umirc.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "umi";
2 | const pxtorem = require("postcss-pxtorem");
3 |
4 | export default defineConfig({
5 | npmClient: "npm",
6 | extraPostCSSPlugins: [
7 | pxtorem({
8 | rootValue: 75, //这里根据设计稿大小配置,一般是375
9 | propList: ["*"],
10 | }),
11 | ],
12 | define: {
13 | baseApi: "https://apidev.somedomain.com",
14 | "process.env": {
15 | NODE_ENV: "dev",
16 | UMI_ENV: "dev",
17 | },
18 | },
19 | plugins: ["@umijs/plugins/dist/dva"],
20 | dva: {},
21 | });
22 |
--------------------------------------------------------------------------------
/src/layouts/index.less:
--------------------------------------------------------------------------------
1 | .app {
2 | height: 100vh;
3 | display: flex;
4 | flex-direction: column;
5 | }
6 |
7 | .top {
8 | flex: 0;
9 | border-bottom: solid 1px var(--adm-color-border);
10 | }
11 |
12 | .bottom {
13 | z-index: 99999;
14 | position: fixed;
15 | width: 100%;
16 | bottom: 0;
17 | height: fit-content;
18 | background-color: white;
19 | border-top: solid 1px var(--adm-color-border);
20 | .tabbar {
21 | padding: 10px 0;
22 | font-size: 24px;
23 | height: 110px;
24 | .tabbarItem {
25 | height: 110px;
26 | justify-content: space-around;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/models/user.js:
--------------------------------------------------------------------------------
1 | // import { queryUsers, queryUser } from "../../services/user";
2 |
3 | export default {
4 | state: {
5 | user: {
6 | test: "test123",
7 | },
8 | },
9 |
10 | effects: {
11 | *queryUser({ payload }, { call, put }) {
12 | // const { data } = yield call(queryUser, payload);
13 | // yield put({ type: "queryUserSuccess", payload: data });
14 | },
15 | },
16 |
17 | reducers: {
18 | queryUserSuccess(state, { payload }) {
19 | return {
20 | ...state,
21 | user: payload,
22 | };
23 | },
24 | },
25 |
26 | test(state) {
27 | console.log("test");
28 | return state;
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "author": "Lakshya",
4 | "scripts": {
5 | "build": "umi build",
6 | "dev": "umi dev",
7 | "postinstall": "umi setup",
8 | "setup": "umi setup",
9 | "start": "npm run dev",
10 | "start:uat": "cross-env UMI_ENV=uat umi dev"
11 | },
12 | "dependencies": {
13 | "antd-mobile": "^5.24.1",
14 | "axios": "^1.1.3",
15 | "dva": "^2.4.1",
16 | "lib-flexible": "^0.3.2",
17 | "normalize.css": "^8.0.1",
18 | "postcss-pxtorem": "^6.0.0",
19 | "redux": "^4.2.0",
20 | "umi": "^4.0.25"
21 | },
22 | "devDependencies": {
23 | "@types/react": "^18.0.0",
24 | "@types/react-dom": "^18.0.0",
25 | "@umijs/plugins": "^4.0.26",
26 | "cross-env": "^7.0.3",
27 | "typescript": "^4.1.2"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/api/user.js:
--------------------------------------------------------------------------------
1 | // axios
2 | import request from "@/utils/request";
3 |
4 | //################### #使用mock时不设置baseURL ####################//
5 | import axios from "axios";
6 | // create an axios instance
7 | const service = axios.create({
8 | //baseURL: baseApi, // url = base api url + request url 如果使用 mock 去掉此项
9 | withCredentials: false, // send cookies when cross-domain requests
10 | crossDomain: true,
11 | timeout: 10000, // request timeout
12 | });
13 | export function getCaptchaMock(params) {
14 | return service({
15 | url: "/user/captcha/getCaptcha",
16 | withCredentials: false,
17 | method: "get",
18 | params,
19 | });
20 | }
21 | //################### #使用mock时不设置baseURL ####################//
22 |
23 | export function getCaptcha(params) {
24 | return request({
25 | url: "/user/captcha/getCaptcha",
26 | withCredentials: false,
27 | method: "get",
28 | params,
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/src/assets/css/home.less:
--------------------------------------------------------------------------------
1 | .indexContainer {
2 | overflow-x: hidden;
3 | padding-bottom: 125px;
4 | .indexHeader {
5 | display: flex;
6 | height: 100px;
7 | justify-content: space-between;
8 | align-items: center;
9 | padding: 32px 4.3vw 16px;
10 | position: fixed;
11 | width: 100%;
12 | background-color: #f5f5f5;
13 | }
14 |
15 | .indexBody {
16 | padding-top: 150px;
17 | .indexTop {
18 | margin: 32px;
19 | font-size: 32px;
20 | .desc {
21 | }
22 | .btn {
23 | margin: 32px 0;
24 | background-color: chocolate;
25 | color: #fff;
26 | }
27 | }
28 | .swiper {
29 | margin: 16px 32px;
30 | width: calc(100vw - 64px);
31 | .swiperItem {
32 | height: 175px;
33 | }
34 | .customIndicator {
35 | display: flex;
36 | justify-content: center;
37 | border-radius: 4px;
38 | }
39 | .indicator {
40 | width: 40px;
41 | height: 8px;
42 | background-color: #fff;
43 | }
44 | .activeIndicator {
45 | border-radius: 4px;
46 | background-color: rgb(255, 88, 20);
47 | }
48 | }
49 | .banner {
50 | border-radius: 20px;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/pages/login.jsx:
--------------------------------------------------------------------------------
1 | import { Checkbox, Input, Button, NavBar } from "antd-mobile";
2 | import { useState } from "react";
3 | import { getCaptcha, getCaptchaMock } from "@/api/user.js";
4 | import { useNavigate } from "react-router-dom";
5 | import styles from "@/assets/css/login.less";
6 | export default function LoginPage() {
7 | const getCaptchaFunc = () => {
8 | getCaptcha();
9 | getCaptchaMock();
10 | };
11 | const navigate = useNavigate();
12 | return (
13 | <>
14 | navigate(-1)}
20 | >
21 | 标题
22 |
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
40 | >
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/src/utils/request.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | // create an axios instance
3 | const service = axios.create({
4 | baseURL: baseApi, // url = base api url + request url 如果使用 mock 去掉此项
5 | withCredentials: false,
6 | crossDomain: true,
7 | timeout: 10000, // request timeout
8 | });
9 | // request拦截器 request interceptor
10 | service.interceptors.request.use(
11 | (config) => {
12 | // 设置用户token
13 | return config;
14 | },
15 | (error) => {
16 | // do something with request error
17 | console.log(error); // for debug
18 | return Promise.reject(error);
19 | }
20 | );
21 | // respone拦截器
22 | service.interceptors.response.use(
23 | (response) => {
24 | const res = response.data;
25 | console.log("response-->", res);
26 | if (res.status && res.status !== 200) {
27 | } else {
28 | if (res.code === 200 || res.message === "SUCCESS") {
29 | return Promise.resolve(res);
30 | } else if (res.code === 305 || res.code === 306) {
31 | Promise.reject(res || "error");
32 | return;
33 | } else {
34 | return Promise.reject(res || "error");
35 | }
36 | }
37 | },
38 | (error) => {
39 | console.log("request error" + error, typeof error); // for debug
40 | return Promise.reject(error);
41 | }
42 | );
43 |
44 | export default service;
45 |
--------------------------------------------------------------------------------
/src/pages/home.jsx:
--------------------------------------------------------------------------------
1 | import { PullToRefresh, Button, Swiper, Toast } from "antd-mobile";
2 | import { useNavigate } from "react-router-dom";
3 | import styles from "@/assets/css/home.less";
4 | const colors = ["#ace0ff", "#bcffbd", "#e4fabd", "#ffcfac"];
5 |
6 | const items = colors.map((color, index) => (
7 |
8 | {
12 | Toast.show(`你点击了卡片 ${index + 1}`);
13 | }}
14 | >
15 | {index + 1}
16 |
17 |
18 | ));
19 | const Home = () => {
20 | const navigate = useNavigate();
21 |
22 | return (
23 |
24 |
首页Header
25 |
26 |
{
28 | console.log("PullToRefresh");
29 | }}
30 | >
31 |
32 |
33 | {"一个基于umi4 antd mobile5 的react移动端脚手架;"}
34 |
35 |
{"含dva,mock,多环境配置等;"}
36 |
49 |
50 | (
56 |
57 | {Array(total)
58 | .fill(1)
59 | .map((el, i) => (
60 |
66 | ))}
67 |
68 | )}
69 | loop
70 | >
71 | {items}
72 |
73 |
74 |
75 |
76 | );
77 | };
78 |
79 | export default Home;
80 |
--------------------------------------------------------------------------------
/src/layouts/index.jsx:
--------------------------------------------------------------------------------
1 | import { Outlet } from "umi";
2 | import { useNavigate, matchRoutes, useLocation } from "react-router-dom";
3 | import { useEffect, useState } from "react";
4 | import router from "@/routes";
5 | import "lib-flexible";
6 | import { TabBar, SafeArea } from "antd-mobile";
7 | import { AppOutline, HeartOutline, UserOutline } from "antd-mobile-icons";
8 | import "normalize.css/normalize.css"; //全局引入
9 | import styles from "./index.less";
10 | export default function Layout() {
11 | const location = useLocation();
12 | const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]);
13 | const [defaultOpenKeys, setDefaultOpenKeys] = useState([]);
14 | const [isInit, setIsInit] = useState(false);
15 | const navigate = useNavigate();
16 | const { pathname } = location;
17 | useEffect(() => {
18 | console.log(router, location.pathname);
19 | const routes = matchRoutes(router.routes, location.pathname); // 返回匹配到的路由数组对象,每一个对象都是一个路由对象
20 | const pathArr = [];
21 | if (routes !== null) {
22 | routes.forEach((item) => {
23 | const path = item.route.path;
24 | if (path) {
25 | pathArr.push(path);
26 | }
27 | });
28 | }
29 | setDefaultSelectedKeys(pathArr);
30 | setDefaultOpenKeys(pathArr);
31 | setIsInit(true);
32 | }, [location.pathname]);
33 | if (!isInit) {
34 | return null;
35 | }
36 |
37 | const setRouteActive = (value) => {
38 | navigate(value);
39 | };
40 |
41 | const tabs = [
42 | {
43 | key: "/home",
44 | title: "首页",
45 | icon: ,
46 | },
47 | {
48 | key: "/about",
49 | title: "关于",
50 | icon: ,
51 | },
52 |
53 | {
54 | key: "/my",
55 | title: "我的",
56 | icon: ,
57 | },
58 | ];
59 | return (
60 | <>
61 |
62 |
63 |
64 |
65 | {["/home", "/about", "/my"].includes(pathname) && (
66 |
67 | setRouteActive(value)}
72 | >
73 | {tabs.map((item) => (
74 |
80 | ))}
81 |
82 |
83 |
84 | )}
85 | >
86 | );
87 | }
88 |
--------------------------------------------------------------------------------